Branch#
An agent harness for weight-based learning.
Branch is a minimal two-branch agent interface for collecting actual preference and correction trajectories.
Each user turn forks the current canonical Pi conversation into two independent runs. Both responses stream side-by-side. Selecting 1 or 2 promotes that branch and rejects the other. Selecting r1 or r2 adds a natural-language correction, asks that branch to answer again as if it already understood the correction, and squashes the correction exchange out of the canonical conversation.
Every decision is appended to a local JSONL event store with:
- the full parent history and its hash
- the exact system prompt and its hash
- both branch trajectories, model IDs, reasoning fields, and responses
- the chosen and rejected candidate IDs
- correction feedback and the revised candidate, when present
That makes the local event store canonical. Tinker receives training data later; it is not expected to be the trajectory archive.
Current safety boundary#
Pi runs with all tools disabled. Two branches that can both write to the same checkout are not an experiment; they are a race condition wearing a lab coat. Tool-bearing branches should get isolated git worktrees before tools are enabled.
The branch sessions also use an isolated Pi resource directory under .harness/; global Pi extensions, skills, and prompt files do not silently contaminate the constitutional target. Authentication still comes from Pi's normal auth store or the explicit Tinker environment key.
Install and run with Tinker#
pnpm install
export TINKER_API_KEY=...
pnpm start
pnpm start uses Thinking Machines' Tinker inference endpoint and forks two independent samples from openai/gpt-oss-120b by default. There is no automatic mock fallback. The active backend and model are printed at the top of the terminal so a fake run cannot quietly masquerade as an experiment.
You can keep the key in a gitignored .env file instead:
cp .env.example .env
# edit .env, then:
pnpm start
Choose another currently supported Tinker base model or a sampler checkpoint with TINKER_MODEL:
TINKER_MODEL='openai/gpt-oss-20b' pnpm start
TINKER_MODEL='tinker://<run-id>:train:0/sampler_weights/<step>' pnpm start
pnpm start -- --tinker-model 'openai/gpt-oss-20b'
The mock backend remains available for tests and UI work without model credits:
pnpm demo
Run with an existing Pi model#
Pi uses its normal credentials and model registry:
pnpm start -- --provider anthropic --model claude-sonnet-4-6
The key is read from the environment and never written to the event store. Tinker's OpenAI-compatible inference is currently beta and intended for low-traffic evaluation/internal use. Branch's preference-collection traffic is exactly that shape; this is not a production serving claim.
Branch preserves the separate reasoning stream emitted by the default model and by compatible Tinker checkpoints. It does not currently send reasoning_effort: Tinker's endpoint rejects that field for several otherwise-reasoning-capable base models. Model-specific renderer controls belong in a later native SamplingClient adapter rather than a hopeful generic flag.
Context Constitution target#
Pass the exact constitution or agent prompt under evaluation:
pnpm start -- \
--provider anthropic \
--model claude-sonnet-4-6 \
--constitution /path/to/context-constitution/constitution/CONSTITUTION.md
The public source is https://github.com/letta-ai/context-constitution. Pin a commit when comparing runs so prompt drift does not impersonate weight improvement.
Controls#
1: prefer left2: prefer rightr1: correct left, regenerate, and prefer the revisionr2: correct right, regenerate, and prefer the revision/q: quit
Preference records default to data/preferences.jsonl. Override with --data <path> or BRANCH_DATA_PATH.
Export training examples#
The event store remains the lossless source. A deterministic exporter derives normalized SFT examples and one preference pair for each rejected candidate:
pnpm export
This writes data/training.sft.jsonl and data/training.dpo.jsonl. They are model-agnostic intermediate records, not a claim that chat JSON can be handed straight to Tinker's optimizer. A Tinker training job still needs the chosen model's renderer/tokenizer, loss mask construction, train/eval split, and checkpoint policy.
What this first pass does not pretend to solve#
- Tinker-specific rendering, loss construction, and training jobs
- frozen evaluation sets and adapter promotion
- tool-call trajectory normalization
- multi-rater agreement
- branch isolation for write-capable agents
- automatic violation detection
The next useful layer is a Tinker training adapter plus a frozen evaluation set and explicit checkpoint promotion gate. The collected records already preserve enough provenance to build that without pretending the model should grade its own homework.