docs/deployment-guide.md

Deployment guide

Deployment options and operational guidance.

🚀 Deployment Guide

Randal runs anywhere Bun runs. The core OSS deployment primitive is randal serve: one long-lived gateway/runner process, backed by your chosen persistence and exposed through whatever host or reverse proxy you prefer. This guide covers a local Mac Mini, a generic self-hosted/server model, Railway as one supported cloud example, and importing Randal as a library into an existing project.

The marketing/docs site is deployed separately as static Astro output on Vercel. See Public Site Deployment for Vercel previews, PostHog, domains, and public-site setup. That path is not required for self-hosting randal serve or deploying a cloud runtime.


📋 Prerequisites

All deployments require:

  • Bun >= 1.1
  • **OpenCode agent CLI installed and on PATH (opencode)
  • Either provider API keys or a local OpenCode/OpenAI login you can bootstrap once with opencode auth login
  • Postgres + pgvector for memory/search, chat, mesh, job audit, checkpoints, and annotations.

Optional connectors such as Tavily, Image, Video, and Notion are configured through Randal connector manifests and secret providers, not by editing generated OpenCode config. See Connectors and Secret Providers.


🍎 Mac Mini (Local)

A Mac Mini is the simplest deployment: Randal runs as a background process with launchd or a process manager.

1. Install

curl -fsSL https://raw.githubusercontent.com/Hassion-Studio/randal/main/install.sh | bash

This single command:

  • Installs Bun (if not present)
  • Clones the Randal repo to ~/randal
  • Installs dependencies and links the randal CLI
  • Runs the interactive setup wizard
  • Starts or validates local Postgres + pgvector for source-of-truth persistence

Or manually:

git clone <repo-url> ~/randal
cd ~/randal
bun install && bun link
randal init
randal setup

2. Configure

cd ~/randal  # use examples/local-mac/ as a starting point
cp .env.example .env
# Edit .env with your API keys

Example randal.config.yaml:

name: home-agent
runner:
  defaultAgent: opencode
  defaultModel: anthropic/claude-sonnet-4
  workdir: ~/dev
credentials:
  envFile: ./.env
  allow: [ANTHROPIC_API_KEY]
gateway:
  channels:
    - type: http
      port: 7600
      auth: "${RANDAL_API_TOKEN}"
memory:
  store: postgres
database:
  profile: local
  url: "${DATABASE_URL:-postgres://randal:randal@127.0.0.1:5432/randal}"
  ssl: disable

3. Postgres

Postgres is the source of truth for new installs. Use randal db start, randal db migrate, and /health to verify readiness before serving traffic.

To manage manually:

randal db start
randal db migrate
randal db status

Existing deployments should back up Postgres and verify randal db status --write-check before switching traffic.

4. Start Randal

cd ~/randal
randal serve

Optional published-artifact bootstrap on any deployment target:

export RANDAL_MANAGED_CONTROL_PLANE_URL=https://control-plane.example
export RANDAL_MANAGED_WORKSPACE_ID=workspace-123
export RANDAL_MANAGED_VERSION_ID=version-7
randal serve

Failure modes:

  • bootstrap endpoint unreachable: startup fails
  • version export missing: startup fails with published-artifact lookup error
  • malformed YAML export: startup fails before the runtime begins serving

If you do not set managed bootstrap env vars, startup remains local-first.

To run as a persistent background service, create a launchd plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>com.randal.agent</string>
  <key>ProgramArguments</key>
  <array>
    <string>/Users/you/.bun/bin/bun</string>
    <string>/Users/you/randal/packages/cli/src/index.ts</string>
    <string>serve</string>
  </array>
  <key>WorkingDirectory</key>
  <string>/Users/you/randal</string>
  <key>RunAtLoad</key>
  <true/>
  <key>KeepAlive</key>
  <true/>
  <key>StandardOutPath</key>
  <string>/tmp/randal.out.log</string>
  <key>StandardErrorPath</key>
  <string>/tmp/randal.err.log</string>
</dict>
</plist>
cp com.randal.agent.plist ~/Library/LaunchAgents/
launchctl load ~/Library/LaunchAgents/com.randal.agent.plist

5. Verify

curl http://localhost:7600/health
# {"status":"ok","uptime":...,"version":"0.1.0"}

# Open Randal Console
open http://localhost:7600/

🧱 Generic Server / VPS / Container Host

Use this model for any provider that can run Bun or a container and expose an HTTP port.

  1. Install Randal and OpenCode.
  2. Provide randal.config.yaml and required environment variables through your host's normal secret mechanism.
  3. Run randal serve under systemd, launchd, Docker, Nomad, Kubernetes, or another process manager.
  4. Expose the configured HTTP port, usually 7600, behind TLS and authentication-aware routing.
  5. Persist Postgres data for memory, chat, jobs, checkpoints, mesh, and audit timelines. Persist ~/.randal or RANDAL_STATE_DIR separately for scheduler files, workspaces, generated config/cache, and local connector secrets.

Connector credentials should be managed through randal connector secret set or the Console. Local connector secrets are stored under ~/.randal/secrets/workspaces/ by default. Randal Cloud will use user/workspace-level connector secret storage rather than requiring each customer connector secret to be configured as a hosting-provider deployment variable.

Connector changes do not require a full randal serve process restart. The gateway reloads connector config for new brain sessions and interrupts active brain sessions when necessary so the next session can see updated tools.


🚂 Railway (Cloud)

Railway is one supported hosting example, not a requirement for OSS Randal. The default Railway deployment is one Randal service backed by Railway managed Postgres or another pgvector-compatible DATABASE_URL.

For voice on Railway, the intended first working path is PSTN/Twilio. Browser voice remains supported, but is secondary and stays behind authenticated HTTP admin routes.

For voice deployments, treat Railway as a public gateway unless you explicitly put the service behind private networking or another authenticated edge. Only /, /health, and /assets/* are intentionally public. Browser token issuance (POST /api/voice/token) and voice status (GET /voice/status) stay behind normal HTTP auth.

1. Project Structure

Create a deployment directory with:

my-deployment/
  randal.config.yaml
  Dockerfile
  railway.toml

2. Dockerfile

FROM ghcr.io/hassion-studio/randal:latest

# Copy your config
COPY randal.config.yaml /app/randal.config.yaml

# Copy knowledge files (if any)
# COPY knowledge/ /app/knowledge/

The official image includes Bun, OpenCode, and Randal. Durable memory, chat, mesh, jobs, checkpoints, and audit state live in Postgres; run migrations before treating the service as ready.

3. Railway Configuration

In the Railway dashboard or the GitHub Actions deploy workflow:

  1. Create a new project.
  2. Add a custom service pointing to your repo.
  3. Set environment variables.

If you use .github/workflows/railway-deploy.yml, set these as GitHub Actions repository secrets because the workflow copies secrets into Railway. Your local .env is not copied into Railway by that workflow.

VariableValue
OPENCODE_AUTH_JSONPreferred for GPT Pro / OpenCode OAuth on Railway. Paste the contents of ~/.local/share/opencode/auth.json after a local opencode auth login.
OPENROUTER_API_KEYOptional alternative for OpenRouter-based model access; required when the Video connector is enabled through its default OpenRouter path.
OPENAI_API_KEYOptional alternative for direct OpenAI API billing.
ANTHROPIC_API_KEYOptional alternative for direct Anthropic API billing.
DATABASE_URL or RANDAL_DATABASE_URLRequired managed Railway Postgres or compatible external Postgres URL with pgvector, pg_trgm, and pgcrypto.
RANDAL_STATE_DIRDurable filesystem state root for workspaces, auth/tool caches, scheduler files, connector overlays, and control-plane local state. Postgres remains the source of truth for jobs, memory, chat, mesh, checkpoints, and audit.
RANDAL_API_TOKENA generated secret for API auth
RANDAL_REQUIRE_MEMORYSet to true for hosted Railway deployments so startup and /health fail closed when Postgres memory is unavailable.
RANDAL_STATE_DIRSet to /data/randal; mount a Railway volume there for workspace and auth/tool cache persistence.
RANDAL_VOICE_PUBLIC_URLPublic HTTPS/WSS base URL for Randal's /voice/* routes
TAVILY_API_KEYOptional Tavily connector key when your private config maps secrets.processEnv.connectors.tavily.TAVILY_API_KEY.
LIVEKIT_URLLiveKit server URL
LIVEKIT_API_KEYLiveKit API key
LIVEKIT_API_SECRETLiveKit API secret
DEEPGRAM_API_KEYDeepgram STT key
ELEVENLABS_API_KEYElevenLabs TTS key
ELEVENLABS_VOICE_IDElevenLabs voice ID
TWILIO_ACCOUNT_SIDTwilio account SID
TWILIO_AUTH_TOKENTwilio auth token
TWILIO_PHONE_NUMBERTwilio phone number in E.164
  1. Mount a Railway volume at /data and set RANDAL_STATE_DIR=/data/randal.
  2. Set the deploy config to use your Dockerfile.

Production GitHub App flow: let Railway deploy committed code from Git, but keep deployment-specific Randal config private. Store a complete private config in Railway as RANDAL_CONFIG_YAML, or mount/write a private config file and set RANDAL_CONFIG_PATH. Use examples/cloud-railway/randal.config.connectors.yaml as a safe template for Tavily/Image/Video connectors; it maps connector secret names to Railway service env var names and does not contain raw secret values.

Console-managed connector flow: deploy the baseline config, then enable connectors in Console. The official Docker entrypoint enables RANDAL_CONNECTOR_OVERLAY=true, so non-secret connector enable/settings state persists at $RANDAL_STATE_DIR/connectors/overlay.yaml and connector secrets persist separately through the configured SecretProvider. Remote MCP OAuth tokens for connectors such as Notion are owned by OpenCode and persist in $HOME/.local/share/opencode/mcp-auth.json, normally $RANDAL_STATE_DIR/home/.local/share/opencode/mcp-auth.json because the official entrypoint sets durable HOME=$RANDAL_STATE_DIR/home. Config-declared connectors in RANDAL_CONFIG_YAML or RANDAL_CONFIG_PATH remain authoritative over overlay entries with the same id.

The checked-in root randal.config.railway.yaml remains a generic fallback and should not enable optional connectors that require credentials. Use a private config override for production connector enablement so missing Railway variables fail closed instead of silently exposing half-configured tools.

Railway persistence checklist:

  • Mount a Railway volume at /data before treating the service as production.
  • Set RANDAL_STATE_DIR=/data/randal and keep every durable runtime path under that root unless you intentionally use a more specific persistent override.
  • Use /data/randal/workspace for runner.workdir so resumed jobs can see files created before a redeploy.
  • Attach managed Postgres and set DATABASE_URL or RANDAL_DATABASE_URL; this is the source of truth for memory, chat, graph, mesh, jobs, and audit state.
  • Keep durable HOME, OpenCode auth/session state, remote MCP OAuth tokens (mcp-auth.json), and agent-installed tools on the same mounted volume through the official image defaults.

Durable state ownership:

SurfaceRailway pathOwnerNotes
Job workspace/cache/data/randal/workspaceRunner/OpenCode process filesWorkspace output and partial files remain available after redeploy. Durable job records/events/checkpoints/audit timelines are in Postgres.
Scheduler state/data/randal/heartbeat-state.yaml, /data/randal/cron.yamlScheduler heartbeat and cronWake items, tick counters, cron status, and last-run metadata survive restarts.
Gateway metadata/data/randal/gateway.pidGatewayRuntime metadata follows the same state root for predictable operations.
Managed/control-plane state/data/randal/control-planeControl-plane file persistenceRANDAL_CONTROL_PLANE_STATE_DIR can override this when needed.
Runner workspace/data/randal/workspaceRunner/OpenCode jobsJob artifacts and partial work remain available after redeploy.
OpenCode home/session state/data/randal/homeEntrypoint, OpenCode, and runner auth materializationDefault OpenCode auth/session/cache paths live under durable HOME, including $HOME/.local/share/opencode/auth.json and remote MCP OAuth tokens in $HOME/.local/share/opencode/mcp-auth.json.
Agent-installed tools/data/randal/tools/binEntrypoint and agent runtimePrepended to PATH before /app/tools/bin for tools that must survive image replacement.

Use randal db dump, randal db restore, and randal db scoped-copy for Postgres backups and moves.

Railway runtime paths should not point at /app for durable filesystem state. Use /data/randal/workspace for runner.workdir, keep scheduler/control-plane/cache state under RANDAL_STATE_DIR, and treat /app as image/runtime scratch. Job records, events, checkpoints, tool invocation summaries, artifact metadata, and audit timelines belong in Postgres.

The official container sets HOME=$RANDAL_STATE_DIR/home, so default OpenCode auth/session/cache paths such as $HOME/.local/share/opencode survive redeploys. This directory includes provider auth.json and remote MCP OAuth token storage such as mcp-auth.json. It also prepends $RANDAL_STATE_DIR/tools/bin to PATH before /app/tools/bin; use that state-root tools directory for agent-installed tools that must survive image replacement. /app/knowledge and /app/tools/bin are image/runtime scratch unless you deliberately bake files into the image or mount them yourself.

On SIGTERM, SIGINT, or a gateway restart, Randal enters a drain mode: health reports draining, new job/scheduler mutations return 503 gateway_draining, scheduler state is flushed, active Postgres job records are updated with shutdown metadata, and child agent processes get RANDAL_SHUTDOWN_GRACE_MS milliseconds to exit before startup recovery resumes persisted queued and running jobs. The recovery model is Postgres-first for job state/audit plus filesystem persistence for workspace output; it does not guarantee every agent can semantically checkpoint mid-token before Railway stops the container.

Startup diagnostics: every gateway boot logs the resolved state root and derived durable paths for heartbeat, cron, control-plane state, OpenCode home, tools, and workspace, plus redacted Postgres health. Railway-like environments warn when RANDAL_STATE_DIR is unset or a durable surface points under known ephemeral roots such as /app or /tmp. Set RANDAL_STRICT_PERSISTENCE=true to escalate those warnings to error diagnostics during startup validation.

Extension points: keep filesystem-backed runtime state under RANDAL_STATE_DIR unless a backend owns that surface explicitly. Postgres owns jobs, memory, chat, mesh registry, checkpoints, annotations, delegation audit, and graph source-of-truth rows; Randal Cloud control-plane storage can replace individual non-source filesystem surfaces later without changing the Railway volume contract for workspaces/cache.

Use a dedicated Twilio subaccount for this deployment. The current PSTN runtime expects TWILIO_ACCOUNT_SID + TWILIO_AUTH_TOKEN + TWILIO_PHONE_NUMBER, not Twilio API keys.

4. Preferred GPT Pro / OpenCode OAuth Flow

This is the shortest path if you want Railway to run through an existing OpenCode login instead of API billing.

  1. Bootstrap locally once:
opencode auth login
  1. Confirm the local auth file exists:
ls ~/.local/share/opencode/auth.json
  1. Copy the file contents into the Railway secret named OPENCODE_AUTH_JSON.
  2. Deploy with runner.opencodeAuth.mode: openai-oauth, runner.opencodeAuth.authFileEnv: OPENCODE_AUTH_JSON, runner.defaultModel: openai/gpt-5.4, and runner.opencode.variant: xhigh.

Concrete Railway config:

name: my-cloud-agent
runner:
  defaultAgent: opencode
  defaultModel: openai/gpt-5.4
  workdir: /data/randal/workspace
  opencodeAuth:
    mode: openai-oauth
    authFileEnv: OPENCODE_AUTH_JSON
  opencode:
    variant: xhigh
credentials:
  envFile: ./.env
  allow: [OPENCODE_AUTH_JSON, OPENROUTER_API_KEY, OPENAI_API_KEY, ANTHROPIC_API_KEY, RANDAL_API_TOKEN, DATABASE_URL, RANDAL_REQUIRE_MEMORY, RANDAL_STATE_DIR, RANDAL_HOME_DIR, RANDAL_TOOLS_DIR, RANDAL_WORKSPACE_DIR]
  inherit: [PATH, HOME, USER, SHELL, TERM, OPENCODE_AUTH_JSON, OPENROUTER_API_KEY, OPENAI_API_KEY, ANTHROPIC_API_KEY, DATABASE_URL, RANDAL_REQUIRE_MEMORY, RANDAL_STATE_DIR, RANDAL_HOME_DIR, RANDAL_TOOLS_DIR, RANDAL_WORKSPACE_DIR]
gateway:
  channels:
    - type: http
      port: 7600
      auth: "${RANDAL_API_TOKEN}"
memory:
  store: postgres
  index: memory-cloud-agent
database:
  profile: generic
  url: "${DATABASE_URL}"
  ssl: require
  migrationMode: validate-only

Operational caveat: the initial OpenAI/OpenCode login is local and interactive, but steady-state Railway runtime is headless after the secret is in place.

OpenCode persistence: OPENCODE_AUTH_JSON is materialized into $RANDAL_STATE_DIR/home/.local/share/opencode/auth.json because the container sets durable HOME before randal setup and randal serve. Remote MCP OAuth connectors use OpenCode's adjacent MCP auth store at $RANDAL_STATE_DIR/home/.local/share/opencode/mcp-auth.json. If a future OpenCode version requires additional adjacent session files, keep the full $RANDAL_STATE_DIR/home/.local/share/opencode directory on the same Railway volume rather than adding another service.

Security note: OPENCODE_AUTH_JSON is reusable session state. Store it only as a Railway secret, do not commit it, do not print it in logs, and rotate it by re-running opencode auth login if you suspect exposure.

API-based paths remain supported. If you prefer explicit provider billing instead of subscription auth, keep using OPENROUTER_API_KEY, OPENAI_API_KEY, or ANTHROPIC_API_KEY with the same Railway deployment flow.

5. Alternative API-Key Config for Railway

If you do not want to use the OAuth/session-based flow above, this API-key example remains supported:

name: my-cloud-agent
runner:
  defaultAgent: opencode
  defaultModel: anthropic/claude-sonnet-4
  workdir: /data/randal/workspace
credentials:
  envFile: ./.env
  allow: [ANTHROPIC_API_KEY]
gateway:
  channels:
    - type: http
      port: 7600
      auth: "${RANDAL_API_TOKEN}"
memory:
  store: postgres
  index: memory-cloud-agent
database:
  profile: generic
  url: "${DATABASE_URL}"
  ssl: require
  migrationMode: validate-only

Note: DATABASE_URL must point at a migrated Postgres database. Run randal db migrate --config randal.config.railway.yaml during rollout, then let /health validate Postgres reachability, migrations, extensions, and writeability.

Postgres Topology

For a normal single hosted Railway Randal, use one Randal service plus Railway managed Postgres. This is the first-class default: one Railway service, one DATABASE_URL, one Railway volume mounted at /data, and memory.store: postgres.

Use one shared Postgres database for a posse unless you intentionally isolate agents by database. Every agent still gets its own identity/scope while shared memory, graph, mesh registry, jobs, and audit events remain tenant/project-scoped rows.

Spawned runtime workers should not choose their own memory backend. They inherit the parent runtime's Postgres-backed memory/chat tools and tenant scope.

The GitHub Actions Railway workflow has an explicit topology input for manual dispatch:

  • single (default): deploys one Randal service using managed Postgres and /data/randal workspace/auth persistence.
  • posse: reserved for future multi-agent service creation. Current deployments should use managed Postgres for shared memory, graph, mesh registry, jobs, and delegation audit.

For direct CLI deployment of a Posse, generate per-agent configs with explicit name, posse, HTTP auth, mesh role/expertise, and a private mesh endpoint such as http://product-engineering.railway.internal:7600 so agents delegate over the Railway private network rather than registering localhost. Use Postgres-backed memory/mesh state for all deployments.

Persistence, Migration, And Backup

Railway deployments must attach managed Postgres and mount persistent storage at /data/randal. Postgres is the durable source of truth; agent workspaces and auth/tool caches live under /data/randal/workspace, /data/randal/home, and /data/randal/tools. Keep durable HOME on that volume so $HOME/.local/share/opencode/auth.json and remote MCP OAuth tokens in $HOME/.local/share/opencode/mcp-auth.json survive image replacement.

To migrate a hosted Randal to another computer, export the Randal config/secrets separately and use Postgres tooling for data. Use randal db dump/restore for whole-database or whole-schema moves, and randal db scoped-export/scoped-import/scoped-copy for one org/project without overwriting an entire cloud database. Dump and scoped manifests redact database URLs and report row counts/checksums.

Managed mode on Railway is optional. If you want the deployed runtime to start from the active published workspace artifact instead of a repo-local config file, provide:

VariableValue
RANDAL_MANAGED_CONTROL_PLANE_URLControl-plane base URL
RANDAL_MANAGED_WORKSPACE_IDManaged workspace ID
RANDAL_MANAGED_VERSION_IDOptional fixed version pin
RANDAL_MANAGED_ARTIFACT_URLOptional explicit export URL override
RANDAL_CONTROL_PLANE_STATE_DIROptional explicit persistent directory for managed workspace state. If unset, it defaults to $RANDAL_STATE_DIR/control-plane.

6. Deploy

# Via Railway CLI
railway up

# Or push to your connected Git repo for auto-deploy
git push origin main

Voice Deployment Guidance

Shared-instance voice is supported, but the safest production posture is split deployment:

  1. Admin/browser voice instance: private or tightly restricted, authenticated browser token issuance, full admin voice access.
  2. External/PSTN voice instance: public-facing, explicit external grants, no ambient admin trust unless intentionally configured.

Use one shared instance only if you accept the larger blast radius of putting a single gateway in front of both admin and external voice paths.


📦 Importing Randal into an Existing Project

You can add a Randal agent to an existing project by extending the official Docker image. This is ideal for adding an AI agent alongside your own codebase, knowledge base, or application.

How It Works

  1. Your Dockerfile extends ghcr.io/hassion-studio/randal:latest (includes Bun, OpenCode, and Randal)
  2. You copy your randal.config.yaml into the image
  3. You ship whatever files your agent needs (codebase, knowledge, data)
  4. The official entrypoint handles randal serve; configure Postgres with DATABASE_URL
  5. For custom pre-start logic, add a pre-start.sh hook

1. Project Structure

your-project/
  randal.config.yaml    # agent configuration
  Dockerfile            # extends the official Randal image
  knowledge/            # optional: files your agent needs
  pre-start.sh          # optional: custom startup logic

2. Dockerfile

FROM ghcr.io/hassion-studio/randal:latest

# Copy your config
COPY randal.config.yaml /app/randal.config.yaml

# Ship whatever your agent needs
COPY knowledge/ /app/knowledge/

# Optional: custom pre-start logic (e.g., DB sync)
# COPY pre-start.sh /app/pre-start.sh

The official image handles everything else — Randal serves on port 7600 and uses the configured Postgres database as its source of truth.

3. Pre-Start Hook

If you need custom logic before Randal starts (database sync, file setup, etc.), create a pre-start.sh. The Randal entrypoint sources this automatically:

#!/bin/bash
# pre-start.sh — runs before Randal starts

echo "Pulling data from my database..."
bun /app/scripts/sync-data.mjs || echo "Sync failed, continuing"

4. Security

The Docker container is the isolation boundary. Recommended config for imported usage:

sandbox:
  enforcement: env-scrub

runner:
  workdir: /data/randal/workspace
  allowedWorkdirs:
    - /data/randal/workspace

credentials:
  allow: [ANTHROPIC_API_KEY]  # only what the agent needs

See SECURITY.md for the full security model.

For imported-service Docker deployments, mount a persistent volume at /data or choose an equivalent host-backed path before using /data/randal/workspace for durable work. Keep /app for image contents and scratch files that can be replaced with the container.

5. Postgres Persistence

Set DATABASE_URL or RANDAL_DATABASE_URL for imported-service deployments before enabling memory, chat, mesh, job audit, or checkpoint recovery.

  1. Provision Postgres with pgvector, pg_trgm, and pgcrypto.
  2. Run randal db migrate before serving production traffic.
  3. Set memory.store: postgres in the Randal config.

6. Programmatic Usage (Advanced)

For full programmatic control, override the CMD to run your own entry point:

FROM ghcr.io/hassion-studio/randal:latest
COPY randal.config.yaml /app/randal.config.yaml
COPY index.ts /app/index.ts
CMD ["bun", "run", "/app/index.ts"]
import { createRandal } from "@randal/harness";

const randal = await createRandal({
  configPath: "./randal.config.yaml",
});

See examples/imported-service/ for a complete working example.


🗄️ Postgres Setup

Postgres with pgvector is the durable source of truth for memory, chat, graph/DAG recall, mesh registry, jobs, checkpoints, annotations, and audit history.

📦 Local Install

randal db start
randal db migrate
randal db status --write-check

The local compose file uses pgvector/pgvector:pg16 and preserves the Docker volume between runs.

⚙️ Configuration

memory:
  store: postgres
  index: memory-my-agent

database:
  profile: generic
  url: "${DATABASE_URL:-postgres://randal:randal@127.0.0.1:5432/randal}"
  ssl: disable
  migrationMode: validate-only

For Railway and other managed providers, set ssl: require and provide DATABASE_URL or RANDAL_DATABASE_URL as a secret. Logs and manifests redact database credentials.

🤝 Cross-Agent Shared State

For multiple agents in a posse, use shared Postgres plus explicit tenant/project/instance identity. Agent-specific memory scopes and shared recall are represented as rows rather than separate search services.

🔒 Production Considerations

  • Require pgvector, pg_trgm, and pgcrypto.
  • Run migrations before serving production traffic.
  • Set RANDAL_REQUIRE_MEMORY=true; /health returns 503 if Postgres source-of-truth readiness fails.
  • Use randal db dump/restore for full database movement and randal db scoped-export/scoped-import/scoped-copy for project/org-scoped movement.

🔌 Custom Relay Setup

The Randal Console is built in. If you want Randal inside another product, deploy a small custom relay next to that product and connect it to the gateway session API.

Minimum relay environment:

RANDAL_GATEWAY_URL=https://your-randal-service.example
RANDAL_API_TOKEN=strong-token-from-randal-config

Minimum gateway config:

gateway:
  channels:
    - type: http
      port: 7600
      auth: "${RANDAL_API_TOKEN}"

Relay flow:

  1. Verify the external product webhook.
  2. Map the external thread ID to a Randal session ID.
  3. Call POST /api/sessions.
  4. Call POST /api/sessions/:id/messages for inbound user text.
  5. Subscribe to GET /api/sessions/:id/events and render output back to the product.

See Custom Relay Guide for meta-code and the reusable builder prompt.


🔑 Environment Variables Reference

VariableUsed ByDescription
RANDAL_API_TOKEN📡 Gateway HTTP authBearer token for API authentication.
DATABASE_URL / RANDAL_DATABASE_URL🧠 Postgres source-of-truthManaged or local Postgres URL. Keep secret; Randal redacts it in diagnostics.
RANDAL_REQUIRE_MEMORY🧠 Memory/healthSet to true for hosted deployments so startup and /health fail closed when Postgres is unavailable.
RANDAL_STATE_DIR💾 Filesystem persistencePersistent state root for Railway/Docker workspaces, caches, connector overlays, scheduler files, and control-plane local state. Postgres owns jobs, memory, chat, mesh, checkpoints, and audit rows. Recommended /data/randal.
ANTHROPIC_API_KEY🤖 Agent (Claude/OpenCode)Anthropic API key for model access.
OPENROUTER_API_KEY🤖 Agent / EmbedderOpenRouter API key (if using OpenRouter models).
OPENAI_API_KEY🔌 EmbedderOpenAI API key (if using OpenAI embeddings).