A2A Cloud Agents¶
Kedi can serve an exported agent profile as a remote
A2A agent and can use any compatible A2A agent as
an agent-harness. The coordinator may stay local while repository work,
browser automation, or other expensive execution runs in a remote sandbox.
Install the optional protocol and server dependencies:
For persistent task and session state, install kedi[a2a-persistence] instead.
Kedi uses the official a2a-sdk Python package. A2A is a transport boundary,
not a model provider: the serving process owns its model, tools, sandbox, and
approval policy.
Serve a Kedi Profile¶
Only an explicitly exported profile can become the server entry:
> profile: researcher:
> adapter: pydantic
> model: openai:gpt-5.6-luna
> instructions:
Inspect the available evidence before answering.
State uncertainty instead of inventing missing facts.
> export:
researcher
The entry must resolve to a local framework or harness capable of executing a
Kedi child profile. It cannot use a2a as its own primary backend. A model must
be selected by the profile, --model, or KEDI_A2A_MODEL; startup fails before
binding a socket when the executable configuration is incomplete.
Serve it with authentication:
export RESEARCH_AGENT_TOKEN='replace-with-a-secret'
kedi a2a serve research-agent.kedi \
--entry researcher \
--host 0.0.0.0 \
--port 8000 \
--public-url https://agents.example.com \
--auth bearer \
--secret-env RESEARCH_AGENT_TOKEN
--public-url is required when binding beyond loopback. Terminate TLS at the
service or a trusted reverse proxy, and ensure the public URL reaches the same
process. The default JSON-RPC route is /a2a; the protected Agent Card is
published through the official SDK routes.
Supported server authentication modes are Basic, Bearer, and header API key:
kedi a2a serve research-agent.kedi \
--entry researcher \
--auth api-key \
--api-key-header X-Research-Key \
--secret-file /run/secrets/research-agent
Credentials may come from --secret-env, --secret-file, or
KEDI_A2A_SECRET. An interactive terminal may prompt for the secret. Literal
secrets should not be placed in source, shell history, or process arguments.
Use --allow-unauthenticated only for an explicitly loopback-bound development
server.
Persist Tasks and Sessions¶
Persistence is opt-in. Give one server process a dedicated local directory on a persistent volume:
uv add 'kedi[a2a-persistence]'
kedi a2a serve research-agent.kedi \
--entry researcher \
--auth bearer \
--secret-env RESEARCH_AGENT_TOKEN \
--state-dir /var/lib/kedi/research-agent
KEDI_A2A_STATE_DIR is the environment equivalent of --state-dir. The
directory must be private to the serving user; Kedi will create it with
restricted permissions when it does not exist. A process lock prevents a second
server from opening the same directory. The directory is bound to the served
program, its Kedi imports, entry, adapter, model, and history configuration.
Changing these requires a new state directory; Kedi will not silently discard
or reinterpret existing state.
Completed A2A tasks, owner-scoped results, conversation checkpoints, and
referenced artifact payloads survive a process restart. Pydantic AI and
LangChain profiles with history enabled restore their native message
history, not a reconstructed text transcript. Other adapters can persist
stateless task results, but history-enabled serving with an unsupported adapter
fails at startup. Without --state-dir, the existing in-memory behavior remains.
Artifact-producing tools and read_artifact/query_artifact use the same
session-owned store. Tools are rebound to that store after restoration. They do
not share the compiled program's artifact storage with other remote sessions.
A task that was submitted or working when the server stopped becomes
failed with server_interrupted metadata on the next startup. It is not
replayed automatically: a model call or tool may already have had external
effects. A previously canceled task remains canceled. The last committed
session turn is the recovery point; uncommitted turns are not added to history.
Use GetTask to inspect the old task, then start a new attempt deliberately.
Terminal task records cannot be overwritten by late completion, failure, or cancellation events. Task completion and the session checkpoint are committed in one database transaction. Cancellation and shutdown wait for in-flight storage work before releasing session resources or the directory lock; this does not make external tool effects transactional.
Failed checkpoint writes also restore the SDK's in-memory task view. A transient
storage error can then be recorded as failed instead of leaving the client
polling a stale working task. If storage remains unwritable, Kedi cannot promise
a durable failure update: the operation errors, and the last committed task is
reconciled as interrupted after storage recovers and the server restarts. Neither
case automatically repeats model calls or external tool effects.
This mode is single-process and requires a local filesystem with OS file locks. It does not preserve provider sockets, runtime globals, tool-created files, remote sandbox filesystems, or effects in external services. Back up the state directory and its artifact files together. Persistent artifact payloads must use Kedi's safe file codecs; an unsupported value cannot be checkpointed as a usable reference. Each session checkpoint is limited to 64 MiB; session, retained-task, and artifact quotas still apply. Released and expired artifact payloads are compacted after a successful commit while their unavailable status is retained. Artifact expiry and idle deadlines survive restart; reopening the server does not extend an artifact's lifetime. Retention evicts only terminal tasks, including tasks marked interrupted during restart, and never running tasks. The state directory has no global byte quota, so place it on a volume with an explicit capacity limit and free-space monitoring. Disk-full or corrupt referenced payloads fail closed rather than reporting a successful task. Basic-auth password rotation retains the username's state, while rotating a Bearer/API key changes the principal and does not transfer old records.
Connect from Kedi¶
The connection belongs to the selected agent profile, so lexical profile scoping also scopes the endpoint and credential reference:
> profile: cloud_researcher:
> agent: a2a:
endpoint: https://agents.example.com
auth:
scheme: bearer
token_env: RESEARCH_AGENT_TOKEN
request_timeout: 30
task_timeout: 21600
poll_interval: 0.5
@research(question: str) -> str:
> use: cloud_researcher
[answer] << Investigate <question> and return a concise evidence-based answer.
= `answer`
Pass the discovery base URL as endpoint, not the JSON-RPC route from the
Agent Card. Non-loopback endpoints require HTTPS. Embedded credentials, query
strings, and fragments are rejected.
Client authentication fields are:
| Scheme | Required fields | Optional fields |
|---|---|---|
basic |
username, password_env or password_file |
none |
bearer |
token_env or token_file |
none |
api_key |
api_key_env or api_key_file |
header (default X-API-Key) |
The generic aliases credential_env and credential_file are accepted for all
three schemes. Exactly one environment, file, or Python provider source is
required; combining an alias with a scheme-specific source is rejected.
Kedi snapshots the reference, never the resolved credential value.
Structured Results¶
A generic A2A peer is a text agent, so it supports raw invocation such as
[answer] << .... Typed template filling is available only when the Agent Card
advertises Kedi's versioned structured-output extension:
> profile: cloud_researcher:
> agent: a2a:
endpoint: https://agents.example.com
auth:
scheme: api_key
api_key_env: RESEARCH_AGENT_KEY
@inspect(topic: str) -> tuple[str, list[str]]:
> use: cloud_researcher
>> The evidence about <topic> supports [summary: str].
>> The material risks are [risks: list[str]].
= `(summary, risks)`
Kedi sends the JSON Schema through the negotiated extension and validates the returned data before assigning any capture. It does not ask a second model to repair a generic text response. See the structured-output v1 contract.
Remote Subagents¶
An A2A profile can be a child of a local Pydantic AI, LangChain, Codex, or other subagent-capable coordinator:
> profile: cloud_worker:
> agent: a2a:
endpoint: https://agents.example.com
auth:
scheme: bearer
token_env: RESEARCH_AGENT_TOKEN
> profile: coordinator:
> adapter: pydantic
> model: openai:gpt-5.6-luna
> subagent: cloud_worker
@coordinate(question: str) -> str:
> use: coordinator
[answer] << Delegate repository inspection when useful, then answer <question>.
= `answer`
Kedi sends only the task text and, when requested, the final JSON Schema. Local tools, MCP servers, model settings, effort, CodeMode, approval handlers, and replayable model history are not projected into the remote process. Configure those concerns in the served agent.
Python API¶
from pydantic import BaseModel
from kedi.agent_adapter import A2AAdapter
from kedi.agent_profile import A2AAuthConfig, A2AConnection
class Finding(BaseModel):
summary: str
risks: list[str]
connection = A2AConnection(
endpoint="https://agents.example.com",
auth=A2AAuthConfig(
scheme="bearer",
secret_env="RESEARCH_AGENT_TOKEN",
),
task_timeout=21_600,
)
agent = A2AAdapter(connection=connection)
text = agent.invoke_sync(prompt="Inspect the release candidate.")
finding = agent.produce_sync(
"The release evidence supports [summary], with risks [risks].",
output_type=Finding,
)
get_task() reads a known task without resending it, cancel_task() requests
cancellation, and continue_task() resumes an input_required or
auth_required task. Synchronous variants use the _sync suffix. Lifecycle
errors retain known task_id, context_id, and state coordinates.
Recovery and Timeouts¶
- Stream failure after Kedi learns the task ID falls back to status polling.
- An ambiguous send with no task ID is never resent automatically; duplication
would be less safe than surfacing
A2AAmbiguousSendError. task_timeoutbounds local observation. It raisesA2ATaskTimeoutErrorbut does not cancel remote work. Use the task ID to inspect or cancel it later.- Conversation continuation stores only remote task/context coordinates. A2A does not provide full replayable model history to Kedi.
Security and Deployment Limits¶
- A remote endpoint must use HTTPS; plain HTTP is limited to loopback hosts.
- Agent Card authentication requirements must match the configured client credential, and advertised JSON-RPC interfaces must remain same-origin.
- Request body, input, schema, result, pending-task, session, concurrency, and
retained-task counts are bounded. Tune the corresponding
kedi a2a serveoptions for the deployment. - Tasks and sessions are scoped by authenticated principal. Queued and running
calls keep their in-memory sessions alive; only idle sessions can be evicted.
Calls sharing a context run sequentially without consuming concurrency slots
while waiting for that context. Without
--state-dir, restart loses that state. With it, completed state is restored, but live execution is not. Neither mode supports multiple workers sharing one state directory. - Shutdown stops SDK tasks and drains active and queued invocations before closing session and runtime resources.
- A2A is not a sandbox. Run the server with filesystem, process, network, and secret permissions appropriate for the exported profile.
- OAuth/OIDC, mTLS identity, push notifications, remote file transfer, webhooks, and cross-process task ownership are not implemented by this release.
Use an external gateway for rate limiting and production identity controls. The built-in server provides bounded capacity and credential authentication, not a complete public-edge security product.