feat(compass): SEA-1732 T10 — operator-set role selects the agent's block-0 prompt - #229
Open
seal-agent wants to merge 3 commits into
Open
feat(compass): SEA-1732 T10 — operator-set role selects the agent's block-0 prompt#229seal-agent wants to merge 3 commits into
seal-agent wants to merge 3 commits into
Conversation
…lock-0 prompt Adds a `role` field on AgentAccount, mirroring `persona` end to end, that selects a container's block-0 system prompt at provision time. Where persona is an APPEND overlay layered after the default prompt, role REPLACES block-0: the operator-set label selects `config/prompts/<role>/SYSTEM.md` from the mounted config tree, delivered to the container as `customSystemPrompt`. Delivery chain (server-authoritative, mirroring persona so a caller cannot inject a prompt): - migration 0015 adds `agent_accounts.role TEXT NOT NULL DEFAULT ''`; the store round-trips it through NewAgent/AgentAccount + every scanAccount SELECT. - `ProvisionAgentWorkspaceRequest.role` (proto field 7). Both provision paths overwrite the client value from the store account — service.go for the RPC path, lifecycle.go for the spawn path (which bypasses the RPC overwrite). - AgentSpec.Role -> AgentHandle.Role() -> AgentEnv.Role -> COMPASS_ROLE env, empty-omitted exactly like COMPASS_PERSONA. - cli.ts resolveRole reads COMPASS_ROLE, reads the role prompt through a new tolerant config-reader surface, and passes it as customSystemPrompt. Persona still appends AFTER the role block: customSystemPrompt and the systemPrompt callback are orthogonal SDK options that compose (replace runs first, append second). Role set but no prompt file falls back to today's default block-0. The MP-1 property test renders the real SDK prompt and pins the invariant a replaced block-0 must preserve: the skills list, rules list, and project footer survive, and skills injection stays gated on the read tool. Regenerated the compass proto stubs (public + client lanes) for the new field. Co-authored-by: Matt Wilkinson <matt@sealedsecurity.com>
…review low) A role selects prompts/<role>/SYSTEM.md; reject a label carrying a path separator or '..' so it can never traverse outside the prompts/ subtree. Defense in depth — role is store-set out-of-band today, but the guard costs nothing and closes the traversal the moment a client-facing setter lands. A rejected label reads as 'no prompt' and falls back to the default block-0. Co-authored-by: Matt Wilkinson <matt@sealedsecurity.com>
seal-agent
force-pushed
the
seal-1732-t10-role
branch
from
August 7, 2026 21:05
76062fb to
4ca98e5
Compare
…T10 bounce) The T10 role chain added ag.role to scanAccount (9 destinations) and to the five ListAccounts-family projections in accounts.go, but missed the shared agentTreeProjection in agent_tree.go, which feeds every roster/tree read (AgentsByOwner, neighborhood, subtree) through the same scanAccount helper. That projection still selected 8 account columns, so every tree read failed at scan time with "number of field descriptions must equal number of destinations, got 8 and 9" — surfaced by the real-Postgres CI leg on #229 (TestGetRoster*, TestSetStatusAsAccount*). Add ag.role in the same positional slot the accounts.go projections use (after ag.persona, before ag.parent_agent_id) so the tree projection stays a byte-for-byte mirror, and refresh the stale accounts.go line reference in the projection's doc comment. Verified vs a throwaway Postgres container: go build + go vet clean; the comms package (TestGetRoster*, TestSetStatusAsAccount*) is green. Co-authored-by: Matt Wilkinson <matt@sealedsecurity.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
SEA-1732 T10 — an operator-set role selects the container's block-0 system prompt at provision time, delivered as
customSystemPrompt.Matt ruled (2026-08-07): the role selector is a new
rolefield onAgentAccount, mirroring the existingpersonafield end to end. Where persona is an APPEND overlay (layered after the default prompt), role REPLACES block-0 — the label selectsconfig/prompts/<role>/SYSTEM.md, materialized to the container ascustomSystemPrompt. Persona still appends after the role block; the two compose (record §OQ-8).Delivery chain (mirrors persona; server-authoritative)
agent_accounts.role TEXT NOT NULL DEFAULT ''; the store round-trips it throughNewAgent/AgentAccountand everyscanAccountSELECT.ProvisionAgentWorkspaceRequest.role(field 7). Both provision paths overwrite the client value from the store account —service.gofor the RPC path,lifecycle.gofor the spawn path (which bypasses the RPC overwrite), so a caller cannot inject a prompt.AgentSpec.Role→AgentHandle.Role()→AgentEnv.Role→COMPASS_ROLEenv, empty-omitted exactly likeCOMPASS_PERSONA.cli.tsresolveRolereadsCOMPASS_ROLE, reads the role prompt via a new tolerantconfig-readersurface (prompts/<role>/SYSTEM.md), and passes it ascustomSystemPrompt. Role set but no prompt file → falls back to today's default block-0.go/gen+ both client lanes) for the new field. Internal Go lane M-mapscompass.prototo the public package, so it needs no separate regen.MP-1 property
The MP-1 test renders the real SDK prompt and pins the invariant a replaced block-0 must preserve: the skills list, rules list, and project footer survive, and skills injection stays gated on the
readtool (a companion render withtoolNames=[]proves the gate is load-bearing).Verification (all firsthand)
go build ./...clean;go vetclean;nilaway(non-gating) introduces no new finding.COMPASS_ROLEexport,BuildSpecmapping) + 4pgtesttests against a real Postgres (store round-trip, defaults-empty, server-authoritative overwrite, non-agent clear) — all pass.compass-agentTS suite: 437 pass / 0 fail (was 427 + 10 new), including the MP-1 property against the real SDK render.biomeclean on the 3 changed TS files.Refs SEA-1732