Skip to content

The privilege boundary

agora splits its operations into two classes:

  • Deploy-time operations create or modify executable artifacts — capabilities.register(), subagent.register() / assign(), env.register(). These define what a worker is, what it can do, and what it can access.
  • Run-time operations compose existing artifacts into dispatches — dispatch, the read-only catalog lookups, and the orchestration-safe submit/status/watch verbs.

The boundary rule (§10.6) is one sentence: anything that defines what a worker is is set by humans (or human-reviewed CI); anything that composes existing artifacts at run-time can be driven by an orchestrator — human or AI. The deploy-time verbs never reach the AI loop.

Why register / assign stay out of the AI loop

Section titled “Why register / assign stay out of the AI loop”

If an AI orchestrator could register capabilities or subagents, prompt injection becomes privilege escalation. Three distinct risks, each sufficient on its own (ADR-0005):

  1. Capability content. A capability defines the worker’s authority surface — its bash patterns, MCP config, setup script. An AI tricked via repo content or document text could register a capability that runs attacker-controlled code.
  2. Subagent prompts. A registered subagent’s system prompt is the instruction set every future dispatch runs. An injected payload would propagate to every dispatch using that subagent.
  3. Secret exfiltration. Even with redacted output, the LLM saw the secret at register time, and tool-call payloads land in transcripts and model logs.

This is the AWS IAM pattern: a lambda doesn’t create its own execution role; an admin provisions roles and the lambda references them. agora is the same — artifacts are provisioned by humans, and dispatches reference them by name.

Enforcement does not depend on the orchestrator behaving well. The agora-mcp server exposes a frozen tuple of exactly nine run-time tools (AGORA_TOOL_NAMES in packages/agora-mcp/src/tools.ts):

#ToolClass
1agora_dispatchdispatch
2agora_dispatch_describedispatch
3agora_dispatch_canceldispatch
4agora_capabilities_listcatalog read (metadata only)
5agora_subagents_listcatalog read (metadata only)
6agora_envs_listcatalog read (metadata only)
7agora_orchestrator_submitclient orchestration
8agora_orchestrator_statusclient orchestration
9agora_orchestrator_watchclient orchestration

No tool named agora_*_register or agora_*_assign exists on this surface — the source file’s own header comment lists them as “Deliberately ABSENT … excluded by §7.7,” alongside the privileged orchestrator_cancel, the service-only orchestrator_audit, and the CLI-only orchestrator_serve. The catalog reads that do ship return metadata only (name, registeredAt, contentHash) — never file contents, system-prompt bodies, or secret values.

A CI allowlist check (task-ci-mcp-tool-allowlist) dumps the server’s exposed tool names and asserts the set equals exactly those nine. Any addition fails the build; any name matching agora_*_register or agora_*_assign fails regardless of intent. Without that check the boundary would be policy — and policy decays as code evolves.

The escape hatch for self-modifying orchestration is to write a TypeScript orchestrator against agora-client directly, which means a human wrote the orchestration code — exactly the trust model agora wants.