RFC-004: CapiscIO Transaction and Hop Binding Protocol (TCHB)¶
Version: 0.3 Status: Draft Authors: CapiscIO Core Team Created: 2025-12-24 Updated: 2026-01-02 Requires: RFC-002 (Trust Badge Specification)
1. Abstract¶
This RFC defines a lightweight protocol for binding multi-hop agent workflows into a verifiable chain of custody.
The protocol introduces:
- A stable transaction identifier (
txn_id) propagated across hops. -
A signed Hop Attestation (HA) that binds each hop to:
-
the transaction (
txn_id), - the immediate parent hop (optional
parent_hop_hash), and - the specific Trust Badge session used to authenticate (
badge_jti).
This yields a self-verifying “tracking number” for agent workflows without requiring a global ledger.
2. Motivation¶
In an agentic workflow, requests traverse multiple agents and services. Standard tracing correlates spans, but does not produce a cryptographic chain that can be audited for tampering.
We need a minimal mechanism to answer:
- Which agent initiated each hop?
- Which authenticated badge session was used?
- How did the request propagate across participants?
- Can we detect missing or injected hops after the fact?
3. Goals and Non-Goals¶
3.1 Goals¶
- Provide a stable
txn_idacross a workflow. - Provide a signed Hop Attestation per hop.
- Bind each hop to the Trust Badge session (
badge_jti) used for authentication. - Support verification at gateways/services (synchronous) and reconstruction in telemetry pipelines (asynchronous).
- Support HTTP and non-HTTP transports (example mapping for MCP/JSON-RPC).
3.2 Non-Goals¶
- Payload integrity or non-repudiation of request bodies (no
body_hashin v0.2). - Global ordering guarantees.
- A global registry of hops.
- Per-request proof-of-possession beyond the hop signature itself.
- Replacing OpenTelemetry or existing tracing systems.
4. Terminology¶
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHOULD”, “SHOULD NOT”, and “MAY” are to be interpreted as described in RFC 2119.
| Term | Definition |
|---|---|
Transaction (txn_id) | A stable ID that groups a workflow across multiple hops. |
| Hop | A single propagation step from one participant to the next. |
| Hop Attestation (HA) | A signed JWS asserting hop metadata and bindings. |
Badge Session (badge_jti) | The jti claim of the Trust Badge used for authentication on that hop. |
| Synchronous Verification | On-request checks performed by the receiving service or gateway. |
| Asynchronous Audit | Offline reconstruction and linkage verification in logs/event sinks. |
5. Protocol Overview¶
Each hop carries:
X-Capiscio-Txn: a stable transaction ID.X-Capiscio-Hop: a Hop Attestation JWS, signed by the initiating agent.- A Trust Badge (RFC-002) used to authenticate the caller.
The receiving service verifies:
- The Trust Badge is valid.
- The HA signature is valid using the agent key bound in the presented badge.
- The HA is bound to the presented badge via
badge_jti. - The HA is bound to the current request via
htuandhtm(with canonicalization rules).
Parent linkage (parent_hop_hash) is intended primarily for asynchronous audit and MUST NOT be required for request acceptance.
6. Transport and Propagation¶
6.1 HTTP Headers (Normative)¶
Implementations MUST support the following headers:
X-Capiscio-Txn: <txn_id>
X-Capiscio-Hop: <hop_attestation_jws>
Authorization: Bearer <trust_badge_jws> ; per RFC-002
Header requirements:
X-Capiscio-TxnMUST be present for CapiscIO-governed hops.X-Capiscio-HopMUST be present for CapiscIO-governed hops.- The Trust Badge MUST be present per the deployment’s authentication rules.
6.2 Propagation Rules (Normative)¶
- Intermediaries that forward a request (gateway, agent, service mesh) MUST forward
X-Capiscio-Txn. - Intermediaries MUST generate a new
X-Capiscio-Hopfor the outbound request they initiate. - Intermediaries SHOULD include
parent_hop_hashwhen they have access to the inbound hop attestation they received.
6.3 Non-HTTP Transports (MCP / JSON-RPC) (Normative)¶
For JSON-RPC protocols like MCP, the CapiscIO context MUST be propagated in a standardized metadata field. One acceptable mapping is:
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": { "...": "..." },
"_meta": {
"capiscio_txn": "<txn_id>",
"capiscio_hop": "<hop_attestation_jws>"
}
}
For non-HTTP transports, htu MUST be the canonical target identifier for that transport.
MCP htu Canonicalization (Normative):
For MCP (Model Context Protocol) transports, htu MUST use the following format:
Where: - <server-identifier> is the MCP server's advertised name or endpoint (e.g., filesystem, github, slack). - <method> is the JSON-RPC method being invoked (e.g., tools/call, resources/read).
Example: mcp://filesystem/tools/call
For MCP servers accessed via stdio or other local transports, <server-identifier> SHOULD be the server's canonical name as declared in its manifest.
7. Identifiers¶
7.1 Transaction ID (txn_id) (Normative)¶
txn_idMUST be a string.txn_idSHOULD be a UUID (v4 or v7) to maximize interoperability with tracing systems.txn_idMUST be treated as an opaque identifier by verifiers.
7.2 Hop ID (hop_id) (Normative)¶
- Each Hop Attestation MUST contain a unique
hop_id(UUID recommended). hop_iduniqueness is scoped to the issuer. Collisions across issuers are not expected but are not protocol-breaking.
8. Hop Attestation (HA)¶
8.1 Format (Normative)¶
A Hop Attestation is a JWS (compact serialization):
8.2 HA Header (Normative)¶
Example:
Requirements:
algMUST beEdDSA(Ed25519).typMUST becapiscio.hop+jwt.kidSHOULD be present and SHOULD reference the signing key identifier. Fordid:web, this SHOULD be a DID URL fragment to a verification method.
8.3 HA Payload (Claims) (Normative)¶
Example payload:
{
"txn_id": "018f4e1d-7e5d-7a9f-a9d2-8b6a0f2c9b11",
"hop_id": "550e8400-e29b-41d4-a716-446655440000",
"parent_hop_hash": "sha256:LCa0a2j_xo_5m0U8HTBBNBNCLXBkg7-g-YpeiGJm564",
"iss": "did:web:example.com:agents:my-agent",
"target_aud": "https://api.partner.com",
"badge_jti": "b8f2c6a5-2d6f-4e44-9f55-2a1d6d9e0f12",
"iat": 1733788800,
"exp": 1733789100,
"htm": "POST",
"htu": "https://api.partner.com/v1/task"
}
Claims:
| Claim | Requirement | Description |
|---|---|---|
txn_id | REQUIRED | Stable workflow transaction ID. |
hop_id | REQUIRED | Unique hop identifier. |
parent_hop_hash | OPTIONAL | Hash of the immediate parent hop attestation (see §8.5). |
iss | REQUIRED | Agent DID that issued the hop. MUST equal the sub claim of the presented Trust Badge (see §9.2). |
target_aud | REQUIRED | Target audience identifier for this hop (typically the receiver origin). This is a single origin string, distinct from RFC-002 Badge aud (which is an array). |
badge_jti | REQUIRED | Trust Badge jti bound to this hop. |
iat | REQUIRED | Issued-at (Unix seconds). |
exp | REQUIRED | Expiration (Unix seconds). |
htm | REQUIRED | HTTP method (or equivalent) bound to the hop. |
htu | REQUIRED | Target URI (or canonical target identifier) bound to the hop. |
body_hash | RESERVED | Not used in v0.2. MUST be ignored if present. |
8.3.1 badge_jti Binding (Normative)¶
badge_jtiMUST be present in every Hop Attestation.- Verifiers MUST confirm
badge_jtiequals thejticlaim of the presented Trust Badge. - If the badge is revoked, the hop’s credibility is reduced to “unsigned context” for governance purposes. (The request may still have been processed historically, but audit systems can flag it.)
8.4 Lifetime (Normative)¶
- HA
exp - iatSHOULD be short (recommended: 5 minutes) and SHOULD NOT exceed the Trust Badge lifetime. - Verifiers MUST reject Hop Attestations that are expired.
8.5 Parent Hop Hash (Normative)¶
When included, parent_hop_hash MUST be computed over the parent hop attestation using the canonical hashing rules in §8.6.
Audit Obligation: The hash chain binds hop payloads but does not bind signatures. Asynchronous audit systems that verify parent_hop_hash linkage MUST also independently verify the parent hop's signature to ensure the parent was not forged. The hash alone is insufficient for tamper detection.
8.6 Canonical Hashing (Normative)¶
When hashing a hop attestation for parent_hop_hash, implementations MUST:
- Decode the parent hop JWS payload JSON.
- Canonicalize JSON using RFC 8785 (JCS).
- UTF-8 encode.
- SHA-256 hash.
- base64url encode without padding.
- Prefix with
sha256:.
9. Verification¶
9.1 Verification vs Audit (Normative Split)¶
Synchronous verification (at gateway or service) MUST verify the current hop’s authenticity and bindings, but MUST NOT require access to the parent hop.
Asynchronous audit (in telemetry pipelines) SHOULD reconstruct hop chains and verify parent linkage.
9.2 Synchronous Verification Algorithm (Normative)¶
Given a request containing X-Capiscio-Txn, X-Capiscio-Hop, and a Trust Badge:
- Parse Trust Badge and validate per RFC-002 (signature,
exp, issuer trust, audience rules as applicable). - Parse Hop Attestation JWS.
-
Validate HA structure:
-
alg == EdDSA typ == capiscio.hop+jwt- required HA claims present
- Verify
txn_idmatchesX-Capiscio-Txn. If mismatch, reject. - Verify
badge_jtiis present. If missing, reject. - Verify
badge_jti == badge.jti. If mismatch, reject. - Verify
iss == badge.sub. If mismatch, reject. This prevents semantically mis-attributed hops where the signature is valid but the claimed issuer differs from the authenticated caller. -
Verify HA signature using the agent public key from the presented Trust Badge
keyclaim (RFC-002). -
If the deployment requires DID resolution, it MAY instead verify using the DID document, but this is OPTIONAL.
- Validate time claims with skew tolerance (recommended: 60 seconds).
-
Validate request binding:
- Verify
htmmatches the request method. - Verify
htumatches the canonicalized target (see §9.3). - If all checks pass, accept request and emit telemetry per §10.
- Verify
9.3 htu Canonicalization (Normative)¶
Real deployments use reverse proxies. The receiver may see an internal URL that does not match what the caller signed. To avoid brittle failures:
- Verifiers MUST canonicalize the target identity for
htucomparison using one of the following configured modes.
Mode A (Path-only verification):
- Compare only the path and query (if used), ignoring scheme and host.
- Recommended when services sit behind gateways that rewrite scheme/host.
Mode B (Public Identity URI verification):
- Verifier is configured with a “public identity origin” (for example
https://api.partner.com). - Verifier reconstructs
htuas:<public_origin><request_path_and_query>. - Compare against HA
htu.
A deployment MUST document which mode it uses. Mode B is RECOMMENDED for production because it preserves host binding.
9.3.1 Query String Normalization (Normative)¶
If the htu includes a query string, implementations MUST normalize before signing and comparing:
- Include or exclude: A deployment MUST document whether query strings are included in
htu. If excluded, signers MUST omit the query; verifiers MUST strip it before comparison. - If included:
- Query parameters MUST be sorted lexicographically by key (byte order).
- Keys and values MUST be percent-encoded using the following algorithm:
- Decode any existing percent-encoded unreserved characters (RFC 3986 §2.3:
A-Z,a-z,0-9,-,.,_,~). - Reserved characters (RFC 3986 §2.2:
:/?#[]@!$&'()*+,;=) MUST NOT be decoded; they remain percent-encoded. - Re-encode all characters outside the unreserved set using uppercase hex (
%2F, not%2f). - Encode spaces as
%20(not+).
- Decode any existing percent-encoded unreserved characters (RFC 3986 §2.3:
- Empty query (
?with no parameters) MUST be treated as absent (omit?). - Duplicate keys are preserved in sorted order.
- Example:
?b=2&a=1normalizes to?a=1&b=2.?name=hello%20worldand?name=hello+worldboth normalize to?name=hello%20world.
Failure to normalize consistently will cause false rejections or create bypass opportunities if different components normalize differently.
9.4 Parent Linkage Checks (Normative)¶
- The verifier (Service B) will often not have Hop N-1.
- Therefore, synchronous verifiers MUST NOT fail a request if
parent_hop_hashcannot be validated. - Parent linkage verification is primarily the responsibility of the asynchronous audit layer.
10. Telemetry and Observability¶
Implementations MUST be capable of emitting structured telemetry events to enable reconstruction of a durable chain of custody.
10.1 Canonical Hop Event Schema (Normative)¶
When a hop is emitted (client) or verified (server), implementations MUST be capable of emitting a structured log event containing these fields. Field names SHOULD use the capiscio. namespace.
| Field | Type | Description |
|---|---|---|
capiscio.txn_id | String | Stable workflow transaction ID. |
capiscio.hop.hop_id | String | Unique ID of the current hop. |
capiscio.hop.parent_hash | String or null | Hash of the parent hop. |
capiscio.hop.sig_kid | String | The kid used to sign the hop. |
capiscio.agent.did | String | Agent DID initiating the hop (from HA iss). |
capiscio.badge.jti | String | The jti of the Trust Badge used for authentication. |
capiscio.target_aud | String | Intended audience of the hop (from HA target_aud). |
event.name | String | RECOMMENDED: capiscio.hop_verified (server) or capiscio.hop_emitted (client). |
Implementations SHOULD include additional operational fields when available (duration, status code, error code), but MUST NOT change the semantics of the required fields.
10.2 OpenTelemetry (OTel) Mapping (Normative)¶
If OpenTelemetry is used, implementations MUST map CapiscIO context as follows.
1. Trace ID mapping:
- If
capiscio.txn_idis a valid UUID (v4 or v7), implementations SHOULD use the underlying 16 bytes as the OTelTraceId. - If
txn_idis not a UUID, generate a new OTelTraceIdand attachcapiscio.txn_idas a span attribute.
2. Span attributes: These attributes MUST be attached to the active span while processing the request:
capiscio.txn_id: <txn_id>
capiscio.hop_id: <hop_id>
capiscio.parent_hash: <parent_hop_hash>
capiscio.agent.did: <iss>
capiscio.badge.jti: <badge_jti>
3. Context propagation:
- Implementations MUST preserve
X-Capiscio-TxnandX-Capiscio-Hopacross service boundaries. - OTel Baggage MAY be used for convenience but MUST NOT replace the HTTP headers as the source of truth.
10.3 Logging and Redaction (Normative)¶
Logging pipelines MUST enforce:
- No full tokens: MUST NOT log the full
X-Capiscio-HopJWS or Trust Badge JWT by default. - JTI only: Logging
capiscio.badge.jtiis REQUIRED; logging full badge payload is FORBIDDEN by default. - No payload capture: Request bodies SHOULD NOT be logged unless explicitly configured for full-capture debugging.
11. Security Considerations¶
11.1 Threats and Mitigations¶
| Threat | Mitigation |
|---|---|
| Hop forgery | HA is signed, verified against badge-bound agent key. |
| Badge theft used to sign fake hops | badge_jti binding REQUIRED, reduces blast radius and improves audit correlation. |
| Replay within TTL | Short HA TTL; production gateways SHOULD maintain a short-lived replay cache keyed by (badge_jti, hop_id). |
Proxy rewriting breaks htu | Canonicalization modes in §9.3. |
| Token leakage in logs | Redaction rules in §10.3. |
11.2 SSRF Note¶
This RFC does not require DID resolution for HA verification because the badge embeds the agent key. If a deployment does perform DID resolution, it SHOULD apply SSRF protections similar to RFC-002 verifier guidance.
12. Implementation Notes (Non-Normative)¶
12.1 Fan-In and Aggregation¶
If an agent aggregates inputs from multiple upstream hops:
- The agent SHOULD choose the primary causal parent for
parent_hop_hash, or - Omit
parent_hop_hashif no clear primary parent exists, effectively starting a new hop chain linked only bytxn_id.
12.2 Recommended Defaults¶
txn_id: UUID v7 if available; else UUID v4.- HA TTL: 5 minutes.
htuverification: Mode B (Public Identity URI) in production.
13. Future Work¶
- Optional per-request signing separate from HA (to reduce reliance on bearer credentials).
- Constrained delegation artifacts.
- Standardized metric names derived from spans (example
capiscio.hop_latency). - Sampling guidance for high-value transactions.
- Optional
body_hashas an experimental extension once streaming-safe patterns exist.
Appendix A: Canonical Hop Event JSON Schema (Normative)¶
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "capiscio:schema:rfc-004:hop-event:v0.2",
"title": "CapiscIO Hop Event",
"type": "object",
"required": [
"event.name",
"capiscio.txn_id",
"capiscio.hop.hop_id",
"capiscio.agent.did",
"capiscio.badge.jti",
"capiscio.target_aud"
],
"properties": {
"event.name": {
"type": "string",
"enum": ["capiscio.hop_emitted", "capiscio.hop_verified"]
},
"capiscio.txn_id": { "type": "string" },
"capiscio.hop.hop_id": { "type": "string" },
"capiscio.hop.parent_hash": { "type": ["string", "null"] },
"capiscio.hop.sig_kid": { "type": "string" },
"capiscio.agent.did": { "type": "string" },
"capiscio.badge.jti": { "type": "string" },
"capiscio.target_aud": { "type": "string" }
},
"additionalProperties": true
}
Appendix B: Vendor Export Guidelines (Non-Normative)¶
Datadog¶
- Map
capiscio.txn_idtotrace_idwhen feasible. - Map
capiscio.agent.didtousr.idto enable “User” pivot views.
Splunk / ELK¶
- Ensure
capiscio.*fields are indexed as keyword strings (not full-text) to allow exact filtering on transaction IDs.
Honeycomb¶
- Use
capiscio.txn_idas the high-cardinality grouping key for BubbleUp analysis. - Attach
capiscio.agent.didandcapiscio.badge.jtias first-class fields for slicing.
Appendix C: Example HTTP Exchange (Non-Normative)¶
Outbound request:
POST /v1/task HTTP/1.1
Host: api.partner.com
Authorization: Bearer <badge>
X-Capiscio-Txn: 018f4e1d-7e5d-7a9f-a9d2-8b6a0f2c9b11
X-Capiscio-Hop: <hop_attestation_jws>
Content-Type: application/json
{ ... }
Server emits event:
{
"event.name": "capiscio.hop_verified",
"capiscio.txn_id": "018f4e1d-7e5d-7a9f-a9d2-8b6a0f2c9b11",
"capiscio.hop.hop_id": "550e8400-e29b-41d4-a716-446655440000",
"capiscio.hop.parent_hash": null,
"capiscio.hop.sig_kid": "did:web:example.com:agents:my-agent#key-1",
"capiscio.agent.did": "did:web:example.com:agents:my-agent",
"capiscio.badge.jti": "b8f2c6a5-2d6f-4e44-9f55-2a1d6d9e0f12",
"capiscio.target_aud": "https://api.partner.com"
}
Changelog¶
| Version | Date | Changes |
|---|---|---|
| 0.3 | 2026-01-02 | Added: iss == badge.sub semantic binding (§9.2 step 7); MCP htu canonicalization (§6.3); explicit percent-encoding algorithm. Changed: aud → target_aud to distinguish from RFC-002 Badge aud. Fixed: Query normalization rules (sorting, encoding, %20 not +); parent hash audit obligation note; replay cache upgraded to SHOULD. |
| 0.2 | 2025-12-24 | Initial draft. |