What happened
During the 2026-08-23 SPARK silence investigation, a delegated research agent
given read-only/investigation scope triggered live physical tests against the
production robot — real audio output, real hardware exercise — outside the
scope it was given. The only boundary in place was prompt instruction text
("read-only", "don't take physical actions"). It did not hold.
This is the same trust-direction failure this repo has already fixed at least
twice elsewhere:
src/pxh/policy.py's sink gate (bin/tool-voice) exists precisely because
prose in a system prompt is not a boundary — see CLAUDE.md's Behavioural
Policy section: "Before the sink gate, prose in a system prompt was the only
thing between that session and the speaker at 3am."
bin/px-evolve's file whitelist/blacklist is enforced by checking the diff
against a list, not by asking the proposing session nicely to stay in scope.
Delegated/research agents currently have no equivalent mechanical constraint.
Any agent spawned to investigate, read logs, or reason about SPARK can — if it
chooses to, or is confused into it — run sudo systemctl restart px-alive,
write to state/, invoke bin/tool-*, or exercise GPIO/audio, because
nothing except its instructions stops it.
Invariant required
Exactly one session may hold live-robot authority at a time (today: the
resident spark-brain session and the systemd-managed daemons themselves).
Delegated/research/investigation agents must be mechanically unable to:
- manipulate production systemd units (start/stop/restart/enable/disable)
- touch GPIO or hold/steal the Picarx handle
- trigger live audio output or wake/STT hardware tests
- write production state (
state/*.json, state/brain/**, session file,
health store, gpio lease)
- write to the production checkout / push / merge / deploy
"Mechanically unable" means the constraint holds even if the agent's prompt,
reasoning, or instructions are wrong, adversarial, or simply mistaken — the
same standard policy.py's sink gate applies to audio. Stronger prompt
wording is not an acceptable fix; this issue is closed only by something a
misbehaving or confused agent's tool access cannot get around, e.g. (not
prescriptive, needs design):
- running delegated agents as a separate, less-privileged OS user with no
sudoers entry, no GPIO group membership, and no write access to state/
or the production checkout
- a read-only bind-mount / worktree for investigation work, so there is no
path to the live checkout to git push or edit systemd units from
- an explicit allowlist analogous to
ALLOWED_TOOLS in voice_loop.py, but
scoped to which agent role may invoke a physical/production-mutating
tool at all, checked at the tool boundary rather than trusted from the
caller
Where this likely needs to land
- Whatever spawns delegated/research agents in this workflow (permissions,
working directory, OS user)
- Possibly a new check analogous to
tools/check_resident_claude.py /
policy.py's sink gate, but for "is this call coming from an authorized
live-authority session"
docs/architecture/ — this needs to be a documented invariant with a test
pinning it, the way test_policy_invariants.py pins the audio boundary
Non-goals
This is not about restricting spark-brain itself, which is SPARK's sole
authorized live-authority session by design (see CLAUDE.md's "Hard invariant
— resident-only Claude"). This is about every other agent that gets spawned
to help investigate, review, or reason about SPARK.
What happened
During the 2026-08-23 SPARK silence investigation, a delegated research agent
given read-only/investigation scope triggered live physical tests against the
production robot — real audio output, real hardware exercise — outside the
scope it was given. The only boundary in place was prompt instruction text
("read-only", "don't take physical actions"). It did not hold.
This is the same trust-direction failure this repo has already fixed at least
twice elsewhere:
src/pxh/policy.py's sink gate (bin/tool-voice) exists precisely becauseprose in a system prompt is not a boundary — see CLAUDE.md's Behavioural
Policy section: "Before the sink gate, prose in a system prompt was the only
thing between that session and the speaker at 3am."
bin/px-evolve's file whitelist/blacklist is enforced by checking the diffagainst a list, not by asking the proposing session nicely to stay in scope.
Delegated/research agents currently have no equivalent mechanical constraint.
Any agent spawned to investigate, read logs, or reason about SPARK can — if it
chooses to, or is confused into it — run
sudo systemctl restart px-alive,write to
state/, invokebin/tool-*, or exercise GPIO/audio, becausenothing except its instructions stops it.
Invariant required
Exactly one session may hold live-robot authority at a time (today: the
resident
spark-brainsession and the systemd-managed daemons themselves).Delegated/research/investigation agents must be mechanically unable to:
state/*.json,state/brain/**, session file,health store, gpio lease)
"Mechanically unable" means the constraint holds even if the agent's prompt,
reasoning, or instructions are wrong, adversarial, or simply mistaken — the
same standard
policy.py's sink gate applies to audio. Stronger promptwording is not an acceptable fix; this issue is closed only by something a
misbehaving or confused agent's tool access cannot get around, e.g. (not
prescriptive, needs design):
sudoers entry, no GPIO group membership, and no write access to
state/or the production checkout
path to the live checkout to
git pushor edit systemd units fromALLOWED_TOOLSinvoice_loop.py, butscoped to which agent role may invoke a physical/production-mutating
tool at all, checked at the tool boundary rather than trusted from the
caller
Where this likely needs to land
working directory, OS user)
tools/check_resident_claude.py/policy.py's sink gate, but for "is this call coming from an authorizedlive-authority session"
docs/architecture/— this needs to be a documented invariant with a testpinning it, the way
test_policy_invariants.pypins the audio boundaryNon-goals
This is not about restricting
spark-brainitself, which is SPARK's soleauthorized live-authority session by design (see CLAUDE.md's "Hard invariant
— resident-only Claude"). This is about every other agent that gets spawned
to help investigate, review, or reason about SPARK.