Amplitude agent context v1
このページはまだあなたの言語に翻訳されていません。 現在取り組んでいますので、後でもう一度確認してください。
Amplitude agent context v1 is the envelope that one agent sends another so both sides record correct Agent Analytics data. It carries the caller's session, agent, and trace over Agent2Agent (A2A), Agent Client Protocol (ACP), and HTTP. Every event on both sides still has a user: by default the envelope doesn't share the caller's user ID, and the receiver records its own. The caller can choose to send a per-recipient pseudonym or, with signing, the real user ID. A receiver trusts the envelope only when its HMAC-SHA256 signature verifies against a key the receiver holds. The receiver links an unverified envelope to its own session and never merges the two.
This page is the normative wire format. The Amplitude AI SDK for Python and Node implements it, and both SDKs produce byte-identical envelopes. To instrument agents with the SDK, refer to Across processes.
Extension URI
The URI of this page is the A2A extension URI:
https://amplitude.com/docs/amplitude-ai/agent-analytics/agent-context-v1
Agents declare it in the AgentCard's capabilities.extensions with required: false, so clients that don't send the envelope still work. The AI SDK's declare_a2a_extension() (declareA2aExtension() in Node) adds that entry. Clients that activate the extension list the URI in the X-A2A-Extensions header. Implementations compare the URI as an exact string and never fetch it.
In this spec, ACP always means the Agent Client Protocol, which editors and orchestrators use to drive coding agents. Agent context v1 defines no mapping for the Agentic Commerce Protocol. To join commerce outcomes such as a checkout to an agent session, put the agent's IDs on the outcome event. Refer to Join outcomes to the agent session.
Envelope fields
The logical envelope has six context fields and four signing fields. Every transport decodes to this same envelope before signing or verifying.
| Field | Type | Description |
|---|---|---|
session_id | string | The sender's [Agent] Session ID. |
parent_agent_id | string | The sending agent's ID. On the receiver, it becomes the parent agent when trusted, or the peer agent when not. |
user_id | string | The end user, as an account ID or a pairwise ID. Present only when identity_type is account or pairwise. |
identity_type | string | account, pairwise, or none. An absent value means none. |
actor_type | string | agent or human: who drives the sender's session. Receivers honor it only on a verified envelope. |
trace_id | string | The sender's exact [Agent] Trace ID, such as a UUID or req-123. Senders include it whenever the session has a trace. |
alg | string | Signature algorithm. v1 defines only HS256. |
kid | string | ID of the signing key. Matches [A-Za-z0-9._-]{1,64}. |
iat | integer | Issued-at time, in Unix seconds, from 0 to 2^53 − 1. |
exp | integer | Expiry time, in Unix seconds, from 0 to 2^53 − 1. Defaults to iat plus 300. exp minus iat must be greater than 0 and at most 86,400. |
Format rules:
- IDs:
session_id,parent_agent_id,user_id,trace_id, andalgare printable ASCII, 1 to 128 characters, with no leading or trailing space. - Absent fields: Omit them. Never send
null. - Unknown keys: Receivers drop them before verification. A peer can add its own keys without breaking the signature, and those keys never count as signed.
- Signing fields: An envelope carries all four signing fields plus a signature, or none of them.
Signing
The signature covers the canonical JSON of the logical envelope, with every present field from the table and nothing else:
- Keep only the envelope fields. Drop unknown keys and the signature itself.
- Serialize as JSON with keys sorted,
,and:as separators, no whitespace, and UTF-8 encoding. Non-ASCII characters stay unescaped. String values stay strings, andiatandexpare integers. - Compute HMAC-SHA256 over those bytes with the secret for
kid. - Encode the 32-byte MAC as base64url without padding. The result is 43 characters.
Receivers recompute the signature and compare it in constant time. Because alg is inside the signed bytes, changing alg after signing fails as bad_signature.
Secrets are random values of at least 32 bytes. The AI SDK rejects shorter secrets and malformed key IDs when you configure them.
What a signing key means
Any service that holds a signing key can vouch for any user and any session in your project. Treat key holders as part of your trust boundary, the same as database credentials. Share keys only between services you operate, and never ship a key to a browser or mobile app. Give each counterparty its own key ID and secret, so a leaked key vouches only for that pair of services and you can revoke it without rotating the others.
Compact signature format
ACP and HTTP carry the signing fields and the signature in one string, with exactly these five fields in this order:
alg=HS256;kid=<kid>;iat=<iat>;exp=<exp>;sig=<sig>
iat and exp are decimal integers with no leading zeros. A value that doesn't parse in this exact shape makes the whole envelope malformed.
Transport encodings
Each transport maps the logical fields one to one, so the same envelope produces the same signing input on every transport.
| Logical field | A2A, inside com.amplitude/context | ACP _meta key | HTTP header |
|---|---|---|---|
session_id | session_id | com.amplitude/session-id | x-amplitude-session-id |
parent_agent_id | parent_agent_id | com.amplitude/parent-agent-id | x-amplitude-agent-id |
user_id | user_id | com.amplitude/user-id | x-amplitude-user-id |
identity_type | identity_type | com.amplitude/identity-type | x-amplitude-identity-type |
actor_type | actor_type | com.amplitude/actor-type | x-amplitude-actor-type |
trace_id | trace_id | com.amplitude/trace-id | x-trace-id |
alg, kid, iat, exp, signature | Separate keys alg, kid, iat, exp, and sig | com.amplitude/sig, in the compact format | x-amplitude-context-sig, in the compact format |
- A2A: One object under the
com.amplitude/contextkey ofMessage.metadata. A value that isn't an object ismalformed. - ACP: Flat keys in the request's
_meta. - HTTP: Request headers. Header names are case-insensitive.
W3C trace context
On ACP and HTTP, senders also send a W3C traceparent (ACP's reserved _meta key, or the traceparent header) when the trace ID is exactly 32 hex characters once dashes are removed, and not all zeros. traceparent isn't an envelope field and is never signed, because OpenTelemetry instrumentation and proxies rewrite it in transit. A signed envelope still verifies after its traceparent changes or disappears, and a receiver takes its trace only from the signed trace_id. A2A carries no traceparent.
A valid traceparent matches <version>-<32 hex trace ID>-<16 hex span ID>-<2 hex flags> in lowercase hex. Version ff and an all-zero trace ID are invalid.
Responses
A receiver can return its own context to the caller, so both sides can link the two sessions. Responses use the same envelope and signing:
- A2A:
metadataon the returned Message or Task. - ACP:
_metaon the result. - HTTP: Response headers.
Response envelopes never carry actor_type, and their identity defaults to none. A caller attaches a response context to the call that produced it, never to the caller's session.
Verify results
A receiver checks an envelope in this order and records the first result that applies:
| Result | Condition | Trust |
|---|---|---|
none | No envelope on the message. On ACP and HTTP, an unsigned message that carries only traceparent or the trace_id key is ordinary traced traffic, not an envelope: the receiver continues that trace. | none |
malformed | The envelope breaks a format rule, or the compact signature doesn't parse. | untrusted |
unsigned | The envelope has no signing fields. | untrusted |
unsupported_alg | alg isn't HS256. | untrusted |
unknown_kid | The receiver holds no key for kid. | untrusted |
bad_signature | The signature doesn't match. | untrusted |
expired | The current time is more than 60 seconds after exp. | untrusted |
not_yet_valid | The current time is more than 60 seconds before iat. | untrusted |
ok | Every check passes. | trusted |
Trust follows from the verify result, and nothing sets it on its own. The receiver records both: [Agent] Context Trust for simple chart filters, and [Agent] Context Verify Result for debugging. A wrong key or clock shows up as a named result rather than a silent untrusted. The AI SDK also logs each non-ok result once per process as a warning, with the key ID but never the secret.
Clock skew and TTL
Verification allows 60 seconds of clock skew in both directions. An envelope is valid from iat minus 60 seconds through exp plus 60 seconds. The default lifetime is 300 seconds and the maximum is 86,400 seconds.
Receivers don't keep a nonce store. Inside its validity window, a replayed envelope attributes events to the same session it already named. Keep lifetimes short to limit that window.
Receiver behavior
The verify result decides what the receiver adopts:
- Trusted (
ok): The receiver joins the sender's session. It adoptssession_id,user_id, andparent_agent_id, and uses the signedtrace_idas its[Agent] Trace ID, so both sides record the same Trace ID string. It never takes the trace fromtraceparent. - Untrusted (any other result except
none): The receiver keeps its own session, user, and trace ID. It records the sender's agent, session, and trace ID (trace_id; on an unsigned envelope, the trace ID fromtraceparentwhentrace_idis absent) as[Agent] Peer Agent ID,[Agent] Peer Session ID, and[Agent] Peer Trace ID, and ignoresuser_id. Its identity type isnone. Amalformedenvelope records only the verify result.
The W3C traceparent that OpenTelemetry propagates is separate from these rules, so your own distributed tracing still forms one trace.
[Agent] Actor Type depends on the transport:
| Transport | Actor Type |
|---|---|
| A2A | Always agent, because A2A is agent to agent. |
| ACP | The signed actor_type when the result is ok, otherwise human. |
| HTTP | The signed actor_type when the result is ok, otherwise human. |
An unsigned actor_type claim never changes Actor Type.
Unsigned HTTP during the deprecation window
HTTP context propagation predates signing. While accept_unsigned_http is on, a receiver still adopts unsigned HTTP headers as before and records [Agent] Context Verify Result as unsigned. The SDK logs a deprecation warning once per process. Until the next major SDK release, accept_unsigned_http is on by default only for a receiver with no signing keys. A receiver with signing keys treats unsigned HTTP as link-only, as A2A and ACP already are, so a request can't skip verification by leaving out the signature. A receiver whose key configuration is invalid also treats unsigned HTTP as link-only, so a configuration mistake never makes it adopt unsigned context.
On unsigned HTTP, an x-trace-id that isn't 1 to 64 letters, digits, or ._:-, starting with a letter or digit, is ignored rather than making the envelope malformed, as before. On a signed envelope, x-trace-id is the signed trace_id and follows the ID rules. A signed HTTP request carries only the headers in the transport table, plus traceparent.
Identity modes
The sender chooses how much of the end user's identity leaves the process:
| SDK option | identity_type on the wire | What the receiver gets |
|---|---|---|
none (default for A2A and ACP) | none | No user ID. |
pairwise | pairwise | A stable pseudonym unique to that receiver. |
raw | account | Your real user ID. Allowed only on a signed envelope. The SDK raises an error rather than send a raw ID unsigned. |
Pairwise IDs
A pairwise ID gives each counterparty a stable ID for a returning user that it can't reverse or join with any other counterparty's ID:
pw<version>_ + first 26 characters of lowercase, unpadded base32(
HMAC-SHA256(salt[version], user_id + "\n" + canonical_recipient)
)
The canonical recipient identifies who receives the ID:
- An
httporhttpsURL, such as an AgentCard URL: Lowercase the scheme and host, and drop the user info, the default port (80 forhttp, 443 forhttps), the query, and the fragment. Remove a trailing/from the path. Path case stays as is. For example,https://Seller.Example.com:443/agent/?x=1becomeshttps://seller.example.com/agent. - Anything else:
agent:followed by the trimmed string. For example,seller-agentbecomesagent:seller-agent.
Each salt has a version, and the version is part of the ID (pw1_, pw2_). Rotating to a new salt changes every pairwise ID a counterparty sees. It's a deliberate break in continuity, and the version prefix makes it visible so a counterparty can bridge the old and new IDs. For how pairwise IDs affect privacy and deletion, refer to Pairwise IDs and deletion.
Key rotation
Each key has a kid. Receivers accept every key they hold, and senders sign with one active key. Use a distinct kid and secret for each counterparty, so you rotate or revoke one relationship at a time. To rotate without dropping trust:
- Add the new key to every receiver.
- Switch senders to the new key as the active key.
- Remove the old key after the longest envelope lifetime you issue has passed.
The AI SDK reads keys from the AMPLITUDE_AI_CONTEXT_KEYS environment variable, a JSON object of key ID to secret, and the active key ID from AMPLITUDE_AI_CONTEXT_ACTIVE_KID. Pairwise salts come from AMPLITUDE_AI_PAIRWISE_SALTS, a JSON object of version to salt, and the SDK mints with the highest version. If any of these variables is invalid, the SDK logs an error and runs with no keys, so every inbound envelope is untrusted until you fix it. amplitude-ai doctor reports the problem.
A ContextPolicy passed in the client configuration replaces the environment variables. It applies to the client's sessions and, outside a session, to the whole process. A process has one such policy: a second client with a different policy replaces it and logs a warning. To use different keys for one call, pass the policy to that call.
Worked example
This example uses the test key k1, with the secret k1-secret-0123456789abcdef0123456789. Never use it outside tests.
The logical envelope:
{
"session_id": "sess-buyer-1",
"parent_agent_id": "buyer-agent",
"identity_type": "none",
"trace_id": "4bf92f35-77b3-4da6-a3ce-929d0e0e4736",
"alg": "HS256",
"kid": "k1",
"iat": 1800000000,
"exp": 1800000300
}
The signing input, the canonical JSON as one line:
{"alg":"HS256","exp":1800000300,"iat":1800000000,"identity_type":"none","kid":"k1","parent_agent_id":"buyer-agent","session_id":"sess-buyer-1","trace_id":"4bf92f35-77b3-4da6-a3ce-929d0e0e4736"}
The signature:
9psFj4AidjGvzDdvHO7cePh0ang9m4eN0sfsNU12vC8
To reproduce it with the Python standard library:
import base64, hashlib, hmac, json
envelope = {
"session_id": "sess-buyer-1",
"parent_agent_id": "buyer-agent",
"identity_type": "none",
"trace_id": "4bf92f35-77b3-4da6-a3ce-929d0e0e4736",
"alg": "HS256",
"kid": "k1",
"iat": 1800000000,
"exp": 1800000300,
}
signing_input = json.dumps(envelope, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
mac = hmac.new(b"k1-secret-0123456789abcdef0123456789", signing_input.encode("utf-8"), hashlib.sha256)
print(base64.urlsafe_b64encode(mac.digest()).rstrip(b"=").decode("ascii"))
The same envelope on each transport. ACP and HTTP add the unsigned traceparent; the signature is the same on all three:
{
"com.amplitude/context": {
"session_id": "sess-buyer-1",
"parent_agent_id": "buyer-agent",
"identity_type": "none",
"trace_id": "4bf92f35-77b3-4da6-a3ce-929d0e0e4736",
"alg": "HS256",
"kid": "k1",
"iat": 1800000000,
"exp": 1800000300,
"sig": "9psFj4AidjGvzDdvHO7cePh0ang9m4eN0sfsNU12vC8"
}
}
A receiver that holds k1 and checks the envelope between Unix time 1799999940 and 1800000360 gets ok. It joins session sess-buyer-1 with buyer-agent as the parent agent and 4bf92f35-77b3-4da6-a3ce-929d0e0e4736 as the trace ID, even if a proxy rewrote traceparent on the way. A receiver without k1 gets unknown_kid, keeps its own session, and records buyer-agent and sess-buyer-1 as the peer agent and peer session.
Event properties and OpenTelemetry attributes
Receivers record the result as seven [Agent] properties. OpenTelemetry instrumentation sets the matching span attributes.
| Property | OpenTelemetry span attribute | Values |
|---|---|---|
[Agent] Peer Agent ID | amplitude.peer.agent.id | The sender's agent, when untrusted. |
[Agent] Peer Session ID | amplitude.peer.session.id | The sender's session, when untrusted. |
[Agent] Peer Trace ID | amplitude.peer.trace.id | The sender's trace ID, when untrusted. |
[Agent] Context Trust | amplitude.context.trust | trusted, untrusted, or none. |
[Agent] Context Verify Result | amplitude.context.verify.result | One of the verify results. |
[Agent] Actor Type | amplitude.actor.type | agent or human. |
[Agent] Identity Type | amplitude.identity.type | account, pairwise, or none. |
For how these properties appear on events, refer to Agent context properties.
これは役に立ちましたか?
For AI agents: a documentation index is available at /docs/llms.txt. Append .md to any page URL for markdown, or send Accept: text/markdown.