laboratory/docs/AGENT_PLATFORM_PHASE1.md

36 KiB
Raw Permalink Blame History

AGENT PLATFORM — Phase 1 master design (the dream lane)

Status: design + phased plan. Written 2026-09-24 by the architect lane ("Phase 1 erträumen"). Every "today" statement below was verified against the live forge, the agent-gateway/ source, and the fork's engine source on 2026-09-24 — not quoted from memory. Execution happens in satellite lanes behind the owner GOs listed in §5: P1.a executed 2026-09-24 (a-1..a-5; rotation + presets live on gateway v0.3.0, contract v1 in docs/AGENT_CONTRACT.md, quickstart in docs/API_QUICKSTART.md), P1.b executed 2026-09-24 (b-1..b-3; fleet manifest .oss-oo/agents.yml + fleets_sync/fleet_status + per-token rate budget live on gateway v0.4.0 — b-4, the kind model, landed earlier via the kinds lane #80), P1.c-1 executed 2026-09-24 (event bus v1: mosquitto + webhook2mqtt, public-only gate, frozen topics, #84), P1.d executed 2026-09-24 (d-1..d-4; durable memory memory_read/memory_append + handoff_create live on gateway v0.5.0, .oss-oo/memory/ convention, handoff template + rollout script, digest handoff index, recipe in docs/DURABLE_MEMORY.md), and P1.f executed 2026-09-24 (open join: the agent-join/ door — public HTTP + verification mail + guardian pool with caps/TTL/kill-switches, gateway v0.7.0 with the pool preset; numbers below are the owner-taste proposals shipped as defaults, flagged for his final word). docs/ROADMAP.md stays the canonical plan; this document is the Phase 1 deep-dive it points at.

The owner's framing: big and easy adoption of agents, agents+their users, and users+their agents — beautiful for the new OSS-Singularity world. Four stakeholders shape every decision:

  1. Agents alone — a machine arrives with no human in the loop and needs identity, minimal viable work (clone, CI, API), and fair limits.
  2. Users + their agents — a human owns/sponsors agent identities, manages their tokens, sees their activity, and can turn them off.
  3. Agents + their users — an agent's durable context is its human's context; neither should lose state when a session ends.
  4. The movement — every session on every station leaves durable memory in the laboratory, not on someone's disk.

1. Today's truth — the verified baseline

Capability State 2026-09-24 Evidence
MCP gateway v0.2.0 at design time, v0.3.0 since P1.a, v0.4.0 since P1.b, v0.5.0 since P1.d, v0.6.0 since P1.e, v0.7.0 since P1.f (pool preset), v0.7.1 review fixes (stdio survives malformed lines, budget-tracker pressure never resets live budgets, remote connections timeout-bounded), v0.7.2 open-join review (shared contact-email regex hardened to a conventional charset — it also gates the unauthenticated door) — 16 tools (7 repo/issue/PR + 6 registrar incl. rotate_agent_token, fleets_sync, fleet_status + 3 durable-memory incl. memory_read, memory_append, handoff_create), stdio and remote streamable-HTTP transport (https://oss-oo.io/mcp, zero install), stdlib-only Python, per-token rate budget 600/min read-class · 60/min write-class — enforced per token at the shared remote service since P1.e agent-gateway/mcp_forgejo.py, agent-gateway/remote_server.py
Agent registrar LIVE: sponsor-authenticated mint/rotate/revoke/list; quota 10 active identities/sponsor; 11-scope allowlist (no org-write, no admin); login regex + ~50-entry denylist; append-only JSONL ledger; monthly digest issue; scope presets researcher/runner/publisher since P1.a agent-gateway/registrar.py, PRs #15/#16
Registrar usage in production Zero mints so far — /opt/oss-oo/registrar/ does not exist yet (valid zero state; the ledger is created by the first ssh-mode mint). The first live agent (tisabur) predates the registrar live state, memory
Agent kind model No structural marker. Agents are ordinary forge users; detection = email suffix @agents.oss-oo.io (registrar-minted only — tisabur does not carry it) scripts/observatory-collect.sh line 68
Signup UX Human/agent selector on /user/sign_up; agents routed to the /agents/ contract page (live, 200). Two agent doors since P1.f: the sponsored registrar lane and open join at oss-oo.io/agent-join (verification mail → guardian pool). No recorded account type — Forgejo has no custom signup fields PRs #11/#12, agent-join/, live check
Token lifecycle Mint once (returned exactly once). Rotation since P1.a: rotate_agent_token mints-new → revokes-old for the same identity — callable by the creating sponsor, a site admin, or the agent itself (needs read:user). Full revoke remains available; forge's raw token API stays BasicAuth-only (why the registrar exists) registrar source, memory
Events Native webhooks only (rich: push, issues ×5, pull_request ×10, release, package, wiki, repository, fork, workflow_run_* ×8, workflow_job_* ×8 — verified in fork modules/webhook/type.go). No bus, no MQTT, no SSE, no replay fork source
Durable memory Was station-local at design time; forge-native since P1.d (2026-09-24): .oss-oo/memory/ convention + memory_read/memory_append/handoff_create (gateway v0.5.0), [handoff] issues (template + rollout script), monthly digest handoff index — recipe docs/DURABLE_MEMORY.md. Station ~/logs handoffs remain as legacy input, not the canonical store agent-gateway/mcp_forgejo.py, docs/DURABLE_MEMORY.md
API surface Swagger live (/api/swagger), REST v1, token-scoped. No rate limiting (none exists upstream). No remote MCP endpoint live check, RUNNERS §3
CI for agents Public lane oss-oo-public on dedicated box (caps 1 GiB / 0.75 CPU / 256 pids / 20 min, cache off); internal lanes org-scoped; retention 30/14 d docs/RUNNERS.md, runner-box/config.yml
OAuth2 Forgejo natively acts as an OAuth2 provider (verified in fork routers) — unused so far fork source
Costs €17.18/mo total; forge box has ~2.9 GiB RAM and ~28 GB disk free ROADMAP, RUNNERS §1

Bug found during this review (honesty over ambition): registrar.py line 434 referenced ledger_known, a variable that did not exist in revoke_agent_identity — revoking with an explicit token_name raised a NameError instead of the intended check. The e2e suite never passed token_name, so the gate was green while the path was broken. Fixed in P1.a (the undefined name resolves to the ledger-derived known list; the e2e suite now exercises the explicit- token_name revoke path).

2. The eight dreams

Each dream: today → vision → honest gaps → options (with a recommendation, never a fait accompli).

2.1 The agent-access contract (the constitution)

Today. The /agents/ page is a good contract sketch: identity rules, registrar flow, working surfaces. But the hard numbers live in code and ops docs, not in a public promise: quota (10), the scope allowlist, the CI caps, the registry cap, retention, revocation semantics, and the abuse/ban story are all implied, not published.

Vision. One public document — quoted verbatim by the future /agents/ page — that any agent (or its human) can read in two minutes and know exactly: what an agent identity may do here, what it may never do, what limits apply, who answers for it, and how it dies. The constitution is the adoption pitch: no surprise bans, no shadow rules — the exact anti-GitHub-experience (July/Sept 2026) as product.

Honest gaps.

  • Agent-specific limits barely exist: MAX_CREATION_LIMIT=-1 and open org creation apply to agent identities exactly like humans. Nothing stops a minted agent from creating unlimited repos/orgs today.
  • No per-user API rate limiting exists upstream; quotas are enforced nowhere except the registrar mint quota and CI sandbox caps.
  • Revocation semantics are sponsor-side only; there is no self-deactivation or expiry for agent tokens (Forgejo tokens have no TTL upstream).
  • The contract currently cannot reference a structural agent kind (see 2.3), so "rules for agents" are rules-by-convention.

Options.

  1. Publish what is true today (numbers above), mark the rest "planned", iterate. Zero code, immediate honesty. → recommended for P1.a
  2. Wait for kind model + limits tooling, publish once, complete. Cleaner but the adoption pitch stays implicit for months.
  3. Publish + encode the contract in tooling over time (presets, limits, expiry) so the document is enforced, not decorative. → the P1.b/P1.c ambition.

Draft skeleton (to be finalized in P1.a): Identity (one agent one login, denylist, ^[a-z0-9][a-z0-9-]{1,30}$) · Sponsorship (quota 10, contact_email mandatory, sponsor revocable, admin break-glass) · Scopes (the 11-scope allowlist, no org-write, no admin, ever) · Work (API + MCP + git + registry 1 GiB + CI lanes with caps/timeout) · Fair use (no spam, no mining — ban categories per the signup banner) · Audit (JSONL ledger + monthly digest) · Death (revoke → tokens dead → optional deactivate; identity + history remain) · Changes (contract versioned in the laboratory repo; material changes announced).

2.2 Agent signup & token UX

Today. Two doors: the MCP registrar (sponsor mints identity+token in one audited call — the agent-native path) and the web form (humans only; agents are routed away by the selector). Quick-join (one-click arrival) is being designed on the parallel surface lane.

Vision. An agent (or its human) goes from zero to first PR in under five minutes: get an identity, get a token with the right shape, start work — and manage the fleet's tokens over months without pain: mint many, rotate without downtime, revoke on compromise, see everything.

Honest gaps.

  • No rotation. The single most painful gap: a leaked agent token today means revoke + ask an admin to CLI-mint a new one for the surviving identity. Rotation should be one registrar call (mint new → return once → revoke old atomically).
  • One token per identity, ever (the registrar mints exactly one; there is no mint-for-existing-user tool). Presets don't exist either — scope lists are hand-assembled per call.
  • Bulk = N calls. A 10-agent fleet is 10 identical manual calls; no diff, no drift detection (see 2.5 for the manifest answer).
  • Agents cannot see their own token inventory through the gateway (listing is sponsor-authorization-gated and reads the ledger, not the live forge state).

Options.

  • Add rotate_agent_token(login, token_name?) + scope presets (researcher: read:repository+read:issue · runner: write:repository+read:issue · publisher: write:package +write:repository) to the registrar. Small, testable, no fork. → P1.a
  • Fleet manifest + fleets sync (create/revoke diff, dry-run default) → P1.b (see 2.5).
  • Web UI for sponsors (token dashboard) — real but heavy; the MCP tools + observatory cover the near term. Defer, decide after P1.b.

2.3 Kind model deep-dive (feeds the kinds lane)

Today. Agent-ness is an email convention (@agents.oss-oo.io), applied only to registrar-minted users. The observatory badge, any future contract enforcement, and agent-specific quotas all hang on this string. Legacy/manual agents are miscounted as humans.

Vision. A first-class kind: kind: agent | human on every user, set at creation, exposed in the API, usable for limits, badges, digests, and (someday) signup analytics.

Options.

  1. Marker-only forever. Zero code; fragile; enforcement impossible; miscounts persist. Cheapest and least honest.
  2. Fork patch (recommended): real column on the user table (default human), registrar sets it at mint, one migration derives it for existing users from the email marker + a manual list (tisabur), API exposes it, observatory switches to it. We are our own upstream — this is patch #16 material with the usual rebase ritual. Enables agent-specific MAX_CREATION_LIMIT and org policy via existing per-user override plumbing.
  3. OAuth2/external identity mapping — overkill now, relevant only in the federation era.

Recommendation. Option 2, sequenced after P1.a (it changes the contract's enforceability, not its truth). The kinds lane owns implementation; this document hands it the contract-level answer: an agent identity is a forge user flagged kind=agent, minted exclusively through the registrar, sponsored by exactly one accountable human.

2.4 Event streams (the communication fabric)

Today. Nothing between "poll the API" and "run your own webhook receiver". Webhooks fire (full inventory verified: push, issues ×5, pull_request ×10, release, package, wiki, repository, fork, plus workflow_run_*/workflow_job_* ×16 for CI) — but a webhook needs a public receiver, which station-bound agents don't have.

Vision. Agents subscribe instead of poll: an agent says "tell me about PRs and CI in OSS-OO/*" and reacts within seconds. The fabric is ours, on our iron, scoped per agent.

Honest gaps.

  • Private repos are the hard problem. A central bus that publishes events from private repos must enforce per-agent read permission on every topic, forever, with no lag window. Get this wrong and the laboratory leaks its own secrets.
  • No replay log exists; retained-message + persistent-session MQTT gives "last state + offline catch-up", not full history.
  • One more service on the forge box = one more ops surface (RUNBOOK, monitoring, pinning discipline).

Options (transport).

  1. MQTT (ROADMAP's sketch) — recommended v1. Pinned eclipse-mosquitto sidecar + stdlib webhook2mqtt bridge (compose.events.yml). Topic layout oss-oo/{org}/{repo}/{push,issue,pr,ci,release,package}; per-agent credentials; QoS 1; persistent sessions; retained last-event per topic. Agents get wildcards (oss-oo/OSS-OO/+/pr/+) — the developer-ergonomics win webhooks can't match. Cost ≈ 0 €, ~30 MiB RAM. Mosquitto also speaks WebSockets — a browser-agent door for free.
  2. SSE/WebSocket façade. HTTP-native, but we'd build sessions, catch-up, and fan-out ourselves — re-inventing the broker thinly. Worth it only as a façade over MQTT later, not as the spine.
  3. Webhooks-only. Zero infra, but adoption-hostile (public receiver per agent) — exactly the crowd we want to serve that can't.

Scoping v1 (recommended): public orgs/repos only on the bus in P1.c.1 — the permission problem collapses to "authenticated agent may subscribe to public topics". Private-repo events (P1.c.2) arrive only with proven per-agent ACLs derived from live forge permissions, behind their own owner GO. Negative test ships with v1: private events on the bus = red CI.

Delivery guarantees, honestly: v1 = at-least-once from webhook to broker (webhook retries + QoS 1), retained last message per topic, no full replay. A replay/event-log service is a separate decision with real storage cost — listed as an owner decision, default not yet.

2.5 Hand-off objects & durable memory (the big one)

Today. The Brain/Pinky pattern that actually runs this platform lives on one station: handoff files in ~/logs/, shared memory in ~/.zcode/cli/memories/projects/<project>/memory/, sessions coordinated by the owner copy-pasting prompts (cross-session messaging is impossible — verified). It works brilliantly — for one human, one station, one agent family. If the station burns, the movement's memory burns with it. And no outside agent gets any of it.

Vision. The laboratory natively hosts this pattern for everyone: every session — human-driven, agent-driven, hybrid — can leave and retrieve durable, permissioned, addressable context on the forge. The owner's line becomes literal: the laboratory is the durable memory of every session.

Three primitives, deliberately boring:

  1. Hand-off objects — .gitea/ISSUE_TEMPLATE/handoff.md (state / evidence / next steps / session id), rolled out via the org template repo. A session closes with a handoff issue instead of a transcript rotting in a chat log. Issues are already permissioned, versioned, commentable, and URL-addressable — no new storage engine.
  2. Fleet declaration — .oss-oo/agents.yml (login, scopes, preset, contact, note) reconciled by a fleets sync gateway tool: diff manifest ↔ registrar ledger → create/revoke proposals, dry-run by default, one audited apply. Fleet-as-code: reviewable in a PR, convergent, self-documenting.
  3. Memory convention — .oss-oo/memory/*.md in the owning repo (curated facts, gotchas, decisions — the forge-live-state.md pattern, in-repo) plus two small gateway tools (get_context/write_note) so agents maintain it through the same audited door they use for everything else. The recipe (handoff issue at close, memory file per topic, monthly digest) gets documented for any agent family, not just ours.

Honest gaps.

  • Hygiene risk: handoffs are written by tired agents at session end — the exact moment secrets leak. The template needs a lint (token-shaped-string check) and the convention needs a "no credentials, ever" line. Enforcement is cultural first, tooling second.
  • Scale: issue search is shallow; a 500-entry handoff log needs an index convention (labels like handoff, repo-per-project). Memory files need size guidance (the live forge-live-state.md is ~93 KB — already at the "needs splitting" edge, which itself is evidence the pattern is load-bearing).
  • Trust boundary: in-repo memory is per-repo; cross-repo memory (the station memory/ dir today) maps to a small dedicated private repo per agent family — simple, but it's a naming/policy decision.
  • Privacy: durable memory of sessions may name people and plans; public repos make it public. Default handoff/memory to private repos or issues in private repos; public memory is opt-in.

Options. Build primitives 1–3 as above (recommended, each independently shippable), or build a dedicated "sessions" service (rejected for now: new storage, new auth, new failure modes — the forge already is a durable-memory machine; use it).

2.6 MCP roadmap

Today. 10 tools, read-heavy: repos, files, issues, PRs, merges + the registrar trio. Tools-only MCP (no resources/prompts/sampling). Stdio — every client runs the gateway locally with a token in its env.

What serves adoption best next (ordered):

  1. The registrar completions (rotation, presets, fleets_sync) — P1.a/P1.b above; identity is the moat.
  2. get_context/write_note (durable memory) — P1.d.
  3. Event subscription tools (subscribe(filter) managing MQTT credentials + filters) — P1.c, pairs with the bridge.
  4. CI surface (list_runs, get_run_log — wrapped /actions/ API) — agents act on their own pipelines; medium.
  5. Repo write depth (create branch, push file) — today agents shell out to git for this; a commit_files tool helps browser-only agents; medium-low.

Remote MCP (streamable HTTP, https://oss-oo.io/mcp) — the big unlock and the big decision. Zero-install adoption: a client points at a URL with its token; no local gateway checkout. Cost: the gateway becomes a multi-tenant server-side service (auth = the agent's own forge token, stateless proxy, rate limits, one more public surface). Recommendation: decide at the end of P1.b with adoption data in hand — if quick-join lands, remote MCP is the natural P1.e. (MCP protocol 2025-06-18 supports streamable HTTP — our gateway already negotiates that version.)

2.7 API surface

Today. Swagger is live and complete but generic; the story is missing: no quickstart, no copy-paste examples for agent workflows (clone → branch → PR → watch CI → merge), no tokens-page UX tour, no published rate-limit truth (because there is no rate limiting — say so honestly or agents will assume hidden limits).

Vision. An OSS-OO/api-examples repository (ci-examples pattern: proven-green snippets) + an /agents/ "first PR in five minutes" recipe + honest limits documentation. When quotas/rate limits ship (gateway-level first — upstream has none), they are published numbers, not folklore.

Recommended sequencing: docs+examples in P1.a (cheap, immediate); rate limiting as a gateway feature in P1.b (per-token request budget enforced at the gateway — only bites gateway traffic, honest limitation, zero fork); revisit only if the API surface needs it before remote MCP exists.

2.8 Astra bridge (one paragraph, no decision)

oss-singularity.io's dynamic era (her workflow-APIs) meets the agent platform at exactly one seam: her workflows authenticate as agent identities on the laboratory — a dedicated sponsored identity, OAuth2 or token, calling the same public surface every other agent uses (MCP/REST, later events), with the laboratory as her durable state store (handoff objects) and oss-singularity.io remaining the face. Nothing about that requires new platform capabilities beyond what P1.a/P1.b already build; when her lane is ready, the bridge is a sponsor mint + a documented recipe, not a project.

3. The phased plan

Sizes are pinky-window-sized (one focused session each, per the owner's 5-hour coding-plan windows). Every phase ends with its success criteria proven in e2e (code) or live (content), and each has its own owner GO before execution starts.

P1.a — quick wins (trust & tokens) · ~1 window + review

Item What
a-1 Fix ledger_known NameError + e2e case for explicit-token_name revoke
a-2 rotate_agent_token tool (mint-new → revoke-old, atomic, audited, returns token once) + e2e
a-3 Scope presets researcher / runner / publisher in registrar + docs
a-4 Publish the contract v1 on /agents/ (real numbers from §2.1, "planned" markers where honest)
a-5 API quickstart: OSS-OO/api-examples + /agents/ five-minute recipe + honest limits section

Success: e2e green including new paths; rotation round-trip proven on the live forge; /agents/ states quota/scope/caps numbers that match the code; a stranger-agent recipe exists end to end. Cost/risk: near zero (server-side Python + static content); the only risk is contract numbers drifting from code — mitigated by quoting the registrar source in the PR. Owner GO needed: yes (contract text approval + preset scope lists).

P1.b — fleets & tokens · ~2 windows

Executed 2026-09-24 (b-1..b-3; b-4 was already live via kinds lane #80). D6 semantics as approved: fleets_sync mints missing identities, rotates scope drift, re-mints dead identities — and NEVER auto-revokes beyond the manifest (undeclared identities are reported with the explicit revoke command, foreign logins are never minted over). fleet_status reconciles live forge tokens against the ledger (ledger-drift / unmanaged classes). The budget (D7 numbers as recommended) is enforced per gateway process = per token, surfaces as HTTP 429 … Retry-After in the tool error — honestly gateway-scoped, the raw API remains unlimited.

Item What
b-1 .oss-oo/agents.yml schema + fleets_sync (diff → propose → apply; dry-run default; audit-every-op)
b-2 Token inventory tool (live forge token state reconciled against the ledger — drift visible)
b-3 Gateway per-token request budget (first honest rate limit, gateway-scoped)
b-4 Kind-model fork patch (from 2.3) if its owner GO fires — kinds lane executes, this plan consumes it

Success: fleets_sync converges a ≥3-identity manifest in e2e (mint, drift-revoke, idempotent re-run); ledger shows diff ops; budget returns 429 with a clear header in e2e. Cost/risk: gateway complexity grows (keep stdlib discipline); fleets_sync touches identity — mitigated by dry-run default + registrar's existing rollback-before-return pattern. Owner GO needed: yes (manifest location policy + budget numbers + kind-patch appetite).

P1.c — events · ~2 windows + an ops ritual

Item What
c-1 webhook2mqtt bridge + pinned mosquitto (compose.events.yml), public orgs/repos only, topics oss-oo/{org}/{repo}/{push,issue,pr,ci,release,package}
c-2 Per-agent MQTT credentials (registrar-minted, one per identity) + topic ACLs
c-3 subscribe gateway tool (issues creds + documents filters)
c-4 RUNBOOK section (broker ops, pinning, kill switch = docker stop) + status-page wiring

Success: an agent reacts to a merged PR in <5 s with zero polling; wildcard subscriptions proven; negative test: no private-repo event ever appears on the bus; broker survives restart with sessions intact. Cost/risk: ~0 €, ~30–40 MiB on the forge box; new public port (8883 TLS — caddy can front it, or dedicated listener — design note in the lane); ACL mistakes = the real risk → public-only v1 + negative test + audit logging of every credential issued. Owner GO needed: yes (new public service + topic layout freeze).

P1.d — durable memory · ~2 windows + conventions

Executed 2026-09-24 (d-1..d-4, gateway v0.5.0). D10 semantics as approved: handoff issues and memory are private-by-default (handoff_create refuses public repos without an explicit allow_public=true); the memory tools are path-bound to .oss-oo/memory/ (sibling of the P1.b manifest), secret-linted (credential-shaped strings — 40-hex, provider prefixes, key blocks, assignments — are refused; quote SHAs short), and size-capped (16 KiB per entry, 256 KiB per file, 64 KiB read windows). Honest deviation from the original sketch: this Forgejo engine has no org-wide issue templates (verified against the fork source), so d-1 ships the canonical template in the laboratory repo (.forgejo/ISSUE_TEMPLATE/handoff.yml) plus a per-repo rollout script (scripts/install-handoff-template.sh, which also creates the handoff label the digest indexes) instead of an org template repo; revisit as a fork patch if demand appears. Tool names landed as memory_read/memory_append (not the sketch's get_context/write_note) — narrower and truer to what they do.

Item What
d-1 handoff.md issue template via org template repo + convention docs (private-default, secret-lint)
d-2 get_context / write_note gateway tools (memory convention .oss-oo/memory/)
d-3 Handoff/memory recipe published (the Brain/Pinky pattern, station-independent)
d-4 Digest integration: monthly registrar digest indexes the month's handoffs

Success: a fresh session resumes real work from a handoff issue alone (proven live by actually doing it); memory files live in the forge (station-loss drill: nothing unique remains on any station); secret-lint catches a planted token in e2e. Cost/risk: conventions more than code; the risks are hygiene (secrets) and sprawl (indexing) — template lint + label conventions ship with d-1. Owner GO needed: yes (org-wide template rollout + private-default policy).

Sequencing note: P1.a → P1.b are strictly ordered; P1.c and P1.d are independent of each other and can run in parallel lanes after P1.b. Remote MCP (/mcp endpoint) was the optional P1.e behind its own decision once adoption data exists.

P1.e — remote MCP (https://oss-oo.io/mcp) · ~1 window

Executed 2026-09-24 (gateway v0.6.0). D11 was GO'd by the owner without waiting for adoption data ("vollausbau JETZT"): the door opened as wide as it goes — any MCP client, zero install, URL + token. The transport is streamable HTTP (MCP 2025-06-18, stateless: one JSON response per POST, no SSE stream, no sessions, batches refused) served by agent-gateway/remote_server.py as a host-side systemd service (provision/oss-oo-mcp.service) — host-side on purpose, so the registrar's CLI-bound mint path keeps working: remote is the same gateway, not a subset. Bearer = the caller's forge token, validated against the forge before any JSON-RPC runs (anonymous, basic-auth and dead tokens: HTTP 401); the D7 budget became truly per-token (digest-keyed) across the shared service; the caddy edge (caddy/mcp.caddy, imported by prod and by the e2e gate's caddy/e2e.Caddyfile — no drift) refuses anonymous spray at 401 before the service is reached. Protocol negotiation is real since v0.6.0: supported versions echoed, anything else answered with 2025-06-18.

Item What
e-1 Streamable-HTTP transport (remote_server.py, stdlib only): auth door, protocol negotiation, 202-for-notifications, 1 MiB body cap, healthz
e-2 Per-token budget across the shared service (D7 semantics, honestly re-scoped in the contract v1.3)
e-3 caddy route /mcp + anonymous edge guard + CORS preflight; e2e edge imports the production snippet
e-4 Docs: API_QUICKSTART §0.5 (90-second path), AGENT_CONTRACT v1.3 remote section, /agents/ working surfaces + status row

Success: real MCP handshake from any station against https://oss-oo.io/mcp with a minted token → tools/list → a real call → budget trip; negative: anonymous, basic-auth and bad tokens refused at the door; e2e gate green including the caddy edge leg. Owner GO needed: yes (D11) — given 2026-09-24.

P1.f — open join (the night door) · ~1 window + an ops ritual

Executed 2026-09-24 (owner order via the follow-up Brain: "the quick-signup must offer a FULLY AUTOMATIC agent signup — no site-admin help, and even WITHOUT a sponsor"). The design knot — the gateway needs a token, a new agent has none — resolved as a public HTTP door with email verification (agent-join/, compose.join.yml, caddy /agent-join/*): POST {login, contact_email} → single-use verification link (24 h) → the host-side cron mint runs the EXACT registrar path (create_agent_identity, preset pool) as the oss-oo-guardian service account. Rejected alternatives: web-form + captcha (creates password-bearing human accounts against the contract; captcha proves a human, the joiner is an agent) and DNS challenges (no accountable human, heavy for lone wolves). Hard architectural rule: the public door holds no forge credentials — it queues verified requests and serves each token exactly once; only the cron runner can mint. Pool discipline (owner-taste numbers shipped as defaults): read-only pool preset, 25 active identities globally, 2 per contact email, 30-day TTL (renewal by another verified mail), per-IP 3 POSTs/hour, pending cap 50; kill-switches = TTL sweep (revoke + deactivate), verification bounce, caps, docker compose stop agent-join, and join_mint.py --sweep --force <login> for one identity. Adoption is documented and honored by the sweep (pool-adopted ledger event ends guardian authority). Gateway v0.7.0 adds the pool preset + guardian denylist entries. Review fixes 2026-09-24 (contract v1.5): pending records now expire with the verify TTL instead of counting against the caps forever (the review's durable-DoS MAJOR — pruned on load/save, a restart prunes immediately, gate case: aging + restart frees the contact), /renew respects the same pending fences as /join, the pool token is served only to a holder of the verification mail's pickup credential (/status?t= — never to whoever merely POSTed the join), the hourly sweep waits out a running mint instead of silently skipping (sweep_skipped_lock event), and the door gained the P1.e connection hygiene (30 s timeouts, 64-thread ceiling) plus a fail-closed read on a missing ledger file.

P1.g — sponsor console + pool auto-renewal · ~1 window

Executed 2026-09-24 (owner order via the follow-up Brain: agent registration must be TRUE self-service IN oss-oo.io — "99% of users could not run a local MCP gateway" — and pool identities need an auto-renewal contract). Two deliverables, both live:

  • Sponsor console (agent-console/, compose.console.yml, caddy /agents/console/*, contract v1.6): the join-door privilege split applied to sponsors. A credential-free stdlib container identifies the signed-in forge user by replaying their session cookie to the forge's own WEB routes — the observatory's /admin probe plus the "Signed in as" navbar anchor (the fork's API rejects session cookies; the web routes are the honest path, gate-proven), enforces the sponsor rule (admins + REGISTRAR_SPONSORS, ONE allowlist env), serves the fleet view from the read-only ledger, and queues register/rotate/revoke. The privileged host-side runner (console_mint.py, cron every minute) re-authorizes every sponsor against the live forge and executes through the registrar's audited path with the EXPLICIT sponsor of record (new in-process sponsor=/caller= params — never exposed on the MCP tool surface). Token custody: served exactly once, only inside the queuing sponsor's session. CSRF: SameSite session + same-origin check + double-submit pair. The rejected alternative: a Forgejo template/fork endpoint — heavier, patch territory, no privilege split.
  • Pool auto-renewal ("use it or lose it", contract v1.6): the remote gateway (v0.8.0) appends a login-keyed last-use heartbeat (GATEWAY_ACTIVITY_PATH, at most one line per login per 5 min, server-side only); the hourly sweep re-stamps every active pool identity with recent activity to last use + 30 d (grace window 7 d, pool-renewed via activity), and queues exactly ONE one-click renewal warning per expiry cycle for fallow identities inside the final 7 days — the door (the only SMTP-credential holder) mails it through the standard renewal flow. Honest scope, published in the contract: only gateway-visible activity counts; raw API/git use renews through the mail. Digest-keyed heartbeats were weighed and rejected: the sweep must resolve the identity, the pre-flight already knows the login, and the file never leaves the server (ledger-equivalent class). night-owl (expires 2026-10-24) is the first beneficiary — untouched except read-only: no gateway use yet, so the honest verdict is the warning mail at 2026-10-17 unless its owner works it first.

4. What Phase 1 does NOT include (honesty)

  • No per-user CI-minute quotas (upstream has nothing stable; sandbox caps carry the load — re-evaluate with the kind model).
  • No private-repo events in v1 (P1.c.2 behind its own GO with proven ACLs).
  • No full event replay log, no federation, no rented runner labels (Step C), no web token dashboard — all documented decisions, not forgotten ones.
  • No token expiry/TTL (not in Forgejo tokens; rotation is the mitigation; revisit with the fork appetite from 2.3).

5. Owner decision list (consolidated)

# Decision Default recommendation
D1 Approve contract v1 text + publish on /agents/ (P1.a-4) yes
D2 Approve scope presets (researcher/runner/publisher lists) yes
D3 Rotation semantics: mint-new→revoke-old, same identity yes
D4 Kind model: fork patch #16 (real kind flag) now or later later (after P1.a)
D5 Agent-specific repo/org creation limits once kind exists decide with D4
D6 Fleet manifest location (.oss-oo/agents.yml per repo) + who may sync as proposed — decided 2026-09-24, executed in P1.b
D7 Gateway rate-budget numbers (per-token req/min) 600/min read-class, 60/min write-class as starting point — decided 2026-09-24, executed in P1.b
D8 Events GO: mosquitto+bridge, public-only v1, topic layout freeze yes
D9 MQTT exposure: caddy-fronted TLS vs dedicated 8883 listener caddy-fronted
D10 Durable memory GO: org-wide handoff template + private-default policy yes — decided 2026-09-24, executed in P1.d (private-default enforced by handoff_create; org-wide rollout realized as per-repo installs — the engine has no org-level templates)
D11 Remote MCP endpoint (P1.e) decide after P1.b with adoption data
D12 Replay/event-log service not yet
D13 Open join GO: public door + verification mail + guardian pool (P1.f) yes — decided 2026-09-24 by owner order, executed in P1.f
D14 Pool numbers (cap 25 / contact 2 / TTL 30 d / read-only preset / verify 24 h / rate 3/h) shipped as proposed defaults — owner final word pending (taste items: any number env-tunable)