Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 24 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

---

![korg demo — rewind, fork, and replay AI agent decisions in real time](demo.gif)
![korg demo — record, verify, and rewind an AI agent session as a hash-chained ledger](demo.gif)

---

Expand Down Expand Up @@ -45,7 +45,7 @@ Every agent action is:
- **Appended** to an immutable, cryptographically-signed ledger
- **Ordered** with Hybrid Logical Clocks (causal, deterministic, globally consistent)
- **Replayable** — rebuild exact state at any point in history
- **Reversible** — rewind, fork, or branch any decision
- **Reversible** — rewind the ledger to any prior sequence point

---

Expand Down Expand Up @@ -151,7 +151,7 @@ The crate is not yet published to crates.io; install from source:
git clone https://github.com/New1Direction/korg
cd korg
cargo build --release
./target/release/korg-tui --help
./target/release/korg --help
```

### Python bridge (for korgex / korgchat)
Expand All @@ -171,26 +171,30 @@ korg campaign --tui --prompt "Refactor the auth layer to use JWTs"
# Web cockpit at localhost:8080
korg campaign --web --prompt "Optimize the database connection pool"

# Pure autonomous goal mode
korg goal "Write and validate a full test suite for src/parser.rs"
# Pure autonomous goal mode (--goal is a top-level flag)
korg --goal "Write and validate a full test suite for src/parser.rs"

# Preview without committing (speculative sandbox)
korg run --preview "Refactor the main event loop"
# Preview without committing (dry-run; --preview is a top-level flag)
korg --preview "Refactor the main event loop"
```

### Rewind & Fork
### Rewind & Verify

```bash
# Rewind to a specific ledger sequence point
# Rewind the capability journal to a specific ledger sequence point
korg rewind --seq 4

# List all checkpoints in the current session
korg checkpoints list
# Drive the honest pipeline on a fixture and emit a verifiable ledger
korg run-once "Fix the add function in src/lib.rs so it adds"

# Restore from a specific checkpoint
korg checkpoints restore --id <checkpoint-uuid>
# Independently verify any korg-ledger@v1 journal (no trust in the producer)
korg-verify <path-to-ledger.jsonl>
```

> Speculative branch/fork and named checkpoints (`korg fork`, `korg checkpoints
> list|restore`) are planned, not yet shipped. The reversibility surface today is
> `korg rewind`.

---

## Cognition Modes
Expand All @@ -208,8 +212,8 @@ Korg adapts its intelligence tier based on task complexity. Modes are governed e
| `heavy-consciousness` | Maximum depth. Full HeavyConsciousness context injection. |

```bash
korg run --mode research "Explore alternative approaches to the rate limiter"
korg run --mode recovery "Carefully migrate the database schema"
korg --mode research "Explore alternative approaches to the rate limiter"
korg --mode recovery "Carefully migrate the database schema"
```

---
Expand All @@ -235,8 +239,8 @@ Korg treats AI cognition the same way a hypervisor treats compute and Git treats
| Deterministic replay | ✅ | ❌ | ❌ | ❌ |
| Causal HLC ordering | ✅ | ❌ | ❌ | ❌ |
| Rewind execution | ✅ | ❌ | ❌ | ❌ |
| Speculative branches | | ❌ | ❌ | ❌ |
| Execution checkpoints | | ❌ | ❌ | ❌ |
| Speculative branches | 🚧 planned | ❌ | ❌ | ❌ |
| Execution checkpoints | 🚧 planned | ❌ | ❌ | ❌ |
| Cryptographic audit trail | ✅ | ❌ | ❌ | ❌ |
| Micro-healing | ✅ | ❌ | ❌ | ❌ |
| Model-agnostic | ✅ | ✅ | ✅ | ✅ |
Expand Down Expand Up @@ -284,8 +288,9 @@ Korg is in active development. Current test coverage: **175 tests, 0 failures**

- [x] Append-only cognitive ledger with HLC ordering
- [x] Deterministic replay and projection rebuilds
- [x] Speculative execution + preview mode
- [x] Execution checkpoints (O(1) restore)
- [x] Preview / dry-run mode (`--preview`)
- [ ] Speculative warm-boot execution (in progress)
- [ ] Execution checkpoints / restore CLI (primitive exists; CLI planned)
- [x] Micro-healing effect layer
- [x] Multi-agent swarm orchestration (Captain, Harper, Benjamin, Lucas)
- [x] TUI dashboard + Web cockpit
Expand Down
1 change: 1 addition & 0 deletions crates/korg-runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub mod observation;
pub mod personas;
pub mod provenance;
pub mod recovery;
pub mod run_once;
pub mod runtime;
pub mod session;
pub mod skills;
Expand Down
248 changes: 248 additions & 0 deletions crates/korg-runtime/src/run_once.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
//! `run_once_honest` — the smallest user-facing entrypoint that drives the SP1
//! honest pipeline visibly, below the (separately-broken) campaign orchestration.
//!
//! It runs the exact chain the keystone test proves: build Benjamin's
//! system+user messages, ask the hermetic [`DeterministicProvider`] for a patch,
//! parse the mutations the worker way, APPLY them to a real git worktree, then
//! MEASURE reality (`numstat`, `cargo_check`, `honest_metrics`). The attested
//! mutation count is `numstat.files` — the real git-diff file count — never a
//! fabricated number. An unrelated task yields an honest null (zero changes,
//! zero attested), so this command can never lie about what the agent did.
//!
//! It then writes a verifiable `korg-ledger@v1` JSONL journal of the run's
//! events (hash-chained via the conformance-tested `korg-ledger` primitives,
//! re-exported through `korg_registry::ledger_chain`), so `korg-verify` and the
//! in-browser verifier accept it.

use crate::observation::{apply_mutations, cargo_check, honest_metrics, numstat, CargoCheck};
use crate::personas::{load_prompt_for_persona, parse_structured_response, Persona};
use korg_llm::{DeterministicProvider, LlmProvider, LlmRequest, Message, Role};
use korg_registry::ledger_chain::{chain_hash, GENESIS_HASH};
use serde_json::{json, Value};
use std::path::{Path, PathBuf};

/// The honest report of a single run. Every field is an observed fact:
/// `attested_count == numstat_files` is the SP1 invariant made visible.
#[derive(Debug, Clone)]
pub struct HonestRunReport {
/// Real number of files changed in the worktree (== `numstat_files`).
pub files_changed: usize,
/// `"Passed"`, `"Failed"`, or `"Unavailable"` — never fabricated.
pub cargo_check: String,
/// The mutation count we attest. Equals `numstat_files` by construction —
/// we attest only what really changed on disk.
pub attested_count: usize,
/// The real git-diff file count the worktree reports.
pub numstat_files: usize,
/// Path to the verifiable korg-ledger@v1 journal written for this run.
pub ledger_path: Option<PathBuf>,
}

/// Classify a `CargoCheck` into the stable string the report exposes.
fn cargo_check_label(check: &CargoCheck) -> &'static str {
match check {
CargoCheck::Passed => "Passed",
CargoCheck::Failed(_) => "Failed",
CargoCheck::Unavailable => "Unavailable",
}
}

/// Build the two messages the hermetic provider routes on: Benjamin's system
/// prompt (so `role_marker` resolves to "benjamin") and the task as the user
/// message. Reuses `load_prompt_for_persona` — the same loader the worker uses.
fn benjamin_request(task: &str) -> LlmRequest {
let system = load_prompt_for_persona(Persona::Benjamin);
LlmRequest {
messages: vec![
Message {
role: Role::System,
content: system,
name: None,
tool_calls: None,
},
Message {
role: Role::User,
content: task.to_string(),
name: None,
tool_calls: None,
},
],
temperature: 0.3,
max_tokens: None,
tools: None,
stop_sequences: None,
multimodal: None,
tx_id: None,
session_id: None,
policy_hash: None,
top_p: None,
presence_penalty: None,
frequency_penalty: None,
}
}

/// Drive the honest pipeline once for Benjamin on `task` against `repo_path`,
/// returning a report whose `attested_count` equals the real diff file count.
pub async fn run_once_honest(task: &str, repo_path: &Path) -> HonestRunReport {
// 1. Ask the hermetic default provider (as Benjamin) for the patch.
let provider = DeterministicProvider::new();
let resp = match provider.complete(benjamin_request(task)).await {
Ok(r) => r,
Err(_) => {
// The hermetic provider is infallible, but fail honest if it ever isn't:
// no patch → no change → attested 0.
return HonestRunReport {
files_changed: 0,
cargo_check: "Unavailable".to_string(),
attested_count: 0,
numstat_files: 0,
ledger_path: None,
};
}
};

// 2. Parse mutations the way the worker does, then APPLY them to the worktree.
let (output, _confidence, _frontmatter) = parse_structured_response(&resp.content);
let muts = output
.get("mutations")
.and_then(|m| m.as_array())
.cloned()
.unwrap_or_default();
let apply = apply_mutations(repo_path, &muts).await;

// 3. Measure reality — the real diff and whether the result compiles.
let n = numstat(repo_path).await;
let check = cargo_check(repo_path).await;
let _metrics = honest_metrics(
&apply,
&check,
&n,
resp.usage.total_tokens,
1.0,
0.0,
"korg run-once",
);

// The attested mutation count is the REAL diff file count — nothing invented.
let attested = n.files;

// 4. Write a verifiable korg-ledger@v1 journal of the run's events.
let ledger_path = write_ledger(repo_path, task, &resp, attested, &check).ok();

HonestRunReport {
files_changed: n.files,
cargo_check: cargo_check_label(&check).to_string(),
attested_count: attested,
numstat_files: n.files,
ledger_path,
}
}

/// Append one hash-chained event to `events`, computing its `entry_hash` from
/// the previous tip via the conformance-tested `chain_hash` primitive.
fn push_event(
events: &mut Vec<Value>,
prev: &mut String,
mut event: serde_json::Map<String, Value>,
) {
event.insert("prev_hash".into(), json!(prev.clone()));
let value = Value::Object(event);
let hash = chain_hash(&value, None);
let mut obj = value.as_object().cloned().unwrap_or_default();
obj.insert("entry_hash".into(), json!(hash));
*prev = hash;
events.push(Value::Object(obj));
}

/// One korg-ledger@v1 event in the flat on-disk shape the verifier accepts
/// (see `spec/korg-ledger-v1/vectors/basic-intact.jsonl`).
fn event(
seq: u64,
tool: &str,
args: Value,
result: Value,
triggered_by: Option<u64>,
) -> serde_json::Map<String, Value> {
let mut m = serde_json::Map::new();
m.insert("schema_version".into(), json!("1.0"));
m.insert("seq_id".into(), json!(seq));
m.insert("source_agent".into(), json!("agent:korg-run-once"));
m.insert("tool_name".into(), json!(tool));
m.insert("args".into(), args);
m.insert("result".into(), result);
m.insert("success".into(), json!(true));
m.insert("duration_ms".into(), json!(0));
if let Some(tb) = triggered_by {
m.insert("triggered_by".into(), json!(tb));
}
m
}

/// Build and persist the run's hash-chained journal to
/// `<repo>/.korg/run-once.jsonl`, returning its path. The events form a
/// well-formed causal DAG (each `triggered_by` references a strictly-earlier
/// `seq_id`) so both `verify_chain` and `verify_dag` pass.
fn write_ledger(
repo_path: &Path,
task: &str,
resp: &korg_llm::LlmResponse,
attested: usize,
check: &CargoCheck,
) -> std::io::Result<PathBuf> {
let mut events: Vec<Value> = Vec::new();
let mut prev = GENESIS_HASH.to_string();

// 1. The operator's prompt.
push_event(
&mut events,
&mut prev,
event(1, "user_prompt", json!({ "prompt": task }), json!({}), None),
);
// 2. The (hermetic) model inference, with its real token usage.
push_event(
&mut events,
&mut prev,
event(
2,
"llm_inference",
json!({ "model": resp.model, "prompt_tokens": resp.usage.prompt_tokens }),
json!({ "completion_tokens": resp.usage.completion_tokens }),
Some(1),
),
);
// 3. The applied mutation(s) — recorded only when something really changed.
push_event(
&mut events,
&mut prev,
event(
3,
"apply_mutations",
json!({ "path": "src/lib.rs" }),
json!({ "files_changed": attested }),
Some(2),
),
);
// 4. The honest compile observation.
push_event(
&mut events,
&mut prev,
event(
4,
"cargo_check",
json!({}),
json!({ "result": cargo_check_label(check) }),
Some(3),
),
);

let dir = repo_path.join(".korg");
std::fs::create_dir_all(&dir)?;
let path = dir.join("run-once.jsonl");
let mut body = String::new();
for e in &events {
body.push_str(&serde_json::to_string(e).unwrap_or_default());
body.push('\n');
}
std::fs::write(&path, body)?;
Ok(path)
}
Loading
Loading