Skip to main content

MissionEngine

Struct MissionEngine 

Source
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

Source

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

Source

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.

Source

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

Source

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

Source

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.

Source

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.

Source

pub fn state(&self) -> &MissionState

Current reduced state (read-only).

Source

pub fn mission_id(&self) -> &str

Mission id.

Source

pub fn paths(&self) -> &MissionPaths

Mission data paths.

Source

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).

Source

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).

Source

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).

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more