Work on Chimaera's structured chat mode (Tier B) — the stream-json/app-server/ACP drivers, the seq-numbered journal + gap-replay, the server glue, and the chat web UI. Use when adding or changing a chat feature, a driver, the event model, the journal/replay, the view-switch/rewind flow, or the chat UI; when a chat change needs live verification; or when deciding whether a change requires the (billed) chat-smoke suite.
Installs into .claude/skills of the current project.
Are you the author of Chat Mode?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/martinappberg-chat-mode-chimaera)
---
name: chat-mode
description: Work on Chimaera's structured chat mode (Tier B) — the stream-json/app-server/ACP drivers, the seq-numbered journal + gap-replay, the server glue, and the chat web UI. Use when adding or changing a chat feature, a driver, the event model, the journal/replay, the view-switch/rewind flow, or the chat UI; when a chat change needs live verification; or when deciding whether a change requires the (billed) chat-smoke suite.
---
# Working on chat mode (Tier B structured agents)
Chat mode drives coding-agent CLIs through their **structured** protocols instead
of a PTY, and renders the result in a rich UI. It spans three places — read the
AGENTS.md that governs the part you're touching first, they are the fast map:
- Engine: [`crates/chimaera-agent/AGENTS.md`](../../../crates/chimaera-agent/AGENTS.md)
— drivers, journal, registry, event model. Wire facts:
[`PROTOCOL.md`](../../../crates/chimaera-agent/PROTOCOL.md).
- Server glue: [`crates/chimaera-server/AGENTS.md`](../../../crates/chimaera-server/AGENTS.md)
— `chat.rs`, the WS/REST seams, the lifecycle locks.
- UI: [`web-ui/src/lib/chat/AGENTS.md`](../../../web-ui/src/lib/chat/AGENTS.md)
— the store reducer, the socket, the components.
## The end-to-end path (know it before you change it)
```
child stdio ─▶ driver (claude.rs/codex.rs/acp.rs) ─▶ AgentEvent ─▶ ChatManager pump
│ seq + journal + broadcast
ws.rs /ws/chat/{id} ◀── ChatManager::attach (replay+live) ──┤
│ ready(head) → batch replay → live ev │
▼ │
chatWs.ts ─▶ store.apply (reducer) ─▶ ChatView │
▲ │
socket.send(AgentCommand) ─▶ ws.rs ─▶ ChatManager::command ─┘─▶ driver ─▶ child stdin
```
The **seq number is the contract** end to end: assigned once in
`Journal::append`, replayed on reconnect from the client's `last_seq`. Don't
break, reset, or renumber it anywhere.
## Verifying a change — pick the right level
1. **Hermetic (always, free).** `cargo test -p chimaera-server` and the driver
crate's `tests/manager.rs` (via `bin/fake-claude`) exercise the whole
pipeline — spawn → handshake → mapping → journal → broadcast — with no
network or billing. Add/extend a hermetic test for any logic change.
2. **Live app (for anything user-visible).** Bring up the isolated dev daemon and
its served UI (see the **develop** skill; use `chimaerad-isolated` in a worktree),
open a chat session, and drive the actual flow — send a turn, hit a
permission, toggle chat↔terminal, kill the socket and watch it reconnect.
Don't claim a UI/lifecycle change works from unit tests alone.
3. **`just chat-smoke` (REQUIRED when a driver or an agent CLI changes; bills a
few cents).** Claude/Codex's wires are unversioned and pinned to
`TESTED_*_VERSION`; only the live suite catches upstream drift. ACP
negotiates v1 but still needs **`just chat-smoke-acp`** when its driver or
CLIs change (`CHIMAERA_TEST_AGY_ACP` / `CHIMAERA_TEST_GROK` name the official
executables). Hermetic tests cannot prove real-agent behavior. Record any
new wire fact in `PROTOCOL.md` the moment you learn it.
### Codex schema preflight (free, before the live gate)
Current Codex app-server builds can emit a version-matched TypeScript or JSON
schema (`codex app-server generate-ts --out DIR` / `generate-json-schema --out
DIR`). Before editing `codex.rs`, generate into a temporary directory and diff
the request/notification types you are touching against the mapper and
`PROTOCOL.md`. This is a fast, non-billable drift check and the authoritative
shape inventory for that installed binary; it **does not replace**
`just chat-smoke`, because ordering, conditional emission, and lifecycle
behavior are not proven by a schema.
## Rules that bite (all enforced in review)
- **Bounded everything** — keep the daemon light on shared hosts (target
~150 MB RSS), whether in a compute allocation or on an allowed login node. Bounded channels, capped journal/ring, capped per-line reads, caps at
*event construction*. No blocking fs on the async pump or the reactor
(`spawn_blocking` journal reads/copies).
- **Agent output is untrusted** — sanitize it in the UI (`Markdown.svelte`);
cap it before it enters an `AgentEvent`.
- **Protocol is authoritative** in chat mode — derive session state from events,
not hooks (hooks are unreliable under `-p stream-json`).
- **Kill-then-respawn isn't atomic** — resolve respawn preconditions before
killing; serialize concurrent view switches.
- **Keep the normalized contract consistent across drivers** — audit caps,
turn-end resets and unhandled frames in the other adapters. Advertise native
controls only when the provider supports them.
## When you're done
- Build `web-ui/dist` before Rust checks on a fresh checkout. `just check` must be
green (plugin assets, fmt, clippy, and both Rust workspaces' tests); if
`web-ui/**` changed, run its `check`, `test`, and `build` scripts too (Node 22).
- Say in the PR what you ran and observed (hermetic + live; chat-smoke if a
driver changed), per the repo's verify-live rule.