Skip to main content

MissionHost

Struct MissionHost 

Source
pub struct MissionHost { /* private fields */ }
Expand description

Registry of missions this server process hosts (see module docs).

Implementations§

Source§

impl MissionHost

Source

pub fn new(repo_root: PathBuf) -> Self

Host for repo_root, discovering the Claude backend on first use.

Source

pub fn with_backend(repo_root: PathBuf, backend: Arc<dyn AgentBackend>) -> Self

Host with an injected backend (tests drive the full lifecycle through kranz_engine::backend_mock without a claude binary).

Source

pub fn with_gate_executor<F>(repo_root: PathBuf, gate_executor: F) -> Self
where F: Fn(&str, &Path) -> (bool, String) + Send + Sync + 'static,

Host with an injected gate-suite executor (tests script CI gate outcomes for MissionHost::merge hermetically, without ever shelling out to cargo/npm). Mirrors MissionHost::with_backend’s seam, for the gate suite instead of the agent backend.

Source

pub fn repo_root(&self) -> &PathBuf

The repository this host creates missions in.

Source

pub async fn create( &self, goal: &str, config_patch: Option<&Value>, ) -> Result<String, ApiError>

POST /api/missions: layered config + optional request patch → validate → create the mission → hold its engine in the registry. Public: the Slack bridge drives the same lifecycle through this host (wired by kranz serve --slack), so these five operations are the shared client surface, not axum-private plumbing.

Source

pub async fn draft( &self, slug: &str, then_enqueue: bool, ) -> Result<DraftOutcome, ApiError>

Entry point any surface (REST, Slack, CLI-over-HTTP) can call to draft a backlog ticket non-interactively: validate the slug, load the ticket, create its planning mission through this host — so the create path registers it in missions and its lifecycle events stream over GET /api/missions/:id/ws exactly like POST /api/missions — then run drive_draft (roadmap f-1-1) to completion against that hosted engine. The engine is dropped and the registry entry removed once the draft turn ends (mirroring [run_to_end]’s drop-then-remove ordering) so the mission stays observable/resumable afterward; no operator checkout restoration happens here — a headless server has no checkout to restore.

Source

pub async fn draft_async( &self, slug: &str, then_enqueue: bool, ) -> Result<String, ApiError>

POST /api/tickets/:slug/draft: fire-and-observe twin of Self::draft for the REST surface — a draft can run for a while (a planning conversation with the orchestrator), so this creates the planning mission SYNCHRONOUSLY (registering it exactly like create, so its lifecycle streams over GET /api/missions/:id/ws immediately), then spawns drive_draft as a background task and returns the mission id right away. The final outcome (Review vs NeedsContext) is read back later via GET /api/tickets/:slug.

Source

pub fn approve_ticket( &self, slug: &str, force: bool, ) -> Result<ApprovedTicket, ApiError>

POST /api/tickets/:slug/approve: the shared kranz_engine::deps gate (cycle detection, unsatisfied-blocker refusal) plus the enqueue side effects — the exact same core kranz_cli’s kranz ticket approve calls, so the CLI and REST surfaces can never drift.

Source

pub async fn planning_turn( &self, id: &str, text: &str, ) -> Result<String, ApiError>

POST /api/missions/:id/planning/turn: one conversational turn. A captured seed reply (fresh session / re-seed) is prepended — it happened first in the conversation.

Source

pub async fn request_plan(&self, id: &str) -> Result<Value, ApiError>

POST /api/missions/:id/planning/request-plan: demand the plan. Ready → plan + cost estimate; NotReady → the orchestrator’s prose (back to the conversation).

Source

pub async fn approve(&self, id: &str, plan: Plan) -> Result<String, ApiError>

POST /api/missions/:id/approve: commit plan.json/plan.md/index.md on the mission branch exactly like the CLI. Returns the mission branch.

Source

pub async fn start(&self, id: &str) -> Result<(), ApiError>

POST /api/missions/:id/start: consume the hosted engine into a background engine.run() task — or, for a mission not in the registry (blocked earlier, server restarted, or CLI-created), resume it from the event log and run that.

Source

pub async fn merge(&self, id: &str) -> Result<Value, ApiError>

POST /api/missions/:id/merge: the human-triggered gated Merge action (roadmap M6). Loads the mission’s base_branch/base_sha/ mission_branch from its event log (no engine needs to be hosted — merge is independent of the planning/run-loop registry) and runs kranz_engine::merge::merge_mission under spawn_blocking (git and the gate suite are both blocking work). Never pushes.

Source

pub async fn ask(&self, question: &str) -> Result<Value, ApiError>

Read-only, LLM-backed Q&A for /kranz ask: ground the model in current mission/ticket state and return one answer plus usage. This deliberately bypasses the hosted mission registry: it must never create a mission, append mission events, enqueue work, approve, start, or merge.

Source

pub fn release(&self, id: &str) -> Result<bool, ApiError>

Release a hosted idle engine: drop it from the registry (flushing its log and freeing the single-writer lock) so an EXTERNAL runner — the kranz work dispatcher, a terminal kranz plan/run — can take the mission over. The approve-and-QUEUE path needs this: without it the approved engine would sit attached here holding the lock, and the very dispatcher the queue points at would be refused with LockHeld.

Returns true when the mission is now free of THIS host (released, or was never hosted), false when it is actively running here (never interrupted). A turn in flight is an error, mirroring the other planning operations.

Source

pub fn sweep_idle(&self, threshold: Duration) -> Vec<String>

Release every Planning entry idle for at least threshold (a mission touched more recently than that is left alone). A mid-turn cell can never actually be released — release refuses it via turn_in_flight, which this treats as “not idle yet” rather than an error. Returns the ids this call actually released.

Source

pub async fn abandon(&self, id: &str, reason: &str) -> Result<(), ApiError>

POST /api/missions/:id/abandon: retire a mission through the engine’s canonical abandon path (terminal-refusing, event-recorded). A mission hosted HERE is taken out of the registry first — an idle planning engine is dropped (freeing the lock), a running task is aborted and awaited (the engine’s Drop flushes the log and kills its agent children) — so the abandon event lands on a quiet log. A lock held by a FOREIGN process (a terminal kranz plan/run) surfaces as the engine’s LockHeld → 409; the web never force-steals.

Source

pub fn clean(&self, id: &str, all: bool) -> Result<(), ApiError>

POST /api/missions/:id/delete: remove a TERMINAL mission’s directory, mirroring kranz clean exactly — cleanable_class decides, all opts in to deleting Complete missions (which otherwise stay: they feed the cost-calibration corpus), and a live lock is re-checked immediately before removal so nothing is ever deleted under a running engine. Only the mission directory and its own missions/index.md line go; branches, tags, and every other mission’s index line are left intact (same contract as the CLI).

Source

pub fn pending_plan(&self, id: &str) -> Option<Plan>

The reviewed plan awaiting approval, if any (clone). GET /api/missions/:id/pending-plan and the glasses PLAN page read this.

Source

pub async fn try_approve_pending( &self, id: &str, ) -> Result<Option<String>, ApiError>

Approve the currently parked plan for an explicit untargeted command (/kranz approve). Preview-based clients must use the matching variant.

Source

pub async fn try_approve_pending_matching( &self, id: &str, expected_identity: Option<&str>, ) -> Result<PendingApproval, ApiError>

Approve only the plan the caller reviewed. The engine lock serializes replacement and approval; the pending-plan lock protects comparison and consumption. An approval failure leaves the original plan parked, with no restore that could overwrite a concurrent replacement.

Source

pub async fn approve_pending( &self, id: &str, expected_identity: Option<&str>, ) -> Result<String, ApiError>

REST approval requires the identity returned with the reviewed preview.

Source

pub async fn drain(&self) -> Result<Value, ApiError>

POST /api/queue/drain: run the queue drain/claim/skip loop (kranz_engine::work::drain_queue) as a background task on this serve process. This is just ANOTHER dispatcher: it does not register missions in the missions planning registry, and arbitrates against an external kranz work process exactly as today — through the queue claim files and the events.jsonl single-writer lock, no new locking.

IDEMPOTENT while a drain is live: a second call while the tracked drain task has not finished returns THAT drain’s current state instead of spawning a second one.

Source

pub fn queue_state(&self) -> Value

GET /api/queue: the queue front-to-back, who (if anyone) currently holds the busy lock, and this host’s own drain tracker.

Readiness is probed for the front entry only (with a short TTL cache). Deeper entries omit readiness so a long queue cannot turn every dashboard poll into N CLI shells.

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<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<A, B, T> HttpServerConnExec<A, B> for T
where B: Body,

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> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

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<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

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