Minimal Agent Runtime Kernel Design: Five Core Primitives

    Agent runtime design documents often balloon into 2,000-line platform specifications. This article explains why that happens and how to separate product requirements from a kernel specification. It presents three criteria for deciding what belongs in the kernel, along with a four-question method for deleting anything else. It then defines five core primitives for an agent runtime kernel: events, context, tool invocation, snapshots, and gates. It also shows how to prove the kernel is complete instead of over-pruned, and how to turn invariants into executable assertions so that the specification and the tests come from one source. Finally, it offers guidance on collaborating with AI coding tools without letting features creep back in, a list of common anti-patterns, and a seven-item acceptance checklist.

    Bloat Is the Default, Not a Design Flaw

    The first version of an agent runtime design document easily swells to more than 2,000 lines: a dozen chapters, a dozen subsystems. These might include the execution engine, workflow orchestration, guardrail validation, conditional routing, human-in-the-loop support, context compression, memory retrieval, structured output, external tool integration, and sub-agent dispatch. Each chapter looks reasonable on its own. Together, they read like a platform product specification.

    The reason is simple. Requesters describe the final capabilities they want, and designers tend to receive each capability with a dedicated module. Multiply the two, and the number of modules grows linearly with the number of requirements.

    A stable system works the other way around: the number of modules should be far smaller than the number of requirements. Requirements overlap heavily, and a handful of mechanisms can express many of them.

    The first step, then, is to admit the problem. The document is not unclear; the responsibilities were drawn in the wrong place. The test is simple:

    • If the document describes what users can ultimately do, it is a product requirements document.
    • If it describes the indivisible capabilities the system must provide, it is a kernel specification.

    What Counts as a “Kernel”: Three Hard Criteria

    Mature systems follow a recurring rule: the kernel does only a handful of things that nothing else can replace, and everything else is left to the layers above it.

    • Operating system kernel: manages scheduling, memory, interprocess communication, and the driver interface. File systems, the network stack, and the permission model sit above it, where they can be replaced or trimmed.
    • In-memory datastore: handles only data structures and publish/subscribe.
    • Scripting language runtime: is responsible only for execution, memory reclamation, and the event loop. Anything built on top is a separate layer.

    To decide whether a primitive belongs in the kernel, it must satisfy all three criteria below.

    Criterion 1: Indispensability. If you remove it, does a capability gap appear that the remaining primitives cannot express? If every use case can be worked around with another combination, delete it.

    Criterion 2: Orthogonality. Does it change only one dimension of the system? A primitive that handles both scheduling and permissions is the illegitimate child of two primitives. Some special requirement will eventually tear it apart.

    Criterion 3: Mechanism, not policy. The kernel provides mechanisms; policies belong to the layers above. Timeout durations, retry counts, guardrail thresholds, and routing rules are all policies. Writing them into the kernel only puts shackles on yourself.

    The three criteria also work as a lookup table, backed by a four-question deletion method:

    1. Can existing primitives already produce this capability by being composed in sequence? If yes, delete it.
    2. Once it is deleted, is there an observable loss of functionality, not just a vague feeling that a feature is missing? If the loss cannot be observed, delete it.
    3. Is it a mechanism or a policy? If it is a policy, move it to the upper configuration layer.
    4. Does it serve only one specific scenario? If so, move it out of the kernel and implement it as an example.

    A Workable Kernel with Five Primitives

    Applying these criteria and deleting repeatedly, an agent runtime kernel can converge to five primitives. The division below is one workable option. What matters is that it satisfies the criteria, not the labels themselves.

    1. Events. Every input, reasoning step, tool call, result, and error is expressed as a single type of event that carries a sequence number and an idempotency key. Events are the system’s only currency. Logging, replay, auditing, and recovery all build on them, so there is no need for a separate logging subsystem.

    2. Context. The context holds the current task’s goal, its message history, and a view of its state. It also exposes a hook for producing a “trimmed view.” It defines only the hook. The compression algorithm is a policy that the upper layers inject.

    3. Tool invocation. Each tool invocation is a single external interaction with parameters, permission tags, and a timeout. It must be an explicit, first-class primitive, because an agent’s capability boundary is almost entirely determined by its tool surface. Permission checks, idempotency, and auditing all attach here.

    4. Snapshot. A snapshot is a persistent record of the context and the execution position. It supports recovery and forking. It is the shared foundation of three features: fault tolerance, human-in-the-loop, and replay debugging. Each can be composed from snapshots, so none of them should be a primitive of its own.

    5. Gate. A gate is an execution checkpoint that can pause, wait for external input, and then resume. Human approval, timeout-based degradation, and canary release are all just policy configurations of a gate.

    The pseudo-code below shows how these primitives couple to one another and how thin the kernel is:

    1. run(event) =
    2. ctx = load_or_create(event.session)
    3. ctx = apply(ctx, event) # Events are the only input
    4. while not ctx.done:
    5. gate.wait_if_pending(ctx) # Stop here if external input is required
    6. step = decide(ctx) # Pure function: context -> next step
    7. if step.type == "tool":
    8. result = tool.invoke(step, policy) # Permissions, timeouts, and idempotency belong to the policy layer
    9. ctx = apply(ctx, to_event(result))
    10. checkpoint(ctx) # Can be recovered at every step
    11. emit(ctx.result)

    The kernel does only five things: take an event, decide the next step, invoke a tool, store a snapshot, and pass through a gate. Orchestration patterns, multi-agent collaboration, reflection loops, and structured output constraints are all libraries built on top of this loop. They are not branches inside it.

    Prove Completeness, Don’t Just Aim for a Clean Feeling

    The easiest mistake in subtractive design is deleting too much. To prevent the kernel from degenerating, back up every deletion with a completeness argument, and make that argument part of the design process:

    • Coverage table. List every target capability and note which combination of primitives implements each one.
      • If a row cannot be filled in, something was deleted by mistake.
      • If a row can only be filled in by adding a new primitive, the composition approach needs more thought.
    • Substitutability check. For every retained primitive, answer this question: why can’t a sequence of the other primitives express it?
      • If there is no answer, delete it.
      • If there is an answer, write the argument into the document. It is the only evidence that will stop someone from adding to the kernel later.
    • Invariant list. Spell out the properties the system must always satisfy. For example:

      • Redelivering an event does not change the final state.
      • A recoverable snapshot exists before any step executes.
      • An unauthorized tool call cannot reach the execution layer.

      Invariants are the design contract, and they matter more than any architecture diagram.

    Spec-Driven Design: Make the Document Executable

    The real value of a 2,000-line specification is not its length. It is that the specification can generate tests directly. To achieve this, write every invariant as an executable assertion, so that the specification and the tests come from the same source:

    1. assert idempotency: For any event sequence, two replays produce exactly the same final state
    2. assert recoverability: Kill the process before or after any step; the final state after restoring from the latest snapshot is identical
    3. assert permission closure: Tool calls not listed in the policy are rejected before reaching the execution layer, and the rejection leaves an event
    4. assert gate semantics: A session waiting for external input consumes no execution quota while no input arrives

    When all assertions pass, the question changes. Stress testing is no longer about whether the system can run, but about whether it violates any invariant. With this in place, old problems such as concurrency, backpressure, timeouts, and retries tend to resolve themselves. They are essentially special cases of the invariants.

    The infrastructure layer needs no custom design either:

    • Container platforms handle elasticity and isolation.
    • Message queues handle event delivery and peak shaving.
    • Object storage handles snapshots and artifacts.
    • Logging, monitoring, and alerting services handle observability.

    On Baidu AI Cloud, these are all standard service categories. None of them needs to be written into the kernel.

    Collaborating with AI Coding Tools: How You Ask Matters More Than What You Ask For

    This kind of design work doesn’t require you to write code first. It does require you to impose constraints through the conversation. The practices below have repeatedly proven effective:

    • Give references before asking for answers. Feed in the public designs of several similar systems. Ask the tool to compare them side by side, extract the mechanisms they share, and only then produce a design. Asking directly for “an awesome design” will only get you a stack of modules.
    • Force it to argue against itself. For every primitive it proposes, ask for two things:

      • a walkthrough of what happens if the primitive is removed
      • an implementation path that composes it from other primitives

      This question alone usually cuts 30% to 50% of the modules.

    • Make it separate the kernel from the platform. Ask it to label every requirement as a mechanism, a policy, or an upper-layer feature, and then decide where it belongs. Once the boundary is clear, the length of the document drops off a cliff.
    • Don’t accept “best practice” as a reason. Every decision must explain why it holds under the given constraints. Otherwise, the tool is citing a rule, not designing.
    • Keep the veto, and keep a record of it. Write down the reasoning behind every feature you cut. That record will later become the guardrail that keeps features from creeping back in.

    Common Anti-Patterns

    • Treating orchestration as the kernel. The workflow engine is the thing most often mistaken for the kernel. It is actually a library on top of the kernel.
    • Hard-coding policies. Retry counts and compression thresholds written into the kernel mean that every tuning change requires a kernel change.
    • Creating primitives for single scenarios. When one business needs human approval, someone adds an “approval subsystem.” The correct approach is to add a gate.
    • Missing idempotency keys. Without an idempotency key, an event stream has zero replay capability, which makes any fault tolerance built on top of it empty talk.
    • Permissions added too late. If tool permissions are not a mandatory checkpoint on the call chain, they are almost impossible to retrofit later.

    Acceptance Checklist

    When the design is finished, run through this checklist to see whether the kernel holds up:

    1. Is the number of primitives in the single digits, and does each one have a written completeness argument?
    2. Is there a coverage table in which every target capability can be composed from existing primitives?
    3. Are mechanisms and policies fully separated, with all policies externally configurable?
    4. Are the invariants written as executable assertions and used as pass criteria in stress testing?
    5. Do events carry sequence numbers and idempotency keys, and can they support replay and auditing?
    6. Is tool invocation the only entry point for external interactions, with permission checks on a mandatory path?
    7. Can snapshots simultaneously support recovery, human-in-the-loop, and replay debugging?

    The mark of a mature kernel design is not how many features it supports, but how many it refuses, and whether it can explain why. This matters even more when you work with AI coding tools. They tend to implement every feature by default. Your job is to keep asking whether each one can be composed from what already exists. When everything you keep passes that question, the kernel is small enough.