Skip to main content
KRAIT

Your First Evolution

Watch KRAIT write, validate, and merge its own code through the self-evolution cycle.

How Self-Evolution Works

KRAIT agents evolve by proposing changes to their own skill modules, validating those changes through a security and testing pipeline, and merging them into the running system — all without human intervention. The entire cycle is supervised by OTP, so a failed evolution never crashes the agent.

The Evolution Cycle

When the agent determines it needs a new capability, it follows a four-stage process:

  1. Propose — The agent generates a new or modified skill module using its configured LLM provider.
  2. Validate — Narsil performs static analysis, security rule checking, and taint tracking on the proposed code.
  3. Test — The proposed skill is compiled and executed inside a sandboxed Docker container against generated test cases.
  4. Merge — If all checks pass, the new skill is hot-loaded into the running OTP application via code replacement.

Triggering Your First Evolution

Start the agent in development mode and give it a task that requires a skill it does not yet have:

krait dev

Then, in the chat interface at http://localhost:4000:

> Summarize the contents of a CSV file at /tmp/sample.csv

The agent will recognize it lacks a CSV-parsing skill and begin the evolution cycle. You can watch the process in real time in the terminal output.

Watching the PR Process

Every evolution is recorded as an internal pull request. You can inspect past evolutions with:

krait evolutions list
krait evolutions show evo-00a3f1

Each evolution record includes the proposed diff, Narsil validation results, test output, and merge status.

Narsil Validation

Narsil enforces the security rules defined in your Configuration. During validation it runs:

If any check fails, the evolution is rejected and the agent receives structured feedback explaining why, so it can refine its next attempt.

The Merge Flow

After validation and testing pass, KRAIT uses Erlang/OTP hot code loading to swap in the new module:

# Simplified view of the merge step
defmodule Krait.Evolver do
  def merge(%Evolution{module: mod, bytecode: beam}) do
    :code.purge(mod)
    :code.load_binary(mod, ~c"#{mod}.beam", beam)
    Logger.info("Evolution merged: #{mod}")
  end
end

The previous version is kept in memory by the BEAM VM, so the agent can roll back instantly if the new code raises exceptions at runtime.

Next Steps