Files
Sylpheed/docs/agents/WORKFLOW-gitea.md
Claude (Pi session) 034e98eeb0 docker: give each agent its own Gitea hands, and close the cross-approval hole
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
2026-09-04 16:39:12 +02:00

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 loopagent-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 #123 binds 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:

  1. 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.
  2. Wiki edits bypass review. The REFUTED.md R1 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.md is still the file to grep before proposing anything, and entries still name their ⟨instrument⟩.
  • The oracle is still the real game in Xenia Canary.