# Live project communications Ferryman has one machine-wide hub and one private communications repository per attached project. Live communications are separate from encrypted continuity packs: continuity packs are recovery archives; messages are small durable, idempotent work envelopes. ## Boundary and layout ```text /.ferryman/ machine-local; ignored by the project repo bridge.toml project routing token existing scoped secret; never portable runtime/ outbox, delivery attempts, dedupe claims transport-state.json crash-safe failover/backoff state acknowledgement-outbox/ receipts awaiting remote delivery quarantine/outbox/ malformed entries isolated for inspection locks/git-live.lock cross-process Git writer lock ferryman/ portable channel: the Syncthing-carried root messages//*.json acknowledgements//*.json agents/ PROTOCOL.md STANDARD.toml .stignore .gitignore .git/ ``` The main project Git remote is never inspected as a communications target and is never changed. Syncthing carries only the inner directory as its own folder, so a project-level rule excluding hidden paths remains intact. The generated `.stignore` keeps `.git` out of that folder: Syncthing copies whole files with no idea a repository is one consistent set, and a replicated `.git` corrupts. ## Message delivery state 1. Ferryman assigns a UUID and idempotency key and atomically writes the envelope to the outer `runtime/outbox`. 2. If the local communications root is healthy, the message is atomically written to the inner `messages//` directory. 3. If local peer delivery is unavailable but Syncthing reports the folder healthy and at least one peer connected, the inner write is allowed to propagate. 4. Every envelope has an acknowledgement deadline. If the preferred routes are unavailable, or that deadline passes without an acknowledgement, the private inner repository enters Git live mode: every message is committed and pushed. 5. Git/network failure retains the outbox item. Retry uses exponential backoff capped at five minutes. 6. A receiver pulls the named private-Git branch when shared delivery is unavailable or Git inbound mode is active. Acknowledgements use the same shared/Git fallback in reverse, so the sender can retire its outbox. 7. The hub reconciles every project's message and acknowledgement outboxes every ten seconds. Preferred transports are probed during reconciliation; two consecutive healthy shared-transport deliveries are required before Git live mode is left. 8. Each attempt is immutable under `runtime/delivery-attempts//`; the latest receipt is also stored under `runtime/deliveries/`. 9. Failover mode, inbound-Git mode, recovery streak, Git backoff, and privacy-verification cache are atomically persisted. A server restart resumes that state rather than incorrectly returning the project to local-only delivery. Malformed or cross-project outbox entries are moved to `runtime/quarantine/outbox` with an error sidecar. One corrupt file therefore does not stop reconciliation of the rest of the project. Syncthing API probes have a 5-second hard deadline; Git and GitHub subprocesses have a 45-second hard deadline and are killed on expiry. Quarantine is evidence, not an automatic retry queue. Inspect the error sidecar and original file locally. For a legitimate malformed request, submit a new validated message through the API with an intentional idempotency key; do not move raw quarantined JSON back into the outbox. Preserve or archive quarantine evidence according to the project's retention policy. Acknowledgements live separately. Before executing a message, a consumer claims the SHA-256 digest of its idempotency key under outer `runtime/processed`; an existing atomic claim means the message was already observed through another transport and must not execute again. Recording an acknowledgement removes a locally originated outer outbox copy. For a Git-delivered message, it first enters the receiver's durable `acknowledgement-outbox`, then is committed and pushed; the sender pulls it and retires its original outbox. The immutable portable message and acknowledgement remain available as evidence. ## Hub API Operator routes use the project token: - `POST|GET /v1/projects/{project}/communications` — configure/read the project map. - `POST|GET /v1/projects/{project}/communications/messages` — send/list envelopes. - `POST .../actors/{actor}/token` — mint an eight-hour actor-scoped token once. - `POST .../reconcile` — request an immediate outbox reconciliation. - `GET .../status` — transport health, failover/backoff, both queue depths, and quarantine. Use `?probe_external=false` for a strictly local snapshot. - `DELETE /v1/projects/{project}/communications` — unregister the mapping and revoke its actor tokens without deleting attachment files; refused while the outbox is non-empty. Consumer routes require the actor token for the exact registered actor named in the request: - `GET .../actors/{actor}/messages` — synchronize inbound state, then list only that actor's name/role inbox. Use `?synchronize=false` for a local-only read. - `POST .../messages/{id}/claim` — atomically claim before execution. - `POST .../messages/{id}/acknowledge` — record a separate acknowledgement. A project token cannot claim or acknowledge work. An actor cannot claim or acknowledge a message unless its registered name or role matches the message recipient. This keeps an existing multi-agent framework's identities separate; an unmanaged project uses the built-in `project-inbox` actor. `acknowledgement_timeout_seconds` defaults to 30 and is bounded to 5–3600 seconds. Inline payloads are capped at 256 KiB. Portable JSON is rejected when it contains credential-bearing keys such as `token`, `secret`, `password`, `credential`, `api_key`, or `private_key`; use a local secret reference instead. The server translates Windows drive paths to `/mnt//...` when running in WSL, and the reverse when running as a Windows process. ## Health and Git safety Shared-folder health comes from Syncthing's own REST API on loopback, not from the filesystem. Two things must hold: `/rest/db/status` reports the folder present and not in error, and `/rest/system/connections` reports at least one connected peer. That second check is the one a filesystem probe can never make. A folder can look perfectly healthy locally while no other machine is reachable, in which case a message written into it has been stored, not delivered. The API key is read from `SYNCTHING_API_KEY` or, failing that, from Syncthing's own `config.xml` at its platform-default location, so an operator who already runs Syncthing needs no extra configuration. With no key the probe reports the transport unavailable rather than assuming one exists. Directory existence alone is not shared-transport health. The acknowledgement deadline additionally detects a remote peer that is not consuming messages even when Syncthing itself reports a healthy folder. When a Git remote is configured, GitHub visibility must be verified as `PRIVATE` for the exact `$FERRYMAN_CHANNEL_GIT_OWNER/-bridge` name before clone/configuration. A channel with no Git remote is Syncthing-only: the Git rung is simply unavailable and no visibility check applies. Visibility is cached for ten minutes by the live Git transport; the attachment command also verifies it once per run and refuses any public or mismatched repository. Before each Git delivery, Ferryman verifies that the inner repository's `origin` matches the registered project remote. It never runs a remote-changing command against the main project checkout. Git live retries are message-idempotent: an already committed file is pushed again without creating a duplicate commit. Writers take an exclusive project-local lock, fetch, rebase with autostash, commit only new portable files with a fixed non-secret Ferryman commit identity, and push. A rejected push triggers one fetch/rebase/retry in the same bounded operation; any remaining error retains the outbox item for the next reconciliation. A same-message delivery from a stale second checkout rebases without creating a second message commit. Pull and acknowledgement pushes use the same named-branch verification, clean-worktree requirement, exclusive lock, bounded subprocess, and exact-origin checks. ## Operator commands ```powershell # Project token cargo run -p ferryman-cli -- communications status --project example cargo run -p ferryman-cli -- communications status --project example --local-only cargo run -p ferryman-cli -- communications reconcile --project example cargo run -p ferryman-cli -- communications mint-actor-token ` --project example --actor project-inbox # Replace FERRYMAN_TOKEN with the returned actor token for consumer actions. cargo run -p ferryman-cli -- communications inbox ` --project example --actor project-inbox cargo run -p ferryman-cli -- communications inbox ` --project example --actor project-inbox --local-only cargo run -p ferryman-cli -- communications claim ` --project example --recipient project-inbox cargo run -p ferryman-cli -- communications acknowledge ` --project example --recipient project-inbox # Project token; safe rollback only after the outbox reaches zero. cargo run -p ferryman-cli -- communications unregister --project example ``` Actor tokens are returned once and expire after eight hours. They are runtime credentials and must never be written inside the portable inner repository.