experimental-ash 0.56.0 → 0.58.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 (79) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/docs/internals/compiler-and-artifacts.md +1 -1
  3. package/dist/docs/internals/context.md +3 -3
  4. package/dist/docs/public/advanced/typescript-api.md +19 -0
  5. package/dist/docs/public/advanced/vercel-deployment.md +2 -1
  6. package/dist/docs/public/channels/github.md +145 -0
  7. package/dist/docs/public/channels/index.md +23 -2
  8. package/dist/docs/public/sandbox.md +18 -3
  9. package/dist/docs/public/subagents.mdx +56 -10
  10. package/dist/docs/public/tools.mdx +8 -2
  11. package/dist/src/cli/commands/channels.d.ts +3 -3
  12. package/dist/src/cli/commands/channels.js +1 -1
  13. package/dist/src/compiler/manifest.d.ts +8 -2
  14. package/dist/src/compiler/manifest.js +1 -1
  15. package/dist/src/compiler/normalize-sandbox.js +1 -1
  16. package/dist/src/context/providers/sandbox.js +1 -1
  17. package/dist/src/execution/delegated-parent-result.d.ts +1 -1
  18. package/dist/src/execution/next-driver-action.d.ts +1 -1
  19. package/dist/src/execution/node-step.js +1 -1
  20. package/dist/src/execution/remote-agent-dispatch.js +1 -1
  21. package/dist/src/execution/session-callback-step.d.ts +1 -1
  22. package/dist/src/execution/session.d.ts +4 -2
  23. package/dist/src/execution/session.js +3 -1
  24. package/dist/src/execution/subagent-tool.js +1 -1
  25. package/dist/src/execution/workflow-entry.d.ts +1 -1
  26. package/dist/src/execution/workflow-steps.d.ts +1 -1
  27. package/dist/src/execution/workflow-steps.js +1 -1
  28. package/dist/src/harness/code-mode.js +1 -1
  29. package/dist/src/harness/tool-loop.js +1 -1
  30. package/dist/src/harness/types.d.ts +1 -1
  31. package/dist/src/internal/application/package.js +1 -1
  32. package/dist/src/internal/authored-definition/sandbox.d.ts +2 -1
  33. package/dist/src/internal/authored-definition/sandbox.js +1 -1
  34. package/dist/src/packages/ash-scaffold/src/channels.js +1 -1
  35. package/dist/src/packages/ash-scaffold/src/steps/index.js +1 -1
  36. package/dist/src/packages/ash-scaffold/src/steps/project-resolution.js +1 -0
  37. package/dist/src/packages/ash-scaffold/src/steps/run-add-to-agent.js +2 -2
  38. package/dist/src/public/channels/github/api.d.ts +166 -0
  39. package/dist/src/public/channels/github/api.js +1 -0
  40. package/dist/src/public/channels/github/auth.d.ts +83 -0
  41. package/dist/src/public/channels/github/auth.js +2 -0
  42. package/dist/src/public/channels/github/binding.d.ts +49 -0
  43. package/dist/src/public/channels/github/binding.js +1 -0
  44. package/dist/src/public/channels/github/checkout.d.ts +48 -0
  45. package/dist/src/public/channels/github/checkout.js +1 -0
  46. package/dist/src/public/channels/github/constants.d.ts +2 -0
  47. package/dist/src/public/channels/github/constants.js +1 -0
  48. package/dist/src/public/channels/github/defaults.d.ts +21 -0
  49. package/dist/src/public/channels/github/defaults.js +3 -0
  50. package/dist/src/public/channels/github/dispatch.d.ts +34 -0
  51. package/dist/src/public/channels/github/dispatch.js +1 -0
  52. package/dist/src/public/channels/github/githubChannel.d.ts +109 -0
  53. package/dist/src/public/channels/github/githubChannel.js +1 -0
  54. package/dist/src/public/channels/github/inbound.d.ts +183 -0
  55. package/dist/src/public/channels/github/inbound.js +2 -0
  56. package/dist/src/public/channels/github/index.d.ts +9 -0
  57. package/dist/src/public/channels/github/index.js +1 -0
  58. package/dist/src/public/channels/github/limits.d.ts +4 -0
  59. package/dist/src/public/channels/github/limits.js +2 -0
  60. package/dist/src/public/channels/github/pr-context.d.ts +45 -0
  61. package/dist/src/public/channels/github/pr-context.js +5 -0
  62. package/dist/src/public/channels/github/state.d.ts +48 -0
  63. package/dist/src/public/channels/github/state.js +1 -0
  64. package/dist/src/public/channels/github/verify.d.ts +35 -0
  65. package/dist/src/public/channels/github/verify.js +1 -0
  66. package/dist/src/public/definitions/sandbox.d.ts +3 -3
  67. package/dist/src/public/sandbox/index.d.ts +1 -1
  68. package/dist/src/runtime/agent/mock-model-adapter.js +1 -1
  69. package/dist/src/runtime/resolve-agent-graph.js +1 -1
  70. package/dist/src/runtime/resolve-sandbox.js +1 -1
  71. package/dist/src/runtime/sandbox/keys.js +1 -1
  72. package/dist/src/runtime/sandbox/template-plan.d.ts +5 -0
  73. package/dist/src/runtime/sandbox/template-plan.js +1 -1
  74. package/dist/src/runtime/session-callback-route.js +1 -1
  75. package/dist/src/runtime/subagents/registry.d.ts +6 -0
  76. package/dist/src/runtime/subagents/registry.js +1 -1
  77. package/dist/src/runtime/types.d.ts +6 -2
  78. package/dist/src/shared/sandbox-definition.d.ts +22 -2
  79. package/package.json +6 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # experimental-ash
2
2
 
3
+ ## 0.58.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 6544348: Add built-in `agent` tool that runs the current agent as a subagent with a focused task. Self-delegated children share the parent's Vercel Sandbox so file writes are visible to the parent. The tool accepts `message` and optional `outputSchema` for structured output.
8
+
9
+ ### Patch Changes
10
+
11
+ - 0fd74c8: Refresh the composed system prompt between turns in `ash dev` so edits to authored markdown, including `instructions.md`, apply to existing development sessions after HMR rebuilds.
12
+ - 4a84d65: Refactored onboarding/channel setup state so Vercel project resolution and AI Gateway readiness are tracked separately while preserving existing create and `ash channels add` behavior.
13
+
14
+ ## 0.57.0
15
+
16
+ ### Minor Changes
17
+
18
+ - b4d3ff0: Sandbox definitions with `bootstrap()` may now provide an optional build-time `revalidationKey()` function. Resolved revalidation keys and authored sandbox source hashes are stored in compiled artifacts so bootstrap templates can reuse stable snapshots across deploys when the revalidation key, sandbox source, and seed content match.
19
+ - ad9c3c5: Add the native GitHub channel (`experimental-ash/channels/github`). Verifies GitHub App webhooks, derives session auth from the GitHub actor, and dispatches `@mention` turns from issue comments, PR timeline comments, and inline review threads. Replies land on the native surface (timeline comment or inline review-thread reply) and acknowledge with an `eyes` reaction. Pull-request turns always inject the PR diff (with noisy files like lock files excluded from the prompt) and always check out the triggering ref into the sandbox so the built-in filesystem tools operate on the real tree. Checkout is incremental — because the sandbox persists for the session, a turn whose target commit the workspace is already on skips the fetch entirely — and brokers the installation token at the sandbox firewall (so it never enters the sandbox process) rather than embedding it in the git remote URL. Supports opt-in `onIssue`/`onPullRequest` automation hooks, an `onComment` override, a generic `ctx.github.request()` escape hatch, and proactive `receive()` sessions.
20
+ - ad9c3c5: Add `credentials.webhookVerifier` to the GitHub channel, matching the other native channels. When supplied, it replaces the built-in `X-Hub-Signature-256` HMAC check (the `GITHUB_WEBHOOK_SECRET` fallback is skipped), letting integrations like Vercel Connect authenticate forwarded webhooks out-of-band (e.g. with Vercel OIDC). Combined with the existing function-form `appId`/`privateKey`/`webhookSecret` credentials, the GitHub channel now has the same Connect integration surface as Slack, Teams, Telegram, and Discord.
21
+ - 4de5be3: Add optional `outputSchema` to subagent tool input schema for structured output. When a parent agent passes `outputSchema` to a subagent tool call, the child runs in task mode and must produce structured output matching the schema via the `final_output` tool.
22
+
3
23
  ## 0.56.0
4
24
 
5
25
  ### Minor Changes
@@ -33,7 +33,7 @@ Discovery avoids executing authored code. The module map is the handoff — it s
33
33
 
34
34
  **`compiled-agent-manifest.json`** — the normalized runtime-facing manifest. Preserves config, prompt, skill, sandbox, schedule, and tool metadata; flattened subagent nodes with explicit `subagentEdges`; manifest kind and version. `sandbox` is `null` or one entry, and `sandboxWorkspaces` is an array of size 0 or 1 (single-sandbox model).
35
35
 
36
- **`compile-metadata.json`** — artifact paths, SHA-256 digests, diagnostics summary, compile status, generator version, and `sourceGraphHash`. The `sourceGraphHash` drives sandbox template cache invalidation runtime and Vercel build-time prewarm both derive template keys from it.
36
+ **`compile-metadata.json`** — artifact paths, SHA-256 digests, diagnostics summary, compile status, generator version, and `sourceGraphHash`. Runtime and Vercel build-time prewarm derive sandbox template keys from compiled artifacts: seed-only templates use workspace content hashes, bootstrap templates use the optional resolved authored `revalidationKey()` plus authored sandbox source and workspace content, and source-graph fallback paths use `sourceGraphHash`.
37
37
 
38
38
  **`module-map.mjs`** — frozen object keyed by `(nodeId, sourceId)`. Authored sandbox modules are indexed directly (runtime needs their lifecycle hooks); `lib/` modules come in transitively.
39
39
 
@@ -80,9 +80,9 @@ Authored hook stream-event dispatch runs inside step (4)'s ALS scope.
80
80
  Event hooks receive `(event, ctx)`. See [Hooks](./hooks.md) for the
81
81
  full pipeline.
82
82
 
83
- `StepInput.modelContext` is provided by channels through
84
- `SendPayload.modelContext`. The messages are visible to the next model
85
- call only and are never persisted to durable session history.
83
+ `StepInput.context` is provided by channels through `SendPayload.context`.
84
+ Each entry is appended as a `role: "user"` message to durable session
85
+ history before the delivery message.
86
86
 
87
87
  ## Channel Context
88
88
 
@@ -118,6 +118,24 @@ Channel and Slack types exported from `experimental-ash/channels/slack`:
118
118
  - `Card`, `Button`, `Actions`, `Section`, `Modal`, `Table`, etc. - card builders re-exported for
119
119
  rendering Slack messages
120
120
 
121
+ Channel and GitHub types exported from `experimental-ash/channels/github`:
122
+
123
+ - `githubChannel` - GitHub App webhook channel factory for issue/PR comments, review comments,
124
+ and opt-in issue/pull-request hooks
125
+ - `GitHubChannelConfig` - config type for credentials, route, bot mention trigger, PR diff
126
+ exclusions, reaction acknowledgement, and the `onComment`/`onIssue`/`onPullRequest` handlers
127
+ - `GitHubInboundContext` - pre-dispatch context for inbound hooks (`thread`, `github`,
128
+ `repository`, `sender`, `delivery`)
129
+ - `GitHubEventContext` - event-handler context (`thread`, `github`, mutable `state`,
130
+ repository/conversation metadata, and channel session operations)
131
+ - `GitHubHandle` - GitHub helper handle: `request()` plus `installationId` and `repository`
132
+ - `GitHubThread` - conversation-scoped operations: `post` and `react`
133
+ - `GitHubComment`, `GitHubIssueEvent`, and `GitHubPullRequestEvent` - parsed inbound payload types
134
+ - `GitHubIssueAction` / `GitHubPullRequestAction` - typed webhook action unions
135
+ - `GitHubPullRequestContextConfig` - PR diff exclusion config (`excludedFiles`)
136
+ - `GitHubReceiveTarget` - target accepted by proactive `receive(github, ...)`
137
+ - `defaultGitHubAuth` - default GitHub actor-to-session-auth projection
138
+
121
139
  Channel and Twilio types exported from `experimental-ash/channels/twilio`:
122
140
 
123
141
  - `twilioChannel` - Twilio channel factory for SMS and speech-transcribed voice webhooks
@@ -203,6 +221,7 @@ import { defineAgent } from "experimental-ash";
203
221
  import { defineChannel, POST, GET } from "experimental-ash/channels";
204
222
  import { ashChannel } from "experimental-ash/channels/ash";
205
223
  import { vercelOidc } from "experimental-ash/channels/auth";
224
+ import { githubChannel } from "experimental-ash/channels/github";
206
225
  import { slackChannel } from "experimental-ash/channels/slack";
207
226
  import { telegramChannel } from "experimental-ash/channels/telegram";
208
227
  ```
@@ -63,7 +63,8 @@ Important behavior:
63
63
  - Ash skips prewarm for sandboxes with no `bootstrap()` and no workspace seed files
64
64
  - seed-only templates are keyed by skills and workspace file contents, so unchanged seeds can reuse
65
65
  a template across deploys
66
- - sandboxes with `bootstrap()` remain deployment-scoped
66
+ - sandboxes with `bootstrap()` are keyed by the optional resolved `revalidationKey()` value plus
67
+ authored sandbox source and seed contents, so matching inputs can reuse a template across deploys
67
68
  - the build log labels each template `reused cached` or `built`, so a reuse across deploys is visible
68
69
  - `onSession()` still runs later at runtime
69
70
  - if build-time prewarm fails, the build fails
@@ -0,0 +1,145 @@
1
+ ---
2
+ title: "GitHub channel setup"
3
+ description: "Create a GitHub App-backed Ash channel with automatic PR context and sandbox checkout."
4
+ type: integration
5
+ related:
6
+ - /channels
7
+ - /sandbox
8
+ ---
9
+
10
+ Ash GitHub channels receive GitHub App webhooks at `/ash/v1/github`, verify the webhook signature,
11
+ derive auth from the GitHub actor, and deliver responses back to the native GitHub surface.
12
+
13
+ ## Add The Channel File
14
+
15
+ Create `agent/channels/github.ts`:
16
+
17
+ ```ts
18
+ import { githubChannel } from "experimental-ash/channels/github";
19
+
20
+ export default githubChannel({
21
+ botName: "my-agent",
22
+ credentials: {
23
+ appId: process.env.GITHUB_APP_ID,
24
+ privateKey: process.env.GITHUB_APP_PRIVATE_KEY,
25
+ webhookSecret: process.env.GITHUB_WEBHOOK_SECRET,
26
+ },
27
+ });
28
+ ```
29
+
30
+ Configure the GitHub App webhook URL to `https://<deployment>/ash/v1/github`. Subscribe to
31
+ `issue_comment` and `pull_request_review_comment` for mention-driven turns. Subscribe to `issues`
32
+ and `pull_request` when you configure their opt-in hooks.
33
+
34
+ The agent starts a turn whenever a comment or inline review comment `@mention`s `botName`.
35
+
36
+ Each field has an environment-variable fallback, so the `credentials` block above is optional when
37
+ those variables are set: `appId` reads `GITHUB_APP_ID`, `privateKey` reads `GITHUB_APP_PRIVATE_KEY`,
38
+ `webhookSecret` reads `GITHUB_WEBHOOK_SECRET`, and `botName` reads `GITHUB_APP_SLUG`. `appId`,
39
+ `privateKey`, and `webhookSecret` also accept a function that resolves the value lazily (e.g. from a
40
+ secret manager).
41
+
42
+ ## Acknowledgement & Replies
43
+
44
+ The channel manages the GitHub-native side of a turn for you:
45
+
46
+ - When a turn starts it adds an `eyes` reaction to the triggering comment so the user knows the agent
47
+ picked it up. Disable the reaction with `progress: { reactions: false }`.
48
+ - The agent's reply is posted back to the conversation as a comment (issue/PR timeline comments reply
49
+ on the issue or PR; inline review comments reply in the review thread). Long replies are split
50
+ across multiple comments to stay within GitHub's size limit.
51
+ - If a turn or session fails, the channel posts a short error comment with an error id instead of
52
+ going silent.
53
+
54
+ ## Pull Request Context
55
+
56
+ When the agent is summoned on a pull request, it always sees the PR. The channel injects PR metadata
57
+ and the changed-file diff as `context` strings appended to the turn before the comment. There is no
58
+ flag to disable it — seeing the diff is the point.
59
+
60
+ Large, generated files (lock files, minified bundles, source maps, snapshots) are excluded from the
61
+ diff loaded into the prompt: they are still listed with their stats, but their patch body is omitted.
62
+ Add your own globs with `excludedFiles` (they extend the built-in list):
63
+
64
+ ```ts
65
+ export default githubChannel({
66
+ botName: "my-agent",
67
+ pullRequestContext: {
68
+ excludedFiles: ["**/*.generated.ts", "docs/**/*.svg"],
69
+ },
70
+ });
71
+ ```
72
+
73
+ Excluded files are still on disk via the checkout, so the agent can `read_file` one if it needs the
74
+ contents. Custom inbound hooks can also return their own `context`; Ash prepends the
75
+ channel-generated PR diff first, then your hook context.
76
+
77
+ ## Sandbox Checkout
78
+
79
+ Every triggered turn checks out the relevant ref into the sandbox before the first model call, so the
80
+ built-in `read_file`, `glob`, `grep`, and `bash` tools operate on the real tree:
81
+
82
+ - issue comments and `onIssue` turns check out the repository default branch.
83
+ - PR comments, inline review comments, and `onPullRequest` turns check out the PR head, and also
84
+ fetch the base ref so `git diff <base>...HEAD` works.
85
+
86
+ Checkout brokers a fresh installation token at the sandbox firewall: `git` fetches a clean,
87
+ token-free URL and the platform injects an `Authorization` header on egress to `github.com`, so the
88
+ token never enters the sandbox process. This requires a firewall-capable backend (the Vercel
89
+ backend); the local backend cannot broker, so checkout is skipped there (the failure is logged and
90
+ the turn proceeds with the PR diff still in context).
91
+
92
+ The sandbox persists for the life of the session, so checkout is incremental: when a later turn in
93
+ the same session targets a commit the workspace is already on, the turn skips the fetch entirely
94
+ (no installation token is minted and nothing is downloaded).
95
+
96
+ ## Inbound Hooks
97
+
98
+ Three optional hooks let you customize dispatch. Each returns `{ auth }` to dispatch or `null` to
99
+ ignore.
100
+
101
+ Derive session auth from the GitHub actor with `defaultGitHubAuth(ctx)`.
102
+
103
+ ```ts
104
+ import { defaultGitHubAuth, githubChannel } from "experimental-ash/channels/github";
105
+
106
+ export default githubChannel({
107
+ botName: "my-agent",
108
+
109
+ // Replaces the default `@mention` gate for issue/PR/review comments.
110
+ // ctx.conversation.kind is "issue" | "pull_request" | "review_thread".
111
+ async onComment(ctx, comment) {
112
+ const context: string[] | undefined = comment.body.includes("/grammar")
113
+ ? ["Review only prose, comments, and documentation."]
114
+ : undefined;
115
+ return { auth: defaultGitHubAuth(ctx), context };
116
+ },
117
+
118
+ // Opt in to act on issues. There is no default dispatch on this event.
119
+ onIssue(ctx, issue) {
120
+ if (issue.action !== "opened") return null;
121
+ return { auth: defaultGitHubAuth(ctx) };
122
+ },
123
+
124
+ // Opt in to act on pull requests. There is no default dispatch on this event.
125
+ onPullRequest(ctx, pullRequest) {
126
+ if (pullRequest.action !== "opened") return null;
127
+ return { auth: defaultGitHubAuth(ctx) };
128
+ },
129
+ });
130
+ ```
131
+
132
+ `issue.action` and `pullRequest.action` are typed unions of the common GitHub webhook actions
133
+ (`opened`, `closed`, `edited`, `synchronize`, …) and stay open to any action GitHub sends.
134
+
135
+ ## Arbitrary GitHub API Calls
136
+
137
+ For anything the channel does not wrap, use `ctx.github.request()` with installation-token auth:
138
+
139
+ ```ts
140
+ await ctx.github.request({
141
+ method: "POST",
142
+ path: `/repos/${ctx.repository.fullName}/issues/${issueNumber}/labels`,
143
+ body: { labels: ["triaged"] },
144
+ });
145
+ ```
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: "Channels"
3
- description: "Deliver your agent over HTTP, Slack, Discord, Twilio, Telegram, Microsoft Teams, and custom transports."
3
+ description: "Deliver your agent over HTTP, Slack, GitHub, Discord, Twilio, Telegram, Microsoft Teams, and custom transports."
4
4
  url: /channels
5
5
  ---
6
6
 
@@ -23,7 +23,7 @@ Ash ships the public HTTP protocol as channels:
23
23
  - `ashChannel({ auth })` for the built-in Ash protocol channel
24
24
 
25
25
  Ash also supports authored channels under `agent/channels/` for platform-specific integrations such
26
- as Slack, Discord, Twilio, Telegram, Microsoft Teams, or custom webhooks.
26
+ as Slack, GitHub, Discord, Twilio, Telegram, Microsoft Teams, or custom webhooks.
27
27
 
28
28
  ## Filesystem Shape
29
29
 
@@ -283,6 +283,26 @@ dispatch for app mentions, direct messages, and interactions.
283
283
  For a Slack app backed by Vercel Connect, see [Slack channel setup](./slack.mdx) to create the Connect client
284
284
  and channel file.
285
285
 
286
+ ## GitHub Channels
287
+
288
+ GitHub channels are authored with `githubChannel()`:
289
+
290
+ ```ts
291
+ import { githubChannel } from "experimental-ash/channels/github";
292
+
293
+ export default githubChannel({
294
+ botName: "my-agent",
295
+ });
296
+ ```
297
+
298
+ The channel verifies GitHub App webhooks at `/ash/v1/github`, dispatches bot-directed issue/PR
299
+ comments and pull-request review comments, can opt into issue and pull-request hooks, exposes
300
+ GitHub-native issue, PR, reaction, and checkout helpers, and injects bounded PR metadata,
301
+ file lists, and patch text as turn `context`.
302
+
303
+ See [GitHub channel setup](./github.md) for GitHub App permissions, PR context, checkout, and
304
+ custom inbound hooks.
305
+
286
306
  ## Discord Channels
287
307
 
288
308
  Discord channels are authored with `discordChannel()`:
@@ -632,6 +652,7 @@ See [Channel file uploads](./attachments.md) for the full guide.
632
652
  ## What To Read Next
633
653
 
634
654
  - [Slack channel setup](./slack.mdx)
655
+ - [GitHub channel setup](./github.md)
635
656
  - [Telegram channel setup](./telegram.mdx)
636
657
  - [Channel file uploads](./attachments.md)
637
658
  - [Project Layout](../project-layout.md)
@@ -29,6 +29,7 @@ author a sandbox definition module:
29
29
  import { defineSandbox } from "experimental-ash/sandbox";
30
30
 
31
31
  export default defineSandbox({
32
+ revalidationKey: () => "jq-bootstrap-v1",
32
33
  async bootstrap({ use }) {
33
34
  const sandbox = await use();
34
35
  await sandbox.run({ command: "apt-get install -y jq" });
@@ -52,6 +53,9 @@ The public lifecycle surface is intentionally small:
52
53
 
53
54
  - `bootstrap({ use })` — template-scoped setup (runs once when the template is built). Call `use()`
54
55
  to get a `SandboxSession` for filesystem setup. Only filesystem state survives snapshotting.
56
+ - `revalidationKey()` — optional build-time function for external inputs that affect
57
+ `bootstrap()` output. Authored sandbox source and framework-managed seed contents are included
58
+ automatically.
55
59
  - `onSession({ use })` — durable-session-scoped setup (runs once per session). Call `use(opts?)` to
56
60
  get a `SandboxSession`; `opts` are forwarded to the backend's update path (the Vercel SDK's
57
61
  `sandbox.update(...)`) before the session is returned. `onSession`'s `use()` never creates a
@@ -190,6 +194,13 @@ Inside a subagent's authored code, `ctx.getSandbox()` binds to the subagent's ow
190
194
  Call `use()` to get a `SandboxSession`. Only filesystem state survives — configuration changes
191
195
  like network policy or resource allocation do not persist into the snapshot.
192
196
 
197
+ When external inputs can change `bootstrap()` output, define
198
+ `revalidationKey: () => string | Promise<string>`. Ash evaluates `revalidationKey()` during
199
+ compile/build, stores the resolved string in compiled artifacts, and uses that frozen value for both
200
+ build-time prewarm and runtime session creation. Authored sandbox source and framework-managed seed
201
+ contents are included automatically, so use `revalidationKey()` only for inputs outside that source,
202
+ such as a git commit, lockfile hash, base snapshot version, or daily rotation string.
203
+
193
204
  Framework-managed seed files (compiled skills, authored `workspace/` entries) are written into the
194
205
  template before the authored `bootstrap` runs, so your bootstrap can read them.
195
206
 
@@ -246,6 +257,7 @@ import { defineSandbox, vercelBackend } from "experimental-ash/sandbox";
246
257
 
247
258
  export default defineSandbox({
248
259
  backend: vercelBackend({ runtime: "node24", resources: { vcpus: 2 } }),
260
+ revalidationKey: () => "repo-bootstrap-v1",
249
261
  async bootstrap({ use }) {
250
262
  const sandbox = await use();
251
263
  await sandbox.run({ command: "git clone https://example.com/repo.git repo" });
@@ -322,8 +334,8 @@ also work and follow the same rules. When `source.type === "snapshot"`, the Verc
322
334
  `runtime`, and TypeScript enforces that at the factory call site.
323
335
 
324
336
  Ash does not detect changes to an external snapshot. If you rebuild your snapshot and want Ash to
325
- pick it up, force a template rebuild — for example, by changing the sandbox definition so its
326
- template key changes.
337
+ pick it up, force a template rebuild by changing the `revalidationKey()` value used by your
338
+ bootstrap template.
327
339
 
328
340
  ### Deferring Backend Construction
329
341
 
@@ -473,7 +485,10 @@ On hosted Vercel builds, Ash can prewarm the authored sandbox template during `a
473
485
 
474
486
  Ash skips template prewarm for sandboxes that have no `bootstrap()` and no workspace seed files.
475
487
  Seed-only sandboxes use a content-addressed template key, so matching skills and workspace files can
476
- reuse an existing template across deploys. Sandboxes with `bootstrap()` remain deployment-scoped.
488
+ reuse an existing template across deploys. Sandboxes with `bootstrap()` use the optional resolved
489
+ `revalidationKey` plus authored sandbox source and workspace seed content, so matching inputs can
490
+ reuse an existing template across deploys. Changing `revalidationKey()` or sandbox source only takes
491
+ effect after rebuilding compiled artifacts.
477
492
 
478
493
  Important behavior:
479
494
 
@@ -3,9 +3,15 @@ title: "Subagents"
3
3
  description: "Delegate specialized work to child agents with their own prompts, tools, and sandbox."
4
4
  ---
5
5
 
6
- Subagents let the root agent delegate specialized work to local child agents.
6
+ Subagents let an agent delegate specialized work to child agents.
7
7
 
8
- Use them when the task deserves its own prompt, tools, sandbox, and local runtime context.
8
+ There are two kinds of delegation:
9
+
10
+ - **Built-in `agent` tool** — every agent gets this automatically. It runs a copy of the current
11
+ agent as a subagent with a focused task. The child shares the parent's sandbox filesystem but
12
+ gets fresh session state. Use this when the task doesn't need a different prompt or tool surface.
13
+ - **Declared subagents** — authored under `agent/subagents/<id>/` with their own prompt, tools,
14
+ skills, and sandbox. Use these when the child needs a clearly different identity.
9
15
 
10
16
  A subagent is just an agent that lives under `agent/subagents/<id>/`. It is authored with the same
11
17
  `defineAgent` helper as the root agent — the location under `subagents/` is what makes it a
@@ -21,6 +27,37 @@ subagent.
21
27
  stream, and do not commit unless the user asks.
22
28
  </CopyPrompt>
23
29
 
30
+ ## The Built-in `agent` Tool
31
+
32
+ Every agent gets a built-in `agent` tool. The model calls it to delegate a subtask to a copy of
33
+ itself — same tools, connections, and instructions, fresh conversation history.
34
+
35
+ ```ts
36
+ {
37
+ message: string;
38
+ outputSchema?: object;
39
+ }
40
+ ```
41
+
42
+ The child shares the parent's sandbox filesystem. File writes from the child are immediately
43
+ visible to the parent. This makes it natural for the model to split work across multiple parallel
44
+ calls — for example, fixing different files concurrently.
45
+
46
+ Session state (`defineState`) is **not** shared. Each child gets its own fresh state. Auth,
47
+ connections, and the full tool surface are inherited, but conversation history and durable state
48
+ start empty.
49
+
50
+ If a declared subagent calls `agent`, the child is a copy of that subagent (not the root agent).
51
+ Every node self-delegates to itself.
52
+
53
+ An authored tool at `agent/tools/agent.ts` takes priority over the built-in.
54
+
55
+ ## Declared Subagents
56
+
57
+ A declared subagent is an agent that lives under `agent/subagents/<id>/`. It is authored with the same
58
+ `defineAgent` helper as the root agent — the location under `subagents/` is what makes it a
59
+ subagent.
60
+
24
61
  ## What A Local Subagent Looks Like
25
62
 
26
63
  ```text
@@ -74,7 +111,7 @@ export default defineRemoteAgent({
74
111
  });
75
112
  ```
76
113
 
77
- Remote agents use the same lowered `{ message: string }` tool shape as local subagents. The
114
+ Remote agents use the same lowered `{ message, outputSchema? }` tool shape as local subagents. The
78
115
  difference is dispatch: the parent starts a task-mode session on the remote agent's
79
116
  `POST /ash/v1/session`, passes a framework callback URL, parks the parent turn, and resumes when
80
117
  the remote agent posts a terminal callback back to the parent. The parent sees the same
@@ -98,11 +135,12 @@ owns redelivery instead of marking the remote task complete.
98
135
 
99
136
  Ash lowers every subagent into a model-visible tool on the parent agent.
100
137
 
101
- The lowered tool always exposes the same single parameter:
138
+ The lowered tool exposes:
102
139
 
103
140
  ```ts
104
141
  {
105
142
  message: string;
143
+ outputSchema?: object;
106
144
  }
107
145
  ```
108
146
 
@@ -110,9 +148,13 @@ The parent fills `message` with everything the subagent needs to run the task
110
148
  not see the parent's history. This mirrors the public message API used to invoke the parent agent
111
149
  itself, so prompt-engineering a delegation feels the same as prompt-engineering an initial run.
112
150
 
113
- ## What The Subagent Gets
151
+ When `outputSchema` is provided, the subagent runs in task mode and must produce structured output
152
+ matching the given JSON Schema via the `final_output` tool. The structured output becomes the tool
153
+ result returned to the parent.
114
154
 
115
- Subagent execution gets:
155
+ ## What A Declared Subagent Gets
156
+
157
+ Declared subagent execution gets:
116
158
 
117
159
  - its own instructions prompt
118
160
  - its own tools
@@ -120,10 +162,11 @@ Subagent execution gets:
120
162
  - its own `skills/` set (independent of the parent's skills)
121
163
  - immediate parent lineage in `ctx.session.parent`, including the parent subagent tool `callId`
122
164
 
123
- Skills and sandboxes do not cross the parent/child boundary. The subagent only sees skills
124
- authored under `agent/subagents/<id>/skills/`; skills under the root `agent/skills/` are not
125
- available inside the subagent's turns. If two subagents need the same procedure, duplicate the
126
- markdown under each subagent's `skills/` (or, for typed code reuse, share helpers via `lib/`).
165
+ Skills and sandboxes do not cross the parent/child boundary for declared subagents. The subagent
166
+ only sees skills authored under `agent/subagents/<id>/skills/`; skills under the root
167
+ `agent/skills/` are not available inside the subagent's turns. If two subagents need the same
168
+ procedure, duplicate the markdown under each subagent's `skills/` (or, for typed code reuse, share
169
+ helpers via `lib/`).
127
170
 
128
171
  The subagent's sandbox follows the same rule: it does not inherit from the parent, since different
129
172
  agents generally have different skills and sharing a filesystem would be confusing. Each subagent
@@ -132,6 +175,9 @@ gets the framework default sandbox unless it authors its own override at
132
175
  `workspace/` folder). A subagent can also author only `subagents/<name>/sandbox/workspace/` to seed
133
176
  files without overriding the definition.
134
177
 
178
+ The built-in `agent` tool is different: its children share the parent's sandbox because they are
179
+ copies of the same agent working on the same codebase.
180
+
135
181
  ## Stream Behavior
136
182
 
137
183
  Delegated local subagents now create their own public child session and stream.
@@ -119,8 +119,14 @@ That means tools are:
119
119
  ## Customizing Framework Tools
120
120
 
121
121
  Every Ash agent gets a small set of framework-provided tools by default:
122
- `bash`, `read_file`, `write_file`, `todo`, `web_fetch`, `web_search`, and, when the agent has skills,
123
- `load_skill`.
122
+ `bash`, `read_file`, `write_file`, `todo`, `web_fetch`, `web_search`, `agent`, and, when the agent
123
+ has skills, `load_skill`.
124
+
125
+ `agent` runs a copy of the current agent as a subagent with a focused task. The child gets the same
126
+ tools, connections, and instructions but a fresh conversation history. The child shares the parent's
127
+ sandbox filesystem — file writes from the child are visible to the parent. Session state
128
+ (`defineState`) is isolated: the child gets fresh state, not the parent's values. See
129
+ [Subagents](./subagents.mdx) for more on delegation.
124
130
 
125
131
  `load_skill` loads on-demand skill instructions into the current turn. It does not add a new
126
132
  execution surface by itself; executable behavior still comes from the tools the agent already has.
@@ -1,4 +1,4 @@
1
- import { type DeployProjectOptions, type RunAddToAgentOptions } from "@vercel/ash-scaffold/steps";
1
+ import { type AddToAgentState, type DeployProjectOptions, type RunAddToAgentOptions } from "@vercel/ash-scaffold/steps";
2
2
  import { type ChannelAddPrompter } from "@vercel/ash-scaffold/cli";
3
3
  import { type DeploymentInfo } from "@vercel/ash-scaffold/primitives";
4
4
  export interface CliLogger {
@@ -13,8 +13,8 @@ export interface AddChannelCommandOptions {
13
13
  export interface ChannelsAddDependencies {
14
14
  createPrompter?: () => ChannelAddPrompter;
15
15
  detectDeployment(projectPath: string): Promise<DeploymentInfo>;
16
- deployProject(options: DeployProjectOptions): Promise<void>;
17
- runAddToAgent(options: RunAddToAgentOptions): Promise<void>;
16
+ deployProject(options: DeployProjectOptions): Promise<AddToAgentState>;
17
+ runAddToAgent(options: RunAddToAgentOptions): Promise<AddToAgentState>;
18
18
  }
19
19
  export declare function runChannelsAddCommand(logger: CliLogger, appRoot: string, args: {
20
20
  kind?: string;
@@ -1 +1 @@
1
- import{assertCanAddSelectedChannels,inspectExistingChannelRegistrations}from"./channel-add-conflicts.js";import{isAshProject}from"../../packages/ash-scaffold/src/project.js";import{listAuthoredChannels}from"../../packages/ash-scaffold/src/channels.js";import"../../packages/ash-scaffold/src/index.js";import{detectDeployment}from"../../packages/ash-scaffold/src/primitives/detect-deployment.js";import{createAddToAgentState,deployProject,runAddToAgent}from"../../packages/ash-scaffold/src/steps/run-add-to-agent.js";import"../../packages/ash-scaffold/src/steps/index.js";import{ChannelAddCancelledError,createChannelAddPrompter}from"../../packages/ash-scaffold/src/cli/channel-add-prompter.js";import"../../packages/ash-scaffold/src/cli/index.js";import"../../packages/ash-scaffold/src/primitives/index.js";const NOT_AN_AGENT_MESSAGE="No Ash agent in this directory. Run `pnpm create experimental-ash-agent`, then run this command from inside the new project.",KNOWN_CHANNEL_KINDS=[`slack`,`web`];function isChannelKind(e){return KNOWN_CHANNEL_KINDS.includes(e)}function parseChannelKind(e){if(!isChannelKind(e))throw Error(`Unknown channel kind "${e}". Known: ${KNOWN_CHANNEL_KINDS.join(`, `)}.`);return e}const defaultChannelsAddDependencies={detectDeployment,deployProject,runAddToAgent};async function runAddChannelsFlow(n,r,i,o){if(r===void 0&&(i.yes||!process.stdin.isTTY||!process.stdout.isTTY))throw Error(`Pass a channel kind: \`ash channels add <${KNOWN_CHANNEL_KINDS.join(`|`)}>\`.`);let s=o.createPrompter?.()??createChannelAddPrompter();s.intro(`Add channels to your Ash agent`),s.log.message(`Checking the current Vercel project...`);let c=createAddToAgentState(await o.detectDeployment(n)),l;function inspectRegistrations(){return l===void 0&&(s.log.message(`Inspecting existing channel registrations...`),l=inspectExistingChannelRegistrations(n)),l}let u=r===void 0?(await inspectRegistrations()).disabledChannelReasons:void 0;await o.runAddToAgent({prompter:s,projectPath:n,state:c,presetChannels:r===void 0?void 0:[r],presetCreateSlackbot:i.yes?!0:void 0,disabledChannelReasons:u,force:i.force,validateSelectedChannels:async t=>{!t.includes(`web`)&&!t.includes(`slack`)||assertCanAddSelectedChannels(t,await inspectRegistrations())}}),await o.deployProject({prompter:s,projectPath:n,vercelProjectId:c.vercelProjectId,state:c}),s.outro(c.channels.length===0?`No channels added.`:`Channels added.`)}async function runChannelsAddCommand(e,t,r,i=defaultChannelsAddDependencies){if(!await isAshProject(t)){e.error(NOT_AN_AGENT_MESSAGE),process.exitCode=1;return}try{await runAddChannelsFlow(t,r.kind===void 0?void 0:parseChannelKind(r.kind),r.options,i)}catch(t){if(t instanceof ChannelAddCancelledError)return;e.error(t instanceof Error?t.message:String(t)),process.exitCode=1}}async function runChannelsListCommand(e,t,i){if(!await isAshProject(t)){e.error(NOT_AN_AGENT_MESSAGE),process.exitCode=1;return}let a=await listAuthoredChannels(t);if(i.json){e.log(JSON.stringify({channels:a},null,2));return}if(a.length===0){e.log("No channels defined. Run `ash channels add` to add one.");return}for(let t of a)e.log(t)}export{runChannelsAddCommand,runChannelsListCommand};
1
+ import{assertCanAddSelectedChannels,inspectExistingChannelRegistrations}from"./channel-add-conflicts.js";import{isAshProject}from"../../packages/ash-scaffold/src/project.js";import{listAuthoredChannels}from"../../packages/ash-scaffold/src/channels.js";import"../../packages/ash-scaffold/src/index.js";import{detectDeployment}from"../../packages/ash-scaffold/src/primitives/detect-deployment.js";import{createAddToAgentState,deployProject,runAddToAgent}from"../../packages/ash-scaffold/src/steps/run-add-to-agent.js";import"../../packages/ash-scaffold/src/steps/index.js";import{ChannelAddCancelledError,createChannelAddPrompter}from"../../packages/ash-scaffold/src/cli/channel-add-prompter.js";import"../../packages/ash-scaffold/src/cli/index.js";import"../../packages/ash-scaffold/src/primitives/index.js";const NOT_AN_AGENT_MESSAGE="No Ash agent in this directory. Run `pnpm create experimental-ash-agent`, then run this command from inside the new project.",KNOWN_CHANNEL_KINDS=[`slack`,`web`];function isChannelKind(e){return KNOWN_CHANNEL_KINDS.includes(e)}function parseChannelKind(e){if(!isChannelKind(e))throw Error(`Unknown channel kind "${e}". Known: ${KNOWN_CHANNEL_KINDS.join(`, `)}.`);return e}const defaultChannelsAddDependencies={detectDeployment,deployProject,runAddToAgent};async function runAddChannelsFlow(n,r,i,o){if(r===void 0&&(i.yes||!process.stdin.isTTY||!process.stdout.isTTY))throw Error(`Pass a channel kind: \`ash channels add <${KNOWN_CHANNEL_KINDS.join(`|`)}>\`.`);let s=o.createPrompter?.()??createChannelAddPrompter();s.intro(`Add channels to your Ash agent`),s.log.message(`Checking the current Vercel project...`);let c=createAddToAgentState(await o.detectDeployment(n)),l;function inspectRegistrations(){return l===void 0&&(s.log.message(`Inspecting existing channel registrations...`),l=inspectExistingChannelRegistrations(n)),l}let u=r===void 0?(await inspectRegistrations()).disabledChannelReasons:void 0;c=await o.runAddToAgent({prompter:s,projectPath:n,state:c,presetChannels:r===void 0?void 0:[r],presetCreateSlackbot:i.yes?!0:void 0,disabledChannelReasons:u,force:i.force,validateSelectedChannels:async t=>{!t.includes(`web`)&&!t.includes(`slack`)||assertCanAddSelectedChannels(t,await inspectRegistrations())}}),c=await o.deployProject({prompter:s,projectPath:n,state:c}),s.outro(c.channels.length===0?`No channels added.`:`Channels added.`)}async function runChannelsAddCommand(e,t,r,i=defaultChannelsAddDependencies){if(!await isAshProject(t)){e.error(NOT_AN_AGENT_MESSAGE),process.exitCode=1;return}try{await runAddChannelsFlow(t,r.kind===void 0?void 0:parseChannelKind(r.kind),r.options,i)}catch(t){if(t instanceof ChannelAddCancelledError)return;e.error(t instanceof Error?t.message:String(t)),process.exitCode=1}}async function runChannelsListCommand(e,t,i){if(!await isAshProject(t)){e.error(NOT_AN_AGENT_MESSAGE),process.exitCode=1;return}let a=await listAuthoredChannels(t);if(i.json){e.log(JSON.stringify({channels:a},null,2));return}if(a.length===0){e.log("No channels defined. Run `ash channels add` to add one.");return}for(let t of a)e.log(t)}export{runChannelsAddCommand,runChannelsListCommand};
@@ -18,7 +18,7 @@ export declare const ROOT_COMPILED_AGENT_NODE_ID = "__root__";
18
18
  /**
19
19
  * Current compiled manifest schema version.
20
20
  */
21
- export declare const COMPILED_AGENT_MANIFEST_VERSION = 26;
21
+ export declare const COMPILED_AGENT_MANIFEST_VERSION = 27;
22
22
  /**
23
23
  * Compiled channel entry preserved in the compiled manifest.
24
24
  */
@@ -189,6 +189,8 @@ declare const compiledSandboxDefinitionSchema: z.ZodObject<{
189
189
  description: z.ZodOptional<z.ZodString>;
190
190
  exportName: z.ZodOptional<z.ZodString>;
191
191
  logicalPath: z.ZodString;
192
+ revalidationKey: z.ZodOptional<z.ZodString>;
193
+ sourceHash: z.ZodString;
192
194
  sourceId: z.ZodString;
193
195
  sourceKind: z.ZodLiteral<"module">;
194
196
  }, z.core.$strict>;
@@ -248,6 +250,8 @@ declare const compiledAgentNodeManifestSchema: z.ZodObject<{
248
250
  description: z.ZodOptional<z.ZodString>;
249
251
  exportName: z.ZodOptional<z.ZodString>;
250
252
  logicalPath: z.ZodString;
253
+ revalidationKey: z.ZodOptional<z.ZodString>;
254
+ sourceHash: z.ZodString;
251
255
  sourceId: z.ZodString;
252
256
  sourceKind: z.ZodLiteral<"module">;
253
257
  }, z.core.$strict>>;
@@ -358,6 +362,8 @@ export declare const compiledAgentManifestSchema: z.ZodObject<{
358
362
  description: z.ZodOptional<z.ZodString>;
359
363
  exportName: z.ZodOptional<z.ZodString>;
360
364
  logicalPath: z.ZodString;
365
+ revalidationKey: z.ZodOptional<z.ZodString>;
366
+ sourceHash: z.ZodString;
361
367
  sourceId: z.ZodString;
362
368
  sourceKind: z.ZodLiteral<"module">;
363
369
  }, z.core.$strict>>;
@@ -408,7 +414,7 @@ export declare const compiledAgentManifestSchema: z.ZodObject<{
408
414
  sourceId: z.ZodString;
409
415
  sourceKind: z.ZodLiteral<"module">;
410
416
  }, z.core.$strict>>;
411
- version: z.ZodLiteral<26>;
417
+ version: z.ZodLiteral<27>;
412
418
  workspaceResourceRoot: z.ZodObject<{
413
419
  contentHash: z.ZodOptional<z.ZodString>;
414
420
  logicalPath: z.ZodString;
@@ -1 +1 @@
1
- import{z}from"#compiled/zod/index.js";import{discoverDiagnosticsSummarySchema}from"#discover/diagnostics.js";import{compiledRemoteAgentNodeSchema}from"#compiler/remote-agent-node.js";import{jsonObjectSchema}from"#shared/json-schemas.js";const COMPILED_AGENT_MANIFEST_KIND=`ash-agent-compiled-manifest`,ROOT_COMPILED_AGENT_NODE_ID=`__root__`,COMPILED_AGENT_MANIFEST_VERSION=26,moduleSourceRefSchema=z.object({exportName:z.string().optional(),sourceKind:z.literal(`module`),logicalPath:z.string(),sourceId:z.string()}).strict(),channelMethodSchema=z.union([z.literal(`GET`),z.literal(`POST`),z.literal(`PUT`),z.literal(`PATCH`),z.literal(`DELETE`)]),compiledChannelDefinitionSchema=z.object({kind:z.literal(`channel`),name:z.string(),logicalPath:z.string(),method:channelMethodSchema,urlPath:z.string(),sourceId:z.string(),sourceKind:z.literal(`module`),exportName:z.string().optional(),adapterKind:z.string().optional()}).strict(),disabledCompiledChannelEntrySchema=z.object({kind:z.literal(`disabled`),name:z.string(),logicalPath:z.string()}).strict(),compiledChannelEntrySchema=z.union([compiledChannelDefinitionSchema,disabledCompiledChannelEntrySchema]),compiledRuntimeModelReferenceSchema=z.object({contextWindowTokens:z.number().int().positive().optional(),id:z.string(),source:moduleSourceRefSchema.optional(),providerOptions:z.record(z.string(),jsonObjectSchema).optional()}).strict(),compiledAgentBuildDefinitionSchema=z.object({externalDependencies:z.array(z.string()).optional()}).strict(),compiledAgentCompactionDefinitionSchema=z.object({model:compiledRuntimeModelReferenceSchema.optional(),thresholdPercent:z.number().finite().min(0).max(1).optional()}).strict(),compiledAgentConfigSchema=z.object({build:compiledAgentBuildDefinitionSchema.optional(),compaction:compiledAgentCompactionDefinitionSchema.optional(),description:z.string().optional(),experimental:z.object({codeMode:z.boolean().optional()}).strict().optional(),model:compiledRuntimeModelReferenceSchema,name:z.string(),outputSchema:jsonObjectSchema.optional(),source:moduleSourceRefSchema.optional()}).strict(),compiledInstructionsSchema=z.object({name:z.string(),logicalPath:z.string(),markdown:z.string(),sourceId:z.string(),sourceKind:z.union([z.literal(`markdown`),z.literal(`module`)])}).strict(),compiledSkillBaseFields={name:z.string(),description:z.string(),license:z.string().optional(),markdown:z.string(),metadata:z.record(z.string(),z.string()).optional(),sourceId:z.string(),logicalPath:z.string()},compiledSkillSourceSchema=z.discriminatedUnion(`sourceKind`,[z.object({...compiledSkillBaseFields,sourceKind:z.literal(`markdown`)}).strict(),z.object({...compiledSkillBaseFields,sourceKind:z.literal(`module`),exportName:z.string().optional()}).strict(),z.object({...compiledSkillBaseFields,sourceKind:z.literal(`skill-package`),skillId:z.string(),skillFilePath:z.string(),rootPath:z.string(),assetsPath:z.string().optional(),referencesPath:z.string().optional(),scriptsPath:z.string().optional()}).strict()]),compiledScheduleDefinitionSchema=z.object({cron:z.string(),hasRun:z.boolean(),name:z.string(),logicalPath:z.string(),markdown:z.string().optional(),sourceId:z.string(),sourceKind:z.union([z.literal(`markdown`),z.literal(`module`)])}).strict(),compiledSandboxDefinitionSchema=z.object({description:z.string().optional(),exportName:z.string().optional(),logicalPath:z.string(),sourceId:z.string(),sourceKind:z.literal(`module`)}).strict(),compiledSandboxWorkspaceSchema=z.object({logicalPath:z.string(),rootEntries:z.array(z.string()).readonly(),sourceId:z.string(),sourcePath:z.string()}).strict(),compiledWorkspaceResourceRootSchema=z.object({contentHash:z.string().optional(),logicalPath:z.string(),rootEntries:z.array(z.string()).readonly()}).strict(),compiledConnectionDefinitionSchema=z.object({connectionName:z.string(),description:z.string(),exportName:z.string().optional(),logicalPath:z.string(),sourceId:z.string(),sourceKind:z.literal(`module`),url:z.string(),vercelConnect:z.object({connector:z.string()}).strict().optional()}).strict(),compiledToolDefinitionSchema=z.object({description:z.string(),exportName:z.string().optional(),inputSchema:jsonObjectSchema.nullable(),logicalPath:z.string(),name:z.string(),outputSchema:jsonObjectSchema.optional(),sourceId:z.string(),sourceKind:z.literal(`module`)}).strict(),compiledDynamicToolDefinitionSchema=z.object({eventNames:z.array(z.string()).readonly(),exportName:z.string().optional(),logicalPath:z.string(),slug:z.string(),sourceId:z.string(),sourceKind:z.literal(`module`)}).strict(),compiledDynamicSkillDefinitionSchema=z.object({eventNames:z.array(z.string()).readonly(),exportName:z.string().optional(),logicalPath:z.string(),slug:z.string(),sourceId:z.string(),sourceKind:z.literal(`module`)}).strict(),compiledDynamicInstructionsDefinitionSchema=z.object({eventNames:z.array(z.string()).readonly(),exportName:z.string().optional(),logicalPath:z.string(),slug:z.string(),sourceId:z.string(),sourceKind:z.literal(`module`)}).strict(),compiledHookDefinitionSchema=z.object({exportName:z.string().optional(),logicalPath:z.string(),slug:z.string(),sourceId:z.string(),sourceKind:z.literal(`module`)}).strict(),compiledAgentNodeManifestSchema=z.object({agentRoot:z.string(),appRoot:z.string(),channels:z.array(compiledChannelEntrySchema),config:compiledAgentConfigSchema,connections:z.array(compiledConnectionDefinitionSchema),diagnosticsSummary:discoverDiagnosticsSummarySchema,disabledFrameworkTools:z.array(z.string()).readonly(),dynamicInstructions:z.array(compiledDynamicInstructionsDefinitionSchema).default([]),dynamicSkills:z.array(compiledDynamicSkillDefinitionSchema).default([]),dynamicTools:z.array(compiledDynamicToolDefinitionSchema).default([]),hooks:z.array(compiledHookDefinitionSchema),sandbox:compiledSandboxDefinitionSchema.nullable(),sandboxWorkspaces:z.array(compiledSandboxWorkspaceSchema),schedules:z.array(compiledScheduleDefinitionSchema),remoteAgents:z.array(compiledRemoteAgentNodeSchema),skills:z.array(compiledSkillSourceSchema).readonly(),instructions:compiledInstructionsSchema.optional(),tools:z.array(compiledToolDefinitionSchema),workspaceResourceRoot:compiledWorkspaceResourceRootSchema}).strict(),compiledSubagentNodeSchema=z.object({agent:compiledAgentNodeManifestSchema,description:z.string(),entryPath:z.string(),logicalPath:z.string(),name:z.string(),nodeId:z.string(),rootPath:z.string(),sourceId:z.string(),sourceKind:z.literal(`module`),exportName:z.string().optional()}).strict(),compiledSubagentEdgeSchema=z.object({childNodeId:z.string(),parentNodeId:z.string()}).strict(),compiledAgentManifestSchema=z.object({agentRoot:z.string(),appRoot:z.string(),channels:z.array(compiledChannelEntrySchema),config:compiledAgentConfigSchema,connections:z.array(compiledConnectionDefinitionSchema),diagnosticsSummary:discoverDiagnosticsSummarySchema,disabledFrameworkTools:z.array(z.string()).readonly(),dynamicInstructions:z.array(compiledDynamicInstructionsDefinitionSchema).default([]),dynamicSkills:z.array(compiledDynamicSkillDefinitionSchema).default([]),dynamicTools:z.array(compiledDynamicToolDefinitionSchema).default([]),hooks:z.array(compiledHookDefinitionSchema),kind:z.literal(COMPILED_AGENT_MANIFEST_KIND),remoteAgents:z.array(compiledRemoteAgentNodeSchema),sandbox:compiledSandboxDefinitionSchema.nullable(),sandboxWorkspaces:z.array(compiledSandboxWorkspaceSchema),schedules:z.array(compiledScheduleDefinitionSchema),skills:z.array(compiledSkillSourceSchema).readonly(),subagentEdges:z.array(compiledSubagentEdgeSchema),subagents:z.array(compiledSubagentNodeSchema),instructions:compiledInstructionsSchema.optional(),tools:z.array(compiledToolDefinitionSchema),version:z.literal(26),workspaceResourceRoot:compiledWorkspaceResourceRootSchema}).strict();function createCompiledAgentNodeManifest(e){let t={agentRoot:e.agentRoot,appRoot:e.appRoot,channels:[...e.channels??[]],connections:[...e.connections??[]],config:{build:e.config.build===void 0?void 0:{externalDependencies:e.config.build.externalDependencies===void 0?void 0:[...e.config.build.externalDependencies]},compaction:{model:e.config.compaction?.model===void 0?void 0:cloneCompiledRuntimeModelReference(e.config.compaction.model),thresholdPercent:e.config.compaction?.thresholdPercent},description:e.config.description,experimental:e.config.experimental===void 0?void 0:{codeMode:e.config.experimental.codeMode},model:cloneCompiledRuntimeModelReference(e.config.model),name:e.config.name,outputSchema:e.config.outputSchema,source:e.config.source===void 0?void 0:{...e.config.source}},diagnosticsSummary:e.diagnosticsSummary??{errors:0,warnings:0},disabledFrameworkTools:[...e.disabledFrameworkTools??[]],dynamicInstructions:[...e.dynamicInstructions??[]],dynamicSkills:[...e.dynamicSkills??[]],dynamicTools:[...e.dynamicTools??[]],hooks:[...e.hooks??[]],remoteAgents:[...e.remoteAgents??[]],sandbox:e.sandbox??null,sandboxWorkspaces:[...e.sandboxWorkspaces??[]],schedules:[...e.schedules??[]],skills:[...e.skills??[]],tools:[...e.tools??[]],workspaceResourceRoot:e.workspaceResourceRoot??{logicalPath:``,rootEntries:deriveResourceRootEntries({sandboxWorkspaces:e.sandboxWorkspaces,skills:e.skills})}};return e.instructions!==void 0&&(t.instructions=e.instructions),t}function deriveResourceRootEntries(e){let t=new Set;(e.skills??[]).length>0&&t.add(`skills/`);for(let n of e.sandboxWorkspaces??[])for(let e of n.rootEntries)t.add(e);return[...t].sort((e,t)=>e.localeCompare(t))}function createCompiledSubagentNodeId(e,t){return e===`__root__`?t:`${e}::${t}`}function createCompiledAgentManifest(e){return{...createCompiledAgentNodeManifest(e),kind:COMPILED_AGENT_MANIFEST_KIND,subagentEdges:[...e.subagentEdges??[]],subagents:[...e.subagents??[]],version:26}}function cloneCompiledRuntimeModelReference(e){return e.contextWindowTokens===void 0&&e.source===void 0&&e.providerOptions===void 0?{id:e.id}:e.source===void 0?e.providerOptions===void 0?{contextWindowTokens:e.contextWindowTokens,id:e.id}:{contextWindowTokens:e.contextWindowTokens,id:e.id,providerOptions:{...e.providerOptions}}:e.contextWindowTokens===void 0&&e.providerOptions===void 0?{id:e.id,source:{...e.source}}:{contextWindowTokens:e.contextWindowTokens,id:e.id,providerOptions:e.providerOptions===void 0?void 0:{...e.providerOptions},source:{...e.source}}}export{COMPILED_AGENT_MANIFEST_KIND,COMPILED_AGENT_MANIFEST_VERSION,ROOT_COMPILED_AGENT_NODE_ID,compiledAgentManifestSchema,createCompiledAgentManifest,createCompiledAgentNodeManifest,createCompiledSubagentNodeId,deriveResourceRootEntries};
1
+ import{z}from"#compiled/zod/index.js";import{discoverDiagnosticsSummarySchema}from"#discover/diagnostics.js";import{compiledRemoteAgentNodeSchema}from"#compiler/remote-agent-node.js";import{jsonObjectSchema}from"#shared/json-schemas.js";const COMPILED_AGENT_MANIFEST_KIND=`ash-agent-compiled-manifest`,ROOT_COMPILED_AGENT_NODE_ID=`__root__`,COMPILED_AGENT_MANIFEST_VERSION=27,moduleSourceRefSchema=z.object({exportName:z.string().optional(),sourceKind:z.literal(`module`),logicalPath:z.string(),sourceId:z.string()}).strict(),channelMethodSchema=z.union([z.literal(`GET`),z.literal(`POST`),z.literal(`PUT`),z.literal(`PATCH`),z.literal(`DELETE`)]),compiledChannelDefinitionSchema=z.object({kind:z.literal(`channel`),name:z.string(),logicalPath:z.string(),method:channelMethodSchema,urlPath:z.string(),sourceId:z.string(),sourceKind:z.literal(`module`),exportName:z.string().optional(),adapterKind:z.string().optional()}).strict(),disabledCompiledChannelEntrySchema=z.object({kind:z.literal(`disabled`),name:z.string(),logicalPath:z.string()}).strict(),compiledChannelEntrySchema=z.union([compiledChannelDefinitionSchema,disabledCompiledChannelEntrySchema]),compiledRuntimeModelReferenceSchema=z.object({contextWindowTokens:z.number().int().positive().optional(),id:z.string(),source:moduleSourceRefSchema.optional(),providerOptions:z.record(z.string(),jsonObjectSchema).optional()}).strict(),compiledAgentBuildDefinitionSchema=z.object({externalDependencies:z.array(z.string()).optional()}).strict(),compiledAgentCompactionDefinitionSchema=z.object({model:compiledRuntimeModelReferenceSchema.optional(),thresholdPercent:z.number().finite().min(0).max(1).optional()}).strict(),compiledAgentConfigSchema=z.object({build:compiledAgentBuildDefinitionSchema.optional(),compaction:compiledAgentCompactionDefinitionSchema.optional(),description:z.string().optional(),experimental:z.object({codeMode:z.boolean().optional()}).strict().optional(),model:compiledRuntimeModelReferenceSchema,name:z.string(),outputSchema:jsonObjectSchema.optional(),source:moduleSourceRefSchema.optional()}).strict(),compiledInstructionsSchema=z.object({name:z.string(),logicalPath:z.string(),markdown:z.string(),sourceId:z.string(),sourceKind:z.union([z.literal(`markdown`),z.literal(`module`)])}).strict(),compiledSkillBaseFields={name:z.string(),description:z.string(),license:z.string().optional(),markdown:z.string(),metadata:z.record(z.string(),z.string()).optional(),sourceId:z.string(),logicalPath:z.string()},compiledSkillSourceSchema=z.discriminatedUnion(`sourceKind`,[z.object({...compiledSkillBaseFields,sourceKind:z.literal(`markdown`)}).strict(),z.object({...compiledSkillBaseFields,sourceKind:z.literal(`module`),exportName:z.string().optional()}).strict(),z.object({...compiledSkillBaseFields,sourceKind:z.literal(`skill-package`),skillId:z.string(),skillFilePath:z.string(),rootPath:z.string(),assetsPath:z.string().optional(),referencesPath:z.string().optional(),scriptsPath:z.string().optional()}).strict()]),compiledScheduleDefinitionSchema=z.object({cron:z.string(),hasRun:z.boolean(),name:z.string(),logicalPath:z.string(),markdown:z.string().optional(),sourceId:z.string(),sourceKind:z.union([z.literal(`markdown`),z.literal(`module`)])}).strict(),compiledSandboxDefinitionSchema=z.object({description:z.string().optional(),exportName:z.string().optional(),logicalPath:z.string(),revalidationKey:z.string().optional(),sourceHash:z.string(),sourceId:z.string(),sourceKind:z.literal(`module`)}).strict(),compiledSandboxWorkspaceSchema=z.object({logicalPath:z.string(),rootEntries:z.array(z.string()).readonly(),sourceId:z.string(),sourcePath:z.string()}).strict(),compiledWorkspaceResourceRootSchema=z.object({contentHash:z.string().optional(),logicalPath:z.string(),rootEntries:z.array(z.string()).readonly()}).strict(),compiledConnectionDefinitionSchema=z.object({connectionName:z.string(),description:z.string(),exportName:z.string().optional(),logicalPath:z.string(),sourceId:z.string(),sourceKind:z.literal(`module`),url:z.string(),vercelConnect:z.object({connector:z.string()}).strict().optional()}).strict(),compiledToolDefinitionSchema=z.object({description:z.string(),exportName:z.string().optional(),inputSchema:jsonObjectSchema.nullable(),logicalPath:z.string(),name:z.string(),outputSchema:jsonObjectSchema.optional(),sourceId:z.string(),sourceKind:z.literal(`module`)}).strict(),compiledDynamicToolDefinitionSchema=z.object({eventNames:z.array(z.string()).readonly(),exportName:z.string().optional(),logicalPath:z.string(),slug:z.string(),sourceId:z.string(),sourceKind:z.literal(`module`)}).strict(),compiledDynamicSkillDefinitionSchema=z.object({eventNames:z.array(z.string()).readonly(),exportName:z.string().optional(),logicalPath:z.string(),slug:z.string(),sourceId:z.string(),sourceKind:z.literal(`module`)}).strict(),compiledDynamicInstructionsDefinitionSchema=z.object({eventNames:z.array(z.string()).readonly(),exportName:z.string().optional(),logicalPath:z.string(),slug:z.string(),sourceId:z.string(),sourceKind:z.literal(`module`)}).strict(),compiledHookDefinitionSchema=z.object({exportName:z.string().optional(),logicalPath:z.string(),slug:z.string(),sourceId:z.string(),sourceKind:z.literal(`module`)}).strict(),compiledAgentNodeManifestSchema=z.object({agentRoot:z.string(),appRoot:z.string(),channels:z.array(compiledChannelEntrySchema),config:compiledAgentConfigSchema,connections:z.array(compiledConnectionDefinitionSchema),diagnosticsSummary:discoverDiagnosticsSummarySchema,disabledFrameworkTools:z.array(z.string()).readonly(),dynamicInstructions:z.array(compiledDynamicInstructionsDefinitionSchema).default([]),dynamicSkills:z.array(compiledDynamicSkillDefinitionSchema).default([]),dynamicTools:z.array(compiledDynamicToolDefinitionSchema).default([]),hooks:z.array(compiledHookDefinitionSchema),sandbox:compiledSandboxDefinitionSchema.nullable(),sandboxWorkspaces:z.array(compiledSandboxWorkspaceSchema),schedules:z.array(compiledScheduleDefinitionSchema),remoteAgents:z.array(compiledRemoteAgentNodeSchema),skills:z.array(compiledSkillSourceSchema).readonly(),instructions:compiledInstructionsSchema.optional(),tools:z.array(compiledToolDefinitionSchema),workspaceResourceRoot:compiledWorkspaceResourceRootSchema}).strict(),compiledSubagentNodeSchema=z.object({agent:compiledAgentNodeManifestSchema,description:z.string(),entryPath:z.string(),logicalPath:z.string(),name:z.string(),nodeId:z.string(),rootPath:z.string(),sourceId:z.string(),sourceKind:z.literal(`module`),exportName:z.string().optional()}).strict(),compiledSubagentEdgeSchema=z.object({childNodeId:z.string(),parentNodeId:z.string()}).strict(),compiledAgentManifestSchema=z.object({agentRoot:z.string(),appRoot:z.string(),channels:z.array(compiledChannelEntrySchema),config:compiledAgentConfigSchema,connections:z.array(compiledConnectionDefinitionSchema),diagnosticsSummary:discoverDiagnosticsSummarySchema,disabledFrameworkTools:z.array(z.string()).readonly(),dynamicInstructions:z.array(compiledDynamicInstructionsDefinitionSchema).default([]),dynamicSkills:z.array(compiledDynamicSkillDefinitionSchema).default([]),dynamicTools:z.array(compiledDynamicToolDefinitionSchema).default([]),hooks:z.array(compiledHookDefinitionSchema),kind:z.literal(COMPILED_AGENT_MANIFEST_KIND),remoteAgents:z.array(compiledRemoteAgentNodeSchema),sandbox:compiledSandboxDefinitionSchema.nullable(),sandboxWorkspaces:z.array(compiledSandboxWorkspaceSchema),schedules:z.array(compiledScheduleDefinitionSchema),skills:z.array(compiledSkillSourceSchema).readonly(),subagentEdges:z.array(compiledSubagentEdgeSchema),subagents:z.array(compiledSubagentNodeSchema),instructions:compiledInstructionsSchema.optional(),tools:z.array(compiledToolDefinitionSchema),version:z.literal(27),workspaceResourceRoot:compiledWorkspaceResourceRootSchema}).strict();function createCompiledAgentNodeManifest(e){let t={agentRoot:e.agentRoot,appRoot:e.appRoot,channels:[...e.channels??[]],connections:[...e.connections??[]],config:{build:e.config.build===void 0?void 0:{externalDependencies:e.config.build.externalDependencies===void 0?void 0:[...e.config.build.externalDependencies]},compaction:{model:e.config.compaction?.model===void 0?void 0:cloneCompiledRuntimeModelReference(e.config.compaction.model),thresholdPercent:e.config.compaction?.thresholdPercent},description:e.config.description,experimental:e.config.experimental===void 0?void 0:{codeMode:e.config.experimental.codeMode},model:cloneCompiledRuntimeModelReference(e.config.model),name:e.config.name,outputSchema:e.config.outputSchema,source:e.config.source===void 0?void 0:{...e.config.source}},diagnosticsSummary:e.diagnosticsSummary??{errors:0,warnings:0},disabledFrameworkTools:[...e.disabledFrameworkTools??[]],dynamicInstructions:[...e.dynamicInstructions??[]],dynamicSkills:[...e.dynamicSkills??[]],dynamicTools:[...e.dynamicTools??[]],hooks:[...e.hooks??[]],remoteAgents:[...e.remoteAgents??[]],sandbox:e.sandbox??null,sandboxWorkspaces:[...e.sandboxWorkspaces??[]],schedules:[...e.schedules??[]],skills:[...e.skills??[]],tools:[...e.tools??[]],workspaceResourceRoot:e.workspaceResourceRoot??{logicalPath:``,rootEntries:deriveResourceRootEntries({sandboxWorkspaces:e.sandboxWorkspaces,skills:e.skills})}};return e.instructions!==void 0&&(t.instructions=e.instructions),t}function deriveResourceRootEntries(e){let t=new Set;(e.skills??[]).length>0&&t.add(`skills/`);for(let n of e.sandboxWorkspaces??[])for(let e of n.rootEntries)t.add(e);return[...t].sort((e,t)=>e.localeCompare(t))}function createCompiledSubagentNodeId(e,t){return e===`__root__`?t:`${e}::${t}`}function createCompiledAgentManifest(e){return{...createCompiledAgentNodeManifest(e),kind:COMPILED_AGENT_MANIFEST_KIND,subagentEdges:[...e.subagentEdges??[]],subagents:[...e.subagents??[]],version:27}}function cloneCompiledRuntimeModelReference(e){return e.contextWindowTokens===void 0&&e.source===void 0&&e.providerOptions===void 0?{id:e.id}:e.source===void 0?e.providerOptions===void 0?{contextWindowTokens:e.contextWindowTokens,id:e.id}:{contextWindowTokens:e.contextWindowTokens,id:e.id,providerOptions:{...e.providerOptions}}:e.contextWindowTokens===void 0&&e.providerOptions===void 0?{id:e.id,source:{...e.source}}:{contextWindowTokens:e.contextWindowTokens,id:e.id,providerOptions:e.providerOptions===void 0?void 0:{...e.providerOptions},source:{...e.source}}}export{COMPILED_AGENT_MANIFEST_KIND,COMPILED_AGENT_MANIFEST_VERSION,ROOT_COMPILED_AGENT_NODE_ID,compiledAgentManifestSchema,createCompiledAgentManifest,createCompiledAgentNodeManifest,createCompiledSubagentNodeId,deriveResourceRootEntries};
@@ -1 +1 @@
1
- import{loadModuleBackedDefinition}from"#compiler/normalize-helpers.js";import{normalizeSandboxDefinition}from"#internal/authored-definition/sandbox.js";async function compileSandboxDefinition(e,t){return{description:normalizeSandboxDefinition(await loadModuleBackedDefinition({agentRoot:e,kind:`sandbox`,source:t}),`Expected the sandbox export "${t.exportName??`default`}" from "${t.logicalPath}" to match the public Ash shape.`).description,exportName:t.exportName,logicalPath:t.logicalPath,sourceId:t.sourceId,sourceKind:`module`}}export{compileSandboxDefinition};
1
+ import{join}from"node:path";import{readFile}from"node:fs/promises";import{toErrorMessage}from"#shared/errors.js";import{createHash}from"node:crypto";import{loadModuleBackedDefinition}from"#compiler/normalize-helpers.js";import{normalizeSandboxDefinition}from"#internal/authored-definition/sandbox.js";async function compileSandboxDefinition(e,t){let n=`Expected the sandbox export "${t.exportName??`default`}" from "${t.logicalPath}" to match the public Ash shape.`,r=normalizeSandboxDefinition(await loadModuleBackedDefinition({agentRoot:e,kind:`sandbox`,source:t}),n),o=r.revalidationKey===void 0?void 0:await resolveSandboxRevalidationKey({message:n,revalidationKey:r.revalidationKey,source:t});return{description:r.description,exportName:t.exportName,logicalPath:t.logicalPath,revalidationKey:o,sourceHash:await resolveSandboxSourceHash(e,t),sourceId:t.sourceId,sourceKind:`module`}}async function resolveSandboxRevalidationKey(e){let t;try{t=await e.revalidationKey()}catch(t){throw Error(`${e.message} Failed to execute the "revalidationKey" function from "${e.source.logicalPath}": ${toErrorMessage(t)}`)}if(typeof t!=`string`)throw Error(`${e.message} The "revalidationKey" function must return a string.`);if(t.trim().length===0)throw Error(`${e.message} The "revalidationKey" function must return a non-empty string.`);return t}async function resolveSandboxSourceHash(n,i){let a=await readFile(join(n,i.logicalPath));return createHash(`sha256`).update(a).digest(`hex`)}export{compileSandboxDefinition};