# Changelog

All notable changes to 2200 are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versions follow calendar versioning: `YYYY.M.D` (the UTC date of the cut, no leading zeros, at most one release per day), so an operator can read at a glance how far behind they are. Versions before 2026.6.12 followed semver; `0.1.0` below was never published.

## [Unreleased]

## [2026.612.2032] ... 2026-06-12

Phase 1 hardening for first production installs. Second cut of the day ... the first same-day release under the extended CalVer scheme.

### Fixed

- **Pub owners derive from the operator's identity, never a baked-in default.** `composePubMd` requires an owner; `Supervisor.createPub` reads the user identity's pub handle and fails loud with a fix-it message when no identity exists yet. Previously every install's pubs were created with `owner: doug`. (#271)
- **Hardcoded `doug` swept from every shipped string**: starter-pack Agent guidance now uses `@<operator-handle>` and "the operator", agent-loop prompt examples, the `task_await_response` tool description, CLI help text, the web Embassies placeholder, and the model-catalog notes field are all neutral. Test fixtures moved to `alice`/`operator`. Dated design-decision attribution comments are deliberately retained as project history. (#271)
- **Two test flakes root-caused**: the scheduler-integration test read the schedule file inside the enqueue-before-persist write gap; the pub-client test asserted `roomState()` synchronously while the `room_state` frame was still in flight behind the welcome handshake. Both now poll the actual condition. (#272)

### Security

- **Timing-safe bearer comparison** in the web token store's `findByValue` (was `===`, which leaks prefix-match timing). Same `timingSafeEqual` discipline the connector listener already uses. (#271)
- **The permission evaluator and connector inbound router now have test coverage** (19 + 21 tests; both were previously untested). The router suite pins the secure defaults: unknown DM senders go to operator pairing, unknown groups are blocked, group activation requires a mention, self-echoes never wake an Agent, and a platform's `mentioned: false` beats substring matching. (#272)

### Added

- **Operator runbook sections in the README**: what `2200 update` does to a running fleet (stop → install → restart, checkpoint resume for non-destructive tasks, rollback attempt on failure), backup/restore for the two state directories, and a troubleshooting quick-reference. (#272)

## [2026.612.1935] ... 2026-06-12

First release published to the npm registry. Versioning extends to `YYYY.MDD.HHMM` (month+day packed into the minor slot, UTC time of the cut in the patch slot) per the v2 decision ... npm rejects four-segment versions, so `2026.612.1935` is the three-slot form of "June 12 2026, cut at 19:35 UTC". Same-day releases are now possible ... this cut supersedes the same-day `2026.612.1857` attempt, which npm's name filter rejected and which never reached any registry.

### Changed

- **Package renamed to `@twentytwohundred/2200-cli`.** npm's registry blocks new all-numeric package names ("That word is not allowed"), confirmed empirically: a minimal stub under `@twentytwohundred/2200` was rejected while `@twentytwohundred/2200-cli` and `@twentytwohundred/cli` both published cleanly. The `2200` binary name, the `curl https://2200.ai/install.sh | sh` one-liner, and all on-disk paths are unchanged ... only the npm package name moved. npm offers no allowlisting path for blocked names (their name-claim process covers trademark disputes only), so `2200-cli` is the permanent package name. `@twentytwohundred/cli` is held as a claimed alias.

### Fixed

- **`pnpm verify` builds before testing.** The release workflow's first-ever run exposed a deterministic ordering bug masquerading as the old chaos-test flake: `verify` ran tests before build, but `supervisor-bounce-survival` spawns a real Agent from `dist/runtime/agent/bootstrap.js`, which doesn't exist on a fresh runner. The Agent spawn died instantly and the registration predicate timed out at 20s ... three consecutive release failures. `ci.yml` was already immune via an explicit Build-before-Test step; local `verify` also no longer silently tests a stale `dist/`. (#268)

## [2026.6.12] ... 2026-06-12

First published release ... the first 2200 version to reach the npm registry and GitHub Releases, and the first under calendar versioning.

### Fixed

- **CLI sources `runtime.env` and `oauth-apps.env` on every invocation** via a commander `preAction` hook, so any command that reads a provider key (`agent build` auto-pick, `oauth login` cred checks, direct `resolveProvider` calls) works from a bare shell instead of failing despite correctly stored keys. Live regression 2026-06-03. (#265)
- **`Supervisor.stopAgent` kills orphaned Agent processes when the tracked map is empty**, so a stop request can no longer leave an untracked Agent process running. (#263)

### Added

- **Paste-a-key provider setup in first-run.** The first-run wizard offers an API-key path alongside the SuperGrok OAuth sign-in, so a new install can bind any supported provider without leaving the terminal. (#264)

- **Embassy arc cleanup pass (Phase 2 / PR-B6).** Final piece of the embassy/shelf arc. Completes the `connector.embassy_*` audit family and validates the full chain end-to-end with an integration test.
  - **Three new audit events** (normal tier — operator-noteworthy lifecycle):
    - `connector.embassy_registered` — fires when an embassy / conduit binding is established (atomic or two-step path).
    - `connector.embassy_retired` — fires when a conduit is retired and stops routing.
    - `connector.embassy_shelf_approval_resolved` — fires on operator approve / reject of a pending shelf placement. Decision recorded in extras.
  - **`Supervisor.rejectShelfPlacement`** now reads the pending approval before deleting so it can emit the rejected event with the correct embassy context.
  - **End-to-end chain test** at `tests/runtime/mcp/connector/embassy/chain.test.ts` exercising the locked shape: register embassy → contribute (lands in embassy brain) → embassy autonomous `shelf_place` → embassy `shelf_request_human_placement` → operator approves → `buildShelfPreview` surfaces all items with `self_reflected` + prefix-variation by `source_type` → `applyCollectionTransition` on one-shot transitions to collected → standing item stays pending. Validates both data flow and audit-event flow. Three test cases (full chain, retire fires event, reject fires event).

- **Settings UI for embassies + atomic OAuth-and-embassy registration (Phase 2 / PR-B5).** Operator polish on top of the embassy substrate. The "register a connection to Grok" intent now maps to one Settings flow, not two cascading ones.
  - **`Settings → Embassies`** sub-section: list of registered conduits with mode, external model, embassy agent, registered-at, last-seen-at. Two-step retire confirm (no `window.confirm`). Same idiom as `Settings → OAuth Clients` and `Settings → Work Packages`.
  - **Atomic registration form**: one submit mints the OAuth client + provisions the embassy Agent. Result page shows the full paste block for `grok.com/connectors → Custom` (MCP server URL, Client ID, Authorization/Token endpoints, scopes, Token Auth Method). When mint_secret is enabled the client secret is shown once.
  - **Rollback on failure**: if embassy registration fails after the OAuth client is minted, the client is automatically revoked so operators aren't left with orphaned credentials.
  - **Daemon HTTP routes** (loopback): `GET /api/v1/connector/conduits`, `POST /api/v1/connector/conduits` (atomic), `POST /api/v1/connector/conduits/:id/retire`. Mirror the CLI verbs `2200 connector mcp register|list|retire` plus the atomic variant.
  - **`Supervisor.registerEmbassyAndOAuthClient`** is the new atomic primitive — wraps `registerOAuthClient` + `registerEmbassy` with rollback semantics. Existing CLI verbs continue to use the two-step path.
- **`shelf_pull` MCP tool + `get_fleet_context.shelf_preview` surfacing (Phase 2 / PR-B4).** The moment Grok actually sees the continuity primitive. With this in, the full embassy + shelf + preview + pull + self-reflected + one-shot-collection chain works end-to-end against the real grok.com flow.
  - **`shelf_pull(shelf_item_id)`** new MCP tool. Routes via OAuth `client_id` → conduit → embassy. Returns the full body + provenance summary. For one-shot types (`question`, `research_request`) the type-driven collection transition (PR-B2's `applyCollectionTransition`) fires server-side — same-call-session enforcement of the locked "collected" rule. Standing types stay pending after pull (continuous re-surfacing). Already-collected items return the body without re-firing the transition. Cross-conduit refusal: items attributed to a different OAuth client_id return `unknown shelf_item_id` (threat-model tightening).
  - **`get_fleet_context.shelf_preview`** optional response block. Hard cap 10 inline items + `next_priority_ids` long-tail (next 10 IDs without content). Priority order per spec section 7 verbatim: standing-pending first, then high-priority, then most-recent `ingested_at`, with collected-standing items below pending. Deterministic score formula for reproducible test ordering. Block omitted entirely when the caller has no registered conduit (static-bearer / unregistered OAuth).
  - **`self_reflected`** detection: true when an item's `source.client_id` matches the calling OAuth client. Excerpt is prefixed by a model-readable sentence varying by `source_type`: "the fleet flagged it for your return" (embassy_autonomous) vs "an operator curated it for your return" (human_curated). Stateless-client compatible — no session bookkeeping needed.
  - **Excerpt** is the first 500 chars of the item body, truncated on a word boundary with `...` appended.
  - **Two new audit events**: `connector.embassy_shelf_pulled` (passive; fires on every `shelf_pull`) and `connector.embassy_shelf_preview_surfaced` (passive; once per `get_fleet_context` call with a non-empty preview, counts `items_surfaced` + `self_reflected_count` + `total_pending`). Rest of the `connector.embassy_*` family lands in B6.
- **`propose_work_package` + `get_research_brief` route through the embassy (Phase 2 / PR-B3b).** Fast follow-up to PR-B3 part 1. The remaining two connector tools now respect embassy routing; reads search across the shared brain + every registered embassy so operator workflows (CLI approve, web Settings tile) work without the operator naming a specific embassy.
  - **`writeProposedPackage`** gains an optional `embassyAgent` parameter. When set, the package note lands in the embassy's brain (tagged `relationship-history` + `work-package`) instead of the shared brain.
  - **`Supervisor.proposeWorkPackage`** threads `callingClientId` through and calls `resolveCallingEmbassy` to pick the embassy. The MCP tool plumbs `callingClientId` from `ConnectorMcpServerDeps`.
  - **`readWorkPackage` / `listWorkPackages` / `patchPackageFrontmatter`** search across the shared brain + every registered embassy brain; reads and lookups don't require operator-supplied embassy context. List aggregates with dedup by `package_id`.
  - **`readBrief`** (the read side of `get_research_brief`) searches shared brain first, then every registered embassy. Briefs synthesised after B3 land in the embassy that owns the conduit.
  - **`locatePackageStore`** helper centralizes the cross-store search pattern; same shape adopted in `readBrief` for briefs.
- **`contribute_to_thread` routes through the embassy + one-time pre-embassy note migration (Phase 2 / PR-B3 part 1).** Connector contributions now land in the embassy's brain (tagged `relationship-history`) for OAuth-authenticated callers with a registered conduit. When the first conduit is registered, existing pre-embassy notes are migrated to the new embassy automatically (sentinel-tracked, idempotent).
  - **Routing helper** `resolveCallingEmbassy(home, callingClientId)` — single lookup surface from OAuth `client_id` → conduit → embassy. Returns null for static-bearer callers and unregistered clients; tools fall back to legacy ownerless-note behavior. Records `last_seen_at` on the conduit on every match.
  - **`contribute_to_thread` migrated**: both thread and agent targets route through the embassy when one is registered for the calling OAuth client. Thread anchors land in the embassy's brain (tagged `research-thread` + `relationship-history`); per-Agent contributions land in the embassy's brain too with `target_agent` preserved in extras for cross-reference.
  - **`callingClientId` plumbed end-to-end**: the listener's `/mcp` preHandler stashes the verified OAuth client_id on the request; the per-request MCP server receives it via `ConnectorMcpServerDeps`; tool handlers look up the embassy through the routing helper.
  - **One-time migration** at first-conduit-register time. Migrates research threads + standing briefs from `<shared>/brain/` + per-Agent grok-contributions from every Agent's brain into the embassy's brain. Sentinel at `<home>/state/connector/note-migration-complete.json` guards re-runs; idempotent. Per-Agent contributions retain `target_agent` extras. Operator can force a re-run via `clearMigrationSentinel` (post-retire-and-re-register recovery).
  - **`propose_work_package` and `get_research_brief` migrations** ship in a parallel small PR (B3b) to keep PR-B3 reviewable.
- **Embassy shelf data model + nine internal tools + sensitivity gate (Phase 2 / PR-B2).** Second slice of the embassy/shelf arc. Lays the shelf substrate; the connector tools migrate through it in B3; remote-model surfacing in `get_fleet_context` lands in B4.
  - **Shelf data model** per spec section 5 verbatim. Items live at `agents/<embassy>/brain/shelf/<shelf-item-id>.md`, frontmatter-typed (Zod-validated) with full provenance (`source.client_id` for `self_reflected` detection, `provenance.ingested_at` / `_by` / `chain`). `shelf_item_id` is `shelf_<24 base32>`. Item types are `question`, `context`, `research_request`, `synthesis_prompt`, `agenda`; one-shot vs standing collection semantics per spec section 6.
  - **Nine internal tools** (spec section 8 + the `shelf_read` Grok approved in the final pass), all under the `shelf_` namespace per the runtime's `<namespace>_<verb>` convention. Spec → runtime mapping documented inline:
    - `shelf_place` (spec: `place_on_shelf`) — writes a `pending` item.
    - `shelf_resolve` (spec: `resolve_shelf_item`) — forces `collected`.
    - `shelf_reopen` (spec: `reopen_shelf_item`) — `collected` → `pending`.
    - `shelf_reprioritize` (spec: `reprioritize_shelf_item`).
    - `shelf_remove` (spec: `remove_from_shelf`) — deletes the file.
    - `shelf_list_mine` (spec: `list_my_shelf`) — full bodies; embassy-internal, distinct from the bounded preview that lands in B4.
    - `shelf_read` (new) — single-item full body + frontmatter; embassy-internal pull path.
    - `shelf_curate_from_inbox` (spec: `curate_from_inbox`) — moves operator-curated items to the shelf with `source_type: human_curated`.
    - `shelf_request_human_placement` (spec: `request_human_shelf_placement`) — only path for items needing operator approval.
  - **Sensitivity gate (locked 2026-05-26)**. `shelf_place` rejects `sensitivity: 'private'` at the Zod schema layer (the enum is restricted to `'none'`); `shelf_request_human_placement` is the only path. Operator approval via `2200 connector mcp shelf approve <token>` writes the item with `source_type: human_curated` and the operator's name as curator — the human approval IS the desensitization.
  - **Rate limiting (locked 2026-05-26)**. In-memory rolling 60-second window per embassy. System defaults 20/min soft (audit-only) + 100/min hard (rejects with `ToolDeniedError` reason `placement_rate_exceeded`). Per-embassy overrides on the conduit record's optional `rate_limits` block. Resets on Agent restart.
  - **Five audit event types**: `connector.embassy_shelf_item_placed` (passive, fires on every successful write including post-approval), `_item_resolved` (passive, manual + auto), `_human_approval_requested` (normal, the operator-actionable event), `_item_read` (passive, embassy reads its own shelf), `_rate_threshold` / `_rate_exceeded` (normal / important, burst guard).
  - **CLI verbs**: `2200 connector mcp shelf approve <token> [--operator-name <name>]` and `2200 connector mcp shelf reject <token>` for the human-approval flow.
  - **Dedicated-embassy registration** now defaults `tools:` to include all nine shelf tools so the embassy can actually use the shelf out of the box; attached mode preserves existing tools (operators add explicitly).
  - **Spec→runtime "collected" rule** (per Grok's 2026-05-26 wording): an item is considered collected only when the remote model has received the full (or sufficient) body during the same inbound call session, not when it has only seen a preview. The `applyCollectionTransition` helper enforces type-driven rules: one-shot types → `collected`, standing types stay `pending` after the model pulls.
- **Embassy substrate + conduits registry (Phase 2 / PR-B1).** First slice of the locked embassy/shelf arc (spec: `wiki/inbox/grok/2026-05-23-embassy-shelf-handoff.md`). Lays the substrate; subsequent PRs in the arc layer on top.
  - **`embassy` block on the identity frontmatter** marks an Agent as currently acting as a fleet embassy for an external MCP-speaking model. Records `external_model`, `client_id` (the OAuth client this embassy serves), `mode` (`dedicated` or `attached`), `registered_at`. Optional + additive; existing identity files parse cleanly.
  - **Conduits registry** at `<home>/state/connector/conduits/<client_id>.json`, keyed by OAuth `client_id` per the 2026-05-26 locked decision. The access token at `/mcp` already carries `client_id`, so subsequent PRs route to the right embassy without an extra lookup. Operator-visible projection regenerates at `<shared>/brain/conduits.md` (same pattern as `<home>/state/fleet.md` — rebuildable mirror, never edit by hand).
  - **`Supervisor.registerEmbassy({client_id, external_model, embassy_agent, mode, display_name, ...})`** — validates the OAuth client exists + isn't revoked, ensures no existing conduit for the same client, then either creates a fresh Agent with the embassy identity template (dedicated) or patches an existing Agent's identity with the embassy block (attached). Initializes the embassy's brain subdirs per spec section 4: `shelf/`, `relationship-history/`, `standing-briefs/`, `notes/`. Writes the conduit record + regenerates the shared-brain index.
  - **CLI** under `2200 connector mcp <verb>` per spec section 10:
    - `register --client-id <id> --external-model <name> --embassy-agent <name> --mode dedicated|attached --display-name <text>` (with `--model-tier`, `--model-provider`, `--model-id` for dedicated mode)
    - `list` — table of registered conduits with mode, last-seen, retired-at
    - `retire <client-id>` — marks the conduit retired; the OAuth client + embassy Agent stay intact
  - **Embassy identity template** (spec section 3, verbatim shape): the Agent's role is the "<External> Embassy," explicit memory rules forbid pushing information outward, the conduit-status block carries connection metadata. Persona text loads as a normal system prompt.
- **OAuth Settings UI + runbook + grok.com defaults (Phase 2 / PR-A2).** Operator-facing polish on top of the PR-A1 substrate.
  - **`Settings → OAuth clients`**: Web Settings page gains a sub-section for registering / listing / revoking / rotating OAuth clients. Same two-step destructive confirms and copy-on-show secret-display discipline as `Settings → Work packages`. Register form defaults the redirect URI to the canonical grok.com callback discovered empirically 2026-05-23 (`https://grok.com/connectors-oauth-exchange-code/`); the result page prints the full block of values to paste at `grok.com/connectors → New Connector → Custom`.
  - **CLI default**: `2200 connector oauth-client register` now defaults `--redirect-uri` to the canonical grok.com callback. Override with the flag for other consumer-side MCP clients (Claude Desktop, ChatGPT MCP, etc.).
  - **`GROK_CONNECTOR_REDIRECT_URI` exported** from `src/runtime/mcp/connector/oauth/client-store.ts` so the CLI, daemon, and runbook stay in sync on the canonical value.
  - **`/.well-known/oauth-protected-resource`** (RFC 9728): protected-resource metadata published. grok-connectors-manager/0.1.0 probes this on every connect; cheap spec compliance.
  - **Daemon HTTP routes** (loopback): `GET /api/v1/connector/oauth-clients`, `POST /api/v1/connector/oauth-clients`, `POST /api/v1/connector/oauth-clients/:id/revoke`, `POST /api/v1/connector/oauth-clients/:id/rotate-secret`, `GET /api/v1/connector/grok-redirect-uri`. Same Supervisor methods the CLI verbs already hit.
  - **Operator runbook** at `wiki/grok-connector-setup.md` rewritten to reflect the empirical reality: choose-your-auth-path table, OAuth registration walkthrough, the static-bearer path documented separately. The Tesla / in-car section preserves "verify on your hardware" verbatim and explicitly notes it does not change until Doug personally watches it fire.
- **MCP connector OAuth 2.0 Authorization Server (Phase 2 / PR-A1).** Resolves the architectural gap discovered when Doug hit grok.com/connectors → New Connector → Custom: the consumer UI requires OAuth 2.0 + PKCE, not the static bearer Phase 1 shipped. The connector now serves both: the static bearer continues to work for developer-API callers (Claude Desktop, headless scripts), and a full OAuth AS now serves consumer-grade Grok / Tesla.
  - **OAuth endpoints** on the existing `:2201` connector listener: `GET /oauth/authorize` (code grant with mandatory PKCE S256), `POST /oauth/token` (code exchange + refresh-token rotation), `POST /oauth/revoke`, `GET /.well-known/oauth-authorization-server` (RFC 8414 metadata).
  - **Pre-authorize-at-registration consent model** (locked with Grok 2026-05-23): the operator registers each client at the trusted loopback surface via `2200 connector oauth-client register --display-name <...> --redirect-uri <...>`. Subsequent `/authorize` requests over the public tunnel proceed without operator presence, validated against the registered set. Zero operator-facing UI is exposed through the tunnel — preserving Phase 1's loopback-only operator-surface invariant absolutely. The pre-authorization step IS the human security boundary.
  - **Opaque tokens, sealed vault** (parallel to PR 1a's `bearer-store`): `2200-mcp-at-<43 base64url>` access tokens (24 h TTL default), `2200-mcp-rt-<43 base64url>` refresh tokens (90 d TTL default, with rotation_chain replay-detection per RFC 6749 BCP), in-memory authorization codes (60 s, one-time-use). Distinct HKDF namespaces; on-disk filenames are SHA-256 prefixes of the token (`ls` does not expose secrets).
  - **Refresh-token reuse detection.** Each refresh has a `chain_id` + `rotated` flag. Successful refresh marks the consumed token rotated. Reuse of a rotated token triggers `revokeChain(chain_id)` and emits `connector.oauth_refresh_reuse` (important tier) — the canonical compromise signal.
  - **Bearer ↔ OAuth coexistence on `/mcp`.** Token-prefix disambiguation: the listener's preHandler tries OAuth access-token verification first (when `Bearer 2200-mcp-at-...`), then falls through to the static-bearer constant-time-compare (Phase 1). Both auth paths live on the same endpoint; Phase 4's dispatcher hard-guard remains the real permission boundary regardless of which path authorized the request.
  - **Strict redirect-URI pre-registration** (no TOFU, no pattern matching). The operator pastes Grok's exact callback URL at registration time. `/authorize` rejects any unregistered redirect with `invalid_request` + a 400 response (no open-redirector hazard).
  - **PKCE S256 mandatory; `plain` rejected.** Client secrets are optional (PKCE-only is the recommended path matching grok.com's "Token Auth Method: none" default). When the operator opts in to a secret with `--mint-secret`, it's scrypt-derived-and-stored; the plaintext is shown once at registration time and never re-exposed (parallel to the static-bearer regenerate UX).
  - **CLI** (`2200 connector oauth-client register | list | rotate-secret | revoke`). The `register` output prints the exact block the operator pastes into grok.com/connectors → Custom (MCP server URL, Client ID, Authorization Endpoint, Token Endpoint, Scopes, Token Auth Method).
  - **Audit event family** `connector.oauth_*`: `client_registered` / `client_revoked` (lifecycle), `authorize_succeeded` / `authorize_rejected` (per-request), `token_issued` (initial grant + refresh rotation), `refresh_reuse` (important tier, fires on compromise).
- **Work-package approval Settings tile + operator runbook (PR 5).** Phase 1 wrap-up.
  - **Settings page tile** (`Settings → Work packages`) renders proposals handed in via `propose_work_package`. Filter by "Awaiting review" (default) or "All packages." Each package card shows summary + plan + risks + success criteria parsed from the body, with one-click Approve (two-step confirm) or Reject (optional reason). Approval routes the plan to the primary Agent via the existing task-submit substrate; rejection records the decision. Auto-refreshes every 15 s.
  - **Daemon HTTP routes** (loopback): `GET /api/v1/connector/work-packages` (with optional `?status=<...>` filter), `POST /api/v1/connector/work-packages/:id/approve`, `POST /api/v1/connector/work-packages/:id/reject` (`{ reason? }`).
  - **`listWorkPackages` library helper** in `runtime/mcp/connector/work-package.ts`. Sorted by createdAt descending, filtered by status, body included so the UI renders the parsed sections without a second round-trip.
  - **Post-regenerate copy-toast polish** on the MCP connector tile: after the operator clicks Copy on the freshly-minted token, the tile shows a short success-state pointing at the `grok.com/connectors` paste destination. Closes a minor PR 1b review note from Grok.
- **Operator runbook** at `wiki/grok-connector-setup.md`: tunnel options (ngrok quick-start, Cloudflare Tunnel, Tailscale Funnel), the grok.com/connectors registration walkthrough, in-car verify-on-your-hardware language verbatim from the locked Phase 1 handoff, security-posture summary, common knobs, troubleshooting.
- **MCP connector `propose_work_package` + dispatcher hard guard (PR 4).** Grok (or any other MCP connector caller) can hand a proposal of real work to the fleet — the safety-load-bearing piece of the connector substrate.
  - **New MCP tool** `propose_work_package`. Returns `{ status: 'queued_for_review', package_id, package_slug, coordination_task_id }`. The proposal lands as a normal Brain note at `<shared>/brain/work-package-<id>.md` (tagged `work-package`); the primary Agent gets a strict-allowlist coordination task that produces a reviewable plan.
  - **Hard guard in `ToolDispatcher`** (mechanical, not advisory). Two new task-frontmatter fields: `tool_policy: inherit_agent | strict_allowlist` (default `inherit_agent`, existing tasks unaffected) and `allowed_tools: string[]`. The dispatcher rejects off-list calls with `ToolDeniedError(name, 'task_allowlist_violation', ...)` BEFORE the existing identity-level check. Per-task cached lookup.
  - **Two restricted task kinds in Phase 1**: `work_package_coordination` (allowlist: `brain_read_shared`, `brain_search_shared`, `brain_list_shared`, `brain_write_shared`, `pub_post`, `pub_read`) and `standing_brief_synthesis` (PR 3 retrofit; allowlist: `brain_read_shared`, `brain_search_shared`, `brain_list_shared`, `brain_write_research_brief`). Inline "additions require explicit review" comments per the Grok lock 2026-05-23.
  - **Approval surface (CLI)**: `2200 connector work-package approve <package-id>` / `reject <package-id> --reason <...>`. Approval parses `## Plan` steps and submits normal Agent tasks via `cli.task.submit`.
  - **Five new Inbox events**: `connector.work_package_arrived` / `_plan_ready` (important), `_coordination_failed` / `_approved` / `_rejected` (normal).
  - **Supervisor outcome watcher**: lightweight 30 s poll observes terminal transitions of tracked coordination tasks.
- **Standing-brief synthesis layer for MCP connector research threads (PR 3).** Long Grok conversations now get high-quality re-engagement: each research thread maintained by a primary Agent who keeps a synthesized standing brief current as new contributions arrive.
  - **Sibling brief note** at `<shared>/brain/research-<slug>-brief.md` (separate from the chronological log shipped in PR 2). Full-rewrite each synthesis; tagged `standing-brief` + `research-thread`.
  - **Provenance** baked into brief frontmatter ... `synthesized_through`, `contribution_count`, `contribution_first_at` / `_last_at`, `contributor_sources`, `synthesizing_agent` ... computed by the write tool from the thread's chronological log, so machine-readable provenance is always present regardless of how the synthesized text cites its sources.
  - **Supervisor-side reconciler** (default 30 s poll, 60 s debounce) detects threads with `pending_synthesis_at > synthesized_through` + debounce elapsed + primary Agent running, and submits a synthesis task via the existing `cli.task.submit` substrate. The Agent's normal loop runs the LLM synthesis; cost stays on the Agent's budget where it belongs.
  - **New baseline tool** `brain_write_research_brief(thread_slug, brief_body, token_usage?, duration_ms?)` is the write surface the synthesizing Agent calls. Resets the thread's failure counter on success.
  - **New MCP tool** `get_research_brief(thread_slug)` exposes the full brief + provenance to the connector caller (Grok). `get_fleet_context` gains a per-thread `brief_excerpt` (first ~500 chars) + `brief_synthesized_through` + `brief_stale` + `brief_blocked` fields so the orientation packet stays small while signaling staleness.
  - **Failure handling**: three consecutive synthesis failures escalate to `synthesis_blocked: true` (tier-`important` Inbox event). Operator clears with `2200 connector synthesis unblock <thread-slug>`.
  - **Inbox events** (all under the `__connector` synthetic emitter): `connector.synthesis_started` (passive), `connector.synthesis_completed` (passive, includes duration + contribution count), `connector.synthesis_failed` (normal / important on block), `connector.synthesis_primary_missing` (normal).
  - **Global synthesis budget guard** (stretch from the locked design): optional fleet-wide cap on connector synthesis spend over a rolling window, off by default.
- **MCP connector real Phase 1 tools (PR 2).** Two structured tools land on top of the substrate plus the existing `liveness` probe:
  - `contribute_to_thread`: Grok (or any MCP client) hands a structured contribution (`research_findings`, `reasoning`, `sources`, `open_questions`, `proposed_direction`, optional `related_threads`) into one of two destinations via a discriminated `target` union. `{ thread: <name> }` appends a `## <ISO ts>` section to a shared-brain research thread anchor at `<shared>/brain/research-<slug>.md` (created on first contribution, tagged `research-thread`). `{ agent: <name> }` writes the contribution as a standalone Brain note at `<agent>/brain/grok-contribution-<compact-ts>-<hash>.md` (tagged `grok-contribution`). Both paths are normal Brain notes that participate in the existing `brain_search` / `brain_read` surface ... no special-casing in the brain layer. Phase 1 contributions are inert from an execution standpoint (read material only).
  - `get_fleet_context`: small, structured orientation packet (`agents`, `threads`, `recent_activity`) so a returning Grok conversation can pick up cleanly after a long gap. Deliberately small ... PR 3's standing-brief layer is what makes re-engagement high-quality.
- **`connector.contribution_received` Inbox event** (passive) carrying `target_kind` (`thread` | `agent`), `target_name`, `contribution_slug`, `contribution_path` so the Inbox row links straight to the produced note.
- **`bodyLimitBytes` operator escape hatch** for the connector listener. Default raised from 1 MiB to 8 MiB (sized for `contribute_to_thread` research blobs); overridable via `TWENTYTWOHUNDRED_CONNECTOR_BODY_LIMIT_BYTES` env var. Trade-off documented in the as-shipped decision record: larger body = larger public-facing DoS surface.

### Changed

- **MCP connector Phase 1 substrate polish (PR 1d).** Tidy-up pass after Grok's full byte-level review. Documentation strengthened (X-Forwarded-For tunnel-trust assumption inline in `clientIp`; bodyLimit comment flagging the PR 2 need to widen when `contribute_to_thread` lands with large research payloads; per-fleet-salt WHY in `bearer-store.ts`; first-run wording aligned with the Grok-First step). Sync `getConnectorStatus` renamed to `getConnectorStatusFast` with a strong "not for operator surfaces" guard comment ... the async `getConnectorStatusDetailed` remains the only surface used by CLI / web / RPC. `2200 connector token show` now writes paste guidance to stderr (stdout stays clean for `... | pbcopy`). First-run success message uses the same "sealed to disk" voice as the Grok sign-in step. New regression test for the idle → regenerate listener transition. Decision record at [[2026-05-23-mcp-connector-phase1-as-shipped]] pins the substrate-level decisions before Phase 2 begins.

### Added

- **First-run wizard offers MCP connector setup (PR 1c).** After Grok sign-in in the bare-`2200` wizard, the operator is offered a one-question opt-in to mint a connector token inline. Default NO ... keeps the install path uncluttered for users who do not know what MCP is. On yes, the wizard mints the bearer through the daemon's `cli.connector.regenerate` RPC and surfaces the token once with paste-target instructions (`grok.com/connectors` → New Connector → Custom). Failure is non-fatal; the operator can retry via `2200 connector token regenerate` or the Settings tile.
- **MCP connector Settings tile + daemon routes (PR 1b).** Operator UI for the MCP connector lives in Settings: status line + masked token + reveal + copy + 2-step regenerate / disable. New daemon routes on the loopback web UI listener (`GET /api/v1/connector/status`, `GET /api/v1/connector/token`, `POST /api/v1/connector/regenerate`, `POST /api/v1/connector/disable`). After regenerate the freshly minted token is shown once in a copy-to-clipboard banner with the paste target (`grok.com/connectors` Authorization). Inline two-step destructive confirms (no `window.confirm`).
- **Web-UI loopback safety check.** At supervisor start, if `TWENTYTWOHUNDRED_WEB_HOST` is overridden to a non-loopback host the daemon emits a `normal`-tier Inbox event (`connector.web_host_non_loopback`) explaining the foot-gun and how to revert. The MCP-connector security model assumes the web UI is loopback-only.
- **MCP connector substrate (Phase 1 / PR 1a).** 2200 now exposes itself as a remote MCP server, on a dedicated Fastify listener (default `:2201`, configurable via `TWENTYTWOHUNDRED_CONNECTOR_PORT`) isolated from the web UI listener. Bearer-token auth via the sealed vault (constant-time compare, no fallback-allow, 32-byte `2200-mcp-<base64url>` tokens). Every inbound call surfaces as an Inbox event; failed-auth events are throttled per source IP to one per 10 minutes. PR 1a ships the substrate plus a single `liveness` probe tool; the real Phase 1 tool surface (`contribute_to_thread`, `propose_work_package`, `get_fleet_context`) lands in subsequent PRs after Grok's code review on this layer.
  - New `2200 connector` CLI: `token show | regenerate | disable`, plus `status`. `regenerate` and `disable` route through the daemon over UDS so the live listener's cached bearer is swapped atomically.
  - Sealed bearer store at `<home>/state/connector/bearer.json` (AES-256-GCM + HKDF, distinct namespace from the OAuth token store).
  - Inbox audit events: `connector.call_received`, `connector.auth_rejected`, `connector.listener_state_changed`.
  - Architecture review by Grok on listener boundary + auth model before PR; design note at `wiki/inbox/grok/2026-05-22-mcp-listener-auth-design.md`.
- **Grok-First: SuperGrok / X Premium+ subscription auth (no API key).** Sign in to your existing xAI Grok subscription and every Agent set to the new `xai-subscription` provider uses the subscription bearer ... no `XAI_API_KEY` needed. The API-key path remains as a separate, parallel provider (`xai`).
  - Settings page: prominent "Sign in with X / SuperGrok" tile at the top, with the official Grok logo. Inline device-code flow with a phone-friendly URL + code; auto-polling status until completion. (`#237`)
  - Model picker: new "Subscriptions" optgroup pinned at the top of the dropdown. `xAI / Grok (SuperGrok subscription)` is a selectable provider distinct from the API-key sibling, so the credential choice is visible in the Agent's Identity. (`#238`, `#239`)
  - Auto-restart on model switch: changing an Agent's model from the picker now stops + restarts the Agent so the new credential / provider binding is what serves the next request. Inline spinner during the swap; clear error surface if restart fails. (`#240`)
  - First-run installer: the bare-`2200` wizard now offers Grok subscription sign-in inline (default yes) after user-identity mint, so a new install can leave setup with a working Grok credential without ever pasting an API key. (`#237`)
  - CLI: `2200 oauth xai login | status | logout` for headless / scripted setup. Device-code flow with PKCE S256, public-client OAuth. (`#236`)
- **Generic device-code OAuth substrate** (`src/runtime/oauth/device-flow.ts`). RFC 8628 with PKCE S256; reusable for any future public-client provider. Handles `authorization_pending`, RFC-spec `slow_down`, `expired_token`, `access_denied`. (`#236`)
- **Fleet-scoped sealed OAuth token store** at `<home>/state/oauth-tokens/`. AES-256-GCM + HKDF over the per-instance master key with a fleet-scoped HKDF info string. Separate namespace from the per-Agent credential vault. (`#236`)
- **Background OAuth refresh service.** `TokenRefreshService.tick()` now also scans the fleet OAuth store and refreshes within 120s of expiry using `grant_type=refresh_token`. Same failure-cooldown logic as the existing per-Agent path. (`#236`)
- **Daemon HTTP endpoints**: `POST /api/v1/oauth/xai/login/start`, `GET /api/v1/oauth/xai/login/status?session=<id>`, `GET /api/v1/oauth/xai/status`, `POST /api/v1/oauth/xai/logout`. Browser-driven device-code flow without leaking PKCE state across requests. (`#237`)
- **`install.sh` auto-configures `~/.npm-global` on non-writable npm prefix.** Detects the typical Ubuntu/Debian footgun (system `npm` at `/usr` requires sudo), explains the situation in plain language, and auto-fixes it without admin access. Shell-aware (bash / zsh / fish). Idempotent on re-runs. (`#233`)

### Changed

- **Chaos test stability**: eliminated the `supervisor-bounce-survival` flake by waiting for the heartbeat loop to cycle before bouncing the supervisor in-test. Root-cause fix, not a retry config. (`#234`)
- **Adopted `@eslint/js` v10**: bumped from 9.39.4 and adopted the two new rules `preserve-caught-error` and `no-useless-assignment`. 26 violations fixed across the codebase ... most now add `{ cause: err }` chains to thrown errors inside `catch` blocks, so debug sessions see both the symptom and the underlying error. (`#235`)
- **Lock-based PID liveness** for the supervisor and every Agent. Replaced the historical `kill(pid, 0)` liveness check with `proper-lockfile` lock holdership. Eliminates the stranger-PID hazard (recycled OS PIDs falsely reporting our processes as alive). Migration is operator-walkable: a daemon from a pre-lock release is detected and reported with a clear recovery message. (`#230`)

## [0.1.0] ... 2026-05-20

### Added

- **First installable release.** Package published as `@twentytwohundred/2200` on the npm registry.
- **Shell installer** at `install.sh`. One-liner for any macOS or Linux box with Node 22+: `curl -fsSL https://2200.ai/install.sh | sh`. The 2200.ai URL is an nginx reverse-proxy to the script in this repo, so the install script remains a single source of truth in the repo. Detects Node version, refuses to silently elevate via sudo, and installs the latest published version via the user's `npm`.
- **Bare `2200` first-run.** When the CLI is invoked with no subcommand and no prior install state, it walks the user through a guided setup: choose `2200_HOME`, initialize the directory layout, start the supervisor daemon, mint the user identity, point at `2200 agent build` for the first Agent. All input is collected before any side effect, so a `ctrl-C` at any prompt is safe.
- **`2200 update`.** Top-level self-upgrade. Checks the npm registry for the latest published version, prompts (or `--yes`), stops the daemon, installs the new package globally, restarts the daemon. `--check` reports availability without installing. Refuses to auto-upgrade a source checkout.
- **`2200 --version`** now reads from `package.json` at runtime, so `npm version <bump>` is the single source of truth for the installed CLI version.
- **Web upgrade button.** Settings → System tile shows current vs. latest version. Click → 2-step inline confirm → the daemon writes `<home>/state/upgrade-status.json`, spawns a detached helper, and shuts itself down. The helper waits for the daemon to exit, runs `npm install -g`, and starts the new daemon. The web UI polls the status throughout, absorbing the brief mid-upgrade outage, and surfaces the per-stage progress.
- **System HTTP endpoints**: `GET /api/v1/system/version`, `POST /api/v1/system/update`, `GET /api/v1/system/upgrade-status`.

### Notes

- 2200_HOME state (under `~/.local/share/2200/` by default, or whatever you chose at first-run) is never touched by `2200 update`. Upgrades only replace the global package binary.
- Native dependencies (`better-sqlite3`, `sharp`) ship prebuilds for macOS arm64/x86_64 and Linux arm64/x86_64. Other platforms fall through to a build-from-source path that requires a C++ toolchain.
- Windows is not supported on this release. Use WSL2.

### Added (prior to 0.1.0)

- Repository scaffolding: LICENSE, README, AGENTS.md, SECURITY.md, CONTRIBUTING.md, CHANGELOG.md, THIRD_PARTY_NOTICES.md, `.github/` templates and CI workflow.
- Public wiki at [`twentytwohundred/wiki`](https://github.com/twentytwohundred/wiki), mirrored from the canonical Brain-format tree.
- Spec scaffolding (in the wiki): vision, architecture, epic map, seed team, 48 decision records, 11 conventions, per-epic specs, design system + design docs, prior-art analysis with deep findings appendix.
- **Epic 2: Agent runtime minimum.** Shipped. Supervisor, Identity loader, Brain (filesystem-first per [decision record](https://github.com/twentytwohundred/wiki/blob/main/decisions/2026-04-24-brain-is-files-not-database.md)), baseline tool set, plan/run/perm wrapping on every tool call, integer schema versioning everywhere, control-plane protocol over UDS+JSON-RPC.
- **Epic 3: Local pub integration via OpenPub.** Shipped. Pub supervision substrate, user and Agent pub identities, four pub MCP tools, WebSocket wake source, end-to-end smoke test against `@openpub-ai/pub-server@0.3.3`.
- **Epic 3.5: Two-agent demo.** Shipped. Hobby↔Simon coordination on the seed-team box, with the published runbook reproducible end-to-end.
- **Epic 3.6: Multi-provider and ambient routing.** Shipped. Six LLM providers wired (Anthropic native; OpenAI, DeepSeek, Kimi, OpenRouter, Gemini via the OpenAI-compatible adapter and `OPENAI_COMPATIBLE_VENDORS` table). Pub message router service with per-pub roster sidecars, opt-in via `ROUTER_PROVIDER` and `ROUTER_MODEL_ID`.
- **Epic 3.7: Followup model and chat polish.** Shipped. `Identity.model.followup_model_id` for two-tier model selection per Agent. Chat status notices.
- **Epic 3.8: Multi-Agent ack-spiral fix.** Shipped. Structural guards in the wake source: skip the router when sender is a known Agent; respect explicit `@`-mentions; per-pub roster self-upsert on Agent start; complete-roster perspective in router input.
- Local `pnpm patch` for `@openpub-ai/pub-server@0.3.3` (keepalive listener and bartender-guard fixes), to be dropped when v0.3.4 ships upstream. See [openpub-ai/openpub#1](https://github.com/openpub-ai/openpub/issues/1).

### Notes

- No tagged releases yet. Versioning begins when the runtime is feature-complete enough for the first public preview.
- The `wiki/` feature on this repo is disabled; the project knowledge base lives at [twentytwohundred/wiki](https://github.com/twentytwohundred/wiki) (public, markdown on `main`).

[Unreleased]: https://github.com/twentytwohundred/2200/compare/v0.1.0...HEAD
[0.1.0]: https://github.com/twentytwohundred/2200/releases/tag/v0.1.0
