odek: The Autonomous Agent Runtime

~11 MB, 4 direct deps, one loop — a ReAct agent built like infrastructure, not middleware.

odek: The Autonomous Agent Runtime ~3K words

odek: The Autonomous Agent Runtime

~11 MB, 4 direct deps, one loop — a ReAct agent built like infrastructure, not middleware.

odek is a Go autonomous agent runtime. One binary. One ReAct loop. No framework layer.

import "github.com/BackendStack21/odek"

agent, _ := odek.New(odek.Config{Model: "deepseek-v4-flash"})
defer agent.Close()
result, _ := agent.Run(ctx, "Refactor this module")

It exposes a ReAct loop — Reason → Act → Observe → Repeat — as a tight for loop with exactly three participants: an LLM client, a tool registry, and a memory system. That is the entire runtime.


The Wrong Abstraction

Every agent framework adds layers. Python agents need CPython (40+ MB), a packaging system (pip), and a dependency tree that imports half the internet. LangChain v0.3 has 217 public dependencies. The startup tax alone is 2–10 seconds — paid before a single token is generated.

odek compiles to a ~11 MB static binary with just 4 direct dependencies — two Go team sub-repos (golang.org/x/net for WebSocket, golang.org/x/term for REPL) and two 21no.de packages (go-mcp for the MCP protocol, go-vector for semantic memory). The full transitive tree adds only 1 more (golang.org/x/sys, Go team maintained) for a total of 5. No ORMs, no HTTP routers, no YAML parsers, no framework.

odek Python agents
Dependencies 4 direct (5 total) 200+ packages
Binary size ~11 MB static 50-200 MB with venv
Startup time ~3 ms 2-10 seconds
Tool interface Four methods (Name, Description, Schema, Call) Class hierarchies + decorators
Sandbox --sandbox flag, wired into the loop Manual Docker setup
Security audit Audit 4 direct deps Full pip dependency tree

All dependencies are under active maintenance by the Go team or 21no.de. There is no transitive framework dependency beyond the golang.org/x/ ecosystem.

The tool interface is four methods:

type Tool interface {
    Name() string
    Description() string
    Schema() any
    Call(ctx context.Context, args string) (string, error)
}

That is the entire abstraction surface. Implement all four, register it, done. No base classes, no decorators, no dependency injection, no chain templates.

The dominant pattern in agent frameworks today is to abstract the LLM and abstract the tools, then provide declarative "chains" and "pipelines" that compose them. This works for demos. In production it breaks because:

  1. LLM output is not deterministic enough for declarative composition. A chain that works with GPT-4 fails with DeepSeek because the token distribution shifts. A pipeline that assumes structured tool output from a semi-structured LLM response is a hypothesis, not an architecture.
  2. Frameworks optimize for the happy path. Error recovery, retry logic, permission gating, and resource cleanup are framework concerns that every framework defers to "the developer can override." In a minimal runtime, there is nothing to override — you write the logic at the call site.
  3. Every abstraction layer is a security boundary. Each class, each serializer, each import is a potential injection surface. An ~11 MB static binary has fewer CVEs than 200 pip packages by construction.

Architecture: One Loop

The ReAct loop is the core of every modern LLM agent. odek executes it directly:

flowchart TD
    U["👤 User Task"] --> MC["📦 Build Context
Memory + System Prompt"] MC --> F{{"for i < max_iter"}} F --> SL["📖 SkillLoader
Inject relevant skills"] SL --> L["🧠 LLM Client
Reason + Decide"] L --> C{"Tool calls?"} C -->|"No"| R["✅ Return final answer"] C -->|"Yes"| T["⚙️ Tool Registry"] T --> S{"Shell
tool?"} S -->|"Yes"| SH["🐳 Docker Sandbox
--cap-drop ALL, --rm"] SH --> TR["📎 Append tool result
↑ messages"] S -->|"No"| OT["🧰 Other tools
read, write, search, patch..."] OT --> TR TR --> F F -.->|"i ≥ max_iter"| TO["⏱️ Timeout error"]

Context (memory, system prompt) is built once before the loop, with memory content refreshed each iteration. On each pass, if a SkillLoader is configured, skills relevant to the user's latest message are injected, then the LLM reasons and either returns a final answer or emits tool calls. Tool calls pass through the registry — shell commands run inside a Docker sandbox, while other tools (read, write, search, patch, browser) execute directly. Results append to the message history and the loop repeats. If the LLM says "I'm done," it stops. If it hits max_iter, it stops with a timeout error.

What this means in practice:

This is not a simplification for marketing. It is a structural property of eliminating the framework layer.

One Binary, Nine Features

If the runtime is minimal, where do the features live? In the binary — compiled in, not imported at runtime.

Three-Tier Persistent Memory

Most agent frameworks have ephemeral memory — context window, conversation history, done. odek implements three persistent tiers backed by a shared vector index:

  1. Facts — agent-managed durable entries (e.g., "the user prefers TypeScript"). Merged on write via go-vector RandomProjections: cosine similarity > 0.7 auto-merges duplicate concepts, < 0.3 auto-adds as new, saving ~80% of LLM calls by avoiding re-embedding.
  2. Session Buffer — auto-appended turn summaries, capped at 20 lines. Prevents long context windows from drowning the current signal in historical noise.
  3. Episodes — LLM-extracted knowledge from past sessions. The agent revisits past runs, distills what mattered, and makes it accessible to future sessions.

The go-vector library is a 21no.de zero-dep package — the entire approximate nearest-neighbor search fits in ~1,600 lines of Go.

MCP: Two-Way

The Model Context Protocol (MCP) lets LLM applications share tools. odek speaks MCP in both directions from a single binary:

The server direction uses the go-mcp library — a 21no.de zero-dep MCP implementation in ~1,200 lines of Go. The client direction is a hand-rolled JSON-RPC 2.0 implementation in ~480 lines of Go (stdlib only). This means odek can both drive the conversation AND be driven by other agents, without an orchestration framework.

Skills with Auto-Learning

Skills are SKILL.md files stored in ~/.odek/skills/ (global) or ./.odek/skills/ (project-local). They load in two modes:

The auto-learning system creates skills from observed behavior — enabled by default (--no-learn to disable):

After a few sessions, the agent gets faster at tasks you do repeatedly — not because the LLM is smarter, but because the skill system reduces the inferential surface area. Import skills from any URI with automatic LLM risk assessment.

Docker Sandbox

odek run --sandbox spawns an isolated Docker container per session: no host mounts beyond the working directory, zero capabilities (--cap-drop ALL), no setuid escalation (--security-opt no-new-privileges), a noexec /tmp, and destruction on exit (--rm). The network is air-gapped by default (--network none) — pass --sandbox-network bridge when a task genuinely needs internet (npm install, go mod download). odek serve runs sandboxed by default (--no-sandbox to opt out); odek run keeps the sandbox opt-in but emits a warning when running unsandboxed. The sandbox is not a post-hoc wrapper — it is wired into the agent loop itself. Every tool call is gated by a unified security layer (dangerous config) that classifies operations as allow/deny/prompt per risk class.

Sub-Agent Delegation

Parallel OS-process sub-agents via delegate_tasks. Each sub-agent is a fresh odek subagent process with its own config, tools, and termination timeout. Up to 8 tasks per call, 3 concurrent by default (configurable via max_concurrency). True OS process isolation — not goroutines, not coroutines — real exec.Command boundaries. Sub-agents stream live logs back to the parent via NDJSON over stdout, surfaced in the Web UI in real-time.

Web UI

odek serve launches a browser-based agent interface with WebSocket streaming, drag-and-drop file attachments (with 5 MB / 10 MB limits), @ resource completion (@file.go, @sess:abc123), and an IDE-style console with per-message token stats. The entire Web UI is a single Go binary serving an HTML page with no build step, no React, no npm — just vanilla JavaScript loaded from /static/.

Telegram Bot

odek telegram starts a long-polling Telegram bot with per-chat session isolation. Each chat gets its own agent instance — separate memory, separate conversation history, separate tool state. Calls are dispatched asynchronously so the polling loop never blocks. Features:

Prompt-Injection-Aware Security

An agent that reads web pages, files, command output, and MCP responses is an agent that ingests attacker-controllable text. odek treats prompt injection as a first-class threat, not a disclaimer. The defense is layered:

Other hardening: secret redaction across all tool output and persisted memory, an API-key handoff to sub-agents via an unlinked file descriptor (never the process environment), and a WebSocket origin allowlist on the Web UI. The wrappers are a structural signal, not a fence — they work best with models trained to honour them (Claude Sonnet 4.6+ / Opus 4.6+).

File Attachments

Attach files to any prompt without spending tool calls on them. Use --ctx/-c on the CLI (--ctx main.go,lib.go for several), @filename inline references in the CLI, REPL, and Web UI (@README.md what does this do?), or drag-and-drop in the browser. File content is injected as context blocks before the task. Under --sandbox, attachments are docker cp'd into the container at /workspace/, so the agent both sees the content and can operate on the physical file with read_file, patch, or shell — no "visible but missing" gap.


Quick Start

# 1. Install (requires Go)
go install github.com/BackendStack21/odek/cmd/odek@latest

# 2. Set your API key — any OpenAI-compatible provider
export DEEPSEEK_API_KEY=sk-...

# 3. Run
odek run "List the files in the current directory"
odek run --session "Refactor this auth module"
odek run --sandbox "npm audit"                    # Docker-isolated, air-gapped by default
odek run --ctx main.go,lib.go "compare these"     # attach files as context
odek run --thinking high "Analyze this codebase"  # reasoning depth: enabled/disabled/low/medium/high
odek repl                          # interactive multi-turn REPL
odek continue                      # continue the most recent session
odek serve                         # browser Web UI (sandboxed by default)
odek telegram                      # start the Telegram bot (long-polling)
odek audit --list                  # review prompt-injection audit logs

Pre-built binaries for Linux and macOS (AMD64 + ARM64) are available on the GitHub releases page. Use odek run --model gpt-4o to switch providers; any endpoint speaking /chat/completions works (OpenAI, Anthropic, DeepSeek, Ollama, vLLM, Groq, Together, Fireworks). Pass --prompt-caching to enable automatic prompt caching for reduced latency and cost on supported models.

For the full user guide — config files, skills management, memory system, Web UI walkthrough, sub-agent orchestration, MCP integration, and the programmatic Go SDK — see odek.21no.de or the CLI Reference.

The Lifecycle Argument

A minimal-dependency Go binary costs less to deploy, less to maintain, and less to audit over its lifetime:

What This Means for the Agent Ecosystem

The agent space is in a framework-maximalist phase — every week brings a new orchestration layer, a new abstraction for "agent memory," a new protocol for tool composition. Most of these frameworks are solving problems the framework itself created: slow startup requires caching, opaque dependencies require lockfiles, class hierarchies require dependency injection, implicit state requires debugging tools.

odek demonstrates that a production-grade autonomous agent can exist without any of this. It does not prove that frameworks are useless — large teams building complex multi-agent systems will always benefit from higher-level abstractions. What it proves is that the default should be minimal. Start with as little as possible. Add abstractions only when the measured cost of not having them exceeds the cost of maintaining them.

The next generation of agent infrastructure will not be built on top of the current frameworks. It will be built from the ground up — in systems languages with small runtimes, predictable performance, and minimal deployment footprints. odek, in Go, is a step in that direction.

Why Not Just Use the LLM API Directly?

Because the loop matters more than the API call. An LLM API returns text. An agent returns results — text that has been read, files that have been written, code that has been tested, decisions that have been executed. The loop that connects "think" to "act" to "observe" is not an implementation detail. It is the entire application.

odek gives you that loop in ~3 ms. Everything else is optional.


Kyberneees, May 2026

References