Documentation · v0.1 preview

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.

Cheaper AI
Mechanical work runs locally for free; frontier tokens are spent only on the reasoning that needs them.
Local-first & private
Everything runs on 127.0.0.1, BYOK — keys, code, and receipts never leave the box.
Provable governance
Every irreversible action mints an Ed25519 receipt you can verify offline, independent of RailCall.

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

LayerRole
Terminalsource of truth — real commands, real output
Local foldersource of truth — your files on your SSD
Studioorganization layer over the terminal
Receiptsproof layer — evidence for every claim
BYOKintegration layer — your keys, local
Cost model. The engine runs locally and is free forever — code search, file ops, context assembly, and checks never leave your machine and are never metered per run. Frontier tokens (your BYOK Anthropic/OpenAI keys, no markup) are spent only on the reasoning that needs them. Paid is per seat for teams ($20 / seat / month), not per run.

2 · Quickstart

From a fresh shell to a verified receipt and Studio running in five steps.

bash
# 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 studio

Authentication

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.

bash
# Kill Studio then reinstall
railcall studio --kill
curl -fsSL https://railcall.ai/install.sh | bash

3 · 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

Install CLI → curl … | bash
Studio → organizes runs locally on 127.0.0.1
BYOK → add your own provider keys, 0600
Loopback → 127.0.0.1 only, never 0.0.0.0
Team (optional) → sign in to add seats / shared policy

Webhook ingestion path

External sender → optional tunnel → 127.0.0.1:port
Signature check → ingest queue
Airlock decide → dry-run receipt → approve or discard
Live receipt → artifact / connector write
Tunnels note. When a webhook needs to reach your machine from the public internet, RailCall never binds to 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.

Install (one line):
curl -fsSL https://railcall.ai/install.sh | bash
then 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

Builder: English prompt → compose LLM writes typed spec (with command_id + inputs_template per step)
Workflows: click Run → flow_engine routes rows, per-row payloads materialize as airlock previews
Sends: human clicks Approve → binds approval to payload_hash → clicks Execute → real send
Receipts: signed record on disk · integrity_root + Ed25519 · re-verifiable anytime

Tabs — the whole surface

Builder
Describe a workflow in English. The compose LLM writes a typed spec + binds steps to airlock commands. Ships a signed workflow receipt.
Workflows
Every workflow you've built or installed from the marketplace, with a Run button. Marketplace-installed workflows carry creator attribution.
Sends
The human airlock. Fire any of the 10+ wired commands directly, or approve+execute workflow-bridged writes. Configure new providers inline.
Receipts
Every action's signed receipt. Re-verifiable with the independent auditor — integrity_root + Ed25519 signature checked byte-for-byte.
Canvas · Monitor · Integrations · Audit
Visual DAG of a spec · live run + queue view · saved credential vault · signed audit chain.

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.

discord.post_message
Discord webhook
slack.post_webhook
Slack incoming webhook
webhook.post_generic
any HTTP endpoint (Zapier catch-hooks, custom services)
github.create_issue
GitHub REST v3, Bearer PAT
notion.add_page
Notion database page, Notion-Version header
linear.create_issue
Linear GraphQL, raw-key auth
airtable.create_record
Airtable REST, per-base PAT
pagerduty.trigger_incident
PagerDuty Events v2, routing-key auth
stripe.create_refund
Stripe REST + idempotency key from airlock hash
twilio.send_sms
Twilio, HTTP Basic Auth
sendgrid.send_email
SendGrid Mail Send v3
local.csv_append
governed local file — no external touch
local.file_write
governed local file — no external touch

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.

preview: airlock cardapprove: bound to payload_hashexecute: writes signed receiptreplay: refused

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.

Preview → Approve → Execute → Receipt. Nothing skips this path. Not from Studio, not from the CLI, not from MCP, not from a Builder-composed workflow, not from an autonomous AI agent.

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.

Nothing external happens here. Preview writes only to 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.

approved_payload_hash = sha256(canonicalize(inputs))method = ui_click | mcp_tools_call | apitimestamp = server clock at approve

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

compute pre_execution_hash (sha256 over the input + approval + timestamp)
call the wired handler (Discord POST, Stripe refund, GitHub issue, …)
compute post_execution_result_hash (sha256 over the handler's output)
pack into railcall_command_receipt.v1 with integrity_hash + Ed25519 signature
persist to receipts/ with a unique-per-attempt filename

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)

Approve without Preview
no pending record for this hash → blocked_by_policy → receipt says so
Execute without Approve
no approval bound to this payload_hash → blocked_by_policy → receipt says so
Execute the same approved payload twice
second call: approval already consumed → blocked_by_policy → receipt says so
Tamper with inputs between Approve and Execute
recomputed payload_hash mismatch → blocked_by_policy → receipt says so
Handler throws (network error, provider 4xx)
approval still consumed (attempt happened) → failed_safely → receipt captures the error
Global freeze switch on
every write blocked including approved ones → blocked_by_policy → receipt says so
Bad input schema (missing required field, wrong type)
airlock validation catches it before Preview even lands → failed_with_receipt → receipt says which field
No fake green anywhere. Every path — success, refused, blocked, failed — leaves a receipt with the accurate status and a signature. The whole product refuses to return "ok" without proof.

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:

  1. Cmd+Shift+P (macOS) or Ctrl+Shift+P (Windows/Linux)
  2. Type Extensions: Install from VSIX…
  3. Select the downloaded file

Option B — Command line

bash
# macOS / Linux
code --install-extension https://railcall.ai/railcall-vscode.vsix

# Windows (PowerShell)
code --install-extension https://railcall.ai/railcall-vscode.vsix
Marketplace availability: Publication to the Microsoft VS Code Marketplace and Open VSX registry is in review. Once live, you'll be able to install by searching "RailCall" in the Extensions tab. Until then, the direct install above is the supported path.

4.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.

SettingPurpose
railcall.groqApiKeyGroq API key. Fastest option, free tier available.
railcall.anthropicApiKeyAnthropic key for Claude models. Best for code understanding.
railcall.openaiApiKeyOpenAI key for GPT models.
railcall.discordWebhookUrlDiscord incoming webhook. Enables the send-to-Discord action.
railcall.slackWebhookUrlSlack incoming webhook.
railcall.autoSyncKeysAuto-push settings changes to Studio. Default: true.
railcall.serverUrlStudio URL. Default: http://127.0.0.1:8799
railcall.receiptsDirExtra 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 typeWhat happens
explain how this function worksRegular 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 discordAI composes the message → preview card → click Run → real receipt.
/discord good morning teamFast-path send. No AI composition. Preview → confirm.
/search DuckDB vs PostgreSQLWeb 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

ComponentLocal?
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

SymptomCause / 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 installReload the window: Cmd+Shift+P → Reload Window.
Sidebar shows blank contentRare — 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.
Honest status. Version 0.5.7. Actively developed. Not yet on the Microsoft Marketplace — publication is planned. The MCP server is live: Claude Desktop, Cursor, and other MCP clients can drive RailCall directly, and MCP-staged workflows appear in the Pending Approvals view with their full blast radius.

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

bash
# 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
Get a webhook URL from your Discord server: Server Settings → Integrations → Webhooks → New Webhook → Copy URL. The URL is stored in your local vault (~/.railcall/station/.railcall_workspace/keys.local.json), never transmitted anywhere.

How it works

[csv_read] read contacts.csv → 3 rows
[message_template] format each row → "Sami Ben Chaalia · founder · 2024-01-01"
[discord_send] POST each message to Discord webhook → 3 sent ✓

The template is auto-generated from your CSV column names when you omit --template. Every column becomes a {{field}} token.

CLI reference

bash
# 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 set

Available node types

NodeInputOutputConfig keys
csv_readlist of row dictspath
message_templatelist or dictlist of stringstemplate ({{field}} tokens)
filterlistfiltered listfield, value, not_empty
limitlistfirst N itemsn
discord_sendlist of stringssend receiptwebhook_url, dry_run
slack_sendlist of stringssend receiptwebhook_url, dry_run
http_requestanyresponse bodyurl, 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.

json
{
  "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

DescribeComposeBuildAuditReceiptArtifact
StatusMeaning
PASSRan end-to-end with governed writes. Signed live receipt on disk.
PARTIALSome connector writes succeeded, others were held back. Partial receipt keeps the diff.
FAILAborted by airlock or connector. Failure receipt kept for audit.
UNKNOWNVerifier couldn't confirm — treated as not-a-pass.
NEEDS HUMAN QADry-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

ZendeskOpenAIHubSpotSlack
  1. Webhook receives a new ticket from Zendesk.
  2. OpenAI (your key) classifies intent and urgency.
  3. HubSpot contact is enriched with the tags.
  4. Slack channel gets a compact digest.
What RailCall keeps: Receipt bundling ticket ID, classifier prompt hash, HubSpot delta, Slack message ID.

Stripe payout reconciliation

StripeLocal ledgerGoogle SheetsEmail
  1. Poll Stripe for the last payout batch.
  2. Reconcile line items against the local ledger folder.
  3. Write a row per line to a governed Google Sheet.
  4. Email a signed summary to finance.
What RailCall keeps: Receipt with payout ID, row hashes, sheet range written, message-id of the email.

GitHub release → docs + changelog

GitHubOpenAILocal folderDocs site
  1. Webhook fires on a new tag.
  2. OpenAI summarizes the commit range using audited prompts.
  3. Changelog entry is written to your local docs folder.
  4. Docs site build kicks off from the same folder.
What RailCall keeps: Receipt binding tag SHA, prompt hash, file diff SHAs, build job ID.

Nightly ops digest

SentryPostgresLocal reportEmailSlack
  1. Scheduled flow reads last-24h Sentry issues.
  2. Runs a read-only Postgres query for user impact counts.
  3. Renders a signed report file locally.
  4. Emails ops + posts the same summary to Slack.
What RailCall keeps: Receipt: Sentry query hash, SQL hash, report SHA256, email + Slack IDs.

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 classWhere it livesWhat RailCall keeps
Business recordsSource system (Stripe / CRM / DB)Nothing — read on demand, forget after receipt.
Provider keys~/.railcall/keys/ (0600)Encrypted key material, never leaves the machine.
PromptsLocal flow definitionHash + input digest — full text on disk, not uploaded.
Model outputsDestination systemHash + destination pointer.
Receipts~/.railcall/receipts/Full signed JSON, offline verifiable.
FilesYour local foldersPath + SHA256 + size — file itself never uploaded.

Custody & Recovery

ConcernBehavior
Lose your machineRecords are still in their source systems. Reissue keys, re-verify receipts.
Compromise concernrailcall keys rotate
bash
# 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 --offline

8 · 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

ProviderSet commandNotes
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.
Groqrailcall 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:11434
qwen:7b or any model you have pulled locally.
Zero cost. Used automatically for private/PII content.
Discordrailcall set discord-webhook https://discord.com/api/webhooks/…Used by workflow discord_send node.
Slackrailcall 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.

TierWhen usedExample models
0 — nanoShort Q&A, classification, formattingHaiku, GPT-4o-mini, Llama-8b
1 — lightCode snippets, quick answers, general queriesHaiku, GPT-4o-mini, Llama-70b
2 — midDebugging, architecture, multi-step reasoningSonnet, GPT-4o, Llama-70b
3 — heavyVery long context, complex planningOpus, 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
Cost visibility. Every model call is logged to .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

  1. Run railcall set <provider>-key <key> — written to vault at 0600.
  2. Open Studio → the routing badge in the model picker shows the active provider, estimated cost per message, and budget remaining.
  3. Click the badge to open the Router Config panel: change preferred provider or budget without touching the CLI.
  4. Run railcall cost anytime 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

PropertyBehavior
Bind127.0.0.1 only — never 0.0.0.0
AuthHMAC secret per endpoint, rotated with the connector.
IdempotencyProvider event IDs deduped for 24h by default.
BackpressureBounded queue; overflow returns 429 with retry-after.
ReceiptsEvery accepted event mints a signed ingest receipt.

Tunnel path

Provider (Stripe / GitHub / …)
Cloudflared / ngrok / tailscale funnel
127.0.0.1:<port> (RailCall daemon)
HMAC verify → ingest queue → airlock

Test command

bash
# 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.json

Expected response

json
{
  "accepted": true,
  "event_id": "evt_1PXY…",
  "receipt": "rcpt_ingest_01H…",
  "queue_position": 1,
  "next": "airlock_decide"
}
CSRF note. Webhook endpoints are POST-only, HMAC-verified, and reject any request whose 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

F1Hard floor

Irreversible actions always require a human. Money movement, external messaging, external API writes — no policy widens this without a signed receipt.

F2Airlock policy

No live action without a dry-run first. Default requires approval; a scoped, signed rule can auto-approve.

F3Widening lock

Policy can never authorize its own widening. Auto-approve only within the pre-audited component surface.

F4Proof of action

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.

Propose (draft policy in Studio / CLI)
Stage (compute delta vs current policy)
Approve (approver signs Ed25519 receipt)
Land (new policy active · old policy archived)

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

json
{
  "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

FieldMeaning
integrity_rootSHA256 over the ordered set of inputs, prompts, and outputs. Changes iff behavior changed.
connector_writesEvery destination write with a stable pointer (row range, message ID, path).
sockets_observedlsof-based per-run socket sweep. Non-zero = failed receipt.
signatureEd25519 over the canonical JSON. Verifiable without RailCall.

Audit commands

bash
# 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.bundle

12 · 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

PlanPriceWhat you get
Free (solo)$0 foreverLocal engine, single seat, unlimited local runs, signed receipts — local runs are never metered
Team$20 / seat / monthEverything in Free, plus collaboration, shared policy, team receipt history, priority support
EnterpriseCustomAir-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.

bash
# See your plan and seats
railcall plan
# Local runs are free and unmetered — there is no balance to check.

Governed payment flow

Draft payment (amount · rail · destination)
Dry-run (settlement diff · no live send)
Approve (Ed25519 signature over the diff)
Settle (Stripe / x402 / bank rail)
Receipt (signed · references settlement ID)

x402 Agent Economy

ConceptHow RailCall handles it
Agent-to-agent invoiceDraft an x402 invoice; dry-run diff shows amount, memo, and destination.
USDC settlementRequires an F1 hard-floor approval — no autopay by default.
Provider meteringProvider 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.

text
~/.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

FolderPurpose
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.

bash
# 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…
All endpoints are loopback-bound. There is no public API surface — the network never sees these calls.

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

FieldValue
Namerailcall-airlock
Version1.0.0
ProtocolMCP 2025-06-18
Transportstdio JSON-RPC 2.0
Entry pointworkbench/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).

ProviderVerbExample tool
linearcreate_issuelinear_create_issue_plan
slackpost_messageslack_post_message_plan
discordpost_messagediscord_post_message_plan
twiliosend_smstwilio_send_sms_plan
gcalcreate_eventgcal_create_event_plan
gsheetsappend_rowgsheets_append_row_plan
s3put_objects3_put_object_plan
intercomsend_messageintercom_send_message_plan
stripecreate_invoicestripe_create_invoice_plan
airtablecreate_recordairtable_create_record_plan
hubspotcreate_contacthubspot_create_contact_plan
postgresinsert_rowpostgres_insert_row_plan
salesforcecreate_leadsalesforce_create_lead_plan
githubissue_opengithub_issue_open_plan
webhookfire_hookwebhook_hook_fire_plan
teamsmessage_postteams_message_post_plan
telegrammessage_sendtelegram_message_send_plan
notionpage_appendnotion_page_append_plan
resendemail_sendresend_email_send_plan
gdocsdoc_appendgdocs_doc_append_plan
code (apply_patch)patch_applycode_patch_apply_plan
checks (run_tests)runchecks_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.

FamilyToolsWhat it gives the agent
Code intelligencesymbol_find · references_find · implementations_find · callers_find · dependencies_find · related_files · tests_find · file_outline · project_map · semantic_searchFind/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 enginecontext_pack · file_summaryProgressive 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 intelligencelocal_reasonSummarize / 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 workflowsworkflow_blocks · workflow_plan · workflow_applyCompose 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.
Attestationattest · attestation_verifySeal 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.

ToolWhat it does
railcall_integrations_listReturns every registered integration with its action class (reversible / external_send / compensable) and readiness flag.
railcall_receipts_listLists Ed25519-signed receipts from the local workspace, newest first. Supports limit / provider filters.
railcall_receipt_verifyVerifies a receipt's signature and returns the parsed payload — proves what actually ran and against which policy.

Airlock guarantees

RuleEffect
Dry-run by default_apply refuses without a valid consent_token minted by the matching _plan in this session.
Hard floorAny action with action_class = external_send and non-zero spend requires human confirmation — the model cannot bypass it.
Signed receiptsEvery apply writes an Ed25519-signed receipt to the workspace. Verifiable offline; independent of RailCall's servers.
Local-firstThe 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:

bash
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/):

json
{
  "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):

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:

bash
export RAILCALL_MCP_ALLOW_LIVE=1

in 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.

Try it in 30 seconds. After registering the server, ask your MCP client: "List the integrations available via railcall." It will call 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.

The split.
RailCall gives you the map — flows, connectors, receipts, policies — and a local HTTP API. Studio renders that map one way. Your CLI, your dashboard, your Claude Desktop, your Raycast extension can render it any other way. The map is the contract, not the UI.

Hand the map to a model

  • Expose state. Point Claude (or any MCP-aware model) at railcall_integrations_list and railcall_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 a consent_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 Discord

Request 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.

1

Install and version

bash
curl -fsSL https://railcall.ai/install.sh | bash
railcall --version   # must print a semver
2

Login

bash
railcall login   # stores device token 0600
railcall plan    # shows Free (solo) — unlimited local runs
3

Run the demo flow (dry-run)

bash
railcall demo run --dry-run
# Expected: single signed dry-run receipt, sockets_observed = 0
4

Verify the receipt offline

bash
railcall audit demo --verify --offline
# Expected: OK · signature valid · integrity_root matches
5

Studio smoke test

bash
railcall studio
# Open http://127.0.0.1:8787 in your browser

Verify: 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

railcall: command not found after install
Your shell hasn't re-read PATH. Open a new terminal, or run source ~/.zshrc(or your shell's rc file).
Studio port 8787 already in use
Something else is bound to the loopback port. Kill it, or run railcall studio --port 8797.
Webhook returns 401 signature invalid
Provider secret rotated or the endpoint is using an old HMAC. Re-issue with railcall webhook rotate <endpoint>.
Receipt verifier reports UNKNOWN
UNKNOWN is not a pass. Check that your public key file is present, then rerun with --offline. If it still reports UNKNOWN, file an issue with the receipt ID.

Quick reference

CommandWhat it does
railcall loginLoopback OAuth. Stores device token 0600.
railcall planShow your plan and seats. Local runs are unmetered.
railcall studioLaunch the local Studio UI.
railcall audit <id> --verifyVerify a receipt end-to-end.
railcall receipts exportExport a signed bundle.