Forking¶
A fork is a branch from a parent run at a specific event. The fork shares the parent's event log up to the fork point; from there, it has its own independent log. The two runs can be diffed, configured differently, and inspected side-by-side without touching the parent's state.
Forking is what lets the framework answer "what would have happened if I'd done X differently?" — a question agentic systems need to answer routinely and most frameworks can't answer at all. The shared-lineage model plus the cache layer makes fork cheap (no LLM re-execution for the shared prefix) and honest (the fork's lineage is verifiable from the event log).
The shared-lineage model¶
A fork copies events from the parent run, in order, up to the
--at-event cutoff. The cutoff is inclusive — events at the
cutoff id and before are in the fork; events after are not.
parent: evt_001 ... evt_042 evt_043 evt_044 ... (continues)
|
+- fork from evt_042
v
fork: evt_001 ... evt_042 evt_045 evt_046 ... (fork's own work)
The fork's events 1 through 42 are the parent's. Event 45 onward is the fork's own; event ids don't collide because the fork has its own run id and its own monotonic id generator.
The shared prefix doesn't re-execute when the fork starts. The framework replays the prefix against the fork's in-memory graph, then resumes live execution from the cutoff. LLM and tool responses for the shared prefix are served from the cache — no new LLM calls, no new tool calls — which keeps fork cheap.
The CLI surface¶
The --set flag is part of the v1.1 release
The --set <pack>.<key>=<value> flag below is documented in
CONTRACT v1.0 but lands in v1.1 (see CONTRACT v1.1 #1).
Until then, use the Python-API form documented in
Fork with a pack-setting override (v1.0 — Python API)
for fork-with-override workflows.
activegraph fork <parent-url> \
--run-id <parent-run> \
--at-event <event-id> \
--label <human-readable> \
--set <pack>.<setting>=<value> \
--record
Three flags shape the fork:
--at-event— the cutoff. Required.--set <key>=<value>— override a pack setting in the fork. The key is a dotted path into pack settings only (diligence.confidence_threshold_for_review=0.9is in scope;runtime.budget.max_cost_usd=10is out of scope). Multiple--setflags compose; type coercion is Pydantic's job; unknown keys fail loud at fork-time with aRegistrationError-style message naming the typo and the valid keys.--record— mark the fork as a re-recording. Behaviors whose prompts changed since the parent run will be re-recorded rather than cache-hit; new cache entries land in the fork's events.
--set is the primitive that makes "what if I'd configured this
differently?" cheap. The semantics — pack settings only, fail
loud on typos — are documented in the
CLI reference.
How the cache replays¶
For events before the fork point, the cache serves recorded responses by content hash:
- LLM call with the same prompt hash → cached response from
the parent run's
llm.respondedevent. - Tool call with the same args hash → cached response from
the parent run's
tool.respondedevent. - LLM or tool call whose hash drifted from the parent —
expected only after
--setchanged something upstream. Without--record, the fork's strict replay firesreplay-divergence-error; with--record, the fork accepts the new responses and records them as its own.
The cache is per-store, indexed by run id. A fork that needs to re-execute the same prompt the parent already ran in a different run can't reach across — caches don't cross runs. (Migration is the primitive for moving runs across stores; see Operating in production.)
When to fork vs when to use frames¶
Both let parallel computations proceed in isolation. The difference is durability and replay:
- Fork — a separate run with shared event-log lineage up to the fork point. Forks are durable, replayable, diffable. Use fork when the parallel branches might diverge permanently or need independent persistence.
- Frames (
frames.md) — sub-contexts within one run. The frame's events live in the same event log as everything else; replay is the whole run. Use frames when the parallel contexts are short-lived or semantically belong together.
The decision rule: if you'd want to inspect, diff, or migrate the two branches independently after the fact, use fork. If the branches converge back to a single output within the same run, use frames.
A common pattern is to start parallel work in frames, then fork only the branches worth keeping. Frames are the cheap parallel primitive; forks are the durable one.
What's related¶
graph— the world state the fork projects from its event log.events— the append-only history forks share up to the cutoff.replay— the operation that reconstructs the shared prefix in the fork.frames— the in-run parallel primitive that complements fork.patches— patches in a fork are independent of the parent's patches once the fork point passes.replay-divergence-error— the error case when strict-mode replay finds a divergent prompt hash or event stream.- CLI reference — the full surface for
activegraph forkand the surrounding commands.