跪拜 Guibai
← All articles
LLM · JavaScript · Architecture

Violin: A Zig-Powered Coding Agent That Lays Bare the While-Loop at AI's Core

By 得物技术 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Most coding agents ship as opaque monoliths inside editors. Violin's layered, language-split design and its insistence that every agent type collapses into one loop gives engineers a concrete blueprint for building or debugging their own agent systems, especially in performance-sensitive environments where a garbage-collected runtime is a liability.

Summary

Violin is a toy coding agent built from scratch in Zig, deeply modeled on the open-source Pi agent's three-layer architecture: model adaptation, agent runtime, and product layer. The engine runs as a Zig daemon that communicates with a Python TUI client over TCP and JSON-lines, a deliberate split that lets each language handle what it does best—Zig for the performance-sensitive loop and memory management, Python for rapid terminal UI development. The project adapts both OpenAI and Anthropic APIs behind a unified Model.complete() interface, implements a tool registry with six built-in tools, and adds session persistence via JSONL with tree-structured message history that supports branching and rollback.

Context-window management falls to a compaction system that summarizes old messages when tokens exceed a 100K threshold, keeping the most recent 10 messages intact. A Lua plugin system hooks into EventBus callbacks for agent start, tool execution, and session compaction, letting users intercept dangerous commands or inject system instructions without touching the core loop. Resources like AGENTS.md project rules and SKILL.md skill files are parsed from the filesystem and formatted as XML injected into the system prompt.

The project's explicit goal is pedagogical: by building a coding agent from zero, the author demonstrates that customer-service agents, data-analysis agents, and workflow orchestrators are all just variations of the same ask-model-then-execute-tools loop. Several gaps remain—tool definitions aren't serialized into the model request yet, Lua plugins run without permission isolation, and the ACP protocol is unimplemented—but the architecture is complete enough to validate the central claim.

Takeaways
Every AI agent—coding, customer service, data analysis, workflow—reduces to a while-loop that alternates between calling a model and executing tools until no tool call remains.
A max_turns limit is essential; models can loop indefinitely on tool calls without a safety valve.
Tool results must be appended back into the message history so the model can see outcomes before deciding the next step.
Violin uses Zig for the agent engine and Python for the TUI client, communicating over TCP with JSON-lines—a split that keeps the engine fast and memory-safe while letting the UI leverage Python's ecosystem.
Model adaptation wraps OpenAI and Anthropic APIs behind a single Model.complete() interface, hiding differences in tool-call structures and streaming protocols.
Session storage uses JSONL with a tree structure, supporting message branching and rollback—each message carries a parent_id pointing to its predecessor.
Context compaction kicks in at 100K tokens, summarizing old messages into a 500-token digest while preserving the 10 most recent messages intact.
Lua plugins hook into EventBus callbacks at agent start, tool execution, and session compaction, enabling command interception and system-prompt injection without modifying the core loop.
Project rules (AGENTS.md/CLAUDE.md) and skills (SKILL.md) are loaded from the filesystem, parsed for YAML frontmatter, and injected as XML into the system prompt.
The tools parameter in buildJson is not yet serialized, so the model currently receives no tool definitions—a known gap.
Lua plugins have no permission isolation and can execute arbitrary operations.
The ACP protocol is not implemented; the project uses a custom TCP + JSON-lines protocol instead.
Conclusions

Violin's most useful contribution is demystifying the agent: the architecture diagram and code walkthrough make explicit what commercial agents obscure behind polished UIs.

Choosing Zig over TypeScript for the engine is a bet that memory control and predictable performance matter more than ecosystem convenience for the agent loop itself—a tradeoff most agent builders haven't explored.

The decision to split the engine (Zig) from the client (Python) over a network socket, rather than embedding everything in one process, is architecturally cleaner but introduces latency and serialization overhead that a production agent would need to measure.

Using Lua for plugins is a pragmatic minimum-viable choice—500KB runtime, decades of embedding precedent—but the lack of sandboxing means the plugin system is currently a security hole, not a feature.

The compaction strategy (character-count/4 as a token estimate, 100K threshold, keep-last-10) is deliberately crude; the author explicitly calls out that precision isn't needed for compaction decisions, which is a refreshingly honest engineering tradeoff.

Violin inherits Pi's tree-structured sessions but doesn't yet expose branching in the UI, leaving a powerful capability latent in the data model.

The project validates a specific claim: that understanding one coding agent's internals transfers directly to customer-service, data-analysis, and workflow agents because they all share the same loop structure.

Concepts & terms
Agent Loop
The core while-loop of any AI agent: call the model, check if it wants to invoke a tool, execute the tool if so, append the result to the conversation, and repeat until the model produces a final answer without a tool call.
EventBus
A publish-subscribe event system where the agent loop emits events (turn start, token delta, tool call) and any subscriber—plugins, UI, logging—can register callbacks to react without coupling to the loop's internals.
Context Compaction
When a conversation's token count exceeds the model's context window, older messages are summarized into a short digest by calling the model itself, preserving key information while freeing space for recent messages.
JSONL Session Storage
A line-delimited JSON format where each line is a complete JSON object. The first line stores session metadata; subsequent lines store individual messages with parent_id pointers, enabling tree-structured conversation history with branching and rollback.
ArenaAllocator
A Zig memory-allocation strategy where all allocations come from a single arena and are freed at once when the arena is destroyed, avoiding per-object deallocation overhead and use-after-free bugs.
SSE (Server-Sent Events)
A streaming protocol where the server pushes data to the client as it becomes available, using `data:` lines (OpenAI) or `event:` lines (Anthropic). Enables real-time token-by-token display in agent UIs.
ACP (Agent Communication Protocol)
A proposed standard protocol for agent-client communication. Violin chose not to implement it due to complexity and lack of Zig libraries, opting instead for a simpler custom TCP + JSON-lines protocol.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗