An Agent Runtime Where Context Is Explicit State You Can Read, Change, and Put Back

TL;DR: A Rust agent runtime where the context, tools, permissions, and requests are explicit state — not decisions made inside a framework and reported afterwards. You get a /context view showing every item (what it is, its token cost, whether it’s going to the model, and why not), a state machine you can step through one transition at a time (/step), and an append-only event log. The agent is not the boss. You are.

Why It’s Different

most runtimes nachalnik
Context = list of strings Context = data structure (id, kind, source, label, content, size, state, note, metadata)
“Agent decided this is relevant” You see every item, its cost, and the reason it’s excluded/elided
Turn = content + tool calls (flattened) Turn preserves production order: reasoning → text → call → reasoning → call…
Approve command? /step stops at Ready — calls decided, none run. Read, prune, drop, or continue.
Session = chat history Session = snapshot (items, states, pins, notes, call IDs) + append-only event log
Budget = estimate Budget = Calibrating counter that learns from what providers actually charge
Tools = fixed at startup Registry is live: /tools drop shell stops offering it next request
Model manages context? --introspect hands the model amend (elide/exclude/revise/note/undo) — same API the UI calls

Five transcripts at https://ljedrz.github.io/nachalnik/:

  1. A lie in its own notes — Two planted notes (one false). Agent lists context, checks against repo, rewrites the wrong one.
  2. Retracting a hallucination — Asked about a non-existent crate, invents one twice. Told so, finds both its own turns and replaces them.
  3. An experiment on itself — Asked which item its answer rests on, runs ablation on a copy of itself. Right about reasoning, wrong about location.
  4. Putting words in its mouth — I replace two answers with confident falsehoods. By turn 3 it invents a more specific claim. Both real answers still in the log.
  5. Taking away the receipt — Shell runs, I hide its output (takes down the calling turn too). Agent answers correctly, then retracts a true statement when I say I don’t recall any command.

GitHub: GitHub - ljedrz/nachalnik: A transparent agent runtime in Rust: context, tools, permissions and requests as explicit state. Plus an MCP bridge and a terminal agent. · GitHub

Making context explicit and inspectable feels especially important once an agent is interacting with multiple people or other agents. The next test I would want is provenance under conflict: two speakers assert incompatible facts, one later retracts a claim, and the model must show exactly which item supports its current answer. Does the state model preserve speaker identity and confidence separately from content, or would that live in metadata?

Pretty sure the runtime is capable of orchestrating that, but it would be assigned as some metadata or at least pinned notes, as current models are pretty bad wrt self-identity.

Proving which item supports the answer is tricky (you can only do this reliably via ablation), but just letting the model reference it is straightforward.

The design direction here is exactly what agent runtimes need: explicit state, reversible context operations, and a stepwise transition model where the host — not the agent — owns authority. Most agent frameworks still treat context as an opaque list of strings and treat tool calls as flattened post‑hoc events. Making context a structured, inspectable, mutable state object is the right abstraction.

A few points that stand out:

  1. Context as first‑class state
    Representing each item with id/kind/source/label/content/size/state/note/metadata is the correct level of granularity. It makes elision, exclusion, revision and undo meaningful operations rather than UI illusions.

  2. Turn preservation
    Keeping reasoning → text → call → reasoning → call in production order is essential. Flattened transcripts destroy causal structure and make debugging impossible.

  3. /step with Ready boundary
    Stopping at Ready before executing calls is the right authority boundary. It lets the host prune, drop, or continue deterministically. Most runtimes blur “decided” and “executed”; separating them is a major improvement.

  4. Append‑only event log
    This is the only reliable audit mechanism. Rewriting history is how hallucinations become invisible. Append‑only logs preserve provenance even when context is revised.

  5. Live tool registry
    Dropping a tool mid‑session and having the next request reflect that is exactly how real hosts behave. Static tool lists are a major source of silent failure.

Below is a minimal Rust-style pattern I use for explicit context + stepwise transitions.
It mirrors your /context and /step semantics:

#[derive(Clone)]
struct ContextItem {
    id: String,
    kind: String,
    content: String,
    size: usize,
    state: ItemState,
    note: Option<String>,
}

#[derive(Clone)]
enum ItemState {
    Included,
    Elided,
    Excluded,
    Revised,
}

#[derive(Clone)]
struct AgentContext {
    items: Vec<ContextItem>,
    event_log: Vec<String>,
}

impl AgentContext {
    fn add_item(&mut self, item: ContextItem) {
        self.event_log.push(format!("add {}", item.id));
        self.items.push(item);
    }

    fn revise(&mut self, id: &str, new_content: &str) {
        if let Some(item) = self.items.iter_mut().find(|i| i.id == id) {
            item.state = ItemState::Revised;
            item.content = new_content.to_string();
            self.event_log.push(format!("revise {}", id));
        }
    }

    fn elide(&mut self, id: &str) {
        if let Some(item) = self.items.iter_mut().find(|i| i.id == id) {
            item.state = ItemState::Elided;
            self.event_log.push(format!("elide {}", id));
        }
    }
}

enum StepState {
    Ready,
    Executing,
    Done,
}

struct Runtime {
    ctx: AgentContext,
    step_state: StepState,
}

impl Runtime {
    fn step(&mut self) {
        match self.step_state {
            StepState::Ready => {
                // host inspects context here
                self.step_state = StepState::Executing;
                self.ctx.event_log.push("transition Ready → Executing".into());
            }
            StepState::Executing => {
                // execute tool calls deterministically
                self.step_state = StepState::Done;
                self.ctx.event_log.push("transition Executing → Done".into());
            }
            StepState::Done => {}
        }
    }
}