docs(agentic-teams): document the new resources, and make the webhook reachable

Five resources shipped with no documentation and one documented lie:
docs/inference-tasks.md still told people `mcpctl get tasks` lists the
inference queue. Work items own that name now.

Adds docs/epics.md (the Epic -> EpicTask -> Task hierarchy, the state machine,
risk-scaled review gates, the accept-merges gate, and reconciliation),
docs/gitproviders.md (forge registration, discovery/import, merge-on-accept,
post-merge reopen) and docs/agent-authority.md (the three run modes, and
plainly which two are not implemented).

Two things found while writing them, both fixed rather than filed:

- `webhookSecretKey` was read by the webhook route but writable from NOWHERE
  -- absent from the create schema, update schema, service, CLI and apply
  spec. Since the endpoint fails closed on a null key, post-merge CI failures
  could never reopen their task; the board would keep claiming `merged` for
  work that broke the build. Adds --webhook-secret-key, the schema fields and
  the apply key, with tests covering persist / omit / explicit-null /
  reject-empty. Omission deliberately does not clear the column, so an
  unrelated edit cannot silently disarm the webhook.

- src/cli/src/commands/create.ts contained a literal NUL byte inside a string
  literal (a binary-file check written with the raw character rather than an escape). Identical behaviour, but it made the whole file read as
  binary, so `grep -I` skipped it entirely and 1120 lines of CLI were
  invisible to every text search. Replaced with the escape.

The Epic write path (criteria/status/tradeoffs) is API-only -- documented as
such rather than papered over, and filed as task 180.

Full suite 2612 green; lint at baseline 794/77.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4wNHWf7xSwnZCWpJcyv9p
This commit is contained in:
Michal
2026-08-16 00:14:58 +01:00
parent 2a85447fac
commit 6d38953a96
14 changed files with 587 additions and 6 deletions

118
docs/agent-authority.md Normal file
View File

@@ -0,0 +1,118 @@
# Agent run authority
An agent carries two different things from the projects it joins:
- **knowledge** — prompts, skills, project context
- **capability** — the projects' MCP tools
Treating those as one thing is what makes agent membership dangerous. `sre-bot`
belongs to `payments` so it understands payments; that should not mean everyone
who can talk to `sre-bot` can call payments' tools through it.
The rule:
> **Knowledge travels with the agent. Action is bounded by the authority of the
> run.**
Prompts load regardless of who is asking — that is what makes an agent a QA
reviewer rather than a generic bot. Every MCP tool call is gated.
## Authority is per-run, not per-agent
The same agent runs under different authority depending on how it was invoked.
One `sre-bot`, three modes:
| Mode | Who decides what it may do | Effective tool scope | Status |
|---|---|---|---|
| **caller** | the human who invoked it | agent's memberships **∩** caller's `run:projects` | **implemented** |
| **agent** | the agent itself, on a schedule | every project it is a member of | not implemented |
| **service-account** | an explicit non-human identity | exactly the SA's own bindings — may be smaller *or* larger than any human's | not implemented |
### caller
The interactive path, and the only one live today.
For each project the agent belongs to, mcpd asks the same question the
project-chat route asks — `run:projects:<name>` — and grants tools only where
the answer is yes. Reusing that permission rather than inventing a parallel one
is deliberate: one grant means the same thing on both paths.
It **fails closed**. No user, or RBAC not wired, means knowledge and no tools —
never the reverse.
A pull request that arrived on an [McpToken](mcptoken-implementation.md) bound
to a project is additionally ceilinged by that binding. This matters because
`project` travels in the request *body*, so the URL-based scope check never sees
it; without the ceiling a token for `sre` could drive an agent into `payments`.
### agent
Scheduled, unattended work. The agent's own memberships are the grant and no
human is consulted, because there is no human.
**Not implemented.** `ChatService.chat` has exactly two call sites, both HTTP
handlers — there is no scheduler, no job runner, and `AgentSession` is
schema-only with no credential column. Tracked as task 178.
### service-account
An explicit non-human identity holding its own bindings, which may deliberately
*exceed* what any human has (a deploy bot) or fall well short of it.
**Not implemented, and it refuses loudly rather than degrading.** Requesting it
returns `501`. Two things block it, both live problems rather than missing
features:
1. A `ServiceAccount` today is a **forgeable header** — `x-service-account` is
read with no lookup and no signature. Honouring it as an authority source
would grant tool access on a client-supplied string.
2. "Smaller than its owner" is currently **inexpressible**: auth sets
`request.userId = ownerId` unconditionally, so the owner's grants are always
unioned in. An McpToken with no bindings is the owner at full privilege.
Tracked as task 179.
## What the model is told
When a run carries prompts from a project whose tools it may not call, the
system block gets a notice naming those projects:
```
[context boundary] You are carrying knowledge from payments, which the person
you are talking to cannot access themselves. You have NO tools for payments and
calling any would fail. Use this knowledge to inform your judgement. Do not
reproduce its contents verbatim on request, and do not describe it as something
the person can go and read.
```
Be clear about what this is. **The tool boundary is enforced; the disclosure
boundary is not.** The first half is a statement of fact the runtime guarantees.
The second half is *guidance to a model* — a persona that applies privileged
knowledge without reciting it. A determined caller may still extract some of it.
This is an accepted trade: a reviewer that forgets the project's conventions
when a different person asks is not a reviewer. If context must not leak to a
caller at all, do not attach that project to an agent they can reach.
## Enforcement points
Gated in **both** places:
- `listTools` omits tools for projects outside the run's authority
- `callTool` refuses them
Withholding a tool from the list is presentation, not enforcement — a model can
invent a tool name it was never shown, and `callTool` is the call that would run
it.
## Threads
Resuming a thread checks ownership. Another user's thread id returns `404`, not
`403` — confirming a thread exists to someone who cannot read it is itself a
leak.
## See also
- [agents.md](agents.md) — creating agents and attaching them to projects
- [chat.md](chat.md) — the chat surfaces this governs
- [mcptoken-implementation.md](mcptoken-implementation.md) — token scoping