omk 0.5.0

A Rust runtime for Kimi CLI. Turns prompts into proof-backed engineering runs with gates, worktrees, and replay.
Documentation
use anyhow::Result;

use crate::runtime::config::EVENTS_FILE;
use crate::runtime::events::{
    Event, EventBuilder, EventKind, EventWriter, RunId, TaskId, WorkerId,
};
use crate::runtime::goal::evidence::GoalAgentRunEvidence;
use crate::runtime::goal::planner::controller_task_summary;
use crate::runtime::goal::state::{GoalState, GOAL_AGENT_WORKER_ID};
use crate::runtime::goal::task_graph::GoalTaskStatus;
use crate::runtime::worker::WorkerSpec;

pub(super) async fn read_goal_agent_worker_results(
    specs: &[WorkerSpec],
    task_ids: &[String],
) -> Result<Vec<crate::runtime::worker::WorkerResult>> {
    let mut filtered = Vec::new();
    for spec in specs {
        let results: Vec<crate::runtime::worker::WorkerResult> = spec.read_results().await?;
        filtered.extend(
            results
                .into_iter()
                .filter(|result| task_ids.iter().any(|task_id| task_id == &result.task_id)),
        );
    }
    Ok(filtered)
}

pub(super) fn summarize_goal_agent_worker_results(
    results: &[crate::runtime::worker::WorkerResult],
) -> Option<String> {
    let summaries: Vec<String> = results
        .iter()
        .map(|result| format!("{}: {}", result.task_id, result.summary))
        .collect();
    (!summaries.is_empty()).then(|| summaries.join(" | "))
}

pub(crate) async fn append_agent_execution_task_events(
    state: &GoalState,
    task: &crate::runtime::goal::task_graph::GoalTask,
    evidence: &GoalAgentRunEvidence,
) -> Result<()> {
    let writer = EventWriter::new(state.state_dir.join(EVENTS_FILE));
    let run_id = RunId(state.goal_id.clone());
    let task_id = TaskId(task.id.clone());
    let worker_id = WorkerId(GOAL_AGENT_WORKER_ID.to_string());
    let summary = format!(
        "{} via {} (run: {}, scheduler: {})",
        controller_task_summary(task),
        GOAL_AGENT_WORKER_ID,
        evidence.run_path.display(),
        evidence.summary.run_id
    );
    let event = if task.status == GoalTaskStatus::Done {
        EventBuilder::new(run_id).task_completed(task_id, worker_id, Some(&summary))?
    } else {
        Event::new(run_id, EventKind::TaskFailed)
            .with_actor(GOAL_AGENT_WORKER_ID)
            .with_payload(serde_json::json!({
                "task_id": task.id,
                "worker_id": GOAL_AGENT_WORKER_ID,
                "summary": summary,
            }))?
    };
    writer.append(&event).await
}