README.md

Quickstart

Install, configure, and run Randal from the repository README.

Randal

The composable harness for autonomous AI agent posses.

Point it at an agent. Give it a config. Let it ride.

License: MIT Built with Bun TypeScript v0.1.0


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:

  1. A LiveKit project or self-hosted LiveKit server
  2. A Deepgram key for STT
  3. An ElevenLabs key for TTS
  4. A dedicated Twilio subaccount with a phone number
  5. 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 .env is 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.md
  • docs/architecture.md
  • docs/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 /jobs returns root jobs by default, with additive workerRollup fields when child workers ran.
  • GET /job/:id includes the root job plus any cost.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:

PrimitiveWhat It Does
πŸ’“ HeartbeatPeriodic wake-ups. Agent reads a checklist, decides what needs attention.
πŸ“… CronPrecise scheduled tasks. "At 7am, compile a morning briefing."
πŸͺ HooksExternal 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"]
  1. Create a bot at discord.com/developers/applications
  2. Enable Message Content Intent under Bot settings
  3. Invite the bot with Send Messages, Read Message History, View Channels permissions
  4. Set DISCORD_BOT_TOKEN in your .env

What You Get

FeatureDescription
πŸ’¬ ConversationsThreaded, multi-turn conversations with full context
⌨️ Slash Commands/run, /status, /jobs, /stop, /resume, /memory, /dashboard
πŸ”˜ Interactive ButtonsStop, Inject Context, Details, Retry, Resume, Save to Memory
πŸ“Š Progress TrackingEdit-in-place status with plan checklist, iteration count
🏒 Per-Server ConfigCustom commands, agent/model overrides, server-specific instructions
πŸ”„ RecoveryConversations and jobs survive gateway restarts

Prefix Commands

CommandDescription
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
jobsList all jobs
memory: <query>Search memory
resume: <id>Resume a failed job
helpShow 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

CommandDescription
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

ExampleWhat 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

RequirementNotes
Bun >= 1.1Runtime. Install
Agent CLIOpenCode (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

DocWhat's Inside
ArchitectureSystem design, package map, data flow diagrams
CLI ReferenceEvery command, every flag, HTTP API endpoints
Config ReferenceAll YAML config options with examples
Deployment GuideMac Mini, Railway, Docker, Meilisearch setup
Discord Integration GuideFull Discord setup, slash commands, buttons, per-server config
Channel Adapters GuideHTTP API, channel overview, custom channel development
Voice & Video GuideLiveKit, Twilio, STT/TTS integration
Mesh GuideMulti-instance deployment, routing, discovery
Browser Automation GuideCDP setup, screenshots, web interaction
Analytics GuideAnnotations, reliability scoring, feedback loops
Security ModelDeployment 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.json is 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, or errored are treated as invalid and must be regenerated or cleaned up.
  • Gateway /instance now 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.