ZELL.docs
Docs

ZELL Documentation

One binary, four modes, one protocol. The commands, event model, and limits below are checked against the repository implementation; the full protocol source is docs/protocol/v1.md.

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+.

quickstart · build
# 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

quickstart · run
# 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
NOTE
Use 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.

FlagDescription
-p, --printprint mode: one-shot prompt, answer straight to stdout
--mode jsonJSON mode: stdout emits only the NDJSON event stream (see protocol v1)
--mode rpcRPC mode: reads command JSON on stdin, mixes responses and events on stdout (IDE / embedding)
--jsonequivalent to --mode json
-c, --continueresume the last session
--name SESSIONsession name; sessions persist to .zell/sessions/<id>.jsonl
--provider Pexperimental wire-format selector for the fixed DeepSeek endpoint; it is not a generic provider or OpenAI endpoint selector
--model Mmodel identifier; defaults are currently mode-dependent (TUI: deepseek-chat, non-interactive modes: deepseek-v4-flash), so pass it explicitly
--api-key KAPI key (or environment variable)
@filefile reference, contents merged into context; repeatable. The current baseline reads with the invoking user's authority, not a confined workspace capability.
EXPERIMENTAL
Dynamic Wren extensions are off by default. They require a -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 · interactive
$ 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.

tui · default

Interactive

Streaming interactive TUI; slash commands set the pace. Built for a human in the loop.

zell
Implemented
print

Print

One-shot prompt, answer straight to stdout — the plainest and most composable.

zell -p "say hi"
Implemented
json

JSON

stdout emits only the NDJSON event stream — scripts and assertions consume it directly.

zell --mode json -p "say hi"
Implemented · protocol v1
rpc

RPC

stdin commands / stdout responses + events, built for IDE and embedding scenarios.

printf '…' | zell --mode rpc
Implemented · protocol v1

JSON mode: event types

zell --mode json
$ 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

zell --mode rpc
$ 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.

typefieldssemantics
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_endtext = stop_reasonturn ends: stop / tool_calls / length / end_turn
message_startrolemessage lifecycle starts (every message)
message_updatetext = deltaassistant streaming delta (streaming only)
message_endrolemessage lifecycle ends
tool_execution_starttext = tool name, extra = argstool begins executing
tool_execution_endtext = tool name, extra = resulttool finished executing
tool_execution_updatetext = stdout deltabash partial output (streaming only)
errortexterror (HTTP failure, truncated tools, …)
done—stream terminator

Typical single-turn order

protocol · sequence
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)

zell --mode json --name proto1 -p "say hi" · real capture
{"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).

commandsemantics
promptstart a prompt, then stream events
steerinject direction: running → applies next turn; idle → queued for next run
follow_upfollow-up: running → queued until the agent finishes; idle → same as prompt
abortcancel the running agent (takes effect at the next event/tool boundary)
get_statequery model, session id, busy, steering/follow-up modes
get_messagesfetch session messages (each content truncated to 1000 chars)
set_steering_modesteer queue mode: all / one-at-a-time (default)
set_follow_up_modefollow-up queue mode, same as above
continueresume the session without a new user message
set_thinking_levelthinking level: off / low / medium / high
clear_steeringclear the pending steer queue
clear_follow_upclear the pending follow-up queue
clear_queuesclear both queues
NOTE
Uniform failure shape: {"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.

sessions
$ 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 bash tool 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.
STATUS
Security First is planned, not shipped. The approved direction is a small two-mode design: a Trusted mode and a future Guarded mode with one fixed workspace root, one execution gate, a provider broker, and an OS-restricted worker. Guarded remains unavailable until an approved platform backend is verified; it is intended not to require Docker installation, but that is not a current protection claim. Use external OS/container isolation when isolation is required today.

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"
RULE
Security first: an invariant belongs in the core/OS boundary; workflow choices belong in extensions or files. The absence of built-in MCP or a permission popup is not, by itself, a security guarantee.

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.