Skip to content

feat(js): implemented defineAgent#5251

Draft
pavelgj wants to merge 29 commits into
pj/remove-chatfrom
pj/session-flow-take-2
Draft

feat(js): implemented defineAgent#5251
pavelgj wants to merge 29 commits into
pj/remove-chatfrom
pj/session-flow-take-2

Conversation

@pavelgj
Copy link
Copy Markdown
Member

@pavelgj pavelgj commented May 6, 2026

Parent PR: #5248

pavelgj added 15 commits May 6, 2026 19:24
review

feedback
Add a new test case in `session-flow_test.ts` to ensure that session flows correctly handle native tool interrupts and can successfully resume using the provided tool responses. Additionally, update the agents testapp index to import, log, and export the new `interrupt-agent` for further testing and validation of interrupt flows.
Change `abort()` to return the snapshot's previous status before
setting it to 'aborted', or `undefined` if the snapshot was not
found. This allows callers to distinguish between aborting a
pending, done, or non-existent session.

Update action output schema from `z.void()` to `z.string().optional()`
and add tests covering all abort return value scenarios.
Wrap the flow function invocation in `runWithSession` to ensure
the session is available in the async context during execution.
This imports `runWithSession` from `session.js` and applies it
around the `fn(runner, ...)` call in `defineSessionFlow`.
Add AgentInit, AgentInput, and AgentOutput type exports from
@genkit-ai/ai to the beta public API surface.
Wrap the turn execution logic in `SessionRunner.runTurn` with a
`run()` call to produce proper tracing spans for each turn. This
improves observability by labeling each turn as `runTurn-{index}`.

Also includes formatting cleanups and updated tests.
Expose metadata on custom agent actions indicating whether state is
managed on the server or client, and whether the agent supports
abort via `onSnapshotStateChange`. This allows consumers to inspect
agent capabilities without needing access to the original config.
Introduce a new 'agent-abort' action type to distinguish abort actions
from regular agent actions. Previously, the abort action was registered
with actionType 'agent', which made it indistinguishable from normal
agent actions in the registry.
Introduce a `ClientStateTransform` type and `clientStateTransform`
config option for `defineCustomAgent`, `definePromptAgent`, and
`defineAgent`. When provided, the transform is applied to session
state before it is returned to the client (in `AgentOutput.state`,
`getSnapshotData`, etc.), allowing agents to redact sensitive fields
or reshape state for external consumers.
The `getSnapshotDataAction` and `abortAgentAction` no longer append
`__getSnapshotData` and `__abort` suffixes to `config.name`. These
actions already have distinct `actionType` values (`agent-snapshot`
and `agent-abort`), making the suffixes unnecessary for disambiguation.
Refactor prompt agent to pass session history into the prompt's
render() call instead of blindly appending it after rendered messages.
This lets prompts (e.g. dotprompt {{history}}) control where history
is placed. Tag history and preamble messages separately so prompt
template messages are correctly stripped from stored history after
generation while preserving tool-loop and model response messages.

Add comprehensive tests covering system-only, system+user, and
multi-turn prompt rendering to verify preamble messages don't
accumulate in stored history across turns.
@github-actions github-actions Bot added docs Improvements or additions to documentation js config root labels May 6, 2026
@pavelgj pavelgj changed the title Pj/session flow take 2 feat(js): implemented defineAgent May 6, 2026
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a unified Agent primitive for Genkit JS/TS, replacing the existing beta Session and SessionStore APIs with a snapshot-based model to support multi-turn conversations and state persistence. The changes include the addition of a SessionRunner for execution, new Agent definition APIs, and a file-system backed store. Review feedback identified several critical and medium-severity issues: the hardcoding of prompt inputs, a breach of encapsulation when accessing internal prompt properties, Node.js version compatibility concerns regarding crypto.randomUUID(), a lack of error handling robustness in the new EventEmitter utility, and the use of permissive z.any() types in action schemas which compromises type safety.

Comment thread js/ai/src/agent.ts
{ sendChunk, abortSignal }
) => {
await sess.run(async (input) => {
const promptInput = {};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The promptInput is hardcoded to an empty object {}. This is a critical issue because any agent defined from a prompt that requires input variables (e.g., {{name}} in a template) will fail to render correctly, as those variables are never provided.

The agent implementation should allow for initial input variables to be passed (likely via AgentInit) and persisted in the session state so they can be used across multiple turns.

Comment thread js/ai/src/agent.ts
// Let the prompt control where history is placed (e.g. dotprompt
// {{history}}). When the prompt has no explicit `messages` config
// the render helper simply appends history after system/user.
const genOpts = await cachedPromptAction.__executablePrompt.render(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Accessing __executablePrompt breaks encapsulation as it is an internal/private property of PromptAction. This makes the agent implementation fragile and dependent on internal details of the prompt system.

Consider exposing a public render method on PromptAction or ExecutablePrompt that supports rendering with a provided history, or refactoring how agents interact with prompts to avoid relying on internal properties.

Comment thread js/ai/src/agent.ts Outdated
this.session.addMessages(input.messages);
}

const turnSnapshotId = this.newSnapshotId || crypto.randomUUID();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

crypto.randomUUID() is not available globally in Node.js versions prior to 19.0.0 (including Node 18 LTS, which Genkit typically supports). In Node 18, crypto must be imported from the node:crypto module or accessed via globalThis.crypto (which was only added in later 18.x releases).

This will cause a ReferenceError in environments where it's not global. Please ensure compatibility by importing randomUUID or using a cross-platform utility.

Comment thread js/core/src/async.ts

emit(event: string, ...args: any[]) {
if (!this.listeners[event]) return;
this.listeners[event].forEach((l) => l(...args));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The custom EventEmitter implementation lacks robustness in its emit method. If a listener throws an error, it will stop the execution of subsequent listeners for the same event and propagate the error to the caller, which might not be expected.

Consider wrapping listener calls in a try-catch block to ensure that one failing listener doesn't prevent others from running.

References
  1. Avoid making changes for minor optimizations if the existing code is functionally correct, but ensure robustness in core utilities.

Comment thread js/ai/src/agent.ts
description: `Gets snapshot data for ${config.name} by snapshotId`,
actionType: 'agent-snapshot',
inputSchema: z.string(),
outputSchema: z.any(), // SessionSnapshot Schema
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using z.any() for the outputSchema of getSnapshotDataAction reduces type safety and discoverability in the Genkit registry. Since the action is expected to return a SessionSnapshot, consider defining and using a proper Zod schema for it. Stricter validation is preferable to maintain consistency across API versions.

References
  1. Stricter validation is preferable to maintain consistency across API versions and improve type safety, even if it requires replacing permissive types like z.any().

pavelgj added 6 commits May 6, 2026 21:06
Move `stateManagement` and `abortable` properties into a nested
`agent` object within action metadata to avoid polluting the
top-level metadata namespace. Update tests accordingly.
Use conditional spread syntax to only include fields like
snapshotId, artifacts, message, and state when they have
meaningful values, reducing noise in response objects.
pavelgj added 4 commits May 11, 2026 11:11
Refactor AgentInputSchema to replace the flat `toolRestarts` field with
a structured `resume` object containing `restart` and `respond` arrays.
This enables both re-executing interrupted tools and directly providing
tool responses when resuming a session.

Add `validateResumeAgainstHistory` to verify that all resume entries
reference tool requests present in session history and that restart
inputs have not been tampered with, improving safety for interrupted
generation flows.
Make `snapshotId` optional in `saveSnapshot` input and return the
assigned ID from the store. This allows store implementations to
encode grouping or routing information into snapshot IDs rather than
requiring callers to generate UUIDs upfront.

- Add `SessionSnapshotInput` type with optional `snapshotId`
- Change `SessionStore.saveSnapshot` return type from `void` to `string`
- Refactor `FileSessionStore` to use composite IDs (`convoId/suffix`)
  with a conversation-based directory structure
- Update `InMemorySessionStore` to generate IDs when not provided
- Update `SessionRunner` to use store-assigned snapshot IDs
- Add helpers for parsing, building, and validating composite IDs
Comment thread js/ai/src/agent.ts Outdated
*/
export const AgentInitSchema = z.object({
snapshotId: z.string().optional(),
newSnapshotId: z.string().optional(),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Comment thread js/ai/src/agent.ts Outdated
Comment on lines +761 to +764
const previousStatus = snapshot.status;
snapshot.status = 'aborted';
await config.store.saveSnapshot(snapshot, { context: getContext() });
return previousStatus;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this logic is helpful. It should return aborted unless it has reached a different terminal state in which case that status should be returned instead.

pavelgj added 3 commits May 12, 2026 10:19
Remove the `newSnapshotId` optional field from `AgentInitSchema`
as it is no longer needed in the agent initialization schema.
Export AgentInitSchema, AgentInputSchema, AgentOutputSchema, and
AgentStreamChunkSchema alongside existing type exports from
@genkit-ai/ai to allow runtime validation of agent types.
Refactor `saveSnapshot` to accept a snapshot ID and a mutator function
instead of a pre-built snapshot object. The mutator receives the current
snapshot and returns the updated value (or null to skip the write),
enabling atomic read-check-write semantics.

This prevents a race condition where a "done" write could overwrite a
concurrent "aborted" status. The abort logic is also updated to skip
writes on already-terminal snapshots (done, failed, aborted).

- Add `SnapshotMutator` type to `SessionStore` interface
- Update `InMemorySessionStore` and all callers to new signature
- Remove separate `getSnapshot` + `saveSnapshot` pattern in abort flows
- Add JSDoc explaining the concurrency-safety rationale
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

config docs Improvements or additions to documentation js root

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants