Quick start
Zell has no Node or npm runtime requirement. Artifact size and linkage vary by release target; building from source requires zig 0.16+.
# 1. Clone and build (ReleaseSmall keeps it small) git clone https://github.com/Jackallink/zell && cd zell zig build -Doptimize=ReleaseSmall # artifact lands in zig-out/bin/zell ls -lh zig-out/bin/zell # measure the target you will ship
First prompt
# DeepSeek (the currently documented public provider) export DEEPSEEK_API_KEY=sk-... ./zig-out/bin/zell --model deepseek-v4-flash -p "What is 2+2?" # pick a model / attach file references ./zig-out/bin/zell --model deepseek-v4-flash -p "explain this code" @src/main.zig # enter the interactive TUI ./zig-out/bin/zell
DEEPSEEK_API_KEY for the current public endpoint. --api-key is available, but environment injection is preferable on shared hosts because process arguments can be visible. Do not use an OpenAI credential or treat --provider openai as supported until a release explicitly documents an OpenAI endpoint.CLI reference
Usage: zell [flags] [message] [@file …]. With no arguments it enters the interactive TUI; with -p or a mode flag it uses the corresponding output channel.
| Flag | Description |
|---|---|
-p, --print | print mode: one-shot prompt, answer straight to stdout |
--mode json | JSON mode: stdout emits only the NDJSON event stream (see protocol v1) |
--mode rpc | RPC mode: reads command JSON on stdin, mixes responses and events on stdout (IDE / embedding) |
--json | equivalent to --mode json |
-c, --continue | resume the last session |
--name SESSION | session name; sessions persist to .zell/sessions/<id>.jsonl |
--provider P | experimental wire-format selector for the fixed DeepSeek endpoint; it is not a generic provider or OpenAI endpoint selector |
--model M | model identifier; defaults are currently mode-dependent (TUI: deepseek-chat, non-interactive modes: deepseek-v4-flash), so pass it explicitly |
--api-key K | API key (or environment variable) |
@file | file reference, contents merged into context; repeatable. The current baseline reads with the invoking user's authority, not a confined workspace capability. |
-Denable-wren=true build plus --enable-dynamic-extensions, are trusted/test-only, and are not a public SDK or Guarded feature.TUI slash commands
$ zell ─── > Zell | deepseek-chat ─── Welcome to Zell! Type /help for commands, /quit to exit. > /help /help list commands /settings not implemented /model show current model (switching not implemented) /quit exit (/q)
Four modes
One core, four exits: a human in the terminal, scripts pulling data, CI assertions, IDE embedding. All four modes live in the same binary — no daemon.
Interactive
Streaming interactive TUI; slash commands set the pace. Built for a human in the loop.
One-shot prompt, answer straight to stdout — the plainest and most composable.
JSON
stdout emits only the NDJSON event stream — scripts and assertions consume it directly.
RPC
stdin commands / stdout responses + events, built for IDE and embedding scenarios.
JSON mode: event types
$ zell --mode json -p "say hi" | jq -s 'map(.type)' [ "zell.protocol","agent_start","message_start","message_end","turn_start", "message_start","message_update",…,"message_end","turn_end", "agent_end","done" ]
RPC mode: one prompt
$ printf '%s\n' '{"id":"1","command":"prompt","text":"hi"}' | zell --mode rpc {"type":"zell.protocol","version":1,"capabilities":[…]} {"type":"response","command":"prompt","success":true,"id":"1"} {"type":"agent_start","turn":0} {"type":"message_start","turn":0,"role":"user"} …event stream… {"type":"agent_end","turn":1} {"type":"done","turn":1}
Protocol v1
Version 1: breaking changes bump the version; additive event types do not. Every channel (--mode json / --mode rpc) speaks NDJSON — one JSON object per line, UTF-8. The first line is the zell.protocol handshake; the following agent-event objects carry their turn number.
Event model
Agent events share type (event name), turn (turn number, starting at 0), plus type-specific fields. The handshake and RPC response objects have their own shapes and do not carry turn.
| type | fields | semantics |
|---|---|---|
agent_start | — | agent run starts |
agent_end | — | agent run ends (turn is the next number after the last turn) |
turn_start | — | a turn starts (one assistant response + tool calls/results) |
turn_end | text = stop_reason | turn ends: stop / tool_calls / length / end_turn |
message_start | role | message lifecycle starts (every message) |
message_update | text = delta | assistant streaming delta (streaming only) |
message_end | role | message lifecycle ends |
tool_execution_start | text = tool name, extra = args | tool begins executing |
tool_execution_end | text = tool name, extra = result | tool finished executing |
tool_execution_update | text = stdout delta | bash partial output (streaming only) |
error | text | error (HTTP failure, truncated tools, …) |
done | — | stream terminator |
Typical single-turn order
agent_start message_start(user) message_end(user) turn_start message_start(assistant) message_update ×N message_end(assistant) turn_end agent_end done
With tools, one turn: after message_end(assistant) and before turn_end, each tool emits one tool_execution_start / tool_execution_end pair. If stop_reason=length (output truncated), tool calls in that turn don't run and are reported with an error result, then a retry turn follows.
Real capture (DeepSeek)
{"type":"zell.protocol","version":1,"capabilities":["tool_execution_update","prepare_next_turn_snapshot","default_parallel","thinking_level","steer_follow_up","structured_session","rpc_continue"]}
{"type":"agent_start","turn":0}
{"type":"message_start","turn":0,"role":"user"}
{"type":"message_end","turn":0,"role":"user"}
{"type":"turn_start","turn":0}
{"type":"message_start","turn":0,"role":"assistant"}
{"type":"message_update","turn":0,"text":"Hi"}
{"type":"message_update","turn":0,"text":" there"}
{"type":"message_update","turn":0,"text":"!"}
…streaming deltas…
{"type":"message_end","turn":0,"role":"assistant"}
{"type":"turn_end","turn":0,"text":"stop"}
{"type":"agent_end","turn":1}
{"type":"done","turn":1}
RPC commands
One command JSON per stdin line; stdout mixes response and events (both NDJSON); id correlates request/response (optional).
| command | semantics |
|---|---|
prompt | start a prompt, then stream events |
steer | inject direction: running → applies next turn; idle → queued for next run |
follow_up | follow-up: running → queued until the agent finishes; idle → same as prompt |
abort | cancel the running agent (takes effect at the next event/tool boundary) |
get_state | query model, session id, busy, steering/follow-up modes |
get_messages | fetch session messages (each content truncated to 1000 chars) |
set_steering_mode | steer queue mode: all / one-at-a-time (default) |
set_follow_up_mode | follow-up queue mode, same as above |
continue | resume the session without a new user message |
set_thinking_level | thinking level: off / low / medium / high |
clear_steering | clear the pending steer queue |
clear_follow_up | clear the pending follow-up queue |
clear_queues | clear both queues |
{"type":"response","command":"<cmd>","success":false,"error":"…"}. Turn cap MAX_TURNS=25.Sessions & resume
Sessions persist as structured message JSONL at .zell/sessions/<id>.jsonl. -c / --continue restores the selected session as provider history; --name selects its id.
$ zell --name oncall -p "continue the last fix" $ zell --name oncall -c -p "push one more layer" $ ls .zell/sessions/ oncall.jsonl proto1.jsonl demo1.jsonl c13.jsonl … $ head -3 .zell/sessions/proto1.jsonl {"role":"user","content":"continue the last fix"} {"role":"assistant","content":"…"} {"role":"tool","content":"…","tool_call_id":"call_0","is_error":false}
A session file is not the protocol event stream and is not an immutable audit log. It is a local conversation record used for resume.
Security
The current Zell baseline is a local host tool: it runs with the invoking user's authority. It is not a sandbox, a secret-isolation mechanism, or a hardened filesystem boundary.
- The current
bashtool has host process, filesystem, and network authority; it is not confined. - A child shell can inherit the host environment. Environment/CLI key input is configuration, not a key-isolation guarantee.
- No built-in MCP is a product-surface choice, not a sandbox boundary.
- A small binary can reduce dependencies, but it does not replace authorization or OS enforcement.
- Session JSONL is an operational conversation record, not tamper-proof or immutable audit evidence.
Principles
Zell is a minimal coding-agent harness implemented in Zig. Public documentation distinguishes verified behavior from planned work and states security boundaries explicitly.
We do
- Less is More — minimal interaction surface, no feature stacking
- Zig single binary; no Node.js or npm runtime requirement
- print / JSON / RPC / TUI — four modes, one binary
- Resumable structured session messages, with their scope stated plainly
We don't
- Daemons or background processes
- Built-in MCP or permission-prompt stacks
- Marketing roadmap items as shipped capabilities
- Sacrificing the single binary's size for an "ecosystem"
FAQ
What runtime does it need?
No Node or npm runtime is required. Build from source with zig 0.16+; binary size, linkage, and supported targets are release-specific measurements.
Which models are supported?
DeepSeek is the current documented public endpoint. Defaults are currently mode-dependent: deepseek-chat in the TUI and deepseek-v4-flash in non-interactive modes. Pass --model explicitly. OpenAI is not yet a documented public endpoint.
Where does the API key go?
For the current public endpoint, use DEEPSEEK_API_KEY. --api-key is supported but can expose a secret through process arguments on shared hosts; environment injection is preferable. This is not a secret-isolation claim.
How do I cross-compile?
zig build -Doptimize=ReleaseSmall -Dtarget=x86_64-linux-musl produces a Linux target. Validate each target and release artifact before describing it as supported or statically linked.
Where are sessions stored?
.zell/sessions/*.jsonl — structured local conversation messages used for resume, not the protocol v1 event sequence.
How is Zell designed?
Zell is implemented in Zig around a small, inspectable interaction surface. Behavior is measured against documented scope, while Security First is a deliberately documented future boundary.