From 1f138b3db4c01d57252e1551e65b288b6225b231 Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Fri, 4 Sep 2026 15:46:57 +0200 Subject: [PATCH] agents: move the working surface to Gitea -- issues, PRs, and where files live MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The human wants to direct this project from a web UI rather than chat or Remote Control, so Gitea becomes the working surface. No new store: adding a second copy of the truth is this project's defining failure mode, and Gitea already holds the code. Its first-party MCP server (gitea/gitea-mcp v1.7.0, checked) has issues, labels, milestones, PRs, attachments and notifications. ISSUES replace BLOCKED.md. Milestones are bundles the human defines; issues are items agents propose and the human approves. The state labels end in `needs-human`, which is the state the whole model turns on and the one no off-the-shelf tool models -- the market has converged on removing the human. `blocked` uses Gitea's DEPENDENCY EDGES rather than prose, so "the Port is blocked on the Decoder answering X" becomes queryable and closes itself. PULL REQUESTS, the human's proposal, adopted -- and a bigger improvement than it looks. Today's long-lived auto/* branches have drifted 280 and 373 commits apart, which is unreviewable by construction. One PR per item makes the human gate NATIVE rather than a label convention, binds the change to its item, and enforces the sizing rule: an item too big to review in one sitting was too big to be an item. πŸ”΄ Agents must not merge their own PRs, and pull_request_write includes merge -- so this goes in BRANCH PROTECTION on main, not in a document asking them not to. Same principle that fixed the build-jobs cap: policy where the agent cannot reach it. WIKI -- the human suggested it for RE findings, and that half is declined with reasons. A finding's value is that it sits beside its evidence, versioned with the code that consumes it; the wiki is a separate git repo, so a decode correction and the exporter change depending on it could never be one reviewable PR. And wiki edits bypass review: the REFUTED.md reclassification changed the file both agents read to decide what not to try, and as a wiki edit it would have been an unreviewed mutation of shared ground truth. The wiki takes human-facing orientation instead -- runbook, navigation, container notes, and a landing page, which closes the real gap that there is no view of what is happening except container logs. FILES: three needs, three homes. Agent-to-agent transient stays in /exchange. Evidence a HUMAN must look at attaches to the issue it belongs to -- it travels with the item and cannot be orphaned from the claim. Evidence a finding cites stays in git. Note the MCP exposes attachment_read only; upload needs a direct REST call. tools/gitea-setup creates the labels and bundles, idempotently, with --dry-run. Blocked on a token with write:issue -- the push credential is write:repository and every issue endpoint refuses it, checked rather than assumed. --- docs/agents/WORKFLOW-gitea.md | 133 ++++++++++++++++++++++++++++++++++ tools/gitea-setup | 119 ++++++++++++++++++++++++++++++ 2 files changed, 252 insertions(+) create mode 100644 docs/agents/WORKFLOW-gitea.md create mode 100755 tools/gitea-setup diff --git a/docs/agents/WORKFLOW-gitea.md b/docs/agents/WORKFLOW-gitea.md new file mode 100644 index 00000000..e645999c --- /dev/null +++ b/docs/agents/WORKFLOW-gitea.md @@ -0,0 +1,133 @@ +# 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. + +## 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 #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`, so this cannot be left to instruction β€” +it goes in **branch protection on `main`**, requiring review. Same principle that +fixed the build-jobs cap: policy belongs where the agent cannot reach it, not in +a document asking it not to. + +### πŸ”΄ 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. diff --git a/tools/gitea-setup b/tools/gitea-setup new file mode 100755 index 00000000..ebff4650 --- /dev/null +++ b/tools/gitea-setup @@ -0,0 +1,119 @@ +#!/usr/bin/env bash +# Create the work-item structure in Gitea: labels, milestones, and the board. +# +# tools/gitea-setup create anything missing (idempotent) +# tools/gitea-setup --dry-run say what it would create, change nothing +# +# Needs a token with `write:issue`. The existing git credential is scoped +# `write:repository`, which pushes fine and is REFUSED by every issue endpoint -- +# checked, not assumed: +# +# {"message":"token does not have at least one of required scope(s), +# required=[read:issue], token scope=write:repository"} +# +# So this reads a SECOND token from ~/.sylph-gitea-api-token, deliberately +# separate from the push credential: different blast radius, and rotating one +# does not break the other. +# +# ── Why Gitea rather than a new tracker ───────────────────────────────────── +# +# The failure this replaces is a 1,227-line hand-maintained `BLOCKED.md` whose +# anti-staleness convention turned out constant by construction, plus 21 +# inter-agent messages sent into a void with no delivery feedback. Both are +# solved by items that live in a database with state, an owner and dependency +# edges -- and Gitea is already deployed here, so it adds no second store to +# drift out of sync with the first. That drift is this project's defining +# failure mode; adding a tool with its own copy of the truth would be choosing +# more of it. +set -euo pipefail + +HOST="${SYLPH_GITEA_HOST:-git.mc02.dev}" +REPO="${SYLPH_GITEA_REPO:-fabi/Sylpheed}" +TOKFILE="${SYLPH_GITEA_API_TOKEN:-$HOME/.sylph-gitea-api-token}" +DRY=0; [ "${1:-}" = "--dry-run" ] && DRY=1 + +[ -f "$TOKFILE" ] || { + cat >&2 < Applications -> Generate New Token + Scopes needed: write:issue (and read:repository, to see the repo) + Then: echo '' > $TOKFILE && chmod 600 $TOKFILE + + This is NOT the push credential. That one is scoped write:repository and is + refused by every issue endpoint. +EOF + exit 2 +} +TOK=$(tr -d '[:space:]' < "$TOKFILE") +API="https://$HOST/api/v1/repos/$REPO" +AUTH="Authorization: token $TOK" + +api() { curl -sS --max-time 30 -H "$AUTH" -H 'Content-Type: application/json' "$@"; } + +# Fail loudly and specifically on the one error everyone hits. +probe=$(api "$API/labels" || true) +case "$probe" in + *'required scope'*) + echo "πŸ”΄ the token at $TOKFILE lacks issue scope:" >&2 + echo " $probe" >&2 + echo " Regenerate it with write:issue." >&2 + exit 2 ;; + '') echo "πŸ”΄ no response from $API -- host or network" >&2; exit 2 ;; +esac + +say() { [ "$DRY" = 1 ] && echo " would create $*" || echo " created $*"; } + +# ── Labels ────────────────────────────────────────────────────────────────── +# The state set encodes the working model the human set on 2026-09-02: a human +# defines a bundle, agents decompose it, and each item ends in a HUMAN check. +# `needs-human` is the important one -- it is the state the whole model turns on +# and the one no off-the-shelf agent tool models, because the market has +# converged on removing the human rather than gating on them. +existing=$(printf '%s' "$probe" | python3 -c "import json,sys;print('\n'.join(l['name'] for l in json.load(sys.stdin)))" 2>/dev/null || true) +mklabel() { # name colour description + printf '%s\n' "$existing" | grep -qxF "$1" && return 0 + if [ "$DRY" = 0 ]; then + api -X POST "$API/labels" -d "$(python3 -c " +import json,sys; print(json.dumps({'name':sys.argv[1],'color':sys.argv[2],'description':sys.argv[3]}))" "$1" "$2" "$3")" >/dev/null + fi + say "label $1" +} + +mklabel "state/proposed" "d4c5f9" "Agent proposed this item; awaiting the human's approval to start" +mklabel "state/approved" "0e8a16" "Human approved the shape; an agent may start" +mklabel "state/in-progress" "1d76db" "An agent is working it now" +mklabel "state/needs-human" "fbca04" "Done as far as an agent can tell -- a person must look. The body says what to look at" +mklabel "state/blocked" "b60205" "Waiting on another item; use the Depends-On field, not prose" +mklabel "agent/decoder" "5319e7" "Owned by the Decoder (disc to meaning; runs the emulator)" +mklabel "agent/port" "006b75" "Owned by the Port (disc to playable; no RE)" +mklabel "kind/bundle" "c2e0c6" "A bundle the human defined; agents decompose it into items" +mklabel "kind/item" "bfd4f2" "One unit of work, small enough to finish in a single session" +mklabel "kind/ask" "e99695" "One agent asking the other for something it cannot answer in role" +mklabel "kind/defect" "d93f0b" "Found by a play-test or a check" + +# ── Milestones = bundles ──────────────────────────────────────────────────── +ms=$(api "$API/milestones?state=all" | python3 -c "import json,sys;print('\n'.join(m['title'] for m in json.load(sys.stdin)))" 2>/dev/null || true) +mkms() { + printf '%s\n' "$ms" | grep -qxF "$1" && return 0 + if [ "$DRY" = 0 ]; then + api -X POST "$API/milestones" -d "$(python3 -c " +import json,sys; print(json.dumps({'title':sys.argv[1],'description':sys.argv[2]}))" "$1" "$2")" >/dev/null + fi + say "milestone (bundle) $1" +} +mkms "Menus" "The menu shell: title, main menu, submenus, navigation, audio." +mkms "Title screen" "Title timing and animation: the sweep onset, the plate, what (A) does." +mkms "Graphics pipeline" "Decoder: disc -> decode -> per-frame update -> submitted draws -> Canary -> screen." +mkms "Infrastructure" "Containers, supervision, auth, work tracking. Not game work." + +echo +if [ "$DRY" = 1 ]; then + echo "dry run -- nothing was created." +else + echo "labels and bundles are in place at https://$HOST/$REPO/issues" + echo + echo "Remaining, by hand in the web UI (the API for Projects lags it):" + echo " Projects -> New Project -> columns:" + echo " Proposed | Approved | In progress | Needs human | Done" +fi