# 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
```bash
# 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:**
```json
{"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)
```bash
mix escript.build
./legatus http://localhost:4000/rpc
```
### With Bearer Token Authentication
When your upstream server requires authentication, pass the token via environment variable:
```bash
# 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.):**
```json
{
"context_servers": {
"my_server": {
"source": "custom",
"enabled": true,
"command": "legatus",
"args": ["http://localhost:4000/rpc"],
"env": {"token": "your_secret_token"}
}
}
}
```
### Flows
HTTP mode:
```text
STDIN -> Aussenwelt.receptio -> Umwelt.percipere -> Canalis.Http.Client -> Aussenwelt.profanatio -> STDOUT
```
WebSocket mode:
```text
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:
1. **Receptio** (Aussenwelt): Parse JSON
`{:phaenomenon, map}` | `{:fiasco, json_error}`
2. **Percipere** (Merkwelt): Validate request
`{:actio, map}` | `{:fiasco, error_map}`
3. **Portare** (Wirkwelt): HTTP transport
`{:gloria, map}` | `{:fiasco, error_map}` | `{:silentium, map}`
4. **Profanatio** (Aussenwelt): Format output
`{:gloria, json}` | `{:fiasco, json}` | `{:silentium, "Nullius in verba"}`
5. **Emit** (Geist): Write to STDOUT or skip
### Key Modules
- `Legatus` — entrypoint (`invoke/2`, `invoke/3`)
- `Legatus.Paramount.Http` — HTTP runtime process
- `Legatus.Paramount.Ws` — WebSocket runtime process
- `Legatus.Canalis.Stdio` — STDIN/STDOUT boundary
- `Legatus.Canalis.Http.Client` — HTTP POST transport
- `Legatus.Canalis.Ws` — stateful WS client process
- `Legatus.Paramount.Memento` — pending request ids for WS close semantics
- `Legatus.Chronica` — logging
### Runtime Semantics
- In `:http` mode, runtime terminates when STDIN reaches EOF.
- In `:websocket` mode, runtime terminates on WS close/down and emits JSON-RPC error
`-32001` with message `"connection_closed"` for pending requests.
### Error Handling
All errors are JSON-RPC compliant:
- `-32700` Parse error (invalid JSON)
- `-32600` Invalid Request (missing method)
- `-32000` HTTP errors (4xx/5xx)
- `-32001` Transport errors (connection refused)
## Configuration
Legatus is configured via command-line arguments:
```bash
./legatus http://localhost:4000/rpc
```
Mode selection:
```bash
./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: `Aussenwelt` tests
- Integration runtime tests:
- `test/legatus/paramount/http_test.exs`
- `test/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