Legatus
JSON-RPC boundary: STDIO ↔ HTTP / WebSocket
Legatus is a protocol boundary process.
It reads JSON-RPC from STDIN and relays messages through one of two modes:
:http— request/response transport to upstream HTTP endpoint:websocket— bidirectional relay over persistent WebSocket
Why Legatus Exists
Legatus solves one specific problem: make STDIO JSON-RPC clients speak to remote transports without embedding transport logic in the client.
Quick Start
# Build the escript
mix escript.install hex legatus
# or
git clone git@github.com:sovetnik/legatus.git
mix escript.build
# Start your JSON-RPC HTTP server (example on port 4000)
# Then send a request:
echo '{"jsonrpc":"2.0","method":"add","params":[2,3],"id":1}' | \
./legatus http://localhost:4000/rpc
mix escript.build собирает legatus локально, а mix escript.install устанавливает его как команду, доступную из PATH.
Expected output:
{"jsonrpc":"2.0","result":5,"id":1}
That's it. One line in, one line out. STDIO becomes HTTP, HTTP becomes STDIO.
Architecture (Current)
Layers
Legatus.Paramount
Process orchestration and runtime lifecycle.Legatus.Canalis.*
Concrete channels/transports (Stdio,Http,Ws).Legatus.Umwelt.*
Interpretation pipeline:Merkwelt.distinctio -> Verstand.descriptio -> Wirkwelt.portare.Legatus.Aussenwelt
Boundary parse/format (receptio/profanatio).
Usage Modes
As Escript (recommended for production)
mix escript.build
./legatus http://localhost:4000/rpc
With Bearer Token Authentication
When your upstream server requires authentication, pass the token via environment variable:
# Using escript
token=your_secret_token ./legatus http://localhost:4000/rpc
# Using escript
token=your_secret_token ./legatus http://localhost:4000/rpc
Legatus will automatically add the Authorization: Bearer <token> header to all HTTP requests.
Editor integration example (Zed, Claude Code, etc.):
{
"context_servers": {
"my_server": {
"source": "custom",
"enabled": true,
"command": "legatus",
"args": ["http://localhost:4000/rpc"],
"env": {"token": "your_secret_token"}
}
}
}
Flows
HTTP mode:
STDIN -> Aussenwelt.receptio -> Umwelt.percipere -> Canalis.Http.Client -> Aussenwelt.profanatio -> STDOUT
WebSocket mode:
STDIN (uplink) -> Aussenwelt.receptio -> Umwelt.percipere -> Canalis.Ws.send_request
Canalis.Ws.receive_message (downlink) -> Aussenwelt.receptio -> Umwelt.percipere -> STDOUT
Architecture
Data Flow
The pipeline uses tagged tuples to track data state:
Receptio (Aussenwelt): Parse JSON
{:phaenomenon, map}|{:fiasco, json_error}Percipere (Merkwelt): Validate request
{:actio, map}|{:fiasco, error_map}Portare (Wirkwelt): HTTP transport
{:gloria, map}|{:fiasco, error_map}|{:silentium, map}Profanatio (Aussenwelt): Format output
{:gloria, json}|{:fiasco, json}|{:silentium, "Nullius in verba"}Emit (Geist): Write to STDOUT or skip
Key Modules
Legatus— entrypoint (invoke/2,invoke/3)Legatus.Paramount.Http— HTTP runtime processLegatus.Paramount.Ws— WebSocket runtime processLegatus.Canalis.Stdio— STDIN/STDOUT boundaryLegatus.Canalis.Http.Client— HTTP POST transportLegatus.Canalis.Ws— stateful WS client processLegatus.Paramount.Memento— pending request ids for WS close semanticsLegatus.Chronica— logging
Runtime Semantics
- In
:httpmode, runtime terminates when STDIN reaches EOF. - In
:websocketmode, runtime terminates on WS close/down and emits JSON-RPC error-32001with message"connection_closed"for pending requests.
Error Handling
All errors are JSON-RPC compliant:
-32700Parse error (invalid JSON)-32600Invalid Request (missing method)-32000HTTP errors (4xx/5xx)-32001Transport errors (connection refused)
Configuration
Legatus is configured via command-line arguments:
./legatus http://localhost:4000/rpc
Mode selection:
./legatus http://localhost:4000/rpc
./legatus ws://localhost:4000/ws --ws
Testing
- Process lifecycle assertions:
ExUnitEx(assert_processes_started/stopped) - Queue/waiting semantics for WS channel memory:
test/legatus/canalis/ws/memento_test.exs - Boundary parse/format semantics:
Aussenwelttests - Integration runtime tests:
test/legatus/paramount/http_test.exstest/legatus/paramount_test.exs
JSON-RPC Support
Requests
- ✅ Standard requests with
id - ✅ Notifications (no
id) - ❌ Batches (array of requests) are not supported
Responses
- ✅ Success responses (
result) - ✅ Error responses (
error) - ✅ HTTP 204 handling (notifications)
Limitations
- One runtime per process invocation
- No retry/backoff policy built in
- No business logic; transport/translation only
- No JSON-RPC batch request/response support
License
See LICENSE file.
Etymology
- Legatus (Latin) — envoy, messenger
- Aussenwelt — outer world
- Merkwelt — perceptual distinction
- Verstand — interpretation/description
- Wirkwelt — action world
- Paramount — mount-point of runtime process reality