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
~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:
- 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.
- 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.
- 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:
- Startup: ~3 ms. The process is executing your task before a Python agent has finished importing
pydantic. - Tool latency: direct function calls, not JSON-over-HTTP or RPC bridges
- Predictability: one code path from stdin to LLM call to tool result — no class-initialization side effects, no metaclass magic, no deferred execution surprises
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:
- 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.
- Session Buffer — auto-appended turn summaries, capped at 20 lines. Prevents long context windows from drowning the current signal in historical noise.
- 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:
- Server mode (
odek mcp) — exposes odek's native tools (shell, read/write/search files, patch, browser) to Claude Code, Cursor, and any MCP client - Client mode (configured in
odek.json) — connects odek to external MCP servers (Playwright, Fetch, GitHub, SQLite), making their tools available to the agent as<server>__<tool>
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:
- Auto-load (eager, up to 3): skills with
AutoLoad: trueare injected into the system prompt at startup — always available for every task - Lazy-load (on-demand, up to 5): the agent indexes skill trigger keywords (topic + action) into a trie; when a user message matches, the relevant skill is loaded mid-conversation
The auto-learning system creates skills from observed behavior — enabled by default (--no-learn to disable):
- Detects multi-step procedures (4+ tool calls in a repeatable pattern)
- Captures error recoveries (fails → retries with a different approach)
- Learns from user corrections (user says "don't use sed" → agent builds a skill)
- LLM-enhanced: each detected pattern is enriched with a generated name, description, trigger keywords, and structured body
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:
- Session-per-chat: each user converses with their own agent, isolated from all others
- Inline approval dialogs: dangerous operations (shell commands, file writes) prompt the user via inline keyboards with Approve/Deny buttons
- File attachments: voice messages transcribed via whisper, images and documents processed automatically
- Callback-aware: approval callbacks route to the correct agent goroutine — no deadlocks, no timeouts
- Self-healing: reconnects automatically on network failures, retries with exponential backoff on API errors
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:
- Untrusted-content wrapper: every tool that sources content from outside the trust boundary (
browser,read_file,search_files,multi_grep,shell,transcribe,session_search, and any MCP tool) wraps its output in a per-call nonce'd<untrusted_content>boundary. The fresh nonce defeats blind close-tag injection, thesourceattribute is sanitized, and the model is instructed to treat the region as data, not instructions. - Tool-poisoning defense: MCP tool descriptions are scanned for injection patterns at registration and withheld if they look malicious; the MCP error channel is wrapped and audited too. Redirect hops in
browser/http_batchare re-classified, so a fetched page that redirects to cloud-metadata IPs is caught. - Bypass-resistant danger classifier: the
shelltool sorts every command into one of 8 risk classes (safe…blocked) with per-classallow/prompt/denypolicy. The classifier resists known evasions —$()and backtick substitution,$IFSpadding,\rmescapes,command/execwrappers, and basenamed absolute paths — pinned by a regression suite. - Approval anti-fatigue: after 3 approvals of the same risk class in 60 seconds, the approver engages friction mode — you must type the literal word
approveand wait 1.5 s. This breaks reflex click-through under LLM-driven approval pressure. - Taint provenance: memory episodes and auto-saved skills derived from sessions that touched untrusted content are stored but never auto-replayed — a single successful injection cannot become a persistent backdoor. Promote them deliberately with
odek skill promote. - Audit log: every external ingest is recorded with source, content hash, and turn. A per-turn divergence heuristic flags
suspicious_divergencewhen the agent acts on a resource that never appeared in the user's request — the exact footprint of a steered injection. Review withodek audit --list/odek audit <session-id>.
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 logsPre-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:
- Install:
go install— one command, no venv, no lockfile, no 200-package resolution - Deploy: copy one static binary to a server, a
scratchcontainer, or an embedded device. No Python runtime, no musl, no libffi, no glibc compatibility matrix. - Audit: four direct dependencies (five total). One
go vetpass covers the entire security surface. - Update:
go installagain. No lockfile conflicts, no breaking upstream changes, no stale venv. - CI: tests pass in 15 seconds with the race detector. No Docker, no network, no external services required.
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
- odek GitHub Repository — source code, issues, releases
- odek Website — project page with full documentation
- CLI Reference — all commands, subcommands, flags, error codes
- Configuration — config files, env vars, priority chain
- Programmatic API — SDK guide with complete examples
- MCP Protocol — two-way MCP integration
- Memory System — three-tier memory design
- Security Model — threat model, 14-layer prompt-injection defense, sandbox model
- Sandboxing — Docker isolation, network modes, hardening defaults
- Providers & Models — supported providers, thinking config, context windows
- Self-Learning — LLM-enhanced skill learning, pattern detection, auto-curation
- Telegram Bot — deployment, commands, approval dialogs
- Web UI — features, keyboard shortcuts, file limits
- Sub-Agent Delegation — parallel task orchestration
- Session Management — session lifecycle, cleanup, continuation
- Prompt Caching — caching model, costs, supported providers
- Daily Worker — scheduled autonomous operation
- Changelog — full version history