ReyCode is a terminal-native coding harness with OMP-style sessions: one Primary Assistant for ordinary conversation and explicit task agents for specialized work.
ReyCode owns the agent loop and tool execution itself. Providers stream
responses; ReyCode executes tools through its trusted workspace registry with
durable authorization and owner approval. OpenCode is one provider among
several behind the ReyCode.Provider behaviour. A deterministic simulator
remains available for automated FSM, failure-injection, and Monte Carlo
testing. OpenCode credentials remain in OpenCode; ReyCode discovers its
configured models and stores each agent profile's runtime and model selection.
Active decisions and their acceptance criteria are recorded in DECISIONS.md.
New to Elixir or this codebase? Start with the Documentation Index to find the right doc, or jump straight to the Architecture Guide — it walks through the entire program end-to-end, from keypress to database.
ReyCode is an Elixir application, so you need the Elixir language and the
Erlang/OTP runtime before you can run anything. This project requires Elixir
~> 1.19 and Erlang/OTP 27 or newer (mix.exs pins the constraint; CI
builds against Elixir 1.19.5 on OTP 28.3.1). Check what you have:
elixir --version # should report Elixir 1.19.x on Erlang/OTP 27+macOS (Homebrew):
brew install elixir # the formula installs a matching Erlang/OTP tooLinux: your distro's elixir package is usually years old and won't
satisfy ~> 1.19. Use a version manager instead — mise or asdf:
# mise — install Erlang, then Elixir, then pin both for this directory
mise install [email protected]
mise install [email protected]
mise use [email protected] [email protected]
# or with asdf:
asdf plugin add erlang https://github.com/asdf-vm/asdf-erlang.git
asdf plugin add elixir https://github.com/asdf-vm/asdf-elixir.git
asdf install erlang 28.3.1
asdf install elixir 1.19.5
asdf local erlang 28.3.1
asdf local elixir 1.19.5Prefer OTP 28.x to match CI. Building Erlang from source needs its development
libraries (libssl-dev, libncurses-dev, etc. on Debian/Ubuntu). Some
project dependencies (exqlite, exile) also compile native code, so you
need a C toolchain: Xcode Command Line Tools on macOS, build-essential on
Debian/Ubuntu.
First time using Mix (Elixir's build tool)? Install the Hex package manager to fetch dependencies:
mix local.hex --forceThen verify and start:
elixir --version
mix deps.get
mix run --no-haltmix deps.get
mix run --no-haltStartup opens a clean session home. No prior transcript is shown. The first message creates a fresh durable Session with one Primary Assistant.
Task agents are opt-in durable profiles:
- Run
/agent, give the agent a name and standing responsibility, then select its provider and model. - Run
/task, choose exactly one task agent, and enter a concrete task.
Creating an agent never runs it. Ordinary conversation never invokes task agents. This allows a Release agent, Test agent, and Documentation agent to use different models without automatically multiplying token cost.
-
EnterorCtrl+S: send the current draft -
/orCtrl+P: open the command palette (fuzzy:/resfinds/resume) -
/help: open the deterministic capability reference without invoking a provider -
/neworCtrl+N: start a clean Session -
/resume: pick and reopen a previous Session -
/home: return to the session home -
/agent: create a task agent -
/agents: change an agent's provider/model -
/model: switch the Assistant model in one step -
/task: delegate one task to one task agent
│
!cmd: run a shell command in the workspace; output lands in the transcript@file/#file: attach a file's content to the next message (workspace files only, 512 KB per file, 2 MB total)Tab: move between the prompt and current transcriptCtrl+G: configure agent runtimes and modelsCtrl+T: cycle the themej/k: scroll the focused transcriptCtrl+Q: exit
Each message shows the tool runs it produced as compact one-line blocks
(Tool · read · path · ok), with the tool, its target, and the run's
outcome. Native agents that surface intermediate reasoning render dimmed
activity lines under the message (· note), collapsed behind
+k more activity when the trail grows past three lines.
The header is the ambient status line: ReyCode · model · ⑂ branch · workspace · tok 12.4k/200k, with thinking · 8s while a turn runs. Token
usage is summed from durable provider usage records against the configured
context_budget_tokens budget (REYCODE_CONTEXT_BUDGET_TOKENS).
In the command palette, arrow keys move the selection, Tab completes it, Enter
runs it, and Escape returns to the draft. /cancel stops the current task and
/tools reviews a pending tool approval.
On macOS, event data is stored transactionally in
~/Library/Application Support/ReyCode/rey_code.sqlite3. On first launch, a
legacy ~/.local/share/rey_code/events-v2.ndjson log is imported and retained
with a .pre-sqlite-backup rollback copy.
ReyCode.Application rest-for-one dependency supervision
|-- ReyCode.AgentRegistry unique process registry for Agent workers
|-- ReyCode.EventRegistry duplicate process registry for subscriptions
|-- ReyCode.EventStore transactional SQLite event store
|-- ReyCode.ProviderTaskSupervisor bounded discovery task supervisor
|-- ReyCode.Provider.Catalog transient provider discovery and runtime resolution
|-- ReyCode.Orchestration.Supervisor engine/worker restart boundary
| |-- DynamicSupervisor monitored temporary Agent workers
| `-- ReyCode.Orchestration.Engine room commands, FIFO scheduling, admission control
`-- Breeze.Server terminal room client (TUI only)
Key logical modules (owning the loop and execution, not separate processes):
ReyCode.AgentLoop— durable provider/tool continuation loop per invocationReyCode.ToolRegistry— workspace-trusted tool dispatch and execution
Rooms, messages, turns, invocations, rounds, tool runs, and approvals are durable. The TUI only dispatches commands and renders projected state. Providers consume normalized requests and emit sequenced frames; tool execution is wholly owned by ReyCode.
Squad mode is a static, durable FSM supervised by a single squad leader. It uses one configured seat for each of the twelve roles: Squad Leader, Analyst, Reviewer, Gherkin Author, QA Author, Implementer, Cleaner, Code Reviewer, Hardener, QA Tester, Architect, and Senior Implementer.
The fixed 15-phase flow is:
leader_intake
→ stories → story_review → story_gate
→ specification (gherkin + QA plan) → specification_gate
→ implementation → integration → cleanup → code_review → code_gate
→ hardening → qa_validation → architecture_review → release_gate
The squad leader automatically approves, requests targeted rework, or aborts at
the story, specification, and code gates. Release-gate authority is frozen at
turn start and is explicit: --release auto (the default for headless) keeps
the leader authoritative, while --release wait makes the human owner
authoritative. In the TUI, the leader's release-gate decision is always a
recommendation and the human project owner is authoritative: /release may
approve, return the work to integration, or abort.
Downstream gate rework repeats from integration through validation. The default rework budget is three cycles. When the budget is exhausted, the leader cannot extend it — only the human owner can, by approving another rework at the pending review. Each owner override recomputes the grant from the current count, so repeated overrides keep working without a hard ceiling.
Worker artifacts, owner directives, leader recommendations, human release decisions, provider retries, logical work IDs, and attempts are durable and replayable. The implementer must return code, unit tests, and acceptance tests as three separately validated artifacts.
The squad dashboard and directive controls are headless operational surfaces; they are intentionally absent from the ordinary session TUI.
Run one live squad from the command line. Any provider works — a CLI runtime,
a keyed API profile, or a keyless local one. Use --workspace to choose the
project directory; otherwise the current workspace is used. The --release
flag selects the release authority (auto for leader-authoritative, wait
for owner review):
mix rey_code.squad \
--provider opencode \
--model openai/gpt-5.6-sol \
--workspace "$PWD" \
--release auto \
"Implement the requested change"
# Keyed API profile:
mix rey_code.squad \
--provider deepseek --model deepseek-chat --workspace "$PWD" \
"Fix the flaky test"
# Keyless local profile (Ollama running on this machine):
mix rey_code.squad \
--provider ollama --model llama3 --workspace "$PWD" \
"Summarize the README"Run deterministic Monte Carlo testing without processes or sleeping:
mix rey_code.squad --runs 10000 --seed 42 --failure-rate 0.02 --jitter-ms 25The test-only simulator injects seeded bounded delays, retryable and permanent failures,
crashes, timeouts, malformed structured output, and failures after partial frames.
Use --json for machine-readable summaries.
Each command batch is written as one SQLite transaction. Versioned, checksummed projection checkpoints bound startup tail replay; legacy schema-v2 NDJSON import ignores an incomplete final record without modifying the preserved source. Complete malformed records fail loudly.
Create and configure task agents with /agent and /agents, then run the same
task against an explicit subset without opening the TUI:
mix rey_code.eval \
--agent Luna \
--agent Local \
--task "Run the focused tests and summarize any failures" \
--workspace "$PWD"Each --agent resolves the most recent exact-named task Participant profile
(for example, Luna on OpenCode or DeepSeek and Local on the keyless Ollama
profile). ReyCode copies only those profiles into a fresh durable Session and
runs independent, blind invocations; the automatically-created Primary
Participant is not auditioned. Missing or unavailable profiles produce their
own error rows while configured candidates continue.
The human report has one row per requested name:
Agent Outcome Prompt Completion Wall ms Response
Luna completed 1842 96 3241 Focused tests passed
Local failed - - 211 Provider is unavailable
Use --json for the same fields in machine-readable form. The command exits
zero only when every candidate completes; otherwise it prints the complete
report and exits nonzero. --timeout-ms bounds the audition (default 600000).
Workspace roots and tool approval are identical to ordinary runs: bash and
write still need owner approval, so unattended auditions should use
read-only tasks.
OpenCode and OMP are CLI providers behind the ReyCode.Provider behaviour.
Press Ctrl+G or submit /connect or /models to configure one agent or
every agent in the current Session. ReyCode reports whether each CLI is
installed, checking, configured, or missing rather than treating process
presence as an online state.
If a CLI has no available models, authenticate in another terminal and press
R in the configuration screen:
opencode auth login
# OMP credentials use OMP's own login/configuration flow.Set explicit executable paths when the CLI is not on PATH:
export REYCODE_OPENCODE_PATH=/path/to/opencode
export REYCODE_OMP_PATH=/path/to/ompEach CLI keeps its own credential store authoritative. API keys are never copied into ReyCode's append-only event log. The adapters stream one bounded provider round and retain legacy provider-managed tool execution; ReyCode continues to own orchestration, durability, and approvals.
ReyCode drives OpenAI-compatible chat completion APIs directly — these are
first-class providers, not a fallback. This is where the native agent runtime
lives: each round streams text and usage, returns normalized tool calls, and
ReyCode's own AgentLoop executes those requests through its trusted tool
registry, feeds results back into the provider conversation, and keeps every
round durable. Nothing is chat-only; there is no provider-side recursive tool
loop.
DeepSeek ships as a built-in keyed profile. Set its API key in your
environment and it appears alongside OpenCode in Ctrl+G:
export DEEPSEEK_API_KEY=sk-...Ollama and LM Studio ship as built-in keyless profiles targeting
http://localhost:11434/v1 and http://localhost:1234/v1. They need no
credential: requests through them never carry an Authorization header, not
even an empty bearer token. Start your local server and both appear in
Ctrl+G; discovery queries /models on the same schedule as every other
provider.
ReyCode reads keys from the environment at invocation time only. They are
never written to the event log, the catalog snapshot, or the diagnostics
report. On first use, the /models endpoint is queried once to populate the
model picker; the result is refreshed on the same schedule as OpenCode
discovery and whenever you press R.
Add more OpenAI-compatible providers by configuring profiles, each with a base
URL and the environment variable that holds its key (require_key: false
makes a profile keyless like the built-in local ones):
config :rey_code,
openai_compatible_providers: [
%{
id: :openai,
name: "OpenAI",
base_url: "https://api.openai.com/v1",
key_env: "OPENAI_API_KEY"
},
%{
id: :vllm_local,
name: "vLLM",
base_url: "http://localhost:8000/v1",
require_key: false
}
]Override any profile's base URL at runtime without changing config:
export REYCODE_DEEPSEEK_BASE_URL=https://your-proxy.exampleSome servers reject optional request features with HTTP 400. Profiles carry two
capability flags, both defaulting to true: supports_tools and
supports_stream_options. A strict endpoint can be pinned in profile config or
through the environment without touching files:
export REYCODE_LMSTUDIO_SUPPORTS_STREAM_OPTIONS=false
export REYCODE_VLLM_LOCAL_SUPPORTS_TOOLS=falseUnpinned, ReyCode fails loudly rather than silently degrading: if a server
rejects stream_options, the request is retried once without it and the
working shape is remembered for later rounds; if the server then still rejects
the request while tools were offered, the invocation fails non-retryably with
tool_calls_unsupported, naming the flag to pin. Dropping tools silently to
degrade into chat-only mode never happens.
Fresh Sessions copy the current Assistant and task-agent runtime assignments.
Sending is blocked only when the addressed agent has no ready runtime. The
ReyCode-owned tool loop is active for all providers with the :reycode_tools
capability (OpenAI-compatible and the simulator); OpenCode's stdio adapter uses
the legacy :provider_managed_tools capability.
Providers can only request workspace tools — read, write, edit, bash,
grep, glob, and list. ReyCode executes them itself, one durable tool run
at a time, inside the trusted workspace roots configured via
REYCODE_WORKSPACE_ROOTS; paths outside the roots fail closed. Read-only and
edit tools run without prompting after containment checks; bash and write
always wait for explicit owner approval. Every adapter runs under focused
resource caps — wall-clock timeouts, bounded output sizes, environment
allowlists, CPU-second and open-file limits — declared per tool in
configuration. Unknown tools are denied. See Tool approval
for the approval surface.
Providers can only request workspace tools — read, write, edit, bash,
grep, glob, and list. ReyCode executes them itself, one durable tool run
at a time, inside the trusted workspace roots. read, grep, glob, list,
and edit run without prompting; bash and write always wait for owner
approval first. Unknown tools fail closed.
When a tool needs approval, a banner appears above the current transcript:
tool approval required / write / /tools
Run /tools (or click the banner's command) to open the review modal. For
bash it shows the exact command, working directory, the names of every
environment variable that will be passed through, and a reminder that Bash is
explicit host execution rather than a sandbox. For write it shows the target
path, content size, and a bounded preview. Approve with A, deny with D.
Decisions are addressed to a specific durable tool run ID, so a stale modal can never approve a different request than the one displayed. Waiting approvals consume no concurrency slot, survive an engine restart, and denial finishes the turn as failed without any side effect.
A running assistant can hand bounded subtasks to one of your task agents by
calling the spawn_task orchestration tool with an exact participant name and
a self-contained brief:
spawn_task {"agent": "Luna", "brief": "Run the focused test suite and report failures"}
The child invocation runs in the same turn with its own durable loop, its own
model resolved through the provider catalog, and the same workspace roots and
approval gates as any other run. The parent pauses — zero further provider
rounds — until the child terminates; the child's structured report (output,
usage) then enters the parent's conversation as the tool result. The timeline
shows the child as its own message under the turn, with a delegate · <agent>
row on the parent.
Delegation is depth-bounded: children cannot delegate further (delegation
depth 1), each invocation spawns at most
delegation_max_children_per_invocation children (default 8), and briefs are
capped at delegation_brief_max_bytes (default 16384). Addressing fails
closed — unknown names and primary participants are rejected without
spawning. Delegation itself is auto-allowed; everything the child executes
still passes the normal tool approval model above. Suspension, restart
recovery (child first, exactly once per side), and cancellation are durable.
Inspect production readiness with the doctor task:
mix rey_code.doctor
mix rey_code.doctor --jsonThe report includes runtime and operating system versions, the resolved data and database paths with permissions and available space when the platform exposes it, OpenCode executable/version readiness from the provider catalog, and configured operational limits. It never includes credential names, environment variables, model names, event contents, or prompts. The JSON form is intended for support automation and deployment checks.
Verify the database or create a consistent SQLite backup while ReyCode is stopped:
mix rey_code.store verify
mix rey_code.store checkpoint
mix rey_code.store backup ~/Backups/rey_code.sqlite3The source database must already exist and be a regular file. These maintenance commands never create a missing source; a missing source or directory is reported as an error.
checkpoint builds a versioned, checksummed projection checkpoint for an older
database before its replay tail exceeds the configured startup limit.
Restore requires the backup's generated manifest and refuses to overwrite an
existing database unless --replace is explicit:
mix rey_code.store restore ~/Backups/rey_code.sqlite3 --replaceBuild the unsigned arm64 release archive on Apple Silicon:
./scripts/build_macos_arm64_release.shThe local release stores its SQLite database under
~/Library/Application Support/ReyCode and rotating owner-only logs under
~/Library/Logs/ReyCode. Configure trusted workspaces with a comma-separated
REYCODE_WORKSPACE_ROOTS value. Public distribution still requires Apple
Developer ID signing and notarization.
mix check
mix coverage
MIX_ENV=dev mix dialyzermix check runs formatting, warning-strict compilation, Credo strict (including
the five custom checks in credo_checks/: no String.to_atom/1, no
HTTPoison/Tesla/Finch, :httpc.request must set a timeout, no bracket access on
structs, and no integer indexing of lists), and the full ExUnit suite. The suite
includes quality guardian tests (test/quality/) that fail when production code
violates project invariants, and security-boundary property tests
(test/property/) for canonical path resolution, JSON normalization, and
hashing.
mix coverage runs the suite once and writes cover/lcov.info via ExCoveralls,
failing if total coverage drops below the 75% floor (coveralls.json). On pull
requests, CI additionally requires 90% coverage of executable lines changed from
the base branch:
mix quality.changed_coverage --base "$BASE_SHA" --lcov cover/lcov.info --threshold 90CI also enforces per-function CRAP scores (CC^2 x (1 - coverage)^3 + CC),
which keeps clean-code complexity from drifting: functions scoring above 30
must shrink or gain tests, existing offenders may never worsen, and new
offenders fail the build. Legacy offenders are pinned in a committed ratchet
baseline (quality/crap-baseline.json):
MIX_ENV=test mix quality.crap --lcov cover/lcov.info --baseline quality/crap-baseline.json
MIX_ENV=test mix quality.crap --write-baseline # regenerate after improvements only