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:

text
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.

Format rules:

  • IDs: session_id, parent_agent_id, user_id, trace_id, and alg are 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:

  1. Keep only the envelope fields. Drop unknown keys and the signature itself.
  2. Serialize as JSON with keys sorted, , and : as separators, no whitespace, and UTF-8 encoding. Non-ASCII characters stay unescaped. String values stay strings, and iat and exp are integers.
  3. Compute HMAC-SHA256 over those bytes with the secret for kid.
  4. 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:

text
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.

  • A2A: One object under the com.amplitude/context key of Message.metadata. A value that isn't an object is malformed.
  • 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: metadata on the returned Message or Task.
  • ACP: _meta on 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:

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 adopts session_id, user_id, and parent_agent_id, and uses the signed trace_id as its [Agent] Trace ID, so both sides record the same Trace ID string. It never takes the trace from traceparent.
  • 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 from traceparent when trace_id is absent) as [Agent] Peer Agent ID, [Agent] Peer Session ID, and [Agent] Peer Trace ID, and ignores user_id. Its identity type is none. A malformed envelope 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:

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:

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:

text
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 http or https URL, such as an AgentCard URL: Lowercase the scheme and host, and drop the user info, the default port (80 for http, 443 for https), 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=1 becomes https://seller.example.com/agent.
  • Anything else: agent: followed by the trimmed string. For example, seller-agent becomes agent: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:

  1. Add the new key to every receiver.
  2. Switch senders to the new key as the active key.
  3. 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:

json
{
  "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:

text
{"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:

text
9psFj4AidjGvzDdvHO7cePh0ang9m4eN0sfsNU12vC8

To reproduce it with the Python standard library:

python
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:

json
{
  "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.

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.

このページでは