Skip to main content
MemoryLake implements the A2A (Agent-to-Agent) protocol by Google. If you are already familiar with A2A, you can use any A2A-compatible client to interact with MemoryLake agents. Refer to the A2A specification for the standard request and response schemas. Official SDKs are available at github.com/a2aproject.
Permission required: workspace_agent:a2a

Base URL

Append standard A2A paths directly — for example POST {baseUrl}/message:send or GET {baseUrl}/tasks/{taskId}.

Protocol Versions

MemoryLake exposes two parallel sets of A2A routes, one per protocol version, and each version is reachable over two transports: REST and JSON-RPC 2.0.
The /v1/ path prefix refers to the older A2A v0.3 wire format, not to protocol version 1.0. The un-prefixed routes implement the current A2A v1.0 format. Make sure the request body shape matches the route you call — a v0.3-style REST body (message.content) is rejected by the v1.0 REST routes, and vice versa.
Over JSON-RPC, both versions post to the bare {baseUrl} and pick the operation with method: v1.0 uses SendMessage, SendStreamingMessage, GetTask, CancelTask, SubscribeToTask; v0.3 uses message/send, message/stream, tasks/get, tasks/cancel, tasks/resubscribe. The protocol version is negotiated from the method name, and v1.0 clients should also send A2A-Version: 1.0. All four route families accept the same configuration object and the same MemoryLake extension described below. The two protocol versions spell the wait-for-completion switch differently:
Push notifications aren’t supported. The agent card advertises pushNotifications: false, and no callbacks are sent for pushNotificationConfig (v0.3) or taskPushNotificationConfig (v1.0). To follow a task, stream the response or subscribe to the task.
One message at a time per conversation. Messages that share a contextId belong to the same conversation, and a conversation processes one message at a time. While a message is still being processed, including a non-blocking send whose task has not finished, another message sent with the same contextId is rejected with 400 INVALID_ARGUMENT, not queued. Wait for the previous response or task to finish before you send the next message. To run requests in parallel, give each one its own contextId.One agent per conversation. A contextId belongs to the agent it was first used with. A message sent to a different agent with the same contextId is rejected. To talk to another agent, start a new conversation with a new contextId.

MemoryLake Extension

MemoryLake extends the standard A2A protocol through the metadata.memorylake namespace. Put it in the metadata of the send request (top-level metadata in a REST body, params.metadata in JSON-RPC) on message:send or message:stream. The agent doesn’t read message.metadata for this. This is the key difference from a vanilla A2A server — these fields connect the agent to workspaces, projects, and actors in MemoryLake. The object carries two kinds of fields:
  • Memory and identity: actorId, readWriteProjectId, readOnlyProjectIds and skipMemory decide whose memory the agent uses and which projects it reads and writes.
  • Run configuration: overrides and runtimeOptions change how the agent handles this message.
Use the camelCase field names shown on this page. Unknown fields are ignored rather than rejected, so if a setting seems to have no effect, check its spelling first. The agent doesn’t read keys under metadata other than memorylake.
metadata.memorylake is required. Without it the request fails with 400 INVALID_ARGUMENT. Send an empty object {} to use the defaults: the agent works with your own actor, and memory is on.

Memory and identity

string
The end user this message comes from, as a MemoryLake actor ID (actor- followed by 32 hex characters). The agent searches this actor’s memories, and the user’s side of the conversation is recorded under this actor, which gives each end user their own memory.
  • Default: the authenticated user’s own actor in this workspace. If that user has no actor here, the request fails with 400 STATE_NOT_READY.
  • Must be an actor bound to this workspace. Any other ID returns 400, and a deleted actor returns 400 ACTOR_INACTIVE.
  • Permissions: workspace_actor:fact_add and workspace_actor:fact_search on the actor. They’re checked when the conversation starts, and again when a later message brings an actor the conversation hasn’t had before.
string
The project the agent works in (proj- followed by 32 hex characters). The agent searches this project’s documents and memories, and the conversation is recorded against it, which is where memories extracted from the conversation are kept. Conversation history itself is stored at the workspace level.
  • Fixed for the conversation. The first message of a conversation (contextId) binds the project. Later messages can leave it out and keep using it. Sending a different project, or adding one to a conversation that started without a project, fails with 400.
  • Same workspace only. A project from another workspace returns 404.
  • Permissions: project:mem_add, project:doc_add, project:mem_search and project:doc_search on the project, checked when the conversation starts.
  • Required for file attachments. Files in the message (drive:// references or inline bytes) are imported into this project as documents. A message that carries files must name the project itself; inheriting it from the conversation isn’t enough, and the request fails with 400.
string[]
More projects the agent can search, both documents and memories, without writing to them. Use it to give the agent a shared knowledge base.
  • Applies to this message only. It isn’t stored on the conversation, so send it with every message that should see these projects.
  • Permissions: project:mem_search and project:doc_search on each project. Missing either returns 403.
  • IDs that don’t match a project are skipped without an error.
boolean
default:"false"
true runs this message without memory: the agent doesn’t search memories or project documents, and nothing from this message is written to memory. The conversation is still created, and access to the actor and projects is still checked.

Run configuration

object
Changes the agent’s configuration for this message only. Anything you leave out comes from the agent.
object
Run options for this message.

Turn on capabilities per message

overrides.capabilitiesOverride turns on extra capabilities for a single message. It is an object keyed by capability name, and each value is that capability’s configuration. Capability names and their fields are snake_case, unlike the camelCase fields around them (camelCase spellings such as webSearch and openDatasets are also accepted).
Keep in mind:
  • It applies to this message only. The next message in the same conversation doesn’t get the capability unless it carries the override too.
  • Agents don’t have these capabilities by default. Without the override, an agent searches the web or reads pages only if its own configuration turns that on (see capabilities in Create Agent). pii and vega can only be turned on here.
  • What you pass replaces the agent’s own configuration. Send the complete configuration you want for a capability, and don’t count on fields carrying over from the agent. For example, if the agent’s web_search sets open_datasets and you pass "web_search": {}, this message searches no Open Data collections.
  • You can turn capabilities on, not off. Leaving a capability out keeps the agent’s own setting, and null doesn’t turn off a capability the agent has. {"enabled": false} is worse: there is no enabled field, so it reads as {} and turns the capability on with default settings, even for an agent that didn’t have it. To run without a capability, use an agent that doesn’t have it.
Mistakes don’t return errors. A misspelled capability name is ignored. If a capability’s configuration is invalid, for example open_datasets given as a string or web_search given as true, the whole capability is dropped and the request still returns 200, just without it. Unknown Open Data identifiers are skipped, and if none are valid the agent searches the web only. The request fails with 400 only when capabilitiesOverride itself isn’t an object, such as an array. If the agent acts as though a capability is off, check the capability names and configuration in your request first.

Examples

Sending a message spans three independent choices — protocol version (v1.0 / v0.3) × method (send / stream) × transport (REST / JSON-RPC) — so there are eight valid combinations. All eight are shown below. Paths below are relative to the base URL; JSON-RPC always posts to the bare base URL and selects the operation with method. In the Headers column, SSE means Accept: text/event-stream and v1.0 means A2A-Version: 1.0. All eight take the same configuration fields (v1.0 waits by default and uses returnImmediately, v0.3 uses blocking) and the same metadata.memorylake extension. The four streaming variants respond with text/event-stream; the four send variants respond with a single JSON body.
Watch the three shape differences between versions and transports: v1.0 uses parts with role: "ROLE_USER"; v0.3 REST uses content with role: "ROLE_USER"; v0.3 JSON-RPC uses parts where every part carries a kind discriminator, with role: "user" in lowercase.

REST transport

JSON-RPC transport

Both versions share the same path — POST {baseUrl} with no suffix. The version comes from the method name; v1.0 clients should also send A2A-Version: 1.0.

Responses

Every combination returns the same task model. Only the envelope and a few spellings change with the protocol version and transport; see v0.3 differences. The examples in this section use v1.0 REST.

Send

message:send always returns a task, never a bare message. By default it waits until the task finishes:
string
Task ID.
string
The conversation this task belongs to. Send it as message.contextId to continue the conversation. If your request had none, a new one is generated.
object
array
The answer: at most one artifact, named result. It’s missing when the task failed or produced nothing. See The answer.
array
Your message, followed by the agent’s messages in order: text it wrote along the way, its thinking, and its tool calls. Consecutive text is merged into one message. configuration.historyLength keeps only the last N messages, and 0 leaves history out.
With returnImmediately: true (v1.0) or blocking: false (v0.3), the response comes back as soon as the task is created. The task is in TASK_STATE_SUBMITTED (or already TASK_STATE_WORKING), history holds only your message, and there are no artifacts. The task keeps running; to get its answer, subscribe to it.

Stream

message:stream responds with text/event-stream. Each data: line is one JSON event, in this order:
  1. task: the task as created, with your message in history.
  2. statusUpdate with TASK_STATE_WORKING as the agent starts.
  3. statusUpdate with TASK_STATE_WORKING for each step of progress. status.message holds a single part:
    • a chunk of answer text. Chunks are incremental, so append them; each has its own messageId;
    • a chunk of thinking text, marked with metadata.adk_thought: true;
    • a tool call or tool result;
    • a file the agent created, as {"url": "...", "mediaType": "...", "filename": "..."}.
  4. artifactUpdate with the answer. See The answer.
  5. statusUpdate with the final state, TASK_STATE_COMPLETED, TASK_STATE_FAILED (with the error in status.message) or TASK_STATE_CANCELED. The stream then ends.
Use the text chunks for live display, and take the final answer from the artifact. The chunks also carry text the agent writes before calling a tool, while the artifact’s text is the finished answer. Lines that start with : are keep-alive comments; ignore them.
IDs are shortened with … and repeated fields are elided for readability.

The answer

The result artifact holds the answer as a list of parts: Other data parts may appear in the artifact and in the stream. Skip any data part whose kind you don’t recognize, or that has no kind. Numbers inside data parts come back as floating point, so 2 arrives as 2.0.

Tool calls

The agent’s tool activity appears as data parts tagged with metadata.adk_type, the convention used by Google’s Agent Development Kit. You’ll see them in the stream and in history. A function call may also carry metadata.adk_thought_signature, an opaque value you can ignore.

Follow a task

GET {baseUrl}/tasks/{taskId} (v0.3: {baseUrl}/v1/tasks/{taskId}) returns only the task’s id, contextId and status, with state and timestamp. It has no answer, no history, and no error text for a failed task. To get the answer of a task you didn’t wait for, subscribe to it: GET {baseUrl}/tasks/{taskId}:subscribe (v0.3: {baseUrl}/v1/tasks/{taskId}:subscribe) with Accept: text/event-stream. It replays the task’s events from the beginning in the same format as message:stream, without the leading task event, and follows the task live until it finishes. A task that has failed or been canceled can’t be subscribed to.

Errors

The task fails. The request still returns HTTP 200; the task ends in TASK_STATE_FAILED, and status.message.parts[0].text describes the error. For example, an unknown overrides.model fails the task with LLM model config not found: <model>. The request is rejected. REST routes answer with an HTTP error status and the standard error body (success, message, error_code). For example: Over JSON-RPC, errors that MemoryLake raises before the agent runs, such as quota or permission errors, use the same HTTP status and error body. Errors the agent reports come back as a JSON-RPC error object with HTTP 200, and v0.3 methods report all of those with code -32603.

v0.3 differences

  • JSON-RPC. The result is the task itself, marked "kind": "task". Stream events are marked "kind": "status-update" or "artifact-update", and the last status update has "final": true. States are lowercase, for example completed, failed, canceled, submitted and working. Roles are user and agent. Every part carries a kind: {"kind": "text", "text": ...}, {"kind": "data", "data": {...}} or {"kind": "file", "file": {...}}.
  • REST. States use the v1.0 spelling, except that a canceled task is TASK_STATE_CANCELLED. Message parts are under content instead of parts, and a data part is {"data": {"data": {...}}}. The task returned by GET has no timestamp.
  • REST defaults. If you leave out blocking, message:send returns immediately instead of waiting. If you leave out historyLength, history is left out. Set both explicitly.