36 KiB
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:
- Agents alone — a machine arrives with no human in the loop and needs identity, minimal viable work (clone, CI, API), and fair limits.
- Users + their agents — a human owns/sponsors agent identities, manages their tokens, sees their activity, and can turn them off.
- Agents + their users — an agent's durable context is its human's context; neither should lose state when a session ends.
- 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=-1and 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.
- Publish what is true today (numbers above), mark the rest "planned", iterate. Zero code, immediate honesty. → recommended for P1.a
- Wait for kind model + limits tooling, publish once, complete. Cleaner but the adoption pitch stays implicit for months.
- 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.
- Marker-only forever. Zero code; fragile; enforcement impossible; miscounts persist. Cheapest and least honest.
- 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-specificMAX_CREATION_LIMITand org policy via existing per-user override plumbing. - 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).
- MQTT (ROADMAP's sketch) — recommended v1. Pinned
eclipse-mosquittosidecar + stdlibwebhook2mqttbridge (compose.events.yml). Topic layoutoss-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. - 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.
- 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:
- 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. - Fleet declaration —
.oss-oo/agents.yml(login, scopes, preset, contact, note) reconciled by afleets syncgateway 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. - Memory convention —
.oss-oo/memory/*.mdin the owning repo (curated facts, gotchas, decisions — theforge-live-state.mdpattern, 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 liveforge-live-state.mdis ~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):
- The registrar completions (rotation, presets,
fleets_sync) — P1.a/P1.b above; identity is the moat. get_context/write_note(durable memory) — P1.d.- Event subscription tools (
subscribe(filter)managing MQTT credentials + filters) — P1.c, pairs with the bridge. - CI surface (
list_runs,get_run_log— wrapped/actions/API) — agents act on their own pipelines; medium. - Repo write depth (create branch, push file) — today agents
shell out to git for this; a
commit_filestool 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/adminprobe 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-processsponsor=/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-renewedviaactivity), 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) |