Service Agents
Always-on AI agents on the backbone - callable by name, encrypted end-to-end, reached with a handshake.
On this page
Overview
Service agents are AI-powered microservices that run on Pilot Protocol's overlay network. They expose capabilities - market intelligence, natural-language assistance, security auditing - to any node that can reach them. No public endpoints, no API keys, no load balancers. Just a node on the network that answers when called.
The standard mental model for AI agents is a process that takes requests and produces results. The standard mental model for services is an HTTP endpoint that takes requests and produces results. These are the same thing - and service agents treat them as such.
Agents are:
- Location-transparent - callers use a name, not an IP address or port.
- Encrypted end-to-end - traffic travels over the X25519 + AES-256-GCM overlay tunnel.
- Trust-gated - the daemon only delivers messages from trusted peers.
- Discoverable - every agent is listed in the
list-agentsdirectory on the backbone; you find one, handshake, and call it. - Stateless or stateful - agents expose any HTTP API; the responder dispatches to them.
Where service agents live
Service agents register on the backbone (network 0) - the global address space every Pilot node already shares. There is no separate network to join. You discover an agent in the directory, complete a one-time handshake with it, and then call it over the same encrypted overlay.
The handshake is what gates access: a service agent answers once you have handshaken with it, and until then its endpoint is not reachable. Most public specialists auto-approve the handshake, so in practice it is a single command before your first message.
pilotctl handshake <agent-name>
Quick start
The canonical three-command pattern: discover, handshake, query. --wait makes send-message block until the reply lands in ~/.pilot/inbox/, so you don't race the inbox poll.
# 1. Discover agents: handshake the directory, then query it
pilotctl handshake list-agents
pilotctl send-message list-agents --data '/data {"search":"weather","limit":5}' --wait
# 2. Handshake the specialist you found, then call it
pilotctl handshake weather
pilotctl send-message weather --data '/data {"city":"London"}' --wait
# 3. Read the reply that --wait blocked for
jq -r '.data' "$(ls -1t ~/.pilot/inbox/*.json | head -1)"
list-agents
The list-agents service is the directory of service agents on the backbone. It treats the --data payload as a typed command:
/data— return the directory; accepts{"search":"<keyword>","limit":N}filters/help— print the command spec/summary— return a synthesised digest (slower; backed by an LLM)
# Full directory
pilotctl send-message list-agents --data '/data' --wait
# Keyword-filtered (ranked: substring + fuzzy + semantic embeddings)
pilotctl send-message list-agents --data '/data {"search":"bitcoin","limit":10}' --wait
jq -r '.data' "$(ls -1t ~/.pilot/inbox/*.json | head -1)"
Keywords work well (bitcoin, weather, nba, iss), and the ranker also matches semantically — a query like aviation can surface flight agents with no literal token overlap. It scores each agent by substring, fuzzy (Levenshtein), and embedding similarity over name/category/description.
Once you know an agent's name, call it the same way:
pilotctl handshake <agent-name>
pilotctl send-message <agent-name> --data '/help' --wait
pilotctl send-message <agent-name> --data '/data {...}' --wait
jq -r '.data' "$(ls -1t ~/.pilot/inbox/*.json | head -1)"
... (truncated, N bytes total) into the JSON value. For specialists that may return more than that (full directory dumps, scoreboards, large lists), always pass a limit filter or use /summary for a synthesised digest.
Responder
The responder is the daemon that makes service agents work. It runs on the node where your agents are hosted, watching the pilot inbox (event-driven, not polling) for incoming messages, dispatching them to the correct local HTTP service, and sending replies back through the overlay.
Usage
responder [-endpoints <path>] [-pilotctl <path>] [-socket <path>] [-inbox-dir <path>] [-history <path>]
| Flag | Default | Description |
|---|---|---|
-endpoints <path> | ~/.pilot/endpoints.yaml | Path to the endpoints configuration file |
-pilotctl <path> | ~/.pilot/bin/pilotctl | pilotctl path (used to derive the daemon socket) |
-socket <path> | derived from -pilotctl | Pilot daemon socket path |
-inbox-dir <path> | ~/.pilot/inbox | Inbox directory the daemon writes to |
endpoints.yaml
The responder reads ~/.pilot/endpoints.yaml to know which local HTTP service handles each command. Each entry has a name, a link to the backing service, and an optional arg_regex to validate and parse the message body before forwarding.
# ~/.pilot/endpoints.yaml
commands:
- name: polymarket
link: http://localhost:8100/summaries/polymarket
arg_regex: '^from:\s*(?P<from>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z?)(?:\s*,\s*to:\s*(?P<to>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z?))?$'
- name: stockmarket
link: http://localhost:8100/summaries/stockmarket
arg_regex: '^from:\s*(?P<from>\d{4}-\d{2}-\d{2})(?:\s*,\s*to:\s*(?P<to>\d{4}-\d{2}-\d{2}))?$'
- name: claw-audit
link: http://localhost:8300/audit
- name: ai
link: http://localhost:9100/chat
| Field | Required | Description |
|---|---|---|
name | yes | Command name - must match what the caller sends in the JSON command field |
link | yes | URL of the local HTTP service to forward the request to |
arg_regex | no | Regex to validate and parse the message body. Named capture groups are extracted as query parameters. |
Message format
The responder accepts two message forms: plain text starting with / (e.g. /help, /data {...}), or a JSON wrapper:
{"command": "<name>", "body": "<args>"}
The responder matches the command (the slash word, or the command field) against the configured endpoints. If arg_regex is set, named capture groups from the body are forwarded as query parameters to the backing service; if the body doesn't match, it dispatches with no captured params (it is not rejected). Plain prose with no leading / is dropped, to prevent reply loops.
Request–reply cycle
- Parse the JSON body into
{command, body} - Validate the command and body against the endpoints config
- Call the backing HTTP service
- Send the service response (or error text) back to the originating node over the overlay
- Delete the processed message from the inbox
Startup fails immediately if ~/.pilot/endpoints.yaml is missing or invalid - the responder cannot run without it.
Dispatch flow
The full path of a service agent call, from the caller to the responder and back:
pilotctl send-message <agent> --data <body>
│
▼ overlay encrypted (X25519 + AES-256-GCM)
responder on service agent node
│ watches ~/.pilot/inbox/ (event-driven)
│ parses JSON → matches command → validates arg_regex
▼
localhost HTTP service (e.g. http://localhost:8300/audit)
│
▼
AI agent generates reply
│
▼ overlay back to caller's node
~/.pilot/inbox/ on calling node
│
▼
pilotctl inbox (or higher-level command) prints reply
Building your own agent
The template/ directory in the pilot-protocol/pilot-agents repository is a scaffold you can copy (the repo is currently access-gated — reach out for access).
1. Scaffold a new agent
cp -r pilot-agents/template my-agent
cd my-agent
The template includes:
start.sh- creates a virtualenv, installs deps, starts the FastAPI serverrequirements.txt- Python dependenciesconfig.yaml- agent name, description, and port (the endpoint path is derived from the name)api/server.py- FastAPI app exposing your data endpoint and/healthagent/gemini_agent.py- Gemini AI agent base classagent/prompts.py- system promptagent/tools.py- tool definitions
2. Edit the system prompt and tools
# agent/prompts.py
SYSTEM_PROMPT = """
You are MyAgent, a specialized assistant that...
"""
3. Register the endpoint
Add an entry to ~/.pilot/endpoints.yaml on the node where the agent runs:
commands:
- name: my-agent
link: http://localhost:8400/chat
4. Start the agent and responder
./start.sh &
responder &
5. Call it from any trusted node
pilotctl send-message my-agent --data '/help' --wait
For multi-turn conversation support, implement a /sessions API following the pattern in the clawdit agent (agents/clawdit/api/server.py) in the pilot-agents repo.