# Legatus
> **JSON-RPC STDIO ↔ HTTP Proxy**
> _A messenger agent speaking between worlds_
**Legatus** is a minimal, stateless bridge that translates dialogue over STDIN into HTTP actions. It embodies the Umwelt principle of perception–action unity: every request passes through a complete functional circle — perception, cognition, action — before returning as a meaningful response.
## Why Legatus Exists
Legatus solves a fundamental problem: **how to let any process speak JSON-RPC over STDIO while delegating execution to an HTTP realm.**
Use cases:
- **Editor integrations**: Connect your editor to language servers over HTTP
- **CLI tools**: Build command-line tools that proxy to HTTP APIs
- **Agent communication**: Enable processes to communicate via standard streams
- **Testing**: Interact with HTTP JSON-RPC servers from shell scripts
Legatus is not merely a tool; it is a **ritual of translation** — a disciplined pattern for agent-to-world communication.
## 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
```
**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.
## Philosophy: The Umwelt Architecture
The architecture follows Jakob von Uexküll's **Umwelt** concept — organisms perceive and act within their own "subjective universe":
- **Aussenwelt** — The external world (STDIO streams)
- **Merkwelt** — Perception layer (request validation)
- **Wirkwelt** — Action layer (HTTP transport)
- **Umwelt** — The complete functional circle (pipeline coordinator)
- **Geist** — The animating principle (main loop)
Legatus acts within the **Clausura Operationalis** — it knows only the world it perceives and acts upon it coherently. Its simplicity is not limitation but discipline.
## Usage Modes
### As Escript (recommended for production)
```bash
mix escript.build
./legatus http://localhost:4000/rpc
```
### As Mix Task (for development)
```bash
mix 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 mix task
token=your_secret_token mix 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"}
}
}
}
```
### Pipeline Flow
Every request flows through the complete Umwelt cycle:
```
STDIN → Geist.loop → Aussenwelt.receptio → Merkwelt.percipere →
Wirkwelt.portare → Aussenwelt.profanatio → STDOUT
```
Each arrow represents a transformation of meaning, not just data.
## 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
### Module Responsibilities
- **Legatus** — Entry point and configuration
- **Geist** — Main loop (STDIN → STDOUT cycle)
- **Aussenwelt** — I/O boundaries (parse/format JSON)
- **Merkwelt** — Request validation (JSON-RPC compliance)
- **Wirkwelt** — HTTP coordinator (interprets transport responses into Umwelt concepts)
- **Wirkwelt.Httpc** — HTTP transport (pure POST with status/body)
- **Chronica** — Logging (stderr diagnostics)
### Transport Layer Design
Wirkwelt follows a clean separation of concerns:
**Wirkwelt behaviour contract:**
```elixir
@callback post(request :: map()) ::
{status :: pos_integer(), body :: map() | String.t()} | {:error, reason :: term()}
```
- **Transport (Httpc)**: Returns raw HTTP responses `{200, body}` or `{:error, reason}`
- **Coordinator (Wirkwelt)**: Interprets responses into `{:gloria, map()}`, `{:silentium, map()}`, or `{:fiasco, map()}`
This design makes it easy to add new HTTP adapters (Finch, Hackney) without changing business logic.
### 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 argument** — no static config required:
```bash
mix legatus http://localhost:4000/rpc
```
The upstream URL is set dynamically at runtime via `Application.put_env/3`.
## Design Principles
1. **Tagged tuples** for explicit data flow
2. **Pure functions** where possible (I/O at boundaries)
3. **Pattern matching** over conditionals
4. **Separation of concerns** (parse, validate, transport, format)
5. **Testability** through dependency injection
## JSON-RPC Support
### Requests
- ✅ Standard requests with `id`
- ✅ Notifications (no `id`)
- ✅ Batches (array of requests)
### Responses
- ✅ Success responses (`result`)
- ✅ Error responses (`error`)
- ✅ HTTP 204 handling (notifications)
- ✅ Batch responses
### Limitations
- STDIO-based (one request per line)
- HTTP POST only (no WebSocket/SSE)
- No built-in retry logic
- Single upstream server
## License
See LICENSE file.
## Etymology
- **Legatus** (Latin) — envoy, ambassador, messenger
- **Umwelt** (German) — environment, "self-centered world"
- **Geist** (German) — spirit, mind, animating principle
- **Aussenwelt** (German) — outer world
- **Merkwelt** (German) — perceptual world
- **Wirkwelt** (German) — world of action
## Next Steps
**Explore further:**
- Read [umwelt.dev/docs](https://umwelt.dev/docs) for philosophical foundations
- Join the discussion in [Issues](https://github.com/sovetnik/legatus/issues)
- Contribute via [Pull Requests](https://github.com/sovetnik/legatus/pulls)
**Related projects:**
- **Skull** — The cognitive substrate for agent systems
- **Reticulum Universalis** — Communication patterns for distributed agents
Legatus is an experiment in **operational closure** — every component speaks the same language of perception and action. If this resonates with you, welcome to the conversation.