Skip to content

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:

uv add 'kedi[a2a]'

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_timeout bounds local observation. It raises A2ATaskTimeoutError but 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 serve options 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.