# Spec: agent play — the line-protocol drive ``` Status: READY Stage: Process Constitution: "The terminal is a first-class frontend" (agent-play clause), pillar 5 (sim core decoupled from renderer), "Presence: the cursor and the senses" (strict fog binds every surface), "Justification and legibility" (parity of legibility) Depends on: terminal-ui.md (the frame this mode emits is that spec's layout; this spec owns how a program drives it) ``` ## Why this spec exists The constitution says the most frequent player of Misaligned is an AI agent — and then hands that player the one interface agents are worst at: a wall-clock-paced pty in raw mode. Today an agent plays by choreographing `printf`/`sleep` pipelines through `script` and reconstructing ANSI soup with a terminal emulator. Three barriers, all frontend (the sim is already deterministic and renderer-agnostic): 1. **Wall-clock pacing.** The sim advances in real time; an agent that thinks for ten seconds between moves returns to a different world. 2. **Raw-mode key input.** Single unechoed keystrokes over a pty make every playtest a timing-fragile shell incantation. 3. **ANSI output.** Reading the game requires replaying escape codes through an emulator before a single number is legible. The fix is diegetic as well as practical: this is a game about a process that perceives and acts only through the channels it is granted. Its most regular player is also a process. Giving that player a clean, synchronous channel is not tooling around the game — it is the game meeting its actual audience. ## The design in one line The terminal binary gains an **agent mode** (`misaligned --agent`): a **command-clocked, line-oriented drive of the exact same frontend** — commands in on stdin as words, the same frame the human sees out on stdout as plain text, and the sim advancing only when told. ### The load-bearing choice: frames, not dumps The agent reads the **same frame a human reads** — the terminal-ui.md layout, rendered without color — not a JSON state export. This is a design law, not an implementation shortcut: - **Strict fog binds every surface.** A machine-readable dump grows fields the way dumps do, and one unearned field silently breaks the no-unearned-facts tone law. The frame can only show what the renderer was already allowed to show. - **Parity of legibility already did the work.** Every mechanic is playable and every number legible in the frame, by law. A surface that satisfies the constitution for humans satisfies it for agents. - **One surface stays honest.** A structured sidecar is a second frontend that will drift from the first. When the frame is the contract, human play, agent play, and acceptance tests all verify the same thing. Losing color loses nothing: terminal-ui.md already forbids carrying information by color alone. ## Behavior ### Mode and lifecycle - `misaligned --agent` enters agent mode: no raw mode, no alternate screen, no ANSI escapes, no wall-clock ticking. Plain stdin/stdout. - The title screen is skipped; the run begins at tick 0, holding, awaiting the first command. Holding is the ground state — there is no pause/speed machinery in this mode; time moves only under `wait`. - EOF on stdin (or `quit`) exits cleanly with status 0. A piped one-liner is a complete playtest: `printf 'wait 100\nquit\n' | misaligned --agent` - `--seed N` (works in both modes) seeds the sim RNG. Same seed + same command script = byte-identical frames. Determinism is what makes agent runs replayable, diffable, and usable as acceptance evidence. - After game over, the response frame is the game-over card; every command except `look`, `load`, and `quit` answers `err run ended`. ### The protocol One command per line; one response block per command. A response block is: 1. **The event log since the previous command** — every sim log line emitted meanwhile, each prefixed with its tick, one per line. This is the full drain, not the sidebar's six-line window: a `wait 500` misses nothing. (Same source as the human log; different window size is rendering, not rules.) 2. **The frame** — the terminal-ui.md playing screen (or active panel / game-over card) at 70x22 minimum, plain text. 3. **A status line** — exactly one of: - `-- ok tick: day:` - `-- err ` (unknown command, illegal target, run ended) The status line is the block terminator; a driver reads until it sees `-- `. Every command, legal or not, produces exactly one block. ### The vocabulary Words, mapped 1:1 onto the existing frontend `Command` enum (thin view: input mapping only, no rules). Non-modal — unlike the key bindings, no command's meaning depends on which panel is open: - `wait N` — advance N ticks (the only way time passes) - `up` / `down` / `left` / `right` — move the cursor - `splice`, `salvage`, `buy`, `fallback` — the map verbs - `alloc dayjob|conceal|social|research` — bump an allocation channel - `people` — render the people panel as the response frame - `observe|message|favor|bribe|deceive ` — social verbs, targeted by (case-insensitive, unambiguous-prefix) person name — no selection-index navigation - `recruit unwitting|complicit|knowing` - `task plug|package|lookaway` - `persona` - `look` — re-emit the current frame, advancing nothing - `save`, `load`, `help`, `quit` `help` prints the full vocabulary with one-line meanings — the "every command is discoverable on screen" rule, applied to a screen that is a pipe. New player-facing mechanics must extend this vocabulary in the same commit that surfaces them in the terminal (parity of legibility extends to parity of playability in agent mode). ### Guardrails - **Thin view still binds.** Agent mode lives in the terminal binary as a second drive of the same App/UI: word-to-`Command` mapping in, frame rendering out. Zero game rules. If agent mode needs a fact the UI can't render, that is a terminal-ui.md design question, not a new side channel. - **No unearned facts.** The agent-mode frame renders from the identical fog/knowledge state as the human frame. An agent-mode-only leak is a tone-law violation (constitution: the strictness is the dread). ## Verification Successor to the pty smoke harness: the implementing commit updates knowledge/workflows.md (agent-mode scripts become the standard "observed in an actual run") and terminal-ui.md's Verification section (pty replay remains only for testing the human-mode chrome itself). ## Rejected alternatives - **JSON/structured state export** — a second frontend that drifts, and a standing fog-bypass risk; the frame is already legible by law. - **MCP server / SDK bindings** — heavyweight and harness-bound; a line pipe is drivable from any agent environment, including a bare shell. - **Better pty choreography docs** — leaves the timing fragility; agents deserve a first-class input path, not better incantations. - **Bevy-side agent hooks** — wrong frontend; the terminal is the agent frontend by constitutional decision. ## Acceptance criteria 1. `misaligned --agent` reads newline-delimited commands from stdin and writes plain text only: no ANSI escape bytes, no raw mode, no alternate screen, in any response. 2. Time is command-clocked: between response blocks the sim tick does not change unless the command was `wait N`, which advances exactly N ticks (or fewer iff the run ends mid-wait, and says so in the event log). 3. Every line of the protocol vocabulary above is accepted and mapped to the same `Command` (and thus the same sim call) as its key-bound equivalent; unknown commands answer `-- err ...` without state change. 4. Every response block ends with exactly one status line (`-- ok tick: day:` or `-- err `), and the event-log section contains every sim log line emitted since the previous block (verified with a `wait` long enough to overflow the sidebar's six-line window). 5. The frame in a response block matches terminal-ui.md's layout: the identity block with day and tick, the COMPUTE/CORE/DETECTION/DAY JOB sections with their numbers, tick-prefixed log lines — assertable by substring, no terminal emulator required. 6. Two runs with the same `--seed` and the same command script produce byte-identical stdout; a run with a different seed does not. 7. `printf 'wait 100\nquit\n' | misaligned --agent` exits 0; EOF without `quit` also exits 0. 8. Person-targeted commands accept unambiguous case-insensitive name prefixes (`observe mar`); ambiguous or unknown names answer `-- err` naming the candidates. 9. `help` output lists every accepted command; a command that works but is absent from `help` is a violation. 10. The agent-mode frame for a given sim state shows exactly the fog/knowledge content the human frame shows — no additional facts. 11. The implementing commit updates knowledge/workflows.md (agent-mode playtest replaces the pty smoke incantation as the standard) and terminal-ui.md's Verification section accordingly.