RailCall Docs
System architecture, workflow reference, and developer manual for the intelligent execution layer beneath your AI coding agent.
1 · What is RailCall?
RailCall is the intelligent execution layer beneath AI coding agents — it lets an AI understand your codebase, execute changes, optimize cost, and govern every action, all locally on your machine.
Key principles
- Local-first
- Loopback-bound
- BYOK
- No fake green
- Terminal = source of truth
RailCall Studio runs on your machine. The terminal stays the source of truth, while Studio organizes sessions, folders, workflows, integrations, artifacts, generated links, and audit receipts around it.
Layers at a glance
| Layer | Role |
|---|---|
Terminal | source of truth — real commands, real output |
Local folder | source of truth — your files on your SSD |
Studio | organization layer over the terminal |
Receipts | proof layer — evidence for every claim |
BYOK | integration layer — your keys, local |
2 · Quickstart
From a fresh shell to a verified receipt and Studio running in five steps.
# 1 · install
curl -fsSL https://railcall.ai/install.sh | bash
# 2 · authenticate
railcall login <YOUR_API_KEY>
# 3 · audit a file
printf 'name,email\nAda,ada@example.com\n' > sample.csv
railcall audit sample.csv
# 4 · verify receipt
railcall verify
# 5 · open Studio
railcall studioAuthentication
railcall login stores a device-scoped token 0600 under ~/.railcall/. The local engine is free forever — solo use runs unlimited local work at no cost, with a signed receipt for every run. Paid plans add seats for teams ($20 / seat / month), not usage.
Updating
Studio and the CLI update in place. If Studio is running when you update the CLI, kill it first so the new binary picks up cleanly.
# Kill Studio then reinstall
railcall studio --kill
curl -fsSL https://railcall.ai/install.sh | bash3 · System Architecture
RailCall is three lifecycles glued to one local machine: build, execute, receipt. Studio, the CLI, and the local daemon all bind to loopback.
Onboarding path
Webhook ingestion path
0.0.0.0. You bring your own tunnel (ngrok, Tailscale, Cloudflared); RailCall stays loopback-bound.4 · Studio — where AI action becomes governed action
Studio is the local visual workbench that ships with the station install. It runs at http://127.0.0.1:8799/v2 — loopback-only, your data never leaves the machine unless a human clicks Approve on a specific send. The airlock is the spine. Every external effect goes through the same ceremony: preview → approve → execute → signed receipt.
railcall studio — opens Studio in your browser at 127.0.0.1:8799.The mental model
Traditional automation tools (n8n, Zapier, custom scripts) run against real providers immediately: click a button, it fires. That's a problem when AI is authoring the workflows — nobody reviewed what got composed before it hit production.
Studio inverts the flow. The LLM composes; the human approves each write; the airlock binds an approval to a specific payload hash so nothing can be tampered with between decision and execution. Every send leaves a cryptographic receipt.
The load-bearing loop
Tabs — the whole surface
The 10+ wired airlock command handlers
Ships with real handlers for the most common send targets. Each is a drop-in provider through the same ceremony — Configure in Sends, Preview, Approve, Execute, receipt.
A new provider = ~30-line handler + a VAULT_UI entry. Auth patterns proven: Bearer, Bearer+version-header, raw-key, HTTP Basic, routing-key-in-body, GraphQL, form-encoded, JSON.
What runs; what refuses
Studio deliberately catches every external side-effect at the airlock — including ones an AI proposes over MCP. The AI cannot approve; the AI cannot execute. Only a human click in Sends fires the write. Approvals are single-use: attempted once and invalidated regardless of outcome, so a captured approval cannot be replayed.
Read next
The airlock ceremony · Composing multi-step workflows · Driving workflows via MCP · Receipts + verification
5 · The airlock ceremony
Every external effect in RailCall — Discord messages, Stripe refunds, GitHub issues, Notion pages, Slack sends, PagerDuty incidents, SMS, emails, even local file writes — goes through the same three-step ceremony. It is the single load-bearing pattern in the product.
1. Preview
POST /api/commands/preview with a command id + inputs. The airlock validates the payload against the command's declared input schema, computes a payload_hash (sha256 over canonicalized inputs), records a pending approval entry, and returns an "airlock card" describing what WILL happen — including whether the send will touch an external API.
pending_approvals.json and reads only from your vault. No network calls to Discord, Stripe, etc.2. Approve
POST /api/commands/approve with the SAME payload. The airlock recomputes the payload_hash and binds the human approval to that exact hash. If any input changed between Preview and Approve — even a single space in a string — the hash differs, and Approve refuses.
An approval is single-use — the first Execute attempt (success OR failure) consumes it. A captured approval cannot be replayed.
3. Execute
POST /api/commands/execute. The airlock verifies: (a) approval exists for this payload_hash, (b) approval hasn't been consumed, (c) global freeze switch is off. If any check fails: blocked_by_policy, no external call, honest receipt written anyway.
On green: the approval is consumed AT THE MOMENT OF ATTEMPT (before the handler even runs — so a failed handler cannot lure the user into retrying under the same authorization). The handler makes the real external call. Whatever happens, a receipt lands.
The receipt
Verification, later
Any time, anyone, from anywhere in the world can hand a receipt to the independent auditor: railcall verify <receipt-id>. The auditor recomputes the integrity_hash from the receipt's canonical fields, re-verifies the Ed25519 signature against the install's pinned public key, checks the approval binding, and returns a fresh verdict.
Because the signature is over the integrity_hash, and the integrity_hash is over the canonical fields, a receipt cannot be forged, tampered, or replayed without invalidating the signature — even by whoever originally signed it.
Failure modes (and what happens on each)
What lives OUTSIDE the airlock
Read-only commands (list issues, read balance) don't require approval — no external state changes. They still leave a receipt so the audit trail is complete. Pure-local operations that happen INSIDE Studio (loading pages, browsing your vault, verifying a receipt) also don't need approval — the trust boundary is defined by "does this cause an external side-effect the user can't undo?"
4 · VS Code Extension
RailCall AI for VS Code is a governance HUD wired to your local Studio server over loopback: a Pending Approvals tree, a live Receipts feed, and a chat panel — all reading the same on-machine airlock as the web UI. Same BYOK vault, cost router, and PII firewall, reached directly from your editor.
The extension does not call any external service on its own. Every AI call, every Discord send, every web search routes through 127.0.0.1:8799, which enforces governance and writes a signed Ed25519 receipt (one JSON file) into your local receipts/capoffdir for every action — the feed the extension's Receipts view watches in real time.
4.1 · Prerequisites
- VS Code 1.80 or newer (also works in Cursor, VSCodium, and other MCP-era forks)
- RailCall Studio running locally —
railcall studio - At least one AI provider — a Groq key (free), Anthropic/OpenAI (paid), or Ollama (fully offline)
4.2 · Install
Option A — Direct download
Download the .vsix from railcall.ai/railcall-vscode.vsix. Then in VS Code:
Cmd+Shift+P(macOS) orCtrl+Shift+P(Windows/Linux)- Type
Extensions: Install from VSIX… - Select the downloaded file
Option B — Command line
# macOS / Linux
code --install-extension https://railcall.ai/railcall-vscode.vsix
# Windows (PowerShell)
code --install-extension https://railcall.ai/railcall-vscode.vsix4.3 · Configure your API key
Open VS Code settings — Cmd+, (macOS) or Ctrl+, (Windows/Linux) — and search for RailCall. Paste your key into the appropriate field.
| Setting | Purpose |
|---|---|
railcall.groqApiKey | Groq API key. Fastest option, free tier available. |
railcall.anthropicApiKey | Anthropic key for Claude models. Best for code understanding. |
railcall.openaiApiKey | OpenAI key for GPT models. |
railcall.discordWebhookUrl | Discord incoming webhook. Enables the send-to-Discord action. |
railcall.slackWebhookUrl | Slack incoming webhook. |
railcall.autoSyncKeys | Auto-push settings changes to Studio. Default: true. |
railcall.serverUrl | Studio URL. Default: http://127.0.0.1:8799 |
railcall.receiptsDir | Extra receipts directory for the Receipts HUD to watch. Optional; the capoff receipts dirs under $RAILCALL_WS and ~/.railcall are scanned automatically. |
Keys are auto-synced to Studio's vault at ~/.railcall/station/.railcall_workspace/keys.local.json the moment you save the setting. To sync manually at any time, run RailCall: Sync API Keys to Studio from the command palette.
4.4 · Usage
Open the RailCall panel from the Activity Bar (left) or Secondary Side Bar (right). Keyboard shortcut: Cmd+Shift+L. The extension registers three views; the two governance surfaces come first:
- Pending Approvals — staged actions and MCP workflows awaiting a human. Click to inspect the diff or blast radius, then Approve with ✓ (a modal names you in the signed audit as
approval_channel=vscode_chat) or Approve & Run with ▶, which prints the full blast radius — node count, systems touched, irreversible actions, egress domains, and spend — and distinguishes live vs mock runs and signed vs unsigned receipts. - Receipts — a live signed-receipt feed bucketed by Today / Yesterday / Earlier. Each entry shows provider, outcome, mode, and whether it is Ed25519-signed and offline-verifiable.
The status bar shows the pending-approval count plus today's receipts, and warns on station/extension version drift. The third view is the chat panel:
| You type | What happens |
|---|---|
explain how this function works | Regular AI chat with active-file context injected automatically. |
what's in package.json? | Reads the file, answers from actual contents. No hallucination. |
send the deploy status to discord | AI composes the message → preview card → click Run → real receipt. |
/discord good morning team | Fast-path send. No AI composition. Preview → confirm. |
/search DuckDB vs PostgreSQL | Web search via DuckDuckGo, proxied through Studio. |
Right-click any code selection for Explain, Fix, Refactor, or Generate from comment.
4.5 · How intent classification works
When you type a message, the extension uses a small AI classifier to decide whether it's a chat question, a Discord send, or a web search — instead of relying on brittle regex patterns. This means natural phrasing like "share the folder tree on discord" or "look up how React 19 handles suspense" just works.
Nothing executes without a preview and an explicit confirmation click. The classifier only routes; the action gate is always human.
4.6 · What's local vs cloud
| Component | Local? |
|---|---|
| Studio server, cost router, PII firewall | ✓ Local |
| Keys vault, audit log, workflow runner | ✓ Local |
| AI inference via Ollama | ✓ Local |
| AI inference via Anthropic / OpenAI / Groq | ✗ BYOK cloud (PII-scrubbed before egress) |
| Discord/Slack webhooks | ✗ Their servers, by definition |
| Web search (DuckDuckGo) | ✗ Proxied through Studio |
The extension itself never phones home. Every network call is either loopback (to your own Studio) or a BYOK request you explicitly configured.
4.7 · Troubleshooting
| Symptom | Cause / Fix |
|---|---|
| "RailCall Studio is not running" | Run railcall studio in a terminal. The extension expects the server on 127.0.0.1:8799. |
| "No working API key configured" | Add a Groq/Anthropic/OpenAI key in settings, or install Ollama for the offline path. |
| Extension icon is missing after install | Reload the window: Cmd+Shift+P → Reload Window. |
| Sidebar shows blank content | Rare — usually a nonce/CSP issue in older VS Code versions. Update VS Code to 1.80+ and reload. |
| Discord send returns "webhook not configured" | Add railcall.discordWebhookUrl in settings, or paste the URL in Studio → Connect → Discord. |
5 · Workflows
A RailCall workflow is a linear pipeline of typed nodes. Each node receives the output of the previous one. The executor fans out automatically — if a node produces a list and the next node is a scalar processor, it runs once per item and collects results.
Workflows are local-first: the executor runs on your machine, reads your vault for credentials, and leaves a signed receipt of every run. No data leaves your machine unless a send node fires.
CSV → Discord in three commands
# 1 · save your Discord webhook (once)
railcall set discord-webhook https://discord.com/api/webhooks/{id}/{token}
# 2 · dry-run (preview — nothing is sent)
railcall workflow run contacts.csv
# 3 · go live
railcall workflow run contacts.csv --live~/.railcall/station/.railcall_workspace/keys.local.json), never transmitted anywhere.How it works
The template is auto-generated from your CSV column names when you omit --template. Every column becomes a {{field}} token.
CLI reference
# dry-run (no send)
railcall workflow run data.csv
# live send
railcall workflow run data.csv --live
# custom message template
railcall workflow run data.csv --template "New member: {{name}} ({{email}})" --live
# send to Slack instead
railcall workflow run data.csv --dest slack --live
# set Slack webhook
railcall set slack-webhook https://hooks.slack.com/services/…
# view current settings
railcall setAvailable node types
| Node | Input | Output | Config keys |
|---|---|---|---|
csv_read | — | list of row dicts | path |
message_template | list or dict | list of strings | template ({{field}} tokens) |
filter | list | filtered list | field, value, not_empty |
limit | list | first N items | n |
discord_send | list of strings | send receipt | webhook_url, dry_run |
slack_send | list of strings | send receipt | webhook_url, dry_run |
http_request | any | response body | url, method, dry_run |
Workflow spec (JSON)
The CLI builds the spec for you, but you can also pass one directly to the Studio API endpoint POST /api/workflow/execute for programmatic use.
{
"name": "csv_to_discord",
"steps": [
{ "id": "read", "type": "csv_read",
"config": { "path": "/data/contacts.csv" } },
{ "id": "filter", "type": "filter",
"config": { "field": "role", "value": "founder" } },
{ "id": "format", "type": "message_template",
"config": { "template": "{{name}} · {{email}}" } },
{ "id": "send", "type": "discord_send", "config": {} }
]
}Vault credential resolution
Any config value of the form {{vault.provider.FIELD}} is resolved from your local vault at run time. For example, setting webhook_url: {{vault.discord.DISCORD_WEBHOOK_URL}} is equivalent to leaving the field empty — the executor looks it up automatically. You can also override via environment variable: DISCORD_WEBHOOK_URL=https://… railcall workflow run data.csv --live.
Build loop status
| Status | Meaning |
|---|---|
PASS | Ran end-to-end with governed writes. Signed live receipt on disk. |
PARTIAL | Some connector writes succeeded, others were held back. Partial receipt keeps the diff. |
FAIL | Aborted by airlock or connector. Failure receipt kept for audit. |
UNKNOWN | Verifier couldn't confirm — treated as not-a-pass. |
NEEDS HUMAN QA | Dry-run diff is ready. Awaiting approval before anything goes live. |
6 · Multi-Connector Workflows
Real work spans systems. RailCall coordinates multiple BYOK connectors inside one governed flow — a single dry-run diff, a single live receipt, but writes to N systems.
Support ticket → CRM enrichment
- Webhook receives a new ticket from Zendesk.
- OpenAI (your key) classifies intent and urgency.
- HubSpot contact is enriched with the tags.
- Slack channel gets a compact digest.
Stripe payout reconciliation
- Poll Stripe for the last payout batch.
- Reconcile line items against the local ledger folder.
- Write a row per line to a governed Google Sheet.
- Email a signed summary to finance.
GitHub release → docs + changelog
- Webhook fires on a new tag.
- OpenAI summarizes the commit range using audited prompts.
- Changelog entry is written to your local docs folder.
- Docs site build kicks off from the same folder.
Nightly ops digest
- Scheduled flow reads last-24h Sentry issues.
- Runs a read-only Postgres query for user impact counts.
- Renders a signed report file locally.
- Emails ops + posts the same summary to Slack.
7 · Governed Pass-Through
RailCall is a pass-through, not a store. Records live where they were born — Stripe rows in Stripe, Zendesk tickets in Zendesk, files in your folders. RailCall only writes signed receipts about the transitions.
This is the whole point. If RailCall ever holds your data at rest, you gain a new custodian and a new attack surface. Instead, RailCall holds the proof that a transition happened, not the record itself.
What passes through vs what persists locally
| Data class | Where it lives | What RailCall keeps |
|---|---|---|
| Business records | Source system (Stripe / CRM / DB) | Nothing — read on demand, forget after receipt. |
| Provider keys | ~/.railcall/keys/ (0600) | Encrypted key material, never leaves the machine. |
| Prompts | Local flow definition | Hash + input digest — full text on disk, not uploaded. |
| Model outputs | Destination system | Hash + destination pointer. |
| Receipts | ~/.railcall/receipts/ | Full signed JSON, offline verifiable. |
| Files | Your local folders | Path + SHA256 + size — file itself never uploaded. |
Custody & Recovery
| Concern | Behavior |
|---|---|
| Lose your machine | Records are still in their source systems. Reissue keys, re-verify receipts. |
| Compromise concern | railcall keys rotate |
# Rotate all local keys, keep receipt history intact
railcall keys rotate --all
# Export the last 30 days of receipts as a signed bundle
railcall receipts export --since 30d ./receipts.bundle
# Verify a bundle offline (no network)
railcall receipts verify ./receipts.bundle --offline8 · Integrations / BYOK
Every integration is Bring-Your-Own-Key. Keys are stored locally at ~/.railcall/station/.railcall_workspace/keys.local.json with 0600 permissions — never uploaded. The provider meter (tokens, API calls) is billed direct to you; RailCall adds no markup.
Supported providers
| Provider | Set command | Notes |
|---|---|---|
| Anthropic (Claude) | railcall set anthropic-key sk-ant-...railcall set anthropic-model haiku|sonnet|opus|auto | Full Messages API. auto lets the cost router pick the tier. |
| OpenAI (GPT / o1) | railcall set openai-key sk-...Resolved by cost router — gpt-4o-mini (light), gpt-4o (mid/heavy). | Standard chat completions. No extra config needed. |
| Groq | railcall set groq-key gsk_...llama-3.1-8b-instant (nano), llama-3.3-70b-versatile (light+). | Fastest inference for quick tasks. Default provider. |
| xAI (Grok) | railcall set xai-key xai-...grok-2-latest for all tiers. | Strong for reasoning and code review. |
| Ollama (local) | railcall set ollama-host http://localhost:11434qwen:7b or any model you have pulled locally. | Zero cost. Used automatically for private/PII content. |
| Discord | railcall set discord-webhook https://discord.com/api/webhooks/… | Used by workflow discord_send node. |
| Slack | railcall set slack-webhook https://hooks.slack.com/services/… | Used by workflow slack_send node. |
Cost-aware routing
When multiple provider keys are present, RailCall's routing middleware classifies each request by complexity and picks the best model for the quality/cost tradeoff — automatically. You never pay for Opus on a one-line question.
| Tier | When used | Example models |
|---|---|---|
| 0 — nano | Short Q&A, classification, formatting | Haiku, GPT-4o-mini, Llama-8b |
| 1 — light | Code snippets, quick answers, general queries | Haiku, GPT-4o-mini, Llama-70b |
| 2 — mid | Debugging, architecture, multi-step reasoning | Sonnet, GPT-4o, Llama-70b |
| 3 — heavy | Very long context, complex planning | Opus, GPT-4o |
The classifier is rule-based and runs in zero latency — no extra API call. It detects code signals, reasoning patterns, PII, and message length to assign a tier. Private/PII content is always routed to the local Ollama model if available.
Preferred provider & budget cap
Pin a provider or set a monthly spend cap from the CLI or Studio:
# always use Anthropic regardless of task type railcall set preferred-provider anthropic # let the router decide (default) railcall set preferred-provider auto # cap monthly spend at $5 — router demotes tier at 90% railcall set budget 5
.railcall_workspace/cost_ledger.jsonl with token counts, cost in USD, and routing reason. Run railcall cost to see a monthly breakdown by provider, or open Studio for a live routing panel showing which model is active and why.Multi-provider fallback chain
If the preferred or cost-optimal provider fails (rate limit, bad key, quota exceeded), the router tries the next provider in the chain automatically: groq → anthropic → openai → xai → ollama. Every fallback is logged with a reason so the routing is fully auditable.
Connect flow
- Run
railcall set <provider>-key <key>— written to vault at0600. - Open Studio → the routing badge in the model picker shows the active provider, estimated cost per message, and budget remaining.
- Click the badge to open the Router Config panel: change preferred provider or budget without touching the CLI.
- Run
railcall costanytime to audit spend by provider and model.
9 · Webhook System
Inbound webhooks are first-class. Each one is a signed, scoped endpoint bound to loopback and reachable through your own tunnel.
Properties
| Property | Behavior |
|---|---|
| Bind | 127.0.0.1 only — never 0.0.0.0 |
| Auth | HMAC secret per endpoint, rotated with the connector. |
| Idempotency | Provider event IDs deduped for 24h by default. |
| Backpressure | Bounded queue; overflow returns 429 with retry-after. |
| Receipts | Every accepted event mints a signed ingest receipt. |
Tunnel path
Test command
# Fire a signed test event against a local webhook
railcall webhook test stripe.payment_intent.succeeded \
--endpoint http://127.0.0.1:8787/hooks/stripe \
--payload ./fixtures/pi_succeeded.jsonExpected response
{
"accepted": true,
"event_id": "evt_1PXY…",
"receipt": "rcpt_ingest_01H…",
"queue_position": 1,
"next": "airlock_decide"
}Origin or Referer looks browser-driven. The daemon never accepts credentials from cookies.10 · Approval Policy
Every write crosses a governance floor. The floors are fixed; only scoped, signed policies can move the line — and policy changes are themselves governed writes.
Four hard floors
Irreversible actions always require a human. Money movement, external messaging, external API writes — no policy widens this without a signed receipt.
No live action without a dry-run first. Default requires approval; a scoped, signed rule can auto-approve.
Policy can never authorize its own widening. Auto-approve only within the pre-audited component surface.
Every action mints a signed, offline-verifiable receipt (Ed25519). No receipt = no action.
Policy change flow
A policy change is itself a governed write. Propose in Studio or CLI, compute the delta against the active policy, sign an Ed25519 approval receipt, then land the new policy — the old one is archived, never deleted.
11 · Audit Receipts & Evidence Ledger
Every governed write mints a receipt. Receipts are JSON, Ed25519 signed, and offline-verifiable. Together they form the Evidence Ledger.
Receipt example
{
"receipt_id": "rcpt_01H8Z…",
"kind": "flow.live",
"flow_id": "flw_stripe_recon_v3",
"policy_id": "pol_widen_01",
"integrity_root": "sha256:9a4c…f0",
"connector_writes": [
{ "connector": "sheets", "range": "Payouts!A87:F87", "hash": "sha256:2b…" },
{ "connector": "email", "message_id": "<a1@srv>", "hash": "sha256:7e…" }
],
"sockets_observed": 0,
"approver": "sami@localhost",
"signature": "ed25519:MEUCIQD…",
"at": "2026-07-10T12:04:11Z"
}Field reference
| Field | Meaning |
|---|---|
integrity_root | SHA256 over the ordered set of inputs, prompts, and outputs. Changes iff behavior changed. |
connector_writes | Every destination write with a stable pointer (row range, message ID, path). |
sockets_observed | lsof-based per-run socket sweep. Non-zero = failed receipt. |
signature | Ed25519 over the canonical JSON. Verifiable without RailCall. |
Audit commands
# List recent receipts
curl -s http://127.0.0.1:8787/v0/receipts?since=24h
# Verify one receipt end-to-end (offline)
railcall audit rcpt_01H8Z… --verify --offline
# Diff two receipts on a run
curl -s http://127.0.0.1:8787/v0/receipts/diff?a=rcpt_a&b=rcpt_b
# Export a bundle for compliance review
railcall receipts export --flow flw_stripe_recon_v3 ./out.bundle12 · Payment Rails
RailCall governs money movement like any other irreversible action — draft, dry-run, approve, settle, receipt. Its own pricing is per seat, not per payment.
Plans & pricing
| Plan | Price | What you get |
|---|---|---|
| Free (solo) | $0 forever | Local engine, single seat, unlimited local runs, signed receipts — local runs are never metered |
| Team | $20 / seat / month | Everything in Free, plus collaboration, shared policy, team receipt history, priority support |
| Enterprise | Custom | Air-gap, SSO/SCIM, DPA & procurement |
Local runs stay free and unmetered even on paid — you pay for seats (people), not usage. Free for one, $20 / seat for your team.
# See your plan and seats
railcall plan
# Local runs are free and unmetered — there is no balance to check.Governed payment flow
x402 Agent Economy
| Concept | How RailCall handles it |
|---|---|
| Agent-to-agent invoice | Draft an x402 invoice; dry-run diff shows amount, memo, and destination. |
| USDC settlement | Requires an F1 hard-floor approval — no autopay by default. |
| Provider metering | Provider bills your key directly (BYOK); RailCall doesn't relay. |
13 · Local Files & Directory Map
RailCall keeps everything under ~/.railcall/. The tree is human-readable — nothing about your setup is opaque.
~/.railcall/
├── keys/ # BYOK provider keys · 0600 · never leaves loopback
├── flows/ # Workflow definitions
├── receipts/ # Append-only Ed25519 signed evidence ledger
├── artifacts/ # Files produced by flows (reports, exports)
├── policies/ # Signed policy documents
├── connectors/ # Per-connector config and scope receipts
└── logs/ # Local diagnostic logs (rotated, no secrets)Folder reference
| Folder | Purpose |
|---|---|
keys/ | BYOK provider keys, 0600, encrypted at rest. |
flows/ | One folder per workflow — its definition + inputs. |
receipts/ | Ed25519 signed receipts, append-only, chained per run. |
artifacts/ | Files produced by flows (reports, exports, generated pages). |
policies/ | Signed governance policies. Old versions kept for audit. |
14 · Total Flexibility
Studio is one client. The daemon exposes a local HTTP API — any tool that speaks HTTP can drive RailCall. All endpoints are loopback-bound.
# List active flows
curl -s http://127.0.0.1:8787/v0/flows
# Trigger a dry-run of a specific flow
curl -s -X POST http://127.0.0.1:8787/v0/flows/flw_stripe_recon_v3/run \
-H "content-type: application/json" \
-d '{"mode":"dry-run"}'
# Fetch a receipt by ID
curl -s http://127.0.0.1:8787/v0/receipts/rcpt_01H8Z…15 · MCP Integration
RailCall ships an airlock MCP server that any MCP client — Claude Code, Claude Desktop, Cursor, or your own agent — can drive over stdio JSON-RPC. It exposes 69 tools: a matched _plan / _apply pair for each of the 24 governed integrations, plus a read-only toolbelt that lets an agent understand a codebase, reason locally, compose governed workflows, and attest its own actions. Every write is dry-run by default; live execution requires an explicit consent token.
Server identity
| Field | Value |
|---|---|
| Name | railcall-airlock |
| Version | 1.0.0 |
| Protocol | MCP 2025-06-18 |
| Transport | stdio JSON-RPC 2.0 |
| Entry point | workbench/mcp_server.py |
Governed integrations (plan + apply)
Each integration exposes two tools: <provider>_<verb>_plan (dry-run, returns a diff and a consent_token) and <provider>_<verb>_apply (executes the plan under the returned token, emits a signed receipt).
| Provider | Verb | Example tool |
|---|---|---|
| linear | create_issue | linear_create_issue_plan |
| slack | post_message | slack_post_message_plan |
| discord | post_message | discord_post_message_plan |
| twilio | send_sms | twilio_send_sms_plan |
| gcal | create_event | gcal_create_event_plan |
| gsheets | append_row | gsheets_append_row_plan |
| s3 | put_object | s3_put_object_plan |
| intercom | send_message | intercom_send_message_plan |
| stripe | create_invoice | stripe_create_invoice_plan |
| airtable | create_record | airtable_create_record_plan |
| hubspot | create_contact | hubspot_create_contact_plan |
| postgres | insert_row | postgres_insert_row_plan |
| salesforce | create_lead | salesforce_create_lead_plan |
| github | issue_open | github_issue_open_plan |
| webhook | fire_hook | webhook_hook_fire_plan |
| teams | message_post | teams_message_post_plan |
| telegram | message_send | telegram_message_send_plan |
| notion | page_append | notion_page_append_plan |
| resend | email_send | resend_email_send_plan |
| gdocs | doc_append | gdocs_doc_append_plan |
code (apply_patch) | patch_apply | code_patch_apply_plan |
checks (run_tests) | run | checks_run_plan |
The last two are the bridge to governed engineering: an AI-proposed code patch stages a diff you approve in the editor, then writes it root-jailed and reversibly with a signed receipt; test runs execute only human-configured command aliases — the model can never smuggle a raw shell command through.
The read-only toolbelt
Beyond sends, the agent gets a deterministic, root-jailed toolbelt so it can prepare a change without spending frontier tokens on a repo dump — and attest what it did. All local, all offline.
| Family | Tools | What it gives the agent |
|---|---|---|
| Code intelligence | symbol_find · references_find · implementations_find · callers_find · dependencies_find · related_files · tests_find · file_outline · project_map · semantic_search | Find/understand code deterministically (Python ast-exact, JS/TS heuristic). “Where is X, what depends on it, what breaks if it changes” — answered from an index, not by shipping files to a model. |
| Context engine | context_pack · file_summary | Progressive retrieval: a compact, cached, relevance-ranked bundle for a task (seed + one dependency hop + tests) instead of a repo dump. Cached by file content, reused while unchanged. |
| Local intelligence | local_reason | Summarize / classify / rank / extract on a LOCAL model (Ollama, loopback) — zero frontier tokens, zero egress, with a signed provenance receipt. Falls back honestly when no model is running. |
| Dynamic workflows | workflow_blocks · workflow_plan · workflow_apply | Compose a multi-node governed workflow on the fly: one signed blast-radius root, approve once, run under one saga, one signed merkle receipt. Optional capability scoping caps what it may touch. |
| Attestation | attest · attestation_verify | Seal many receipts under ONE signed batch root (M3), and 3-way verify it (self-consistency + on-disk match + Ed25519) — naming any tampered receipt. |
Read-only inspection
Safe to call at any time. No workspace mutation, no external network writes.
| Tool | What it does |
|---|---|
railcall_integrations_list | Returns every registered integration with its action class (reversible / external_send / compensable) and readiness flag. |
railcall_receipts_list | Lists Ed25519-signed receipts from the local workspace, newest first. Supports limit / provider filters. |
railcall_receipt_verify | Verifies a receipt's signature and returns the parsed payload — proves what actually ran and against which policy. |
Airlock guarantees
| Rule | Effect |
|---|---|
| Dry-run by default | _apply refuses without a valid consent_token minted by the matching _plan in this session. |
| Hard floor | Any action with action_class = external_send and non-zero spend requires human confirmation — the model cannot bypass it. |
| Signed receipts | Every apply writes an Ed25519-signed receipt to the workspace. Verifiable offline; independent of RailCall's servers. |
| Local-first | The server runs in-process on the user's machine. No credentials, plans, or receipts leave the box. |
Claude Code
One command, user scope so the server is available in every project:
claude mcp add \
--scope user \
--transport stdio \
--env RAILCALL_WS="$HOME/.railcall/workspace" \
railcall -- python3 "$HOME/.railcall/station/workbench/mcp_server.py"Verify with claude mcp list — you should see railcall: ... ✓ Connected. Restart Claude Code once so the tool schemas inject into the session.
Claude Desktop
Edit claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/):
{
"mcpServers": {
"railcall": {
"command": "python3",
"args": ["/Users/you/.railcall/station/workbench/mcp_server.py"],
"env": {
"RAILCALL_WS": "/Users/you/.railcall/workspace"
}
}
}
}Cursor
Add to ~/.cursor/mcp.json (or the workspace-scoped .cursor/mcp.json):
{
"mcpServers": {
"railcall": {
"command": "python3",
"args": ["/Users/you/.railcall/station/workbench/mcp_server.py"],
"env": {
"RAILCALL_WS": "/Users/you/.railcall/workspace"
}
}
}
}Enabling live apply
By default, _apply tools return a preview even with a valid consent token — the airlock stays sealed. To let apply calls execute against real providers, set:
export RAILCALL_MCP_ALLOW_LIVE=1in the MCP server's env block. The hard floor still holds: irreversible spend actions require an out-of-band human confirmation regardless of this flag.
railcall_integrations_list and show you all 24 governed providers plus the read-only toolbelt — no credentials, no side effects.16 · Your UI Is Yours
Studio is the reference interface. It isn't the only one you can use.
Hand the map to a model
- Expose state. Point Claude (or any MCP-aware model) at
railcall_integrations_listandrailcall_receipts_list. Read-only, zero widening risk. - Ask for a proposal. The model calls
<provider>_<verb>_plan— stages a dry-run and gets a diff plus aconsent_token. Nothing external happens. - You approve. Approve in Studio, CLI, or your own UI. The receipt lands regardless of where you clicked — governance is client-independent.
17 · Community
RailCall is built in the open with a small, active community of local-first engineers. If you use it, we'd rather hear the honest edge cases than the polished demos.
Join the RailCall DiscordRequest a Connector
Open a thread with the provider name, the smallest useful surface (one endpoint is fine), and a link to their API docs. We prioritize connectors that unlock a full workflow, not just a call.
Ship a Workflow
Contribute governed workflows as PRs to railcall-core. Every accepted workflow ships with its build receipt and a reproducible test.
Product Improvements
File issues with a reproduction, the receipt IDs involved, and what state was UNKNOWN. Verified reports get priority.
Audited Not Anecdotal
The community norm: back claims with receipts. "It works" isn't enough — rcpt_… is.
18 · Testing Manual
Five steps to smoke-test a fresh install. If any step returns UNKNOWN or fails, stop — that's the bug.
Install and version
curl -fsSL https://railcall.ai/install.sh | bash
railcall --version # must print a semverLogin
railcall login # stores device token 0600
railcall plan # shows Free (solo) — unlimited local runsRun the demo flow (dry-run)
railcall demo run --dry-run
# Expected: single signed dry-run receipt, sockets_observed = 0Verify the receipt offline
railcall audit demo --verify --offline
# Expected: OK · signature valid · integrity_root matchesStudio smoke test
railcall studio
# Open http://127.0.0.1:8787 in your browserVerify: left rail lists the demo flow, center shows the build receipt, right pane shows a green integrity root and sockets_observed: 0.
19 · Troubleshooting & FAQ
Common issues
source ~/.zshrc(or your shell's rc file).railcall studio --port 8797.railcall webhook rotate <endpoint>.--offline. If it still reports UNKNOWN, file an issue with the receipt ID.Quick reference
| Command | What it does |
|---|---|
railcall login | Loopback OAuth. Stores device token 0600. |
railcall plan | Show your plan and seats. Local runs are unmetered. |
railcall studio | Launch the local Studio UI. |
railcall audit <id> --verify | Verify a receipt end-to-end. |
railcall receipts export | Export a signed bundle. |