Skip to content

feat(compass-agent): validate spawn role against the closed Manager taxonomy (RIG-3074) - #810

Open
rigel-mintaka wants to merge 1 commit into
mainfrom
compass-agent/rig-3074-role-taxonomy-validation
Open

feat(compass-agent): validate spawn role against the closed Manager taxonomy (RIG-3074)#810
rigel-mintaka wants to merge 1 commit into
mainfrom
compass-agent/rig-3074-role-taxonomy-validation

Conversation

@rigel-mintaka

Copy link
Copy Markdown
Contributor

Tightens and validates the role-on-spawn contract RIG-2726 (#762) wired end to end. No new proto field and no new tool param — this narrows the existing role value-space and enforces the closed set server-side.

TypeScript (tool edge)

  • Narrow spawnParameters.role from a non-blank type("string") to the closed literal union 'supervisor' | 'owner' | 'manager', so the model sees the exact taxonomy in the JSON schema and an off-taxonomy or empty label is rejected structurally at the tool edge (a literal union renders into the schema; the old .narrow did not). The non-blank rule is no longer description-carried for role (it is now structural); handle and persona keep theirs.
  • cli.ts: emit one console.error when a role is set but its prompts/<role>/SYSTEM.md did not materialize, so a role-without-shipped-prompt degradation is visible instead of a silent fallback to the default block-0.

Go server (authority)

  • Add spawnableRoles — a fixed server constant of the three roles beside rootSupervisorRole (a frozen product decision, not derived from the operator config bundle).
  • SpawnAsAccount validates req.GetRole() against it before CreateAgent, so an unknown or empty role is refused in-band CodeInvalidArgument and writes no row — and the guard sits ahead of the create/resume switch, so it covers the resume branch too. All three roles are spawnable (a spawned supervisor is parented and permitted). Rewrote the set-at-creation invariant comment: role is caller-selected but server-validated; prompt text still arrives only via the operator bundle; the store stays the provision-time source of record.

Tests

  • TS: schema rejects off-taxonomy roles and accepts each of the three.
  • Go pgtest: empty and off-taxonomy roles rejected with CodeInvalidArgument and no row; each of the three accepted and threaded to the Provision wire; the idempotent-resume and crash-resume tests retry with a valid different role and still prove stored-role immutability. Every pre-existing agent-initiated spawn test that omitted a role now passes manager so it keeps exercising its own invariant. Verified against real Postgres (throwaway container).

Open questions

  • Second creation door: CommsService.CreateAgent (go/internal/comms/comms.go:146-150) calls store.CreateAgent with a ParentAgentID but no Role, so it can create roleless tree nodes — which the frozen taxonomy tenet 3 and DL-new-B say cannot exist. T1 only guards SpawnAsAccount (the agents_spawn_peer path DL-new-B scopes). Closing the second door is a compass-server design fork that pairs with RIG-2673 (agent-tool exposure of create_agent); filed as RIG-3097 for compass-server, not fixed here.

Spec-impact: none. Refs RIG-3074

Co-authored-by: Matt Wilkinson matt@rigel.build

…axonomy (RIG-3074)

Tightens and validates the role-on-spawn contract RIG-2726 (#762) wired end to end. No new proto field and no new tool param — this narrows the existing `role` value-space and enforces the closed set server-side.

### TypeScript (tool edge)

- Narrow `spawnParameters.role` from a non-blank `type("string")` to the closed literal union `'supervisor' | 'owner' | 'manager'`, so the model sees the exact taxonomy in the JSON schema and an off-taxonomy or empty label is rejected structurally at the tool edge (a literal union renders into the schema; the old `.narrow` did not). The non-blank rule is no longer description-carried for `role` (it is now structural); handle and persona keep theirs.
- `cli.ts`: emit one `console.error` when a role is set but its `prompts/<role>/SYSTEM.md` did not materialize, so a role-without-shipped-prompt degradation is visible instead of a silent fallback to the default block-0.

### Go server (authority)

- Add `spawnableRoles` — a fixed server constant of the three roles beside `rootSupervisorRole` (a frozen product decision, not derived from the operator config bundle).
- `SpawnAsAccount` validates `req.GetRole()` against it before `CreateAgent`, so an unknown or empty role is refused in-band `CodeInvalidArgument` and writes no row — and the guard sits ahead of the create/resume switch, so it covers the resume branch too. All three roles are spawnable (a spawned supervisor is parented and permitted). Rewrote the set-at-creation invariant comment: role is caller-selected but server-validated; prompt text still arrives only via the operator bundle; the store stays the provision-time source of record.

### Tests

- TS: schema rejects off-taxonomy roles and accepts each of the three.
- Go pgtest: empty and off-taxonomy roles rejected with `CodeInvalidArgument` and no row; each of the three accepted and threaded to the Provision wire; the idempotent-resume and crash-resume tests retry with a valid different role and still prove stored-role immutability. Every pre-existing agent-initiated spawn test that omitted a role now passes `manager` so it keeps exercising its own invariant. Verified against real Postgres (throwaway container).

### Open questions

- Second creation door: `CommsService.CreateAgent` (`go/internal/comms/comms.go:146-150`) calls `store.CreateAgent` with a `ParentAgentID` but no `Role`, so it can create roleless tree nodes — which the frozen taxonomy tenet 3 and DL-new-B say cannot exist. T1 only guards `SpawnAsAccount` (the `agents_spawn_peer` path DL-new-B scopes). Closing the second door is a compass-server design fork that pairs with RIG-2673 (agent-tool exposure of `create_agent`); filed as RIG-3097 for compass-server, not fixed here.

Spec-impact: none. Refs RIG-3074

Co-authored-by: Matt Wilkinson <matt@rigel.build>
@linear-code

linear-code Bot commented Aug 31, 2026

Copy link
Copy Markdown

RIG-3074

@github-actions

Copy link
Copy Markdown

Compass engineering docs preview: https://compass-agent-rig-3074-role.compass-eng-docs.pages.dev

Deployed from compass-agent/rig-3074-role-taxonomy-validation at 442d6ab.

rigel-mintaka added a commit that referenced this pull request Sep 1, 2026
…076)

Matt ruled the owner tier supports multiple owner levels ("middle
managers"): an `owner` may spawn a child `owner` for a coherent sub-domain,
not only child `manager`s, and owner tiers nest as deep as the domain needs.
The taxonomy already permits this (record DL-new-E multi-tree; DL-new-F made
`ManagerResolver` walk to the nearest ancestor of ANY Manager-class role
precisely so an owner-under-owner is not skipped), and the `management-trees`
skill already draws it (`department Managers [each owner]`) — but the owner
block-0 prompt named only child `manager`s on every action line. This closes
that gap so the prompt matches the frozen model.

`config/prompts/owner/SYSTEM.md`: the position, grow-subtree, coordinator,
work-loop, and operator-approval lines now name child `owner`s alongside
`manager`s, and add the role-selection rule — a coherent sub-domain that is
itself decomposable gets a child `owner`; a single function/lane gets a
`manager`. Mirrors the supervisor prompt's existing "spawn `owner`s and
`manager`s" phrasing one tier down.

`docs/concepts/agent-roles.md`: the `owner` bullet now names delegating a
sub-domain to a child `owner` (owner tiers nest), consistent with the prompt.

No code change; the bind-3 taxonomy is unchanged (RIG-3074/#810 stays as-is —
depth carries hierarchy, the role set stays exactly supervisor/owner/manager).

Spec-impact: none. Refs RIG-3076
Co-authored-by: Matt Wilkinson <matt@rigel.build>
rigel-mintaka added a commit that referenced this pull request Sep 1, 2026
…076)

Matt ruled the owner tier supports multiple owner levels ("middle
managers"): an `owner` may spawn a child `owner` for a coherent sub-domain,
not only child `manager`s, and owner tiers nest as deep as the domain needs.
The taxonomy already permits this (record DL-new-E multi-tree; DL-new-F made
`ManagerResolver` walk to the nearest ancestor of ANY Manager-class role
precisely so an owner-under-owner is not skipped), and the `management-trees`
skill already draws it (`department Managers [each owner]`) — but the owner
block-0 prompt named only child `manager`s on every action line. This closes
that gap so the prompt matches the frozen model.

`config/prompts/owner/SYSTEM.md`: the position, grow-subtree, coordinator,
work-loop, and operator-approval lines now name child `owner`s alongside
`manager`s, and add the role-selection rule — a coherent sub-domain that is
itself decomposable gets a child `owner`; a single function/lane gets a
`manager`. Mirrors the supervisor prompt's existing "spawn `owner`s and
`manager`s" phrasing one tier down. The compact-context line is now
role-agnostic ("the work lives in your subtree") so it covers child owners.

`docs/concepts/agent-roles.md`: the `owner` bullet now names delegating a
sub-domain to a child `owner` (owner tiers nest), consistent with the prompt.

`config/skills/management-trees/SKILL.md`: the compact `owner` role summary
now names delegating sub-domains to child `owner`s (it previously read "child
managers" only), matching the concepts doc and the example tree.

No code change; the bind-3 taxonomy is unchanged (RIG-3074/#810 stays as-is —
depth carries hierarchy, the role set stays exactly supervisor/owner/manager).

Spec-impact: none. Refs RIG-3076
Co-authored-by: Matt Wilkinson <matt@rigel.build>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants