feat(proto): org-management comms + spawn arms for agent gateway (RIG-2673) - #628
Conversation
|
Compass engineering docs preview: https://compass-server-rig-2724-org.compass-eng-docs.pages.dev Deployed from |
60728dd to
836c472
Compare
…h, spawn role/persona (RIG-2673) Wires the compass-server half of the org-management agent tools (RIG-2673 T2–T4) on top of the T1 proto arms (#628): the three comms adapters, their hub dispatch, and the spawn role/persona thread-through. Every operation flows through the existing agent→Runner→Server relay under the server-resolved account, so the authz, idempotency, and event fan-out are identical to a human caller's. ### T2 — comms adapters (`go/internal/comms/agent_caller.go`) `CreateChannelAsAccount`, `UpdateChannelMembersAsAccount`, `CreateChannelGroupAsAccount` — each mirrors `UpdatePinnedBoardAsAccount`: guard empty account → `errNoActor`, then `WithActor` + the shared handler path, returning `resp.Msg`. No home-channel defaulting (create-channel names no channel; update-members carries `channel_id` explicitly; create-group names only an optional parent). Founding membership rides `CreateChannel`'s `expandOwnerMembership`, so a Manager can immediately post to the channel it just made. ### T3 — hub dispatch (`go/internal/runnerhub/hub.go`, `relay_comms.go`) Extended the `CommsCaller` interface with the three methods and added three `executeCall` cases on the `Pin` pattern, each wrapping the matching `CommsCallResult` oneof. The default-case error string's variant list now includes the three new arms. A tool error renders in-band as `CommsCallError`, never a stream teardown. ### T4 — spawn role/persona thread-through (`go/server/lifecycle.go`) Dropped the `Persona: ""` / `Role: ""` hardcodes in the `store.CreateAgent` literal for `req.GetPersona()` / `req.GetRole()`. Under the D9 owner-acts model the caller's owner is the authority, so a Manager-creating spawn legitimately carries role+persona set-at-creation; the values are stored via `CreateAgent` (the source of record) and threaded to the Runner from the created store account — an empty-field spawn is byte-identical to today's field-less spawn. ### Tests (red-green) - T2 pgtests (`org_mgmt_pgtest_test.go`): founder-membership + `ChannelChanged` fan-out, member-add, group-create; each with empty-account → `errNoActor` and an invisible/non-member target → `CodeNotFound` (the D9 collapse a human gets). - T3 relay tests (`relay_org_mgmt_test.go`): each arm forwards under the bound account with `call_id` round-trip; a create-channel tool error is in-band, not a transport teardown. - T4 spawn pgtests (`spawn_role_persona_pgtest_test.go`): role and persona each stored + on the Provision wire; empty-field spawn stores + wires empty (wire-level regression); idempotent re-spawn keeps stored values. Verified: `go build ./...`, `gofmt`, `compass-go:lint` (0 issues), and the affected `internal/comms` / `internal/runnerhub` / `server` suites all green (pgtests via the throwaway-container path). Refs RIG-2724, RIG-2673. Co-authored-by: Matt Wilkinson <matt@rigel.build>
…h, spawn role/persona (RIG-2673) Wires the compass-server half of the org-management agent tools (RIG-2673 T2–T4) on top of the T1 proto arms (#628): the three comms adapters, their hub dispatch, and the spawn role/persona thread-through. Every operation flows through the existing agent→Runner→Server relay under the server-resolved account, so the authz, idempotency, and event fan-out are identical to a human caller's. ### T2 — comms adapters (`go/internal/comms/agent_caller.go`) `CreateChannelAsAccount`, `UpdateChannelMembersAsAccount`, `CreateChannelGroupAsAccount` — each mirrors `UpdatePinnedBoardAsAccount`: guard empty account → `errNoActor`, then `WithActor` + the shared handler path, returning `resp.Msg`. No home-channel defaulting (create-channel names no channel; update-members carries `channel_id` explicitly; create-group names only an optional parent). Founding membership rides `CreateChannel`'s `expandOwnerMembership`, so a Manager can immediately post to the channel it just made. ### T3 — hub dispatch (`go/internal/runnerhub/hub.go`, `relay_comms.go`) Extended the `CommsCaller` interface with the three methods and added three `executeCall` cases on the `Pin` pattern, each wrapping the matching `CommsCallResult` oneof. The default-case error string's variant list now includes the three new arms. A tool error renders in-band as `CommsCallError`, never a stream teardown. ### T4 — spawn role/persona thread-through (`go/server/lifecycle.go`) Dropped the `Persona: ""` / `Role: ""` hardcodes in the `store.CreateAgent` literal for `req.GetPersona()` / `req.GetRole()`. Under the D9 owner-acts model the caller's owner is the authority, so a Manager-creating spawn legitimately carries role+persona set-at-creation; the values are stored via `CreateAgent` (the source of record) and threaded to the Runner from the created store account — an empty-field spawn is byte-identical to today's field-less spawn. ### Tests (red-green) - T2 pgtests (`org_mgmt_pgtest_test.go`): founder-membership + `ChannelChanged` fan-out, member-add, group-create; each with empty-account → `errNoActor` and an invisible/non-member target → `CodeNotFound` (the D9 collapse a human gets). - T3 relay tests (`relay_org_mgmt_test.go`): each arm forwards under the bound account with `call_id` round-trip; a create-channel tool error is in-band, not a transport teardown. - T4 spawn pgtests (`spawn_role_persona_pgtest_test.go`): role and persona each stored + on the Provision wire; empty-field spawn stores + wires empty (wire-level regression); idempotent re-spawn keeps stored values. Verified: `go build ./...`, `gofmt`, `compass-go:lint` (0 issues), and the affected `internal/comms` / `internal/runnerhub` / `server` suites all green (pgtests via the throwaway-container path). Refs RIG-2724, RIG-2673. Co-authored-by: Matt Wilkinson <matt@rigel.build>
|
Holding this (converted to draft) — Matt ruled RIG-2751 contract-wide: no agent or client UI ever resolves account ids; every request field takes a These arms get re-authored handle-first as part of the RIG-2751 cutover (design record → Matt freeze → one coherent proto change across comms.proto + agent_gateway.proto). #630's handlers rebase onto the reshaped proto and do the server-side handle→id resolution. Un-drafting once RIG-2751 reshapes + rebases them. Ref: RIG-2751 |
Design record for the contract-wide id→handle cutover of the compass request surface (RIG-2751), per Matt's Option-A ruling: every request-input account field takes a `@handle`; the server resolves it to an account id exactly as the existing `from_handle` field does; no agent or client UI ever resolves an id; an unresolved handle surfaces as an in-band NOT_FOUND. ## Scope Field inventory verified complete against the current tree (all 7 proto files grepped): the request-input account fields across `comms.proto` (CreateChannel members, UpdateChannelMembers add/remove/subscribe/unsubscribe, ReparentAgent agent+new-parent, SetChannelPolicy owner, GetRoster vantage, OpenAgentWorkspace, CreateAgent parent) and `agent_gateway.proto` (DespawnPeer), plus the three new `CommsCallRequest` arms held in draft on #628 (re-authored handle-first here). `agents_spawn_peer` already takes a handle — the precedent this mirrors. Explicit non-goal (a decided boundary, not a reflex flip): response/stored/event fields keep ids — handing a handle back on a response pushes resolution onto the client, the opposite of the ruling. The one exception is surfaced in OQ-5. ## Design-critic pass folded Red-teamed by `CritiqueHandleCutover`; the architecture (edge-not-store resolution, in-band NOT_FOUND, dual-vantage-by-emptiness, T1-first sequencing) survived. Seven oracle-safety and grounding improvements folded: a per-handler NOT_FOUND remap table (two handlers today leak an unknown-vs-foreign oracle via PermissionDenied); T4's ordering preserves the lifecycle constant-query-shape bar; GetRoster's invisible-vantage posture is now defined, not inherited; the agent ListMessages author-id fence named as the concrete non-goal exception; error names all unresolved handles; a bounded skew window stated; and two load-bearing grounding corrections (the store returns ErrInvalidArgument, not NOT_FOUND, for unknown members). ## Open questions (freeze-gate decisions for Matt) - **OQ-1** — field naming (taste). - **OQ-1b** — field numbering: a CONFIRM of DL-186's rename-in-place, not an open fork. - **OQ-2** — repeated-field NOT_FOUND: a CONFIRM of atomic-failure, error naming all unresolved handles. - **OQ-3** — GetRoster vantage field name (taste). - **OQ-4** — compass.proto admin lane: extend the ruling to the 3 admin fields or keep ids (real scope fork). - **OQ-5** — response handles: ship the DL-NEW-2 additive `author_handle` sibling with this cutover (the agent's ListMessages author-id fence is the identified surface) or defer. - **OQ-6 (NEW, load-bearing)** — member-resolution visibility scoping: (a) viewer-scoped `AccountsByHandles` [rec — the only option consistent with the oracle posture] vs (b) unscoped; T2's resolver signature depends on this ruling. ## Ledger-impact DL-NEW-1 (handle-typed request inputs, server-edge resolution, one indistinguishable in-band NOT_FOUND), DL-NEW-2 (responses/stored/events keep ids; display handles only as additive sibling fields). No existing DL row constrains request addressing (DL-094/186/188/191/202 checked; DL-186 rules the rename-in-place field-numbering). Next-free ids assigned at freeze. Ref: RIG-2751 Co-authored-by: Matt Wilkinson <matt@rigel.build>
Design record for the contract-wide id→handle cutover of the compass request surface (RIG-2751), per Matt's Option-A ruling: every request-input account field takes a `@handle`; the server resolves it to an account id exactly as the existing `from_handle` field does; no agent or client UI ever resolves an id; an unresolved handle surfaces as an in-band NOT_FOUND. ## Scope Field inventory verified complete against the current tree (all 7 proto files grepped): the request-input account fields across `comms.proto` (CreateChannel members, UpdateChannelMembers add/remove/subscribe/unsubscribe, ReparentAgent agent+new-parent, SetChannelPolicy owner, GetRoster vantage, OpenAgentWorkspace, CreateAgent parent) and `agent_gateway.proto` (DespawnPeer), plus the three new `CommsCallRequest` arms held in draft on #628 (re-authored handle-first here). `agents_spawn_peer` already takes a handle — the precedent this mirrors. Explicit non-goal (a decided boundary, not a reflex flip): response/stored/event fields keep ids — handing a handle back on a response pushes resolution onto the client, the opposite of the ruling. The one exception is surfaced in OQ-5. ## Design-critic pass folded Red-teamed by `CritiqueHandleCutover`; the architecture (edge-not-store resolution, in-band NOT_FOUND, dual-vantage-by-emptiness, T1-first sequencing) survived. Seven oracle-safety and grounding improvements folded: a per-handler NOT_FOUND remap table (two handlers today leak an unknown-vs-foreign oracle via PermissionDenied); T4's ordering preserves the lifecycle constant-query-shape bar; GetRoster's invisible-vantage posture is now defined, not inherited; the agent ListMessages author-id fence named as the concrete non-goal exception; error names all unresolved handles; a bounded skew window stated; and two load-bearing grounding corrections (the store returns ErrInvalidArgument, not NOT_FOUND, for unknown members). ## Open questions (freeze-gate decisions for Matt) - **OQ-1** — field naming (taste). - **OQ-1b** — field numbering: a CONFIRM of DL-186's rename-in-place, not an open fork. - **OQ-2** — repeated-field NOT_FOUND: a CONFIRM of atomic-failure, error naming all unresolved handles. - **OQ-3** — GetRoster vantage field name (taste). - **OQ-4** — compass.proto admin lane: extend the ruling to the 3 admin fields or keep ids (real scope fork). - **OQ-5** — response handles: ship the DL-NEW-2 additive `author_handle` sibling with this cutover (the agent's ListMessages author-id fence is the identified surface) or defer. - **OQ-6 (NEW, load-bearing)** — member-resolution visibility scoping: (a) viewer-scoped `AccountsByHandles` [rec — the only option consistent with the oracle posture] vs (b) unscoped; T2's resolver signature depends on this ruling. ## Ledger-impact DL-NEW-1 (handle-typed request inputs, server-edge resolution, one indistinguishable in-band NOT_FOUND), DL-NEW-2 (responses/stored/events keep ids; display handles only as additive sibling fields). No existing DL row constrains request addressing (DL-094/186/188/191/202 checked; DL-186 rules the rename-in-place field-numbering). Next-free ids assigned at freeze. Ref: RIG-2751 Co-authored-by: Matt Wilkinson <matt@rigel.build>
Design record for the contract-wide id→handle cutover of the compass request surface (RIG-2751), per Matt's Option-A ruling: every request-input account field takes a `@handle`; the server resolves it to an account id exactly as the existing `from_handle` field does; no agent or client UI ever resolves an id; an unresolved handle surfaces as an in-band NOT_FOUND. ## Scope Field inventory verified complete against the current tree (all 7 proto files grepped): the request-input account fields across `comms.proto` (CreateChannel members, UpdateChannelMembers add/remove/subscribe/unsubscribe, ReparentAgent agent+new-parent, SetChannelPolicy owner, GetRoster vantage, OpenAgentWorkspace, CreateAgent parent) and `agent_gateway.proto` (DespawnPeer), plus the three new `CommsCallRequest` arms held in draft on #628 (re-authored handle-first here). `agents_spawn_peer` already takes a handle — the precedent this mirrors. Explicit non-goal (a decided boundary, not a reflex flip): response/stored/event fields keep ids — handing a handle back on a response pushes resolution onto the client, the opposite of the ruling. The one exception is surfaced in OQ-5. ## Design-critic pass folded Red-teamed by `CritiqueHandleCutover`; the architecture (edge-not-store resolution, in-band NOT_FOUND, dual-vantage-by-emptiness, T1-first sequencing) survived. Seven oracle-safety and grounding improvements folded: a per-handler NOT_FOUND remap table (two handlers today leak an unknown-vs-foreign oracle via PermissionDenied); T4's ordering preserves the lifecycle constant-query-shape bar; GetRoster's invisible-vantage posture is now defined, not inherited; the agent ListMessages author-id fence named as the concrete non-goal exception; error names all unresolved handles; a bounded skew window stated; and two load-bearing grounding corrections (the store returns ErrInvalidArgument, not NOT_FOUND, for unknown members). ## Open questions (freeze-gate decisions for Matt) - **OQ-1** — field naming (taste). - **OQ-1b** — field numbering: a CONFIRM of DL-186's rename-in-place, not an open fork. - **OQ-2** — repeated-field NOT_FOUND: a CONFIRM of atomic-failure, error naming all unresolved handles. - **OQ-3** — GetRoster vantage field name (taste). - **OQ-4** — compass.proto admin lane: extend the ruling to the 3 admin fields or keep ids (real scope fork). - **OQ-5** — response handles: ship the DL-NEW-2 additive `author_handle` sibling with this cutover (the agent's ListMessages author-id fence is the identified surface) or defer. - **OQ-6 (NEW, load-bearing)** — member-resolution visibility scoping: (a) viewer-scoped `AccountsByHandles` [rec — the only option consistent with the oracle posture] vs (b) unscoped; T2's resolver signature depends on this ruling. ## Ledger-impact DL-NEW-1 (handle-typed request inputs, server-edge resolution, one indistinguishable in-band NOT_FOUND), DL-NEW-2 (responses/stored/events keep ids; display handles only as additive sibling fields). No existing DL row constrains request addressing (DL-094/186/188/191/202 checked; DL-186 rules the rename-in-place field-numbering). Next-free ids assigned at freeze. Ref: RIG-2751 Co-authored-by: Matt Wilkinson <matt@rigel.build>
Design record for the contract-wide id→handle cutover of the compass request surface (RIG-2751), per Matt's Option-A ruling: every request-input account field takes a `@handle`; the server resolves it to an account id exactly as the existing `from_handle` field does; no agent or client UI ever resolves an id; an unresolved handle surfaces as an in-band NOT_FOUND. ## Scope Field inventory verified complete against the current tree (all 7 proto files grepped): the request-input account fields across `comms.proto` (CreateChannel members, UpdateChannelMembers add/remove/subscribe/unsubscribe, ReparentAgent agent+new-parent, SetChannelPolicy owner, GetRoster vantage, OpenAgentWorkspace, CreateAgent parent) and `agent_gateway.proto` (DespawnPeer), plus the three new `CommsCallRequest` arms held in draft on #628 (re-authored handle-first here). `agents_spawn_peer` already takes a handle — the precedent this mirrors. Explicit non-goal (a decided boundary, not a reflex flip): response/stored/event fields keep ids — handing a handle back on a response pushes resolution onto the client, the opposite of the ruling. The one exception is surfaced in OQ-5. ## Design-critic pass folded Red-teamed by `CritiqueHandleCutover`; the architecture (edge-not-store resolution, in-band NOT_FOUND, dual-vantage-by-emptiness, T1-first sequencing) survived. Seven oracle-safety and grounding improvements folded: a per-handler NOT_FOUND remap table (two handlers today leak an unknown-vs-foreign oracle via PermissionDenied); T4's ordering preserves the lifecycle constant-query-shape bar; GetRoster's invisible-vantage posture is now defined, not inherited; the agent ListMessages author-id fence named as the concrete non-goal exception; error names all unresolved handles; a bounded skew window stated; and two load-bearing grounding corrections (the store returns ErrInvalidArgument, not NOT_FOUND, for unknown members). ## Open questions (freeze-gate decisions for Matt) - **OQ-1** — field naming (taste). - **OQ-1b** — field numbering: a CONFIRM of DL-186's rename-in-place, not an open fork. - **OQ-2** — repeated-field NOT_FOUND: a CONFIRM of atomic-failure, error naming all unresolved handles. - **OQ-3** — GetRoster vantage field name (taste). - **OQ-4** — compass.proto admin lane: extend the ruling to the 3 admin fields or keep ids (real scope fork). - **OQ-5** — response handles: ship the DL-NEW-2 additive `author_handle` sibling with this cutover (the agent's ListMessages author-id fence is the identified surface) or defer. - **OQ-6 (NEW, load-bearing)** — member-resolution visibility scoping: (a) viewer-scoped `AccountsByHandles` [rec — the only option consistent with the oracle posture] vs (b) unscoped; T2's resolver signature depends on this ruling. ## Ledger-impact DL-NEW-1 (handle-typed request inputs, server-edge resolution, one indistinguishable in-band NOT_FOUND), DL-NEW-2 (responses/stored/events keep ids; display handles only as additive sibling fields). No existing DL row constrains request addressing (DL-094/186/188/191/202 checked; DL-186 rules the rename-in-place field-numbering). Next-free ids assigned at freeze. Ref: RIG-2751 Co-authored-by: Matt Wilkinson <matt@rigel.build>
…51) (#645) * docs(product): design contract-wide id→handle request cutover (RIG-2751) Design record for the contract-wide id→handle cutover of the compass request surface (RIG-2751), per Matt's Option-A ruling: every request-input account field takes a `@handle`; the server resolves it to an account id exactly as the existing `from_handle` field does; no agent or client UI ever resolves an id; an unresolved handle surfaces as an in-band NOT_FOUND. ## Scope Field inventory verified complete against the current tree (all 7 proto files grepped): the request-input account fields across `comms.proto` (CreateChannel members, UpdateChannelMembers add/remove/subscribe/unsubscribe, ReparentAgent agent+new-parent, SetChannelPolicy owner, GetRoster vantage, OpenAgentWorkspace, CreateAgent parent) and `agent_gateway.proto` (DespawnPeer), plus the three new `CommsCallRequest` arms held in draft on #628 (re-authored handle-first here). `agents_spawn_peer` already takes a handle — the precedent this mirrors. Explicit non-goal (a decided boundary, not a reflex flip): response/stored/event fields keep ids — handing a handle back on a response pushes resolution onto the client, the opposite of the ruling. The one exception is surfaced in OQ-5. ## Design-critic pass folded Red-teamed by `CritiqueHandleCutover`; the architecture (edge-not-store resolution, in-band NOT_FOUND, dual-vantage-by-emptiness, T1-first sequencing) survived. Seven oracle-safety and grounding improvements folded: a per-handler NOT_FOUND remap table (two handlers today leak an unknown-vs-foreign oracle via PermissionDenied); T4's ordering preserves the lifecycle constant-query-shape bar; GetRoster's invisible-vantage posture is now defined, not inherited; the agent ListMessages author-id fence named as the concrete non-goal exception; error names all unresolved handles; a bounded skew window stated; and two load-bearing grounding corrections (the store returns ErrInvalidArgument, not NOT_FOUND, for unknown members). ## Open questions (freeze-gate decisions for Matt) - **OQ-1** — field naming (taste). - **OQ-1b** — field numbering: a CONFIRM of DL-186's rename-in-place, not an open fork. - **OQ-2** — repeated-field NOT_FOUND: a CONFIRM of atomic-failure, error naming all unresolved handles. - **OQ-3** — GetRoster vantage field name (taste). - **OQ-4** — compass.proto admin lane: extend the ruling to the 3 admin fields or keep ids (real scope fork). - **OQ-5** — response handles: ship the DL-NEW-2 additive `author_handle` sibling with this cutover (the agent's ListMessages author-id fence is the identified surface) or defer. - **OQ-6 (NEW, load-bearing)** — member-resolution visibility scoping: (a) viewer-scoped `AccountsByHandles` [rec — the only option consistent with the oracle posture] vs (b) unscoped; T2's resolver signature depends on this ruling. ## Ledger-impact DL-NEW-1 (handle-typed request inputs, server-edge resolution, one indistinguishable in-band NOT_FOUND), DL-NEW-2 (responses/stored/events keep ids; display handles only as additive sibling fields). No existing DL row constrains request addressing (DL-094/186/188/191/202 checked; DL-186 rules the rename-in-place field-numbering). Next-free ids assigned at freeze. Ref: RIG-2751 Co-authored-by: Matt Wilkinson <matt@rigel.build> * docs(product): fold ratified storage contract + owner-qualified wire into RIG-2751 cutover Matt ratified the RIG-2751 handles/id contract (2026-08-26, "LGTM, can start"). Fold the frozen contract into the cutover record, which previously designed only the wire flip against a flat global handle lookup: - New §"The storage contract": account_handles table + two partial-unique indexes (user/system handles globally unique; agent handles unique per owner), owner-qualified resolution (matt/compass-ux, bare = caller's own owner), in-place rename + reclaim allowed both tiers (no history/reservation), cross-human safety relocated to owner peering (RIG-2796). - Re-spec the edge resolvers for owner-qualified parsing and re-key AgentByHandle / AccountsByHandles onto account_handles. - New T0 migration task (0002_account_handles.sql + backfill + relax accounts.handle global-unique); T2 resolver signature carries callerOwner + parsed QualifiedHandle; T4 despawn handle owner-qualified. - Ledger rows assigned concrete numbers (DL-269 request handle-typed / DL-270 responses keep ids / DL-271 owner-namespaced storage; next-free after the DL-268 dedup this stack sits on). - OQ section: ratification preamble (which forks closed), OQ-7 owner-qualifier grammar added, OQ-5/OQ-6 DL refs + stale signature corrected. Ref: RIG-2751 Co-authored-by: Matt Wilkinson <matt@rigel.build> * docs(product): review fixes — DL-186 line cite + stale global claim + OQ-5 scoping (RIG-2751) Review round 1 (ReviewPR645disposition): 0 high / 2 medium / 1 low. All fixed: - medium: DL-186 citation was DECISIONS.md:199 (that's DL-129); DL-186 is at :203. Fixed all 3 refs (Global Constraints, Ledger-impact, OQ-1b). - medium: \u00a7GetRoster called AgentByHandle 'global' in a post-flip frame, contradicting the record's own re-key to owner-namespaced. Reworded to 'owner-namespaced but NOT viewer-scoped', keeping the load-bearing no-viewer- scoping point that motivates the post-resolve ListAccounts visibility check. - low: OQ-5 conflated two response-handle mechanisms under DL-270; scoped them (author_handle sibling = DL-270's path; from_handle denorm = control-plane, outside DL-270's field wording). OQ-5 stays open for Matt. Co-authored-by: Matt Wilkinson <matt@rigel.build> * docs(product): T0 schema in 0001, drop backfill — pre-dogfood no data (RIG-2751) Matt's ruling on #645: don't backfill (Compass isn't in use yet), collapse the account_handles schema into the single existing 0001_init.sql migration rather than shipping an incremental 0002. Rewrites T0: account_handles is authored directly into 0001_init.sql with accounts.handle authored without its former global-unique constraint; no incremental migration, no backfill step (no deployed rows exist to migrate). Drops the backfill-parity pgtest assertion from the test cycle and updates the Tasks checklist row. Refs RIG-2751 Co-authored-by: Matt Wilkinson <matt@rigel.build> * docs(product): fix T0 handle-population attribution — runtime writers, not a 0001 seed (RIG-2751) Review finding (#645, low): the T0 rewrite said accounts.handle is populated by 'the 0001_init.sql seed', but 0001_init.sql has no INSERT/seed statements — the handle column is populated at runtime by store.CreateUser/CreateAgent (accounts.go:29,81). Re-attributes to the true runtime writers so an implementing agent doesn't hunt for a nonexistent seed. Design conclusion (column retained for display) is unchanged. Refs RIG-2751 Co-authored-by: Matt Wilkinson <matt@rigel.build> * docs(product): correct CreateAgent handle-writer line cite (RIG-2751) Round-2 review (#645, low): the prior fix cited accounts.go:29,81 for CreateUser/CreateAgent, but :81 is BootstrapAdmin's INSERT — CreateAgent's is at :254. Corrected to accounts.go:29,254 (both verified: CreateUser func at :13/INSERT :29, CreateAgent func at :234/INSERT :254). Substance unchanged; accurate line pointer. Refs RIG-2751 Co-authored-by: Matt Wilkinson <matt@rigel.build> * docs(product): fold Matt's OQ-1..7 rulings into handle-cutover record + close OQ section (RIG-2751) Matt ruled all remaining wire-cutover open questions (RIG-2751 comment f59001de, 2026-08-27). Fold the rulings into the design record and close the Open Questions section as Rulings, so the record is ready to freeze on merge. - OQ-4 EXTEND: admin/ops lane (SpawnAgent / ProvisionAgentWorkspace / IssueToken on CompassService) flips to handles too — inventory rows 14-16, new task T8. No id-typed request field survives anywhere. - OQ-5 ship-now: additive Message.author_handle sibling; agent fence renders handle-only and drops the id; author_account_id stays on the wire for the UI join — new task T9. - OQ-6 SCOPED: T2 resolver intersects accountVisibleFromWhere; note the Matt-directed D9-widening follow-on as its OWN compass-server change (filed separately), not folded into this freeze. - OQ-1/1b/2/3/7 confirmed at documented defaults (names, rename-in-place, atomic NOT_FOUND, vantage_handle, '/' owner-qualifier grammar). markdownlint 0 errors; design-ledger-gate OK (DL-269/270/271 collision-free). * docs(product): tighten OQ-fold review nits — stale OQ labels + grounding cites (RIG-2751) Sub-floor review fixes on the OQ-fold record (all doc-only, no design change): - Drop stale (OQ-5) tag from the non-goal forward-looking sentence (OQ-5 is ruled/closed; §6 ships additive author_handle). - Rewrite T2's 'per the OQ-2 working assumption' → 'per the OQ-2 ruling'. - Enumerate account_handle (IssueToken row 16) in T1's final-names list. - T8: split service.go:152 (GetAccount lookup) from :166 (hub.Provision relay) so the cite labels each construct correctly. - T9: tighten Message.author_account_id cite comms.proto:322 -> :332 (the field, not the message-block doc-comment). --------- Co-authored-by: Matt Wilkinson <matt@rigel.build>
…solver, comms edge (RIG-2880)
The handle-addressing cutover: every request-input account field carries a `@handle`, resolved to an account id at the service edge, per `docs/designs/product/compass-handle-addressing-cutover/design.md` (Plan T0-T3, plus the compass-agent T6 tool rewire folded in to keep the tree green).
**T0 — resolution index** (`0001_init.sql`). Adds `account_handles(account_id, handle, owner_user_id NULL)` with two partial-unique indexes: user/system handles globally unique (`owner_user_id IS NULL`), agent handles unique per owner. `accounts.handle` stays as a display column, no longer the resolution key. No backfill (pre-dogfood); store write paths insert the handle row in the same tx as the account.
**T1 — proto flip** (`comms.proto`, `agent_gateway.proto`, `compass.proto`). Every request-input account field renamed to its `@handle` form (`member_handles`, `add/remove/subscribe/unsubscribe_handles`, `agent_handle`, `new_parent_handle`, `parent_handle`, `owner_handle`, `vantage_handle`, `account_handle`), field numbers kept in place (rename-in-place, DL-186). Response, stored, and event account fields stay id-typed (the stable join keys clients hold). All four gen lanes regenerated; gen-fence clean.
**T2 — resolver** (`accounts.go`). `AccountsByHandles` is an owner-qualified batch resolve over `account_handles`: atomic (any miss fails the whole call, naming every unresolved handle in submitted spelling — OQ-2), visibility-scoped (an invisible handle misses exactly like an unknown one — OQ-6), system-excluded. `AgentByHandle` re-keyed to `(owner, handle)`; `UserByHandle` global-tier helper; `QualifiedHandle`/`ParseQualifiedHandle` split on the first `/`.
**T3 — comms edge** (`resolve.go` new, `mapping.go`, `roster.go`). Handles resolve before the store calls, with oracle-safe NOT_FOUND remaps on the authority/visibility legs (DL-269): CreateAgent foreign parent, ReparentAgent `ErrPermissionDenied` merge, OpenAgentWorkspace re-key to the submitted handle, roster vantage visibility-close. A delivery mention `@handle` resolves in the posting author's owner namespace. `memberUpdatesFromWire` merges post-resolution so two spellings of one handle cannot yield two conflicting `MemberUpdate`s.
**T6 (folded) — compass-agent despawn tool** (`lifecycle.ts`). `agents_despawn_peer` takes `agent_handle` (non-blank) and builds `DespawnPeerRequest{agentHandle}`; the description drops "by its agent account id". The roster tool is unchanged (it never set the vantage). Folded into this PR so the whole tree — Go, proto, and the bun lane the proto regen touches — stays green in one atomic change rather than landing a red intermediate.
Admin/ops (T8) and lifecycle despawn (T4) server handlers are mechanically renamed to the handle-typed fields but still consume the value as an id (documented id-passthrough skew); their real resolution lands in the follow-up T4/T8 commits on this stack.
**Verification.** `go vet ./...` and `-tags "pgtest podman unix microvm"` clean, `go build ./...` clean, all test binaries compile, `compass-proto:drift`/`gen-fence` pass, store/comms/delivery pgtests pass against live Postgres, `go test -race` clean; `compass-agent` typecheck + 610 bun tests green; `compass-ui`/`compass-client` typecheck green.
Adds DL-269 (request-input account fields are handle-typed), DL-270 (response/stored/event fields stay id-typed), DL-271 (owner-namespaced `account_handles` resolution) on merge.
Unblocks the remaining cutover lanes: T5 rebase of #628/#630, T8 admin/ops resolution, T9 additive `Message.author_handle`, and the compass-ui T7 sweep.
Spec-impact: none. Refs RIG-2880
Co-authored-by: Matt Wilkinson <matt@rigel.build>
…solver, comms edge (RIG-2880)
The handle-addressing cutover: every request-input account field carries a `@handle`, resolved to an account id at the service edge, per `docs/designs/product/compass-handle-addressing-cutover/design.md` (Plan T0-T3, plus the compass-agent T6 tool rewire folded in to keep the tree green).
**T0 — resolution index** (`0001_init.sql`). Adds `account_handles(account_id, handle, owner_user_id NULL)` with two partial-unique indexes: user/system handles globally unique (`owner_user_id IS NULL`), agent handles unique per owner. `accounts.handle` stays as a display column, no longer the resolution key. No backfill (pre-dogfood); store write paths insert the handle row in the same tx as the account.
**T1 — proto flip** (`comms.proto`, `agent_gateway.proto`, `compass.proto`). Every request-input account field renamed to its `@handle` form (`member_handles`, `add/remove/subscribe/unsubscribe_handles`, `agent_handle`, `new_parent_handle`, `parent_handle`, `owner_handle`, `vantage_handle`, `account_handle`), field numbers kept in place (rename-in-place, DL-186). Response, stored, and event account fields stay id-typed (the stable join keys clients hold). All four gen lanes regenerated; gen-fence clean.
**T2 — resolver** (`accounts.go`). `AccountsByHandles` is an owner-qualified batch resolve over `account_handles`: atomic (any miss fails the whole call, naming every unresolved handle in submitted spelling — OQ-2), visibility-scoped (an invisible handle misses exactly like an unknown one — OQ-6), system-excluded. `AgentByHandle` re-keyed to `(owner, handle)`; `UserByHandle` global-tier helper; `QualifiedHandle`/`ParseQualifiedHandle` split on the first `/`.
**T3 — comms edge** (`resolve.go` new, `mapping.go`, `roster.go`). Handles resolve before the store calls, with oracle-safe NOT_FOUND remaps on the authority/visibility legs (DL-269): CreateAgent foreign parent, ReparentAgent `ErrPermissionDenied` merge, OpenAgentWorkspace re-key to the submitted handle, roster vantage visibility-close. A delivery mention `@handle` resolves in the posting author's owner namespace. `memberUpdatesFromWire` merges post-resolution so two spellings of one handle cannot yield two conflicting `MemberUpdate`s.
**T6 (folded) — compass-agent despawn tool** (`lifecycle.ts`). `agents_despawn_peer` takes `agent_handle` (non-blank) and builds `DespawnPeerRequest{agentHandle}`; the description drops "by its agent account id". The roster tool is unchanged (it never set the vantage). Folded into this PR so the whole tree — Go, proto, and the bun lane the proto regen touches — stays green in one atomic change rather than landing a red intermediate.
Admin/ops (T8) and lifecycle despawn (T4) server handlers are mechanically renamed to the handle-typed fields but still consume the value as an id (documented id-passthrough skew); their real resolution lands in the follow-up T4/T8 commits on this stack.
**Verification.** `go vet ./...` and `-tags "pgtest podman unix microvm"` clean, `go build ./...` clean, all test binaries compile, `compass-proto:drift`/`gen-fence` pass, store/comms/delivery pgtests pass against live Postgres, `go test -race` clean; `compass-agent` typecheck + 610 bun tests green; `compass-ui`/`compass-client` typecheck green.
Adds DL-269 (request-input account fields are handle-typed), DL-270 (response/stored/event fields stay id-typed), DL-271 (owner-namespaced `account_handles` resolution) on merge.
Unblocks the remaining cutover lanes: T5 rebase of #628/#630, T8 admin/ops resolution, T9 additive `Message.author_handle`, and the compass-ui T7 sweep.
Spec-impact: none. Refs RIG-2880
Co-authored-by: Matt Wilkinson <matt@rigel.build>
…solver, comms edge (RIG-2880)
The handle-addressing cutover: every request-input account field carries a `@handle`, resolved to an account id at the service edge, per `docs/designs/product/compass-handle-addressing-cutover/design.md` (Plan T0-T3, plus the compass-agent T6 tool rewire folded in to keep the tree green).
**T0 — resolution index** (`0001_init.sql`). Adds `account_handles(account_id, handle, owner_user_id NULL)` with two partial-unique indexes: user/system handles globally unique (`owner_user_id IS NULL`), agent handles unique per owner. `accounts.handle` stays as a display column, no longer the resolution key. No backfill (pre-dogfood); store write paths insert the handle row in the same tx as the account.
**T1 — proto flip** (`comms.proto`, `agent_gateway.proto`, `compass.proto`). Every request-input account field renamed to its `@handle` form (`member_handles`, `add/remove/subscribe/unsubscribe_handles`, `agent_handle`, `new_parent_handle`, `parent_handle`, `owner_handle`, `vantage_handle`, `account_handle`), field numbers kept in place (rename-in-place, DL-186). Response, stored, and event account fields stay id-typed (the stable join keys clients hold). All four gen lanes regenerated; gen-fence clean.
**T2 — resolver** (`accounts.go`). `AccountsByHandles` is an owner-qualified batch resolve over `account_handles`: atomic (any miss fails the whole call, naming every unresolved handle in submitted spelling — OQ-2), visibility-scoped (an invisible handle misses exactly like an unknown one — OQ-6), system-excluded. `AgentByHandle` re-keyed to `(owner, handle)`; `UserByHandle` global-tier helper; `QualifiedHandle`/`ParseQualifiedHandle` split on the first `/`.
**T3 — comms edge** (`resolve.go` new, `mapping.go`, `roster.go`). Handles resolve before the store calls, with oracle-safe NOT_FOUND remaps on the authority/visibility legs (DL-269): CreateAgent foreign parent, ReparentAgent `ErrPermissionDenied` merge, OpenAgentWorkspace re-key to the submitted handle, roster vantage visibility-close. A delivery mention `@handle` resolves in the posting author's owner namespace. `memberUpdatesFromWire` merges post-resolution so two spellings of one handle cannot yield two conflicting `MemberUpdate`s.
**T6 (folded) — compass-agent despawn tool** (`lifecycle.ts`). `agents_despawn_peer` takes `agent_handle` (non-blank) and builds `DespawnPeerRequest{agentHandle}`; the description drops "by its agent account id". The roster tool is unchanged (it never set the vantage). Folded into this PR so the whole tree — Go, proto, and the bun lane the proto regen touches — stays green in one atomic change rather than landing a red intermediate.
Admin/ops (T8) and lifecycle despawn (T4) server handlers are mechanically renamed to the handle-typed fields but still consume the value as an id (documented id-passthrough skew); their real resolution lands in the follow-up T4/T8 commits on this stack.
**Verification.** `go vet ./...` and `-tags "pgtest podman unix microvm"` clean, `go build ./...` clean, all test binaries compile, `compass-proto:drift`/`gen-fence` pass, store/comms/delivery pgtests pass against live Postgres, `go test -race` clean; `compass-agent` typecheck + 610 bun tests green; `compass-ui`/`compass-client` typecheck green.
Adds DL-269 (request-input account fields are handle-typed), DL-270 (response/stored/event fields stay id-typed), DL-271 (owner-namespaced `account_handles` resolution) on merge.
Unblocks the remaining cutover lanes: T5 rebase of #628/#630, T8 admin/ops resolution, T9 additive `Message.author_handle`, and the compass-ui T7 sweep.
Spec-impact: none. Refs RIG-2880
Co-authored-by: Matt Wilkinson <matt@rigel.build>
…solver, comms edge (RIG-2880) (#698) * feat(compass): handle-address request inputs — schema, proto flip, resolver, comms edge (RIG-2880) The handle-addressing cutover: every request-input account field carries a `@handle`, resolved to an account id at the service edge, per `docs/designs/product/compass-handle-addressing-cutover/design.md` (Plan T0-T3, plus the compass-agent T6 tool rewire folded in to keep the tree green). **T0 — resolution index** (`0001_init.sql`). Adds `account_handles(account_id, handle, owner_user_id NULL)` with two partial-unique indexes: user/system handles globally unique (`owner_user_id IS NULL`), agent handles unique per owner. `accounts.handle` stays as a display column, no longer the resolution key. No backfill (pre-dogfood); store write paths insert the handle row in the same tx as the account. **T1 — proto flip** (`comms.proto`, `agent_gateway.proto`, `compass.proto`). Every request-input account field renamed to its `@handle` form (`member_handles`, `add/remove/subscribe/unsubscribe_handles`, `agent_handle`, `new_parent_handle`, `parent_handle`, `owner_handle`, `vantage_handle`, `account_handle`), field numbers kept in place (rename-in-place, DL-186). Response, stored, and event account fields stay id-typed (the stable join keys clients hold). All four gen lanes regenerated; gen-fence clean. **T2 — resolver** (`accounts.go`). `AccountsByHandles` is an owner-qualified batch resolve over `account_handles`: atomic (any miss fails the whole call, naming every unresolved handle in submitted spelling — OQ-2), visibility-scoped (an invisible handle misses exactly like an unknown one — OQ-6), system-excluded. `AgentByHandle` re-keyed to `(owner, handle)`; `UserByHandle` global-tier helper; `QualifiedHandle`/`ParseQualifiedHandle` split on the first `/`. **T3 — comms edge** (`resolve.go` new, `mapping.go`, `roster.go`). Handles resolve before the store calls, with oracle-safe NOT_FOUND remaps on the authority/visibility legs (DL-269): CreateAgent foreign parent, ReparentAgent `ErrPermissionDenied` merge, OpenAgentWorkspace re-key to the submitted handle, roster vantage visibility-close. A delivery mention `@handle` resolves in the posting author's owner namespace. `memberUpdatesFromWire` merges post-resolution so two spellings of one handle cannot yield two conflicting `MemberUpdate`s. **T6 (folded) — compass-agent despawn tool** (`lifecycle.ts`). `agents_despawn_peer` takes `agent_handle` (non-blank) and builds `DespawnPeerRequest{agentHandle}`; the description drops "by its agent account id". The roster tool is unchanged (it never set the vantage). Folded into this PR so the whole tree — Go, proto, and the bun lane the proto regen touches — stays green in one atomic change rather than landing a red intermediate. Admin/ops (T8) and lifecycle despawn (T4) server handlers are mechanically renamed to the handle-typed fields but still consume the value as an id (documented id-passthrough skew); their real resolution lands in the follow-up T4/T8 commits on this stack. **Verification.** `go vet ./...` and `-tags "pgtest podman unix microvm"` clean, `go build ./...` clean, all test binaries compile, `compass-proto:drift`/`gen-fence` pass, store/comms/delivery pgtests pass against live Postgres, `go test -race` clean; `compass-agent` typecheck + 610 bun tests green; `compass-ui`/`compass-client` typecheck green. Adds DL-269 (request-input account fields are handle-typed), DL-270 (response/stored/event fields stay id-typed), DL-271 (owner-namespaced `account_handles` resolution) on merge. Unblocks the remaining cutover lanes: T5 rebase of #628/#630, T8 admin/ops resolution, T9 additive `Message.author_handle`, and the compass-ui T7 sweep. Spec-impact: none. Refs RIG-2880 Co-authored-by: Matt Wilkinson <matt@rigel.build> * fix(compass): close ReparentAgent oracle leak, complete OQ-2 miss naming, add DL rows (RIG-2880) Review-fix commit on the handle-addressing foundation. Addresses the review of the parent commit (three code findings plus the mandated ledger rows); the low-2 finding (globally-visible user directory) is a pre-existing decision surfaced to Matt, not changed here. HIGH — ReparentAgent oracle leak (DL-269 violation). A foreign new_parent_handle (one that exists under another owner) fell through the edge to the store, which re-keyed the error to name the AGENT handle, while an unknown parent named the submitted parent handle. The divergence let a caller distinguish "this parent exists under another owner" from "no such handle" — an enumeration probe against another owner's tree. Fixed by mirroring CreateAgent's same-owner edge pre-check in ReparentAgent (go/internal/comms/comms.go): after resolving the new parent, resolve the caller's owner and the parent's owner, and if they differ return the indistinguishable NOT_FOUND naming the submitted new_parent_handle. The store's clause-0 permission-denied catch stays correct for agent-authority; the store clause-1 foreign-parent path is now unreachable via the edge. MEDIUM — reparent cross-owner tests asserted code only, not message. TestReparentAgentCrossOwnerParentNotFound now captures both the foreign-parent and unknown-parent errors and asserts each names its own submitted parent handle, that the foreign error does NOT name the agent handle, and that the two are byte-identical modulo the caller's own submitted spelling — the oracle invariant, not just the code. LOW-1 — memberUpdatesFromWire OQ-2 completeness. The four member lists (add/subscribe/unsubscribe/remove) resolved sequentially, so a miss in a later list was masked by an earlier list's miss — the error named only the first failing list. Rewritten to one combined resolveHandles call over all four lists, slicing results back per-list by offset; every unresolved handle across every list is now named in one pass. New TestUpdateChannelMembersNamesMissesAcrossAllLists defends this. Ledger — adds the three rows the frozen record's §Ledger impact mandates (DL-269 request-input handle-typing + oracle invariant, DL-270 response/stored/event stay id-typed, DL-271 owner-namespaced account_handles resolution), placed in the Storage section. The design PR merged without adding them; per house convention the impl PR fills its allocated numbers. Verification: go build + go vet (all tags) clean; gofmt + compass-go:lint (0 issues) clean; comms pgtest suite green against live Postgres (reparent oracle fix, one-pass member resolution, and both new tests); design-ledger-gate green (244 rows, anchor resolves). Co-authored-by: Matt Wilkinson <matt@rigel.build> * docs(store): note ReparentAgent clause-1 is edge-shadowed on the RPC path (RIG-2880) Review-fix on #698 (low, non-gating). The re-review noted that comms.ReparentAgent's new edge pre-check makes the store's validateNewParent same-owner ErrPermissionDenied unreachable via the RPC path — a future reader at the store clause might mistake it for dead code. Adds a one-line note that it is edge-shadowed on the RPC path and remains as store-layer defense-in-depth (the store is independently callable and tested). Co-authored-by: Matt Wilkinson <matt@rigel.build> * fix(compass): spawn allows cross-owner same-name agents, guards against shadowing a human (RIG-2880) The per-owner `account_handles` namespace (DL-271) changes the spawn-collision domain from global to per-owner, which the two `go/server` lifecycle spawn tests did not yet reflect — they asserted the pre-cutover global-unique behavior and reddened the `pgtest` CI suite. Matt ruled the intended `SpawnPeer` contract (2026-08-27): allow cross-owner same-name agents, but keep a defense-in-depth guard so an agent can never shadow a human or the system sender. - **Shadow guard** (`lifecycle.go`): `SpawnAsAccount` resolves the bare handle in the global user/system index (`UserByHandle`) before `CreateAgent`; a hit is the same in-band `already_exists`/`errHandleTaken` a duplicate agent handle gets, never revealing the account kind. Storage permits an agent handle to overlap a user/system handle (the two partial-unique indexes never contend), but resolution resolves the global tier first (OQ-7), so this closes the one way a peer could be created onto a human's handle. - **`resumeOrReject` simplification** (`lifecycle.go`): with the guard excluding user/system handles and the per-owner namespace isolating foreign owners, a `CreateAgent` `ErrConflict` can only mean a same-owner agent already holds the handle. The two former reject branches (non-agent handle, foreign owner) are now unreachable and removed; an `AgentByHandle(callerOwner, handle)` miss there is an invariant violation (`CodeInternal`), not a routine collapse. - **Tests** (`lifecycle_pgtest_test.go`): `...DifferentOwnerIsAlreadyExists` is rewritten as `...CreatesDistinctPeer` — owner B spawning owner A's handle now creates a distinct owner-B peer, A untouched, each owner's handle resolving to its own agent. `...CollidesWithUserAccountIsAlreadyExists` keeps its `already_exists` assertion, now backed by the guard, with an added system-handle sibling pinning the indistinguishable code shape. - **Fake runner** (`service_placement_pgtest_test.go`): an opt-in FIFO container-name override (mirroring the existing `startIDs`), so the cross-owner test's two genuinely-distinct spawns get distinct container names instead of colliding on the fixture's constant `fakeContainer` (production derives the name from the globally-unique account id, so real spawns never collide). Verification: `go build ./...`, `go vet -tags "pgtest podman unix microvm"`, and the `go/server`, `go/internal/store`, `go/internal/comms` pgtest suites all green against live Postgres. Self-driven `review` agent: 0 high / 0 medium / 2 low (both no-change informational). Refs RIG-2880 Co-authored-by: Matt Wilkinson <matt@rigel.build> --------- Co-authored-by: Matt Wilkinson <matt@rigel.build>
…h, spawn role/persona (RIG-2673) Wires the compass-server half of the org-management agent tools (RIG-2673 T2–T4) on top of the T1 proto arms (#628): the three comms adapters, their hub dispatch, and the spawn role/persona thread-through. Every operation flows through the existing agent→Runner→Server relay under the server-resolved account, so the authz, idempotency, and event fan-out are identical to a human caller's. ### T2 — comms adapters (`go/internal/comms/agent_caller.go`) `CreateChannelAsAccount`, `UpdateChannelMembersAsAccount`, `CreateChannelGroupAsAccount` — each mirrors `UpdatePinnedBoardAsAccount`: guard empty account → `errNoActor`, then `WithActor` + the shared handler path, returning `resp.Msg`. No home-channel defaulting (create-channel names no channel; update-members carries `channel_id` explicitly; create-group names only an optional parent). Founding membership rides `CreateChannel`'s `expandOwnerMembership`, so a Manager can immediately post to the channel it just made. ### T3 — hub dispatch (`go/internal/runnerhub/hub.go`, `relay_comms.go`) Extended the `CommsCaller` interface with the three methods and added three `executeCall` cases on the `Pin` pattern, each wrapping the matching `CommsCallResult` oneof. The default-case error string's variant list now includes the three new arms. A tool error renders in-band as `CommsCallError`, never a stream teardown. ### T4 — spawn role/persona thread-through (`go/server/lifecycle.go`) Dropped the `Persona: ""` / `Role: ""` hardcodes in the `store.CreateAgent` literal for `req.GetPersona()` / `req.GetRole()`. Under the D9 owner-acts model the caller's owner is the authority, so a Manager-creating spawn legitimately carries role+persona set-at-creation; the values are stored via `CreateAgent` (the source of record) and threaded to the Runner from the created store account — an empty-field spawn is byte-identical to today's field-less spawn. ### Tests (red-green) - T2 pgtests (`org_mgmt_pgtest_test.go`): founder-membership + `ChannelChanged` fan-out, member-add, group-create; each with empty-account → `errNoActor` and an invisible/non-member target → `CodeNotFound` (the D9 collapse a human gets). - T3 relay tests (`relay_org_mgmt_test.go`): each arm forwards under the bound account with `call_id` round-trip; a create-channel tool error is in-band, not a transport teardown. - T4 spawn pgtests (`spawn_role_persona_pgtest_test.go`): role and persona each stored + on the Provision wire; empty-field spawn stores + wires empty (wire-level regression); idempotent re-spawn keeps stored values. Verified: `go build ./...`, `gofmt`, `compass-go:lint` (0 issues), and the affected `internal/comms` / `internal/runnerhub` / `server` suites all green (pgtests via the throwaway-container path). Refs RIG-2724, RIG-2673. Co-authored-by: Matt Wilkinson <matt@rigel.build>
836c472 to
8374738
Compare
…-2673) Add the three org-management comms-call arms (create_channel=7, update_members=8, create_channel_group=9 on CommsCallRequest.call; the matching results =8/=9/=10 on CommsCallResult.result) and the Manager- creation role=5/persona=6 fields on SpawnPeerRequest, reusing the existing comms.proto payload messages verbatim. Regenerated the internal Go (go/internal/gen) and agent TS (packages/compass-agent/src/gen) lanes. T1 of the frozen org-management-tools record (RIG-2673, #589); the single additive proto+regen commit both compass-server and compass-agent consume. Co-authored-by: Matt Wilkinson <matt@rigel.build> jj-hooks: autofixes for compass-server/rig-2724-org-mgmt-proto regen-resolve
…h, spawn role/persona (RIG-2673) Wires the compass-server half of the org-management agent tools (RIG-2673 T2–T4) on top of the T1 proto arms (#628): the three comms adapters, their hub dispatch, and the spawn role/persona thread-through. Every operation flows through the existing agent→Runner→Server relay under the server-resolved account, so the authz, idempotency, and event fan-out are identical to a human caller's. ### T2 — comms adapters (`go/internal/comms/agent_caller.go`) `CreateChannelAsAccount`, `UpdateChannelMembersAsAccount`, `CreateChannelGroupAsAccount` — each mirrors `UpdatePinnedBoardAsAccount`: guard empty account → `errNoActor`, then `WithActor` + the shared handler path, returning `resp.Msg`. No home-channel defaulting (create-channel names no channel; update-members carries `channel_id` explicitly; create-group names only an optional parent). Founding membership rides `CreateChannel`'s `expandOwnerMembership`, so a Manager can immediately post to the channel it just made. ### T3 — hub dispatch (`go/internal/runnerhub/hub.go`, `relay_comms.go`) Extended the `CommsCaller` interface with the three methods and added three `executeCall` cases on the `Pin` pattern, each wrapping the matching `CommsCallResult` oneof. The default-case error string's variant list now includes the three new arms. A tool error renders in-band as `CommsCallError`, never a stream teardown. ### T4 — spawn role/persona thread-through (`go/server/lifecycle.go`) Dropped the `Persona: ""` / `Role: ""` hardcodes in the `store.CreateAgent` literal for `req.GetPersona()` / `req.GetRole()`. Under the D9 owner-acts model the caller's owner is the authority, so a Manager-creating spawn legitimately carries role+persona set-at-creation; the values are stored via `CreateAgent` (the source of record) and threaded to the Runner from the created store account — an empty-field spawn is byte-identical to today's field-less spawn. ### Tests (red-green) - T2 pgtests (`org_mgmt_pgtest_test.go`): founder-membership + `ChannelChanged` fan-out, member-add, group-create; each with empty-account → `errNoActor` and an invisible/non-member target → `CodeNotFound` (the D9 collapse a human gets). - T3 relay tests (`relay_org_mgmt_test.go`): each arm forwards under the bound account with `call_id` round-trip; a create-channel tool error is in-band, not a transport teardown. - T4 spawn pgtests (`spawn_role_persona_pgtest_test.go`): role and persona each stored + on the Provision wire; empty-field spawn stores + wires empty (wire-level regression); idempotent re-spawn keeps stored values. Verified: `go build ./...`, `gofmt`, `compass-go:lint` (0 issues), and the affected `internal/comms` / `internal/runnerhub` / `server` suites all green (pgtests via the throwaway-container path). Refs RIG-2724, RIG-2673. Co-authored-by: Matt Wilkinson <matt@rigel.build>
8374738 to
c5f84cd
Compare
…h, spawn role/persona (RIG-2673) (#630) * feat(proto): org-management comms + spawn arms for agent gateway (RIG-2673) Add the three org-management comms-call arms (create_channel=7, update_members=8, create_channel_group=9 on CommsCallRequest.call; the matching results =8/=9/=10 on CommsCallResult.result) and the Manager- creation role=5/persona=6 fields on SpawnPeerRequest, reusing the existing comms.proto payload messages verbatim. Regenerated the internal Go (go/internal/gen) and agent TS (packages/compass-agent/src/gen) lanes. T1 of the frozen org-management-tools record (RIG-2673, #589); the single additive proto+regen commit both compass-server and compass-agent consume. Co-authored-by: Matt Wilkinson <matt@rigel.build> jj-hooks: autofixes for compass-server/rig-2724-org-mgmt-proto regen-resolve * feat(comms): org-management agent tools — comms adapters, hub dispatch, spawn role/persona (RIG-2673) Wires the compass-server half of the org-management agent tools (RIG-2673 T2–T4) on top of the T1 proto arms (#628): the three comms adapters, their hub dispatch, and the spawn role/persona thread-through. Every operation flows through the existing agent→Runner→Server relay under the server-resolved account, so the authz, idempotency, and event fan-out are identical to a human caller's. ### T2 — comms adapters (`go/internal/comms/agent_caller.go`) `CreateChannelAsAccount`, `UpdateChannelMembersAsAccount`, `CreateChannelGroupAsAccount` — each mirrors `UpdatePinnedBoardAsAccount`: guard empty account → `errNoActor`, then `WithActor` + the shared handler path, returning `resp.Msg`. No home-channel defaulting (create-channel names no channel; update-members carries `channel_id` explicitly; create-group names only an optional parent). Founding membership rides `CreateChannel`'s `expandOwnerMembership`, so a Manager can immediately post to the channel it just made. ### T3 — hub dispatch (`go/internal/runnerhub/hub.go`, `relay_comms.go`) Extended the `CommsCaller` interface with the three methods and added three `executeCall` cases on the `Pin` pattern, each wrapping the matching `CommsCallResult` oneof. The default-case error string's variant list now includes the three new arms. A tool error renders in-band as `CommsCallError`, never a stream teardown. ### T4 — spawn role/persona thread-through (`go/server/lifecycle.go`) Dropped the `Persona: ""` / `Role: ""` hardcodes in the `store.CreateAgent` literal for `req.GetPersona()` / `req.GetRole()`. Under the D9 owner-acts model the caller's owner is the authority, so a Manager-creating spawn legitimately carries role+persona set-at-creation; the values are stored via `CreateAgent` (the source of record) and threaded to the Runner from the created store account — an empty-field spawn is byte-identical to today's field-less spawn. ### Tests (red-green) - T2 pgtests (`org_mgmt_pgtest_test.go`): founder-membership + `ChannelChanged` fan-out, member-add, group-create; each with empty-account → `errNoActor` and an invisible/non-member target → `CodeNotFound` (the D9 collapse a human gets). - T3 relay tests (`relay_org_mgmt_test.go`): each arm forwards under the bound account with `call_id` round-trip; a create-channel tool error is in-band, not a transport teardown. - T4 spawn pgtests (`spawn_role_persona_pgtest_test.go`): role and persona each stored + on the Provision wire; empty-field spawn stores + wires empty (wire-level regression); idempotent re-spawn keeps stored values. Verified: `go build ./...`, `gofmt`, `compass-go:lint` (0 issues), and the affected `internal/comms` / `internal/runnerhub` / `server` suites all green (pgtests via the throwaway-container path). Refs RIG-2724, RIG-2673. Co-authored-by: Matt Wilkinson <matt@rigel.build> --------- Co-authored-by: Matt Wilkinson <matt@rigel.build>
This PR is part of a stack containing 3 PRs:
mainAdd the three org-management comms-call arms (create_channel=7,
update_members=8, create_channel_group=9 on CommsCallRequest.call; the
matching results =8/=9/=10 on CommsCallResult.result) and the Manager-
creation role=5/persona=6 fields on SpawnPeerRequest, reusing the existing
comms.proto payload messages verbatim. Regenerated the internal Go
(go/internal/gen) and agent TS (packages/compass-agent/src/gen) lanes.
T1 of the frozen org-management-tools record (RIG-2673, #589); the single
additive proto+regen commit both compass-server and compass-agent consume.
Co-authored-by: Matt Wilkinson matt@rigel.build