From 0b73facb66a49114f324b974665c56fc63c88bb7 Mon Sep 17 00:00:00 2001 From: Dai Ha Date: Sat, 11 Jul 2026 15:13:56 +0200 Subject: [PATCH] wiki: number page filenames (1-..5-) so Gitea Pages list sorts - git mv content pages to N-Name.md (history preserved); Home + _Sidebar kept - convert [[wiki-links]] to [display](numbered-slug) markdown links so resolution is unambiguous and prose display stays clean --- Architecture.md => 1-Architecture.md | 22 +++++++++++----------- Message-Server.md => 2-Message-Server.md | 14 +++++++------- Approaches.md => 3-Approaches.md | 6 +++--- Setup.md => 4-Setup.md | 8 ++++---- Operations.md => 5-Operations.md | 12 ++++++------ Home.md | 18 +++++++++--------- _Sidebar.md | 12 ++++++------ 7 files changed, 46 insertions(+), 46 deletions(-) rename Architecture.md => 1-Architecture.md (93%) rename Message-Server.md => 2-Message-Server.md (97%) rename Approaches.md => 3-Approaches.md (97%) rename Setup.md => 4-Setup.md (77%) rename Operations.md => 5-Operations.md (76%) diff --git a/Architecture.md b/1-Architecture.md similarity index 93% rename from Architecture.md rename to 1-Architecture.md index 44b1003..94a6782 100644 --- a/Architecture.md +++ b/1-Architecture.md @@ -7,7 +7,7 @@ model. Two channels carry traffic between them: 1. **Request/response (blocking)** — the primary delegates a task with **one blocking request** that `bridged` (driving [herdr](https://herdr.dev)) holds open until the worker's turn completes, then returns the reply as the response body. This is the main - channel; its full design is in **[[Message-Server]]**. "Blocking" here means a single + channel; its full design is in **[Message Server](2-Message-Server)**. "Blocking" here means a single tool call parked on a result — *not* a busy-poll — so it costs the primary no quota. 2. **Asynchronous / duplex** — either side drops a message for the other to pick up when idle → a **message broker** polled by a `Stop`-hook long-poll (or injected by `bridged` @@ -18,7 +18,7 @@ The engine of the sync channel is **herdr**, fronted by `bridged`: herdr owns th multiplexing, persistence, and **agent-status events**; `bridged` owns policy (the subscription boundary, session lifecycle, status-gated delivery) and the client-facing HTTP/SSE contract. (This supersedes the earlier plan, which used `coder/agentapi` as the -sync transport and treated herdr as an optional ops layer — see [[Approaches]] for why the +sync transport and treated herdr as an optional ops layer — see [Approaches](3-Approaches) for why the positions swapped.) ## Components @@ -79,7 +79,7 @@ cheaper/local model — without a policy-violating proxy on the primary. `ANTHROPIC_BASE_URL` and refuses to ever set it on a pane tagged *primary*. The guard only constrains panes `bridged` itself spawns; it **cannot** inspect a primary it does not host (e.g. an Opus on your Mac, split-host) — that primary's env cleanliness is the operator's - responsibility, backed by a best-effort startup self-check (see [[Message-Server]]). + responsibility, backed by a best-effort startup self-check (see [Message Server](2-Message-Server)). - Injecting keystrokes into the **primary** pane (worker → primary replies) is subscription-safe: it is simulated typing, identical to the human at the keyboard — the primary still authenticates to `api.anthropic.com` on Pro/Max. **This path exists only when @@ -97,7 +97,7 @@ cheaper/local model — without a policy-violating proxy on the primary. `GET /status`) into herdr socket calls, and — unlike a screen-stability heuristic — gates every injection on herdr's live `agent_status_changed` events. One session per pane; many panes per herdr. Full contract, components, and the Go interface sketch are in -**[[Message-Server]]**. +**[Message Server](2-Message-Server)**. The primary's `POST /message` **blocks** until `bridged` sees `agent_status = done` and collects the reply, which it returns as the response body — the primary reads it as an @@ -140,8 +140,8 @@ the primary as the call's result; the primary re-sends the answer with a fresh b Why herdr over the earlier `agentapi` plan: structured **agent-status events** (vs a screen-stability heuristic), **symmetric** injection into either pane (single-host), native **multiplexing** of a herd of workers, and **persistence/detach**. AgentAPI is kept as a -swappable *fallback injector* behind the same interface. See [[Approaches]] for the full -transport comparison and [[Message-Server]] for the design. +swappable *fallback injector* behind the same interface. See [Approaches](3-Approaches) for the full +transport comparison and [Message Server](2-Message-Server) for the design. ## Channel 2 — broker + Stop-hook long-poll (async, duplex) @@ -185,7 +185,7 @@ primary's idle pane.* | **Primary quota burn** | The primary must **never** perpetual-poll. Use the `Stop`-hook variant (fires only at a natural idle boundary), have `bridged` inject on idle, or pull on-demand. Free busy-polling is for `bridged` and the off-subscription **worker** only. | | **Cross-agent ping-pong** | A→B→A→B can loop forever. `stop_hook_active` guards single-agent re-entry but **not** cross-agent. Carry a round/turn budget or a `no-reply-needed` sentinel in the message envelope. | | **Lost / double-processed messages** | Use a broker with **ack + visibility timeout + consumer groups** (Redis Streams `XACK`, NATS JetStream). A crash mid-turn re-delivers instead of dropping. | -| **Context growth** | A perpetual worker's context window fills up. `bridged` caps idle cycles / tokens, then **recycles the pane fresh with state on the filesystem** (Ralph loop — see [[Message-Server]]). Perpetual != one infinite session. | +| **Context growth** | A perpetual worker's context window fills up. `bridged` caps idle cycles / tokens, then **recycles the pane fresh with state on the filesystem** (Ralph loop — see [Message Server](2-Message-Server)). Perpetual != one infinite session. | ## Deployment shape (target) @@ -236,10 +236,10 @@ outage costs you the workers, never your own session. ## Related pages -- **[[Message-Server]]** — the `bridged` design: herdr control contract, lifecycle, API, tech stack -- **[[Approaches]]** — why herdr-centric, and the full transport comparison (AgentAPI, SDK, Stop-hook, tmux) -- **[[Setup]]** — running herdr + `bridged` + a worker pointed at `ollama.ltms.dev` -- **[[Operations]]** — health, restart, model swaps, troubleshooting +- **[Message Server](2-Message-Server)** — the `bridged` design: herdr control contract, lifecycle, API, tech stack +- **[Approaches](3-Approaches)** — why herdr-centric, and the full transport comparison (AgentAPI, SDK, Stop-hook, tmux) +- **[Setup](4-Setup)** — running herdr + `bridged` + a worker pointed at `ollama.ltms.dev` +- **[Operations](5-Operations)** — health, restart, model swaps, troubleshooting ## Sources diff --git a/Message-Server.md b/2-Message-Server.md similarity index 97% rename from Message-Server.md rename to 2-Message-Server.md index 4721e72..707bce1 100644 --- a/Message-Server.md +++ b/2-Message-Server.md @@ -1,7 +1,7 @@ # 2. Herdr Message Server (`bridged`) > **Status:** 🟢 Proposed primary approach (2026-07-11) — supersedes AgentAPI as the -> centric transport. AgentAPI is retained only as a *fallback injector* (see [[Approaches]]). +> centric transport. AgentAPI is retained only as a *fallback injector* (see [Approaches](3-Approaches)). `bridged` is a small, always-on **message server that controls [herdr](https://herdr.dev)** and exposes a clean 2-way messaging API between a **primary** Claude Code session (Opus 4.8, @@ -291,7 +291,7 @@ real herdr events, not heuristics — the reason herdr-centric beats screen scra ## Subscription boundary (enforced, not just documented) -The invariant is unchanged from [[Architecture]] — **anything that sets +The invariant is unchanged from [Architecture](1-Architecture) — **anything that sets `ANTHROPIC_BASE_URL` is, by definition, the worker** — but here it is *enforced in code*: - `bridged` is **not** a `claude` process. It consumes zero Anthropic quota, so it may @@ -416,7 +416,7 @@ turns. Until then, treat one `bridged` as one trust domain. | **Server core** | **Go** | Single static binary → trivial `scp`/systemd deploy to the worker host; goroutines fit the socket + HTTP + broker + SSE fan-in; mirrors `coder/agentapi` (can reuse its `msgfmt` reply parser). | Rust (matches herdr, slower to build); **TypeScript/Node** if the driver is the Agent SDK and you want shared types; Python for a quick spike. | | **herdr transport** | Unix domain socket, **NDJSON**, `id`-correlated request/response + a persistent events stream | Native herdr contract | — | | **North API** | **REST + SSE**, OpenAPI-generated | Drop-in for AgentAPI-shaped clients; SSE streams status/reply cheaply | gRPC (if callers are all code); WebSocket (bidi UI) | -| **Async bus** *(optional)* | **Redis Streams** (consumer groups, `XACK`, visibility timeout) | Simplest durable duplex; satisfies the async guardrails in [[Architecture]] | NATS JetStream for multi-host scale / replay | +| **Async bus** *(optional)* | **Redis Streams** (consumer groups, `XACK`, visibility timeout) | Simplest durable duplex; satisfies the async guardrails in [Architecture](1-Architecture) | NATS JetStream for multi-host scale / replay | | **Config** | Env + YAML (`koanf`) | 12-factor; secrets via env only | — | | **Observability** | `slog` + Prometheus `/metrics` + `/healthz` | Ops from day one | OpenTelemetry traces | | **Process supervision** | **systemd** unit (or Docker Compose) colocating herdr + `bridged` | Restart-on-crash; ordered start (herdr before `bridged`) | k8s (overkill for one host) | @@ -502,14 +502,14 @@ func (g *Guard) AssertLocalPrimaryClean(env []string) error { | **Spawn-with-env uncertainty in socket API** | Launch via `send_text` of the env-prefixed command → env is provably worker-only; verify native spawn in the CLI reference and prefer it if present. | | **Reply-scrape fragility (fallback path)** | Prefer the structured **envelope** path; scrape `recent-unwrapped` only as a last resort. | | **herdr socket API is unversioned + single-dev churn** | Pin the herdr version in the systemd/Compose unit; keep the socket client behind the `Herdr` interface; probe `session.snapshot` shape on startup and fail fast on an unexpected schema. Don't build against `UNCERTAIN` primitives (e.g. native spawn-with-env) until confirmed in the running CLI. | -| **SPOF per channel (bridged / herdr / broker)** | Documented in [[Architecture]] → *Failure modes*. Key property: the **primary is never downstream** of a bridge component, so a total outage costs workers only, never the subscription session. | +| **SPOF per channel (bridged / herdr / broker)** | Documented in [Architecture](1-Architecture) → *Failure modes*. Key property: the **primary is never downstream** of a bridge component, so a total outage costs workers only, never the subscription session. | | **Injection TOCTOU / shared pane** | Single-writer injector + serialized send; worker panes are bridged-owned. Residual collision corrupts a turn (recoverable), never the subscription boundary. See *Delivery gating & races*. | ## Related pages -- **[[Architecture]]** — the two-channel model this refines; subscription boundary -- **[[Approaches]]** — transport comparison; AgentAPI now the *fallback injector* -- **[[Home]]** — project overview +- **[Architecture](1-Architecture)** — the two-channel model this refines; subscription boundary +- **[Approaches](3-Approaches)** — transport comparison; AgentAPI now the *fallback injector* +- **[Home](Home)** — project overview ## Sources diff --git a/Approaches.md b/3-Approaches.md similarity index 97% rename from Approaches.md rename to 3-Approaches.md index 22df279..0f34cd5 100644 --- a/Approaches.md +++ b/3-Approaches.md @@ -50,7 +50,7 @@ scraping a screen. ## 1. herdr socket API via `bridged` — structured injection + status events *(leading)* [herdr](https://herdr.dev) is a persistent agent multiplexer (a "tmux for agents") with a -Unix-socket JSON API. `bridged` (see [[Message-Server]]) drives it: `pane.send_text` + +Unix-socket JSON API. `bridged` (see [Message Server](2-Message-Server)) drives it: `pane.send_text` + `pane.send_keys` deliver a turn into the *running* pane, and `events.subscribe` (`pane.agent_status_changed`) reports **working / blocked / done** as real events. @@ -169,14 +169,14 @@ worker→primary is **not** symmetric — it goes via the broker + the primary's "one mechanism, both directions" story holds for a single-box setup, not the distributed one. ² **"Completion signal" for `bridged` is the timing signal; reply *content* rides a worker -`Stop`-hook envelope** (see [[Message-Server]]), not the status event itself. +`Stop`-hook envelope** (see [Message Server](2-Message-Server)), not the status event itself. ## Recommendation - **Primary Opus → worker (the bridge's main channel):** **herdr via `bridged`** — status-gated injection, structured completion/blocked events, symmetric (single-host), multiplexed, persistent, with the subscription boundary enforced in code. Selected. See - [[Message-Server]] / [[Architecture]]. + [Message Server](2-Message-Server) / [Architecture](1-Architecture). - **Keep AgentAPI as a swappable fallback injector** behind `bridged`'s interface, so herdr's immaturity is a de-riskable risk rather than a load-bearing one. - **External event bus → worker (async wake-ups):** layer a **Stop-hook long-poll** (or diff --git a/Setup.md b/4-Setup.md similarity index 77% rename from Setup.md rename to 4-Setup.md index 863c33b..46ddce5 100644 --- a/Setup.md +++ b/4-Setup.md @@ -1,7 +1,7 @@ # 4. Setup > **Status:** 🟠 Stub — scope defined, procedure not yet written. `bridged` is at the design -> stage ([[Message-Server]]); concrete install steps land with **M0–M1** of the build plan. +> stage ([Message Server](2-Message-Server)); concrete install steps land with **M0–M1** of the build plan. This page will cover standing up the bridge on an off-subscription worker host. @@ -19,14 +19,14 @@ This page will cover standing up the bridge on an off-subscription worker host. subscription guard accepts it and `pane.process_info` shows the expected egress host. 5. **Primary wiring** — point the primary Opus at `bridged` over HTTP (single blocking request per delegation); for split-host, add the primary's `Stop`-hook against the broker. -6. **Topology choice** — single-host vs split-host (see [[Message-Server]] → *Deployment +6. **Topology choice** — single-host vs split-host (see [Message Server](2-Message-Server) → *Deployment model*), and the broker (Redis Streams / NATS) if async/duplex is needed. ## Non-negotiable during setup The **primary** host/process must **never** be given `ANTHROPIC_BASE_URL`. Only worker panes -carry it. See [[Architecture]] → *Subscription boundary*. +carry it. See [Architecture](1-Architecture) → *Subscription boundary*. ## Related -- [[Message-Server]] · [[Architecture]] · [[Operations]] · [[Approaches]] +- [Message Server](2-Message-Server) · [Architecture](1-Architecture) · [Operations](5-Operations) · [Approaches](3-Approaches) diff --git a/Operations.md b/5-Operations.md similarity index 76% rename from Operations.md rename to 5-Operations.md index f7e7ffd..6948ae1 100644 --- a/Operations.md +++ b/5-Operations.md @@ -1,7 +1,7 @@ # 5. Operations > **Status:** 🟠 Stub — scope defined, runbook not yet written. Fills in as `bridged` reaches -> **M4 — Harden** (auth/TLS, metrics, systemd) in the [[Message-Server]] build plan. +> **M4 — Harden** (auth/TLS, metrics, systemd) in the [Message Server](2-Message-Server) build plan. Day-2 runbook for a running bridge. @@ -11,19 +11,19 @@ Day-2 runbook for a running bridge. `agent_status` per session via `GET /sessions`. - **Restart & recovery** — ordered restart (herdr before `bridged`); how `bridged` re-attaches to existing panes via `session.snapshot`; broker replay of unacked items. See - [[Architecture]] → *Failure modes & single points of failure* for what each outage costs. + [Architecture](1-Architecture) → *Failure modes & single points of failure* for what each outage costs. - **Model swaps** — repoint a worker to a different `base_url`/model by recycling its pane (Ralph loop); the subscription guard re-validates the new host against the allowlist. - **Lifecycle / context ceilings** — observing recycle events; confirming workers externalize - state (git + `STATE.md`) before a recycle so continuity survives (see [[Message-Server]] → + state (git + `STATE.md`) before a recycle so continuity survives (see [Message Server](2-Message-Server) → *Worker session lifecycle*). - **Troubleshooting** — stuck `working` (model endpoint down), `blocked` awaiting input, injection collisions on a hand-driven pane, envelope-vs-scrape reply mismatches. - **Security ops** — token rotation, keeping the port off public interfaces; note that one - `bridged` is currently **one trust domain** (no per-session authz yet — [[Message-Server]] + `bridged` is currently **one trust domain** (no per-session authz yet — [Message Server](2-Message-Server) → *Security*). -## Guardrails to watch (from [[Architecture]]) +## Guardrails to watch (from [Architecture](1-Architecture)) - The **primary must never perpetual-poll** — quota burn. Async wake-ups use the `Stop`-hook or `bridged` inject-on-idle only. @@ -33,4 +33,4 @@ Day-2 runbook for a running bridge. ## Related -- [[Message-Server]] · [[Architecture]] · [[Setup]] · [[Approaches]] +- [Message Server](2-Message-Server) · [Architecture](1-Architecture) · [Setup](4-Setup) · [Approaches](3-Approaches) diff --git a/Home.md b/Home.md index 0fb54cf..7eb074f 100644 --- a/Home.md +++ b/Home.md @@ -43,25 +43,25 @@ flowchart LR - **How the primary gets a reply:** it makes **one blocking request** (a `curl`/MCP tool call) that `bridged` holds open until the worker's turn completes, then returns the reply as the response body. Long/detached work instead uses the async broker path (see - [[Architecture]]) — the primary never busy-polls across turns. + [Architecture](1-Architecture)) — the primary never busy-polls across turns. - **Different model** per worker process sidesteps Claude Code's lack of per-subagent provider routing — the worker isn't a subagent, it's its own configured process. - **AgentAPI** ([`coder/agentapi`](https://github.com/coder/agentapi)) is retained only as a - swappable *fallback injector* behind the same interface. See [[Approaches]] for the - comparison and [[Message-Server]] for the full design. + swappable *fallback injector* behind the same interface. See [Approaches](3-Approaches) for the + comparison and [Message Server](2-Message-Server) for the full design. ## Pages Read in order (the sidebar mirrors this): -1. **[[Architecture]]** — process model, subscription boundary, the two-channel model -2. **[[Message-Server]]** — 🟢 **`bridged`**, the herdr-centric message server (primary approach) -3. **[[Approaches]]** — herdr-centric vs AgentAPI vs Agent SDK vs bus/tmux (research matrix) -4. **[[Setup]]** — running herdr + `bridged` + a worker pointed at `ollama.ltms.dev` -5. **[[Operations]]** — health, restart, model swaps, troubleshooting +1. **[Architecture](1-Architecture)** — process model, subscription boundary, the two-channel model +2. **[Message Server](2-Message-Server)** — 🟢 **`bridged`**, the herdr-centric message server (primary approach) +3. **[Approaches](3-Approaches)** — herdr-centric vs AgentAPI vs Agent SDK vs bus/tmux (research matrix) +4. **[Setup](4-Setup)** — running herdr + `bridged` + a worker pointed at `ollama.ltms.dev` +5. **[Operations](5-Operations)** — health, restart, model swaps, troubleshooting ## Status 🟢 Design — **herdr-centric `bridged` message server** selected as the primary approach (2026-07-11), superseding the AgentAPI plan (2026-07-08). AgentAPI is retained as a fallback -injector. See **[[Message-Server]]**. +injector. See **[Message Server](2-Message-Server)**. diff --git a/_Sidebar.md b/_Sidebar.md index 2ea2850..72c16e3 100644 --- a/_Sidebar.md +++ b/_Sidebar.md @@ -1,14 +1,14 @@ ### 📖 claude-bridge -[[Home]] — overview & the decision +[Home](Home) — overview & the decision **Chapters** -1. [[Architecture]] — system · invariant · 2 channels -2. [[Message-Server]] — the `bridged` design -3. [[Approaches]] — transports compared, why herdr -4. [[Setup]] — bring-up -5. [[Operations]] — day-2 runbook +1. [Architecture](1-Architecture) — system · invariant · 2 channels +2. [Message Server](2-Message-Server) — the `bridged` design +3. [Approaches](3-Approaches) — transports compared, why herdr +4. [Setup](4-Setup) — bring-up +5. [Operations](5-Operations) — day-2 runbook --- 🟢 herdr-centric `bridged` · AgentAPI = fallback