Engram is an A2A extension for durable domain state.
It gives agents a clean way to read, write, and subscribe to keyed JSON records backed by a real store, while still using A2A Messages, Tasks, and Artifacts as the interop surface.
- Spec: v0.1 (pre-1.0, evolving; feedback welcome)
- Extension URI:
https://github.com/EmberAGI/a2a-engram/tree/v0.1 - Repo:
https://github.com/EmberAGI/a2a-engram
Engram is designed to evolve independently of the core A2A spec. New breaking versions will use new URIs (.../tree/v0.2, .../tree/v1, etc.).
A2A gives you:
- Contexts – long‑lived sessions / conversations
- Tasks – stateful units of work inside a context
- Messages & Artifacts – immutable snapshots of exchanges and outputs
This is perfect for task‑ and context‑local history, but A2A deliberately does not define a model for:
- Workflow configs
- Strategy performance metrics
- User / agent / project profiles
- Global or per‑tenant settings
…i.e. long‑lived domain records you’d usually keep in a DB.
Engram fills that gap as an extension, not a core change:
-
It gives agents and services a shared, durable store for domain records.
-
It plays nicely with AG-UI shared state, giving you a clear split between:
- AG-UI as the UI- and interaction-facing state model, and
- Engram as the versioned, queryable backing store that survives across sessions, agents, and contexts.
-
A keyed record store:
key -> { value, version, timestamps, tags } -
Backed by a real database or memory system
-
Exposed via JSON‑RPC methods
-
Streamed via A2A Tasks & Artifacts
Engram lets you keep domain truth out of the protocol, while still making it easy for A2A clients, agents, and AG-UI-based UIs to read, write, and subscribe to that truth.
Engram has three main pieces:
- Keyed Records
- JSON‑RPC API (get / list / set / patch / delete / subscribe / resubscribe)
- Streaming via A2A Tasks & Artifacts
Engram stores versioned JSON records keyed by a minimal, domain‑agnostic identifier.
// Canonical identifier for a record
interface EngramKey {
key: string; // unique per record within an Engram store
labels?: Record<string, string>; // optional metadata; semantics are app-defined
}
// A single Engram record
interface EngramRecord {
key: EngramKey;
value: unknown; // arbitrary JSON, app-defined
version: number; // monotonically increasing per key
createdAt: string; // ISO-8601
updatedAt: string; // ISO-8601
tags?: string[]; // optional labels for querying/search
}Notes:
-
The spec only cares that
keyis unique and stable. Its structure is up to you. -
You can encode your own conventions into
keyand/orlabels, e.g.key: "config/workflow/wf:123/settings"key: "metrics/strategy/ETH-USDC/performance"labels: { space: "config", ownerType: "workflow", ownerId: "wf:123" }
Engram uses JSON Patch as the standard format for partial updates.
// RFC 6902
type JsonPatch = Array<{
op: "add" | "remove" | "replace" | "move" | "copy" | "test";
path: string;
from?: string;
value?: unknown;
}>;Patches are always applied to record.value for a given key. Engram automatically bumps version and updatedAt when a patch or full set succeeds.
Engram exposes a small set of JSON‑RPC methods as an A2A extension.
Note: Method names here use a simple
engram/*prefix. In a real AgentCard you may choose a more namespaced prefix (e.g.extensions/engram/get).
Purpose: Fetch one or more full record snapshots.
Params:
interface EngramFilter {
keyPrefix?: string;
tagsAny?: string[];
tagsAll?: string[];
updatedAfter?: string; // ISO-8601
labelEquals?: Record<string, string>;
}
interface EngramGetParams {
key?: EngramKey; // exact key
keys?: EngramKey[]; // multiple exact keys
filter?: EngramFilter; // broader match
includeHistory?: boolean; // optional
}Result:
interface EngramRecordHistoryEntry {
version: number;
value: unknown;
updatedAt: string;
}
interface EngramRecordHistory {
key: EngramKey;
entries: EngramRecordHistoryEntry[];
}
interface EngramGetResult {
records: EngramRecord[];
history?: EngramRecordHistory[]; // only if includeHistory=true
}This is a one‑shot RPC. It does not create a Task or subscription by default.
Purpose: List records matching a filter with pagination.
Params:
interface EngramListParams {
filter?: EngramFilter;
pageSize?: number;
pageToken?: string;
}Result:
interface EngramListResult {
records: EngramRecord[];
nextPageToken?: string;
}Purpose: Create or replace the entire value for a key (upsert).
Params:
interface EngramSetParams {
key: EngramKey;
value: unknown;
expectedVersion?: number; // optional CAS
tags?: string[]; // optional tags
}Result:
interface EngramSetResult {
record: EngramRecord; // new snapshot with incremented version
}If expectedVersion is provided and does not match the current version, the call MUST fail with a version conflict error.
Purpose: Apply JSON Patch to an existing record.
Params:
interface EngramPatchParams {
key: EngramKey;
patch: JsonPatch;
expectedVersion?: number;
}Result:
interface EngramPatchResult {
record: EngramRecord; // snapshot after patch, new version
}If the record does not exist, or expectedVersion mismatches, the call MUST fail.
Purpose: Delete a record.
Params:
interface EngramDeleteParams {
key: EngramKey;
expectedVersion?: number;
}Result:
interface EngramDeleteResult {
deleted: boolean;
previousVersion?: number;
}Engram uses A2A Tasks and Artifacts as the streaming surface.
Purpose: Open a subscription that streams record changes as Engram events, wrapped in Artifacts of a dedicated Task.
Params:
interface EngramSubscribeParams {
filter: EngramFilter; // which records to watch
includeSnapshot?: boolean; // send initial snapshots
contextId?: string; // A2A context for the subscription Task
fromSequence?: string; // optional resume point
}Result:
interface EngramSubscribeResult {
subscriptionId: string;
taskId: string; // underlying A2A Task
}Behavior:
- The server creates a dedicated subscription Task (or uses an implementation-defined Task type) in the given
contextId. - That Task emits
TaskArtifactUpdateEvents over the normal A2A streaming channel. - Each artifact includes one or more EngramEvent payloads in its
DataParts.
interface EngramEvent {
kind: "snapshot" | "delta" | "delete";
key: EngramKey;
record?: EngramRecord; // for snapshot
patch?: JsonPatch; // for delta
version: number;
sequence: string; // monotonically increasing per subscription
updatedAt: string; // ISO-8601
}On the wire, an event is represented as:
{
"kind": "data",
"data": {
"type": "engram/event",
"event": { /* EngramEvent */ }
}
}If includeSnapshot=true, the first artifact(s) SHOULD contain kind: "snapshot" events for all matching records.
Purpose: Resume a subscription after disconnection, mirroring tasks/resubscribe semantics.
Params:
interface EngramResubscribeParams {
subscriptionId: string;
fromSequence?: string; // resume from this EngramEvent sequence
}Result:
interface EngramResubscribeResult {
subscriptionId: string;
taskId: string;
}Notes:
- Implementations MAY map
subscriptionId→taskIdinternally and calltasks/resubscribe(taskId, ...)under the hood. - Clients that already manage
taskIds can calltasks/resubscribedirectly on the underlying Task if they prefer.
Engram is designed to work in two modes:
- RPC Mode (canonical)
- Message‑Embedded Mode (ergonomic)
The RPC methods (engram/get, engram/set, etc.) are the canonical interface.
-
They are advertised in the AgentCard's
extensionslist using the Engram URI. -
They are ideal for:
- other agents and tools,
- backends and orchestrators,
- tests and scripts.
Some A2A clients only speak in terms of message/send / message/stream. For those, Engram defines a simple operation envelope that can be embedded in a data part:
type EngramOpKind = "get" | "set" | "patch" | "delete";
interface EngramOperation {
type: "engram/op";
op: EngramOpKind;
key?: EngramKey; // required for set/patch/delete
value?: unknown; // for set
patch?: JsonPatch; // for patch
filter?: EngramFilter; // for get/list-style ops
requestId?: string; // correlates responses
}Example message:
{
"role": "user",
"parts": [
{
"kind": "data",
"data": {
"type": "engram/op",
"op": "set",
"key": { "key": "config/workflow/wf:123/settings" },
"value": { "maxRisk": 0.01, "rebalanceInterval": "1h" },
"requestId": "req-123"
}
}
]
}Implementation guidance:
-
Message‑embedded ops SHOULD be treated as a thin wrapper that internally calls the corresponding RPC methods.
-
Responses back to the client MAY:
- include EngramEvents via the subscription Task,
- or attach snapshots as Artifacts/messages as appropriate.
Engram is intentionally layered on top of A2A:
-
Tasks, messages, artifacts, metadata remain the canonical way to:
- represent workflows,
- track interactions,
- share outputs between agents.
-
Engram provides a separate, durable domain store:
- Long‑lived records (configs, performance metrics, profiles, etc.) live in the Engram store.
- Tasks read from and write to that store as needed.
- Artifacts serve as snapshots and projections for interop.
-
Use Tasks + Artifacts as the event log / workflow history layer.
-
Use Engram as the materialized domain model layer.
-
Let UIs and other agents:
- call
engram/get/engram/setfor one‑shot operations, - use
engram/subscribeto keep dashboards and shared state in sync.
- call
Imagine a trading workflow with:
- Workflow settings:
config/workflow/wf:123/settings - Workflow performance metrics:
metrics/workflow/wf:123/performance
Change settings via RPC:
{
"jsonrpc": "2.0",
"id": "1",
"method": "engram/set",
"params": {
"key": { "key": "config/workflow/wf:123/settings" },
"value": {
"maxRisk": 0.01,
"rebalanceInterval": "1h"
},
"expectedVersion": 3
}
}Subscribe to performance updates:
{
"jsonrpc": "2.0",
"id": "2",
"method": "engram/subscribe",
"params": {
"filter": {
"keyPrefix": "metrics/workflow/wf:123/"
},
"includeSnapshot": true,
"contextId": "ctx-trading-dashboard"
}
}The client then listens for artifact updates on the returned taskId and applies EngramEvents to its local state store.
Engram is designed to complement, not replace, AG-UI shared state. If you are already using AG-UI, Engram can act as the backing store and subscription layer for that state.
A common pattern is:
-
Treat one or more Engram records as the source of truth for AG-UI shared state.
- e.g.
key: "ui/agent:trader/state"or more granular keys like"ui/agent:trader/layout","ui/agent:trader/filters".
- e.g.
-
When your backend agent updates AG-UI state, it also:
- writes the new state (or slice) to Engram via
engram/setorengram/patch, and - optionally emits EngramEvents via
engram/subscribefor any interested dashboards.
- writes the new state (or slice) to Engram via
-
On startup or reconnect, AG-UI can be hydrated by:
- calling
engram/getto fetch current records, and - mapping them into the initial AG-UI shared state object.
- calling
This keeps AG-UI state durable and cross-session while preserving the AG-UI mental model for frontend code.
If you already use AG-UI's STATE_SNAPSHOT and STATE_DELTA events, Engram can be treated as the underlying store that those events mirror:
-
Snapshots:
- An Engram
EngramRecord(or a set of them) can be serialized into a single AG-UISTATE_SNAPSHOTpayload. - This is ideal for initial hydration or full resync when patch application fails.
- An Engram
-
Deltas:
-
Engram
EngramEventwithkind: "delta"already carries a JSON Patch. -
That patch can be applied directly to:
- the Engram store (backing DB), and
- the AG-UI shared state object as a
STATE_DELTAevent.
-
In other words, you can treat Engram JSON Patch as the same diff format that powers AG-UI state deltas, letting one stream drive both storage and UI.
From a frontend perspective:
-
AG-UI remains the primary interface for rendering, user interaction, and stateful components.
-
The AG-UI bridge / backend is responsible for:
- calling
engram/get/engram/set/engram/patchas needed, and - wiring
engram/subscribeevents into AG-UISTATE_DELTAevents.
- calling
This keeps your React / UI code talking in AG-UI terms, while Engram provides a consistent, versioned domain store that multiple agents and tools can share.
-
Reference TypeScript and/or Python Engram store implementation
-
Helpers for mapping Engram records into:
- LangGraph state / long‑term stores
- AG‑UI shared state /
STATE_SNAPSHOT/STATE_DELTAevents
-
Example A2A agents using Engram for:
- workflow configs
- strategy performance dashboards
- user preference storage
- Replacing A2A Task state or history
- Defining global domain concepts like "user", "strategy", or "workflow" in the core Engram spec
- Mandating any particular backing database implementation
Engram is designed to be:
- Spec‑first: the extension URI and JSON types are the source of truth.
- Implementation‑agnostic: you can implement the Engram store with any DB / runtime.
If you'd like to:
- Propose new methods or filters
- Add reference implementations (TS, Python, etc.)
- Add example agents/UIs that use Engram
…please open an issue or PR in this repo.
Licensed under the Apache License, Version 2.0.