Add /graph G0: serial graph engineering mode over the goal engine
A deterministic DAG scheduler layered on the existing goal engine: /graph <objective> decomposes the objective via a graph-planner subagent, gates the result through Agentproof-style static validation (cycles, unknown deps, duplicate slugs, caps), appends a structural final-verification node, then executes each node as one ordinary goal — planner, worker loop, adversarial verifier, budget and pause machinery all reused verbatim. The in-turn loop advances nodes within the same turn (multi-loop closed loop); goal-side auto-pauses cascade to the graph at a single chokepoint; node goals are armed with the remaining graph budget so mid-node overruns trip graph-wide. Gated by KIGI_GRAPH=1 (default off) + the goal harness. State persists to <session_dir>/graph/state.json with a clear-tombstone; per-version immutable baselines and per-node artifact archives live under graph/<graph_id>/. Restore demotes Active->UserPaused and Running->Ready (verifier-gated re-run). Review hardening (adversarial multi-agent pass, 24 confirmed findings fixed): the goal-inactive loop break now consults the graph seam (mid-turn classifier-disabled completions can no longer strand an Active graph), the pause cascade fires even when no node goal is in flight (cancel during planning), node bookkeeping precedes the long setup await, /graph clear only resets the engine it owns, budget trips terminally fail the in-flight node, and the pause transitions in /goal pause + /graph pause no longer hide inside debug_assert! (a release-build no-op inherited from upstream). Tests: 4908 kigi-shell lib tests green, including a serial 4-node closed-loop e2e, restore/resume, cascade, mutual-exclusion, planning-retry, persistence round-trip + tombstone, and status rendering.
This commit is contained in:
@@ -1912,6 +1912,13 @@ impl Config {
|
||||
.default(true)
|
||||
.resolve()
|
||||
}
|
||||
/// Graph mode (`/graph`) master switch. Default OFF — gray-released via
|
||||
/// `KIGI_GRAPH=1` only (plan.md G0 gate). Graph mode additionally
|
||||
/// requires the goal harness (nodes execute as goals), enforced at
|
||||
/// availability time, not here.
|
||||
pub(crate) fn resolve_graph(&self) -> Resolved<bool> {
|
||||
BoolFlag::env("KIGI_GRAPH").default(false).resolve()
|
||||
}
|
||||
/// 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
|
||||
|
||||
@@ -754,6 +754,7 @@ impl acp::Agent for MvpAgent {
|
||||
persisted_signals: None,
|
||||
persisted_plan_mode: None,
|
||||
persisted_goal_mode: None,
|
||||
persisted_graph_mode: None,
|
||||
persisted_announcement_state: None,
|
||||
session_meta: arguments.meta.as_ref(),
|
||||
model_agent_type: model_agent_type.as_deref(),
|
||||
@@ -985,6 +986,7 @@ impl acp::Agent for MvpAgent {
|
||||
signals: persisted_signals,
|
||||
announcement_state: persisted_announcement_state,
|
||||
goal_mode_state: _persisted_goal_mode,
|
||||
graph_mode_state: _persisted_graph_mode,
|
||||
} = persistence_info;
|
||||
let restored_awaiting_plan_approval = persisted_plan_mode
|
||||
.as_ref()
|
||||
@@ -1207,6 +1209,7 @@ impl acp::Agent for MvpAgent {
|
||||
persisted_signals,
|
||||
persisted_plan_mode,
|
||||
persisted_goal_mode: _persisted_goal_mode,
|
||||
persisted_graph_mode: _persisted_graph_mode,
|
||||
persisted_announcement_state,
|
||||
session_meta: request_meta.as_ref(),
|
||||
model_agent_type: persisted_agent_name.as_deref(),
|
||||
|
||||
@@ -1677,6 +1677,7 @@ impl MvpAgent {
|
||||
persisted_signals,
|
||||
persisted_plan_mode,
|
||||
persisted_goal_mode,
|
||||
persisted_graph_mode,
|
||||
persisted_announcement_state,
|
||||
session_meta,
|
||||
model_agent_type,
|
||||
@@ -2111,6 +2112,7 @@ impl MvpAgent {
|
||||
let web_fetch_config = self.prepare_web_fetch_config();
|
||||
let write_file_enabled = self.cfg.borrow().resolve_write_file().value;
|
||||
let goal_enabled = self.cfg.borrow().resolve_goal().value;
|
||||
let graph_enabled = self.cfg.borrow().resolve_graph().value;
|
||||
let subagents_enabled = self.cfg.borrow().subagents_enabled;
|
||||
let ask_user_question_enabled = parse_ask_user_question_from_meta(session_meta)
|
||||
.unwrap_or_else(|| self.cfg.borrow().resolve_ask_user_question().value);
|
||||
@@ -2314,6 +2316,7 @@ impl MvpAgent {
|
||||
persisted_signals,
|
||||
persisted_plan_mode,
|
||||
persisted_goal_mode,
|
||||
persisted_graph_mode,
|
||||
persisted_announcement_state,
|
||||
self.memory_config.clone(),
|
||||
feedback_flags,
|
||||
@@ -2328,6 +2331,7 @@ impl MvpAgent {
|
||||
app_builder_deployer_config,
|
||||
write_file_enabled,
|
||||
goal_enabled,
|
||||
graph_enabled,
|
||||
subagents_enabled,
|
||||
ask_user_question_enabled,
|
||||
client_hooks,
|
||||
|
||||
@@ -120,6 +120,7 @@ pub(crate) struct SessionSpawnOptions<'a> {
|
||||
pub persisted_signals: Option<crate::session::signals::SessionSignals>,
|
||||
pub persisted_plan_mode: Option<crate::session::plan_mode::PlanModeSnapshot>,
|
||||
pub persisted_goal_mode: Option<crate::session::goal_tracker::GoalOrchestration>,
|
||||
pub persisted_graph_mode: Option<crate::session::graph_tracker::GraphOrchestration>,
|
||||
pub persisted_announcement_state: Option<
|
||||
crate::session::announcement_state::AnnouncementState,
|
||||
>,
|
||||
@@ -257,6 +258,7 @@ pub(crate) fn chat_session_spawn_options<'a>(
|
||||
persisted_signals: None,
|
||||
persisted_plan_mode: None,
|
||||
persisted_goal_mode: None,
|
||||
persisted_graph_mode: None,
|
||||
persisted_announcement_state: None,
|
||||
session_meta,
|
||||
model_agent_type,
|
||||
|
||||
@@ -1036,6 +1036,7 @@ pub(crate) async fn handle_subagent_request(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
if verbatim_mirror_fork {
|
||||
None
|
||||
} else if let Some(scope) = agent_memory_scope {
|
||||
@@ -1069,6 +1070,8 @@ pub(crate) async fn handle_subagent_request(
|
||||
ctx.app_builder_deployer_config.clone(),
|
||||
ctx.write_file_enabled,
|
||||
ctx.goal_enabled,
|
||||
// Graph mode is a depth-0 harness; child sessions never drive it.
|
||||
false,
|
||||
true,
|
||||
ctx.ask_user_question_enabled,
|
||||
ctx.client_hooks.clone(),
|
||||
|
||||
Reference in New Issue
Block a user