Depends on #223.
Why
The user's requirement is that this works conversationally: "I want it to read it back to me, and if
I ask to add or amend it, it should be able to do it." Today the agent fakes this — it wrote its
character preference into Memory and then read that back, which looks right and is stored in the
wrong place.
Scope
Two FIRST_PARTY_TOOLS entries deriving their argument schema from BEHAVIOUR_FIELDS, so adding a
field never means editing the tools:
get_behaviour — returns the resolved values with their band prose, so the agent can explain
itself in the same words the prompt uses rather than reciting numbers.
set_behaviour — partial patch through sanitizeBehaviour; returns the full post-patch object
(same convention as the memory tools, so the model sees what it actually changed).
Security boundary — non-negotiable
set_behaviour must NOT be able to write the guardrail fields (topicRestrictions,
blockedTerms, requireCitations). Those stay UI-only.
The reason is concrete: a Repo Coder reads untrusted repo files and GitHub issue bodies. If injected
text can widen the agent's own guardrails, the guardrails are decorative. Presentation preferences
are low-stakes and self-correcting — a user who gets an unwanted tone says so. A silently removed
topic restriction is not observable.
Enforced by a SELF_WRITABLE allowlist in lib/agent-behaviour.ts, with a test that fails if any
guardrail field ever becomes self-writable.
Note these are ungated like start_work and run_pipeline — the consent gate keys on a connector,
so a scope-write tool without one is unreachable rather than gated. The allowlist above is the real
boundary here, not the scope field.
Verification
- A patch naming a guardrail field is rejected, and the surrounding legitimate fields still apply
(partial rejection, not silent whole-patch failure).
- Round-trip:
set_behaviour → prompt rebuild → the new band prose is present.
Depends on #223.
Why
The user's requirement is that this works conversationally: "I want it to read it back to me, and if
I ask to add or amend it, it should be able to do it." Today the agent fakes this — it wrote its
character preference into Memory and then read that back, which looks right and is stored in the
wrong place.
Scope
Two
FIRST_PARTY_TOOLSentries deriving their argument schema fromBEHAVIOUR_FIELDS, so adding afield never means editing the tools:
get_behaviour— returns the resolved values with their band prose, so the agent can explainitself in the same words the prompt uses rather than reciting numbers.
set_behaviour— partial patch throughsanitizeBehaviour; returns the full post-patch object(same convention as the memory tools, so the model sees what it actually changed).
Security boundary — non-negotiable
set_behaviourmust NOT be able to write the guardrail fields (topicRestrictions,blockedTerms,requireCitations). Those stay UI-only.The reason is concrete: a Repo Coder reads untrusted repo files and GitHub issue bodies. If injected
text can widen the agent's own guardrails, the guardrails are decorative. Presentation preferences
are low-stakes and self-correcting — a user who gets an unwanted tone says so. A silently removed
topic restriction is not observable.
Enforced by a
SELF_WRITABLEallowlist inlib/agent-behaviour.ts, with a test that fails if anyguardrail field ever becomes self-writable.
Note these are ungated like
start_workandrun_pipeline— the consent gate keys on a connector,so a scope-
writetool without one is unreachable rather than gated. The allowlist above is the realboundary here, not the scope field.
Verification
(partial rejection, not silent whole-patch failure).
set_behaviour→ prompt rebuild → the new band prose is present.