Files
stack/docs/fleet/f4-matrix-connector.md
T
fred 2719ec295c
ci/woodpecker/pr/ci Pipeline was successful
docs(fleet): tier the north star, declare the tier-0 operator surface
Add a capability tier orthogonal to phase. `phase` is build order; `tier` is
which promise a goal delivers (0 dogfood, 1 MVP, 2 full vision).

- AC-NS-0: a tier-0 exit test that can fail. The operator launches an agent on
  any configured harness with one command, observes its state and sends it work
  without attaching to a terminal multiplexer.
- `tier` on every goal and success criterion. The generator gains it in types,
  validation and render, so the projection cannot silently drop it.
- NS-10: an adoption is not complete until the mechanism it replaces is removed.
- Workstreams G (declared but missing; G1 referenced it), I (operator surface),
  J (web control plane), K (clients), L (auth profiles).
- Goals A5 and I1-I9 seeded at tier 0, dependency ordered.
- docs/fleet/north-star.md renamed FLEET-DOCTRINE.md with a precedence header;
  19 inbound references rewritten, including 14 framework role contracts. The
  old name sat one character from NORTH_STAR.md.
- docs/TASKS.md, docs/federation/TASKS.md and docs/fleet/TASKS.md carry
  superseded headers. docs/native-kanban-sot/TASKS.md is corrected instead: it
  advertised a blocker that was not real.
- Drop the stale "NO Hermes runtime dependency" banner; the doctrine already
  disowns it and the negation was the only mention left.

Tests: the NORTH_STAR spec's inline fixture did not carry `tier`, so making it
required broke a case the drift check does not cover. Fixture updated. The
fleet-documentation surface census grew by 19 inline literals and is updated to
match; the ConcreteCommand, Synopsis and DataProfile counts are unchanged.

Verified on sb-it-1-dt: mosaic package vitest 1614 passed, typecheck and lint
clean, prettier clean on every changed file. Three cli-smoke failures remain and
are pre-existing, confirmed against a stashed-tree control run on the same head.
2026-08-20 17:30:54 -05:00

93 lines
4.7 KiB
Markdown

# F4 — Orchestrator chat connector + Matrix (local homeserver)
> **Issue:** #616 · **Doctrine:** `docs/fleet/FLEET-DOCTRINE.md` (#613) — orchestrator-chat-connector decision.
> **Status:** Phase 1 (abstraction + scaffold) in this PR; Phase 2+ are follow-ups (below).
## Goal
The fleet **orchestrator** is the operator's single point of contact. The north-star makes the
chat channel a **user-chosen connector** — tmux today, Discord live ("Mos"), with Matrix /
Telegram / Slack configurable. F4 adds **Matrix** (local homeserver) as a **peer** connector and,
first, the small **connector abstraction** that makes connectors pluggable without touching fleet
core.
## The abstraction (Phase 1 — this PR)
Connectors implement one small, uniform interface (`src/fleet/connectors/types.ts`):
```typescript
interface OrchestratorConnector {
readonly kind: 'tmux' | 'discord' | 'matrix';
send(message: OutboundMessage): Promise<SendResult>; // orchestrator → human
subscribe(handler: (m: InboundMessage) => void): Unsubscribe; // human → orchestrator
health(): Promise<ConnectorHealth>; // reachable + authenticated
}
```
- **send / subscribe / health** — the only surface fleet core depends on. `SendResult` is the
ack half; health() is the liveness half.
- **Thread-aware by metadata** — `OutboundMessage.threadId` / `InboundMessage.threadId` are
optional, so thread-capable connectors (Matrix rooms/threads, the future first-party Mosaic
Discord plugin) fit **without an interface change**.
- **Registry** (`registry.ts`) — implementations register a factory by kind; createConnector(config)
resolves one from roster config. Phase 1 ships the registry + `resolveConnectorKind` (defaults
`tmux` when a roster declares no connector — **back-compat**); the factories land in Phase 2.
### Config model
A roster may carry an optional `connector` block (`roster.schema.json`); absent ⇒ tmux.
```yaml
connector:
kind: matrix
matrix:
homeserver_url: https://matrix.example.internal
user_id: '@mos:example.internal'
room_id: '!abc:example.internal'
```
**Secrets are never in the roster.** `MATRIX_ACCESS_TOKEN` / `DISCORD_BOT_TOKEN` come from the
environment (the gateway env-config pattern that already masks them). The sanitization gate would
reject a token committed to a shipped file anyway.
## Matrix connector (Phase 2)
The connector speaks the **Matrix client-server API** directly over HTTPS (`fetch` — no SDK needed
for MVP), so it is **homeserver-agnostic**:
| Op | Matrix CS-API |
| ----------- | ----------------------------------------------------------------------- |
| `send` | PUT /\_matrix/client/v3/rooms/{roomId}/send/m.room.message/{txnId} |
| `subscribe` | GET /\_matrix/client/v3/sync (long-poll, `since` token) → room timeline |
| `health` | GET /\_matrix/client/versions (reachable) + …/account/whoami (authed) |
| threads | `m.thread` relations ↔ `threadId` |
## Local homeserver (infra, not connector code)
Strategic default: a **self-hosted** homeserver on our own infra — no third-party gateway.
- **Default: Conduit** (Rust, single binary, low resource) — trivial to stand up for a fleet/dev
homeserver.
- **Alternative: Synapse** (mature, feature-complete) for scale.
The connector only needs `homeserver_url` + `user_id` + `room_id` + an access token, so the
homeserver choice is a **deployment** concern (a Phase-2 deploy guide), not connector code.
## Phasing
| Phase | Scope | This PR |
| ----- | --------------------------------------------------------------------------------------- | ------- |
| **1** | Connector interface + types, registry + kind resolution, roster `connector` schema, doc | ✅ yes |
| 2 | Matrix CS-API client (fetch-based send/sync/health) + registered factory + tests | follow |
| 2 | fleet init / `configure` connector-selection UX; roster parse wires the block | follow |
| 2 | systemd launch wiring so the orchestrator starts on the chosen connector | follow |
| 3 | Conduit deploy guide; first-party Mosaic Discord (threads) registers as a connector | follow |
## Back-compat & boundaries
- Existing rosters (no `connector`) resolve to tmux — **zero change**.
- Fleet core never branches on connector kind; it depends only on the interface.
- Cross-host reach rides the **federation** layer (W1), not a bespoke broker (north-star assumption).
- Phase 1 touches **no** `fleet.ts` core (a self-contained `connectors/` module), so it is
independent of the in-flight fleet-config PRs.