Skip to content

Architecture ​

Peregrin is the policy layer for coding-agent sessions and workflows: who may act on a session, when, and on which machine — plus the orchestration plane (campaigns, drivers, events, transport) on top. Session mechanism — attach, prompt, restore, per-agent stores — lives in sepia, which peregrin consumes over a subprocess wire rather than by linking internals.

                 ┌─────────────── peregrin-hub ───────────────┐
                 │  aggregation · sync · arbitration · web UI  │
                 └──────────────┬───────────────┬─────────────┘
                        HTTP+SSE │               │ HTTP+SSE
                  ┌──────────────▼──┐    ┌───────▼──────────┐
                  │ peregrin-node A │    │ peregrin-node B  │
                  │  /api/* surface │    │  /api/* surface  │
                  └───────┬─────────┘    └────────┬─────────┘
                  spawn + │ ndjson JSON-RPC       │
                  ┌───────▼─────────┐    ┌────────▼─────────┐
                  │   sepia serve   │    │   sepia serve    │
                  │  session plane  │    │  session plane   │
                  └─────────────────┘    └──────────────────┘

Mechanism vs policy ​

The split is deliberate: sepia does things to sessions; peregrin decides who/when/where. Concretely — peregrin-control holds the lock registry, takeover arbitration, and pick_owner routing; the hub consults it before forwarding; nodes report lock state up. Anything that mutates a session (prompt, attach, rewind) flows down to sepia with the policy decision already made.

The sepia boundary ​

A node never links sepia-session into orchestration code. It spawns sepia serve and drives it over ndjson JSON-RPC stdio — the same framing sepia's own store drivers speak — via peregrin-session::ServicePlane. An in-process LocalPlane exists for tests (PEREGRIN_SESSION_PLANE=inprocess); both implement the same SessionPlane trait, so peregrin-http and everything above it is blind to which side of the fork it's on. The manifest handshake negotiates protocol versions at spawn.

Planes ​

  • Session plane — the sepia surface: sessions, agents, attach, prompts, events. Nodes own it; the hub proxies it.
  • Orchestration plane — peregrin's own: campaigns (declarative workflows over agents via peregrin-engine + loaders + templates), the event bus, remote transport, and the outbox for offline ordering.
  • App plane — peregrin-http/peregrin-web: REST + SSE, the Leptos UI, node-local overlay state (PEREGRIN_META — pins, archives, run spans; node-owned, not part of the session wire).

Ports and adapters ​

Policy crates are pure; every I/O seam is a port (peregrin-{store,bus,remote,config,secret,service,loader,template}) with impls behind it (-toml, -http, -yaml, -keyring, -dotenv, -systemd). Engine semantics are documented in project/adrs/ — read those before changing campaign behaviour.