Skip to content
Merged
3 changes: 3 additions & 0 deletions docs/contributing/cloudflare-agents-sdk.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,9 @@ Use this section when you want to answer "can Agents do X?" quickly.
- Run Cloudflare Workflows from an Agent (`this.runWorkflow(...)`) and handle
progress/completion callbacks. Docs:
https://developers.cloudflare.com/agents/api-reference/run-workflows/
- In Kody runtime code, use `workflows.create` for durable batch sweeps,
migrations, polling loops, or retryable work that may outlive execute's
timeout; inspect runs with `workflow_run_list`.

### LLM integration (bring your model/provider)

Expand Down
10 changes: 8 additions & 2 deletions docs/use/execute.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ helpers are runtime exports:
bound to a storage id
- use **`import { workflows } from 'kody:runtime'`** to queue Cloudflare
Workflows from execute calls, ad hoc jobs, package jobs, package
subscriptions, package services, and package exports. See
[Workflows](./workflows.md)
subscriptions, package services, and package exports. Prefer workflows for
durable batch sweeps, migrations, polling loops, retryable steps, or work that
may run longer than execute's timeout. See [Workflows](./workflows.md)
- use **`import { packageContext } from 'kody:runtime'`** inside saved package
code when you need package metadata; it is **`null`** for ad hoc execute calls
- use **`import { packages } from 'kody:runtime'`** inside saved package runtime
Expand Down Expand Up @@ -96,6 +97,11 @@ several capabilities or package exports, branch on results, and return the final
structured result. Split into multiple **execute** calls only when you need new
user input, confirmation, or a result that changes the plan.

Plain **execute** has a hard timeout (~90s by default). For multi-step or
long-running work (>~60s, batch sweeps, migrations, polling loops), use one
**execute** call to submit `workflows.create({ code, params })`, then inspect
progress with `workflow_run_list` instead of chaining many MCP tool calls.

To read field shapes while coding, use **search** with
**`entity: "{name}:capability"`** for builtin capability type definitions, or
inspect the relevant saved package with **`entity: "{kody_id}:package"`**.
Expand Down
40 changes: 25 additions & 15 deletions docs/use/workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,32 @@ Kody exposes Cloudflare Workflows through `kody:runtime` in every server-side
runtime context: `execute`, ad hoc scheduled jobs, saved package jobs, package
subscriptions, package exports, and package services.

Use workflows instead of plain `execute` for durable batch sweeps, migrations,
polling loops, retryable steps, or work that may run longer than execute's
timeout. The initial `execute` call should submit one `workflows.create`;
inspect that workflow later with `workflow_run_list`.

```ts
import { workflows } from 'kody:runtime'

export default async function main() {
export default async function main(input = {}) {
return await workflows.create({
runAt: new Date(Date.now() + 60_000).toISOString(),
idempotencyKey: `execute-smoke-${Date.now()}`,
code: 'export default async function main(p) { return { ok: true, p } }',
params: { greeting: 'hello' },
params: { greeting: input.greeting ?? 'hello' },
})
}
```

Check status from a later MCP call:

```ts
import { kody } from 'kody:runtime'

export default async function main() {
return await kody.workflow_run_list({ limit: 10 })
}
```

`workflows.create` accepts one durable workflow request with two source shapes:

- `code`: a complete ESM module string with a default export. Kody runs it later
Expand All @@ -25,20 +38,17 @@ export default async function main() {
Kody resolves `packageId` from `packageContext`. Outside a package runtime,
pass `packageId` explicitly.

Both shapes require:
Both shapes accept:

- `runAt`: ISO date-time string or `Date`
- `idempotencyKey`: caller-chosen dedupe key
- `runAt`: optional ISO date-time string or `Date`; defaults to now
- `idempotencyKey`: optional caller-chosen dedupe key; omitted keys create a
fresh run
- `params`: optional JSON object passed to the workflow body

Calling `create` again with the same dedupe fields returns the existing workflow
instead of starting a duplicate. For inline code, repeat the same signed-in
user, source type (`code`), workflow name (or the default `inline-code`),
`idempotencyKey`, and `runAt`. For package exports, repeat the same signed-in
user, package, workflow name (or export name fallback), `idempotencyKey`, and
`runAt`. For example, retrying `code` with `idempotencyKey: "execute-smoke-123"`
and `runAt: "2026-05-08T18:00:00.000Z"` returns the same workflow only when
those dedupe fields are repeated. Kody enforces a per-user concurrent workflow
Calling `create` again with the same explicit `idempotencyKey` and matching
workflow identity for the same user returns the existing workflow instead of
starting a duplicate. Choose keys that include the logical job identity, for
example `storage-sweep:2026-05-08`. Kody enforces a per-user concurrent workflow
limit (default: 100); if the cap is reached, `workflows.create` returns a clear
quota error.

Expand Down
4 changes: 3 additions & 1 deletion packages/worker/src/mcp/capabilities/jobs/workflow-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const workflowListCapability = defineDomainCapability(
{
name: 'workflow_run_list',
description:
'List recent Cloudflare Workflow runs created through kody:runtime workflows.create, including source, status, and completion/error metadata.',
'List recent Cloudflare Workflow runs created through kody:runtime workflows.create for durable work that may outlive execute timeouts, including source, status, and completion/error metadata.',
keywords: [
'workflow',
'workflows',
Expand All @@ -47,6 +47,8 @@ export const workflowListCapability = defineDomainCapability(
'debug',
'status',
'background',
'timeout',
'migration',
],
readOnly: true,
idempotent: true,
Expand Down
4 changes: 2 additions & 2 deletions packages/worker/src/mcp/server-instructions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ Conventions
- \`job_update\`: update an existing scheduled job by id. Supports safe mutable fields such as name, code, params, schedule, timezone, enabled, and kill-switch state. Providing \`code\` publishes a new commit on the job's repo-backed source (the simplest way to change the job module for non-package jobs); the replacement must default export a function and read \`params\` from its first argument, not from \`kody:runtime\`.
- \`job_delete\`: delete one of the signed-in user's scheduled jobs by id.
- \`job_run_now\`: run an existing scheduled job immediately by id and return the updated job view plus execution result for debugging.
- \`workflow_run_list\`: inspect recent Cloudflare Workflow runs created through \`kody:runtime\` \`workflows.create\`, including inline-code and saved-package export workflows.
- \`workflow_run_list\`: inspect recent Cloudflare Workflow runs created through \`kody:runtime\` \`workflows.create\`; use it after durable long-running work such as batch sweeps, migrations, and polling loops.
- Package jobs are schedules owned by a package. For ad hoc work that is not tied to a package, use \`job_schedule\`. Package apps are optional UI surfaces declared by the package, and packages may also declare headless package services for long-lived background runtimes.
- Package apps can also use Kody-managed realtime websocket sessions through \`session_list\`, \`session_emit\`, and \`session_broadcast\`, and package jobs can emit to those sessions when running under the same package caller context.
- Package services are inspected and controlled through \`service_list\`, \`service_get\`, \`service_start\`, and \`service_stop\`.
Expand All @@ -97,7 +97,7 @@ search
- search({ entity: 'coding_guide_get:capability' })

execute
- Single ESM module string with a default export such as \`export default async function main(input = {}) { ... }\`; \`params\` are passed as the first argument. Import runtime APIs from \`kody:runtime\`. Example: \`import { kody, refreshAccessToken, createAuthenticatedFetch, oauthClientCredentials, secretHeaders, workflows } from 'kody:runtime'\`. Built-in capabilities returned by \`search\` are available through \`kody\`: use \`await kody.capability_id(input)\` for valid identifier names or \`await kody["capability-id"](input)\` for non-identifier ids. Remote connector capabilities are deliberately separate: use \`await kody.remote["name"].capability_name(input)\` (for example \`kody.remote["home"].set_pin({ pin })\`), never a flat \`kody.kind_instance_capability(...)\` call. \`workflows.create\` can queue either inline \`code\` or a saved-package \`exportName\` through the shared Workflow hub. Use \`secretHeaders.basic(...)\` or \`oauthClientCredentials(...)\` for client-credentials Basic Auth instead of asking users to precompute a Basic header. Prefer one \`execute\` when the plan is clear. Full rules for \`fetch\`, placeholders, \`secret_list\` / \`value_get\`, and \`x-kody-secret\`: see the \`execute\` tool description.
- Single ESM module string with a default export such as \`export default async function main(input = {}) { ... }\`; \`params\` are passed as the first argument. Import runtime APIs from \`kody:runtime\`. Example: \`import { kody, refreshAccessToken, createAuthenticatedFetch, oauthClientCredentials, secretHeaders, workflows } from 'kody:runtime'\`. Built-in capabilities returned by \`search\` are available through \`kody\`: use \`await kody.capability_id(input)\` for valid identifier names or \`await kody["capability-id"](input)\` for non-identifier ids. Remote connector capabilities are deliberately separate: use \`await kody.remote["name"].capability_name(input)\` (for example \`kody.remote["home"].set_pin({ pin })\`), never a flat \`kody.kind_instance_capability(...)\` call. \`workflows.create\` can queue inline \`code\` or a saved-package \`exportName\`; prefer it for durable, retryable, inspectable work that may outlive execute's timeout, while plain execute is for quick single operations. Use \`secretHeaders.basic(...)\` or \`oauthClientCredentials(...)\` for client-credentials Basic Auth instead of asking users to precompute a Basic header. Prefer one \`execute\` when the plan is clear. Full rules for \`fetch\`, placeholders, \`secret_list\` / \`value_get\`, and \`x-kody-secret\`: see the \`execute\` tool description.
- Cross-package imports use specifiers such as \`kody:@scope/my-package/export-name\`. For dynamic current-version calls inside package runtime code or authenticated execute calls, prefer \`packages.invokeChecked({ kodyId, exportName, params })\` or \`packages.check(...)\` followed by \`packages.invoke(check.invoke)\`; use static imports for library-like bundled snapshots. Saved package names must be scoped (\`@scope/<leaf>\`) and the leaf segment must match \`kody.id\`. Package jobs are owned by packages, ad hoc jobs can be scheduled with \`job_schedule\`, and package apps are optional package surfaces.
- Official how-to guides from the Kody repo: when creating or materially changing a package, call \`coding_guide_get\` with \`guide: "package_authoring"\`. If the package, package app, or workflow depends on a third-party integration, secrets, or OAuth, call \`guide: "integration_bootstrap"\` before building the dependent package. If unsure, \`search\` for this capability and load the right guide before implementing.
- Do not save or present an auth-dependent package as complete until \`search\` shows the required integration or secret reference exists and a minimal authenticated \`execute\` smoke test succeeds.
Expand Down
1 change: 1 addition & 0 deletions packages/worker/src/mcp/tools/execute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Sandbox surface:
- \`import { storage } from 'kody:runtime'\` for durable storage helpers on the bound \`storageId\`, including \`storage.sql(query, params?)\`. \`storage.sql\` returns \`{ columns, rows, rowCount, rowsRead, rowsWritten }\`; read query rows from \`.rows\`.
- \`import { refreshAccessToken, createAuthenticatedFetch, oauthClientCredentials, secretHeaders } from 'kody:runtime'\` for OAuth integrations and secret-derived auth headers. Integration \`name\` may be account-specific (e.g. \`google-personal\`, \`google-business\`); call \`integration_list\` first when the task involves a provider that may have multiple accounts connected. For APIs such as PayPal that require client-credentials Basic Auth, save the client id and client secret separately and use \`secretHeaders.basic({ usernameSecret: 'paypalClientId', passwordSecret: 'paypalClientSecret', scope: 'user' })\` in the Authorization header, or \`oauthClientCredentials(...)\` for the token request. Do not ask users to precompute or save a derived Basic header.
- \`import { workflows } from 'kody:runtime'\` for durable Cloudflare Workflows. \`workflows.create\` accepts either inline \`code\` or a saved-package \`exportName\`; use \`workflow_run_list\` to inspect recent runs.
- Execute has a hard timeout (~90s by default). For batch sweeps, migrations, polling loops, or work likely to run >~60s, submit one durable \`workflows.create({ code, params })\` from a single execute call instead of chaining many MCP round-trips.
- Optional \`params\` are passed as the first argument to the module default export. Prefer \`export default async function main(input = {}) { ... }\`; pass \`input\` to shared helpers explicitly.
- \`import { packageContext } from 'kody:runtime'\` in saved package code when you need package metadata; it is \`null\` for ad hoc execute calls.
- \`import { packages } from 'kody:runtime'\` exposes \`packages.check(...)\`, \`packages.invoke(...)\`, and \`packages.invokeChecked(...)\` in saved package runtime contexts and authenticated ad hoc execute calls. Prefer \`invokeChecked\` for dynamic current-version package calls unless you already called \`check\` and pass \`check.invoke\` to \`invoke\`.
Expand Down
Loading