Cookbook
Ten recipes of twenty lines or fewer. Every Python snippet on this page is executed by the test suite, so a recipe that stops matching the API fails CI rather than you.
Ten recipes, twenty lines or fewer each. Every lockstep.py snippet is executed by the test suite, so a recipe that stops matching the API fails CI rather than a reader. Snippets go in .lockstep/lockstep.py unless a recipe says otherwise.
1. Route triage to a local model: the $0 path
The local provider ships pointed at Ollama and registered free, so nothing needs a price and the run bills exactly zero. Tokens are still counted, because free is not unmeasured.
from in_lockstep import Lockstep
from in_lockstep.core.verbs import Verb
lockstep = Lockstep.detect()
lockstep.models.route(Verb.TRIAGE, "local:qwen3-8b")
in-lockstep triage --ticket '#42'
2. Go keyless in CI with workload identity federation
Delete ANTHROPIC_API_KEY from repository secrets. Give the model-calling job an identity instead, and hand the framework the identifiers of the federation rule that accepts it. Those go in plain env, not in secrets: an identifier in secrets seeds redaction and masks it out of the very errors that name it.
permissions:
contents: read
id-token: write # mint a short-lived GitHub OIDC token; the framework exchanges it
env:
ANTHROPIC_FEDERATION_RULE_ID: fdrl_...
ANTHROPIC_ORGANIZATION_ID: <org uuid>
ANTHROPIC_SERVICE_ACCOUNT_ID: svac_... # the id, not the name — a name is refused locally
A static key set in env still wins when present, so migration is: configure the rule in the Anthropic Console, add the identifiers, watch one green run, then delete the secret.
3. TDD implement, triggered by a comment
/implement on an issue runs implement/from-ticket on the default branch. The strategy IS the adapter, so making it test-driven is naming a different class in the binding. The scaffold binds Oneshot. Red-then-green costs a second model phase, which is why it is a choice and not the default.
from in_lockstep.adapters.ai import TDD, Implement
lockstep.models.route("implement", "anthropic:claude-sonnet-4-6")
lockstep.bind(Implement, TDD())
in-lockstep ls prints the whole story as one line, Implement -> TDD. The model comes from the route above: an adapter bound with no explicit invoker resolves it per run, and egress from the bound EgressPolicy.
This repository's own .lockstep/lockstep.py is the full worked version, including the WorktreeRunner wrap that keeps a model-chosen command's writes off the live tree.
Drawn: chat-ops for the jobs, implement strategies for red then green.
4. A rolling daily spend ceiling
The per-run budget bounds one run. This bounds a runaway trigger, a chat-ops loop firing all night. It sums the ledger's own records over a rolling 24 hours, and refuses before a run starts:
export IN_LOCKSTEP_DAILY_LIMIT=10.00
Honest scope: the window sums this clone's ledger, so a runner that never fetched lockstep-history sums less than the truth; in-lockstep history --pull folds the remote's records in first, and pushes nothing. Two concurrent runs still race the read. The provider console's organisation limit (IN_LOCKSTEP_ORG_SPEND_LIMIT, attested to doctor) remains the durable backstop.
5. House guardrails in every review
Prompt text is data; the binding site is where data enters, visibly. .plus appends after the shipped baseline, so extending cannot quietly drop it.
from in_lockstep.adapters.ai import AiReview, Review
from in_lockstep.prompts.review import review_layers
lockstep.models.route("review", "anthropic:claude-sonnet-4-6")
lockstep.bind(
Review,
AiReview(
layers=review_layers().plus(
guardrails=(("house", "Do NOT propose new dependencies; flag them instead."),),
),
),
)
6. Ship your organisation's standards as a package
At one repository, a lockstep.contribute(...) line. At two hundred, a package: installing it is what applies it, and Lockstep.detect() discovers it before your module's own lines run. The entire org layer is a pyproject.toml entry point and one function:
[project.entry-points."in_lockstep.standards"]
acme = "acme_lockstep:apply"
from in_lockstep import Policy
from in_lockstep.core.standards import Standards
def apply(std: Standards) -> None:
std.contribute(Policy(name="acme-baseline", scan_input="block", max_turns=16))
Everything lands at Tier.PLUGIN, so any repository's own lockstep.bind still wins, and in-lockstep ls prints what applied. Worked example: examples/acme-standards.
7. Feed your proxy: real egress enforcement
The framework never enforces destinations itself (an in-process allowlist would be a checkbox); it verifies that something outside the process does. The manifest is the bridge:
in-lockstep egress-manifest # the hosts a run may dial, one per line
Feed that list to the proxy or firewall your CI host provides, then attest it:
export IN_LOCKSTEP_EGRESS=enforced
enforced is verified, not believed. A probe dials a host that must be unreachable, and the run is refused if it connects. Extra hosts you decide to allow (a package registry, before an EXECUTES_CODE step needs it) go in EgressPolicy(allow=("pypi.org",)), and the manifest prints them.
8. Record a cassette once, review offline forever
Cassettes sit at the LLMInput/LLMOutput seam, so a recording replays against a different provider, and tool IO is captured alongside model IO.
in-lockstep review --base origin/main --cassette "$TMPDIR/review.json" # one real call; recording is the default
in-lockstep review --base origin/main --offline --cassette "$TMPDIR/review.json" # deterministic and free, from here on
Name the cassette on both lines: --offline with no --cassette replays the shipped fixture, not what you recorded. A tape lives outside the repository, which is why the default is $RUNNER_TEMP or $TMPDIR.
The replay refuses to silently call out when the prompt no longer matches the recording. A changed guardrail means re-recording, and it says so rather than billing you quietly.
9. A house review lens, bound rather than monkeypatched
A lens is a prompt class. Binding it is how it becomes real: visible in ls, loaded from the trusted ref, never an import-time side effect.
The body is a file, not a string literal. Prompt text is data a non-programmer edits and a diff reviews, which is why it lives outside the module. A string is refused at class creation rather than at render time.
from in_lockstep.adapters.ai import AiReview, Review
from in_lockstep.ai.prompt import Body
from in_lockstep.prompts.review import LENSES, ReviewPrompt
class LicenseLens(ReviewPrompt):
aspect = "license"
body = Body.from_path("prompts/license.md")
lockstep.bind(
Review,
AiReview(lenses={**LENSES, "license": LicenseLens}),
)
in-lockstep show-prompt license renders it offline, and ls stars it as not-the-shipped-prompt. The body lives in prompts/ at the repository root, not under .lockstep/: that directory is deny-always for every writing verb, so a lens there can never be proposed to by the learning loop.
To add to a shipped lens rather than write one, keep its class and its key. A Lens carries what a subclass would have carried, and a route keyed review/<lens> puts that one lens on a different model:
from in_lockstep.adapters.ai import AiReview, Review
from in_lockstep.prompts.review import LENSES, Lens, SecurityReviewPrompt
lockstep.bind(
Review,
AiReview(lenses={**LENSES, "security": Lens(SecurityReviewPrompt, emphasis="No bare excepts.")}),
)
lockstep.models.route("review/security", "anthropic:claude-opus-4-6")
The key stays security, so review.security in the ledger and the sticky comment's marker both survive the change. A lens of your own gets a name nobody had; a better security stays security.
10. Make the ledger auditable
Every run records to the lockstep-history orphan branch. Two commands read it, and both check it was not rewritten:
in-lockstep report --by model # aggregates, and flags any record rewritten after append
in-lockstep doctor # DOC167, an ERROR — tampering fails a required check
After a proposal merges, in-lockstep report --around <sha|#PR> compares the runs on that subject after the merge with the runs before it, and prints a delta rather than a verdict, with — where either window is too thin.
The check reads the retained chain. A force-push that replaced the chain is the remote's to refuse. Protect lockstep-history with a ruleset blocking force-pushes and deletions. Appends are fast-forwards and still flow, so it needs no reviews and slows nothing down.
A flag you can explain, you acknowledge by name, and it stops being an alarm without becoming a secret:
in-lockstep history --acknowledge adca58e --reason "a laptop's fixed-run-id record replaced the branch's" --push
That appends a note to the branch — who, why, when, and what the commit rewrote — and report and doctor print the note where TAMPERED was. A rewrite in any other commit is still an alarm, and the note itself is protected by the same check. A flag nobody can acknowledge is one everybody learns to read past.
Drawn: the ledger, from a run to origin/lockstep-history and back to report.