Skip to content

authority: delegated/research agents have no mechanical boundary against production systemd, GPIO, live audio, or state #281

Description

@adrianwedd

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingreliabilityReliability and error handlingsecuritySecurity-related issues

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions