pub struct MissionEngine { /* private fields */ }Expand description
The mission engine: composes the event log, reducer state, git repo, runner, control inbox, and the long-lived orchestrator session into the §4.5 loop.
Implementations§
Source§impl MissionEngine
impl MissionEngine
Sourcepub async fn capture_lesson(&mut self) -> Option<Vec<PathBuf>>
pub async fn capture_lesson(&mut self) -> Option<Vec<PathBuf>>
One final orchestrator turn at mission completion: distill at most one
reusable lesson for a future mission in this repo, write it to
.kranz/lessons/<mission-id>.md, and append it to the lesson index.
Best-effort BY DESIGN, same contract as Self::write_mission_report:
the orchestrator only PRODUCES the lesson text — this engine method is
the one that writes files — and any turn/parse/write failure is
downgraded to a warning rather than stranding a mission that already
passed its final gate. Returns the paths written (lesson file, index),
or None if there was nothing worth carrying forward or capture
failed.
Source§impl MissionEngine
impl MissionEngine
Sourcepub async fn request_plan(&mut self) -> Result<PlanRequest>
pub async fn request_plan(&mut self) -> Result<PlanRequest>
Demand the plan JSON (types::Plan, camelCase). Lenient parse with one
retry demanding bare JSON; a plan parses to PlanRequest::Ready
(unapproved). Beside the plan, the planner has two more voices: an
explicit {"wrongPlan": "…"} reply maps to PlanRequest::WrongPlan
(planner-initiated only, never inferred from prose), and when the
retry ALSO answers with prose, the orchestrator is simply not ready to
emit (it wants answers first) — that text comes back as
PlanRequest::NotReady, never as an error.
Sourcepub async fn request_revised_plan(&mut self) -> Result<PlanRequest>
pub async fn request_revised_plan(&mut self) -> Result<PlanRequest>
Propose a REVISED plan for the not-yet-complete work of a running or
blocked mission (roadmap M2). An orchestrator turn — digest + the
current milestone/feature status + a revise-the-remainder instruction —
that returns a full Plan (completed milestones unchanged and first,
then the revised remainder). Reuses the streaming orchestrator, the
lenient JSON parse, and the PlanRequest Ready/NotReady enum
exactly like Self::request_plan; prose (the orchestrator wants to
discuss first) comes back as NotReady, never an error. The draft-stage
wrong-plan escalation is NOT offered on this prompt: a wrongPlan reply
here just fails plan parsing and degrades to NotReady.
This only PROPOSES; Self::approve_revised_plan validates and applies
the subset the event vocabulary can express (see the contract note
above).
Source§impl MissionEngine
impl MissionEngine
Sourcepub fn preflight(&self) -> Vec<PreflightIssue>
pub fn preflight(&self) -> Vec<PreflightIssue>
Best-effort check of obvious prerequisites of the validation contract’s
command assertions, run once at the start of Self::run before the
first worker spawns (roadmap M2). Advisory only: the returned issues are
surfaced as a single orchestrator.decision, never as a block — the
contract gate at mission completion is still the authoritative check.
For each command assertion the leading program token is extracted (the
interpreter for sh -c / python3 -c shapes, else the first word) and
probed on PATH; a clearly-missing program is a warn. Two hard
environment defects are errors: the repo not being a git repo, and
.kranz not being writable. The probe is intentionally lenient — only
programs that plainly do not resolve are flagged, so a shell builtin or
an odd-but-valid command never produces a false warning.
Synchronous by design: run_loop futures are spawned (tokio::spawn,
so Send-bound), and an async fn(&self) here would hold
&MissionEngine — not Sync, via Box<dyn AgentSession> — across an
await, poisoning the whole run() future’s Send. The sandbox
command probes still use the shared ASYNC bounded runner: they run it
on a dedicated thread owning a current-thread runtime (the
crate::command_exec::run_bounded_gate_command pattern), which also
keeps this callable from inside the ambient runtime without a nested
block_on panic.
Source§impl MissionEngine
impl MissionEngine
Sourcepub fn create(
backend: Arc<dyn AgentBackend>,
repo_root: impl Into<PathBuf>,
goal: &str,
cfg: MissionConfig,
) -> Result<Self>
pub fn create( backend: Arc<dyn AgentBackend>, repo_root: impl Into<PathBuf>, goal: &str, cfg: MissionConfig, ) -> Result<Self>
Create a brand-new mission: validate config, open the repo, pick a
mission id, acquire the event log, and emit mission.created.
When goal carries a task class folded in by crate::ticket::Ticket::mission_goal
(execution-class backlog tickets), routes the executor to the local
tier before the config is stored on mission.created and records the
routing decision — every seed path (kranz draft/exec, REST, Slack)
creates missions from that folded goal string, so this is the single
place ticket→routing wiring needs to live. The routing table itself
may come from the tracked, base-branch-owned rules file
(crate::routing_rules, ticket routing-rules-config), read here
from the live base ref — the merge-gates ownership idiom, so a
mission can never edit the rules that route it.
Sourcepub fn resume(
backend: Arc<dyn AgentBackend>,
repo_root: impl Into<PathBuf>,
mission_id: &str,
force: LockForce,
) -> Result<Self>
pub fn resume( backend: Arc<dyn AgentBackend>, repo_root: impl Into<PathBuf>, mission_id: &str, force: LockForce, ) -> Result<Self>
Resume an existing mission from its event log (§4.3 kill-safety).
Rebuilds state by folding the log, re-acquires the single-writer lock
(force selects the LockForce steal tier; a provably dead holder
is always stolen), and remembers the sdk session id of the most recent
orchestrator session for --resume. No agent session is started here
— sessions are lazy.
Sourcepub fn state(&self) -> &MissionState
pub fn state(&self) -> &MissionState
Current reduced state (read-only).
Sourcepub fn mission_id(&self) -> &str
pub fn mission_id(&self) -> &str
Mission id.
Sourcepub fn paths(&self) -> &MissionPaths
pub fn paths(&self) -> &MissionPaths
Mission data paths.
Sourcepub fn set_orch_stall_timeout(&mut self, timeout: Duration)
pub fn set_orch_stall_timeout(&mut self, timeout: Duration)
Shrink the orchestrator stall timeout (tests exercise the death/reseed path without waiting ten minutes).
Sourcepub fn set_grant_request_timeout(&mut self, timeout: Duration)
pub fn set_grant_request_timeout(&mut self, timeout: Duration)
Shrink the grant-request timeout (tests exercise the timeout → deny-default path without waiting an hour).
Sourcepub fn set_grant_request_cap(&mut self, cap: u32)
pub fn set_grant_request_cap(&mut self, cap: u32)
Shrink the per-milestone grant-request cap (tests exercise the cap-boundary → block path without scripting three approvals).
Sourcepub fn force_reseed(&mut self)
pub fn force_reseed(&mut self)
Test hook (plan §4.8 acceptance): drop the live orchestrator session and forget its sdk id, so the next turn takes the fresh re-seed path (digest + plan.json). Behaviour must not visibly change.
Dropping the boxed session kills the real CLI child via
kill_on_drop; the mock simply drops.
Sourcepub fn record_decision(
&mut self,
summary: &str,
detail: Option<String>,
) -> Result<()>
pub fn record_decision( &mut self, summary: &str, detail: Option<String>, ) -> Result<()>
Public entry point for callers outside this module (e.g. the ticket
draft seeding path) to record an orchestrator.decision, such as the
executor-tier routing choice made when a mission is created from a
ticket.
Sourcepub async fn planning_turn(&mut self, user_text: &str) -> Result<String>
pub async fn planning_turn(&mut self, user_text: &str) -> Result<String>
One conversational planning turn: ensure the orchestrator session exists (seeded for planning), send the user’s text, and return the assistant’s full response text.
Sourcepub fn take_seed_reply(&mut self) -> Option<String>
pub fn take_seed_reply(&mut self) -> Option<String>
Take (and clear) the reply text of the most recent orchestrator seed
turn. None when no seed turn ran since the last take, or when its
reply was trivially empty. Callers surface this BEFORE the turn’s own
output — the seed reply happened first in the conversation.
Sourcepub fn approve_plan(&mut self, plan: Plan) -> Result<()>
pub fn approve_plan(&mut self, plan: Plan) -> Result<()>
Approve a plan: normalize it, create the mission branch, write and
commit plan.json (the engine writes and commits — the orchestrator
never touches files, plan §4.4), and emit plan.approved.
Worktree mode (M7 tier 1): the branch is created but never checked
out in the primary tree; the commit instead happens in a short-lived
integration worktree (setup_mission_worktree/teardown_mission_worktree,
same helpers run() uses for the rest of the mission), so the primary
checkout never moves off its starting branch. Checkout mode is
unchanged: check out the branch in the primary tree and commit there.
Sourcepub fn approve_revised_plan(&mut self, plan: Plan) -> Result<()>
pub fn approve_revised_plan(&mut self, plan: Plan) -> Result<()>
Apply a revised plan to a running or blocked mission (roadmap M2), preserving all completed work. See the contract note above for the full rationale and the honest scope of what this expresses.
Validation (rejects with EngineError::InvalidState):
- the mission must be Running or Blocked (re-planning a Planning mission
is
Self::approve_plan; a terminal mission cannot be revised); - every already-Complete milestone must appear in the revised plan, FIRST and in the same order, with its title and full feature set (titles, specs, criteria) UNCHANGED — a dropped or altered completed milestone is rejected.
Application (existing events only): on the FIRST not-yet-complete
milestone, pending planned features the revision drops are
feature.skipped, and features the revision adds are appended via
fixfeature.created. The full revised plan is written + committed as
revised-plan.md, and an orchestrator.decision summarizes the change.
Sourcepub async fn run(&mut self) -> Result<MissionStatus>
pub async fn run(&mut self) -> Result<MissionStatus>
Drive the mission until it is Complete or Failed (returned), Blocked
(returned so the user can intervene), or the process is killed (safe:
the log is the source of truth). Paused missions loop in place,
draining the control inbox, until a Resume arrives.
NOTE on checkout lifetime: in CHECKOUT mode, run() leaves the checkout
on the MISSION branch at terminal states deliberately — report.md/
plan.md are committed there, and yanking the checkout back to base
would make the mission’s own artifacts vanish from the working tree at
the exact moment the operator reads them. The dispatcher (kranz work)
and kranz draft restore the operator’s checkout at THEIR boundaries.
In WORKTREE mode (M7 tier 1) the primary checkout never moves at all —
plan.md/report.md are committed on the mission branch via the
integration worktree (approve_plan/write_mission_report), and a
human-readable, untracked twin of each is written straight to the
primary runtime dir (.kranz/missions/<id>/) so an operator reading
the primary checkout still sees them, without the primary ever leaving
its starting branch.