Add /graph G1: parallel fan-out with worktree isolation and merge-back

With KIGI_GRAPH_CONCURRENCY > 1 (default 3, clamp [1,8]) and >=2 Ready nodes,
drive_graph — the single dispatch loop shared by setup/advance/resume — runs
parallel batches: each node executes as a bounded worker<->verifier subagent
loop (KIGI_GRAPH_NODE_ROUNDS, default 3; general-purpose children with the
full implementer toolset; worktree isolation on round 1, resume keeps context
and worktree on later rounds; NODE_RESULT/NODE_VERDICT terminal contracts
parsed fail-closed with fence-stripping and line anchoring). Achieved nodes
merge back SEQUENTIALLY via kigi_workspace apply_worktree in Merge mode; a
conflict fails the node, block_dependents fires, and surviving chains keep
going. gn-final always runs serially on the full goal engine. Concurrency=1
is byte-identical to the serial G0 path; non-git projects degrade to serial.

Merge primitive hardened for real use (kigi-workspace): the 3-way apply is
now byte-safe (binary files no longer read as UTF-8 and silently deleted)
and gains the identical-content rule (ours==theirs => already present, not a
conflict) — without it, any dirty file inherited via PreserveWorkingTree
false-conflicted every node merge and multi-wave graphs self-poisoned.

Adversarial review pass (16 confirmed findings, all fixed): in-session
resume demotes orphaned Running nodes instead of wedge-pausing forever after
a mid-batch Esc; cancel re-sweeps subagents AFTER the turn abort so workers
spawned in the cancel window die too; empty child ids are never adopted as
resume targets (no unisolated escape to the shared tree); a successful
isolated round returning no worktree fails the node (soft-fallback can no
longer put N writers in one tree); main-HEAD movement during a batch aborts
merges instead of reverse-applying external commits; failed nodes still
charge the token budget; budget trips terminally fail the in-flight node;
runaway (>600s) rounds are cancelled by spawn id and retried via resume;
worker summaries are marker-sanitized before verifier embedding. Merged
worktrees are removed immediately (storage discipline); failed nodes keep
theirs for postmortem.

Tests: 4922 kigi-shell lib tests green (58 graph-specific), including
fan-out proven by a held-reply gate, real-git batch merge with cleanup
assertions, budget charging across verdicts, cap trimming, resume-after-
cancelled-batch, and backgrounded-round cancel semantics.
This commit is contained in:
2026-07-20 15:32:35 -04:00
parent 4361b03259
commit 4d1e4fdc52
20 changed files with 1986 additions and 68 deletions
@@ -1919,6 +1919,26 @@ impl Config {
pub(crate) fn resolve_graph(&self) -> Resolved<bool> {
BoolFlag::env("KIGI_GRAPH").default(false).resolve()
}
/// Max graph nodes running concurrently (`KIGI_GRAPH_CONCURRENCY`).
/// 1 = serial (G0-identical); clamped to [1, 8] — the coordinator has
/// no cap of its own, so this is the only brake on worker fan-out.
pub(crate) fn resolve_graph_concurrency(&self) -> u32 {
std::env::var("KIGI_GRAPH_CONCURRENCY")
.ok()
.and_then(|v| v.parse::<u32>().ok())
.unwrap_or(3)
.clamp(1, 8)
}
/// Max worker↔verifier rounds per parallel graph node
/// (`KIGI_GRAPH_NODE_ROUNDS`); exhausting them fails the node.
/// Clamped to [1, 8].
pub(crate) fn resolve_graph_node_rounds(&self) -> u32 {
std::env::var("KIGI_GRAPH_NODE_ROUNDS")
.ok()
.and_then(|v| v.parse::<u32>().ok())
.unwrap_or(3)
.clamp(1, 8)
}
/// Classifier, planner, and summary all default to goal mode itself: when
/// `/goal` is on they are on unless config/env/remote says otherwise.
/// `goal_enabled` is the session's already-resolved master switch (the same