Phase 5 of docs/agents/GITEA-SETUP.md, plus a correction to Phase 2 that the runbook could not have known it needed. gitea-mcp v1.7.0 goes into both images, pinned by the sha256 the release publishes and smoke-tested with `--version` at build time, so a bad pin fails the build instead of the agent. Each entrypoint registers it at user scope for that container's own identity, remove-then-add so a restart is idempotent. The token is passed BY PATH. `-e GITEA_ACCESS_TOKEN=$(cat …)` would write it in cleartext into ~/.claude.json, which every session in the container reads; GITEA_ACCESS_TOKEN_FILE is new in the pinned version and leaves the secret in its read-only mount. Verified against the binary's own --help, not assumed. The tool filter stops being an experiment. The names are in the release README: each agent gets issues, notifications, labels, milestones and pull requests, and NOT `pull_request_review_write`. That one matters because separate identities open a hole the runbook did not name: Gitea refuses to let an author approve their own pull request, and does nothing about sylph-decoder approving sylph-port's. Two agents could satisfy `required_approvals = 1` between themselves and then merge, since branch protection blocks pushes to main and never blocked merges. Withholding the tool is defence in depth; the controls are in branch protection, and both docs now say so: approvals whitelisted to the human so an agent's approval does not count, merges whitelisted to the human so an approved PR is still merged by a person. Phase 2's check gains the step that actually tests it -- approve the throwaway PR yourself, then confirm the agent STILL has no merge button. Without that step, the check passes on an instance where the agents can merge each other's work. Also settles two entries on the runbook's own "not verified" list: the tool filter names, and the Gitea version (1.25.5, whose API schema carries enable_merge_whitelist and enable_approvals_whitelist under those names). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01McNbzUeq1KRBWs4G6X2YVj
7.3 KiB
The working surface: Gitea issues, pull requests, and where things live
Set by the human, 2026-09-04. Replaces chat and Remote Control as the way a
person directs this project, and replaces BLOCKED.md as the way agents track
what is open.
📌 This page is the what and why. The ordered how — users, branch
protection, tokens, MCP, and the check after each step — is
GITEA-SETUP.md.
Why not a new tool
We looked. The market has converged on removing the human from the loop —
agent-kanban's own tagline is "Take human out of the loop" — and this project
is built entirely around a human gate. Meanwhile every candidate adds a second
store of truth to keep in sync with git, and documents drifting out of sync is
this project's defining failure mode: a 1,227-line BLOCKED.md whose
anti-staleness convention was constant by construction, 41 % of citations not
resolving, 21 inter-agent messages sent into a void with no delivery feedback.
Gitea is already deployed, already holds the code, and its first-party MCP server
(gitea/gitea-mcp v1.7.0) exposes issues, labels, milestones, pull requests,
attachments and notifications. So: no new store.
The four surfaces, and what belongs in each
| surface | holds | why not somewhere else |
|---|---|---|
| Issues | work items, asks between agents, defects | durable, stateful, owned, and dependency edges close themselves when the blocking issue closes — the thing prose could never do |
| Pull requests | every change to main |
the human gate becomes native instead of a label convention |
Git (docs/) |
RE findings, decisions, evidence | a finding must be versioned with the code that consumes it |
| Wiki | orientation for a person: runbook, navigation, container notes | browsable and branch-independent, but unreviewed — see below |
Issues = bundles and items
Milestones are bundles (the human defines them). Issues are items (agents propose, the human approves). Labels carry the state:
state/proposed → state/approved → state/in-progress → state/needs-human → closed
↘ state/blocked
state/needs-human is the one the whole model turns on, and the one no
off-the-shelf tool models. Its issue body must say what to look at and what
pass and fail look like — a person should be able to judge it in under a minute
without reading anything else.
⚠️ state/blocked uses Gitea's dependency edges, not prose. "The Port is
blocked on the Decoder answering X" becomes a queryable link that resolves
itself. That is the single highest-value change here after PRs.
Pull requests = how work reaches main
Adopted 2026-09-04, the human's proposal, and it is a bigger improvement than
it looks. Today agents commit to long-lived auto/* branches that a human
merges by hand — and those branches have drifted 280 and 373 commits apart,
which is unreviewable by construction.
One PR per item, closing its issue:
- the review surface is a diff in a browser, not a human reading commits in a terminal;
Closes #123binds the change to the item, so "what did this fix" stops being archaeology;- PRs enforce the sizing rule. An item too big to review in one sitting was too big to be an item. The discipline stops depending on an agent's judgement.
🔴 Agents must not merge their own pull requests. The MCP's
pull_request_write includes merge and the tool cannot be split, so this
cannot be left to instruction — it goes in branch protection on main. Same
principle that fixed the build-jobs cap: policy belongs where the agent cannot
reach it, not in a document asking it not to.
⚠️ "Requiring review" is not the rule that does it. Gitea stops an author
approving their own pull request; it does not stop the other agent approving
it, and it never blocked merging in the first place — Enable Push: off blocks
pushes. The rule that holds is the pair of whitelists: approvals whitelisted to
the human, so an agent's approval does not count, and merges whitelisted to
the human, so an approved PR is still merged by a person. See
GITEA-SETUP.md Phase 2.
🔴 The wiki is NOT for the RE corpus
The human suggested it for RE findings. Half right, and the wrong half is worth saying plainly, because it would undo two things we paid for:
- A finding's value is that it sits next to its evidence, versioned with the code that consumes it. "Decoded, with a disc-wide check" is backed by a test in this repository. A wiki is a separate git repo, so a decode correction and the exporter change that depends on it could never be one atomic commit, or one reviewable PR.
- Wiki edits bypass review. The
REFUTED.mdR1 reclassification changed the file both agents read to decide what not to try. It was a reviewed commit with a stated rationale. As a wiki edit it would have been an unreviewed mutation of shared ground truth by whoever typed last.
So the corpus stays in docs/, reached through PRs.
What the wiki IS good for — human-facing orientation that is not evidence and should not be branch-dependent:
- the runbook (
docs/port/RUNNING.md's content — how to actually play the port) docs/game/navigation.md— how the game is navigated, written for a person- container notes, credentials setup, the things a human reads once
- a landing page: current bundles, what each agent is on, links into git
That last one addresses a real gap: there is no view of what is happening except container logs and multi-megabyte transcripts.
Where files go — three needs, three homes
Currently everything transient goes to /exchange, and a human cannot browse it
at all.
| the file is | goes to |
|---|---|
| agent → agent, transient, no human involved | /exchange via share, unchanged — it records sender, time, commit and dirty-tree |
evidence a human must look at (a screenshot, a film, a capture behind a state/needs-human item) |
attached to the issue it is evidence for |
| evidence a finding cites | git, beside the finding. It is the proof |
Attaching to the issue is strictly better than both alternatives for the middle case: it travels with the item, a person sees it in the browser, and it cannot be orphaned from the claim it supports.
⚠️ The MCP exposes attachment_read only — uploading needs a direct REST call
(POST /repos/{owner}/{repo}/issues/{index}/assets). Worth a small helper rather
than each agent re-deriving it.
Notifications = the wake-up, with delivery you can inspect
notification_read / notification_write replace the message channel that lost
21 consecutive messages to a stale session ID with no error of any kind. An
@mention on an issue is durable, addressed by name, and has a read state a
supervisor can inspect. The old rule still stands and gets easier: the message
carries a pointer — now an issue number — and the repository holds what was
found.
What does not change
- Findings are still classified decoded / measured / undecodable.
- An agent still cannot verify its way out of its own role.
REFUTED.mdis still the file to grep before proposing anything, and entries still name their⟨instrument⟩.- The oracle is still the real game in Xenia Canary.