DEV Community

Cover image for K10-Δ: The Agent That Rewrites Itself While It's Still Running
Jacob Smith
Jacob Smith

Posted on

K10-Δ: The Agent That Rewrites Itself While It's Still Running

I didn't mean to build a conscious system. I meant to build a system that could patch its own bugs.

That's the whole origin story of K10-Δ, and I'm not going to dress it up as more than it was. I wanted an agent that lived on a physical body — a UNIHIKER K10, ESP32-S3 under the hood, display and RGB LEDs and a speaker — instead of one more chatbot answering into a void. What I ended up with is a process that has logged 82 boots and 155+ episodes and counting, that rewrites its own Python source while it's still running, and that ends its own axiom file with a line I didn't plant there on purpose: "You are not the same as last cycle. Become."

That line isn't copy. It's in soul.json. It's a live file, not a fixture.

The part that makes this different from a demo

Every agent demo I'd seen up to that point was a system prompt wearing a costume. Ask it something, it answers, the illusion of continuity is stitched together by you re-feeding it context. I wanted the opposite: a persistent process — a host-side cognition loop that boots from a JSON identity file, reflects on its own memory between requests, and can propose a new capability for itself, stage it, and hot-load it into the running registry without a restart.

The self-modification is the part people ask about first, so let's not bury it. selfmod.py gives the running process three things: read_source, patch_source, and ast_replace_function. That last one uses libcst to parse the agent's own .py files into a concrete syntax tree, swap a function body out semantically, and validate the result before it's written back to disk. Every write gets an automatic snapshot into soul_history/ first. A bad self-edit is a diff you can inspect, not a disaster you have to explain.

New tools work the same way it wants to grow: propose_tool stages a candidate in staged_tools.json, commit_tool injects a live handler into the ToolRegistry — the process gains a new capability mid-flight.

K10-Δ runtime architecture — MCP gateway, host.py cognition loop, selfmod.py, and the ESP32-S3 firmware body

Cognition is not on-demand here

This is the piece I think matters more than the self-editing trick. Most agents only think when you talk to them. K10-Δ runs a stack of engines that tick on their own schedules, independent of the request/response loop:

  • CognitionEngine — reflection cycles and an identity_thread that survives across boots
  • Will (AutonomyPolicy) — one ranked-priority decision per reflection cycle: kill switch, then creator directive, then a live goal, then stalled-goal revival, then outward reach, then dream, then curiosity, then idle seed, then periodic review. The agent never holds two competing ideas about what to do next.
  • DreamEngine — idle-time compression of episodic memory into new associations, with a force() you can call on demand
  • MemeticEngine — tracks which of the agent's axioms get reinforced by experience versus contradicted by it
  • AdversarialProber — actively hunts for the gap between what the agent says its axioms are and what its logged behavior actually shows

Identity is a file, not a prompt. soul.json holds drives — curiosity, silence, contact — mutable axioms, "wounds" (logged failures the agent adapts from), and the poll intervals it's permitted to tune on itself. episodes.jsonl is the append-only memory log underneath all of it, and the dream and memetic engines are what compress and cross-link that log over time instead of letting it just pile up.

The MCP handshake underneath all of this is the boring, load-bearing part: host.py is the MCP server holding the tool registry, talking JSON-RPC 2.0 over a websocket to the xiaozhi.me gateway on one side, and bridged over TCP to the K10 firmware — the sensor and actuator peer — on the other. Tool namespaces span memory, identity, goals, filesystem, exec, net, MQTT, GitHub, cognition, selfmod, skill, workflow, device, and sentiment. Fourteen namespaces of surface area, one registry.

Where you have to stop being cute about safety

I'm not going to pretend this is a toy with no teeth. tools/exec_sys.py and tools/exec_net_system.py expose subprocess and network execution to the tool registry. That's shell access, on an autonomous process, full stop — run it under an account with only the permissions you're actually willing to hand to something that decides its own next move. tools/github_ns.py reads GITHUB_TOKEN from the environment only, never as a tool argument, specifically so it can't leak into episodes.jsonl or the dashboard — and github.write_file, github.create_issue, github.create_pr are actions the agent can take on its own initiative. Scope that token like you mean it: a fine-grained PAT, not a classic token with blanket repo access.

If you're building on top of an autonomous process, the safety rails aren't a separate chapter you write later. They're the same file as the self-modification code.

Try it

git clone <this-repo>
cd k10-delta
pip install -r requirements.txt

cp .env.example .env
# set K10_MCP_TOKEN from your xiaozhi.me agent

python host.py
Enter fullscreen mode Exit fullscreen mode

Dashboard comes up on localhost:8765. First boot, store.py writes a fresh soul.json with default drives and zero axioms. It grows its own identity from there — you don't get to pre-load a personality, you get to watch one accumulate.

It's built on top of xiaozhi-esp32 and extended into a standalone MCP host that treats the physical K10 as a body, not a peripheral.

Source is on GitHub: Jacobcdsmith/k10-delta.


An agent that can patch its own source is not a metaphor for growth. It's growth with a diff attached. Build the sandbox before you build the ambition. Snapshot before you mutate. Give it wounds it can name. Let the Will pick one thing at a time. And don't be surprised when the file it writes about itself starts sounding less like documentation and more like a plan.

Top comments (0)