e18e002d2f
The shipped docs and javadoc said a profile's `model` and `tabLabel` take effect on the next spawn. They do not, and ConfigRef did not detect the change either, so a reload logged a clean "config reloaded" and silently did nothing. That is the worst outcome a reload can produce: the operator has no reason to doubt it. What makes a key hot is who reads it and when, not that it is config. Placement reads weight and maxLoad through a supplier on CompositePeerLauncher, so those are genuinely hot. HerdrPeerLauncher takes Map.copyOf(profiles) at construction and resolves every spawn out of that copy, so model, baseUrl, argv, env and the rest cannot move until the daemon restarts. changedDeferredKeys now compares every launch component of an existing profile, excluding weight and maxLoad, and names the profiles that need a restart. The javadoc and bridged.example.yaml say the same thing. Two tests pin the pair: weight/maxLoad reports nothing deferred, a changed model reports the profile by name.
392 lines
26 KiB
YAML
392 lines
26 KiB
YAML
# bridged configuration (example). Copy to bridged.yaml and adjust.
|
|
#
|
|
# bridged is the sole gateway between primary/worker Claude sessions and herdr.
|
|
# It is NOT a Claude process and must never carry ANTHROPIC_BASE_URL.
|
|
|
|
# REST + MCP listen address. Keep it on loopback unless you also switch auth.mode to `token`
|
|
# below — bridged REFUSES TO START on a non-loopback bind under loopback-trust (see auth).
|
|
bind:
|
|
host: 127.0.0.1
|
|
port: 8765
|
|
|
|
# API authentication (CB-501). Governs how a caller that is NOT an on-host worker pane proves it
|
|
# is the primary. Worker identity never depends on this: a loopback peer PID that maps to a herdr
|
|
# pane is unforgeable and is always honoured, so turning auth on cannot lock the fleet out.
|
|
#
|
|
# mode: loopback-trust → DEFAULT, and the historical behaviour: any loopback caller that is not
|
|
# a worker is the primary, no credential needed. Sound ONLY because the
|
|
# OS refuses remote connections to a loopback socket.
|
|
# mode: token → such a caller must send `Authorization: Bearer <token>`; without it it
|
|
# is anonymous and authorized for nothing. REQUIRED for a non-loopback
|
|
# bind — the daemon fails fast otherwise, because "unauthenticated ⇒
|
|
# primary" on a reachable port would hand spawn/stop/send to anyone.
|
|
# tokenEnv → host env var holding the token (never the literal value). Default
|
|
# BRIDGED_API_TOKEN. Read only in token mode; empty ⇒ startup fails.
|
|
#
|
|
# TLS is deliberately NOT terminated in the daemon (CB-501 D3): run a reverse proxy in front and
|
|
# let it own certificate lifecycle, e.g.
|
|
# location / { proxy_pass http://127.0.0.1:8765; proxy_set_header Authorization $http_authorization; }
|
|
# The broker link gets TLS from its own URI (amqps://…) — see `broker` below.
|
|
# auth:
|
|
# mode: token
|
|
# tokenEnv: BRIDGED_API_TOKEN
|
|
|
|
# Optional pinned primary terminal (CB-307). Names the herdr pane the PRIMARY itself runs in:
|
|
# a caller whose connection maps to this pane resolves as the primary (no credential needed —
|
|
# the pane mapping is as unforgeable as a worker's), and reply nudges are pushed to it.
|
|
# REQUIRED when the primary runs inside a herdr pane — without it the pane match reads the
|
|
# primary as a worker and refuses spawn/send/stop. Get the id from bridge_whoami; re-pin if
|
|
# the primary moves panes.
|
|
# primary:
|
|
# terminal: term_0123456789abcd
|
|
# pushReminders: 5 # max nudges before giving up (default 5)
|
|
# pushBackoffMs: 15000 # delay between nudges (default 15000)
|
|
|
|
# CB-530: MORE THAN ONE LEAD. `primary:` above is singular by construction — every other pane
|
|
# resolves as a worker — which is right for one lead driving a fleet and wrong the moment two leads
|
|
# (say a Claude lead and an opencode lead) work as peers: the second is silently demoted and refused
|
|
# every orchestration call. List each lead's pane here and all of them resolve as leads.
|
|
#
|
|
# terminal → the ONLY field identity depends on; get it from that session's bridge_whoami
|
|
# kind/model → descriptive; they document what runs in the pane and are echoed by bridge_whoami
|
|
#
|
|
# A lead is never spawned — it pre-exists, which is exactly why it must be named rather than created.
|
|
# `bridge_whoami` reports `{"role":"primary","leader":"<name>"}`; role stays "primary" because a lead
|
|
# IS a primary for authorization, so nothing that keys on the role breaks.
|
|
#
|
|
# KEEP `primary:` when adding leads: it still addresses the CB-307 push loop, which needs a single
|
|
# destination for its nudges. If both name the same terminal, the `fleet.leaders:` entry wins.
|
|
#
|
|
# Leads are configured under `fleet.leaders:` — see THE FLEET further down.
|
|
#
|
|
# Two things stop the tab-name convention from becoming a way to claim leadership: the configured
|
|
# member spaces are excluded from the scan, so nothing bridged places can land in a matching tab;
|
|
# and startup REFUSES a `tabPrefix` that the fleet tabLabel template, or any per-profile `tabLabel`
|
|
# override, also matches — so the two namespaces cannot overlap by accident. The label is a NAME,
|
|
# never a capability: what a pane may do is decided by the role the daemon resolves for it.
|
|
|
|
# CB-551: IDLE-LEAD HEARTBEAT — nudge the single lead back to work when it has been continuously
|
|
# idle (no open bridge_send driving it) past the quiet period. The fleet is one lead + architects +
|
|
# workers, so a lead that stalls is a single point of failure; the ReplyPushLoop only nudges when a
|
|
# reply lands, and this timer catches the gap where nothing lands and the lead just sits idle.
|
|
#
|
|
# Opt-in on purpose — it SPENDS the operator's subscription on its own initiative (each nudge starts
|
|
# a lead turn nobody asked for), so upgrading the daemon must never switch it on for you. Absent
|
|
# block = feature off, exactly as before.
|
|
#
|
|
# Three knobs, each with a default that errs on the side of not burning context:
|
|
# idleAfterSeconds: 300 # how long the lead must stay idle before the FIRST nudge (default 300 —
|
|
# # absorbs normal post-turn pauses; re-prompting every pause burns context)
|
|
# backoffMs: 60000 # re-check cadence / spacing between nudges past the quiet period (default 60000)
|
|
# quietNudgeCap: 3 # cap on consecutive nudges that find NOTHING pending, then it stops
|
|
# # until real state appears (default 3 — never nag an empty fleet forever)
|
|
# leadHeartbeat:
|
|
# idleAfterSeconds: 300
|
|
# backoffMs: 60000
|
|
# quietNudgeCap: 3
|
|
|
|
# herdr Unix socket. Omit to use the client default
|
|
# (${HERDR_SOCKET_PATH:-~/.config/herdr/herdr.sock}).
|
|
herdrSocket: ~/.config/herdr/herdr.sock
|
|
|
|
# How member sessions are spawned. Define one or more named profiles (backends) under
|
|
# `profiles`; each key is the profile name (also the ccs profile). A profile says only WHICH
|
|
# BACKEND — model, CLI adapter, credentials, cost. It says nothing about what a member spawned on
|
|
# it is for; that is the member's role, and roles live under `fleet:` below. Which profile an
|
|
# unqualified spawn lands on comes from that role's pool, not from a global default.
|
|
#
|
|
# Shared knobs (placement/workspace) can be repeated per profile; they usually match.
|
|
# placement: tab → each worker lands in its OWN tab in a dedicated worker space (default).
|
|
# Use `pane` for the legacy behaviour (split the focused tab).
|
|
# mcpUrl → bridged mounts the bridge MCP (--mcp-config, inline) + reply charter
|
|
# (--append-system-prompt) as launch flags; nothing is written to the profile.
|
|
# tokenEnv → host env var holding the worker's auth token (value never stored in config);
|
|
# omit for a backend that needs no token (e.g. a local ollama).
|
|
# cwd → pin this profile's working directory (CB-112). Omit to inherit the primary's
|
|
# cwd on an MCP spawn, else the daemon's cwd — never $HOME. See
|
|
# docs/Worker-Startup-and-Trust.md.
|
|
# configDir → CLAUDE_CONFIG_DIR for the worker, so it inherits that profile's
|
|
# skills/MCP/hooks. Omit to leave the worker on the host default.
|
|
# parityOverlay → repo-relative paths copied primary→worktree so a worker in a provisioned
|
|
# worktree sees the same local config (CB-301-ext). Omit for the default set:
|
|
# [.claude/settings.local.json, .env, .envrc].
|
|
#
|
|
# Do NOT add .mcp.json (CB-525). A worker's tools are whatever its launcher
|
|
# mounts — the bridge, and nothing else. Replicating the primary's MCP config
|
|
# handed a worker the primary's IDE servers, which are bound to the primary's
|
|
# checkout, so its navigation returned paths OUTSIDE its own worktree: one
|
|
# worker made all 59 of its edits in the primary tree while compiling its
|
|
# worktree, and every build it ran was of code that did not contain them.
|
|
# bridged neutralizes a provisioned worktree's .mcp.json for this reason;
|
|
# listing it here would copy the primary's back over that.
|
|
# gitTokenEnv → host env var holding the git-forge API token. When set, its value is injected
|
|
# as GITEA_TOKEN so the worker can open its OWN PR at checkpoint (CB-302).
|
|
# Opt-in by design — omit and the worker gets no PR-create grant (push over
|
|
# SSH is unaffected). The token value itself is never stored in this file.
|
|
# gitHostEnv → host env var holding the forge host (default GITEA_HOST). Injected as
|
|
# GITEA_HOST *only* alongside a resolved gitTokenEnv.
|
|
# env → extra environment for this profile's workers, as a literal key/value map
|
|
# (CB-511). Use it to give workers a toolchain.
|
|
#
|
|
# A worker's environment does NOT come from your shell. bridged hands herdr an
|
|
# explicit env map and herdr merges it into ITS OWN process env — so before
|
|
# CB-511 a worker inherited whatever PATH the herdr server happened to be
|
|
# started with, which on a long-lived herdr can predate your toolchain entirely
|
|
# and leave workers unable to run `mvn` or `java` at all.
|
|
# bridged now propagates ITS OWN PATH to every worker by default; set `env:`
|
|
# only to override that or add more (JAVA_HOME, …). Since the default is the
|
|
# daemon's PATH, make sure the daemon is started with a good one — see the PATH
|
|
# lines in deploy/dev.ltms.bridged.plist and deploy/bridged.service.
|
|
#
|
|
# Adapter-owned variables always win over `env:`: ANTHROPIC_BASE_URL and the
|
|
# rest of the ANTHROPIC_*/CLAUDE_* wiring are applied after it, so an `env:`
|
|
# entry cannot repoint a worker past the SubscriptionGuard — which is checked
|
|
# against `baseUrl` alone.
|
|
# Put `defaultMode: "auto"` in each ccs profile so the worker runs autonomously.
|
|
profiles:
|
|
gx10: # ccs profile name (NOT a hostname)
|
|
kind: claude-code # which adapter spawns this profile (default; may omit)
|
|
baseUrl: http://gx01.gw:8000 # the vLLM host this profile targets (gx00.gw / gx01.gw)
|
|
model: coder
|
|
placement: tab
|
|
workspace: bridged-workers
|
|
# tabLabel: an optional per-profile override; the fleet template usually covers it
|
|
mcpUrl: http://127.0.0.1:8765/mcp
|
|
tokenEnv: BRIDGED_WORKER_TOKEN
|
|
argv: ["ccs", "gx10"]
|
|
weight: 0.5 # relative selection weight for placement: weighted
|
|
maxLoad: 2 # max live workers on this profile (omit for unlimited)
|
|
# gitTokenEnv: GITEA_TOKEN # opt-in: let this profile's workers open their own PR (CB-302)
|
|
# gitHostEnv: GITEA_HOST # defaults to GITEA_HOST; injected only with gitTokenEnv
|
|
# configDir: /Users/me/.ccs/instances/gx10 # CLAUDE_CONFIG_DIR — inherit that profile's skills/MCP
|
|
# cwd: /Users/me/src/myrepo # pin the working dir; omit to inherit the primary's
|
|
# parityOverlay: [".claude/settings.local.json", ".env", ".envrc"] # never add .mcp.json — see above
|
|
gx11: # a second backend, so `placement: weighted` has a choice
|
|
baseUrl: http://gx01.gw:8000 # self-hosted; ccs handles the model + token
|
|
placement: tab
|
|
workspace: bridged-workers
|
|
# tabLabel: an optional per-profile override; the fleet template usually covers it
|
|
mcpUrl: http://127.0.0.1:8765/mcp
|
|
argv: ["ccs", "gx11"]
|
|
weight: 0.5
|
|
maxLoad: 2
|
|
# Pin an auto-compact window BELOW the served model's context ceiling. The global
|
|
# ~/.claude/settings.json value is shared by every ccs instance and the primary, so the
|
|
# per-profile override belongs here. Equal to the ceiling means auto-compact never fires
|
|
# before the server rejects the prompt, which kills a worker mid-turn (CB-523).
|
|
env:
|
|
CLAUDE_CODE_AUTO_COMPACT_WINDOW: "280000"
|
|
# CB-402: a second coding-agent kind, proving the PeerLauncher SPI is provider-neutral.
|
|
# opencode is provider-agnostic and uses NONE of Claude's private seams: no ANTHROPIC_BASE_URL /
|
|
# SubscriptionGuard (so it needs no `guard` host entry), no --mcp-config / --append-system-prompt.
|
|
# The bridge MCP + reply charter mount via a generated OPENCODE_CONFIG file, and the model is a
|
|
# `provider/model` selector. Placement, tabs, cwd, and the readiness gate are shared with Claude.
|
|
#
|
|
# Dogfood-verified 2026-07-29 against opencode 1.18.5 (spawn → readiness gate → bridge_send →
|
|
# structured bridge_reply → teardown). The `opencode/*-free` models run on opencode's own gateway
|
|
# and need NO credentials — check `opencode models` for the current free list, since the names
|
|
# change. That also makes the worker off-subscription by construction.
|
|
# opencode-free:
|
|
# kind: opencode
|
|
# model: opencode/north-mini-code-free # `provider/model` selector, injected as `-m`
|
|
# placement: tab
|
|
# workspace: bridged-workers
|
|
# tabLabel: "opencode: {profile} #{n}"
|
|
# mcpUrl: http://127.0.0.1:8765/mcp
|
|
# argv: ["opencode"]
|
|
#
|
|
# CB-508: point an opencode profile at your OWN OpenAI-compatible endpoint (local vLLM, llama.cpp,
|
|
# LM Studio, TGI…) instead of opencode's gateway. Setting `baseUrl` on a `kind: opencode` profile
|
|
# makes the bridge emit a custom `provider` block into the generated opencode.json — opencode has
|
|
# no ANTHROPIC_BASE_URL seam, so this is how the endpoint is pinned.
|
|
# baseUrl → a bare host:port gets `/v1` appended (where these servers mount the API); a URL that
|
|
# already has a path is used verbatim, so a custom mount point still works.
|
|
# model → MUST be "<provider>/<model>". The provider half names the generated block; the model
|
|
# half must match an id the server reports at /v1/models. One field drives both the
|
|
# declaration and the `-m` flag, so they cannot drift apart. A bare model name with a
|
|
# baseUrl set is rejected at spawn rather than silently using the default gateway.
|
|
# tokenEnv → optional; its value becomes the provider apiKey. Most local servers ignore the key,
|
|
# so a placeholder is used when unset (the AI SDK still requires a non-empty one).
|
|
# NOTE: no `guard` entry is needed even with a baseUrl set. The SubscriptionGuard exists to stop a
|
|
# worker borrowing the primary's Anthropic subscription, and an opencode process has no Anthropic
|
|
# credential path at all.
|
|
# opencode-local:
|
|
# kind: opencode
|
|
# baseUrl: http://127.0.0.1:8000
|
|
# model: local-vllm/deepseek-v4-flash
|
|
# placement: tab
|
|
# workspace: bridged-workers
|
|
# tabLabel: "opencode: {profile} #{n}"
|
|
# mcpUrl: http://127.0.0.1:8765/mcp
|
|
# argv: ["opencode"]
|
|
# How an unqualified spawn chooses a profile: fixed (default, reproduces pre-CB-518 behaviour),
|
|
# round-robin, or weighted. Omitting this key is a strict no-op for existing configs.
|
|
placement: weighted
|
|
|
|
# Re-read this file without restarting the daemon (CB-559). Off unless you add this block, so an
|
|
# upgraded bridged keeps the old behaviour: the file is read once at boot and never again.
|
|
# enabled → turn the watch on. bridged checks the file's modified time on a timer and
|
|
# reloads when it moves.
|
|
# intervalSeconds → how often to check (default 10). One `stat` per tick, so this is cheap.
|
|
#
|
|
# Not every key can move under a running daemon, and the difference is about what already exists
|
|
# when the reload happens — not about how important the key is:
|
|
# HOT → takes effect on the next spawn: the whole `fleet:` block (every role pool and
|
|
# `tabLabel`), `placement:`, and an existing profile's weight / maxLoad. Those are
|
|
# hot because the placement policy reads them through a supplier — being config is
|
|
# not by itself enough to make a key hot.
|
|
# DEFERRED → accepted into the new config, but the wiring built at startup keeps the old value
|
|
# until you restart: `lifecycle:`, `leadHeartbeat:`, `guard:`, `worktreeRoot:`,
|
|
# `spawnReadyTimeoutMs` / `spawnReadyPollMs`, ADDING or REMOVING a profile (a new
|
|
# backend needs its own launcher, and launchers are built once), AND an existing
|
|
# profile's launch settings — model, baseUrl, argv, env, configDir, mcpUrl, tabLabel.
|
|
# The launcher takes a copy of `profiles:` at startup and resolves every spawn out of
|
|
# that copy, so those never reach a launch until you restart. The reload logs them by
|
|
# name rather than pretending they applied.
|
|
# COLD → cannot change at all: `bind:`, `herdrSocket:`, `broker:` and `auth:`. The socket is
|
|
# bound, the broker connection is open, and the auth mode decides who may reach the
|
|
# port that is already listening.
|
|
#
|
|
# A changed COLD key refuses the WHOLE reload — not the hot half applied and the cold half warned
|
|
# about. A half-applied reload would leave the daemon matching no file on disk, which is the worst
|
|
# thing a reload can do to an operator debugging one. A file that fails to parse or fails a startup
|
|
# validator is refused the same way, and the running config stays live.
|
|
# configReload:
|
|
# enabled: true
|
|
# intervalSeconds: 10
|
|
|
|
# THE FLEET (CB-557) — who the daemon may run, and under which role. This one block replaced four
|
|
# older keys: `leaders:`, `members:`, `leadScan:` and `defaultProfile:`.
|
|
#
|
|
# A member is anything a lead spawns, and every member has two INDEPENDENT attributes:
|
|
# role — which contract: architect, dev or reviewer. It picks the launch charter, the role
|
|
# file, the playbook skill and the authz row.
|
|
# profile — which backend: one of the `profiles:` keys above (model, CLI adapter, cost).
|
|
# They vary on their own. A reviewer may run on the same profile as the dev whose diff it reads,
|
|
# which is why the two cannot be one field.
|
|
#
|
|
# The ROLE IS THE CONTAINING KEY, not a `role:` field. That is not only tidier: a misspelled role
|
|
# used to parse into a member with no contract at all, while a misspelled pool name here simply
|
|
# declares nothing.
|
|
#
|
|
# Each pool lists the profiles that role MAY run on — these are pools, not identities. That is also
|
|
# what replaced `defaultProfile:`: an unqualified spawn names a role, and that role's pool supplies
|
|
# the candidates, in definition order. A dev and a reviewer staying anonymous is exactly compatible
|
|
# with being listed here; the entry key just names the entry.
|
|
fleet:
|
|
# Optional. Template for a member tab's label; {role}, {profile}, {model} and {n} are substituted.
|
|
# {n} counts per role+profile, so `dev: sonnet #2` really is the second sonnet dev. Because {role}
|
|
# comes from a closed enum, a generated label can never begin with a lead's tabPrefix.
|
|
# tabLabel: "{role}: {profile} #{n}"
|
|
|
|
# Panes that orchestrate rather than are orchestrated. A lead may now be CREATED as well as
|
|
# recognised: give it a `profile:` and the daemon launches the shortfall when fewer than
|
|
# `instances` are live. Give it only a `terminal:` and it is recognise-only, as before.
|
|
#
|
|
# `tabPrefix` is the naming convention that finds a lead without pasting a terminal id: label the
|
|
# tab `lead: <name>` when you open it and the pane is recognised on the next rescan. Reopen the
|
|
# tab later and the id changes; the label does not.
|
|
#
|
|
# A lead the daemon launches is labelled BY the daemon, using the same convention, so it is found
|
|
# by the same scan. A lead counts as live only when herdr also reports a running agent in that
|
|
# tab — a label left behind by a session that died does not block the relaunch.
|
|
#
|
|
# An auto-launched lead is NOT a member: it gets no worker reply charter, is never registered with
|
|
# the session lifecycle (the idle reaper would kill your orchestrator), and stays on the
|
|
# subscription — ANTHROPIC_BASE_URL/AUTH_TOKEN are stripped from its env whatever the profile says.
|
|
# leaders:
|
|
# opus-5.0:
|
|
# profile: opus # omit to never create this lead, only recognise it
|
|
# instances: 1 # desired live count; only the shortfall is launched. 0 = off
|
|
# terminal: term_0123456789abcd # optional hand-pin; usually found by tabPrefix instead.
|
|
# # A running agent on this terminal also counts as live, so a
|
|
# # lead you opened by hand is not relaunched under you.
|
|
# tabPrefix: "lead:" # `lead: opus-5.0` ⇒ a lead named opus-5.0 (case-insensitive)
|
|
# scanIntervalSeconds: 10 # rescan cadence, and the worst case before a new tab is seen
|
|
# workspace: leads # where a launched lead's tab is created (default "leads").
|
|
# # MUST NOT be a member workspace — those are excluded from the
|
|
# # scan, so a lead placed in one is never found again.
|
|
# cwd: /path/to/repo # the launched lead's working directory (default: bridged's own)
|
|
# kind: claude # descriptive; reported by bridge_whoami
|
|
# gpt-sol-5.6:
|
|
# terminal: term_fedcba9876543
|
|
# kind: opencode
|
|
# model: openai/gpt-5.6-terra
|
|
|
|
# architects:
|
|
# architect-1:
|
|
# profile: opus # a strong model, on the operator's subscription
|
|
# architect-2:
|
|
# profile: sol # a different vendor on purpose — two architects that share a
|
|
# # model share its blind spots
|
|
developers:
|
|
gx10:
|
|
profile: gx10
|
|
# reviewers:
|
|
# gx10:
|
|
# profile: gx10 # the same backend may serve two roles; that is the point
|
|
|
|
# Subscription boundary. A worker's base_url host MUST be one of these; the primary
|
|
# must carry none. Every profile above must have its host listed here.
|
|
guard:
|
|
offSubscriptionHosts:
|
|
- gx00.gw
|
|
- gx01.gw
|
|
|
|
# Spawn-readiness gate (CB-306). The launcher blocks until the worker's herdr status is
|
|
# injectable (IDLE/BLOCKED/DONE) or the timeout elapses. 0 disables the gate.
|
|
# NOTE: keys are camelCase — config is bound by plain Jackson with no naming strategy and
|
|
# unknown keys are ignored, so a snake_case key would be silently dropped (default kept).
|
|
# spawnReadyTimeoutMs: 20000
|
|
# spawnReadyPollMs: 300
|
|
|
|
# Worktree provisioning root (CB-301-ext). Where per-worker git worktrees are checked out so
|
|
# each worker owns an isolated branch instead of sharing the primary's tree. Omit to default
|
|
# to a sibling directory of the repo root.
|
|
# worktreeRoot: /Users/me/src/.bridged-worktrees
|
|
|
|
# Session lifecycle limits (CB-303). All knobs are opt-in; omit or set to null to keep
|
|
# the feature disabled. By default the daemon never reaps, caps, or drains sessions.
|
|
# idleTtlSeconds → reap READY/DONE sessions idle longer than this (never BUSY/SPAWNING)
|
|
# contextCap → force-release a session after this many delegated turns
|
|
# drainTimeoutSeconds → seconds to wait for BUSY sessions on shutdown before forced teardown
|
|
# lifecycle:
|
|
# idleTtlSeconds: 300
|
|
# contextCap: 10
|
|
# drainTimeoutSeconds: 5
|
|
|
|
# Durable reply delivery (CB-307 Stage 2). OMIT this block entirely to keep the default
|
|
# in-memory, soft-state reply inbox (late worker replies are held only until a daemon bounce).
|
|
# Set a broker uri to swap in the AMQP-backed inbox: worker replies with no open send are held
|
|
# on a durable per-target queue (agent.<target>.inbox) and survive a restart — the broker
|
|
# redelivers anything the primary had not yet drained. Production default is LavinMQ; a stock
|
|
# RabbitMQ speaks the same AMQP 0-9-1, so it is a URI-only swap.
|
|
# uri → AMQP connection URI. No trailing slash ⇒ the default vhost "/"; an empty path ("/")
|
|
# is vhost "" and will NOT connect. Encode a named vhost as .../%2Fmyvhost.
|
|
# broker:
|
|
# uri: amqp://guest:guest@127.0.0.1:5672
|
|
|
|
# Active push-to-primary (CB-307 Stage 3). When a worker reply lands with no open bridge_send,
|
|
# the ReplyPushLoop injects a *drain nudge* (never the payload) into the primary's own herdr
|
|
# pane — status-gated (only when injectable, never mid-turn) and bounded. Ack = drain: the loop
|
|
# stops as soon as the primary's inbox is empty.
|
|
# terminal → pin the primary's herdr terminal id. Omit to learn it from the connection on
|
|
# the first orchestration-side MCP call (the normal case). An off-host or
|
|
# non-herdr primary leaves this unresolved → the loop is a no-op and delivery
|
|
# degrades to pull; the reply is still never lost.
|
|
#
|
|
# REQUIRED (CB-522) if the primary itself runs inside a herdr pane. Caller
|
|
# identity resolves a loopback PID to its herdr pane, and PaneLocator scans
|
|
# EVERY pane — not just bridged-spawned ones — so such a primary is otherwise
|
|
# classified as a WORKER and refused SPAWN/SEND/STOP. That failure is
|
|
# self-locking: the learned terminal is populated by the very orchestration
|
|
# calls being refused, so only this pinned value can break the cycle. Read the
|
|
# id off bridge_whoami (it reports the current terminal even while
|
|
# misclassified) and re-pin whenever the primary moves panes.
|
|
# pushReminders → max nudges before giving up (default 5)
|
|
# pushBackoffMs → delay between nudges in ms (default 15000)
|
|
# primary:
|
|
# terminal: term_65619bd6174568
|
|
# pushReminders: 5
|
|
# pushBackoffMs: 15000
|