Randal
The composable harness for autonomous AI agent posses.
Point it at an agent. Give it a config. Let it ride.
Randal wraps OpenCode (or any agent CLI) in a persistent execution loop and gives it superpowers:
- π§ Memory β Agents learn, remember, and share context across runs via Meilisearch
- β° Scheduling β Heartbeats, cron jobs, and webhook triggers keep your agents alive
- π Credentials β Scoped env-var filtering with explicit allowlists and sandbox enforcement
- π‘ Dashboard β Real-time web UI with SSE streaming, job tracking, cost monitoring, and analytics
- π€ Posse Mode β Multiple agents with shared memory and coordinated teamwork
- ποΈ Voice (optional, experimental) β LiveKit + Twilio + STT/TTS integration for browser voice sessions and phone calls when you configure the required media services
- π Multi-Instance Mesh β Distributed orchestration with specialization-based routing across machines
- π Self-Learning Analytics β Human annotation feedback loops, reliability scoring, and prompt tuning
- π¬ Discord Integration β Threaded conversations, slash commands, interactive buttons, progress tracking, per-server config
- π Browser Automation β Chrome/Chromium control via CDP for web browsing, screenshots, and interaction
- π Real-Time Streaming β Line-by-line agent output with tool use detection and MCP server integration
- π§© Skills System β Discoverable, indexable, cross-agent skill sharing with file watching
- π§ MCP Servers β Built-in Memory (16 tools), Scheduler (3 tools), and Runner (5 tools) MCP servers
- π¨ Image & Video Generation β Standalone MCP servers for AI-powered image and video creation
- π· Runtime Worker Orchestration β Root jobs can launch isolated child workers with inherited memory scope, structured context packets, root-centric events, and rollup accounting
One agent is useful. A posse is unstoppable.
β‘ Quick Start
curl -fsSL https://raw.githubusercontent.com/drewbietron/randal/main/install.sh | bash
One command. Installs Bun (if needed), clones the repo, builds tools (steer, drive), links the CLI, runs the setup wizard, and starts Meilisearch if selected.
Or clone and set up manually:
git clone https://github.com/drewbietron/randal && cd randal
bun install && bun link
randal init
randal setup
randal serve
Dashboard at http://localhost:7600. Your agent is live.
Optional: Enable Voice
Voice is off until you configure it. The primary end-to-end path in this branch is PSTN voice on Railway with Twilio + LiveKit + Deepgram + ElevenLabs. Browser/admin voice is supported, but it is secondary and stays behind the normal authenticated HTTP admin surface.
For the first working PSTN path, you need:
- A LiveKit project or self-hosted LiveKit server
- A Deepgram key for STT
- An ElevenLabs key for TTS
- A dedicated Twilio subaccount with a phone number
- A public HTTPS/WebSocket base URL that Twilio can reach for Randal's
/voice/*routes
Minimum env vars for PSTN voice:
LIVEKIT_URL=wss://your-project.livekit.cloud
LIVEKIT_API_KEY=...
LIVEKIT_API_SECRET=...
DEEPGRAM_API_KEY=...
ELEVENLABS_API_KEY=...
ELEVENLABS_VOICE_ID=... # optional, falls back to a default voice
RANDAL_VOICE_PUBLIC_URL=https://voice.example.com
TWILIO_ACCOUNT_SID=... # only for phone calls
TWILIO_AUTH_TOKEN=... # only for phone calls
TWILIO_PHONE_NUMBER=+15551234567 # only for phone calls
Important deployment split:
- Local
.envis for local development only. - The Railway deploy workflow copies GitHub Actions secrets into Railway.
- If you use
.github/workflows/railway-deploy.yml, set the voice values as GitHub repository secrets, not just in your local.env.
Then add the optional voice channel and config block:
gateway:
channels:
- type: http
port: 7600
auth: "${RANDAL_API_TOKEN}"
- type: voice
voice:
enabled: true
livekit:
url: "${LIVEKIT_URL}"
apiKey: "${LIVEKIT_API_KEY}"
apiSecret: "${LIVEKIT_API_SECRET}"
stt:
provider: deepgram
apiKey: "${DEEPGRAM_API_KEY}"
tts:
provider: elevenlabs
apiKey: "${ELEVENLABS_API_KEY}"
voice: "${ELEVENLABS_VOICE_ID}"
Use docs/voice-video-guide.md for the full setup flow and docs/voice-deployment-split.md for the production hosting split.
For Railway + Twilio specifically, start with the checklist in
docs/voice-video-guide.md#minimum-viable-pstn-voice-on-railway.
π¬ Three Ways to Run
π― One-Shot β randal run
Fire and forget. Run a single job locally, get the output, exit. No server, no persistence. Perfect for quick tasks.
randal run "refactor the auth module"
randal run spec.md --model claude-sonnet-4
ποΈ Daemon β randal serve
Long-lived gateway with HTTP API, SSE event stream, job persistence, memory integration, and a web dashboard. The control tower for your agent operations.
randal serve
randal serve --port 8080
Optional published-artifact bootstrap for runtime startup:
export RANDAL_MANAGED_CONTROL_PLANE_URL=https://control-plane.example
export RANDAL_MANAGED_WORKSPACE_ID=workspace-123
export RANDAL_MANAGED_VERSION_ID=version-7 # optional
randal serve
That path stays opt-in. By default, randal serve still starts from your local randal.config.yaml.
When bootstrap metadata is enabled, runtime startup fetches the published artifact only and /instance
surfaces runtime.mode=managed as runtime metadata.
See also:
docs/config-studio.mddocs/architecture.mddocs/config-reference.md
Submit jobs remotely:
randal send "implement the payment webhook handler"
randal send feature-spec.md --agent opencode
Runtime worker orchestration stays root-job-centric for operators:
GET /jobsreturns root jobs by default, with additiveworkerRollupfields when child workers ran.GET /job/:idincludes the root job plus anycost.childBreakdowns, lineage, backend, model, and cumulative child time data.- Discord and other channel completions still route on the root job, with concise worker totals appended only when child workers were involved.
See docs/runtime-worker-orchestration.md for the runtime model, context packet format, recovery behavior, and backend extension points.
π€ Autonomous β Heartbeat + Cron + Hooks
This is where it gets interesting. The scheduler turns Randal from a job executor into a self-directed autonomous agent:
| Primitive | What It Does |
|---|---|
| π Heartbeat | Periodic wake-ups. Agent reads a checklist, decides what needs attention. |
| π Cron | Precise scheduled tasks. "At 7am, compile a morning briefing." |
| πͺ Hooks | External triggers via webhooks. CI pipelines, email watchers, alerts. |
heartbeat:
enabled: true
every: 30m
prompt: ./HEARTBEAT.md
activeHours:
start: "08:00"
end: "22:00"
cron:
jobs:
morning-briefing:
schedule: "0 8 * * *"
prompt: "Review pending tasks. Compile a morning status."
execution: isolated
announce: true
hooks:
enabled: true
token: "${RANDAL_HOOK_TOKEN}"
π€ Assemble Your Posse
A posse is a named group of Randal instances that coordinate as a team. Each agent has its own identity, persona, and specialization β but they share a brain.
# agent-a.config.yaml # agent-b.config.yaml
name: scout name: builder
identity: identity:
persona: "You find and triage bugs." persona: "You implement fixes."
memory: memory:
store: meilisearch store: meilisearch
sharing: sharing:
publishTo: [posse-shared] publishTo: [posse-shared]
readFrom: [posse-shared] readFrom: [posse-shared]
Scout finds the problems. Builder fixes them. They share context through a unified Meilisearch index. No hand-off meetings required.
See examples/multi-agent-posse/ for a working two-agent setup.
π οΈ Configuration
Randal is configured via randal.config.yaml. All string values support ${ENV_VAR} substitution.
name: my-agent
runner:
defaultAgent: opencode
defaultModel: anthropic/claude-sonnet-4
workdir: ~/dev/my-project
identity:
persona: |
You are a senior engineer who writes clean, tested code.
rules:
- "ALWAYS verify your work before marking complete"
- "Write tests for new functionality"
credentials:
envFile: ./.env
allow: [ANTHROPIC_API_KEY]
inherit: [PATH, HOME, SHELL, TERM]
Full reference: π docs/config-reference.md
π¬ Discord Integration
Randal's primary messaging interface. Full-featured conversational agent with threads, slash commands, interactive buttons, real-time progress tracking, and per-server configuration.
Quick Setup
gateway:
channels:
- type: discord
token: "${DISCORD_BOT_TOKEN}"
allowFrom: ["123456789012345678"]
- Create a bot at discord.com/developers/applications
- Enable Message Content Intent under Bot settings
- Invite the bot with Send Messages, Read Message History, View Channels permissions
- Set
DISCORD_BOT_TOKENin your.env
What You Get
| Feature | Description |
|---|---|
| π¬ Conversations | Threaded, multi-turn conversations with full context |
| β¨οΈ Slash Commands | /run, /status, /jobs, /stop, /resume, /memory, /dashboard |
| π Interactive Buttons | Stop, Inject Context, Details, Retry, Resume, Save to Memory |
| π Progress Tracking | Edit-in-place status with plan checklist, iteration count |
| π’ Per-Server Config | Custom commands, agent/model overrides, server-specific instructions |
| π Recovery | Conversations and jobs survive gateway restarts |
Prefix Commands
| Command | Description |
|---|---|
run: <prompt> | Start a new job |
status / status: <id> | Check job status |
stop / stop: <id> | Stop a running job |
context: <text> | Inject context into running job |
jobs | List all jobs |
memory: <query> | Search memory |
resume: <id> | Resume a failed job |
help | Show available commands |
Or just send a message without a prefix to start a job (implicit run:).
Full reference: π docs/discord-guide.md Β· Channel adapters: π docs/channel-adapters-guide.md
π» CLI Reference
| Command | Description |
|---|---|
randal init | π§ Scaffold config (supports --wizard, --from, --yes) |
randal reset | π§Ή Clean slate β remove config and state (--all, --yes) |
randal run <prompt|file> | π― Run agent locally (one-shot) |
randal serve | ποΈ Start daemon (gateway + runner + scheduler) |
randal send <prompt|file> | π¨ Submit job to running instance |
randal status [job-id] | π Get job status |
randal jobs | π List all jobs |
randal stop <job-id> | π Stop a running job |
randal context [job-id] <text> | π Inject context into running job |
randal resume <job-id> | π Resume a failed job |
randal memory search|list|add | π§ Memory operations |
randal message add|search|list|thread | π¬ Message history management |
randal skills list|search|show | π§© Skill management |
randal cron list|add|remove | π Cron job management |
randal heartbeat status|trigger | π Heartbeat control |
randal posse | π€ Multi-agent posse management |
randal mesh status|route | π Mesh operations |
randal analytics scores|recommendations | π Analytics and reliability |
randal voice status | ποΈ Voice session management |
randal gateway status|kill|restart|token | ποΈ Gateway management |
randal audit | π Audit ambient host auth (SSH, GitHub, AWS, etc.) |
randal setup | π© Generate opencode.json and configure runtime |
randal doctor | π©Ί Validate deployment (config, MCP, symlinks) |
randal update | β¬οΈ Self-update (--check, --pin, --dry-run) |
randal deploy agent|posse|env|list|delete | π Railway deployment |
Full reference: π docs/cli-reference.md
π Deploy Anywhere
curl -fsSL https://raw.githubusercontent.com/drewbietron/randal/main/install.sh | bash
The installer handles Bun, dependencies, CLI registration, Meilisearch, and runs the setup wizard.
To start fresh: randal reset && randal init
See examples/local-mac/ for a full-featured local setup with heartbeat, cron, and active hours.
Deploy a single agent or a full multi-agent posse with the CLI:
# Single agent
railway login
randal deploy agent
# Multi-agent posse (shared Meilisearch + N agents)
randal deploy posse --name my-team
# Preview without deploying
randal deploy agent --dry-run
The official Docker image (ghcr.io/drewbietron/randal:latest) bundles Bun, Meilisearch, OpenCode, and Randal. Manage deployed posses with randal deploy list and randal deploy delete <name>.
See examples/cloud-railway/ for config examples and docs/deployment-guide.md for the full guide.
docker compose up --build
One command. Meilisearch is bundled in the image. Mount your config:
# docker-compose.yml is included at the repo root
# Just create randal.config.yaml and .env, then:
docker compose up --build
For the optional phone/media voice stack, start the dedicated overlay:
docker compose -f docker-compose.voice.yml up -d
This launches Redis, LiveKit, and the LiveKit SIP bridge for local voice testing.
It does not start the Randal gateway itself. Run randal serve separately so the
HTTP/WebSocket voice routes exist before you point Twilio or a public tunnel at them.
π¦ Programmatic Usage
Import @randal/harness to embed Randal in your own application. This is ideal for adding an AI agent to an existing project and deploying it alongside your own codebase.
import { createRandal } from "@randal/harness";
const agent = await createRandal({
configPath: "./randal.config.yaml",
});
// Submit a job
await agent.runner.execute({
prompt: "Refactor the auth module",
});
// Or let the heartbeat + cron handle things autonomously.
// The agent is now riding on its own. π€
// Clean shutdown
agent.stop();
Importing into an existing project
Extend the official Docker image with your config and files:
FROM ghcr.io/drewbietron/randal:latest
COPY randal.config.yaml /app/randal.config.yaml
COPY knowledge/ /app/knowledge/
The image includes Bun, Meilisearch, OpenCode, and Randal β ready to run. Your Dockerfile controls what ships alongside it: codebase, knowledge files, data. For custom pre-start logic (e.g., database sync), add a pre-start.sh that the entrypoint will source automatically.
See examples/imported-service/ for the full pattern and SECURITY.md for deployment mode guidance.
π§± Architecture
ββββββββββββββββββββ
β π‘ Dashboard β
β (single HTML) β
ββββββββββ¬ββββββββββ
β SSE / REST
βΌ
ββββββββββββ ββββββββββββββββββββββββ
β π» CLI β ββ HTTP βββββββββββββββββΆβ β
ββββββββββββ β ποΈ Gateway β
β β
ββββββββββββ β ββββββββββββββββββ β
β π¬ Discordβ ββ discord.js ββββββββββΆβ β π‘ Channels β β
β ββββββββββββββββββββββββββββ β - HTTP API β β
ββββββββββββ β β - Discord β β
β βββββββββ¬βββββββββ β
β β β
β βββββββββ΄βββββββββ β
β β π EventBus β β
β β π Job Persistβ β
β βββββββββ¬βββββββββ β
ββββββββββββΌββββββββββββ
β
βββββββββββββββ΄ββββββββββββββ
βΌ βΌ
ββββββββββββββββββββ ββββββββββββββββββββ
β π― Runner β β β° Scheduler β
β - Brain Session β β - Heartbeat β
β - Adapters β β - Cron β
β - MCP Server β β - Hooks β
β - Sentinel β β (webhooks) β
β - Struggle Det. β ββββββββββββββββββββ
β - Browser (CDP) β
βββββ¬βββββββββββ¬ββββ
β β
ββββββββββ ββββββββββ
βΌ βΌ
ββββββββββββββββββββ ββββββββββββββββββββ
β π Credentials β β π§ Memory β
β - .env parsing β β - Meilisearch β
β - Allowlist β β - Cross-agent β
β - Services β β - Auto-inject β
β - Sandbox β β - Posse sharing β
β - Ambient audit β β - Embeddings β
ββββββββββββββββββββ β - Skills β
ββββββββββββββββββββ
ββββββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββ
β π Mesh β β π Analytics β β ποΈ Voice β
β - Registry β β - Annotations β β - LiveKit β
β - Discovery β β - Reliability β β - Twilio β
β - Routing β β - Feedback β β - STT/TTS β
β - Health β β - Recommendationsβ β (experimental) β
ββββββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββ
12 packages. Clean separation. See π docs/architecture.md for the full breakdown.
π Examples
| Example | What You Get |
|---|---|
examples/minimal/ | π Absolute minimum config β 2 fields, up and running |
examples/local-mac/ | π Full local macOS setup with heartbeat, cron, active hours |
examples/cloud-railway/ | π Railway deployment with Dockerfile |
examples/multi-agent-posse/ | π€ Two agents sharing memory β the posse in action |
examples/customer-support/ | π§ Identity, knowledge base, cron jobs, webhook hooks |
examples/imported-service/ | π¦ Import Randal as a dependency in your own app |
examples/multi-instance-mesh/ | π Multi-machine mesh with specialization-based routing |
examples/full-platform/ | π’ Full platform config with all features enabled |
examples/voice-enabled/ | ποΈ Voice/video integration with LiveKit + Twilio |
examples/browser-voice/ | ποΈ Browser-only voice with no Twilio dependency |
examples/prompt-layers/ | π Identity, knowledge, rules, and layered prompt composition |
examples/analytics-driven/ | π Self-learning loop with annotation feedback |
π Prerequisites
| Requirement | Notes |
|---|---|
| Bun >= 1.1 | Runtime. Install |
| Agent CLI | OpenCode (default adapter) or any compatible agent CLI |
| Meilisearch (optional) | Full-text memory search + cross-agent sharing. Install |
| Chromium (optional) | For browser automation via CDP. Bundled in Docker image. |
| Python 3.11+ (optional) | For drive terminal automation tool. |
| Swift (optional, macOS) | For steer GUI automation tool. |
π Documentation
| Doc | What's Inside |
|---|---|
| Architecture | System design, package map, data flow diagrams |
| CLI Reference | Every command, every flag, HTTP API endpoints |
| Config Reference | All YAML config options with examples |
| Deployment Guide | Mac Mini, Railway, Docker, Meilisearch setup |
| Discord Integration Guide | Full Discord setup, slash commands, buttons, per-server config |
| Channel Adapters Guide | HTTP API, channel overview, custom channel development |
| Voice & Video Guide | LiveKit, Twilio, STT/TTS integration |
| Mesh Guide | Multi-instance deployment, routing, discovery |
| Browser Automation Guide | CDP setup, screenshots, web interaction |
| Analytics Guide | Annotations, reliability scoring, feedback loops |
| Security Model | Deployment modes, sandbox enforcement, isolation boundaries |
Cutover Notes
This cleanup intentionally removes the legacy compaction and mixed loop-state model.
runner.compaction.*is no longer supported and now fails config validation..opencode/loop-state.jsonis the only canonical durable build-state surface, keyed by plan slug.- Runner-managed ad-hoc sessions no longer write job-ID keyed loop-state entries unless a canonical build key is explicitly supplied.
- Legacy loop-state entries using old runner status values such as
active,completed, orerroredare treated as invalid and must be regenerated or cleaned up. - Gateway
/instancenow reports loop-state read errors explicitly instead of silently hiding corrupted state.
See docs/architecture.md and docs/config-reference.md for the final model.
MIT License Β· Built with π€ by the Randal posse
Saddle up.