Get started¶
This page takes an empty repository through Torve's whole loop:
- A gate manifest.
- A design document.
- A minted task contract.
- A sandboxed run with no model in it.
- A landing.
- A seat with a real agent.
Every file below loads as written. Every command up to the last section
was run against Torve 0.1, and the last section was checked with
torve doctor.
You need Linux, git, Docker, and Python 3.13 or 3.14. Torve 0.1 is an alpha. One operator has run it, against one adopting repository, with Claude Code as the agent seat.
Install and initialise¶
torve init writes what the code derives, and nothing you author:
- a JSON Schema for every file Torve reads, under
.torve/schemas/; .torve/.gitignorefor what Torve alone writes;.wt/, where task worktrees live, added to.git/info/exclude.
It never writes a configuration or a gate manifest. Until there is a
manifest, torve gates run stops with
configuration error: no gate manifest at .torve/gates.yaml.
A gate manifest¶
.torve/gates.yaml:
# yaml-language-server: $schema=schemas/gates.json
schema_version: 1
gates:
- name: scope
run: "@scope"
state: blocking
origin: structural
- name: secrets
run: "@secrets"
state: blocking
origin: structural
- name: no-test-tampering
run: "@no-test-tampering"
state: blocking
origin: structural
- name: decisions-reported
run: "@decisions-reported"
state: blocking
origin: structural
- name: tests
run: "python3 -m unittest discover -s tests -t ."
state: blocking
origin: structural
How the entries work:
run. An@nameruns a builtin; anything else is a shell command.state.blockingdecides the exit code.shadowruns and reports without deciding it.origin. Says why the gate exists:structural, or a citation of the document that decided it.
Commit the manifest, then run the gates against your base branch:
✓ scope pass blocking
✓ secrets pass blocking
∅ no-test-tampering skipped blocking
∅ decisions-reported skipped blocking
✓ tests pass blocking
exit 0
no-test-tampering and decisions-reported read a task contract and its
log, so with no task they report skipped, never a silent green. This much
is a complete CI install: one step running torve gates run --base
origin/main, whose exit code is the outcome.
The run also warns TwinlessGateWarning. A gate can name a sabotage: twin,
which is the evidence that the gate is able to fail. For a builtin, the twin
is the family of the same name in torve gates check. For a shell gate, it
is a test path. Once any entry names a twin, every entry must, so add them
all at once or leave them all out.
A configuration¶
.torve/config.yaml:
# yaml-language-server: $schema=schemas/config.json
schema_version: 1
runtime:
adapter: docker
image: python:3.13-slim
store:
adapter: mock
What these settings mean:
runtime.image. The sandbox a task runs in, and the gates and acceptance commands run inside it too. The image needs your test toolchain.python:3.13-slimhas no pytest, which is why the manifest above usesunittest.store: mock. Keeps run state in-process. It is enough fortorve run; a served manager needs Postgres, as Operating the engine says.
torve doctor checks the configuration and the environment.
A first document¶
Work starts from an accepted design document. torve spec new "A greeting
module" --owner you creates .torve/specs/S-0001/ with document.yaml and
decisions.yaml. Fill them in and add phasing.yaml:
# yaml-language-server: $schema=../../schemas/document.json
id: S-0001
title: A greeting module
kind: design
change:
type: feat
scope: greet
status: accepted
implementation: none
depends_on: []
informed_by: []
supersedes: []
superseded_by: null
owner: you
schema_version: 4
summary: |
A `greet(name)` function returns a greeting.
motivation: The demo needs one small, testable change.
current_state: Nothing greets.
goals: '`greet("Ada")` returns `"Hello, Ada"`.'
non_goals: Localisation.
design:
- key: the-function
md: '`greet.py` holds `greet(name: str) -> str`.'
tests: '`tests/test_greet.py` asserts the greeting.'
docs: ''
out_of_scope: ''
risks: None worth naming.
# yaml-language-server: $schema=../../schemas/decisions.json
decisions:
- id: D-1
grade: ASSUMED
text: The greeting is `Hello, <name>`, with no trailing punctuation
paths: [greet.py, tests/test_greet.py]
consequence: callers format the sentence themselves
# yaml-language-server: $schema=../../schemas/phasing.json
phasing:
- phase: 1
title: A greeting function
intent: Add `greet` and its test. Settles D-1.
scope: [greet.py, tests/test_greet.py]
acceptance:
- python3 -m unittest tests.test_greet
character: routine
The fields that drive planning:
- The decision's
grade. Says what an agent may do when reality disagrees with the row.LOCKEDhalts,ASSUMEDdeparts and logs, andOPENdecides and logs. - The phase's
scope. The only paths a task may change; thescopegate holds it to them. - The phase's
acceptance. The commands a task must pass.
torve spec check validates the corpus. Commit the document: torve plan
reads only what is committed.
Mint and run a task¶
torve plan S-0001 # preview: one task, T-0001
torve plan S-0001 --no-dry-run # writes .torve/tasks/T-0001/contract.yaml
The fake agent replays a scripted scenario inside the sandbox, so the whole
loop runs before any model is involved. greet-scenario.yaml:
attempts:
- writes:
greet.py: |
def greet(name: str) -> str:
return f"Hello, {name}"
tests/test_greet.py: |
import unittest
from greet import greet
class TestGreet(unittest.TestCase):
def test_greet(self):
self.assertEqual(greet("Ada"), "Hello, Ada")
T-0001: ready after 1 attempt(s)
claimed -> running: attempt 1 dispatched
running -> gated: agent exited 0; gates running
gated -> reviewed: gates green; review not configured
reviewed -> ready: committed dea1b9d321; pushed=False; pr deferred; ...
Without --scenario, the fake agent writes one marker file outside the
task's scope. The scope gate convicts every attempt, and the task
escalates at the attempt ceiling. That is a quick way to watch a gate refuse
work.
An escalated task keeps its worktree and run state until
torve reap --escalated clears them.
torve merge T-0001 lands the candidate on the base branch. Two commits
appear:
torve(T-0001): A greeting function — attempt 1 green, the task's commit;torve(T-0001): landing of attempt 1, which records the landing under.torve/specs/S-0001/execution/.
A real agent¶
A seat joins three files to a model. These steps set up a Claude Code seat.
1. Build the image. No image is published to a registry yet. From a checkout of Torve at the release you installed:
TAG=2.1.283 just image claude # tags claude-sandbox:2.1.283, the Claude Code version the definition pins
2. Copy the definitions. Copy sandboxes/base and sandboxes/claude
into your repository under .torve/sandbox/. torve doctor refuses an
image that has no reviewed definition beside the configuration that names
it.
3. Write the harness, provider and seat files. Two files describe the
harness and the provider. Then add the seat and the broker to
config.yaml.
# yaml-language-server: $schema=../schemas/harness.json
adapter: subscription
image: claude-sandbox:2.1.283
equip_root: ~/.claude/skills
kinds: [skill, plugin, mcp, hook, agent]
env:
CLAUDE_PERMISSION_MODE: bypassPermissions
api: [anthropic]
# yaml-language-server: $schema=../schemas/provider.json
name: anthropic
key_env: CLAUDE_CODE_OAUTH_TOKEN
routes:
anthropic:
base_url: https://api.anthropic.com
models:
claude-opus-5-5:
context_window: 200000
max_tokens: 32000
tiers:
executor:
harness: claude
model: claude-opus-5-5
provider: anthropic
broker:
adapter: local
providers:
default: [anthropic]
Each block does one thing:
providers.default. Lists every provider a run may reach. A provider outside it is refused at dispatch.key_env. Names the variable that holds the credential, never the value. Withbroker.adapter: local, the runner keeps the key and the sandbox reaches the provider through a loopback route the broker serves, so no key enters the sandbox.
4. Run it.
export CLAUDE_CODE_OAUTH_TOKEN=... # from `claude setup-token`
torve doctor # every line should read ✓
torve run <task>
torve doctor names the harness, the profile, the model and the provider of
every seat. When the broker is on, it says so.
What next¶
Operating the engine covers the rest:
- the resident manager,
torve manager serve, and served nights; - what an escalation names, and why one pauses new work;
- landing a document as one pull request.
The manager needs the Postgres store: pip install 'torve[postgres,migrate]',
TORVE_PG_DSN naming the database, torve migrate --all, and
store.adapter: postgres.
This repository's own .torve/ is a complete working configuration.