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:

Legatus is not merely a tool; it is a ritual of translation — a disciplined pattern for agent-to-world communication.

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

Expected output:

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

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)

mix escript.build
./legatus http://localhost:4000/rpc

As Mix Task (for development)

mix 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 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.):

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

Transport Layer Design

Wirkwelt follows a clean separation of concerns:

Wirkwelt behaviour contract:

@callback post(request :: map()) :: 
  {status :: pos_integer(), body :: map() | String.t()} | {:error, reason :: term()}

This design makes it easy to add new HTTP adapters (Finch, Hackney) without changing business logic.

Error Handling

All errors are JSON-RPC compliant:

Configuration

Legatus is configured via command-line argument — no static config required:

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

Responses

Limitations

License

See LICENSE file.

Etymology

Next Steps

Explore further:

Related projects:

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.