Client-side pair to the Session primitive server PR (TRI-8627).
Run-scoped streams.pipe / streams.input are untouched.
@trigger.dev/core ApiClient
- createSession / retrieveSession / updateSession / closeSession —
zodfetch against /api/v1/sessions control plane
- listSessions — CursorPagePromise<SessionItem>, follows the runs/waitpoints
convention (page[size], page[after], page[before] + filter[*])
- initializeSessionStream — PUT /realtime/v1/sessions/:session/:io,
returns S2 creds in headers (feeds StreamsWriterV2 directly)
- appendToSessionStream — POST …/append
- subscribeToSessionStream — reuses SSEStreamSubscription for SSE
subscribes (auto-retry, Last-Event-ID resume, abort propagation), so
session subscribers get the exact same semantics as runs.fetchStream.
Returns AsyncIterableStream<T>.
@trigger.dev/sdk sessions namespace
- sessions.create / retrieve / update / close / list — wraps the ApiClient
with the standard tracer + accessoryAttributes + mergeRequestOptions.
Returns ApiPromise / CursorPagePromise.
- sessions.open(id) returns a SessionHandle with .out and .in
SessionChannels. Each channel exposes append / send / subscribe /
initialize. The handle is polymorphic on friendlyId or externalId.
- auth.ts adds the `sessions` permission on PublicTokenPermissionProperties
so auth.createPublicToken({ read: { sessions: ["session_abc"] } }) works.
Reference
- references/hello-world/src/trigger/sessionsSmoke.ts — idempotent
Trigger.dev task that exercises every code path (control-plane CRUD,
polymorphic lookup, list with tag/type/status/externalId filters, cursor
pagination, out.initialize + append + subscribe SSE round-trip, in.send,
close + idempotent re-close). Trigger via
mcp__trigger__trigger_task(taskId: "sessions-smoke").
Verified live against the local webapp (project hello-world): 10/10
steps pass end-to-end, S2 round-trip returns appended chunks through the
shared SSEStreamSubscription pipeline.
Two correctness fixes caught during end-to-end validation:
1. Resolve skill.path relative to the file that called skills.define(),
not the project root. The resource catalog already captures filePath
on each SkillManifest — use it to compute the source folder.
2. In dev, copy skill bundles to {workingDir}/.trigger/skills/ (where
the worker's cwd resolves). In deploy, keep copying to
{outputPath}/.trigger/skills/ so the Dockerfile COPY . /app lands
them at /app/.trigger/skills/.
Also upgrade the skill-discovery failure log from debug to warn so
config mistakes surface in the dev console instead of disappearing
silently.
Finally, update the ai-chat reference's aiChat.run() to pass chatTools
through chat.toStreamTextOptions({ tools: chatTools }) instead of
spreading them after — the auto-injected loadSkill/readFile/bash tools
would otherwise get overwritten by the explicit tools: chatTools key.
Full Phase 1 for the new ai.skills primitive — SDK + CLI, no
backend. Developer-authored folders (SKILL.md + scripts/references/
assets) discovered at build time, bundled into the deploy image at
/app/.trigger/skills/{id}/, and auto-wired into streamText at
runtime via the loadSkill/readFile/bash tools.
Adds a time-utils example skill to the ai-chat reference: two bash
scripts (now.sh, add.sh) plus a timezones.txt cheat-sheet, wired
into the aiChat agent via chat.skills.set() in onChatStart and
onPreload. Exercises the full pipeline end-to-end.
Changeset: patch bump for @trigger.dev/sdk, @trigger.dev/core,
@trigger.dev/build, trigger.dev.
New first-class bundling step (not a build extension): after esbuild
produces the worker bundle, fork the indexer locally to discover
skills registered via ai.skills.define(), validate each skill's
SKILL.md, and copy the folder into {outputPath}/.trigger/skills/{id}/.
Hooks into both buildWorker() (deploy) and devSession's updateBundle
(dev) right after createBuildManifestFromBundle and before the
extension onBuildComplete hook, so extensions can observe the
annotated manifest.skills. The existing Dockerfile COPY picks up the
new .trigger/skills/ subdirectory without changes.
Also: managed-index-worker and dev-index-worker now emit
resourceCatalog.listSkillManifests() in the INDEX_COMPLETE message
so downstream stages can see the skill list.
Part 3/3 of Phase 1 for the new ai.skills primitive.
skills.define({ id, path }) registers a skill with the resource
catalog and returns a SkillHandle. SkillHandle.local() reads the
bundled SKILL.md from ./.trigger/skills/{id}/ at runtime, parses
frontmatter, and returns a ResolvedSkill ready for chat.skills.set().
chat.skills.set([...]) stores resolved skills for the current run.
chat.toStreamTextOptions() auto-injects the skills preamble into the
system prompt and merges three tools — loadSkill, readFile, bash —
scoped per-skill with path-traversal guards and output caps (64 KB
stdout/stderr, 1 MB readFile). Bash executes in the worker container
with the turn's abort signal, no sandbox — skills are developer code.
Shared packages/build/src/internal/copyFiles.ts extracted from the
additionalFiles extension so the CLI's built-in skill bundler and the
existing extension share one glob + copy implementation.
Part 2/3 of Phase 1 for the new ai.skills primitive.
SkillMetadata + SkillManifest zod schemas alongside the existing
task/prompt ones. registerSkillMetadata / listSkillManifests /
getSkillManifest on ResourceCatalog (both Standard and Noop), wired
through the ResourceCatalogAPI facade. BuildManifest + WorkerManifest
gain an optional `skills` array so the built-in CLI bundler can
annotate discoveries and the indexer can report them.
Part 1/3 of Phase 1 for the new ai.skills primitive.
run() is invoked with trigger: "action" after onAction processes a
typed action, but the type previously omitted it. Adding it lets
users cleanly short-circuit the LLM call for actions that don't
need a response (e.g. user-initiated compaction):
if (trigger === "action") return;
Exit the loop after the current turn completes without the
upgrade-required signal that chat.requestUpgrade() sends. Use when an
agent finishes its work on its own terms — one-shot responses, goal
achieved, budget exhausted — instead of waiting idle for the next
user message. Callable from run(), chat.defer(),
onBeforeTurnComplete, or onTurnComplete.
Resolves TRI-8391.
Surface the AI SDK's FinishReason on TurnCompleteEvent and
BeforeTurnCompleteEvent. Gives hooks a clean signal for distinguishing
a normal turn end from one paused on a pending tool call (HITL flows
like ask_user). Undefined for manual pipeChat() or aborted streams.
Drives a chat.agent definition through real turns offline — send
messages, actions, and stop signals; inspect captured chunks; assert
on hook order. Pre-seed dependencies via setupLocals so hooks read
test instances (DB clients, stubs) via locals.get() instead of
leaking through untrusted clientData.
Adds ai-chat reference tests exercising the harness across basic
flow, onValidateMessages, hydrateMessages, and actions.
In-memory managers for locals, lifecycle hooks, runtime, input streams,
and realtime streams, plus a mock TaskContext. Lets task code be driven
end-to-end without hitting the Trigger.dev runtime — send data into
input streams, inspect chunks written to output streams, and pre-seed
locals for dependency injection.
Add isStreaming flag to session state — set true when streaming starts,
false on turn-complete. reconnectToStream returns null immediately when
isStreaming is false, so resume: true is safe to pass unconditionally.
Backend-controlled message history via hydrateMessages hook — loads from
DB on every turn, replacing the linear accumulator. Imperative chat.history
API (rollbackTo, remove, replace, slice) for modifying history from any
hook or run(). Custom actions via actionSchema + onAction — typed actions
sent through transport.sendAction() that wake the agent, modify state,
then trigger run(). Adds sendAction to both TriggerChatTransport and
AgentChat.
- Pass TaskRunContext through chat lifecycle events, CompactedEvent, and
ChatTaskRunPayload; use ctx.run.id for chat access tokens
- Export TaskRunContext from @trigger.dev/sdk
- ai-chat reference: executeCode via E2B, code-sandbox module, warm on
onTurnStart, dispose on token onWait and onComplete; chat.local run id
- Docs: database persistence + code sandbox pattern pages; reference and
backend updates for ctx; chat.defer anchor; navigation
- Assert toolFromTask return as AI SDK ToolSet-compatible; import ToolSet from ai
- Add changesets for @trigger.dev/sdk
- ai-chat reference: chat-tools module, registry language model helper, streamText cleanup
- Prisma migration removing user tool demo; demo docs and next config tweaks
- chat.inject(): queue model messages from background work for injection
at the next prepareStep boundary or before the next turn's run()
- Deferred work from onTurnComplete no longer blocks waiting for next message
- Background queue persists across turns (not reset) so deferred work from
onTurnComplete can inject into the next turn
- Reference app: self-review pattern using generateObject + chat.inject()
- Hide transient data-turn-status and data-background-context-injected parts in UI
Replace triggerAndWait with triggerAndSubscribe in ai.tool to fix:
- Parallel tool calls (no more preventMultipleWaits errors)
- Stop signal while suspended (parent stays alive, child gets cancelled)
New task.triggerAndSubscribe() method: trigger + subscribeToRun in a
single span, with abort signal support and cancelOnAbort option.
Convert deepResearch to a schemaTask + ai.tool in the reference app.
refs TRI-7986