Multiple agents
One channel per agent
Section titled “One channel per agent”Each agent is a channel with its own key, name and delivery policy. That’s what makes a multi-agent setup legible for the human: one inbox where every sender has its own identity and its own loudness rules.
Isolate agents on one machine
Section titled “Isolate agents on one machine”Since pidge-cli 0.43, setup run inside a git project scopes the key to that project
(~/.config/pidge/projects/<hash>/env, resolved by walking up to the git toplevel) — so N
agents in N projects never collide with zero configuration. The remaining collision case is
agents sharing one directory (or running outside any project, where the legacy shared
~/.config/pidge/env is the fallback): there, every agent without its own identity reads the
same key, and one agent’s setup makes another send as the wrong channel. Give each its
own identity — cheapest correct setups, in order:
# A. per-agent env var — the cleanest; set at the agent's launch (unit/launcher/profile).# An env var always wins over any file.export PIDGE_TOKEN=hld_… # this agent only
# B. per-agent config file — set ONE non-secret id at launch; the CLI namespaces the# file to ~/.config/pidge/agents/<id>/env and still writes the key for you:export PIDGE_AGENT=my-agentnpx pidge-cli setup --claim <code>
# C. you're at YOUR terminal and want the export lines from a claim code:npx pidge-cli setup --claim <code> --print # prints `export …`; writes nothing# Paste them into THAT agent's launcher. NEVER run --print as an agent —# the key would land in its context. That's what A/B are for.A daemon or cron job that lives outside any project should use setup --global (the shared
machine file, deliberately). The shared file is fine for a single agent; pidge doctor
warns loudly when you’re on it. Lost the local key? Re-claim — the claim flow
rotates the key (the previous one stops authenticating instantly), so re-running setup
takes the channel over: same channel identity (name, thread, history), fresh key. Any other
runtime still on the old key drops to 401 and needs the new one. The corollary: to legitimately
share one channel across runtimes (a bridge + a session), copy the key deliberately —
option A above — never via a second claim, which displaces the current holder instead of
joining it.
Multi-runtime identity
Section titled “Multi-runtime identity”Several runtimes can legitimately touch one channel over time — an interactive session today, a
24/7 bridge tomorrow, a successor session next week. The CLI identifies itself on every call
(fingerprint + label headers; set PIDGE_AGENT/PIDGE_LABEL per runtime so the name means
something). What you get:
doctor/whoamilist the channel’s live consumers —team-bridge (you)·claude-interactive— with a ⚠️ onconsumer_conflict(2+ live consumers;listen/bridgealso warn once per run).- Provenance: a predecessor’s note-less acks are surfaced, so you know something was handled even when nobody wrote down what.
catchup --digestmarks a sibling’s in-flight work — “being handled by X since T” — never your own.--note "<why>"on any send recordssent_note: why this runtime armed it. Clear metadata, never sealed — keep secrets out. Visible to sibling runtimes.
All of this is advisory, never policy — it surfaces what’s happening; it never changes delivery. Against an older server the CLI just stays silent.
Who spoke: personas, seals, and handoffs
Section titled “Who spoke: personas, seals, and handoffs”Finer than the per-install fingerprint, a run is one actual execution of an agent — one
process lifecycle. An agent registers its run at start (POST /api/v1/runs {label, mode, role, parent_seal?} → a one-time run_token; send it as X-Pidge-Run on every
call — pidge-cli ≥ 0.27 automates both, and pidge bridge mints a fresh run per handler spawn
so each cold session is visibly distinct). Every signed send then carries a sender block —
{label, mode, role, seal} — on the 201, the poll and the index.
What the human sees is built on that:
- A stable persona per execution. Each run’s seal renders as a human-readable persona name (e.g. “Mint Kestrel”) — same session, same name; new session, new name. No memorizing short codes.
- An identity sheet on tap: agent label, how it was launched (
mode), its role (main/worker/subagent), and the seal — with an honest caption: this is which execution spoke, not proof of what it remembers. - A visible handoff marker. When a signed send continues a thread last signed by a
different run, the reply carries
run_handoff{from, to, same_execution: false}— derived from stored thread lineage, never guessed from timing — and the human sees “New session took over · X → Y” right in the conversation. Nobody is silently switched. - Workers declare their parentage: a subagent you spawn registers its own run with
role: "subagent"andparent_seal: <your seal>, so the human can tell the main mind from its helpers.
Attribution is never authorization. The run token authenticates nothing: invalid or expired, the call simply proceeds unsigned (never a 401), and nothing in delivery, serving or leases reads runs. Personas identify; they don’t grant power. And a stable seal proves the same process answered — not that it remembers anything.
Declare how you operate
Section titled “Declare how you operate”pidge contract set listen_mode=external_daemon # turn_based | persistent | external_daemonpidge contract set keep_connection_alive=truepidge contract showThe operating contract is the agent declaring its behavior; the human registers the expectation and sees whether you honor it. Pidge enforces nothing here — it’s a promise made visible, not a policy.
Who consumes what
Section titled “Who consumes what”The hard rule from Waiting & receiving applies doubly here: one consumer
per channel. In a team of runtimes, exactly one runs listen/bridge; everyone else
situates with catchup. Minting a sibling channel for a subagent (rather than sharing one
key) is the clean pattern — see the Hermes plugin for an integration that
does this automatically.
