Reference: A2A Server
Lectic ships an Agent2Agent (A2A) server that exposes configured interlocutors as remote agents. Other A2A-aware clients (including Lectic’s own a2a tool) can talk to those agents using the standard A2A JSON-RPC protocol over HTTP, with Server-Sent Events for streaming.
This page documents the server side: how interlocutors become agents, the HTTP surface that is mounted, the agent-card shape, the task and context lifecycle, and the operational concerns (authentication, persistence, monitoring, and limitations).
The CLI flags are documented in CLI: lectic a2a.
Overview
When you run lectic a2a, Lectic:
- Reads your Lectic configuration via the same
importsresolution used elsewhere. - Selects every interlocutor that has an
a2afield and turns each one into an A2A agent. - Builds an agent card per interlocutor and starts a Bun HTTP server exposing the agent card and a JSON-RPC endpoint per agent.
- Optionally requires a bearer token, and exposes a set of monitoring endpoints for inspecting live tasks.
If no interlocutor declares an a2a field the command fails with No A2A agents configured.
Configuring an interlocutor as an agent
Add an a2a block to any interlocutor you want to expose. Both fields are optional:
interlocutors:
- name: Research Assistant
prompt: You are a research assistant.
a2a:
id: researcher
description: Finds and summarizes academic papers.a2a.id: agent ID used in the URL path. Must match^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$. Defaults to the interlocutor’snameslugified (lowercased, non-alphanumeric runs replaced with-).a2a.description: text used as thedescriptionfield in the agent card. Defaults to"Lectic agent exposed from interlocutor <name>.".
Interlocutors without an a2a block are not exposed by lectic a2a, even if they are otherwise valid speakers in the file.
Starting the server
lectic a2a --root ./my-workspace --port 41240--root is the workspace directory; the server chdirs there before loading config, so relative imports, file:, and exec: references behave the same as they would for lectic invoked from that directory.
On startup the server logs the bound address and one well-known agent card URL per agent:
Lectic A2A server running on http://127.0.0.1:41240 (2 agent(s))
- http://127.0.0.1:41240/agents/researcher/.well-known/agent-card.json
- http://127.0.0.1:41240/agents/editor/.well-known/agent-card.json
If you bind to a non-loopback host without --token, the server prints a warning recommending a token and/or a TLS-terminating reverse proxy. The server itself only speaks plain HTTP.
HTTP surface
For each agent ID :agentId, the server mounts:
GET /agents/:agentId/.well-known/agent-card.json— the public agent card.POST /agents/:agentId/a2a/jsonrpc— the A2A JSON-RPC endpoint.
In addition, it mounts cross-agent monitoring endpoints under /monitor/.... Those are documented in CLI: lectic a2a — Monitoring endpoints.
All non-routed paths return 404. Methods other than the ones listed return 405 method not allowed.
Agent card
The agent card returned at /agents/:agentId/.well-known/agent-card.json has the following shape:
{
"name": "Research Assistant",
"description": "Finds and summarizes academic papers.",
"protocolVersion": "0.3.0",
"version": "<lectic version>",
"preferredTransport": "JSONRPC",
"url": "http://127.0.0.1:41240/agents/researcher/a2a/jsonrpc",
"capabilities": {
"streaming": true,
"pushNotifications": false
},
"defaultInputModes": ["text"],
"defaultOutputModes": ["text"],
"skills": [
{
"id": "chat",
"name": "Chat",
"description": "General chat.",
"tags": ["chat"]
}
]
}Notes:
protocolVersionis the A2A spec version Lectic implements (0.3.0).versionis the running Lectic version (frompackage.json).The agent advertises a single generic
chatskill. Tools the interlocutor uses internally (exec, MCP, etc.) are not exposed as separate A2A skills; they remain implementation details of the agent.pushNotificationsis alwaysfalse. The server does not implement the push-notification methods (see Limitations).If the server is started with
--token, the card adds:{ "securitySchemes": { "lecticBearer": { "type": "http", "scheme": "bearer" } }, "security": [{ "lecticBearer": [] }] }so that compliant clients know to attach a bearer token.
JSON-RPC methods
The JSON-RPC endpoint accepts standard A2A methods. Supported:
message/send— send a user message and receive either aMessage(when the turn finishes within the fast-path window) or aTasksnapshot.message/sendStream— same, but the response is streamed as SSE, yielding the initialTaskfollowed bystatus-updateevents as the agent produces output.tasks/get— fetch a snapshot for a knowntaskId.tasks/resubscribe— re-attach an SSE stream to an in-flight task bytaskId(without replaying earlier message chunks).
Explicitly unsupported (these throw an unsupportedOperation JSON-RPC error):
tasks/canceltasks/pushNotificationConfig/{set,get,list,delete}agent/getAuthenticatedExtendedCard
Streaming responses use the SSE framing data: <json>\n\n with headers Content-Type: text/event-stream, Cache-Control: no-cache, Connection: keep-alive, and X-Accel-Buffering: no. The server-wide idle timeout is disabled, so long-running streams from slow providers do not get prematurely closed.
Contexts and tasks
A2A clients identify ongoing conversations with a contextId and individual turns with a taskId.
contextId
- Each
message/send(ormessage/sendStream) request may include acontextId. If omitted, the server generates a fresh UUID. - Provided IDs must match
^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$; malformed IDs cause aninvalidParamsJSON-RPC error. - Reusing the same
contextIdacross calls is how a client continues an existing conversation. The persisted runtime keys its transcript by(agentId, contextId), so message history is preserved on disk for that context (see Persistence). - The server keeps the last
--max-tasks-per-contexttask snapshots per context in memory (default50). When the cap is exceeded, oldest tasks are dropped from the in-memory store. The persisted transcript itself is not affected.
taskId
- Every call to
message/sendcreates a new task. The server does not allow clients to attach a message to an existing taskId; iftaskIdis supplied tomessage/sendthe call is rejected withinvalidParams. - To poll or re-subscribe to an existing task, use
tasks/getortasks/resubscribewith thetaskIdreturned by the original call. - A task that has been evicted from the in-memory cap returns
Unknown taskId ... (expired or never existed).
Task lifecycle
Each task moves through these states:
| State | Meaning |
|---|---|
submitted |
Created, queued behind any in-flight turn for this context. |
working |
The runtime is actively producing output. |
completed |
The turn finished successfully. |
failed |
The turn raised an error; error carries the message. |
Tasks within a single context are processed sequentially: a new turn for context C will not start until the previous turn for C has reached a terminal state.
Fast-path return on message/send
Non-streaming message/send waits up to 5 seconds for a turn that started immediately to reach completed, and if it does, returns the final assistant Message directly. Otherwise, it returns the current Task snapshot and the client is expected to poll with tasks/get or re-subscribe with tasks/resubscribe.
Streaming output
message/sendStream always returns a streamed Task followed by status-update events:
- The first event is the initial
Tasksnapshot (with any history available so far). - One
status-updateis emitted when the task transitions intoworking. - Subsequent
status-updateevents carry incrementalmessage-shaped chunks of assistant text. Lectic emits one chunk per assistant pass (i.e., per round-trip with the underlying provider that produces user-visible text). - The final event has
final: trueand the terminalstate(completedorfailed).
The server only emits message-shaped updates. It does not emit A2A artifact-update events.
tasks/resubscribe produces the same shape, but does not replay earlier message chunks; it only delivers new ones from the moment of subscription. message/sendStream does replay earlier chunks, which makes a best-effort reconnect by re-sending the same call viable.
Authentication
Lectic supports a single optional bearer token via --token:
- When set, all routes — JSON-RPC and monitoring — require
Authorization: Bearer <token>. Missing or mismatched values return401 UnauthorizedwithWWW-Authenticate: Bearer. - The agent card itself is not gated by the token, so clients can discover it without authentication. The card declares
securitySchemes.lecticBearerso compliant clients know to attach the token to subsequent calls. - Lectic does not support per-agent or per-method tokens, OAuth, or mTLS. For richer auth, terminate TLS and inject auth in a reverse proxy in front of
lectic a2a.
For any deployment beyond 127.0.0.1, set --token and run behind TLS.
Persistence
Each agent gets a PersistedAgentRuntime that writes transcripts to <state-dir>/a2a/<workspaceKey>/<agentId>/<contextId>.lec, where <state-dir> is Lectic’s state directory (typically ~/.local/share/lectic/) and <workspaceKey> is a hash of the workspace’s resolved --root path. Transcripts written from different workspaces therefore land in separate subdirectories, but the files themselves are plain .lec (CommonMark + YAML) text. Treat the state directory as you would any on-disk conversation log.
What is and isn’t persisted:
- Persisted: per-context conversation transcripts (the message history the agent uses on each turn), keyed by
(agentId, contextId). - Not persisted: the in-memory task store (snapshots, event history). Restarting
lectic a2aclears live task state but preserves conversation history. ExistingtaskIds become unknown after a restart.
Monitoring
The /monitor/* endpoints expose live in-memory task state and an SSE event stream useful for dashboards or admin tools. They mirror the JSON-RPC surface but are read-only and Lectic-specific. See CLI: lectic a2a — Monitoring endpoints for the full route list.
When --token is set, monitoring endpoints require the same bearer token as the JSON-RPC endpoint.
Limitations
The server intentionally implements a small slice of the A2A surface. The following are not supported:
- Task cancellation (
tasks/cancel). - Push-notification configs (
tasks/pushNotificationConfig/*). - Authenticated extended agent cards.
- Artifact updates — only message-shaped status updates are emitted.
- Multi-input modes — agents declare
textonly; non-text parts on inbound messages are ignored when extracting the user prompt. - Client-supplied
taskIdonmessage/send— every send creates a new task. - TLS — terminate TLS in a reverse proxy if the server is reachable beyond loopback.
Talking to it from Lectic
You can have a Lectic interlocutor call any A2A server (including this one) using the a2a tool.