skVM: Building a Cross-Platform Compiler and Runtime for Agent Skills

Treating Agent Skills as programs that need a compiler

Background 🍅

Agent Skills have quietly replaced bare prompts as the mainstream unit of organization in agent development, and the two properties driving that shift are worth naming precisely: progressive disclosure, which keeps a skill’s context cost proportional to how much of it a task actually needs, and hand-editability, which keeps humans in the authoring loop. A single SKILL.md plus a few companion scripts can package domain knowledge, workflow conventions, and tool-calling habits into a distributable asset, which is why GitHub is now flooded with them.

That flood produces a characteristic failure mode. You pull down a well-starred skill, run it locally, and the results fall far short of what the README promised. It’s baffling (there is hardly any code in a skill to be wrong), yet you cannot reproduce the author’s results. Usually the skill isn’t badly written. The author developed it against a different model than yours, or ran it on OpenClaw while you’re on Hermes Agent. A skill that gave its author real leverage arrives on your machine nearly inert.

This is structural, not bad luck, and it decomposes into three layers:

  • Different LLMs emerge from very different training processes with unevenly distributed capabilities (code generation, instruction following, tool calling, long-chain reasoning), each model with its own strengths and blind spots;
  • Different agent harnesses impose their own tool sets, sandbox contracts, context-management policies, and error-reporting protocols;
  • The runtime environment a skill depends on (Python packages, CLI tools, environment variables) caps how much of its nominal capability it can actually deliver.

Copying someone’s skill verbatim onto your machine amounts to pretending none of these differences exist. Before this work, portability was simply never treated as a property skills needed to have. SkVM makes it a first-class problem: a skill is not prompt text addressed to a model, but a program that must be compiled and optimized for a heterogeneous LLM–harness target platform, and SkVM builds a full compiler-and-runtime system around that premise.


An Analogy 🍅

Why should compiling and running skills have anything to do with virtual machines? The metaphor is deeper than it looks, and the way to see that is to revisit an older chapter of engineering history.

From the 1970s through the 1990s, hardware architectures churned relentlessly, a new one every few years. The same C program, landing on a VAX, a SPARC, or an x86 machine, had to re-select instructions, re-allocate registers, and re-align memory accesses before it performed decently. The field converged on exactly two answers: static compilation, where the compiler translates source ahead of time into the best machine code for a known target; and the virtual machine, where source is first lowered to a hardware-independent intermediate representation (IR/bytecode) and translated just-in-time on whatever platform it lands on. Different mechanisms, one essential move: insert an abstraction layer between source and target so that hardware differences get absorbed rather than leaked.

SkVM is an isomorphic transplant of that move. The alignment is worth laying out explicitly:

Classic compiler/VM worldSkVM world
Source codeSkill (SKILL.md plus accompanying scripts and resources)
Target hardwareA combination of some LLM and some Agent Harness
ISA / ABI26 Primitives
Profiler / performance countersThe Target Capability Profile (TCP) produced by the Profiler
AOT compilerThe three-pass AOT Compiler
JITJIT-Boost (hot-path solidification) and JIT-Optimize (self-rewriting content)

Every component in SkVM has a counterpart in an architecture textbook; the mapping holds up under pressure rather than serving as decoration. Once you accept the framing of an LLM as an unusual processor exposing a natural-language ISA, decades of compiler and VM lessons, including their known failure modes, become available for reuse.

The sharpest design decision is where the ISA goes. The ISA became the single most consequential layer in computer architecture because it is the contract that mediates portability against efficiency, and SkVM saw this. Its 26 primitives sit at exactly that point: skill authors write against the primitives, and 26 dimensions proves fine-grained enough to characterize the capability differences that genuinely exist between models without ballooning into an unmaintainable taxonomy.


Unpacking the Technical Principles 🍅

The pipeline in one sentence: the Profiler measures what the target platform can actually do; the AOT Compiler rewrites the skill against those measurements into a version that runs on this platform; the JIT system then keeps raising the skill’s effective capability at runtime, based on what it observes in the field.

1. Profiler: Turning Model Capability from Intuition into Numbers 🍅

The Profiler answers a precise question: for one specific LLM paired with one specific harness, at what tier does each atomic capability sit? It profiles across 26 predefined benchmark dimensions, grouped into four categories:

  • Generation: code and text generation;
  • Reasoning: logical, mathematical, planning, and related reasoning;
  • Tool Use: file and command invocation;
  • Instruction Following: compliance with formats, constraints, and multi-step instructions.

Each primitive is graded into three tiers (L1 / L2 / L3), and the 26 primitives together cover roughly 95% of real skill needs. Every primitive gets a set of micro-benchmarks, scored along one of two paths depending on its nature: primitives with real side effects (such as gen.code.* and tool.*) let the agent actually operate on the environment, after which the file artifacts in workDir are inspected; pure text-generation or reasoning primitives write the model’s answer to response.txt for match-based grading by a scoring script. Everything assembles into a JSON TCP (Target Capability Profile):

{
  "model": "anthropic/claude-sonnet-4-6",
  "adapter": "bare-agent",
  "primitives": {
    "gen.code.python": "L3",
    "reason.math": "L2",
    "tool.file.write": "L3",
    "follow.constraint": "L1"
  }
}

The detail worth underlining: the TCP portrays the model-plus-harness as a whole, never the model in isolation. Give the same model a different visible tool set, a different interaction protocol, or different context management, and it profiles differently. Every downstream decision in SkVM rests on this end-to-end empirical measurement rather than on any context-free capability claim. That is the correct epistemic posture, since context-free claims are exactly what failed the developer in the opening scenario.

2. AOT Compiler: Three Passes That Rewrite a Generic Skill into a Specialized Variant 🍅

The AOT Compiler is the heart of the system. Its input is the original skill plus the TCP; its output is a Variant rewritten for the target platform. The work splits into three passes, each owning a distinct problem.

Pass 1: Capability Gap Filling

The first pass handles the class of failures where the skill assumes a capability the target doesn’t have:

  1. An LLM reads through the skill and extracts an SCR (Skill Capability Requirements): which primitives the skill implicitly assumes the target has, and at what tier;
  2. The SCR is diffed against the TCP to find every gap of the form “requires L2, measured L1,” a step that is pure set arithmetic, with no LLM call;
  3. The gap list goes to the compiler-agent, which decides the rewrite case by case: splitting a complex one-shot reasoning step into multi-step guidance (CoT-ization), converting relative paths to absolute paths to lower the demand tool.exec places on path resolution, or, in the extreme case, swapping in an entirely different implementation route.

The division of labor here deserves a pause. SkVM does not hand the whole pass to the LLM. Only “understanding the skill” and “choosing the rewrite strategy” genuinely need a language model; the gap comparison is deterministic set arithmetic. Deciding which steps go to the LLM and which stay in deterministic code is an engineering sensibility that recurs throughout the system: every step moved out of the LLM is a step that becomes cheap, fast, and reproducible.

Pass 2: Environment Binding

The second pass shifts attention from what’s in the model’s head to what’s installed on the machine:

  1. An LLM distills a dependency list from the skill (Python packages, CLI tools, environment variables, etc.);
  2. A shell script scans the current machine and checks each item;
  3. The output is an idempotent env-setup.sh that runs as a warm-up before the skill starts.

This targets a specific, maddening failure pattern of runtime guessing: the model blindly trying pip install ..., probing which git, testing everything it isn’t sure exists, burning tokens while the actual task hasn’t begun. Resolving dependencies once, ahead of time, converts that open-ended runtime search into a fixed setup cost.

Pass 3: Concurrent DAG Extraction

The third pass is the performance pass:

  1. An LLM decomposes the skill’s workflow into a set of steps with dependency relationships;
  2. Deterministic logic assembles them into a directed acyclic graph;
  3. Three parallelism patterns are mined from this DAG:
    • DLP (Data-Level Parallelism): the same action applied to different data items can run in parallel;
    • ILP (Instruction-Level Parallelism): mutually independent instructions can run in parallel;
    • TLP (Task-Level Parallelism): independent subtasks run concurrently as wholes.

The Runtime schedules execution off this DAG. A substantial share of the paper’s end-to-end speedup comes from this pass: with DLP / ILP / TLP all enabled, average execution time compresses to roughly one-third of the original.

Each pass persists its products to ~/.skvm/proposals/aot-compile/{adapter}/{model}/{skillName}/{passTag}/, none overwriting another, so any single pass can be re-run without starting over. For a compilation process that embeds LLM nondeterminism, per-pass persistence is close to mandatory engineering discipline: a failure in one pass cannot contaminate the results of the passes before it.

3. JIT-Boost: Solidifying Repeated LLM Calls into Deterministic Code 🍅

After AOT, the skill still gets executed over and over, and SkVM notices a plain fact about those executions: many LLM calls are structurally repetitive. A file-analysis skill that makes the model generate a list_directory call before processing each file re-runs full reasoning every single time, indefensible in both tokens and latency, because nothing about that call requires reasoning after the first few occurrences.

JIT-Boost identifies the recurring pattern and bypasses the LLM at the right moment:

  1. Candidate mining: a headless agent reads the entire skill offline, finds call patterns with solidification potential, and writes them to boost-candidates.json, each record carrying keywords, a code signature, a function template, and a parameter list;
  2. Runtime hooking: using the beforeLLM / afterLLM hooks exposed by the Adapter, it instruments before and after each LLM call;
  3. Threshold triggering: the afterLLM hook counts consecutive hits per candidate; once the threshold (default 3) is reached, the next entry into beforeLLM extracts parameters and executes the template directly, and the LLM step is skipped entirely;
  4. Failure fallback: if the solidified path fails M times in a row (default 3), it automatically downgrades back to the normal LLM flow, and the candidate is marked as no longer trustworthy.

The mechanism is an adaptive feedback loop: observe for a while, decide whether to boost, keep watching after boosting, roll back if it doesn’t hold. On high-hit-rate skill fragments the paper reports 19–50× speedups, with tokens and latency shrinking by similar factors. Note the asymmetry the fallback buys: the worst case of a wrong boost is bounded (a few failed attempts, then automatic reversion), while the best case removes the LLM from the loop entirely. That asymmetry is what makes aggressive solidification safe to attempt.

4. JIT-Optimize: Letting Runtime Evidence Rewrite the Skill Itself 🍅

Where JIT-Boost squeezes waste out of the execution path, JIT-Optimize goes one step further and touches the text of the skill itself.

The most interesting part of its design is a recursive structure: the Optimizer is itself a headless agent. Each round runs roughly:

  1. The Engine copies the skill directory to a temporary workspace;
  2. It serializes this round’s “evidence” (execution traces from synthetic tasks, dialogue logs from real bench runs, or an NDJSON of a past session) into the .optimize/ subdirectory;
  3. With the workspace as cwd, it spins up a headless agent (driven by opencode by default);
  4. The agent uses its own read / edit / write / grep / bash tools to modify the skill files;
  5. On finishing, the agent must produce a submission.json whose rootCause field must not be omitted: it has to state the root cause this modification targets;
  6. The Engine snapshots the workspace, computes the diff, and verifies that the agent’s declared changedFiles matches the actual changes.

Why force rootCause to be non-empty? Because without it, the Optimizer degenerates into “tweak something and see if the metric moves,” aimless perturbation. Requiring a root cause forces every modification to carry an explicit diagnosis, and, more importantly, the rootCause history from every round feeds back into the next round’s Optimizer, telling it which paths were already tried and found wanting. What looks like a field-level constraint structurally converts multi-round iteration from a random walk into search with memory.

Each optimization leaves behind a complete Proposal tree:

~/.skvm/proposals/jit-optimize/{harness}/{model}/{skill}/{ts}/
  original/
  round-0/            # baseline
  round-1/ … round-N/ # the full skill directory for each round
  history.json        # rootCause / metric / bestRound for each round
  analysis.md         # human-readable summary

Note that every round-N/ is a complete, independently deployable skill directory, not a diff. A reviewer can accept any single round without swallowing the entire history chain, a deliberate choice that keeps humans able to exercise judgment at the granularity they actually review at.

5. Adapters and Concurrency Scheduling: One “Driver Interface” for Every Harness 🍅

The last piece is the Adapter layer. SkVM wraps five mainstream agent harnesses (bare-agent, opencode, openclaw, hermes, agentclaw) behind a single AgentAdapter interface and requires each to expose the same set of RuntimeHooks: beforeLLM / afterLLM / afterTool / afterRun. This is the real precondition for JIT-Boost’s ability to plug into any harness with zero intrusion: it needs only the hooks, and never has to care whether the implementation behind a hook is Hermes’s session export or OpenClaw’s ephemeral instance.

On the concurrency side, src/core/concurrency.ts implements a hierarchical slot scheduler. It partitions the concurrency budget along three dimensions (adapter × model × task) and permits slot stealing across groups: an idle group can borrow capacity a busy group hasn’t claimed. In large-scale profiling and benching, this scheduler is what decides two very practical questions: whether the cost is affordable at all, and whether the experiment finishes overnight.


Reflections and Thoughts 🍅

After walking through the system, a few points deserve to be discussed on their own.

1. Quantifiable capability is the starting point that makes all the automation possible. Nearly the entire value of computer architecture reduces to one move: finding, between programmer intent and concrete hardware, an intermediate representation formal enough that a compiler can consume upper-layer semantics on one side and absorb lower-layer differences on the other. SkVM transplants exactly this move into the LLM-agent context. Before the 26 primitives, every discussion of “is the model strong enough” lived at the level of intuition: “this model is a bit weak at reasoning,” “that one is steadier at tool calls.” After primitives plus L1/L2/L3, model capability became, for the first time, a line of reason.logic = L1 that code can branch on. Without that layer of quantification, the AOT gap analysis has no ground to stand on, and every downstream mechanism loses its anchor.

2. Embedding an LLM inside a compiler invites nondeterminism into the VM itself. A traditional compiler is a symbolic transformation: identical input necessarily yields identical output. All three of SkVM’s passes embed LLM calls, meaning compiling the same skill twice may well produce different results. SkVM doesn’t pretend otherwise; it contains the problem structurally: each pass’s products persisted separately, JSON at every subsystem boundary validated through Zod schemas, and runtime JIT-Optimize standing by as a corrective backstop. The engineering philosophy amounts to admitting that compile time cannot get it right in one shot, then using persistence, validation, and runtime feedback to keep the resulting uncertainty inside a controllable envelope.

3. Headless agents have effectively become an infrastructure-level primitive. At its two most complex junctures (JIT-Boost’s candidate mining and JIT-Optimize’s Optimizer), SkVM builds no bespoke “code analysis + multi-file editing” toolchain. It hands the job to a headless agent, for the simple reason that this is precisely what agents are good at. The reuse isn’t free: the Optimizer’s ceiling is locked to the driver agent’s ceiling. SkVM’s answer is to factor the driver out explicitly via OptimizeConfig.driver, switchable among bare-agent / opencode / claude-code, the right decoupling, with “the driver will get upgraded” as the default assumption.

4. There is no free lunch between portability and optimality. This is the Achilles’ heel of every middle-layer system: portability and squeezing the last drop out of one specific target are structurally at odds. A prompt hand-written for Claude Sonnet and tuned by a patient human can have a higher theoretical optimum than SkVM’s compiled output, and in niches acutely sensitive to prompt style, generic rewriting may even lose subtle performance. SkVM’s hedges are honest ones: let JIT-Optimize take another corrective pass at runtime, keep the full proposals of every round so a human can pick, and ship a bench system so the gap is measurable rather than argued about. That is the posture a middle layer should hold.

5. What actually makes the system stand up are the unglamorous engineering decisions. The paper-level innovations catch the eye, but reading the code, what makes SkVM runnable and adoptable by strangers is mostly quiet engineering taste:

  • TypeScript + Bun with no build step, so a newcomer can clone and start editing;
  • Contracts locked at every cross-module boundary with Zod schemas, keeping runtime surprises to a minimum;
  • src/adapters/registry.ts as the sole adapter registration entry, so adding a harness touches one file;
  • A globally shared cache directory ~/.skvm/ with an override via SKVM_CACHE, balancing convenience against controllability;
  • The rootCause field forced non-empty, enshrining the discipline of agent self-correction in code rather than in convention.

Each item is unremarkable on its own. Stacked together, they are the watershed between a research prototype and a system other people can actually operate.


Conclusion 🍅

SkVM’s value reads differently depending on which reader you are.

From an engineer’s perspective, it decomposes the long-tolerated problem of skills not working across platforms into a complete toolchain: quantification (Profiler) → static adaptation (AOT Compiler) → continuous runtime tuning (JIT-Boost + JIT-Optimize). A skill stops being an isolated asset that “works okay on my machine, with a particular model, on a particular version” and becomes a distributable, compilable, verifiable, self-evolving software artifact.

From an architecture perspective, it accomplishes a rare paradigm transplant: the entire methodology validated over decades of language virtual machines (capability abstraction, static analysis, dynamic optimization, intermediate representation) systematically moved onto a new computing stack whose compute substrate is the LLM. In this mapping, the 26 primitives correspond to the ISA, the AOT Compiler to LLVM’s front end, and JIT-Boost to V8’s hot-path optimization; JIT-Optimize then goes beyond what any traditional VM does: it lets the “program” rewrite itself during execution. That self-rewriting has no counterpart in the hardware world; it is a genuinely new degree of freedom unlocked only once an LLM sits at the bottom of the stack.

On a longer horizon, the direction most worth watching is dependency relationships and version management between skills. When one skill’s output is consumed as another skill’s input, can SkVM maintain composition compatibility automatically, the way npm or cargo does? Once that step opens up, its role leaps from a tool to distribution-level infrastructure for an entire generation of LLM agents.