mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-07-12 21:02:17 +02:00
Decision: defer code-mode unification; no deletions. The legacy runs runtime (runs/, AgentRuntime/streamAgent, runs:* IPC, App.tsx legacy tab state) remains solely for code-mode sessions. AGENTS.md gets the authoritative carve-out section: what stays and why, the no-new-callers rule (headless work uses agents/headless.ts), the temporary fallbacks that die with unification, and the scoped future project (rowboat-mode code prompts as composition turns; direct-mode ACP streams as a delegated turn kind). Turn-runtime design doc status updated to implemented-and-live. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
101 lines
5 KiB
Markdown
101 lines
5 KiB
Markdown
# AGENTS.md — Working on the Rowboat runtime
|
|
|
|
Context for AI coding agents (and humans) working on this repo. General
|
|
codebase orientation lives in `CLAUDE.md`; this file covers the **new
|
|
turn/session runtime** in `apps/x` — its storage, its debugging tools, and
|
|
the invariants you must not break.
|
|
|
|
## The runtime in one paragraph
|
|
|
|
Chats are **sessions**; each user message starts a **turn**. Both are
|
|
append-only JSONL event logs under `~/.rowboat/storage/{turns,sessions}/YYYY/MM/DD/`.
|
|
All state is derived by pure reducers (`reduceTurn`, `reduceSession` in
|
|
`@x/shared/src/turns.ts` / `sessions.ts`) shared byte-for-byte between the
|
|
main process and the renderer. Design specs:
|
|
`apps/x/packages/core/docs/turn-runtime-design.md` and `session-design.md` —
|
|
read the relevant spec before changing runtime behavior.
|
|
|
|
## Storage is reference-based — files store each fact once
|
|
|
|
Three applications of the same mechanism:
|
|
|
|
1. **Context**: a session turn's `context` is `{ previousTurnId }`; the
|
|
conversation prefix is materialized by walking the chain
|
|
(`TurnRepoContextResolver`).
|
|
2. **Model requests**: `model_call_requested.request.messages` is a list of
|
|
string refs into the turn's own events — `"context"`, `"input"`,
|
|
`"assistant:<index>"`, `"toolResult:<toolCallId>"` — recording only what
|
|
is NEW since the previous call.
|
|
3. **Agent snapshots**: when a turn's system prompt + tools are
|
|
byte-identical to its predecessor's, `turn_created.agent.resolved` is
|
|
`{ agentId, model, inheritedFrom }` instead of re-persisting ~70KB. The
|
|
model stays concrete (mid-session model switches still inherit).
|
|
|
|
The exact provider payload is rebuilt by `composeModelRequest`
|
|
(`packages/core/src/turns/compose-model-request.ts`) — **the same code path
|
|
the loop transmits through**, so the file plus the composer reproduce the
|
|
wire bytes exactly (there is a property test asserting composed == sent).
|
|
|
|
## Inspecting turns and sessions
|
|
|
|
```bash
|
|
cd apps/x/packages/core
|
|
|
|
# A whole session: title, per-turn status/size/input preview
|
|
npm run inspect -- <sessionId | path/to/session.jsonl>
|
|
|
|
# One turn: per model call, the EXACT provider payload — resolved system
|
|
# prompt, tool list, wire-form messages (user-message context woven in,
|
|
# tool-result envelopes), and the response/failure
|
|
npm run inspect -- <turnId | path/to/turn.jsonl> [modelCallIndex] [--full]
|
|
|
|
# Cascade full turn inspection across a session
|
|
npm run inspect -- <sessionId> --turns
|
|
```
|
|
|
|
`--full` prints untruncated system prompts and message contents. Turn vs
|
|
session ids are auto-detected. This is the intended way to see "what did the
|
|
model actually receive" — the raw JSONL deliberately stores structural facts
|
|
and references, never the derived wire form.
|
|
|
|
## The legacy runs runtime is code-mode-only
|
|
|
|
The old runs runtime (`packages/core/src/runs/`, `AgentRuntime`/`streamAgent`
|
|
in `packages/core/src/agents/runtime.ts`, the `runs:*` IPC channels, and the
|
|
renderer's legacy chat-tab state machine in `App.tsx`) remains in the tree
|
|
**solely for code-mode sessions** — a deliberate, documented carve-out:
|
|
|
|
- A code session IS a run (`sessionId === runId`). Direct-mode prompts drive
|
|
an external ACP agent and hand-assemble `code-run-event`s into the run log
|
|
(`code-mode/sessions/service.ts`); rowboat-mode code tabs go through
|
|
`runs:createMessage` → the old `AgentRuntime` loop.
|
|
- Everything else — main chat, background tasks, live notes, knowledge
|
|
pipelines, scheduled agents — runs on turns/sessions. Do NOT add new
|
|
callers of `createRun`/`createMessage`/`streamAgent`; headless work uses
|
|
`packages/core/src/agents/headless.ts` (`startHeadlessAgent`/
|
|
`runHeadlessAgent`).
|
|
- Temporary bridges that die when code-mode is unified: the renderer
|
|
transcript loader's `runs:fetch` fallback (`lib/agent-transcript.ts`), the
|
|
`runs:downloadLog` fallback in the chat sidebar, and the `notify-user`
|
|
gate's `fetchRun` fallback (`application/lib/builtin-tools.ts`).
|
|
- The remaining migration (designed but deferred): port code sessions onto
|
|
sessions/turns — rowboat-mode prompts become normal turns with
|
|
`codeMode`/`codeCwd` composition (already supported by the agent
|
|
resolver's composition overrides), direct-mode prompts become a delegated
|
|
turn kind carrying opaque code events. That project deletes `runs/`
|
|
entirely.
|
|
|
|
## Invariants to respect
|
|
|
|
- Turn/session files are **append-only**; reducers reject impossible
|
|
histories loudly (`TurnCorruptionError`). Never hand-edit files.
|
|
- Durable events are persisted **before** side effects (model calls, tool
|
|
invocations). Deltas (`text_delta`, …) are stream-only, never persisted.
|
|
- The reducers in `@x/shared` must stay pure (no I/O, no node imports) —
|
|
the renderer imports them directly.
|
|
- Every behavior change needs tests: reducers in `packages/shared`,
|
|
runtime/sessions in `packages/core`, renderer stores/views in
|
|
`apps/renderer` (all vitest; run `npm test` per package).
|
|
- Schema changes: the schema is pre-release (`schemaVersion: 1` throughout);
|
|
breaking changes are acceptable but require wiping `~/.rowboat/storage`
|
|
and a note in the commit message.
|