experimental-ash 0.2.0-alpha.21 → 0.2.0-alpha.23

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 (199) hide show
  1. package/dist/src/channel/slack-channel.d.ts +14 -7
  2. package/dist/src/channel/slack-channel.d.ts.map +1 -1
  3. package/dist/src/channel/slack-channel.js +21 -11
  4. package/dist/src/channel/slack-channel.js.map +1 -1
  5. package/dist/src/channel/types.d.ts +14 -14
  6. package/dist/src/channel/types.d.ts.map +1 -1
  7. package/dist/src/channel/types.js +6 -0
  8. package/dist/src/channel/types.js.map +1 -1
  9. package/dist/src/context/accessors.d.ts +24 -10
  10. package/dist/src/context/accessors.d.ts.map +1 -1
  11. package/dist/src/context/accessors.js +34 -16
  12. package/dist/src/context/accessors.js.map +1 -1
  13. package/dist/src/context/container.d.ts +43 -20
  14. package/dist/src/context/container.d.ts.map +1 -1
  15. package/dist/src/context/container.js +54 -22
  16. package/dist/src/context/container.js.map +1 -1
  17. package/dist/src/context/key.d.ts +68 -39
  18. package/dist/src/context/key.d.ts.map +1 -1
  19. package/dist/src/context/key.js +49 -62
  20. package/dist/src/context/key.js.map +1 -1
  21. package/dist/src/context/keys.d.ts +6 -6
  22. package/dist/src/context/keys.d.ts.map +1 -1
  23. package/dist/src/context/keys.js +7 -9
  24. package/dist/src/context/keys.js.map +1 -1
  25. package/dist/src/context/node.d.ts +2 -2
  26. package/dist/src/context/node.d.ts.map +1 -1
  27. package/dist/src/context/node.js +1 -1
  28. package/dist/src/context/node.js.map +1 -1
  29. package/dist/src/context/provider.d.ts +24 -11
  30. package/dist/src/context/provider.d.ts.map +1 -1
  31. package/dist/src/context/providers/connection.d.ts +2 -2
  32. package/dist/src/context/providers/connection.d.ts.map +1 -1
  33. package/dist/src/context/providers/connection.js +1 -1
  34. package/dist/src/context/providers/connection.js.map +1 -1
  35. package/dist/src/context/providers/sandbox.d.ts +2 -2
  36. package/dist/src/context/providers/sandbox.d.ts.map +1 -1
  37. package/dist/src/context/providers/sandbox.js +2 -2
  38. package/dist/src/context/providers/sandbox.js.map +1 -1
  39. package/dist/src/context/providers/session.d.ts +2 -2
  40. package/dist/src/context/providers/session.d.ts.map +1 -1
  41. package/dist/src/context/providers/session.js +4 -4
  42. package/dist/src/context/providers/session.js.map +1 -1
  43. package/dist/src/context/providers/skill.d.ts +2 -2
  44. package/dist/src/context/providers/skill.d.ts.map +1 -1
  45. package/dist/src/context/providers/skill.js +2 -2
  46. package/dist/src/context/providers/skill.js.map +1 -1
  47. package/dist/src/context/run-step.d.ts +9 -8
  48. package/dist/src/context/run-step.d.ts.map +1 -1
  49. package/dist/src/context/run-step.js +32 -22
  50. package/dist/src/context/run-step.js.map +1 -1
  51. package/dist/src/context/seed-keys.d.ts +7 -7
  52. package/dist/src/context/seed-keys.d.ts.map +1 -1
  53. package/dist/src/context/seed-keys.js +7 -19
  54. package/dist/src/context/seed-keys.js.map +1 -1
  55. package/dist/src/context/serialize.d.ts +10 -9
  56. package/dist/src/context/serialize.d.ts.map +1 -1
  57. package/dist/src/context/serialize.js +18 -30
  58. package/dist/src/context/serialize.js.map +1 -1
  59. package/dist/src/execution/continuous-entry.d.ts +8 -6
  60. package/dist/src/execution/continuous-entry.d.ts.map +1 -1
  61. package/dist/src/execution/continuous-entry.js +27 -25
  62. package/dist/src/execution/continuous-entry.js.map +1 -1
  63. package/dist/src/execution/continuous-runtime.d.ts.map +1 -1
  64. package/dist/src/execution/continuous-runtime.js +1 -4
  65. package/dist/src/execution/continuous-runtime.js.map +1 -1
  66. package/dist/src/execution/node-step.js +1 -0
  67. package/dist/src/execution/node-step.js.map +1 -1
  68. package/dist/src/execution/runtime-context.d.ts +3 -3
  69. package/dist/src/execution/runtime-context.d.ts.map +1 -1
  70. package/dist/src/execution/runtime-context.js +3 -3
  71. package/dist/src/execution/runtime-context.js.map +1 -1
  72. package/dist/src/execution/sandboxes/read-file-tool.js +2 -2
  73. package/dist/src/execution/sandboxes/read-file-tool.js.map +1 -1
  74. package/dist/src/execution/sandboxes/require-sandbox.js +2 -2
  75. package/dist/src/execution/sandboxes/require-sandbox.js.map +1 -1
  76. package/dist/src/execution/sandboxes/write-file-tool.d.ts.map +1 -1
  77. package/dist/src/execution/sandboxes/write-file-tool.js +3 -3
  78. package/dist/src/execution/sandboxes/write-file-tool.js.map +1 -1
  79. package/dist/src/execution/subagent-tool.js +6 -6
  80. package/dist/src/execution/subagent-tool.js.map +1 -1
  81. package/dist/src/execution/tool-compaction.d.ts +3 -1
  82. package/dist/src/execution/tool-compaction.d.ts.map +1 -1
  83. package/dist/src/execution/tool-compaction.js +19 -8
  84. package/dist/src/execution/tool-compaction.js.map +1 -1
  85. package/dist/src/execution/types.d.ts +1 -0
  86. package/dist/src/execution/types.d.ts.map +1 -1
  87. package/dist/src/execution/workflow-entry.d.ts +5 -4
  88. package/dist/src/execution/workflow-entry.d.ts.map +1 -1
  89. package/dist/src/execution/workflow-entry.js +8 -3
  90. package/dist/src/execution/workflow-entry.js.map +1 -1
  91. package/dist/src/execution/workflow-runtime.d.ts.map +1 -1
  92. package/dist/src/execution/workflow-runtime.js +2 -3
  93. package/dist/src/execution/workflow-runtime.js.map +1 -1
  94. package/dist/src/execution/workflow-steps.d.ts +2 -2
  95. package/dist/src/execution/workflow-steps.d.ts.map +1 -1
  96. package/dist/src/execution/workflow-steps.js +42 -17
  97. package/dist/src/execution/workflow-steps.js.map +1 -1
  98. package/dist/src/harness/emission.d.ts +1 -1
  99. package/dist/src/harness/emission.js +3 -3
  100. package/dist/src/harness/emission.js.map +1 -1
  101. package/dist/src/harness/execute-tool.d.ts +1 -0
  102. package/dist/src/harness/execute-tool.d.ts.map +1 -1
  103. package/dist/src/harness/execute-tool.js.map +1 -1
  104. package/dist/src/harness/input-requests.d.ts +2 -22
  105. package/dist/src/harness/input-requests.d.ts.map +1 -1
  106. package/dist/src/harness/input-requests.js +47 -41
  107. package/dist/src/harness/input-requests.js.map +1 -1
  108. package/dist/src/harness/tool-loop.d.ts.map +1 -1
  109. package/dist/src/harness/tool-loop.js +19 -1
  110. package/dist/src/harness/tool-loop.js.map +1 -1
  111. package/dist/src/harness/tools.js +5 -1
  112. package/dist/src/harness/tools.js.map +1 -1
  113. package/dist/src/harness/types.d.ts +3 -15
  114. package/dist/src/harness/types.d.ts.map +1 -1
  115. package/dist/src/harness/types.js +1 -6
  116. package/dist/src/harness/types.js.map +1 -1
  117. package/dist/src/internal/application/package.js +1 -1
  118. package/dist/src/internal/authored-definition/connection.d.ts.map +1 -1
  119. package/dist/src/internal/authored-definition/connection.js +14 -1
  120. package/dist/src/internal/authored-definition/connection.js.map +1 -1
  121. package/dist/src/internal/nitro/routes/runtime-stack.d.ts +3 -3
  122. package/dist/src/internal/nitro/routes/runtime-stack.js +3 -3
  123. package/dist/src/public/channels/slack/index.d.ts +0 -5
  124. package/dist/src/public/channels/slack/index.d.ts.map +1 -1
  125. package/dist/src/public/channels/slack/index.js.map +1 -1
  126. package/dist/src/public/context/index.d.ts +17 -0
  127. package/dist/src/public/context/index.d.ts.map +1 -0
  128. package/dist/src/public/context/index.js +15 -0
  129. package/dist/src/public/context/index.js.map +1 -0
  130. package/dist/src/public/definitions/connections/mcp.d.ts +13 -0
  131. package/dist/src/public/definitions/connections/mcp.d.ts.map +1 -1
  132. package/dist/src/public/definitions/connections/mcp.js.map +1 -1
  133. package/dist/src/public/definitions/tool.d.ts +10 -6
  134. package/dist/src/public/definitions/tool.d.ts.map +1 -1
  135. package/dist/src/public/definitions/tool.js.map +1 -1
  136. package/dist/src/public/index.d.ts +1 -6
  137. package/dist/src/public/index.d.ts.map +1 -1
  138. package/dist/src/public/index.js +0 -3
  139. package/dist/src/public/index.js.map +1 -1
  140. package/dist/src/public/sandboxes/index.d.ts +1 -0
  141. package/dist/src/public/sandboxes/index.d.ts.map +1 -1
  142. package/dist/src/public/sandboxes/index.js +1 -0
  143. package/dist/src/public/sandboxes/index.js.map +1 -1
  144. package/dist/src/public/skills/index.d.ts +24 -0
  145. package/dist/src/public/skills/index.d.ts.map +1 -0
  146. package/dist/src/public/skills/index.js +23 -0
  147. package/dist/src/public/skills/index.js.map +1 -0
  148. package/dist/src/public/tools/defaults.d.ts +4 -4
  149. package/dist/src/public/tools/defaults.js +4 -4
  150. package/dist/src/runtime/connections/registry.d.ts +6 -0
  151. package/dist/src/runtime/connections/registry.d.ts.map +1 -1
  152. package/dist/src/runtime/connections/registry.js +8 -0
  153. package/dist/src/runtime/connections/registry.js.map +1 -1
  154. package/dist/src/runtime/connections/types.d.ts +2 -0
  155. package/dist/src/runtime/connections/types.d.ts.map +1 -1
  156. package/dist/src/runtime/framework-tools/connection-execute.d.ts.map +1 -1
  157. package/dist/src/runtime/framework-tools/connection-execute.js +17 -6
  158. package/dist/src/runtime/framework-tools/connection-execute.js.map +1 -1
  159. package/dist/src/runtime/framework-tools/connection-search.d.ts +2 -2
  160. package/dist/src/runtime/framework-tools/connection-search.d.ts.map +1 -1
  161. package/dist/src/runtime/framework-tools/connection-search.js +4 -4
  162. package/dist/src/runtime/framework-tools/connection-search.js.map +1 -1
  163. package/dist/src/runtime/framework-tools/file-state.d.ts +5 -7
  164. package/dist/src/runtime/framework-tools/file-state.d.ts.map +1 -1
  165. package/dist/src/runtime/framework-tools/file-state.js +4 -6
  166. package/dist/src/runtime/framework-tools/file-state.js.map +1 -1
  167. package/dist/src/runtime/framework-tools/skill.js +3 -3
  168. package/dist/src/runtime/framework-tools/skill.js.map +1 -1
  169. package/dist/src/runtime/framework-tools/todo.js +3 -3
  170. package/dist/src/runtime/framework-tools/todo.js.map +1 -1
  171. package/dist/src/runtime/prompt/connections.d.ts.map +1 -1
  172. package/dist/src/runtime/prompt/connections.js +3 -2
  173. package/dist/src/runtime/prompt/connections.js.map +1 -1
  174. package/dist/src/runtime/resolve-agent-graph.js +3 -2
  175. package/dist/src/runtime/resolve-agent-graph.js.map +1 -1
  176. package/dist/src/runtime/resolve-connection.d.ts.map +1 -1
  177. package/dist/src/runtime/resolve-connection.js +3 -0
  178. package/dist/src/runtime/resolve-connection.js.map +1 -1
  179. package/dist/src/runtime/sessions/auth.d.ts +1 -1
  180. package/dist/src/runtime/types.d.ts +13 -0
  181. package/dist/src/runtime/types.d.ts.map +1 -1
  182. package/docs/internals/context.md +257 -81
  183. package/docs/public/README.md +17 -19
  184. package/docs/public/channels/README.md +9 -3
  185. package/docs/public/sandboxes.md +1 -1
  186. package/docs/public/session-context.md +97 -51
  187. package/docs/public/skills.md +2 -2
  188. package/docs/public/tools.md +26 -18
  189. package/docs/public/typescript-api.md +34 -29
  190. package/package.json +11 -1
  191. package/dist/src/context/durable-context.d.ts +0 -18
  192. package/dist/src/context/durable-context.d.ts.map +0 -1
  193. package/dist/src/context/durable-context.js +0 -49
  194. package/dist/src/context/durable-context.js.map +0 -1
  195. package/dist/src/execution/step-context.d.ts +0 -27
  196. package/dist/src/execution/step-context.d.ts.map +0 -1
  197. package/dist/src/execution/step-context.js +0 -54
  198. package/dist/src/execution/step-context.js.map +0 -1
  199. package/docs/public/migration-guide.md +0 -71
@@ -1,126 +1,302 @@
1
1
  # Unified Context
2
2
 
3
- Ash uses one `AshContext` container, bound by one `AsyncLocalStorage`, to carry all ambient runtime
4
- data through execution. There is no second authored-state channel and no separate dehydration path
5
- for public context.
3
+ Ash uses a single `AshContext` container, bound by one `AsyncLocalStorage` instance, to carry all
4
+ runtime state through the execution stack. There are no secondary `AsyncLocalStorage` bindings, no
5
+ custom dehydration protocols, and no out-of-band parameter passing for contextual data.
6
6
 
7
- ## Core Model
7
+ ## Files To Read
8
8
 
9
- Ash now has two key categories:
9
+ - `packages/ash/src/context/key.ts`
10
+ - `packages/ash/src/context/container.ts`
11
+ - `packages/ash/src/context/provider.ts`
12
+ - `packages/ash/src/context/keys.ts`
13
+ - `packages/ash/src/context/serialize.ts`
14
+ - `packages/ash/src/context/run-step.ts`
15
+ - `packages/ash/src/context/accessors.ts`
16
+ - `packages/ash/src/context/providers/session.ts`
17
+ - `packages/ash/src/context/providers/sandbox.ts`
18
+ - `packages/ash/src/context/providers/skill.ts`
10
19
 
11
- - `ContextKey<T>`: public durable authored context. Values live on `session.context`.
12
- - `RuntimeContextKey<T>`: private framework-only runtime values. These seed one step and may be
13
- reconstructed on the next step.
20
+ ## Core Primitives
14
21
 
15
- `AshContext` exposes a uniform API:
22
+ ### ContextKey
23
+
24
+ A typed key identifies a named context slot. Durable values written through `set()` are
25
+ serialized at `"use step"` boundaries. Virtual provider output may also be read through the same
26
+ key during a step.
27
+
28
+ ```ts
29
+ class ContextKey<T> {
30
+ readonly name: string;
31
+ readonly codec?: {
32
+ serialize(value: T): unknown;
33
+ deserialize(data: unknown): T | Promise<T>;
34
+ };
35
+ }
36
+ ```
37
+
38
+ ### Type Hierarchy
39
+
40
+ The context type system is a single inheritance chain defined in `key.ts`:
41
+
42
+ ```
43
+ ContextReader { get, require, has }
44
+ ↑ extends
45
+ ContextAccessor { set, ensure }
46
+ ↑ extends
47
+ AshContext { entries() }
48
+ ```
49
+
50
+ - `ContextReader` — read-only view for providers and codec deserialization
51
+ - `ContextAccessor` — read/write view for channels (`onDeliver`) and authored code
52
+ - `AshContext` — full container with the `entries()` iterator used by the serialization layer
53
+
54
+ ### AshContext / ContextContainer
55
+
56
+ The container interface. One instance per execution scope. Reads see the virtual provider overlay
57
+ first, then the durable map. Writes always target the durable map.
16
58
 
17
59
  ```ts
18
60
  interface AshContext {
19
- get<T>(key: ContextStoreKey<T>): T;
20
- tryGet<T>(key: ContextStoreKey<T>): T | undefined;
21
- has<T>(key: ContextStoreKey<T>): boolean;
22
- set<T>(key: ContextStoreKey<T>, value: T): void;
23
- ensure<T>(key: ContextStoreKey<T>, valueOrFactory: T | (() => T)): T;
61
+ get<T>(key: ContextKey<T>): T | undefined;
62
+ require<T>(key: ContextKey<T>): T;
63
+ has<T>(key: ContextKey<T>): boolean;
64
+ set<T>(key: ContextKey<T>, value: T): T;
65
+ set<T>(key: ContextKey<T>, updater: (current: T | undefined) => T): T;
66
+ ensure<T>(key: ContextKey<T>, create: () => T): T;
24
67
  }
25
68
  ```
26
69
 
27
- Public helpers such as `getContext`, `setContext`, `ensureContext`, `getSession`, `getSandbox`, and
28
- `getSkill` all delegate to this container.
70
+ The concrete implementation class is `ContextContainer` (in `container.ts`).
29
71
 
30
- ## Durable Context Lifecycle
72
+ ### contextStorage
31
73
 
32
- Durable authored context no longer piggybacks on runtime seed serialization.
74
+ The single `AsyncLocalStorage<AshContext>` instance. Only `runStep` and the runtime entry points
75
+ call `contextStorage.run(...)`. Everything else reads via `loadContext()`.
33
76
 
34
- Every step now follows this flow:
77
+ ### loadContext()
35
78
 
36
- 1. Deserialize runtime seed keys with `deserializeRuntimeContext(...)`.
37
- 2. Hydrate all registered public `ContextKey`s from `session.context`.
38
- 3. Seed internal framework bookkeeping from `session.internal`.
39
- 4. Apply deliver-time auth and run `channel.onDeliver(...)`.
40
- 5. Run providers and step code inside `contextStorage.run(...)`.
41
- 6. Commit the full durable context bag back to `session.context`.
79
+ Returns the active `AshContext` from `contextStorage`, or throws. Authored code (tools, steps,
80
+ model callbacks) uses this implicitly through the public accessors.
42
81
 
43
- The main files are:
82
+ ## Key Categories
44
83
 
45
- - `packages/ash/src/context/serialize.ts`
46
- - `packages/ash/src/context/durable-context.ts`
47
- - `packages/ash/src/execution/step-context.ts`
48
- - `packages/ash/src/context/run-step.ts`
84
+ ### Seed keys
85
+
86
+ Set by the runtime entry point with live values. Serialized and deserialized at durable step
87
+ boundaries via their codec.
49
88
 
50
- ## Runtime Seed Serialization
89
+ | Key | Type | Codec |
90
+ |-----|------|-------|
91
+ | `AuthKey` | `SessionAuthContext \| null` | none (JSON-safe) |
92
+ | `InitiatorAuthKey` | `SessionAuthContext \| null` | none |
93
+ | `SessionIdKey` | `string` | none |
94
+ | `RunIdKey` | `string` | none |
95
+ | `ContinuationTokenKey` | `string` | none |
96
+ | `ModeKey` | `RunMode` | none |
97
+ | `NodeSelectorKey` | `string` | none — points at the active graph node (root or a delegated subagent selector) |
98
+ | `ParentSessionKey` | `SessionParent` | none — set only on delegated child contexts to carry parent lineage |
99
+ | `ChannelKey` | `Channel` | serializes kind + state, deserializes by hydrating the correct channel class |
100
+ | `BundleKey` | `CompiledBundle` | serializes to `compiledArtifactsSource`, deserializes via `getCompiledRuntimeAgentBundle` |
51
101
 
52
- `serializeRuntimeContext(...)` and `deserializeRuntimeContext(...)` only handle
53
- `RuntimeContextKey`s marked `serializable: true`.
102
+ Keys self-register in a global registry at construction time. The serialization
103
+ layer uses this registry to resolve string names back to typed keys — there is no
104
+ explicit seed key list.
54
105
 
55
- That includes seed values such as:
106
+ ### Virtual keys
56
107
 
57
- - auth and initiator auth
58
- - session id and continuation token
59
- - run mode
60
- - parent lineage
61
- - compiled bundle
62
- - serialized channel
108
+ Created by providers during `runStep`. Never serialized — providers reconstruct them each step
109
+ from durable context and the harness session.
63
110
 
64
- Durable authored context is intentionally excluded. `assertNoDurableContextInRuntimeSeed(...)`
65
- guards against leaking a public `ContextKey` into the runtime seed payload.
111
+ | Key | Type | Provider |
112
+ |-----|------|----------|
113
+ | `SessionKey` | `Session` | `sessionProvider` |
114
+ | `SandboxKey` | `SandboxAccess` | `sandboxProvider` |
115
+ | `SkillKey` | `SkillAccess` | `skillProvider` |
66
116
 
67
117
  ## Providers
68
118
 
69
- Providers still reconstruct derived runtime values on each step:
119
+ Public channel-scoped providers implement `ContextProvider<T>`:
120
+
121
+ ```ts
122
+ interface ContextProvider<T> {
123
+ readonly key: ContextKey<T>;
124
+ create(ctx: ContextReader): T | undefined | Promise<T | undefined>;
125
+ }
126
+ ```
127
+
128
+ Public providers are derive-only. They run after `onDeliver()` and after the framework providers,
129
+ so they can rebuild live step-local values from the visible context. Returning `undefined` means
130
+ the provider is not active for this step.
131
+
132
+ Framework internals use a superset contract (`FrameworkContextProvider`) that also receives the
133
+ current harness session and may commit provider-owned session data after the step.
134
+
135
+ Provider ordering matters. The framework providers are baked into `runStep` in dependency order:
136
+
137
+ 1. `sessionProvider` — depends only on durable seed keys
138
+ 2. `sandboxProvider` — depends on `BundleKey` and `SessionIdKey`
139
+ 3. `skillProvider` — depends on `BundleKey`
140
+ 4. channel-declared public providers — depend on whatever context is visible after `onDeliver()`
141
+
142
+ There is no separate provider list to import — `runStep` knows its providers internally.
143
+
144
+ ## Step Runner
145
+
146
+ `runStep` orchestrates the provider lifecycle around a step callback:
147
+
148
+ 1. Clears the previous step's virtual overlay.
149
+ 2. Builds framework virtual providers in order.
150
+ 3. Builds any channel-scoped public providers declared on the active channel class.
151
+ 4. Runs the callback inside `contextStorage.run(ctx, ...)` so authored code can read the context.
152
+ 5. After the callback completes, calls framework provider `commit` hooks for provider-owned
153
+ session state.
154
+
155
+ ## Serialization At Step Boundaries
156
+
157
+ `serializeContext` and `deserializeContext` handle durable step boundaries generically.
158
+ `serializeContext` iterates all entries in the context, calling each codec-backed key's codec
159
+ when present. `deserializeContext` iterates the plain JSON record
160
+ and resolves each string name back to its registered `ContextKey` via the global key registry.
161
+ Keys without a codec are stored as-is (they must be JSON-safe).
162
+
163
+ The workflow runtime serializes the durable context once at `start()` time, then threads the latest
164
+ serialized context forward after every durable step. Each `"use step"` boundary deserializes the
165
+ durable map back, `onDeliver()` mutates it for the turn, and providers reconstruct virtual values.
166
+
167
+ The channel codec is owned by the `ChannelKey` definition in `context/keys.ts`.
168
+ Channel classes themselves have no knowledge of serialization.
169
+
170
+ ## Channel Context
171
+
172
+ Auth is separate from the channel — it lives on `RunInput.auth` and `DeliverInput.auth`. There is
173
+ no `ContextUpdater` callback, no `withAuth`/`withContext` wrappers, and no deliver-updater-registry
174
+ side-channel.
175
+
176
+ ### Auth
177
+
178
+ Auth lives on the run and deliver inputs, not on the channel itself:
179
+
180
+ - `RunInput.auth` — the caller for the current run
181
+ - `RunInput.initiatorAuth` — the caller that started the durable session (defaults to `auth` on root
182
+ runs; subagent passes the parent's initiator)
183
+
184
+ The runtime reads `RunInput.auth` and `RunInput.initiatorAuth` when seeding `AuthKey` and
185
+ `InitiatorAuthKey`. On `deliver()`, the runtime updates `AuthKey` from `DeliverInput.auth` so that
186
+ `session.auth.current` reflects the follow-up caller while `session.auth.initiator` stays the same.
187
+
188
+ ### Custom context via `Channel.onDeliver`
189
+
190
+ Channels set custom durable context keys inside `onDeliver(ctx, payload)`. The method receives a
191
+ narrow `ContextAccessor` as its first argument (typed `get` / `require` / `has` /
192
+ `set` / `ensure` with `ContextKey`) and returns a
193
+ `Promise<StepInput>`. This runs once per turn — both the initial `run()` and each `deliver()` —
194
+ after auth keys are seeded. Channel code stays workflow-agnostic because it only touches the
195
+ accessor.
196
+
197
+ ```ts
198
+ class TenantChannel extends Channel {
199
+ static readonly kind = "tenant-channel";
200
+
201
+ private readonly inner: Channel;
202
+ private readonly tenantId: string;
203
+
204
+ constructor(state: { readonly tenantId: string }) {
205
+ super();
206
+ this.tenantId = state.tenantId;
207
+ this.inner = new HttpChannel();
208
+ }
209
+
210
+ async onEvent(event) {
211
+ return await this.inner.onEvent(event);
212
+ }
213
+
214
+ async onDeliver(ctx, payload) {
215
+ ctx.set(TenantKey, this.tenantId);
216
+ return await this.inner.onDeliver(ctx, payload);
217
+ }
218
+
219
+ serialize() {
220
+ return { tenantId: this.tenantId };
221
+ }
222
+ }
223
+ ```
224
+
225
+ ### Deliver path
226
+
227
+ On `deliver()`, the channel is deserialized from the session (it was serialized at `run()` time via
228
+ the `ChannelKey` codec). The runtime applies `DeliverInput.auth` to update the auth key, then calls
229
+ `channel.onDeliver(ctx, payload)` on the deserialized channel. No process-local user registration or
230
+ function stashing is needed because the runtime rebuilds the channel from the compiled bundle's
231
+ channel registry plus the serialized channel state.
232
+
233
+ ### Files
70
234
 
71
- - `SessionKey` via `sessionProvider`
72
- - `SandboxKey` via `sandboxProvider`
73
- - `SkillKey` via `skillProvider`
235
+ - `packages/ash/src/channel/types.ts` `Channel`, `ContextAccessor` types
236
+ - `packages/ash/src/execution/runtime-context.ts` `buildRunContext` (reads `RunInput.auth`,
237
+ seeds auth keys)
238
+ - `packages/ash/src/execution/workflow-steps.ts` — deserializes channel, applies deliver auth
239
+ - `packages/ash/src/execution/continuous-entry.ts` — applies deliver auth on in-memory channel
74
240
 
75
- These are runtime-only values, so they use `RuntimeContextKey`, not public `ContextKey`.
241
+ ## Integration Points
76
242
 
77
- Provider-owned mutable state stays separate from authored durable context:
243
+ ### Runtime entry (workflow)
78
244
 
79
- - durable authored values live on `session.context`
80
- - internal framework bookkeeping lives on `session.internal`
81
- - provider snapshots such as `sandboxState` stay on their dedicated session fields
245
+ `workflow-runtime.ts` builds a `ContextContainer` via `buildRunContext` (for `run()`) or
246
+ `createDelegatedChildContext` (for `delegate()`), sets seed keys, serializes, and passes
247
+ `serializedContext` to `workflowEntry` via `start()`. The ambient runtime is registered at
248
+ this point so durable steps can later recover it via `tryGetAmbientWorkflowRuntime()`.
82
249
 
83
- ## Channel Setup
250
+ ### Runtime entry (continuous)
84
251
 
85
- `packages/ash/src/execution/step-context.ts` owns the pre-step channel path for both runtimes:
252
+ `continuous-runtime.ts` builds a `ContextContainer` via the same two helpers and passes the
253
+ live context directly to `runStep` (no serialization needed since there are no durable step
254
+ boundaries). `RuntimeKey` is attached to the context once at run creation.
86
255
 
87
- 1. rebuild the fresh step context
88
- 2. hydrate durable context
89
- 3. seed pending input request bookkeeping
90
- 4. apply deliver-time auth
91
- 5. call `channel.onDeliver(ctx, payload)`
92
- 6. assert that serialized channel state did not change
256
+ ### Delegation
93
257
 
94
- That ordering matters. `onDeliver(...)` runs after durable context hydration, so channel-seeded
95
- context cannot be overwritten by later hydration.
258
+ `runtime.delegate(input)` starts a child run rooted at a non-root graph node. Its
259
+ `DelegateInput` extends `RunInput` and adds `parent: SessionParent` and
260
+ `target: { selector }`. `createDelegatedChildContext` seeds the child context with the
261
+ forwarded initiator auth, the delegate's current auth, the parent lineage, the child node
262
+ selector, and the child's own run/session identifiers. The subagent tool wrapper in
263
+ `execution/subagent-tool.ts` is the sole in-tree caller of `delegate()`.
96
264
 
97
- ## Channel State Rule
265
+ ### Durable step boundary
98
266
 
99
- Serialized channel state is immutable after the run starts.
267
+ `workflow-steps.ts` deserializes the context from the serialized record, attaches the ambient
268
+ workflow runtime via `RuntimeKey` (when one is registered), resolves the active graph node
269
+ from `NodeSelectorKey`, and calls `runStep` with the framework providers.
100
270
 
101
- The runtime snapshots `channel.serialize()` before `onDeliver(...)` and again after step execution.
102
- If the serialized shape changes, the runtime throws with guidance to move that data into:
271
+ ### Tool executors
103
272
 
104
- - durable context when authored code needs it across turns
105
- - internal `session.internal` when only framework bookkeeping needs it
273
+ Tool executors read from the container via `loadContext()`. Sandbox tools read `SandboxKey`;
274
+ the `load_skill` action reads `SkillKey`. Authored tools access context through the public
275
+ accessors (`getSession`, `getSandbox`, etc.).
106
276
 
107
- This keeps channel responsibilities narrow:
277
+ ### Public API
108
278
 
109
- - transport normalization
110
- - continuation token ownership
111
- - per-turn context seeding
112
- - delivery policy
279
+ The public accessors in `context/accessors.ts` delegate to `loadContext()`:
113
280
 
114
- It prevents channels from becoming an untracked second session storage system.
281
+ - `getContext(key)` returns `T | undefined`
282
+ - `requireContext(key)` returns `T` or throws
283
+ - `hasContext(key)` returns `boolean`
284
+ - `setContext(key, value | updater)` returns `T`
285
+ - `ensureContext(key, factory)` returns `T`
115
286
 
116
- ## Runtime Split
287
+ - `getSession()` reads `SessionKey`
288
+ - `getSandbox(name)` reads `SandboxKey`
289
+ - `getSkill(identifier)` reads `SkillKey`
117
290
 
118
- Both runtime flavors now use the same context story:
291
+ ## What Was Removed
119
292
 
120
- - workflow runtime stores runtime seed keys once, then rebuilds a fresh step context on every
121
- durable step
122
- - continuous runtime also rebuilds a fresh step context on every step, even though the process is
123
- still live
293
+ The unified context replaced these six prior mechanisms:
124
294
 
125
- That removes the old semantic split where in-memory runs behaved differently from durable workflow
126
- runs.
295
+ - `execution/runtime-context.ts` execution-layer `AsyncLocalStorage` and `RuntimeContext`
296
+ - `runtime/session-context.ts` — runtime-layer `AsyncLocalStorage` and `AuthoredRuntimeContext`
297
+ - `execution/framework-context.ts` — `FrameworkContext`, `Dehydratable`, channel emitter
298
+ dehydration
299
+ - `execution/context.ts` — `StepExecutionContext`, `ManagedRuntimeContext`, incremental context
300
+ assembly
301
+ - The `Dehydratable` interface on channel emitters (replaced by key codecs)
302
+ - Separate `session` parameter threading through tool executors and action handlers
@@ -19,22 +19,21 @@ Read in this order:
19
19
  2. [project-layout.md](./project-layout.md)
20
20
  3. [agent-ts.md](./agent-ts.md)
21
21
  4. [typescript-api.md](./typescript-api.md)
22
- 5. [migration-guide.md](./migration-guide.md)
23
- 6. [context-control.md](./context-control.md)
24
- 7. [skills.md](./skills.md)
25
- 8. [tools.md](./tools.md)
26
- 9. [workspace.md](./workspace.md)
27
- 10. [sandboxes.md](./sandboxes.md)
28
- 11. [channels/README.md](./channels/README.md)
29
- 12. [human-in-the-loop.md](./human-in-the-loop.md)
30
- 13. [session-context.md](./session-context.md)
31
- 14. [runs-and-streaming.md](./runs-and-streaming.md)
32
- 15. [subagents.md](./subagents.md)
33
- 16. [schedules.md](./schedules.md)
34
- 17. [evals.md](./evals.md)
35
- 18. [auth-and-route-protection.md](./auth-and-route-protection.md)
36
- 19. [vercel-deployment.md](./vercel-deployment.md)
37
- 20. [cli-build-and-debugging.md](./cli-build-and-debugging.md)
22
+ 5. [context-control.md](./context-control.md)
23
+ 6. [skills.md](./skills.md)
24
+ 7. [tools.md](./tools.md)
25
+ 8. [workspace.md](./workspace.md)
26
+ 9. [sandboxes.md](./sandboxes.md)
27
+ 10. [channels/README.md](./channels/README.md)
28
+ 11. [human-in-the-loop.md](./human-in-the-loop.md)
29
+ 12. [session-context.md](./session-context.md)
30
+ 13. [runs-and-streaming.md](./runs-and-streaming.md)
31
+ 14. [subagents.md](./subagents.md)
32
+ 15. [schedules.md](./schedules.md)
33
+ 16. [evals.md](./evals.md)
34
+ 17. [auth-and-route-protection.md](./auth-and-route-protection.md)
35
+ 18. [vercel-deployment.md](./vercel-deployment.md)
36
+ 19. [cli-build-and-debugging.md](./cli-build-and-debugging.md)
38
37
 
39
38
  ## The Public Mental Model
40
39
 
@@ -57,11 +56,10 @@ Ash then gives you:
57
56
  - a stable HTTP message route
58
57
  - optional channel webhook routes
59
58
  - a reconnectable session stream
60
- - durable session context across turns
59
+ - durable session state across turns
61
60
  - a shared runtime workspace
62
61
  - optional isolated sandboxes
63
- - typed runtime helpers such as `getSession()`, `getContext()`, `setContext()`, `ensureContext()`,
64
- `getSandbox()`, and `getSkill()`
62
+ - typed runtime helpers such as `getSession()`, `getSandbox()`, and `getSkill()`
65
63
 
66
64
  ## The Runtime Shape
67
65
 
@@ -9,7 +9,6 @@ Channels are the transport layer in Ash's channel-harness-runtime split. A chann
9
9
  - deriving or resuming the stable `continuationToken`
10
10
  - applying route auth and network policy
11
11
  - deciding how runtime events are delivered back to the platform
12
- - seeding per-turn durable context inside `onDeliver(...)`
13
12
 
14
13
  The runtime and harness still own the model turn, tool execution, compaction, and session
15
14
  persistence.
@@ -87,8 +86,15 @@ Use a custom channel when you want:
87
86
  - transport-specific delivery behavior
88
87
  - custom request parsing before the runtime turn starts
89
88
 
90
- Channel constructor state is for stable serialized transport identity only. Once a run starts, keep
91
- that state immutable and move mutable per-session data into durable context or `session.internal`.
89
+ When you extend `SlackChannel`, keep context flow in two stages:
90
+
91
+ - write durable serializable facts in `onDeliver()`
92
+ - declare live step-local values on `static readonly contextProviders = [...]`
93
+ - read those values from tools and other authored step code with the unified context helpers
94
+
95
+ `contextProviders` is discovered from the channel class, not the channel instance, and providers run
96
+ after `onDeliver()` on each step. See [`session-context.md`](../session-context.md) for the full
97
+ pattern.
92
98
 
93
99
  ## What To Read Next
94
100
 
@@ -160,7 +160,7 @@ You can also bind a live sandbox directly from any authored runtime function (to
160
160
  callback) via `getSandbox`:
161
161
 
162
162
  ```ts
163
- import { getSandbox } from "experimental-ash";
163
+ import { getSandbox } from "experimental-ash/sandboxes";
164
164
  import { defineTool } from "experimental-ash/tools";
165
165
  import { z } from "zod";
166
166