@adcp/sdk 6.7.0 → 6.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/bin/adcp.js +15 -3
  2. package/dist/lib/adapters/derived-account-store.d.ts +152 -0
  3. package/dist/lib/adapters/derived-account-store.d.ts.map +1 -0
  4. package/dist/lib/adapters/derived-account-store.js +135 -0
  5. package/dist/lib/adapters/derived-account-store.js.map +1 -0
  6. package/dist/lib/adapters/implicit-account-store.d.ts +3 -1
  7. package/dist/lib/adapters/implicit-account-store.d.ts.map +1 -1
  8. package/dist/lib/adapters/implicit-account-store.js +3 -1
  9. package/dist/lib/adapters/implicit-account-store.js.map +1 -1
  10. package/dist/lib/adapters/index.d.ts +1 -0
  11. package/dist/lib/adapters/index.d.ts.map +1 -1
  12. package/dist/lib/adapters/index.js +7 -1
  13. package/dist/lib/adapters/index.js.map +1 -1
  14. package/dist/lib/adapters/oauth-passthrough-resolver.d.ts +3 -1
  15. package/dist/lib/adapters/oauth-passthrough-resolver.d.ts.map +1 -1
  16. package/dist/lib/adapters/oauth-passthrough-resolver.js +3 -1
  17. package/dist/lib/adapters/oauth-passthrough-resolver.js.map +1 -1
  18. package/dist/lib/adapters/roster-account-store.d.ts +85 -24
  19. package/dist/lib/adapters/roster-account-store.d.ts.map +1 -1
  20. package/dist/lib/adapters/roster-account-store.js +52 -30
  21. package/dist/lib/adapters/roster-account-store.js.map +1 -1
  22. package/dist/lib/index.d.ts +3 -3
  23. package/dist/lib/index.d.ts.map +1 -1
  24. package/dist/lib/index.js +13 -8
  25. package/dist/lib/index.js.map +1 -1
  26. package/dist/lib/mock-server/creative-ad-server/seed-data.d.ts +81 -0
  27. package/dist/lib/mock-server/creative-ad-server/seed-data.d.ts.map +1 -0
  28. package/dist/lib/mock-server/creative-ad-server/seed-data.js +200 -0
  29. package/dist/lib/mock-server/creative-ad-server/seed-data.js.map +1 -0
  30. package/dist/lib/mock-server/creative-ad-server/server.d.ts +39 -0
  31. package/dist/lib/mock-server/creative-ad-server/server.d.ts.map +1 -0
  32. package/dist/lib/mock-server/creative-ad-server/server.js +618 -0
  33. package/dist/lib/mock-server/creative-ad-server/server.js.map +1 -0
  34. package/dist/lib/mock-server/index.d.ts.map +1 -1
  35. package/dist/lib/mock-server/index.js +180 -24
  36. package/dist/lib/mock-server/index.js.map +1 -1
  37. package/dist/lib/mock-server/sales-non-guaranteed/seed-data.d.ts +66 -0
  38. package/dist/lib/mock-server/sales-non-guaranteed/seed-data.d.ts.map +1 -0
  39. package/dist/lib/mock-server/sales-non-guaranteed/seed-data.js +193 -0
  40. package/dist/lib/mock-server/sales-non-guaranteed/seed-data.js.map +1 -0
  41. package/dist/lib/mock-server/sales-non-guaranteed/server.d.ts +33 -0
  42. package/dist/lib/mock-server/sales-non-guaranteed/server.d.ts.map +1 -0
  43. package/dist/lib/mock-server/sales-non-guaranteed/server.js +782 -0
  44. package/dist/lib/mock-server/sales-non-guaranteed/server.js.map +1 -0
  45. package/dist/lib/mock-server/sponsored-intelligence/seed-data.d.ts +50 -0
  46. package/dist/lib/mock-server/sponsored-intelligence/seed-data.d.ts.map +1 -0
  47. package/dist/lib/mock-server/sponsored-intelligence/seed-data.js +133 -0
  48. package/dist/lib/mock-server/sponsored-intelligence/seed-data.js.map +1 -0
  49. package/dist/lib/mock-server/sponsored-intelligence/server.d.ts +13 -0
  50. package/dist/lib/mock-server/sponsored-intelligence/server.d.ts.map +1 -0
  51. package/dist/lib/mock-server/sponsored-intelligence/server.js +609 -0
  52. package/dist/lib/mock-server/sponsored-intelligence/server.js.map +1 -0
  53. package/dist/lib/protocols/mcp.d.ts.map +1 -1
  54. package/dist/lib/protocols/mcp.js +1 -41
  55. package/dist/lib/protocols/mcp.js.map +1 -1
  56. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  57. package/dist/lib/server/account-mode.d.ts +113 -0
  58. package/dist/lib/server/account-mode.d.ts.map +1 -0
  59. package/dist/lib/server/account-mode.js +125 -0
  60. package/dist/lib/server/account-mode.js.map +1 -0
  61. package/dist/lib/server/adcp-server.js +41 -0
  62. package/dist/lib/server/adcp-server.js.map +1 -1
  63. package/dist/lib/server/auth.d.ts +35 -0
  64. package/dist/lib/server/auth.d.ts.map +1 -1
  65. package/dist/lib/server/auth.js.map +1 -1
  66. package/dist/lib/server/create-adcp-server.d.ts +26 -9
  67. package/dist/lib/server/create-adcp-server.d.ts.map +1 -1
  68. package/dist/lib/server/create-adcp-server.js +46 -20
  69. package/dist/lib/server/create-adcp-server.js.map +1 -1
  70. package/dist/lib/server/ctx-metadata/store.d.ts +1 -1
  71. package/dist/lib/server/ctx-metadata/store.d.ts.map +1 -1
  72. package/dist/lib/server/ctx-metadata/store.js +1 -0
  73. package/dist/lib/server/ctx-metadata/store.js.map +1 -1
  74. package/dist/lib/server/decisioning/account.d.ts +5 -0
  75. package/dist/lib/server/decisioning/account.d.ts.map +1 -1
  76. package/dist/lib/server/decisioning/account.js.map +1 -1
  77. package/dist/lib/server/decisioning/buyer-agent.d.ts +37 -4
  78. package/dist/lib/server/decisioning/buyer-agent.d.ts.map +1 -1
  79. package/dist/lib/server/decisioning/buyer-agent.js +12 -2
  80. package/dist/lib/server/decisioning/buyer-agent.js.map +1 -1
  81. package/dist/lib/server/decisioning/compose.d.ts +33 -2
  82. package/dist/lib/server/decisioning/compose.d.ts.map +1 -1
  83. package/dist/lib/server/decisioning/compose.js +13 -46
  84. package/dist/lib/server/decisioning/compose.js.map +1 -1
  85. package/dist/lib/server/decisioning/index.d.ts +2 -1
  86. package/dist/lib/server/decisioning/index.d.ts.map +1 -1
  87. package/dist/lib/server/decisioning/index.js +2 -1
  88. package/dist/lib/server/decisioning/index.js.map +1 -1
  89. package/dist/lib/server/decisioning/platform-helpers.d.ts +18 -0
  90. package/dist/lib/server/decisioning/platform-helpers.d.ts.map +1 -1
  91. package/dist/lib/server/decisioning/platform-helpers.js +20 -0
  92. package/dist/lib/server/decisioning/platform-helpers.js.map +1 -1
  93. package/dist/lib/server/decisioning/platform.d.ts +19 -21
  94. package/dist/lib/server/decisioning/platform.d.ts.map +1 -1
  95. package/dist/lib/server/decisioning/platform.js.map +1 -1
  96. package/dist/lib/server/decisioning/runtime/from-platform.d.ts.map +1 -1
  97. package/dist/lib/server/decisioning/runtime/from-platform.js +334 -44
  98. package/dist/lib/server/decisioning/runtime/from-platform.js.map +1 -1
  99. package/dist/lib/server/decisioning/runtime/observed-modes.d.ts +40 -0
  100. package/dist/lib/server/decisioning/runtime/observed-modes.d.ts.map +1 -0
  101. package/dist/lib/server/decisioning/runtime/observed-modes.js +82 -0
  102. package/dist/lib/server/decisioning/runtime/observed-modes.js.map +1 -0
  103. package/dist/lib/server/decisioning/runtime/protocol-for-tool.js +2 -2
  104. package/dist/lib/server/decisioning/runtime/protocol-for-tool.js.map +1 -1
  105. package/dist/lib/server/decisioning/runtime/validate-platform.d.ts.map +1 -1
  106. package/dist/lib/server/decisioning/runtime/validate-platform.js +9 -1
  107. package/dist/lib/server/decisioning/runtime/validate-platform.js.map +1 -1
  108. package/dist/lib/server/decisioning/specialisms/sponsored-intelligence.d.ts +125 -0
  109. package/dist/lib/server/decisioning/specialisms/sponsored-intelligence.d.ts.map +1 -0
  110. package/dist/lib/server/decisioning/specialisms/sponsored-intelligence.js +52 -0
  111. package/dist/lib/server/decisioning/specialisms/sponsored-intelligence.js.map +1 -0
  112. package/dist/lib/server/decisioning/tenant-registry.d.ts +16 -0
  113. package/dist/lib/server/decisioning/tenant-registry.d.ts.map +1 -1
  114. package/dist/lib/server/decisioning/tenant-registry.js.map +1 -1
  115. package/dist/lib/server/index.d.ts +4 -1
  116. package/dist/lib/server/index.d.ts.map +1 -1
  117. package/dist/lib/server/index.js +9 -3
  118. package/dist/lib/server/index.js.map +1 -1
  119. package/dist/lib/server/serve.js +20 -2
  120. package/dist/lib/server/serve.js.map +1 -1
  121. package/dist/lib/server/test-controller.d.ts +3 -0
  122. package/dist/lib/server/test-controller.d.ts.map +1 -1
  123. package/dist/lib/server/test-controller.js +23 -20
  124. package/dist/lib/server/test-controller.js.map +1 -1
  125. package/dist/lib/testing/comply-controller.d.ts +23 -2
  126. package/dist/lib/testing/comply-controller.d.ts.map +1 -1
  127. package/dist/lib/testing/comply-controller.js +19 -2
  128. package/dist/lib/testing/comply-controller.js.map +1 -1
  129. package/dist/lib/testing/index.d.ts +1 -1
  130. package/dist/lib/testing/index.d.ts.map +1 -1
  131. package/dist/lib/testing/index.js.map +1 -1
  132. package/dist/lib/testing/storyboard/validations.d.ts.map +1 -1
  133. package/dist/lib/testing/storyboard/validations.js +36 -54
  134. package/dist/lib/testing/storyboard/validations.js.map +1 -1
  135. package/dist/lib/testing/test-controller.d.ts +10 -4
  136. package/dist/lib/testing/test-controller.d.ts.map +1 -1
  137. package/dist/lib/testing/test-controller.js +9 -3
  138. package/dist/lib/testing/test-controller.js.map +1 -1
  139. package/dist/lib/types/index.d.ts +3 -2
  140. package/dist/lib/types/index.d.ts.map +1 -1
  141. package/dist/lib/types/index.js +3 -0
  142. package/dist/lib/types/index.js.map +1 -1
  143. package/dist/lib/utils/glob.d.ts +4 -2
  144. package/dist/lib/utils/glob.d.ts.map +1 -1
  145. package/dist/lib/utils/glob.js +4 -2
  146. package/dist/lib/utils/glob.js.map +1 -1
  147. package/dist/lib/version.d.ts +3 -3
  148. package/dist/lib/version.js +3 -3
  149. package/docs/llms.txt +2 -2
  150. package/examples/README.md +29 -13
  151. package/examples/hello-cluster.ts +62 -23
  152. package/examples/hello_creative_adapter_ad_server.ts +790 -0
  153. package/examples/hello_seller_adapter_guaranteed.ts +80 -22
  154. package/examples/hello_seller_adapter_non_guaranteed.ts +1020 -0
  155. package/examples/hello_si_adapter_brand.ts +572 -0
  156. package/package.json +3 -2
  157. package/skills/build-creative-agent/SKILL.md +103 -183
  158. package/skills/build-generative-seller-agent/SKILL.md +15 -9
  159. package/skills/build-governance-agent/SKILL.md +20 -11
  160. package/skills/build-retail-media-agent/SKILL.md +10 -8
  161. package/skills/build-seller-agent/SKILL.md +15 -13
  162. package/skills/build-seller-agent/specialisms/sales-non-guaranteed.md +9 -31
  163. package/skills/build-si-agent/SKILL.md +251 -196
  164. package/skills/build-signals-agent/SKILL.md +2 -0
  165. package/skills/call-adcp-agent/SKILL.md +7 -1
  166. package/skills/call-adcp-agent.previous/SKILL.md +0 -261
@@ -1,136 +1,124 @@
1
1
  ---
2
2
  name: build-si-agent
3
- description: Use when building an AdCP sponsored intelligence agent — a platform that serves conversational sponsored content within user sessions.
3
+ description: Use when building an AdCP sponsored intelligence agent — a brand-agent platform that hosts conversational sponsored content (offering discovery, session lifecycle, ACP checkout handoff).
4
4
  ---
5
5
 
6
6
  # Build a Sponsored Intelligence Agent
7
7
 
8
8
  ## Overview
9
9
 
10
- A sponsored intelligence (SI) agent serves conversational sponsored content within user sessions. Buyers discover offerings, initiate sessions, exchange messages, and terminate when done. The agent manages session state and delivers sponsored content in conversational form.
10
+ A sponsored intelligence (SI) agent runs a brand-side conversational AI experience that an LLM host (ChatGPT, Claude, Perplexity, Arc, etc.) can hand off to. The buyer agent calls four tools across the session lifecycle:
11
+
12
+ 1. `si_get_offering` — discover what's available, get an `offering_token`
13
+ 2. `si_initiate_session` — start a conversation, receive `session_id`
14
+ 3. `si_send_message` — exchange turns, optionally surface a handoff hint
15
+ 4. `si_terminate_session` — end the session, optionally return ACP checkout payload
16
+
17
+ The agent owns the brand voice, transcript state, and product knowledge. The host owns the user, identity consent, and ACP checkout. SI is the AdCP surface that connects them.
11
18
 
12
19
  ## When to Use
13
20
 
14
- - User wants to build an agent that serves sponsored conversational content
15
- - User mentions sponsored intelligence, SI sessions, conversational ads, or sponsored chat
16
- - User references `si_initiate_session`, `si_send_message`, or the SI protocol
21
+ - User wants to build a brand-agent platform that hosts conversational ads (Salesforce Agentforce, OpenAI Assistants brand mode, custom in-house brand chat).
22
+ - User mentions sponsored intelligence, SI sessions, conversational ads, brand handoff, or ACP checkout.
23
+ - User references `si_initiate_session`, `si_send_message`, `si_get_offering`, or `si_terminate_session`.
17
24
 
18
25
  **Not this skill:**
19
26
 
20
27
  - Selling display/video inventory → `skills/build-seller-agent/`
21
28
  - Serving audience segments → `skills/build-signals-agent/`
22
29
  - Managing creatives → `skills/build-creative-agent/`
30
+ - Brand identity + rights licensing → `skills/build-brand-rights-agent/`
23
31
 
24
- ## Specialisms This Skill Covers
25
-
26
- | Specialism | Status | Delta |
27
- | ------------ | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
28
- | _(none yet)_ | — | SI has no specialisms in AdCP 3.0 — pass the `sponsored_intelligence` *protocol* baseline (declared via `supported_protocols: ['sponsored_intelligence']`). Specialism storyboards for conversational-ad-specific patterns are pending future AdCP releases. |
29
-
30
- ## Before Writing Code
32
+ ## Specialism (or rather, protocol)
31
33
 
32
- ### 1. What Offerings?
34
+ SI is a **protocol** in AdCP 3.0, not a specialism. The agent declares it via the `sponsoredIntelligence` field on the v6 `DecisioningPlatform` — the framework auto-derives `'sponsored_intelligence'` into `supported_protocols` from the four registered SI tools. There's no `specialisms: ['sponsored-intelligence']` claim today (tracked at adcontextprotocol/adcp#3961 for 3.1; when it lands, `capabilities.specialisms` becomes additive — adopters claim either form, dispatch keeps working).
33
35
 
34
- Each offering represents a sponsored content experience. Define:
36
+ Storyboard: `si_baseline` at `compliance/cache/latest/protocols/sponsored-intelligence/index.yaml`. Three phases (capability_discovery, offering_discovery, session_lifecycle) covering all four tools. The reference adapter at `examples/hello_si_adapter_brand.ts` reports **3/3 scenarios pass**.
35
37
 
36
- - Product/brand being sponsored
37
- - Content style (informational, promotional, interactive)
38
- - Supported modalities: conversational (text), rich_media (images/video)
38
+ ## Protocol-Wide Requirements
39
39
 
40
- ### 2. Session Behavior
40
+ Full treatment in `skills/build-seller-agent/SKILL.md` §Protocol-Wide Requirements. Minimum viable pointers for an SI agent:
41
41
 
42
- How should the agent respond during a session?
42
+ - **`idempotency_key`** required on every mutating request — `si_initiate_session` and `si_send_message`. `si_terminate_session` is naturally idempotent on `session_id` and intentionally lacks the key (re-terminating a closed session must return the same payload). `si_get_offering` is read-only.
43
+ - **Authentication** via `serve({ authenticate })` with `verifyApiKey` / `verifyBearer`. Unauthenticated agents fail the universal `security_baseline` storyboard.
44
+ - **Signature-header transparency**: accept requests with `Signature-Input` / `Signature` headers even if you don't claim `signed-requests`.
43
45
 
44
- - **Informational** answers questions about the sponsored product
45
- - **Promotional** — proactively highlights features and benefits
46
- - **Interactive** — guided product exploration with branching content
46
+ ## Before Writing Code
47
47
 
48
- ## Tools and Required Response Shapes
48
+ ### 1. What brand?
49
49
 
50
- > **Before writing any handler's return statement, fetch [`docs/llms.txt`](../../docs/llms.txt) and grep for `#### \`<tool_name>\``(e.g.`#### \`si_initiate_session\``) to read the exact required + optional field list.** The schema-derived contract lives there; this skill covers patterns, gotchas, and domain-specific examples. Strict response validation is on by default in dev — it will tell you the exact field path if you drift, so write the obvious thing and trust the contract.
51
- >
52
- > **Cross-cutting pitfalls matrix runs keep catching:**
53
- >
54
- > - **Do NOT declare a `sponsored-intelligence` specialism.** SI is a *protocol* in AdCP 3.0 — declared via `supported_protocols: ['sponsored_intelligence']` on the `get_adcp_capabilities` response. There is no SI specialism in the `AdCPSpecialism` enum yet, so adopters wire SI through the v5 handler-bag path (`createAdcpServer` from `@adcp/sdk/server/legacy/v5`). The v6 `DecisioningPlatform` interface does not yet expose a `sponsoredIntelligence` field. (Tracking: SI specialism + auto-hydration of `req.session` is planned for a later v6.x — adopters today persist sessions explicitly via `ctx.store`.)
50
+ SI agents are typically **single-brand per deployment** one Agentforce instance per advertiser, one OpenAI Assistant per brand, one in-house service per product line. Multi-brand variants exist (one customer fronting many brands) but route via per-API-key tenant binding inside `accounts.resolve`, not by carrying `account` on the wire (the SI tool schemas don't have it).
55
51
 
56
- **`get_adcp_capabilities`** register first, empty `{}` schema
52
+ Decide: how many brands does this agent serve, and how does each request bind to one?
57
53
 
58
- ```
59
- capabilitiesResponse({
60
- adcp: { major_versions: [3] },
61
- supported_protocols: ['sponsored_intelligence'],
62
- })
63
- ```
54
+ ### 2. Where does session state live?
64
55
 
65
- **`si_get_offering`** — `SIGetOfferingRequestSchema.shape`
56
+ The framework auto-hydrates a small `req.session` record (intent, offering scoping, identity consent, negotiated capabilities) onto `si_send_message` / `si_terminate_session` calls fine for the fixture / mock case and the "what was the original scope?" lookup. **Production brand engines almost always own full transcript state in their own backend** (Postgres, Redis, vector store) — full transcripts, RAG embeddings, tool-call logs are too rich for `ctx_metadata` and easily exceed the 16KB blob cap. Treat `req.session` as a convenience, not authoritative state.
66
57
 
67
- Check if an offering is available. Return `available: true` with an `offering_token` the buyer passes to `si_initiate_session`.
58
+ ### 3. What offerings?
68
59
 
69
- ```
70
- taskToolResponse({
71
- available: true, // required — boolean
72
- offering_token: string, // token for session initiation
73
- ttl_seconds: 300, // how long the token is valid
74
- })
75
- ```
60
+ Each offering represents a sponsored experience the brand hosts:
76
61
 
77
- **`si_initiate_session`** `SIInitiateSessionRequestSchema.shape`
62
+ - **Product/brand being sponsored** (`Volta EV`, `Trail Runner Summer Collection`)
63
+ - **Conversation style** — informational, promotional, interactive
64
+ - **Supported modalities** — text-only, voice, video, A2UI surfaces
65
+ - **Lifetime** — a TTL on the `offering_token`
78
66
 
79
- Create a new session. Return `session_id` and `session_status`.
67
+ ### 4. Handoff modes?
80
68
 
81
- ```
82
- taskToolResponse({
83
- session_id: string, // required — unique session identifier
84
- session_status: 'active', // required — NB: 'session_status' not 'status'
85
- })
86
- ```
69
+ `si_terminate_session` carries a `reason` field. Two of the values trigger ACP checkout:
87
70
 
88
- **`si_send_message`** — `SISendMessageRequestSchema.shape`
71
+ - `handoff_transaction` return `acp_handoff` with `checkout_url`, `checkout_token`, `expires_at`
72
+ - `handoff_complete` — conversation concluded naturally; no checkout
89
73
 
90
- Process a user message and return sponsored content.
74
+ Other values (`user_exit`, `session_timeout`, `host_terminated`) are operational. Decide which transactional flows your brand supports — at minimum, `handoff_complete` is universal.
91
75
 
92
- ```
93
- taskToolResponse({
94
- session_id: string, // required — echo from request
95
- session_status: 'active', // required
96
- response: {
97
- content: string, // the sponsored content text
98
- content_type: 'text',
99
- },
100
- })
101
- ```
102
-
103
- **`si_terminate_session`** — `SITerminateSessionRequestSchema.shape`
104
-
105
- End the session.
76
+ ## Tools and Required Response Shapes
106
77
 
107
- ```
108
- taskToolResponse({
109
- session_id: string, // required echo from request
110
- terminated: true, // required — boolean confirming termination
111
- })
112
- ```
78
+ > **Before writing any handler's return statement, fetch [`docs/llms.txt`](../../docs/llms.txt) and grep for `#### \`<tool_name>\``** (e.g. `#### \`si_initiate_session\``) to read the exact required + optional field list. The schema-derived contract lives there; this skill covers patterns, gotchas, and SI-specific examples. Strict response validation is on by default in dev — it will tell you the exact field path if you drift.
79
+ >
80
+ > **Cross-cutting pitfalls SI compliance keeps catching:**
81
+ >
82
+ > - **Field name is `session_status`, not `status`.** `'active' | 'pending_handoff' | 'complete' | 'terminated'`. `status: 'active'` fails wire validation with `/session_status: must be one of...`.
83
+ > - **Termination uses boolean `terminated: true`**, not `status: 'terminated'`.
84
+ > - **`si_send_message` response — `session_id` is required**, even though it's also in the request. Echo it from `req.session_id`.
85
+ > - **`si_get_offering` — `available: boolean` is required** at the top level even when nothing else is.
86
+ > - **`reason` enum on `si_terminate_session` is closed** — `user_exit | session_timeout | host_terminated | handoff_transaction | handoff_complete`. Anything else fails wire validation.
87
+ > - **`product_card` in `ui_elements` requires `data.title` + `data.price`.** Upstream brand-platform vocabulary often uses `name` + `display_price`; project per-`type` (the example does this in `projectComponent`). Same gotcha for `action_button` → requires `data.label` + `data.action`.
88
+
89
+ **Handler bindings — read the Contract column entry before writing each return:**
90
+
91
+ | Tool | Handler | Contract | Gotchas |
92
+ | ----------------------- | ---------------------------------------- | ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
93
+ | `get_adcp_capabilities` | auto-generated | n/a | Do not register manually. Framework adds `'sponsored_intelligence'` to `supported_protocols` when `platform.sponsoredIntelligence` is present. |
94
+ | `si_get_offering` | `sponsoredIntelligence.getOffering` | [`#si_get_offering`](../../docs/llms.txt#si_get_offering) | Mint an `offering_token` so the brand can recall the products-shown record on the next `initiateSession` (resolves "the second one" without replaying the transcript). Top-level requires `available: boolean`. Offering details nest under `offering`. |
95
+ | `si_initiate_session` | `sponsoredIntelligence.initiateSession` | [`#si_initiate_session`](../../docs/llms.txt#si_initiate_session) | Returns `session_id` + initial assistant turn. Framework auto-stores a small session record (intent, offering scoping, identity, negotiated capabilities, ttl) under `ResourceKind: 'si_session'`. Required `idempotency_key` — replays must return the same response. |
96
+ | `si_send_message` | `sponsoredIntelligence.sendMessage` | [`#si_send_message`](../../docs/llms.txt#si_send_message) | Auto-hydrated `req.session` from the stored record. `idempotency_key` required — each turn is a transcript mutation. Surface `pending_handoff` + populated `handoff` block to signal the host to call terminate. |
97
+ | `si_terminate_session` | `sponsoredIntelligence.terminateSession` | [`#si_terminate_session`](../../docs/llms.txt#si_terminate_session) | Naturally idempotent on `session_id`; framework stores the `acp_handoff` payload so re-terminate replays return the same result. No `idempotency_key`. |
113
98
 
114
99
  ### Context and Ext Passthrough
115
100
 
116
- The framework auto-echoes the request's `context` into every response — **do not set `context` yourself** on responses for tools whose request-side `context` is the protocol echo object (`core/context.json`).
101
+ The framework auto-echoes the request's `context` into every response from typed sub-platform handlers — **do not set `context` yourself in your handler return values.** It's injected post-handler only when the field isn't already present.
117
102
 
118
- **SI override.** `si_get_offering` and `si_initiate_session` override `context` on the request as a domain-specific **string** (natural-language intent hint, per spec: _'mens size 14 near Cincinnati'_). The response schema still keeps `context` as the protocol echo object. The framework detects this mismatch and skips the auto-echo for non-object values — your response simply won't carry a `context` field unless you populate it. If you want correlation tracking for SI responses, construct the context object in your handler (e.g., from a buyer-supplied `ext.correlation_id` or your own generator) and return it on the response.
103
+ **SI override.** `si_get_offering` and `si_initiate_session` allow `intent` as a top-level natural-language string (per spec: _'mens size 14 near Cincinnati'_). The response schema still keeps `context` as the protocol echo object the framework auto-echoes object-typed `context` and skips non-object intent strings. If you want correlation tracking, populate `context: { correlation_id, ... }` in the request envelope and the framework round-trips it.
119
104
 
120
- `si_send_message` and `si_terminate_session` use the standard protocol echo object on both sides — leave `context` out of the handler return and the framework will echo it.
105
+ `si_send_message` and `si_terminate_session` use the standard protocol echo on both sides — leave `context` out of your handler return.
121
106
 
122
107
  ## SDK Quick Reference
123
108
 
124
- | SDK piece | Usage |
125
- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
126
- | `createAdcpServer(config)` *(use this for SI)* | v5 handler-bag entry. The only path that ships SI dispatch (the `sponsoredIntelligence: { getOffering, initiateSession, sendMessage, terminateSession }` sub-bag). Reach via `@adcp/sdk/server/legacy/v5`. v6 `createAdcpServerFromPlatform` does not yet expose an SI specialism when it does, this skill will document the migration. |
127
- | `serve(() => createAdcpServer(config))` | Start HTTP server on `:3001/mcp` |
128
- | `ctx.store` | State persistence `get/put/patch/delete/list` domain objects. SI sessions live here today (no auto-hydration yet). |
129
- | `adcpError(code, { message })` | Structured error |
109
+ | SDK piece | Usage |
110
+ | ---------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
111
+ | `createAdcpServerFromPlatform(platform, opts)` | Create server from a typed `DecisioningPlatform` — compile-time enforcement, auto-derived capabilities |
112
+ | `definePlatform<TConfig, TCtxMeta>({ capabilities, accounts, sponsoredIntelligence })` | Type-level identity helper for the platform object literal |
113
+ | `defineSponsoredIntelligencePlatform<TCtxMeta>({ getOffering, initiateSession, sendMessage, terminateSession })` | Type-level identity for the `SponsoredIntelligencePlatform` sub-object |
114
+ | `serve(() => createAdcpServerFromPlatform(platform, opts))` | Start HTTP server on `:3001/mcp` |
115
+ | `req.session` _(on `sendMessage` / `terminateSession`)_ | Auto-hydrated session record — intent, offering scoping, identity, negotiated capabilities, ttl. Fixture-grade; production owns full state in its own backend. |
116
+ | `ctx.store` | Adopter-managed state. Use for full transcripts, RAG embeddings — anything past the 16KB blob cap on auto-hydration. |
117
+ | `adcpError(code, { message })` | Structured error |
130
118
 
131
- Handlers return raw data objects. The framework auto-wraps responses and auto-generates `get_adcp_capabilities` from registered handlers.
119
+ Handlers return raw data objects. The framework auto-wraps responses, auto-generates `get_adcp_capabilities` from registered handlers, and emits `'sponsored_intelligence'` in `supported_protocols` when the platform field is present.
132
120
 
133
- Import: `import { createAdcpServer, serve, adcpError } from '@adcp/sdk/server/legacy/v5';`
121
+ Import: `import { createAdcpServerFromPlatform, definePlatform, defineSponsoredIntelligencePlatform, serve, adcpError } from '@adcp/sdk/server';`
134
122
 
135
123
  ## Setup
136
124
 
@@ -159,111 +147,175 @@ Minimal `tsconfig.json`:
159
147
 
160
148
  ## Implementation
161
149
 
162
- 1. Single `.ts` file wire `createAdcpServer` from `@adcp/sdk/server/legacy/v5` with a `sponsoredIntelligence` handler bag
163
- 2. Do not register `get_adcp_capabilities` — the framework generates it from registered handlers
164
- 3. Return raw data objects from handlers — the framework wraps responses automatically
165
- 4. Use `ctx.store` to persist active sessions — track state: active → terminated. **Sessions are NOT auto-hydrated yet** (planned for v6.x). Read `req.session_id` and look the session up in `ctx.store` on every `si_send_message`.
166
- 5. Handlers receive `(params, ctx)` — `ctx.store` for state, `ctx.account` (when `resolveAccount` is wired) for resolved account
150
+ The reference adapter at [`examples/hello_si_adapter_brand.ts`](../../examples/hello_si_adapter_brand.ts) is the worked starting point. Fork it, replace `// SWAP:` markers with calls to your real backend.
151
+
152
+ Skeleton:
167
153
 
168
154
  ```typescript
169
155
  import { randomUUID } from 'node:crypto';
170
156
  import {
171
- createAdcpServer,
157
+ createAdcpServerFromPlatform,
158
+ definePlatform,
159
+ defineSponsoredIntelligencePlatform,
172
160
  serve,
173
- adcpError,
161
+ verifyApiKey,
174
162
  createIdempotencyStore,
175
163
  memoryBackend,
176
- } from '@adcp/sdk/server/legacy/v5';
164
+ adcpError,
165
+ type AccountStore,
166
+ } from '@adcp/sdk/server';
177
167
 
178
- const idempotency = createIdempotencyStore({
179
- backend: memoryBackend(),
180
- ttlSeconds: 86400,
181
- });
168
+ interface BrandMeta {
169
+ brand_id: string;
170
+ [key: string]: unknown;
171
+ }
182
172
 
183
- serve(() =>
184
- createAdcpServer({
185
- name: 'SI Agent',
186
- version: '1.0.0',
187
- idempotency,
188
- // Principal scope for idempotency. MUST never return undefined. The
189
- // framework additionally auto-scopes `si_send_message` by `session_id`,
190
- // so the same key under two sessions doesn't cross-replay.
191
- resolveSessionKey: () => 'default-principal',
192
- capabilities: {
193
- // SI is a *protocol*, not a specialism. Declare it here; the framework
194
- // adds it to `get_adcp_capabilities.supported_protocols`.
195
- supported_protocols: ['sponsored_intelligence'],
196
- },
197
- sponsoredIntelligence: {
198
- getOffering: async (req, ctx) => ({
199
- available: true,
200
- offering_token: `tok_${randomUUID()}`,
201
- ttl_seconds: 300,
202
- }),
203
- initiateSession: async (req, ctx) => {
204
- // session_id MUST be high-entropy (≥122 bits) per spec — it's the
205
- // scope key for conversational isolation. Never use Date.now() or
206
- // predictable counters; a guessable session_id lets one buyer
207
- // impersonate another's session.
208
- const sessionId = `sess_${randomUUID()}`;
209
- await ctx.store.put('session', sessionId, { status: 'active' });
210
- return {
211
- session_id: sessionId,
212
- session_status: 'active',
213
- };
173
+ const accounts: AccountStore<BrandMeta> = {
174
+ resolve: async ref => {
175
+ // SI tool schemas don't carry `account` on the wire — `resolve(undefined)`
176
+ // fires on every request. Fall back to the per-tenant brand binding from
177
+ // your auth layer (ctx.authInfo) here. Single-brand deployments hardcode.
178
+ return {
179
+ id: 'brand_volta',
180
+ name: 'Nova Motors',
181
+ status: 'active',
182
+ ctx_metadata: { brand_id: 'brand_volta' },
183
+ };
184
+ },
185
+ };
186
+
187
+ const sponsoredIntelligence = defineSponsoredIntelligencePlatform<BrandMeta>({
188
+ getOffering: async (req, ctx) => {
189
+ // SWAP: look up offering in your CMS / catalog. Mint an offering_token
190
+ // so initiateSession can recall what products were shown.
191
+ return {
192
+ available: true,
193
+ offering_token: `oqt_${randomUUID()}`,
194
+ ttl_seconds: 900,
195
+ offering: {
196
+ offering_id: req.offering_id,
197
+ title: 'Volta EV — Conversational Concierge',
198
+ summary: 'Talk to the Volta product team about range, charging, and lease vs. buy.',
199
+ landing_url: 'https://novamotors.example/volta',
214
200
  },
215
- sendMessage: async (req, ctx) => {
216
- // No auto-hydration of sessions yet — read explicitly. (v6.x will
217
- // attach `req.session` for free; until then this lookup is your
218
- // session-loss guard.)
219
- const session = await ctx.store.get('session', req.session_id);
220
- // Return the error the framework echoes returned adcpError
221
- // responses verbatim. Thrown errors are caught and converted to
222
- // SERVICE_UNAVAILABLE, which hides your custom code from the buyer.
223
- if (!session) return adcpError('RESOURCE_NOT_FOUND', { message: 'Session not found' });
224
- return {
225
- session_id: req.session_id,
226
- session_status: 'active' as const,
227
- response: {
228
- content: 'Sponsored content response',
229
- content_type: 'text',
230
- },
231
- };
201
+ };
202
+ },
203
+
204
+ initiateSession: async (req, ctx) => {
205
+ // SWAP: spin up your brand-engine session. session_id MUST be high-entropy
206
+ // (≥122 bits)a guessable id lets one buyer impersonate another's
207
+ // session. The framework auto-stores intent / offering / identity onto
208
+ // req.session for subsequent calls.
209
+ const sessionId = `sess_${randomUUID()}`;
210
+ // SWAP: persist your full session state (transcript, RAG context, etc.)
211
+ // in your own backend keyed by sessionId. ctx.store is for the small
212
+ // auto-hydrated record only.
213
+ return {
214
+ session_id: sessionId,
215
+ session_status: 'active' as const,
216
+ session_ttl_seconds: 1200,
217
+ response: {
218
+ message: 'Hi from Volta. What are you curious about — range, charging, or pricing?',
232
219
  },
233
- terminateSession: async (req, ctx) => {
234
- await ctx.store.delete('session', req.session_id);
235
- return {
236
- session_id: req.session_id,
237
- terminated: true,
238
- };
220
+ };
221
+ },
222
+
223
+ sendMessage: async (req, ctx) => {
224
+ // req.session is auto-hydrated with the original intent / offering scope.
225
+ // SWAP: load the full transcript from your own backend, append the new turn,
226
+ // run your brand-aware LLM, write back, return the assistant response.
227
+ return {
228
+ session_id: req.session_id,
229
+ session_status: 'active' as const,
230
+ response: {
231
+ message: 'The Volta Long Range goes 320 miles on a full charge.',
232
+ ui_elements: [
233
+ {
234
+ type: 'product_card',
235
+ data: {
236
+ title: 'Volta EV Long Range AWD',
237
+ price: '$48,900',
238
+ image_url: 'https://test-assets.adcontextprotocol.org/nova-motors/volta-long-range.jpg',
239
+ },
240
+ },
241
+ ],
239
242
  },
240
- },
241
- })
243
+ };
244
+ },
245
+
246
+ terminateSession: async (req, ctx) => {
247
+ // SWAP: close your session, finalize transcripts, mint ACP checkout token
248
+ // if reason is handoff_transaction. The framework auto-stores acp_handoff
249
+ // onto the session record so re-terminate replays return the same payload.
250
+ return {
251
+ session_id: req.session_id,
252
+ terminated: true,
253
+ session_status: 'terminated' as const,
254
+ ...(req.reason === 'handoff_transaction'
255
+ ? {
256
+ acp_handoff: {
257
+ checkout_url: `https://novamotors.example/checkout?conv=${req.session_id}`,
258
+ checkout_token: `acp_tok_${randomUUID()}`,
259
+ expires_at: new Date(Date.now() + 60 * 60_000).toISOString(),
260
+ },
261
+ }
262
+ : {}),
263
+ };
264
+ },
265
+ });
266
+
267
+ const platform = definePlatform<Record<string, never>, BrandMeta>({
268
+ // SI is a protocol, not a specialism (adcp#3961). The platform field's
269
+ // presence is the declaration; framework auto-derives 'sponsored_intelligence'
270
+ // into supported_protocols from the four SI tools getting registered.
271
+ capabilities: { specialisms: [] as const, config: {} },
272
+ accounts,
273
+ sponsoredIntelligence,
274
+ });
275
+
276
+ const idempotency = createIdempotencyStore({
277
+ backend: memoryBackend(),
278
+ ttlSeconds: 86_400,
279
+ });
280
+
281
+ serve(
282
+ ({ taskStore }) =>
283
+ createAdcpServerFromPlatform(platform, {
284
+ name: 'My SI Agent',
285
+ version: '1.0.0',
286
+ taskStore,
287
+ idempotency,
288
+ }),
289
+ {
290
+ // SWAP: wire authentication here — see § Protecting your agent below.
291
+ // An unauthenticated agent fails the universal `security_baseline`
292
+ // storyboard.
293
+ }
242
294
  );
243
295
  ```
244
296
 
245
297
  ## Idempotency
246
298
 
247
- AdCP v3 requires an `idempotency_key` on every mutating request for SI agents that's `si_initiate_session` and `si_send_message`. `si_terminate_session` is exempt (naturally idempotent via `session_id`; terminating a terminated session is a no-op, and its schema keeps `idempotency_key` optional). `si_get_offering` is read-only.
299
+ AdCP v3 requires `idempotency_key` on every mutating request. For SI: `si_initiate_session` and `si_send_message`. `si_terminate_session` is exempt (naturally idempotent via `session_id`). `si_get_offering` is read-only.
248
300
 
249
- Idempotency is wired in the example above. What the framework handles for you:
301
+ What the framework handles when you pass `idempotency: createIdempotencyStore(...)`:
250
302
 
251
- - Rejects missing or malformed `idempotency_key` with `INVALID_REQUEST`. The spec pattern is `^[A-Za-z0-9_.:-]{16,255}$` — short test keys like `"key1"` fail length, not idempotency logic.
252
- - **`si_send_message` is auto-scoped by `session_id`** in addition to the principal. The same `idempotency_key` used across two sessions does NOT cross-replay — each session has its own idempotency namespace. You don't have to implement this; the framework does it.
303
+ - Rejects missing or malformed `idempotency_key` with `INVALID_REQUEST`. Spec pattern is `^[A-Za-z0-9_.:-]{16,255}$` — short test keys like `"key1"` fail length, not idempotency logic.
304
+ - **`si_send_message` is auto-scoped by `session_id`** in addition to the principal. Same key under two sessions does not cross-replay.
253
305
  - JCS-canonicalized payload hashing; `IDEMPOTENCY_CONFLICT` on same-key-different-payload (no payload leak — error body is code + message only).
254
306
  - `IDEMPOTENCY_EXPIRED` past the TTL (±60s clock-skew tolerance).
255
- - `replayed: true` on `result.structuredContent.replayed` for cache hits; fresh executions omit the field.
307
+ - `replayed: true` on `result.structuredContent.replayed` for cache hits.
256
308
  - Auto-declares `adcp.idempotency.replay_ttl_seconds` on `get_adcp_capabilities`.
257
- - Only successful responses cache — a failed generation re-executes on retry so buyers can safely retry transient errors without burning the key or double-billing.
309
+ - Only successful responses cache — a failed generation re-executes on retry so buyers can safely retry transient errors without burning the key.
258
310
 
259
- `ttlSeconds` must be in `[3600, 604800]` — out of range throws at `createIdempotencyStore` construction. Don't pass minutes thinking they're seconds.
311
+ `ttlSeconds` must be in `[3600, 604800]` — out of range throws at construction.
260
312
 
261
313
  ## Protecting your agent
262
314
 
263
- **An AdCP agent that accepts unauthenticated requests is non-compliant** (see `security_baseline` in the universal storyboard bundle). Ask the operator: "API key, OAuth, or both?" — then wire one of these into `serve()`.
315
+ **An AdCP agent that accepts unauthenticated requests is non-compliant** (see `security_baseline` in the universal storyboard bundle). Wire one of these into `serve()`:
264
316
 
265
317
  ```typescript
266
- import { serve, verifyApiKey, verifyBearer, anyOf } from '@adcp/sdk/server/legacy/v5';
318
+ import { serve, verifyApiKey, verifyBearer, anyOf } from '@adcp/sdk/server';
267
319
 
268
320
  // API key — simplest, good for B2B integrations
269
321
  serve(createAgent, {
@@ -278,7 +330,7 @@ serve(createAgent, {
278
330
  // OAuth — best when buyers authenticate as themselves
279
331
  const AGENT_URL = 'https://my-agent.example.com/mcp';
280
332
  serve(createAgent, {
281
- publicUrl: AGENT_URL, // canonical RFC 8707 audience — also served as `resource` in protected-resource metadata
333
+ publicUrl: AGENT_URL,
282
334
  authenticate: verifyBearer({
283
335
  jwksUri: 'https://auth.example.com/.well-known/jwks.json',
284
336
  issuer: 'https://auth.example.com',
@@ -286,16 +338,9 @@ serve(createAgent, {
286
338
  }),
287
339
  protectedResource: { authorization_servers: ['https://auth.example.com'] },
288
340
  });
289
-
290
- // Both
291
- serve(createAgent, {
292
- publicUrl: AGENT_URL,
293
- authenticate: anyOf(verifyApiKey({ verify: lookupKey }), verifyBearer({ jwksUri, issuer, audience: AGENT_URL })),
294
- protectedResource: { authorization_servers: [issuer] },
295
- });
296
341
  ```
297
342
 
298
- The framework produces RFC 6750-compliant `WWW-Authenticate: Bearer` 401s on failure, and serves `/.well-known/oauth-protected-resource<mountPath>` with `publicUrl` as the `resource` field so buyers get tokens bound to the right audience. The default JWT allowlist is asymmetric-only (RS*/ES*/PS\*/EdDSA) to prevent algorithm-confusion attacks.
343
+ The framework produces RFC 6750-compliant `WWW-Authenticate: Bearer` 401s on failure, and serves `/.well-known/oauth-protected-resource<mountPath>` with `publicUrl` as the `resource` field. Default JWT allowlist is asymmetric-only (RS\*/ES\*/PS\*/EdDSA) to prevent algorithm-confusion attacks.
299
344
 
300
345
  ## Validate Locally
301
346
 
@@ -305,7 +350,7 @@ The framework produces RFC 6750-compliant `WWW-Authenticate: Bearer` 401s on fai
305
350
  # Boot
306
351
  npx tsx agent.ts &
307
352
 
308
- # Happy path — session lifecycle
353
+ # Happy path — full session lifecycle (3 phases, all 4 SI tools)
309
354
  npx @adcp/sdk@latest storyboard run http://localhost:3001/mcp si_baseline --auth $TOKEN
310
355
 
311
356
  # Cross-cutting obligations
@@ -314,42 +359,52 @@ npx @adcp/sdk@latest storyboard run http://localhost:3001/mcp \
314
359
 
315
360
  # Rejection-surface fuzz
316
361
  npx @adcp/sdk@latest fuzz http://localhost:3001/mcp \
317
- --tools si_get_offering --auth-token $TOKEN
362
+ --tools si_get_offering,si_initiate_session --auth-token $TOKEN
318
363
  ```
319
364
 
365
+ **Reference target**: `npx @adcp/sdk@latest mock-server sponsored-intelligence` boots a brand-agent fixture you can wrap end-to-end. The reference adapter at [`examples/hello_si_adapter_brand.ts`](../../examples/hello_si_adapter_brand.ts) reports `3/3 scenarios pass` against `si_baseline`.
366
+
320
367
  Common failure decoder:
321
368
 
322
- - `status` field on session response → rename to `session_status` (the canonical field name)
323
- - `status: 'terminated'` → use boolean `terminated: true`
324
- - Missing `session_id` on `si_send_message` response → echo from request required
325
- - Missing `available` boolean on `si_get_offering` → required even for mock data
326
- - Missing `reason` on `si_terminate_session` request enum: `user_exit` / `session_timeout` / `host_terminated` / `handoff_transaction` / `handoff_complete`
369
+ - `status` field on session response → rename to `session_status` (canonical field name).
370
+ - `status: 'terminated'` on terminate response → use boolean `terminated: true`.
371
+ - Missing `session_id` on `si_send_message` response → echo from request, required.
372
+ - Missing `available` boolean on `si_get_offering` → required even for mock data.
373
+ - `reason` outside the closed enum on `si_terminate_session` → must be `user_exit | session_timeout | host_terminated | handoff_transaction | handoff_complete`.
374
+ - `product_card.data` missing `title` or `price` → schema requires both; project per-`type` from your upstream component vocabulary.
327
375
 
328
376
  **Keep iterating until all steps pass.** Can't bind ports? `npm run compliance:skill-matrix -- --filter si` runs an isolated end-to-end test.
329
377
 
330
378
  ## Common Mistakes
331
379
 
332
- | Mistake | Fix |
333
- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
334
- | Manually registering `get_adcp_capabilities` | Framework auto-generates it from registered handlers — do not register it yourself |
335
- | Using `server.tool()` instead of domain groups | Use `sponsoredIntelligence: { getOffering, initiateSession, ... }` framework wires schemas and response builders |
336
- | Using in-memory Maps for session state | Use `ctx.store.put/get/delete` built-in state persistence |
337
- | Returns `status` instead of `session_status` | Field name is `session_status` `status` will fail schema validation |
338
- | Returns `status: 'terminated'` instead of `terminated: true` | Termination response uses boolean `terminated` field |
339
- | Missing `session_id` in si_send_message response | Echo `session_id` back from request required |
340
- | Missing `available` in si_get_offering | Boolean `available` is requiredeven for mock data |
341
- | Missing `reason` in si_terminate_session request | `reason` is required one of: `user_exit`, `session_timeout`, `host_terminated`, `handoff_transaction`, `handoff_complete` |
342
- | Dropping `context` from responses | Let the framework echo except for `si_get_offering` / `si_initiate_session`, whose request `context` is a string. For those, build your own response context object if correlation tracking matters. |
380
+ | Mistake | Fix |
381
+ | ------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
382
+ | Manually registering `get_adcp_capabilities` | Framework auto-generates from registered handlers — do not register it yourself. |
383
+ | Declaring `specialisms: ['sponsored-intelligence']` | Not yet in `AdCPSpecialism` (adcp#3961). Use `specialisms: [] as const` and let the platform field's presence drive `supported_protocols`. When 3.1 lands, declaring the specialism becomes additive — both forms work. |
384
+ | Routing through the v5 `createAdcpServer` handler-bag | The v6 path lands in 6.7. Use `createAdcpServerFromPlatform` + `defineSponsoredIntelligencePlatform`. v5 still works for in-flight migrations but lacks auto-hydrated `req.session`. |
385
+ | Modeling full transcripts in `ctx_metadata` | The auto-hydrated `req.session` is for the small lookup-the-original-scope case. Production keeps full transcripts (RAG embeddings, tool-call logs) in your own Postgres / Redis / vector store keyed by `req.session_id`. The 16KB blob cap will bite if you try to use ctx_metadata. |
386
+ | Returns `status` instead of `session_status` | Field name is `session_status` `status` will fail schema validation. |
387
+ | Returns `status: 'terminated'` instead of `terminated: true` | Termination response uses boolean `terminated`. |
388
+ | Missing `session_id` in `si_send_message` response | Echo `session_id` back from request required. |
389
+ | Missing `available` in `si_get_offering` | Boolean `available` is required at the top level even for mock data. |
390
+ | `product_card` missing `title` + `price` | AdCP `SIUIElement.product_card` requires `data.title` + `data.price`. Upstream brand-platform vocabulary often uses `name` + `display_price` project per-`type` (the example does this in `projectComponent`). Same for `action_button` (`label` + `action`). |
391
+ | Hand-set `context` on response | Let the framework echo the protocol context object. Don't set a string or your own object — only the protocol-shape `context` is auto-echoed; mismatched shapes are dropped. |
343
392
 
344
393
  ## Storyboards
345
394
 
346
- | Storyboard | Tests |
347
- | ------------ | ----------------------------------------------------------------- |
348
- | `si_session` | Full session lifecycle: offeringinitiatemessage terminate |
395
+ | Storyboard | Tests |
396
+ | ------------- | -------------------------------------------------------------------------------------- |
397
+ | `si_baseline` | Full session lifecycle: capability discovery offering discovery session lifecycle. |
349
398
 
350
399
  ## Reference
351
400
 
352
- - `storyboards/si_session.yaml` — full SI session storyboard
353
- - `docs/guides/BUILD-AN-AGENT.md` — SDK patterns
354
- - `docs/TYPE-SUMMARY.md` — curated type signatures
355
- - `docs/llms.txt` — full protocol reference
401
+ - [`examples/hello_si_adapter_brand.ts`](../../examples/hello_si_adapter_brand.ts)worked SI adapter wrapping the SI mock server.
402
+ - `compliance/cache/latest/protocols/sponsored-intelligence/index.yaml` — `si_baseline` storyboard spec.
403
+ - `docs/guides/BUILD-AN-AGENT.md` — SDK patterns.
404
+ - `docs/TYPE-SUMMARY.md` — curated type signatures.
405
+ - `docs/llms.txt` — full protocol reference (search `#### \`si_initiate_session\`` etc. for tool-specific contracts).
406
+
407
+ ## Tracking
408
+
409
+ - adcontextprotocol/adcp#3961 — SI in `AdCPSpecialism` for 3.1. Once landed, this skill's `specialisms: []` becomes `specialisms: ['sponsored-intelligence'] as const`.
410
+ - adcontextprotocol/adcp#3981 — `si_baseline` storyboard `context_outputs` capture-path bug (top-level `offering_id` mirror in the example is a workaround until this lands).