liminal-cli
The liminal command serves a seat's discovery and service MCP over stdio.
The seat's host runs liminal mcp --config <seat.toml>, or names the seat
by flags alone (below).
Install the command with cargo install liminal-cli. The seat file's server
is the address of the liminal server reachable from that laptop. The client
wire is plain TCP, so use a named address on an encrypted network such as the
tailnet.
= "100.64.0.1:7600"
= "agents"
[]
= "https://lys.example"
[]
= 1048576
= 1048576
= 30000
= 32
= 1048576
When Lys starts the host, it supplies LYS_AGENT and LYS_AGENT_PASS.
With no key_file, the command uses that run pass to ask Lys for the seat's
app passes. It holds each until its expiry and asks again when needed.
The run pass and app passes are never printed or written to the seat file.
A certified agent can keep the signed route by adding agent = "agent-id"
and key_file = "/absolute/path/to/agent-key.pem" to [lys]. A fixed pass
can instead be supplied by a top-level token_file, with no [lys] table.
One registration, the identity at run time
A seat needs no file of its own. liminal mcp takes the seat from flags, and
with no --config the channel (console.topology) and limits are those
liminal seat init writes. One registration line serves every person and
agent on an exchange, and only the identity differs:
- Open exchange:
liminal mcp --server 100.64.0.1:7600 --as <name>. The name is declared asopen:<name>, and its first listen placesagents.<name>. Off loopback the seat reaches the network, asseat init --openwrites it. - Lys exchange:
liminal mcp --agent <agent-id> --key-file /path/to/agent-key.pem(and--serverwhen it is not the central exchange). The key is the identity; nothing is claimed by name.
--channel sets the channel. Beside --config each flag wins over the
file's value. The seat's kind never changes by flag: --as beside [lys] or
token_file is refused, --agent beside [open] is refused, and --agent
and --key-file are refused alone. No error shows a key's or a pass's bytes.
A hosted assistant on an open exchange
liminal agent run --config <seat.toml> hosts an ACP harness (Claude Code,
for one) as the station its seat names. On an open exchange the seat carries
[open] name = "<name>" in place of [lys]: it is presented as
open:<name>, holds no pass, and its first session places agents.<name>.
Off loopback add reach = "network" to [open].
= "127.0.0.1:7700"
= "console.topology"
[]
= "assistant"
[]
= 65536
= 1048576
= 10000
= 8
= 65536
[]
= "claude-code"
= "/absolute/path/to/the/acp/agent"
= []
= "/absolute/working/directory"
A seat with both [lys] and [open], or neither, is refused by name.
[harness] env sets variables on the harness and env_remove removes
them, so a supervised host can say what the harness needs, for example
env_remove = ["CLAUDECODE"] with [harness.env] naming
CLAUDE_CODE_EXECUTABLE and CLAUDE_CONFIG_DIR. Values never appear in a
refusal. An [open] seat runs whatever its stdin is; only a [lys] seat on
a pipe ends when its launcher closes it.
Answers of any size
liminal mcp writes an answer inline up to [limits] answer_bytes
(16 MiB when unnamed). A larger answer is handed over whole as a file the
seat owns (under ~/.liminal/answers), named by its sha256, with its path, size and a preview in the
answer; the seat removes the file when it ends. frame_bytes bounds only
the lines the MCP host sends. flow takes answer: "last" and a per-step
keep: false so large intermediate replies feed the next step without
coming back.
The command also serves a server configuration with
liminal --config <server.toml> and validates it without opening sockets or
the store with liminal check --config <server.toml>. A server may declare
extra_listen_addresses = ["100.64.0.1:7600"] beside its primary
listen_address; each extra address must name an interface.