wiki: consistency pass vs rewritten Architecture (5-agent review)
Independent cold reads of every page against 1-Architecture found no invariant violations; fixed the drift the rewrite introduced plus one real contradiction: - terminology: Channel 1/2 -> Mode 1/2, 'two-channel' -> 'two invariants / two modes' (Approaches, Team, Home, Sidebar, Message-Server); north/south face -> SERVER/CLIENT face (Message-Server, 6 spots) - contradiction reconciled: Architecture now acknowledges a non-MCP *worker* Stop-hook (POSTs reply to bridged) as well as the split-host-primary hook - both target bridged, never a broker; Message-Server tier table split into Unified / Hooked / Unmodified to match - Approaches: footnote credits bridge_reply (Stop-hook = fallback); §4 subtitle reframed; <payload> mermaid label de-angled (parse-safe) - Team: SERVER 'role router' -> 'policy brain'; fan-out sequence quoted; inference edges labeled - Home/README: CLIENT-face node regains 'status-gated injector' - Operations: 'broker' -> 'internal broker/queue'; Stop-hook framed as split-host exception - async ticket/bridge_poll reframed as injection-first (push), poll = non-pane fallback All 16 mermaid blocks validated with mmdc.
+12
-4
@@ -174,10 +174,18 @@ sequenceDiagram
|
||||
for Claude, REST for external), it optionally parks the message on its internal queue, waits
|
||||
for the idle event, and injects.*
|
||||
|
||||
> **The one exception.** A **split-host primary that is not a herdr pane** (e.g. Opus on your
|
||||
> Mac) is the sole session `bridged` cannot inject into. There, the primary runs a `Stop`-hook
|
||||
> that **long-polls `bridged`** for queued messages — still the gateway, **never** the broker.
|
||||
> The gateway invariant holds in every topology.
|
||||
> **Where a hook is used.** A `Stop`-hook appears in exactly the spots where neither an MCP
|
||||
> tool call nor a pane injection can serve — and in **every** case it targets `bridged`, never
|
||||
> a broker:
|
||||
> - a **split-host primary that is not a herdr pane** (e.g. Opus on your Mac) — the only
|
||||
> session `bridged` cannot inject into — runs a `Stop`-hook that **long-polls `bridged`** for
|
||||
> queued messages; and
|
||||
> - a **non-MCP (herdr-only) worker** may run a `Stop`-hook that **POSTs its reply to
|
||||
> `bridged`** at turn end, a structured alternative to scraping the pane (see
|
||||
> [Message Server](2-Message-Server) → *Reply envelope*).
|
||||
>
|
||||
> The gateway invariant holds in every topology: a hook is just a transport adapter to
|
||||
> `bridged` for a session MCP/injection can't reach.
|
||||
|
||||
## Worker lifecycle — the Ralph loop
|
||||
|
||||
|
||||
+18
-17
@@ -67,7 +67,7 @@ claude mcp add --transport http bridge http://127.0.0.1:8080/mcp
|
||||
# or a project .mcp.json / CLAUDE.md entry that every session on the host inherits
|
||||
```
|
||||
|
||||
That is the point of the MCP north face: **one unified Claude setup**. Primary and workers
|
||||
That is the point of the MCP SERVER face: **one unified Claude setup**. Primary and workers
|
||||
load the *same* server and differ only in which tools they call — no shell step that could
|
||||
leak env, no hand-rolled HTTP client, no per-session bespoke wiring. REST/SSE (below) stays
|
||||
for *non-Claude* callers (webhooks, dashboards, a human CLI); Claude ↔ Claude goes over MCP.
|
||||
@@ -76,8 +76,8 @@ for *non-Claude* callers (webhooks, dashboards, a human CLI); Claude ↔ Claude
|
||||
|
||||
| Caller | Tool | Blocks? | Does |
|
||||
|---|---|---|---|
|
||||
| **Primary** | `bridge_send(session, task, {mode})` | `"block"` → yes · `"async"` → no | Deliver a turn to a worker. Blocking form returns the worker's reply as the tool result; async form returns a `ticket`. |
|
||||
| **Primary** | `bridge_poll(ticket)` | no | Fetch the reply for an async task once it is ready. |
|
||||
| **Primary** | `bridge_send(session, task, {mode})` | `"block"` → yes · `"async"` → no | Deliver a turn to a worker. Blocking form returns the worker's reply as the tool result; async form returns a `ticket` and the reply is **injected into the primary's idle pane** when ready. |
|
||||
| **Primary** | `bridge_poll(ticket)` | no | Retrieve an async reply **when injection can't serve** — a split-host / non-pane primary pulls it from `bridged` (the gateway, never a broker) instead of being injected. |
|
||||
| **Primary** | `bridge_status(session)` | no | Worker's live `agent_status` — `idle`\|`working`\|`blocked`\|`done`. |
|
||||
| **Worker** | `bridge_reply(result)` | no | Emit a **structured** reply/payload to whoever awaits this turn. |
|
||||
| **Worker** | `bridge_ask(question)` | yes | Worker-initiated question up the chain (true 2-way); parks the worker until the primary answers. |
|
||||
@@ -131,7 +131,8 @@ it degrades cleanly if a worker is left unmodified:
|
||||
| Tier | Worker setup | Reply channel | Worker can ask back? |
|
||||
|---|---|---|---|
|
||||
| **Unified (recommended)** | mounts `bridge` MCP (same one line) | structured `bridge_reply` | yes — `bridge_ask` |
|
||||
| **Herdr-only (fallback)** | unmodified `claude` | `pane.read` on `agent_status=done` | no |
|
||||
| **Hooked (no MCP)** | a `Stop`-hook installed | structured envelope POSTed to `bridged` (see [Reply envelope](#reply-envelope-how-a-worker-emits-a-structured-reply)) | no |
|
||||
| **Unmodified (last resort)** | stock `claude` | `pane.read` scrape on `agent_status=done` (lossy) | no |
|
||||
|
||||
The **primary-side contract is identical** in both tiers; only the worker's reply fidelity
|
||||
changes. Ship the unified setup — one MCP line on every session — and keep herdr-only as the
|
||||
@@ -143,11 +144,11 @@ bridge on either side is subscription-safe by construction (see
|
||||
|
||||
`bridged` is a **standalone daemon** — one component, two faces:
|
||||
|
||||
- a **SERVER (north face)** — an **MCP server** the Claude Code sessions mount, plus REST/SSE
|
||||
for non-Claude clients, sitting over the session tracker, subscription guard, and reply
|
||||
- a **SERVER face** — an **MCP server** the Claude Code sessions mount, plus REST/SSE for
|
||||
non-Claude clients, sitting over the session tracker, subscription guard, and reply
|
||||
rendezvous (the policy brain); and
|
||||
- a **CLIENT (south face)** — a herdr socket client that injects turns (status-gated) and
|
||||
subscribes to agent-status.
|
||||
- a **CLIENT face** — a herdr socket client that injects turns (status-gated) and subscribes
|
||||
to agent-status.
|
||||
|
||||
The Claude sessions themselves live **as panes inside herdr**. Each pane reaches *up* to
|
||||
`bridged`'s MCP server (to send/reply); `bridged`'s herdr client reaches *down* through
|
||||
@@ -163,12 +164,12 @@ flowchart TB
|
||||
end
|
||||
|
||||
subgraph bridged["bridged — standalone daemon (NOT a claude process)"]
|
||||
subgraph srv["SERVER — north face"]
|
||||
subgraph srv["SERVER face"]
|
||||
MCP["MCP server<br/>bridge_send · reply · ask · status"]
|
||||
REST["REST / SSE<br/>(non-Claude clients)"]
|
||||
POL["policy brain<br/>session tracker · subscription guard<br/>· reply rendezvous"]
|
||||
end
|
||||
subgraph cli["CLIENT — south face"]
|
||||
subgraph cli["CLIENT face"]
|
||||
INJ["injector<br/>status-gated"]
|
||||
HCL["herdr socket client<br/>send_text · events · pane.read"]
|
||||
end
|
||||
@@ -213,7 +214,7 @@ primary out of herdr — see [Deployment model](#deployment-model).)*
|
||||
| **Injector** | Per-pane FIFO queue. Delivers `send_text` + `send_keys "enter"` **only when** that pane's `agent_status ∈ {idle, blocked}` — never mid-run. |
|
||||
| **Reply rendezvous** | Resolves an awaiting `bridge_send` on whichever lands first: a worker `bridge_reply` (structured, **preferred**), the `agent_status_changed = done` event (timing guarantee), or — worker-side hook path — a `Stop`-hook envelope; last-resort `pane.read {source:"recent-unwrapped"}` scrape. See [Reply envelope](#reply-envelope-how-a-worker-emits-a-structured-reply). |
|
||||
| **Subscription guard** | Refuses to spawn a *worker* pane without `ANTHROPIC_BASE_URL`; refuses to *ever* set it on a pane designated *primary*; can assert egress host via `pane.process_info`. |
|
||||
| **North API** | **MCP server** — the Claude-facing contract both primary and workers mount (`bridge_send`/`reply`/`ask`/`status`). Plus **REST + SSE** (OpenAPI, AgentAPI-shaped) for non-Claude clients — webhooks, dashboards, a human CLI. |
|
||||
| **SERVER API** | **MCP server** — the Claude-facing contract both primary and workers mount (`bridge_send`/`reply`/`ask`/`status`/`poll`/`sessions`). Plus **REST + SSE** (OpenAPI, AgentAPI-shaped) for non-Claude clients — webhooks, dashboards, a human CLI. |
|
||||
| **Broker connector** *(optional, internal)* | `bridged`-owned durability + cross-host transport, **below the gateway**. Enqueues async messages `bridged` will later inject into an idle pane. No Claude session ever connects to it. |
|
||||
|
||||
## The herdr control contract (what `bridged` drives)
|
||||
@@ -430,7 +431,7 @@ The invariant is unchanged from [Architecture](1-Architecture) — **anything th
|
||||
`ANTHROPIC_BASE_URL` and logs each worker's resolved egress host. It cannot self-check a
|
||||
remote primary.
|
||||
|
||||
## API surface (north side)
|
||||
## API surface (SERVER face)
|
||||
|
||||
**Two faces over one core.** Claude sessions use the **MCP tools**
|
||||
([above](#the-client-contract--mcp-unified-for-primary--workers)); the **REST/SSE** routes
|
||||
@@ -534,8 +535,8 @@ 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 — Claude** | **MCP server**, streamable-HTTP transport (`mcp-go` / official Go SDK) | The unified contract both primary and workers mount; native to Claude Code, no shell/`curl` step, subscription-safe by construction | stdio MCP adapter (per-session subprocess) if a long-lived HTTP endpoint is undesirable |
|
||||
| **North API — others** | **REST + SSE**, OpenAPI-generated | Drop-in for AgentAPI-shaped/non-Claude clients; SSE streams status cheaply | gRPC (if callers are all code); WebSocket (bidi UI) |
|
||||
| **SERVER API — Claude** | **MCP server**, streamable-HTTP transport (`mcp-go` / official Go SDK) | The unified contract both primary and workers mount; native to Claude Code, no shell/`curl` step, subscription-safe by construction | stdio MCP adapter (per-session subprocess) if a long-lived HTTP endpoint is undesirable |
|
||||
| **SERVER API — others** | **REST + SSE**, OpenAPI-generated | Drop-in for AgentAPI-shaped/non-Claude clients; SSE streams status cheaply | gRPC (if callers are all code); WebSocket (bidi UI) |
|
||||
| **Internal queue** *(optional)* | **Redis Streams** (consumer groups, `XACK`, visibility timeout) — `bridged`-owned, below the gateway | Durability + cross-host for async; satisfies the guardrails in [Architecture](1-Architecture). Same-host can start with an in-process queue and add this only when durability/cross-host is needed | NATS JetStream for multi-host scale / replay; embedded (BadgerDB/SQLite) for a single host |
|
||||
| **Config** | Env + YAML (`koanf`) | 12-factor; secrets via env only | — |
|
||||
| **Observability** | `slog` + Prometheus `/metrics` + `/healthz` | Ops from day one | OpenTelemetry traces |
|
||||
@@ -615,19 +616,19 @@ func (g *Guard) AssertLocalPrimaryClean(env []string) error {
|
||||
| Risk | Mitigation |
|
||||
|---|---|
|
||||
| **No held-open conversation** — each exchange is one request in, one reply out | By design. Short/medium tasks use a **single blocking call** (fine — no quota burn); long/detached tasks use **return-and-reinvoke**, the reply delivered later **through `bridged`** (idle-pane injection, or a split-host `Stop`-hook polling `bridged`). What's excluded is a persistent bidirectional stream the primary must babysit. See *How the primary actually consumes a reply*. |
|
||||
| **Blocking call can outlive its timeout** on a very long task | Set a request deadline; on timeout `bridged` returns "still working, await async" and the reply lands via `bridged`'s async path (idle-injection) instead of erroring the delegation. Pick Channel 2 up front for known-long work. |
|
||||
| **Blocking call can outlive its timeout** on a very long task | Set a request deadline; on timeout `bridged` returns "still working, await async" and the reply lands via `bridged`'s async path (idle-injection) instead of erroring the delegation. Pick async delivery (Mode 2) up front for known-long work. |
|
||||
| **herdr is young / single-dev** — betting transport on it | Durability lives in `bridged`'s **internal queue** (mature Redis/NATS), not herdr — herdr carries only ephemeral delivery + status. The injector is a **pluggable interface** — fall back to `tmux send-keys` or AgentAPI without touching the queue or the gateway contract. |
|
||||
| **herdr socket is local-only** | `bridged`'s **MCP/HTTP** is the sole cross-host link; herdr and the queue stay per-host and `bridged`-owned. |
|
||||
| **Mid-run interrupt still unsolved** | Same as AgentAPI. Injection gates on status; `ctrl+c` via `pane.send_input` is the only (disruptive) interrupt. |
|
||||
| **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](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. |
|
||||
| **SPOF (bridged / herdr / queue)** | 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](1-Architecture)** — the two-channel model this refines; subscription boundary
|
||||
- **[Architecture](1-Architecture)** — the two-invariant / two-mode model this refines; subscription boundary
|
||||
- **[Approaches](3-Approaches)** — transport comparison; AgentAPI now the *fallback injector*
|
||||
- **[Home](Home)** — project overview
|
||||
|
||||
|
||||
+9
-7
@@ -58,7 +58,8 @@ Unix-socket JSON API. `bridged` (see [Message Server](2-Message-Server)) drives
|
||||
**MCP rendezvous** — the worker's `bridge_reply` (or the `done` event) resolves the primary's
|
||||
blocking `bridge_send` tool call, so no keystroke into the primary pane is needed, even
|
||||
single-host. Fallbacks: herdr can type into a single-host non-MCP primary (subscription-safe
|
||||
keystrokes); a split-host primary wakes via its own `Stop`-hook polling `bridged` (Channel 2).
|
||||
keystrokes); a split-host primary wakes via its own `Stop`-hook polling `bridged` (the async
|
||||
path — Mode 2 in [Architecture](1-Architecture)).
|
||||
- **North-face contract is MCP — and the sole gateway.** Both primary and workers mount
|
||||
`bridged` as an MCP server (one unified Claude setup); no Claude session ever addresses a
|
||||
broker or peer directly. The herdr injection here is the *south* side, orthogonal to it.
|
||||
@@ -101,7 +102,7 @@ events and permission callbacks instead of scraping a terminal.
|
||||
- **Trade-off:** the worker is an SDK-hosted loop, not a stock `claude` TUI — further from
|
||||
"a real Claude Code process" than herdr/AgentAPI, and the driver must be code.
|
||||
|
||||
## 4. Message-queue + Stop-hook long-poll *(the async layer — complementary)*
|
||||
## 4. Message-queue + Stop-hook long-poll *(raw async primitive — behind the gateway in our design)*
|
||||
|
||||
The **only pure-hooks** way to pull an external message into the **same** session. In
|
||||
`claude-bridge` this is **not** how a Claude session normally receives async work — under the
|
||||
@@ -120,7 +121,7 @@ sequenceDiagram
|
||||
H->>Q: long-poll (BRPOPLPUSH, ≤30s)
|
||||
alt message arrives
|
||||
Q-->>H: payload
|
||||
H-->>CC: {"decision":"block","reason":"<payload>"}
|
||||
H-->>CC: "{ decision: block, reason: payload }"
|
||||
Note over CC: message injected as context → new turn
|
||||
else timeout
|
||||
Q-->>H: (nothing)
|
||||
@@ -179,12 +180,13 @@ worker→primary goes through `bridged` all the same — the primary's `Stop`-ho
|
||||
story holds for a single-box setup; across hosts the gateway is still `bridged`, just not by
|
||||
injection.
|
||||
|
||||
² **"Completion signal" for `bridged` is the timing signal; reply *content* rides a worker
|
||||
`Stop`-hook envelope** (see [Message Server](2-Message-Server)), not the status event itself.
|
||||
² **"Completion signal" for `bridged` is the timing signal; reply *content* rides the worker's
|
||||
structured `bridge_reply` (preferred), with a `Stop`-hook envelope as fallback** (see
|
||||
[Message Server](2-Message-Server)) — not the status event itself.
|
||||
|
||||
## Recommendation
|
||||
|
||||
- **Primary Opus → worker (the bridge's main channel):** **herdr via `bridged`** —
|
||||
- **Primary Opus → worker (the bridge's main path):** **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](2-Message-Server) / [Architecture](1-Architecture).
|
||||
@@ -192,7 +194,7 @@ injection.
|
||||
herdr's immaturity is a de-riskable risk rather than a load-bearing one.
|
||||
- **External event bus → worker (async wake-ups):** the bus hits **`bridged`'s REST ingress**;
|
||||
`bridged` enqueues internally if needed and **injects the idle worker** — the worker runs no
|
||||
queue-polling hook. Complementary to the sync channel, not a replacement — different trigger
|
||||
queue-polling hook. Complementary to the sync path, not a replacement — different trigger
|
||||
shape, same single gateway.
|
||||
- **Avoid hand-rolled `tmux send-keys`** unless neither herdr nor AgentAPI can run; it's the
|
||||
same idea with all the fragility left in.
|
||||
|
||||
+8
-6
@@ -11,7 +11,7 @@ Day-2 runbook for a running bridge.
|
||||
`agent_status` per session via `GET /sessions`, and confirming the **MCP endpoint** is
|
||||
reachable from both the primary and the workers (`claude mcp list` shows `bridge` connected).
|
||||
- **Restart & recovery** — ordered restart (herdr before `bridged`); how `bridged`
|
||||
re-attaches to existing panes via `session.snapshot`; broker replay of unacked items. See
|
||||
re-attaches to existing panes via `session.snapshot`; internal broker/queue replay of unacked items. See
|
||||
[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.
|
||||
@@ -26,11 +26,13 @@ Day-2 runbook for a running bridge.
|
||||
|
||||
## 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.
|
||||
- Cross-agent ping-pong needs a round/turn budget in the message envelope.
|
||||
- Broker must run with **ack + visibility timeout + consumer groups** so a mid-turn crash
|
||||
re-delivers instead of dropping.
|
||||
- The **primary must never perpetual-poll** — quota burn. Async wake-ups are `bridged`
|
||||
inject-on-idle by default; the `Stop`-hook is only the split-host-primary exception, and it
|
||||
polls `bridged` (never a broker).
|
||||
- Cross-agent ping-pong needs a round/turn budget — enforced centrally in `bridged` (sole
|
||||
gateway), not per-session sentinels.
|
||||
- The **internal** broker/queue must run with **ack + visibility timeout + consumer groups**
|
||||
so a mid-turn crash re-delivers instead of dropping.
|
||||
|
||||
## Related
|
||||
|
||||
|
||||
+20
-20
@@ -32,7 +32,7 @@ alike. Scale each kind horizontally by adding panes.
|
||||
flowchart TB
|
||||
LEAD["lead — Opus<br/>(Claude Code, env CLEAN)<br/>MCP client"]
|
||||
subgraph BD["bridged — standalone daemon"]
|
||||
SRV["SERVER face<br/>MCP · REST/SSE · role router"]
|
||||
SRV["SERVER face<br/>MCP · REST/SSE · policy brain"]
|
||||
CLI["CLIENT face<br/>herdr socket"]
|
||||
SRV --> CLI
|
||||
end
|
||||
@@ -49,10 +49,10 @@ flowchart TB
|
||||
HERDR --> WC1 & WC2 & WL1 & WL2
|
||||
WC1 -.->|"MCP bridge_reply"| SRV
|
||||
WL1 -.->|"MCP bridge_reply"| SRV
|
||||
WC1 --> ANT
|
||||
WC2 --> ANT
|
||||
WL1 --> OLL
|
||||
WL2 --> OLL
|
||||
WC1 -->|"inference"| ANT
|
||||
WC2 -->|"inference"| ANT
|
||||
WL1 -->|"inference"| OLL
|
||||
WL2 -->|"inference"| OLL
|
||||
|
||||
classDef ext fill:#2b6cb0,stroke:#1a365d,color:#ffffff;
|
||||
classDef core fill:#2f855a,stroke:#22543d,color:#ffffff;
|
||||
@@ -88,24 +88,24 @@ different workers at once, then results are gathered.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant L as lead (Opus)
|
||||
participant B as bridged
|
||||
participant WC as w-claude-1
|
||||
participant WL as w-local-1
|
||||
participant L as "lead (Opus)"
|
||||
participant B as "bridged"
|
||||
participant WC as "w-claude-1"
|
||||
participant WL as "w-local-1"
|
||||
|
||||
Note over L: split job → subtask A (reasoning), subtask B (bulk)
|
||||
Note over L: "split job → subtask A (reasoning), subtask B (bulk)"
|
||||
par A → Claude worker
|
||||
L->>B: bridge_send {role: w-claude, prompt: A}
|
||||
B->>WC: send_text into running pane
|
||||
WC-->>B: bridge_reply (or status done)
|
||||
B-->>L: tool result reply A
|
||||
L->>B: "bridge_send {role: w-claude, prompt: A}"
|
||||
B->>WC: "send_text into running pane"
|
||||
WC-->>B: "bridge_reply (or status done)"
|
||||
B-->>L: "tool result reply A"
|
||||
and B → local worker
|
||||
L->>B: bridge_send {role: w-local, prompt: B}
|
||||
B->>WL: send_text into running pane
|
||||
WL-->>B: bridge_reply (or status done)
|
||||
B-->>L: tool result reply B
|
||||
L->>B: "bridge_send {role: w-local, prompt: B}"
|
||||
B->>WL: "send_text into running pane"
|
||||
WL-->>B: "bridge_reply (or status done)"
|
||||
B-->>L: "tool result reply B"
|
||||
end
|
||||
Note over L: reduce → integrate A + B into final answer
|
||||
Note over L: "reduce → integrate A + B into final answer"
|
||||
```
|
||||
|
||||
- **Map:** the lead issues N concurrent blocking `bridge_send` tool calls (one per subtask →
|
||||
@@ -114,7 +114,7 @@ sequenceDiagram
|
||||
- **Reduce:** the lead collects the N replies and integrates. A slow local worker never
|
||||
blocks a fast Claude worker — wall-clock ≈ the slowest single subtask, not the sum.
|
||||
- **Detached / long jobs** use `bridged`'s async path instead of a held request — the result
|
||||
is delivered when ready by `bridged` injecting the lead's idle pane (Channel 2 in
|
||||
is delivered when ready by `bridged` injecting the lead's idle pane (Mode 2 in
|
||||
[Architecture](1-Architecture)). The lead talks only to `bridged`, never a broker, and never
|
||||
busy-polls across turns.
|
||||
|
||||
|
||||
+2
-2
@@ -26,7 +26,7 @@ flowchart LR
|
||||
OPUS["Opus — primary<br/>(Claude Code, env CLEAN)<br/>MCP client"]
|
||||
subgraph BD["bridged — standalone daemon (not a claude process)"]
|
||||
SRV["SERVER face<br/>MCP · REST/SSE · policy"]
|
||||
CLI["CLIENT face<br/>herdr socket"]
|
||||
CLI["CLIENT face<br/>status-gated injector · herdr socket"]
|
||||
SRV --> CLI
|
||||
end
|
||||
HERDR["herdr<br/>panes · agent-status"]
|
||||
@@ -69,7 +69,7 @@ flowchart LR
|
||||
|
||||
Read in order (the sidebar mirrors this):
|
||||
|
||||
1. **[Architecture](1-Architecture)** — process model, subscription boundary, the two-channel model
|
||||
1. **[Architecture](1-Architecture)** — process model, the two invariants, two traffic modes
|
||||
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`
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
|
||||
**Chapters**
|
||||
|
||||
1. [Architecture](1-Architecture) — system · invariant · 2 channels
|
||||
1. [Architecture](1-Architecture) — system · 2 invariants · 2 modes
|
||||
2. [Message Server](2-Message-Server) — the `bridged` design
|
||||
3. [Approaches](3-Approaches) — transports compared, why herdr
|
||||
4. [Setup](4-Setup) — bring-up
|
||||
|
||||
Reference in New Issue
Block a user