## Workflow

- install: `make install`
- build: `make dist`
- test all: `make test`
- test file: `uv run python -m pytest tests/<file>.py -v`
- test case: `uv run python -m pytest tests/<file>.py::<test_name> -v`
- lint: `make lint`
- format: `make format`
- after every edit: `make all`
- debug: no launch config found; env vars: `HF_TOKEN`, `OPENROUTER_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, `CHATGPT_API_KEY`, `SWIVAL_TASK`, `SWIVAL_REVIEW_ROUND`, `SWIVAL_MODEL`, `SWIVAL_ENCRYPT_KEY`, `XDG_CONFIG_HOME`; flags: `--repl`, `--quiet`, `--report`, `--yolo`, `--sandbox agentfs`, `--sandbox-session`, `--sandbox-strict-read`

## Conventions

- Tool implementations return strings, not Python objects. Failures must start with `error:` because guardrails, reports, and retry logic depend on that prefix.
- Message values may be dicts or provider objects. Use the `_msg_*` helpers in `swival/_msg.py` instead of direct indexing or attribute assumptions.
- Conversation history is mutated in place. When compacting or replacing messages, preserve list identity rather than swapping in a new list.
- Config precedence is fixed: CLI > project `swival.toml` > global config > defaults. `_UNSET` is only for the argparse/config boundary and should not leak into session logic.
- Use `Session.run()` for isolated one-shot runs and `Session.ask()` for shared multi-turn context. Tests enforce this distinction.
- Tests use `pytest` with `tmp_path` and `monkeypatch`, isolate machine-level state in `tests/conftest.py`, and mock LLM/tool-call objects with `types.SimpleNamespace`.