# AGENTS.md — Cross-cutting conventions

- All tool functions return `str`. Errors start with `"error: "` — this prefix drives the guardrail system, report `succeeded` flag, and compaction scoring across 8+ modules.
- State objects (`ThinkingState`, `TodoState`, `SnapshotState`) implement `reset()` and `summary_line() -> str | None`. They survive message compaction by living outside the message list.
- Messages can be `dict` or `SimpleNamespace`. Always use `_msg_role()`, `_msg_content()`, `_msg_tool_calls()` etc. from `_msg.py` — never index directly.
- The message list is mutated in place via `messages[:] = ...` (slice assignment) to preserve the shared reference across REPL, agent loop, and compaction. Never reassign the variable.
- Every file tool calls `safe_resolve()` and checks `is_relative_to(base_dir)` before access. New file tools must do the same or the sandbox breaks.
- File tool functions share a signature tail: `(base_dir, extra_read_roots, extra_write_roots, unrestricted, tracker)`. `dispatch()` passes these via `**kwargs`.
- Test mocks use `types.SimpleNamespace`, not mock frameworks. Each test file defines its own `_make_message()` / `_make_tool_call()` helpers — there is no shared test utility module.
- Test fixtures use a `_make_args(**overrides)` builder: construct a defaults dict, `defaults.update(overrides)`, return `Namespace(**defaults)`.
