Internals
Kora is a tree-walking interpreter built around durable execution, information-flow labels, and token budgets — not a generic scripting VM. This page traces the pipeline from source text to a running agent, then the runtime mechanisms behind Decisions that don’t show up in a typical language.
Compiler pipeline
Crate map
The unique parts
Journal — durable execution by replay
No stack snapshot. Every effect (model call, tool result, write, human answer, clock read) is appended to .kora/runs/<id>.jsonl, one line each. Resuming = re-running from the top, effects served from the journal instead of re-executed, fast-forwarding to where it stopped.
Writes are recorded twice on purpose: an attempt before the call, its outcome after. A resume that finds an attempt with no outcome stops and names the call, because a process killed in that gap leaves nobody able to say whether the row was written.
Labels — information flow tracking
classified values carry a secrecy tag (Public / Classified) and a trust tag (Unverified / Trusted). Labels are transitive: survive slicing, formatting, arithmetic, container insert, return values.
Declassify blocks — auditable release
audit.rs statically enumerates every declassify … for sink: site in a program, its imports, and its packages — exhaustively, not via grep.
Budgets — tokens as the unit of control
Budgets are denominated in tokens, the thing the runtime can actually measure, not money. They nest: a child scope can only tighten a parent’s limit. Exhaustion is a returned value, never an exception.
Cassettes — record/replay for model calls
Three modes: Live, Record (call + append to disk), Replay (never call — a missing entry is an error). Keys cover call site, model, prompt, and resolved input.
Portable values — the agent isolation boundary
Runtime values use Rc for cheap in-agent sharing, so they can’t cross threads as-is. Portable is an explicit deep, Send-safe copy used only when values move between agents.
Images as values
fs.image(path) returns a first-class value like fs.read does for text, and flows into analyze(). Type is sniffed from magic bytes, never the file extension; size is checked at read time, not at the provider.
Telemetry — labels guard the exporter too
Spans are emitted by the scheduler itself (it already owns every model call, budget meter, declassification) as raw OTLP/HTTP JSON, no SDK. A classified value cannot become a span attribute.
Modules — one file, one namespace
A module is one Kora file (use ”./lib/tax.ko” as tax). Its top-level names live in their own namespace, so two files can reuse a name without colliding, and a function always reads the file it was written in. Import paths resolve relative to the importing file, never the working directory.
Notes — a durable run’s own scratch space
A key-value store scoped to one durable run (.kora/notes/<run-id>.json), for state a plain variable can’t hold: it survives a crash, and a different kora invocation can read it. A classified value written through notes.write keeps its label on the way back out.
Debugger — a zero-cost hook
The interpreter knows nothing about DAP. It keeps a frame stack and asks a Debugger whether to stop before each statement — one Option check, no snapshot, when nothing is attached. The editor-facing protocol lives entirely in kora-dap.
Config - present files fail closed
kora.toml actively configures models, sinks, HTTP, telemetry, MCP, Python, output redaction, and installs. An unreadable file, invalid TOML, or malformed model entry stops execution; unknown top-level sections remain forward-compatible.
Package security
Capabilities — no ambient authority
A dependency reaches the network, filesystem, a database, the environment, a Python worker, or an MCP server only where the importing program granted it (net, fs, …) — and it can never pass on more than it holds itself.
Checksum log — trust-on-first-use, permanently
A lockfile can’t protect the fetch that creates it. The first time a commit is seen, its hash is recorded forever in two logs — the project’s committed kora.sums and a machine-wide log under the user’s home directory — and every later fetch is checked against both.
Install flow, animated
A fresh install walks the teal path top to bottom. If the commit is already in kora.lock, fetch.rs takes the amber shortcut straight to lock.rs. Any hash or commit mismatch at sumlog.rs or lock.rs takes a red path and refuses the install — it never continues quietly on a changed tag or a rewritten repo.
| Attack | Stopped by |
|---|---|
| Maintainer force-pushes a tag | lock.rs: commit/hash mismatch → refuse |
| Repo history rewritten | hash.rs: content hash won’t follow it |
| Publish backdoor, then delete it | sumlog.rs: hash recorded permanently, deletion doesn’t erase the record |
| First-time fetch, nothing local to check against | sumlog.rs machine-wide log: another project already recorded this commit |
| Compromised dep tries to phone home / read disk | grants.rs: no capability granted, no ambient authority |
| Compromised dep re-exports secrets to its own deps | grants.rs: can’t pass on more capability than it holds |
Editor tooling
Debugger hook only