Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,36 @@ _Avoid_: Armonia (the whole stack), workspace, folder
The scheduled agentic-work context — a registered set of jobs that run unattended on a schedule, each producing staged, reviewable output rather than direct changes. Already live as the team-facing nightly Slack automation in `harmoniqs/amico` (morning brief, per-member briefs, EOD check-in, news posts) on GitHub Actions; the unified context grows it to all scheduled agentic work, including local repo/Julia-heavy jobs. Subsumes the dream cycle: `dream-reflect` runs as one job within Notturno. One night's run of the registered jobs is "tonight's notturno".
_Avoid_: cron, scheduler (as concept names), night shift

### Fleet & serving

**Server mode**:
The per-machine stance for where Sessions are served from, in three values. `standalone` — this machine spawns and owns its own chat server (the default; the only mode that ever spawns). `server` — this machine runs the Canonical Server as a system service and the panel attaches to it. `client` — this machine never serves; the panel attaches to the Canonical Server through a Managed Tunnel. Machine-scoped, never synced: a synced "client" landing on the server would silence the fleet.
_Avoid_: profile, spawn vs attach (as concept names)

**Canonical Server**:
The one chat server that owns the fleet's Session store — the single writer every panel attaches to. Runs as a system service on the machine in `server` Server mode, available headless (no editor required). Only one may exist per Fleet.
_Avoid_: master, primary, host

**Fleet**:
The user's machines acting as one logical studio: exactly one Canonical Server plus zero-or-more clients, all attaching to the same Session store.
_Avoid_: mesh, cluster

**Local fallback**:
The deliberate, user-invoked escape hatch: a `client` machine temporarily serving itself locally (a `standalone` spawn) while the Canonical Server is unreachable. Explicit about its trade-off — fleet history returns on reconnect, and Sessions made during fallback merge back into the Canonical Server on rejoin. Never silent: an active Local fallback is a first-class, visible state.
_Avoid_: offline mode, degraded mode

**Rejoin**:
The closing half of Local fallback: on reconnect, the client ships its local Session shard to the Canonical Server, which merges it as the single writer (id-guarded inserts, strictly-newer-wins per row, event-position guard, schema-drift column mapping). After Rejoin, fleet history is whole again — nothing strands on the client.
_Avoid_: sync (bidirectional connotation), upload

**Fleet token**:
The shared secret authenticating a client to the Canonical Server's data routes — minted when the fleet server is enabled, stored at 0600, handed to clients during the ssh-based setup flow. The sibling of the per-boot server password (ADR 0002): that guards a spawned server its extension owns; this guards the service no extension spawns.
_Avoid_: API key, password

**Managed Tunnel**:
The self-healing SSH local-forward a `client` uses to reach the Canonical Server — one component with two launchers. The extension spawns and supervises it for interactive panels (reconnect with backoff, address candidates probed LAN-before-overlay, health surfaced in the status bar); a headless launcher (`amico fleet tunnel`) serves panel-less consumers such as scheduled jobs. Failures are always visible to its consumer — never an invisible external service.
_Avoid_: port forward (as a concept name), launchd tunnel

### Orthogonal axes

**Entitlement**:
Expand Down
68 changes: 68 additions & 0 deletions docs/adr/0005-managed-fleet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Managed Fleet: one canonical server, clients attach, nothing silently forks

Status: draft (2026-08-07)

A user's machines form one logical studio — one canonical chat server, many panels — but
until now that topology lived entirely in ops duct tape: a hand-written ssh config, a
hand-rolled launchd tunnel agent, and an extension whose only behavior was to spawn a local
server, so the "client attaches through the tunnel" design worked *by accident* (the spawn
lost a port race and the health probe rode the tunnel). On 2026-08-07 this failed three
times in one day on one fleet: a silent three-week fork (279 sessions invisible to the
canonical store), a fork reborn during a tunnel reconnect gap, and a panel stranded dead
after a tunnel outage with no self-recovery. Each failure mode was detected by a human
running diagnostics by hand; the product saw nothing.

This ADR makes the fleet a product feature: explicit **Server mode** (`standalone` /
`server` / `client`), the **Canonical Server** as a product-managed system service, the
**Managed Tunnel** as a self-healing extension-owned component, an identity handshake and
**Fleet token** on the server, and **Local fallback** with **Rejoin** merge as the
user-facing repair hatch. Terms per `CONTEXT.md` (Fleet & serving).

**Why:** the deciding requirement is not elegance but repairability for an open-source
user: when the fleet breaks, the user must be able to keep working (Local fallback) and
later rejoin without losing sessions — and the product must never, under any network
condition, silently serve a forked store. Weighing the alternatives: Remote-SSH moves the
user's whole editing context to the server (rejected as the primary pattern — "chat on the
laptop about the laptop's files" breaks); ops-only hardening keeps the fragility (rejected
— we spent a day chasing it); spawning on the server machine from its editor ties fleet
liveness to an editor's lifetime (rejected — the couch scenario). The server-side merge and
identity routes follow ADR 0002's seam precedent: the fork server owns what it owns, with
CLI/headless parity.

**Conditions of merge (the hardening grafts):** the never-fork invariant is enforced by
construction (client/server modes contain no spawn path) and pinned by tests; machine-scoped
settings (`scope: machine`) so Settings Sync can never carry `client` onto the server or leak
the token into a synced file; the merge policy is locked by golden-shard fixtures recovered
from the 2026-08-07 incident (id-guarded inserts, strictly-newer-wins per row with
`directory` excluded, event `(aggregate_id, seq)` position guard, column-name mapping for
schema drift, single FK-off transaction, refuse-and-preserve on unmappable drift); identity
handshake verified before every attach (role + fleet id), mismatch refused with actionable
copy; loopback-only binding with mutation routes refusing otherwise (ADR 0002 graft
persists); the service binary lives at a stable path with the upgrade choreography
(replace → restart → health probe) owned by the extension.

**Flip condition:** revisit the Managed Tunnel + identity ownership if opencode upstream
gains a native remote-attach capability with identity and auth (we would adopt rather than
maintain ours); revisit the Rejoin merge toward park-local if cross-version schema drift
produces merges we cannot verify — the invariant is "never lose, never silently corrupt",
and a merge we cannot verify violates it.

**Accepted costs:** the vendored fork's route surface grows again (identity + rejoin) —
ADR 0002's highest-conflict-on-rebase spot gets busier; the rejoin merge is the most
intricate component and its correctness budget is paid in fixtures and tests, not in review
cleverness; the service installer is per-platform (launchd now, systemd when a Linux server
appears); headless fleet consumers wait for the tunnel's second launcher.

**Considered:** Remote-SSH pivot (zero fleet code, wrong workflow); ops-only hardening
(zero build, keeps the fragility); two modes spawn/attach (server behaviors smear into
spawn); a named Fleet object with membership (heavier than a studio needs); CLI-daemon
tunnel (invisible failures); editor-owned canonical server (fleet dies with the editor);
mDNS auto-discovery setup (LAN-only, extra surface — ssh bootstrap with manual fallback
chosen); anonymous loopback auth (SSH as the boundary — rejected per ADR 0002's threat
model; the fleet token is the per-boot password's sibling for a service no extension
spawns).

**Prior art / source:** the 2026-08-07 incident and recovery (fixtures at
`~/.amico/fleet-recovery/2026-08-07/`); harmoniqs/amicode#279 (attach-only mode, filed from
the incident); the fleet playbook in the Amico skill set; ADR 0001 (at-rest secret
discipline), ADR 0002 (server-route seam, per-boot password, loopback graft).
Loading