WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Conversation

@DanielSLew
Copy link
Contributor

@DanielSLew DanielSLew commented Dec 5, 2025

Addresses #7923 - Better processor support in Mastra.

There were a lot of missing features of processors, all these new features are added to close the loop. The big ones are

  • Workflows can now return tripwires, they bubble up from agents that return tripwires in a step
  • You can write processors as workflows using the existing Workflow primitive, every processor flow is now a workflow.
  • tripwires that you throw can now return additional information including ability to retry the step
  • New processor method processOutputStep added which runs after every step.

What's new:

1. Retry mechanism with LLM feedback - Processors can now request retries with feedback that gets sent back to the LLM:

processOutputStep: async ({ text, abort, retryCount }) => {
  if (isLowQuality(text)) {
    abort('Response quality too low', { retry: true, metadata: { score: 0.6 } });
  }
  return [];
};

Configure with maxProcessorRetries (default: 3). Rejected steps are preserved in result.steps[n].tripwire. Retries are only available in processOutputStep and processInputStep. It will replay the step with additional context added.

2. Workflow orchestration for processors - Processors can now be composed using workflow primitives:

import { createStep, createWorkflow } from '@mastra/core/workflows';
import {
  ProcessorStepSchema,
} from '@mastra/core/processors';

const moderationWorkflow = createWorkflow({ id: 'moderation', inputSchema: ProcessorStepSchema, outputSchema: ProcessorStepSchema })
  .then(createStep(new lengthValidator({...})))
  .parallel([createStep(new piiDetector({...}), createStep(new toxicityChecker({...}))])
  .commit();

const agent = new Agent({ inputProcessors: [moderationWorkflow] });

Every processor array that gets passed to an agent gets added as a workflow
image

3. Extended tripwire API - abort() now accepts options for retry control and typed metadata:

abort('reason', { retry: true, metadata: { score: 0.8, category: 'quality' } });

4. New processOutputStep method - Per-step output processing with access to step number, finish reason, tool calls, and retry count.

5. Workflow tripwire status - Workflows now have a 'tripwire' status distinct from 'failed', properly bubbling up processor rejections.

6. UI updates -

  • Processor workflows show a "Processor" badge in the playground.
image
  • Tripwire events display in agent chat with reason/metadata.
image
  • Tripwire rendering added to workflow run UI
image
  • Tripwire data in modal
image

Summary by CodeRabbit

  • New Features

    • Use workflows as processors; new processor-backed demo agents and examples.
    • Per-step output hook and retry semantics (processOutputStep) and maxProcessorRetries support.
    • UI: processor badge for workflows and a Tripwire notice component with retry info.
  • Bug Fixes / Behavior

    • Tripwire events now carry structured details (reason, retry, metadata, processorId).
    • Blocked content is suppressed from outputs; processor-driven retries propagate into run flow.
  • Documentation

    • Processor enhancements plan, migration guide, API/docs updated with examples.

✏️ Tip: You can customize this high-level summary in your review settings.

@changeset-bot
Copy link

changeset-bot bot commented Dec 5, 2025

🦋 Changeset detected

Latest commit: 710d747

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 19 packages
Name Type
@mastra/playground-ui Patch
@mastra/client-js Patch
@mastra/ai-sdk Patch
@mastra/react Patch
@mastra/server Patch
@mastra/inngest Patch
@mastra/core Patch
@mastra/deployer Patch
@mastra/express Patch
@mastra/hono Patch
@mastra/mcp-docs-server Patch
@mastra/dane Patch
@mastra/longmemeval Patch
mastra Patch
@mastra/deployer-cloud Patch
@mastra/deployer-cloudflare Patch
@mastra/deployer-netlify Patch
@mastra/deployer-vercel Patch
create-mastra Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link

vercel bot commented Dec 5, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
assistant-ui Ready Ready Preview Comment Dec 9, 2025 9:46pm
mastra-docs Ready Ready Preview Comment Dec 9, 2025 9:46pm
mastra-docs-1.x Ready Ready Preview Comment Dec 9, 2025 9:46pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 5, 2025

Walkthrough

Adds workflow-as-processor support and replaces primitive tripwire flags with structured tripwire objects (reason, retry, metadata, processorId). Threads processor/workflow unions and maxProcessorRetries across agents, loops/LLM/streams, processor runner, SDKs, UI, examples, tests, and docs; introduces processor step schemas and per-step processing hooks.

Changes

Cohort / File(s) Summary
Processor core & schemas
packages/core/src/processors/index.ts, packages/core/src/processors/step-schema.ts
New ProcessorWorkflow, Input/OutputProcessorOrWorkflow unions; generic TripWireOptions/TripwireData; ToolCallInfo; Processor step schemas; added processInputStep/processOutputStep signatures and retryCount/abort options.
Processor runner & tests
packages/core/src/processors/runner.ts, packages/core/src/processors/runner.test.ts
ProcessorRunner accepts workflows as processors, can execute workflows-as-processors, propagates structured tripwire payloads (with processorId) and retryCount; tests updated/expanded for tripwire options and retries.
Agent core & tripwire
packages/core/src/agent/*.ts, packages/core/src/agent/trip-wire.ts, packages/core/src/agent/agent.types.ts, packages/core/src/agent/agent-legacy.ts
Public types use Input/OutputProcessorOrWorkflow; added getConfiguredProcessorWorkflows/#maxProcessorRetries; TripWire rewritten to carry options/processorId; internal APIs now propagate tripwire object.
Loop, LLM & streaming
packages/core/src/loop/*, packages/core/src/llm/model/*, packages/core/src/stream/*, packages/core/src/stream/types.ts
Threaded maxProcessorRetries into stream/loop/LLM; added post-LLM output processor execution with retry semantics; processorRetryCount in step results; finish reasons extended with 'tripwire'
Workflow integration & types
packages/core/src/workflows/*, packages/core/src/workflows/types.ts, packages/core/src/workflows/default.ts, packages/core/src/workflows/workflow.ts
createStep overloads to accept Processor; workflow.type/isProcessorWorkflow introduced; tripwire flows and WorkflowResult extended to carry tripwire data and status 'tripwire'; executeStepWithRetry/fmtReturnValue adjusted.
Stream output & step results
packages/core/src/stream/base/output.ts, packages/core/src/stream/aisdk/v5/output-helpers.ts, packages/core/src/stream/aisdk/v5/output.ts, packages/core/src/stream/aisdk/v4/transform.ts
Step results now include StepTripwireData; DefaultStepResult and output streams short-circuit text when tripwire present; removed legacy tripwireReason getters; emit structured tripwire payloads.
Prepare-stream, memory & message-list
packages/core/src/agent/workflows/prepare-stream/*.ts, packages/core/src/memory/*, packages/core/src/agent/message-list/index.ts
map-results/prepare-memory now propagate tripwire objects; memory APIs accept ProcessorOrWorkflow unions; dedupe processors by id (isProcessorWorkflow); MemoryRuntimeContext → MemoryRequestContext; MessageList source checker exposes context.
Processors, built-ins & examples
packages/core/src/processors/processors/structured-output.ts, examples/agent/src/mastra/processors/index.ts, examples/agent/src/mastra/workflows/content-moderation.ts, examples/agent/*
StructuredOutputProcessor signature adjusted (optional controller, retryCount, abort options); new example processors, processor-backed workflows, demo script and npm script added.
SDKs & client mapping
client-sdks/ai-sdk/src/helpers.ts, client-sdks/client-js/src/types.ts, client-sdks/react/src/lib/ai-sdk/*, client-sdks/*/__tests__, client-sdks/ai-sdk/src/middleware.ts
MastraFinishReason exported and mapped to ai-sdk finish reasons; tripwire chunks converted to structured payloads (reason/retry/metadata/processorId); TripwireMetadata type added; tests updated.
Playground UI & workflow UI
packages/playground-ui/src/components/assistant-ui/*, packages/playground-ui/src/domains/workflows/**
New TripwireNotice component and UI branches for status 'tripwire'; processor badges for processor workflows; suppress error rendering when tripwire present; tripwire dialogs/details and related UI adjustments.
Server & schemas
packages/server/src/server/schemas/workflows.ts, packages/server/src/server/handlers/agents.ts, packages/server/src/server/utils.ts
workflowRunStatusSchema includes 'tripwire'; workflowInfo gains optional isProcessorWorkflow; getSerializedProcessors accepts ProcessorOrWorkflow unions; getWorkflowInfo returns isProcessorWorkflow for processor-type workflows.
Tests, tooling & docs
packages/core/**/tests, docs/**, .changeset/*, docs/processor-enhancements-plan.md
Large test updates/additions for processor-step, runner, workflow tripwire scenarios; docs updated/added for tripwire format, processors-as-workflows, processOutputStep and retry semantics; changelogs and migration guidance added.
Misc small updates
various files (memory types, stream transforms, UI helpers, zod provider, examples)
runtimeContext→requestContext renames, zod literal default handling, small UI tweaks (scrolling), test fixture updates to include tripwire fields, and MessageList context additions.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Areas to focus during review:

  • Consistency of Tripwire/TripwireData shape and generics across agent, stream, workflow, runner, SDKs, and UI.
  • Workflow-as-processor execution paths (executeWorkflowAsProcessor, ProcessorRunner.runPart/runProcess*), TripWire propagation with processorId and tracing.
  • Retry semantics: maxProcessorRetries, retryCount threading, and the behavior when retry is requested vs disallowed.
  • Public API/type surface changes (ProcessorOrWorkflow unions, StepTripwireData/MastraFinishReason) and server/schema compatibility.
  • UI changes where tripwire suppresses error rendering and new components/props are introduced.

Possibly related PRs

Suggested labels

cherry

Suggested reviewers

  • abhiaiyer91

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: enhanced processor system with retry, workflow orchestration, and tripwire improvements, which aligns with the PR objectives.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/improve-processors

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1d95c64 and 710d747.

📒 Files selected for processing (2)
  • packages/core/src/processors/runner.ts (17 hunks)
  • packages/core/src/workflows/handlers/control-flow.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/core/src/workflows/handlers/control-flow.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Run pnpm typecheck to validate TypeScript types across all packages

Files:

  • packages/core/src/processors/runner.ts
**/*.{ts,tsx,js,jsx,json,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Run pnpm prettier:format to format code and pnpm format to run linting with auto-fix across all packages

Files:

  • packages/core/src/processors/runner.ts
packages/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

packages/**/*.{ts,tsx}: All packages must use TypeScript with strict type checking enabled
Use telemetry decorators for observability across components

Files:

  • packages/core/src/processors/runner.ts
🔇 Additional comments (4)
packages/core/src/processors/runner.ts (4)

66-69: LGTM!

The union type ProcessorOrWorkflow is well-defined and clearly documented, enabling the runner to handle both regular processors and workflow-based processors.


77-92: LGTM!

Constructor properly updated to accept the union type, maintaining backward compatibility while enabling workflow support.


336-353: LGTM! Defensive check added for workflow result.

The code now uses a defensive check at line 351 (if ('part' in result)) before extracting the part from the workflow result, addressing the previous concern about unsafe casts. This prevents runtime errors if the workflow returns an unexpected shape.


146-154: The validation logic is correct and should not be simplified.

The current validation at line 147 is appropriate for the schema design. The ProcessorStepOutputSchema in step-schema.ts (lines 400-424) explicitly allows all output fields except phase to be optional, but the runtime validation correctly ensures at least one data field (messages, part, or messageList) is present alongside the required phase field.

Additionally, messageList is a legitimate part of ProcessorStepOutput (defined at line 406 in step-schema.ts), not merely an input parameter. The schema comment at lines 395-398 confirms this flexible approach is intentional to support passing data between different phases.

The suggested simplification to only validate the phase field would incorrectly allow outputs containing no actual data, which contradicts the purpose of the validation.

Likely an incorrect or invalid review comment.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/core/src/workflows/types.ts (1)

203-212: Fix incomplete status handling for 'tripwire' and other statuses in execute() method and MastraWorkflowStream.

The new 'tripwire' status addition reveals broader exhaustiveness issues:

  • workflow.ts execute() method (lines 1540-1544): Returns undefined for 'tripwire' status instead of properly handling it. The method only checks 'failed' (throws) and 'success' (returns result), leaving 'tripwire', 'suspended', 'waiting', 'pending', 'canceled', and 'bailed' statuses returning undefined silently.

  • MastraWorkflowStream.ts (lines 106-120): Status assignment logic never sets status to 'tripwire'. The else-if chain handles 'canceled', 'suspended', and 'failed', but has no case for tripwire, so workflow-finish events emit incorrect status.

  • handlers/control-flow.ts (lines 165, 432, 197, 459): Parallel and conditional handlers check for 'failed' but don't explicitly propagate 'tripwire' status through execution paths, though tripwire property is preserved internally.

All WorkflowRunStatus variants must be handled exhaustively in switch statements and conditional chains to prevent silent failures.

🧹 Nitpick comments (33)
packages/playground-ui/src/domains/workflows/components/workflow-information.tsx (1)

80-84: Use design-token-based classes instead of raw Tailwind colors for processor badge

The processor badge behavior and conditional rendering look good, but className="bg-violet-500/20 text-violet-400" couples this UI to raw Tailwind palette values. Please swap these for the appropriate design-token-derived utility classes from src/ds/tokens/index.ts (or an existing Badge variant) to stay aligned with the design system. As per coding guidelines, ...

packages/playground-ui/src/domains/workflows/components/workflow-table/columns.tsx (1)

15-28: Processor badge UX is clear; consider minor consistency + token usage tweaks

The new workflow alias and conditional Processor badge integration into EntryCell look good and match the PR’s processor-workflow goals.

Two small nits you may want to consider:

  • For consistency, you could use workflow instead of row.original in the Link (paths.workflowLink(workflow.id) and workflow.name).
  • The badge uses bg-violet-500/20 text-violet-400 and h-3 w-3. If the design system exposes equivalent token-based classes or a variant/tone API on Badge, prefer those over hardcoded Tailwind color utilities to stay aligned with the playground UI token guidelines.
packages/playground-ui/src/domains/workflows/utils.ts (1)

14-14: Clarify the comment to match actual logic.

The comment states "failed step with tripwire property," but the code doesn't verify that result.status === 'failed'. Any step with a truthy tripwire property will trigger this handling, regardless of status.

Apply this diff to clarify the comment:

-// Check if this is a tripwire (failed step with tripwire property)
+// Check if this step has a tripwire (tripwire takes precedence over error)
packages/playground-ui/src/components/ui/autoform/zodProvider/default-values.ts (1)

6-10: Literal default handling looks correct; consider simplifying the values access

The new $ZodLiteral branch correctly derives a default from schema._zod.def.values, which in Zod v4 is an array of allowed literal values, so taking the first one is a reasonable convention for autoform defaults. Given the type is always an array, the Array.isArray guard is redundant and you could simplify this to something like:

const [first] = schema._zod.def.values;
return first;

or add a small nullish fallback if you want to guard against an unexpected empty values array.

This is a minor readability tweak; behavior is otherwise sound.

packages/core/src/processors/examples/processor-features-demo.example.ts (1)

1-351: Consider making these examples executable or moving to documentation.

The entire file consists of commented-out code. While the examples are comprehensive and well-structured for demonstrating the new processor features, commented-out code in the codebase has limited utility:

  1. It won't be type-checked, so it may become stale as APIs evolve
  2. Users cannot import and run these examples
  3. It adds to code maintenance burden

Consider one of these alternatives:

  • Uncomment the code to make it a proper runnable example file
  • Convert to integration tests that verify the examples work correctly
  • Move to documentation (e.g., a markdown file in docs with code snippets)
workflows/inngest/src/execution-engine.ts (1)

363-381: Add explicit type guard for the success status to eliminate the any cast.

The WorkflowResult type already defines a result property on the success branch, so the (result as any)?.result cast is unnecessary. After the if (result.status === 'suspended') and else if (result.status === 'tripwire') checks, add an explicit if (result.status === 'success') guard before lines 364-381 to enable proper TypeScript type narrowing and safely access result.result without casting to any.

packages/core/src/workflows/types.ts (1)

468-491: Consider tightening the metadata type.

The tripwire variant structure looks good, but the metadata field is typed as unknown, which is very permissive. Consider using a more specific type (e.g., Record<string, unknown> or a dedicated TripWireMetadata interface) to provide better type safety and IDE support for consumers.

  /** Metadata from the tripwire */
- metadata?: unknown;
+ metadata?: Record<string, unknown>;
packages/core/src/processors/processors/structured-output.ts (1)

73-84: Unused retryCount parameter and type safety concerns.

Three observations:

  1. The retryCount parameter is added but never used in the function body. If it's intended for future use or consistency with other processors, consider adding a JSDoc comment explaining this.

  2. The abort signature uses options?: unknown, which is very permissive. Consider using a more specific type like TripWireOptions (if available) for better type safety.

  3. Line 84 casts state.controller to TransformStreamDefaultController<ChunkType<OUTPUT>>. Since controller is now optional, this cast could be undefined at runtime. Consider adding a guard:

  async processOutputStream(args: {
    part: ChunkType;
    streamParts: ChunkType[];
    state: Record<string, unknown> & {
      controller?: TransformStreamDefaultController<ChunkType<OUTPUT>>;
    };
-   abort: (reason?: string, options?: unknown) => never;
+   abort: (reason?: string, options?: TripWireOptions) => never; // or appropriate type
    tracingContext?: TracingContext;
+   /** Retry count for this processing attempt */
    retryCount: number;
  }): Promise<ChunkType | null | undefined> {
    const { part, state, streamParts, abort, tracingContext } = args;
-   const controller = state.controller as TransformStreamDefaultController<ChunkType<OUTPUT>>;
+   const controller = state.controller;
+   if (!controller) {
+     throw new Error('Controller is required for structured output processing');
+   }
packages/core/src/workflows/handlers/control-flow.ts (1)

168-175: Type assertion needed - consider extending StepFailure type.

The code correctly preserves the tripwire property when aggregating parallel step results, but uses (hasFailed as any).tripwire to access it. This suggests the StepFailure type doesn't include the tripwire field.

Consider extending the StepFailure type definition (or creating a StepFailureWithTripwire variant) to include the optional tripwire field for better type safety.

// In types.ts, extend StepFailure or create a union:
export type StepFailure<P, R, S, T> = {
  status: 'failed';
  error: string | Error;
  payload: P;
  resumePayload?: R;
  suspendPayload?: S;
  suspendOutput?: T;
  startedAt: number;
  endedAt: number;
  suspendedAt?: number;
  resumedAt?: number;
  metadata?: StepMetadata;
  tripwire?: { reason: string; retry?: boolean; metadata?: unknown; processorId?: string }; // Add this
};

Then the type assertion can be removed:

  if (hasFailed) {
-   // Preserve tripwire property for proper status conversion in fmtReturnValue
    execResults = {
      status: 'failed',
      error: hasFailed.error,
-     tripwire: (hasFailed as any).tripwire,
+     tripwire: hasFailed.tripwire,
    };
packages/server/src/server/schemas/workflows.ts (1)

4-14: Tripwire status and processor-workflow flag look consistent

Adding 'tripwire' to workflowRunStatusSchema and isProcessorWorkflow?: boolean to workflowInfoSchema cleanly exposes the new runtime state and metadata to the server API. If you rely on generated API docs, consider adding .describe(...) on isProcessorWorkflow to document that it marks auto-generated processor workflows, but the current shape is otherwise solid.

Also applies to: 46-56

packages/core/src/processors/examples/content-moderation-workflow.example.ts (1)

1-394: Example stays in sync with new processor & tripwire APIs

Even though this file is fully commented, the example usage (typed Processor<..., TMetadata>, abort(reason, { retry, metadata }), processOutputStep with retryCount, and tripwire handling on chunks/workflow results) matches the updated processor, TripWire, and workflow semantics in core. No changes needed; just remember to keep this in sync with the live examples under examples/agent as the APIs evolve.

packages/core/src/mastra/index.ts (1)

714-751: Async processor-workflow registration is sound; be aware of eventual consistency

The new getConfiguredProcessorWorkflows().then(...) block cleanly auto-registers processor workflows for an agent without blocking addAgent or the constructor, and it safely swallows/logs failures so a bad processor setup doesn’t crash Mastra initialization.

One behavioral nuance: because registration is async, there’s a small window where addAgent() has returned but addWorkflow() for those processor workflows hasn’t run yet. If callers immediately query mastra.getWorkflow(...) for a processor workflow right after addAgent, they may occasionally miss it. If that matters for consumers, consider documenting this as “eventually consistent” registration or exposing a helper that awaits workflow registration when needed.

packages/core/src/workflows/workflow.test.ts (1)

17532-17782: Tripwire workflow tests look aligned; please verify output-stream part shape and consider stream helper

The new Agent TripWire in Workflow tests correctly exercise:

  • Input processor tripwires in non‑streaming runs and streamVNext
  • Output stream processor tripwires bubbling up as status: 'tripwire' with reason, retry, metadata, and processorId

Two things worth double‑checking:

  1. Shape of part in processOutputStream
const outputStreamTripwireProcessor = {
  id: 'output-stream-tripwire-processor',
  // ...
  processOutputStream: async ({ part, abort }: any) => {
    if (part?.type === 'text-delta' && part?.payload?.text?.includes('inappropriate')) {
      abort('Output contains inappropriate content', { retry: true });
    }
    return part;
  },
};

This assumes the processor sees a part with type === 'text-delta' and a nested payload.text field. Depending on how the agent pipeline wires V2 chunks into processors, the text may instead live under a different key (delta, textDelta, or a differently-shaped payload). If that assumption is wrong, this test will never trigger the abort and will silently assert the wrong behavior.

I’d recommend confirming the actual part shape used by processOutputStream (e.g. by inspecting TripWireOptions['processOutputStream'] usage in agent/trip-wire.ts and the adapter that feeds model chunks into processors) and updating the predicate accordingly so the test is validating the real path.

  1. Stream source construction

In these new tests you construct a raw ReadableStream:

stream: new ReadableStream({ /* enqueue chunks */ }),

Most other tests in this file use simulateReadableStream from @internal/ai-sdk-v4, which is already wired for the test environment and avoids subtle differences between Node/web stream implementations. For consistency and to reduce environment coupling, consider switching these three mocks to simulateReadableStream as well:

stream: simulateReadableStream({
  chunks: [
    { type: 'stream-start', warnings: [] },
    // ...
  ],
});

Functionally the tests are solid; these tweaks would make them more robust against implementation detail changes.

examples/agent/src/mastra/agents/model-v2-agent.ts (1)

11-17: New moderation agents are well-configured; consider standardizing import specifiers

The three new agents correctly wire moderation workflows into inputProcessors, pair them with appropriate outputProcessors, and set maxProcessorRetries consistently. One minor nit: here you import from '../workflows/content-moderation' while processor-demo.ts uses './mastra/workflows/content-moderation.js'; standardizing on one style (with or without .js, per your tsconfig/module resolution) would reduce potential confusion.

Also applies to: 140-194

packages/core/src/loop/workflows/schema.ts (1)

27-35: Consider tightening runtime validation for reason to match the new union

You’ve extended LLMIterationStepResult.reason to LanguageModelV2FinishReason | 'tripwire' and correctly added processorRetryCount to both the TypeScript types and Zod schema. To keep runtime validation aligned with the TS type, consider changing llmIterationStepResultSchema.reason from z.string() to a concrete enum/union that includes the known finish reasons plus 'tripwire'; this would catch invalid reason values earlier.

Also applies to: 69-84, 108-148

packages/playground-ui/src/domains/workflows/workflow/workflow-trigger.tsx (1)

195-200: Tripwire status/UI wiring is sound; consider typing tripwire fields to avoid any casts

Including 'tripwire' in doneStatuses, deriving displayStatus as 'tripwire' for failed steps with tripwire data, and passing a normalized tripwire object into WorkflowStatus all look correct and should render processor tripwires clearly in the UI. The only downside is the repeated (step as any).tripwire and (streamResultToUse as any) casts; if feasible, extending WorkflowRunStreamResult and step types to expose a typed tripwire shape (reason, retry, metadata, processorId) would remove these anys and catch shape drift at compile time.

Also applies to: 330-350, 351-359

packages/server/src/server/handlers/agents.ts (1)

151-162: Consider type narrowing instead of as any cast.

The (processor as any).name cast works but loses type safety. Since ProcessorWorkflow has a name property, you could use a type guard or check if isProcessorWorkflow utility exists in the codebase.

 export function getSerializedProcessors(
   processors: (InputProcessor | OutputProcessor | InputProcessorOrWorkflow | OutputProcessorOrWorkflow)[],
 ): SerializedProcessor[] {
   return processors.map(processor => {
-    // Processors are class instances or objects with a name property
-    // Use the name property if available, otherwise fall back to constructor name
+    // Use the name property if available, otherwise fall back to constructor name
+    const name = 'name' in processor ? processor.name : processor.constructor.name;
     return {
       id: processor.id,
-      name: (processor as any).name || processor.constructor.name,
+      name: name || processor.constructor.name,
     };
   });
 }
packages/core/src/mastra/processor-workflow-registration.test.ts (1)

16-17: Fixed timeout may cause flaky tests.

The 50ms waitForWorkflowRegistration timeout could be insufficient in slower CI environments, leading to intermittent test failures. Consider using a polling mechanism or increasing the timeout with a buffer.

-  // Helper to wait for async workflow registration
-  const waitForWorkflowRegistration = () => new Promise(resolve => setTimeout(resolve, 50));
+  // Helper to wait for async workflow registration with polling
+  const waitForWorkflowRegistration = async (
+    check?: () => boolean,
+    timeout = 500,
+    interval = 10
+  ) => {
+    if (!check) {
+      return new Promise(resolve => setTimeout(resolve, 100));
+    }
+    const start = Date.now();
+    while (Date.now() - start < timeout) {
+      if (check()) return;
+      await new Promise(resolve => setTimeout(resolve, interval));
+    }
+  };
packages/playground-ui/src/domains/workflows/workflow/workflow-step-action-bar.tsx (1)

70-70: Arbitrary Tailwind values used instead of design tokens.

Per coding guidelines, use design tokens from src/ds/tokens/index.ts instead of arbitrary values like bg-amber-900/40 and border-amber-500/20. If amber tokens don't exist, consider adding them to the design system.

As per coding guidelines: "Use design tokens from src/ds/tokens/index.ts instead of arbitrary Tailwind values."

packages/playground-ui/src/components/assistant-ui/messages/tripwire-notice.tsx (2)

73-75: Remove redundant String() wrapper.

JSON.stringify() already returns a string, making the String() wrapper unnecessary.

                 <pre className="text-xs text-amber-200/80 bg-amber-900/30 rounded p-2 overflow-x-auto font-mono">
-                    {String(JSON.stringify(tripwire.tripwirePayload, null, 2))}
+                    {JSON.stringify(tripwire.tripwirePayload, null, 2)}
                 </pre>

18-82: Arbitrary Tailwind values used instead of design tokens.

The component uses numerous arbitrary amber color values (e.g., amber-500/30, amber-950/20, amber-400). Per coding guidelines, design tokens from src/ds/tokens/index.ts should be used. Consider adding tripwire/warning tokens to the design system if they don't exist.

As per coding guidelines: "Use design tokens from src/ds/tokens/index.ts instead of arbitrary Tailwind values."

packages/playground-ui/src/domains/workflows/workflow/workflow-default-node.tsx (1)

81-81: Arbitrary Tailwind values used.

Similar to other components, bg-amber-950/40 and border-amber-500/30 should use design tokens per coding guidelines.

As per coding guidelines: "Use design tokens from src/ds/tokens/index.ts instead of arbitrary Tailwind values."

packages/core/src/stream/base/output.ts (1)

988-1015: Full output text aggregation excludes rejected steps; consider aligning with text getter.

Computing textFromSteps from steps.map(step => step.text || '') and using it as fullOutput.text is the right way to ensure tripwired steps (whose text is now blank) don’t leak into the final text while still returning the full steps array.

Note that await stream.text still resolves from the buffered raw text, which may include rejected content. If consumers are expected to rely only on getFullOutput().text for safe text, this is fine; otherwise, consider in a follow‑up aligning the text getter with the step‑based aggregation to avoid divergent behaviors.

packages/playground-ui/src/domains/workflows/workflow/workflow-status.tsx (1)

82-149: TripwireDetails UX and structure look good; minor styling nit.

The expandable TripwireDetails panel (reason, retry indicator, processor id, JSON metadata) is clear and compact, and correctly keyed off the new TripwireInfo contract.

If you want to align more strictly with the design‑token guideline in packages/playground-ui, you could later replace hard‑coded Tailwind colors like bg-amber-950/20 and text-green-400 with tokenized classes (e.g., bg-surface*, text-accent*) to keep styling consistent with the rest of the DS.

docs/processor-enhancements-plan.md (1)

369-399: Consider removing "visually distinguished" phrasing.

Per coding guidelines, avoid marketing-adjacent language. Consider rephrasing:

-Processor workflows are visually distinguished in the playground UI with a purple "Processor" badge:
+Processor workflows display a purple "Processor" badge in the playground UI:

This is a minor stylistic suggestion.

examples/agent/src/mastra/processors/index.ts (1)

98-98: Remove debug console.log statement.

This appears to be leftover debug code. Consider using the logger passed through the processor context or removing it entirely.

-    console.log('detectedCategories', detectedCategories);
packages/core/src/workflows/workflow.ts (1)

414-439: Consider consolidating duplicate error creation logic.

The error handling for missing messageList is repeated across multiple phases (lines 417-424, 444-451, 483-490, 506-512). Consider extracting a helper function.

// Helper at the top of the execute function
const requireMessageList = (phase: string): MessageList => {
  if (!resolvedMessageList) {
    throw new MastraError({
      category: ErrorCategory.USER,
      domain: ErrorDomain.MASTRA_WORKFLOW,
      id: 'PROCESSOR_MISSING_MESSAGE_LIST',
      text: `Processor ${processor.id} requires messageList or messages for ${phase} phase`,
    });
  }
  return resolvedMessageList;
};

// Then use in each phase:
case 'input': {
  if (processor.processInput) {
    const messageList = requireMessageList('processInput');
    // ...
  }
}
examples/agent/src/mastra/workflows/content-moderation.ts (2)

83-83: Suppress or document the intentional control character regex.

The regex /[^\x00-\x7F]/ intentionally uses control character ranges to detect non-ASCII characters. While static analysis flags this, it's a valid pattern for language detection. Consider adding a comment to clarify intent.

-        const hasNonEnglish = /[^\x00-\x7F]/.test(text);
+        // Regex matches any character outside ASCII range (0x00-0x7F) to detect non-English text
+        // biome-ignore lint/suspicious/noControlCharactersInRegex: Intentional ASCII range check
+        const hasNonEnglish = /[^\x00-\x7F]/.test(text);

244-256: Branch conditions could use more robust input handling.

The branch condition accesses data.messages without null checking. If inputData lacks a messages property, JSON.stringify(undefined) returns undefined which may cause unexpected behavior.

     [
       async ({ inputData }) => {
         const data = inputData as any;
-        const text = JSON.stringify(data.messages || []);
+        const messages = data?.messages ?? [];
+        const text = JSON.stringify(messages);
         return text.includes('@') || /\d{3}/.test(text);
       },
       piiStep,
     ],
packages/core/src/processors/runner.ts (1)

546-591: Large code block duplicates logic from runOutputProcessors.

The workflow handling in runInputProcessors (lines 532-591) closely mirrors runOutputProcessors (lines 160-211). Consider extracting a shared helper for applying workflow results to a messageList.

A helper method could reduce duplication:

private applyWorkflowMessagesToList(
  resultMessages: MastraDBMessage[],
  messageList: MessageList,
  idsBeforeProcessing: string[],
  check: ReturnType<MessageList['makeMessageSourceChecker']>,
  defaultSource: 'input' | 'response'
): void {
  const deletedIds = idsBeforeProcessing.filter(
    (i: string) => !resultMessages.some((m: MastraDBMessage) => m.id === i)
  );
  if (deletedIds.length) {
    messageList.removeByIds(deletedIds);
  }
  for (const message of resultMessages) {
    messageList.removeByIds([message.id]);
    if (message.role === 'system') {
      const systemText = /* extraction logic */;
      messageList.addSystem(systemText);
    } else {
      messageList.add(message, check.getSource(message) || defaultSource);
    }
  }
}
packages/core/src/processors/index.ts (1)

235-252: The isProcessorWorkflow type guard is fragile.

The type guard uses negative checks (!('processInput' in obj), etc.) to distinguish workflows from processors. If a new processor method is added in the future and not added to this list, workflows could be incorrectly identified as processors.

Consider a positive check approach using a discriminator property:

 export function isProcessorWorkflow(obj: unknown): obj is ProcessorWorkflow {
   return (
     obj !== null &&
     typeof obj === 'object' &&
     'id' in obj &&
     typeof (obj as any).id === 'string' &&
     'inputSchema' in obj &&
     'outputSchema' in obj &&
     'execute' in obj &&
     typeof (obj as any).execute === 'function' &&
-    // Must NOT have processor-specific methods (to distinguish from Processor)
-    !('processInput' in obj) &&
-    !('processInputStep' in obj) &&
-    !('processOutputStream' in obj) &&
-    !('processOutputResult' in obj) &&
-    !('processOutputStep' in obj)
+    // Workflows have a 'type' property that processors don't have
+    'type' in obj
   );
 }

Alternatively, add a __kind: 'workflow' discriminator to the Workflow class.

packages/core/src/agent/agent.ts (1)

348-353: Mutating passed-in workflow's type property may cause side effects.

The workflow passed in is directly mutated (workflow.type = 'processor'). If the same workflow instance is reused elsewhere (e.g., as a standalone workflow), this mutation could unexpectedly change its type in those contexts.

Consider creating a shallow copy or documenting that this mutation is intentional:

     if (processors.length === 1 && isProcessorWorkflow(processors[0]!)) {
       const workflow = processors[0]!;
-      // Mark the workflow as a processor workflow
-      workflow.type = 'processor';
+      // Mark the workflow as a processor workflow (mutates the original)
+      // This is intentional as processor workflows should be dedicated to processor use
+      (workflow as any).type = 'processor';
       return [workflow];
     }

Alternatively, if the workflow should remain unmodified for reuse, consider cloning or wrapping it.

Also applies to: 363-368

packages/core/src/processors/step-schema.ts (1)

215-219: Note: z.custom() provides no runtime validation.

z.custom<MessageList>() provides TypeScript type information but performs no runtime validation. Any value will pass. This is acceptable for internal use where MessageList instances are guaranteed, but worth documenting if this schema is used in workflow definitions that could receive external input.

Consider adding a runtime check if external input is possible:

const messageListSchema = z.custom<MessageList>(
  (val) => val != null && typeof val === 'object' && 'add' in val
);

DanielSLew and others added 10 commits December 5, 2025 15:59
The conditional was inverted - returning undefined when metadata exists.
processorId is always available on TripWire regardless of metadata presence.

PR comment: #10947 (comment)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Replace unsafe `as any` casts with proper type narrowing when extracting
tripwire data from workflow results.

PR comment: #10947 (comment)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
The processor overload should use ProcessorStepOutputSchema for output,
not ProcessorStepSchema, to match the actual return type.

PR comment: #10947 (comment)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
In Zod v3, ZodLiteral stores the literal at _def.value (singular),
not _def.values. This was causing all literal fields to be inferred
as 'string' instead of their actual types.

PR comment: #10947 (comment)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
- Add 'tripwire' | 'retry' to FinishPayload.stepResult.reason type
- Add 'retry' to LLMIterationStepResult.reason type
- Update StreamPart.finishReason to include processor reasons
- Use @ts-expect-error with clear explanation for zod schema mismatch
- Cast to LanguageModelV2FinishReason when converting to AI SDK format

PR comment: #10947 (comment)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Zod v3 uses _def.value (singular) while Zod v4 uses _zod.def.values (array).
Handle both to ensure field type inference works across versions.

PR comment: #10947 (comment)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
- Add StepTripwireInfo interface for tripwire data on failed steps
- Add tripwire property to StepFailure type
- Add reason, retry, metadata, processorId to WorkflowRunState
- Remove as any casts in utils.ts now that types are properly defined

PR comments:
- #10947 (comment)
- #10947 (comment)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
The Step type already has tripwire property defined, so the cast is not needed.
Use !== undefined for the condition to be more explicit.

PR comment: #10947 (comment)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Use strict undefined checks instead of truthiness to ensure falsy values
like 0, '', or false are properly detected as "defined metadata".

PR comment: #10947 (comment)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Import TripwireData from the shared location in use-current-run.tsx
instead of re-declaring it, avoiding potential divergence.

PR comment: #10947 (comment)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (9)
packages/core/src/loop/workflows/agentic-execution/llm-execution-step.ts (1)

900-905: Consider: Multiple retry feedback messages may accumulate.

On each retry, a new feedback message is added via messageList.addSystem(). If the processor rejects multiple times before reaching maxProcessorRetries, the context will contain multiple feedback messages. This may be intentional to provide rejection history, but could also clutter the LLM context.

Consider whether previous feedback should be removed/replaced on subsequent retries, or if accumulation is the desired behavior.

packages/core/src/workflows/types.ts (1)

283-290: Consider using Record<string, unknown> for metadata consistency.

The metadata field is typed as unknown here, but StepTripwireInfo.metadata is typed as Record<string, unknown>. Consider aligning these types for consistency across the codebase.

   /** Tripwire metadata when status is 'tripwire' */
-  metadata?: unknown;
+  metadata?: Record<string, unknown>;
packages/core/src/workflows/workflow.ts (1)

346-364: Implicit type assertion may mask schema mismatches at runtime.

Casting inputData (typed as z.infer<typeof ProcessorStepSchema>) directly to ProcessorStepOutput bypasses the discriminated union's type safety. If the input doesn't actually contain the expected fields for a given phase, this could lead to runtime issues with undefined values.

Consider adding runtime validation or using the discriminated union properly:

-        const input = inputData as ProcessorStepOutput;
+        // The discriminated union is validated at schema level; 
+        // access properties via inputData directly to preserve type narrowing
         const {
           phase,
           messages,
-          messageList,
           // ... other fields
-        } = input;
+        } = inputData;
+        // messageList may need explicit handling since it's not serializable
+        const messageList = (inputData as any).messageList;
packages/core/src/processors/runner.ts (4)

131-139: Consider including the underlying error details in the MastraError.

When the workflow fails with a non-success status (other than 'tripwire'), the error message only includes the status but not any underlying error details from the result. This could make debugging harder.

     if (result.status !== 'success') {
+      const errorDetails = 'error' in result ? `: ${(result as any).error}` : '';
       throw new MastraError({
         category: 'USER',
         domain: 'AGENT',
         id: 'PROCESSOR_WORKFLOW_FAILED',
-        text: `Processor workflow ${workflow.id} failed with status: ${result.status}`,
+        text: `Processor workflow ${workflow.id} failed with status: ${result.status}${errorDetails}`,
       });
     }

325-379: Significant code duplication between workflow and processor handling in stream processing.

The workflow handling branch in processPart duplicates much of the state management and error handling logic from the regular processor branch. Consider extracting common patterns into helper methods to reduce duplication and improve maintainability.


537-598: Repetitive workflow result application logic across methods.

The pattern of extracting workflow results and applying them to messageList (checking for deleted IDs, handling system messages, re-adding messages) is duplicated across runOutputProcessors, runInputProcessors, runProcessInputStep, and runProcessOutputStep.

Consider extracting this into a private helper method:

private applyWorkflowResultToMessageList(
  workflowResult: ProcessorStepOutput,
  messageList: MessageList,
  idsBeforeProcessing: string[],
  sourceChecker: ReturnType<MessageList['makeMessageSourceChecker']>,
  defaultSource: 'input' | 'response',
): void {
  if (!workflowResult.messages) return;
  
  const resultMessages = workflowResult.messages as MastraDBMessage[];
  const deletedIds = idsBeforeProcessing.filter(
    (i: string) => !resultMessages.some((m: MastraDBMessage) => m.id === i),
  );
  // ... rest of the logic
}

1178-1196: Error handling includes tripwire metadata in span but could be more consistent.

The error handling for TripWire in runProcessOutputStep includes metadata in the span output, which is good. However, other similar catch blocks (e.g., in runProcessInputStep at line 943) don't include the metadata. Consider making this consistent across all methods.

packages/playground-ui/src/domains/workflows/workflow/workflow-step-action-bar.tsx (1)

62-63: Align tripwire styling with design tokens instead of raw amber Tailwind colors

The new tripwire styles use raw Tailwind palette classes (bg-amber-900/40, border-amber-500/20, text-amber-400, hover:text-amber-300). To keep packages/playground-ui consistent with the design system, prefer token-based classes (like the existing bg-surface*, text-icon*, bg-accent*) wired through src/ds/tokens/index.ts instead of directly using palette colors. Consider introducing dedicated “warning/tripwire” tokens and referencing those here.

As per coding guidelines, Tailwind usage in packages/playground-ui should be driven by design tokens rather than ad-hoc color classes.

Also applies to: 168-183

packages/playground-ui/src/domains/workflows/workflow/workflow-default-node.tsx (1)

74-85: Avoid as any on status and prefer token-based tripwire colors

Two points to tighten up here:

  1. Type safety for status
    status={displayStatus as any} bypasses strict typing. It would be better to narrow displayStatus to the WorkflowStepActionBarProps['status'] | undefined union (or a local DisplayStatus type) instead of casting to any, then pass that typed value through. This keeps strict mode useful and avoids masking status mismatches.

  2. Tripwire color tokens
    The node container and icon use bg-amber-950/40, border-amber-500/30, and text-amber-400. Similar to the action bar, these raw Tailwind amber colors should ideally be replaced with design-token-driven classes (e.g., a warning/tripwire token defined in src/ds/tokens/index.ts) to stay consistent with the design system.

As per coding guidelines, packages/playground-ui components should avoid any casts where possible under strict TS and should use design tokens rather than ad-hoc Tailwind color values.

Also applies to: 124-129, 165-165

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 12e5d2e and 6fa1984.

📒 Files selected for processing (13)
  • packages/core/src/loop/workflows/agentic-execution/llm-execution-step.ts (8 hunks)
  • packages/core/src/loop/workflows/schema.ts (3 hunks)
  • packages/core/src/loop/workflows/stream.ts (2 hunks)
  • packages/core/src/processors/runner.ts (17 hunks)
  • packages/core/src/stream/aisdk/v5/transform.ts (2 hunks)
  • packages/core/src/stream/types.ts (6 hunks)
  • packages/core/src/workflows/types.ts (8 hunks)
  • packages/core/src/workflows/workflow.ts (12 hunks)
  • packages/playground-ui/src/components/ui/autoform/zodProvider/field-type-inference.ts (1 hunks)
  • packages/playground-ui/src/domains/workflows/utils.ts (2 hunks)
  • packages/playground-ui/src/domains/workflows/workflow/workflow-default-node.tsx (5 hunks)
  • packages/playground-ui/src/domains/workflows/workflow/workflow-status.tsx (2 hunks)
  • packages/playground-ui/src/domains/workflows/workflow/workflow-step-action-bar.tsx (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/core/src/loop/workflows/stream.ts
  • packages/core/src/loop/workflows/schema.ts
  • packages/playground-ui/src/domains/workflows/workflow/workflow-status.tsx
  • packages/playground-ui/src/components/ui/autoform/zodProvider/field-type-inference.ts
  • packages/playground-ui/src/domains/workflows/utils.ts
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Run pnpm typecheck to validate TypeScript types across all packages

Files:

  • packages/core/src/stream/aisdk/v5/transform.ts
  • packages/playground-ui/src/domains/workflows/workflow/workflow-default-node.tsx
  • packages/core/src/loop/workflows/agentic-execution/llm-execution-step.ts
  • packages/core/src/stream/types.ts
  • packages/playground-ui/src/domains/workflows/workflow/workflow-step-action-bar.tsx
  • packages/core/src/workflows/types.ts
  • packages/core/src/workflows/workflow.ts
  • packages/core/src/processors/runner.ts
**/*.{ts,tsx,js,jsx,json,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Run pnpm prettier:format to format code and pnpm format to run linting with auto-fix across all packages

Files:

  • packages/core/src/stream/aisdk/v5/transform.ts
  • packages/playground-ui/src/domains/workflows/workflow/workflow-default-node.tsx
  • packages/core/src/loop/workflows/agentic-execution/llm-execution-step.ts
  • packages/core/src/stream/types.ts
  • packages/playground-ui/src/domains/workflows/workflow/workflow-step-action-bar.tsx
  • packages/core/src/workflows/types.ts
  • packages/core/src/workflows/workflow.ts
  • packages/core/src/processors/runner.ts
packages/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

packages/**/*.{ts,tsx}: All packages must use TypeScript with strict type checking enabled
Use telemetry decorators for observability across components

Files:

  • packages/core/src/stream/aisdk/v5/transform.ts
  • packages/playground-ui/src/domains/workflows/workflow/workflow-default-node.tsx
  • packages/core/src/loop/workflows/agentic-execution/llm-execution-step.ts
  • packages/core/src/stream/types.ts
  • packages/playground-ui/src/domains/workflows/workflow/workflow-step-action-bar.tsx
  • packages/core/src/workflows/types.ts
  • packages/core/src/workflows/workflow.ts
  • packages/core/src/processors/runner.ts
packages/playground-ui/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (packages/playground-ui/CLAUDE.md)

packages/playground-ui/src/**/*.{ts,tsx}: Use Tailwind CSS (v3.x) for all styling in packages/playground-ui components
Use design tokens from src/ds/tokens/index.ts instead of arbitrary Tailwind values (e.g., do not use bg-[#1A1A1A])
Use PascalCase for component names (e.g., EntryList)
Use kebab-case for component filenames (e.g., entry-list.tsx)
Use named exports only; avoid default exports in components
Use TanStack Query for all data fetching hooks in packages/playground-ui
Use useMastraClient SDK for API calls instead of direct fetch() calls
Export explicit prop types separately from components (e.g., export type ComponentProps = {...})
Prefer derived values over useState + useEffect; calculate values directly when possible
Keep type definitions alongside components within the same file
Minimize useEffect usage in components; only use it when necessary

packages/playground-ui/src/**/*.{ts,tsx}: Use Tailwind CSS v3.x for all styling in packages/playground-ui
Use design tokens from src/ds/tokens/index.ts for Tailwind styling in packages/playground-ui; forbidden to use arbitrary values (e.g., bg-[#1A1A1A]) unless explicitly requested
Use PascalCase for component names in packages/playground-ui
Use kebab-case for component file names in packages/playground-ui
Use named exports only; avoid default exports in packages/playground-ui
Use TanStack Query for all data-fetching hooks in packages/playground-ui; forbidden to use direct fetch() calls
Use useMastraClient SDK for API calls in data-fetching hooks in packages/playground-ui
Export explicit prop types separately from components in packages/playground-ui; keep type definitions alongside components
Prefer derived values over useState + useEffect in packages/playground-ui; minimize useEffect usage and calculate values directly when possible
Use TanStack Query for all server state management in packages/playground-ui

Files:

  • packages/playground-ui/src/domains/workflows/workflow/workflow-default-node.tsx
  • packages/playground-ui/src/domains/workflows/workflow/workflow-step-action-bar.tsx
packages/playground-ui/src/**/*.{tsx,css,scss}

📄 CodeRabbit inference engine (packages/playground-ui/CLAUDE.md)

Prefer Tailwind utilities over custom CSS files for shadows, gradients, and other styles

Files:

  • packages/playground-ui/src/domains/workflows/workflow/workflow-default-node.tsx
  • packages/playground-ui/src/domains/workflows/workflow/workflow-step-action-bar.tsx
packages/playground-ui/src/**/*.{tsx,css}

📄 CodeRabbit inference engine (packages/playground-ui/.cursor/rules/frontend.mdc)

Prefer Tailwind utilities over custom CSS files for shadows, gradients, and other styling; only use CSS files when Tailwind cannot express the style

Files:

  • packages/playground-ui/src/domains/workflows/workflow/workflow-default-node.tsx
  • packages/playground-ui/src/domains/workflows/workflow/workflow-step-action-bar.tsx
🧠 Learnings (5)
📚 Learning: 2025-11-24T16:41:10.784Z
Learnt from: CR
Repo: mastra-ai/mastra PR: 0
File: packages/playground-ui/.cursor/rules/frontend.mdc:0-0
Timestamp: 2025-11-24T16:41:10.784Z
Learning: Applies to packages/playground-ui/src/**/*.{ts,tsx} : Export explicit prop types separately from components in `packages/playground-ui`; keep type definitions alongside components

Applied to files:

  • packages/playground-ui/src/domains/workflows/workflow/workflow-step-action-bar.tsx
📚 Learning: 2025-11-24T16:40:41.785Z
Learnt from: CR
Repo: mastra-ai/mastra PR: 0
File: packages/playground-ui/CLAUDE.md:0-0
Timestamp: 2025-11-24T16:40:41.785Z
Learning: Applies to packages/playground-ui/src/**/*.{ts,tsx} : Export explicit prop types separately from components (e.g., export type ComponentProps = {...})

Applied to files:

  • packages/playground-ui/src/domains/workflows/workflow/workflow-step-action-bar.tsx
📚 Learning: 2025-11-24T16:40:52.090Z
Learnt from: CR
Repo: mastra-ai/mastra PR: 0
File: packages/playground/CLAUDE.md:0-0
Timestamp: 2025-11-24T16:40:52.090Z
Learning: Applies to packages/playground/**/*.tsx : REQUIRED: Export explicit prop types separately as a type definition in packages/playground components

Applied to files:

  • packages/playground-ui/src/domains/workflows/workflow/workflow-step-action-bar.tsx
📚 Learning: 2025-11-24T16:41:20.908Z
Learnt from: CR
Repo: mastra-ai/mastra PR: 0
File: packages/playground/.cursor/rules/frontend.mdc:0-0
Timestamp: 2025-11-24T16:41:20.908Z
Learning: Applies to packages/playground/src/**/*.{ts,tsx} : Export explicit prop types separately from component definitions

Applied to files:

  • packages/playground-ui/src/domains/workflows/workflow/workflow-step-action-bar.tsx
📚 Learning: 2025-10-29T09:37:59.778Z
Learnt from: TheIsrael1
Repo: mastra-ai/mastra PR: 9247
File: packages/core/src/loop/network/index.ts:357-393
Timestamp: 2025-10-29T09:37:59.778Z
Learning: In the agent network routing completion stream (packages/core/src/loop/network/index.ts), when `completionStream.error` is true, the first stream should not have written any text (currentTextIdx should be 0), so the fallback reset of currentText and currentTextIdx does not cause duplication.

Applied to files:

  • packages/core/src/workflows/workflow.ts
🧬 Code graph analysis (7)
packages/playground-ui/src/domains/workflows/workflow/workflow-default-node.tsx (1)
packages/playground/src/lib/utils.ts (1)
  • cn (6-8)
packages/core/src/loop/workflows/agentic-execution/llm-execution-step.ts (2)
packages/core/src/agent/trip-wire.ts (1)
  • TripWire (35-45)
packages/core/src/processors/runner.ts (1)
  • ProcessorRunner (71-1201)
packages/core/src/stream/types.ts (3)
packages/core/src/llm/model/base.types.ts (1)
  • ToolSet (29-29)
packages/core/src/processors/index.ts (1)
  • OutputProcessorOrWorkflow (227-229)
packages/core/src/workflows/types.ts (1)
  • StepResult (122-127)
packages/playground-ui/src/domains/workflows/workflow/workflow-step-action-bar.tsx (2)
packages/playground-ui/src/domains/workflows/context/use-current-run.tsx (1)
  • TripwireData (4-11)
packages/core/src/stream/base/output.ts (2)
  • tripwire (1025-1027)
  • status (1266-1268)
packages/core/src/workflows/types.ts (2)
observability/mastra/src/model-tracing.ts (1)
  • Record (203-205)
packages/core/src/observability/types/tracing.ts (1)
  • TracingProperties (795-798)
packages/core/src/workflows/workflow.ts (3)
packages/core/src/processors/index.ts (4)
  • Processor (130-184)
  • ProcessorStepSchema (259-259)
  • ProcessorStepOutputSchema (261-261)
  • ProcessorStepOutput (286-286)
packages/core/src/processors/step-schema.ts (3)
  • ProcessorStepSchema (416-416)
  • ProcessorStepOutputSchema (386-410)
  • ProcessorStepOutput (439-439)
packages/core/src/workflows/types.ts (1)
  • WorkflowType (24-24)
packages/core/src/processors/runner.ts (7)
packages/core/src/processors/index.ts (5)
  • Processor (130-184)
  • ProcessorWorkflow (215-215)
  • ProcessorStepOutput (286-286)
  • isProcessorWorkflow (235-252)
  • TripWireOptions (257-257)
packages/core/src/processors/step-schema.ts (1)
  • ProcessorStepOutput (439-439)
packages/core/src/observability/types/tracing.ts (1)
  • TracingContext (787-790)
packages/core/src/agent/trip-wire.ts (2)
  • TripWire (35-45)
  • TripWireOptions (14-25)
packages/core/src/agent/message-list/index.ts (1)
  • MastraDBMessage (58-60)
packages/core/src/memory/types.ts (1)
  • MastraDBMessage (6-6)
packages/core/src/stream/types.ts (1)
  • ChunkType (624-624)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (28)
packages/core/src/loop/workflows/agentic-execution/llm-execution-step.ts (7)

474-475: LGTM!

The maxProcessorRetries parameter with a default of 3 is a reasonable limit. The parameter integrates cleanly with the existing function signature.


718-744: Verify: Using 'tripwire' reason for user-initiated abort.

This bail path is triggered when the abort signal is received (user cancellation). Changing the reason from 'abort' to 'tripwire' may conflate processor-triggered tripwires with user-initiated aborts. Consider whether a distinct reason like 'abort' should be preserved here for clarity.


788-835: LGTM!

The processOutputStep integration is well-structured:

  • TripWire errors are captured separately for retry handling
  • Other errors are re-thrown appropriately
  • The step context (text, toolCalls, finishReason, retryCount) is correctly assembled

843-861: LGTM!

The retry decision logic correctly:

  • Combines both tripwire sources (stream and processOutputStep)
  • Validates retry eligibility against maxProcessorRetries
  • Logs a warning when retry limit is exceeded for debugging visibility

873-882: Verify: Tripwire data only captured from processOutputStep.

The stepTripwireData only captures tripwire information from processOutputStepTripwire, but tripwireTriggered at line 844 includes outputStream.tripwire as well. If a tripwire is triggered via outputStream.tripwire (stream processors), the step result won't contain tripwire details (message, metadata, processorId).

Is this intentional? If stream-triggered tripwires should also populate step tripwire data, consider:

      const stepTripwireData = processOutputStepTripwire
        ? {
            message: processOutputStepTripwire.message,
            retry: processOutputStepTripwire.options?.retry,
            metadata: processOutputStepTripwire.options?.metadata,
            processorId: processOutputStepTripwire.processorId,
          }
+       : outputStream.tripwire
+       ? {
+           message: outputStream.tripwire.message,
+           retry: outputStream.tripwire.options?.retry,
+           metadata: outputStream.tripwire.options?.metadata,
+           processorId: outputStream.tripwire.processorId,
+         }
        : undefined;

913-958: LGTM!

The step result computation is well-designed:

  • The stepReason priority chain (retry → tripwire → error → finishReason) correctly prioritizes retry state
  • shouldContinue properly allows the loop to continue on retry or tool-use scenarios
  • Clearing toolCalls on retry prevents duplicate tool executions
  • processorRetryCount is correctly incremented and propagated

The previous review comment about retryProcessorId has been addressed—it now directly accesses processOutputStepTripwire.processorId.


477-480: Type definitions are correctly implemented.

The code properly accesses inputData.processorRetryCount and passes tripwire to DefaultStepResult. Both fields are correctly typed:

  • llmIterationOutputSchema includes processorRetryCount: z.number().optional() (line 148)
  • DefaultStepResult accepts tripwire?: StepTripwireData in its constructor options
packages/core/src/workflows/types.ts (3)

19-24: LGTM!

The WorkflowType union type is well-documented and provides clear discriminant values for workflow categorization in the UI.


66-72: LGTM!

The StepTripwireInfo interface captures all necessary tripwire metadata fields with appropriate optionality. The Record<string, unknown> for metadata provides flexibility while maintaining type safety.


486-509: No changes needed — processorId is already correctly typed as optional.

The processorId field in the tripwire WorkflowResult variant is already optional (processorId?: string), consistent with StepTripwireInfo.processorId and the TripWire class constructor. The codebase correctly reflects that tripwires can be triggered with or without a processor ID.

Likely an incorrect or invalid review comment.

packages/core/src/workflows/workflow.ts (4)

143-154: LGTM! Output schema correctly uses ProcessorStepOutputSchema.

The processor overload signature correctly specifies ProcessorStepOutputSchema as the output schema type, matching the return type of the execute function implementation. This addresses the previous review comment.


241-285: LGTM! Proper TripWire propagation from agent streams.

The tripwire detection and re-throwing mechanism correctly propagates tripwire data (reason, retry, metadata, processorId) from stream chunks, ensuring the workflow can handle processor rejections appropriately.


573-591: Type guard relies on method existence but doesn't verify method signatures.

The isProcessor type guard checks for the existence of processor methods but doesn't validate their signatures. An object with an id and a processInput property that's a function but with wrong arguments would pass the guard. This is acceptable for runtime duck-typing but worth noting.


407-410: No action needed. The MessageList instantiation pattern used here is correct for UI-triggered workflows. When new MessageList().add(messages, 'input') is called, the tracking Sets (memoryMessages, newUserMessages, etc.) are properly initialized and the 'input' source parameter correctly marks messages in the newUserMessages set. Recording state is managed by the ProcessorRunner (which calls startRecording() before each processor execution), not expected to be inherited from the MessageList constructor. This is the intended pattern when messageList cannot be serialized from the UI.

Likely an incorrect or invalid review comment.

packages/core/src/processors/runner.ts (3)

112-129: LGTM! Type-safe tripwire extraction from workflow result.

The code properly narrows the result type before accessing tripwire properties, addressing the previous review concern about unsafe type assertions.


972-1011: LGTM! Well-documented runProcessOutputStep method.

The JSDoc documentation clearly explains the method's purpose, parameters, and when it runs in the agentic loop. The implementation correctly handles the outputStep phase for both workflows and regular processors.


66-92: LGTM! Clean type union and constructor update.

The ProcessorOrWorkflow type union cleanly expresses the dual nature of processors, and the constructor properly accepts the new type for both input and output processors.

packages/playground-ui/src/domains/workflows/workflow/workflow-step-action-bar.tsx (1)

8-22: Tripwire wiring and dialog behavior look coherent

Importing TripwireData from the shared context, extending WorkflowStepActionBarProps with tripwire and the 'tripwire' status, and adding isTripwireOpen plus the Tripwire dialog are all consistent with the existing input/output/error patterns. The top-level visibility guard now including tripwire ensures the action bar appears when only tripwire data is present, and the dialog payload (reason/retry/metadata/processorId) matches the shared TripwireData shape. No functional issues noted.

Also applies to: 31-45, 56-66, 166-189

packages/playground-ui/src/domains/workflows/workflow/workflow-default-node.tsx (1)

55-58: Tripwire detection and propagation into the action bar look correct

Deriving isTripwire from a failed step that has step.tripwire, mapping that to a displayStatus of 'tripwire', and then passing error={isTripwire ? undefined : step?.error} with tripwire={isTripwire ? step?.tripwire : undefined} into WorkflowStepActionBar cleanly separates tripwire from normal failure. This matches the intent to treat tripwire as a distinct status while still preserving the underlying step data. No behavioral issues spotted here.

Also applies to: 162-164

packages/core/src/stream/types.ts (7)

16-16: LGTM! Import aligns with workflow-as-processor support.

The change from OutputProcessor to OutputProcessorOrWorkflow correctly supports the processor workflow functionality introduced in this PR.


171-195: LGTM! Tripwire support properly integrated.

The generic typing over Tools and extension of reason to include 'tripwire' | 'retry' correctly implements the processor tripwire feature. The switch to MastraStepResult<Tools>[] appropriately propagates tripwire data through the steps array.


220-251: LGTM! Consistent tripwire propagation in step finish payload.

The update to MastraStepResult<Tools>[] is consistent with the FinishPayload changes and ensures tripwire data flows through step finish events.


309-317: LGTM! Well-typed tripwire metadata.

The generic TMetadata parameter and optional fields (retry, metadata, processorId) provide good type safety while maintaining flexibility. The documentation clearly explains each field's purpose.


682-695: LGTM! Output options support processor workflows.

The change to OutputProcessorOrWorkflow[] correctly enables the workflow-as-processor functionality described in the PR objectives.


697-719: LGTM! Clean type extension for tripwire support.

The StepTripwireData interface and MastraStepResult type are well-designed:

  • Clear structure for tripwire metadata
  • Proper extension of AI SDK's StepResult type
  • Excellent documentation
  • Maintains compatibility while adding Mastra-specific functionality

755-756: LGTM! LLMStepResult gains tripwire support.

The addition of the optional tripwire field is consistent with the MastraStepResult pattern and properly typed.

packages/core/src/stream/aisdk/v5/transform.ts (2)

14-27: LGTM! StreamPart correctly extends finish reason.

The extension of finishReason to include 'tripwire' | 'retry' is consistent with the type changes in types.ts and properly documented.


362-369: The cast at line 366 is a necessary architectural bridge between Mastra's internal extended format and AI SDK v5's standard format. No action required.

The type cast narrowing 'tripwire' | 'retry' away to LanguageModelV2FinishReason is intentional and safe. AI SDK v5 gracefully maps unknown finish reason values to the "unknown" sentinel rather than throwing errors. The current implementation is correct: the internal Mastra format (StepResult.reason) supports extended reasons for processor scenarios, but when converting to AI SDK v5 format, these must be narrowed to standard reasons. The comment already documents this necessity.

Likely an incorrect or invalid review comment.

- Add MastraFinishReason type that extends LanguageModelV2FinishReason
  with 'tripwire' | 'retry' for processor scenarios
- Export MastraFinishReason from @mastra/core/stream
- Fix @mastra/ai-sdk helpers.ts to cast to AISDKFinishReason when
  converting Mastra chunks to AI SDK format

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/core/src/stream/types.ts (1)

177-201: Use MastraFinishReason type for consistency.

Line 180 manually defines LanguageModelV2FinishReason | 'tripwire' | 'retry', which duplicates the MastraFinishReason type defined at line 33. Use the named type for DRY and maintainability.

 interface FinishPayload<Tools extends ToolSet = ToolSet> {
   stepResult: {
     /** Includes 'tripwire' and 'retry' for processor scenarios */
-    reason: LanguageModelV2FinishReason | 'tripwire' | 'retry';
+    reason: MastraFinishReason;
     warnings?: LanguageModelV2CallWarning[];
     isContinued?: boolean;
     logprobs?: LanguageModelV1LogProbs;
   };
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6fa1984 and 73d32e5.

📒 Files selected for processing (3)
  • client-sdks/ai-sdk/src/helpers.ts (2 hunks)
  • packages/core/src/stream/index.ts (1 hunks)
  • packages/core/src/stream/types.ts (7 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Run pnpm typecheck to validate TypeScript types across all packages

Files:

  • packages/core/src/stream/index.ts
  • client-sdks/ai-sdk/src/helpers.ts
  • packages/core/src/stream/types.ts
**/*.{ts,tsx,js,jsx,json,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Run pnpm prettier:format to format code and pnpm format to run linting with auto-fix across all packages

Files:

  • packages/core/src/stream/index.ts
  • client-sdks/ai-sdk/src/helpers.ts
  • packages/core/src/stream/types.ts
packages/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

packages/**/*.{ts,tsx}: All packages must use TypeScript with strict type checking enabled
Use telemetry decorators for observability across components

Files:

  • packages/core/src/stream/index.ts
  • packages/core/src/stream/types.ts
🧬 Code graph analysis (1)
packages/core/src/stream/types.ts (4)
packages/core/src/stream/index.ts (1)
  • MastraFinishReason (9-9)
packages/core/src/llm/model/base.types.ts (1)
  • ToolSet (29-29)
packages/core/src/processors/index.ts (1)
  • OutputProcessorOrWorkflow (227-229)
packages/core/src/workflows/types.ts (1)
  • StepResult (122-127)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (9)
packages/core/src/stream/types.ts (7)

16-16: LGTM!

Import correctly updated to support the new workflow-as-processor functionality.


29-33: LGTM!

The new MastraFinishReason type is well-documented and correctly extends the AI SDK's finish reasons with Mastra-specific values.


226-257: Verify asymmetry in stepResult.reason types.

FinishPayload.stepResult.reason (line 180) includes 'tripwire' | 'retry', but StepFinishPayload.stepResult.reason (line 236) remains LanguageModelV2FinishReason. If a step can be rejected by a processor, should this also use MastraFinishReason?


315-323: LGTM!

The TripwirePayload enhancements with generic metadata, retry flag, and processor ID are well-designed and properly documented.


695-695: LGTM!

Type correctly updated to support both traditional processors and workflow-based processors.


703-725: LGTM!

The StepTripwireData and MastraStepResult types are well-structured with clear JSDoc documentation. This cleanly extends the AI SDK's StepResult with Mastra-specific tripwire data.


761-762: LGTM!

Consistent addition of tripwire field to LLMStepResult, aligning with the new tripwire data propagation pattern.

packages/core/src/stream/index.ts (1)

1-10: These types are not currently consumed by external packages and do not need to be exported.

StepTripwireData is only used internally within packages/core/src/stream/aisdk/v5/output-helpers.ts and is imported directly from types.ts rather than from the barrel file. MastraStepResult has no external usage. External packages (playground-ui, react, cli) import from @mastra/core/agent and @mastra/core/workflows but not from the stream module. Adding these exports without demonstrated external demand would expand the public API unnecessarily.

Likely an incorrect or invalid review comment.

client-sdks/ai-sdk/src/helpers.ts (1)

10-14: LGTM: Type union correctly extended.

The addition of DataChunkType and undefined to the union appropriately reflects the return values from the convertMastraChunkToAISDKv5 function.

DanielSLew and others added 2 commits December 8, 2025 10:23
Add instanceof MessageList check before array/object checks to properly
extract messages via result.get.all.db() when a processor returns a
MessageList instance.

PR comment: #10947 (comment)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Add toAISDKFinishReason helper function to properly map Mastra's extended
finish reasons ('tripwire' | 'retry') to AI SDK's 'other' value instead
of just casting. Apply to both 'finish' and 'step-finish' cases.

PR comment: #10947 (comment)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/memory/integration-tests-v5/src/processors.test.ts (1)

488-488: Inconsistent model specification format.

This test uses openai('gpt-4o') while the test at line 372 uses the string literal 'openai/gpt-4o'. According to the AI summary, the model field was updated to use string literals. Ensure consistent usage across all tests.

Apply this diff to align with the updated pattern:

-      model: openai('gpt-4o'),
+      model: 'openai/gpt-4o',
🧹 Nitpick comments (2)
packages/memory/integration-tests-v5/src/processors.test.ts (2)

30-32: Consider aligning abort helper with the enhanced tripwire API.

The PR introduces an enhanced abort signature abort(reason, { retry, metadata }), but this test helper only accepts a reason string. While the current tests don't exercise retry/metadata functionality, consider adding a more complete helper or documenting why the simplified version is sufficient for these integration tests.

Example enhancement:

-const abort: (reason?: string) => never = reason => {
-  throw new Error(reason || 'Aborted');
-};
+const abort = (reason?: string, options?: { retry?: boolean; metadata?: unknown }): never => {
+  const error: any = new Error(reason || 'Aborted');
+  if (options) {
+    error.retry = options.retry;
+    error.metadata = options.metadata;
+  }
+  throw error;
+};

612-678: Address or remove skipped test with TODO.

This test is skipped with a TODO at line 650 questioning the test's purpose. The test appears to reference memory.processMessages() which may no longer exist. Consider either:

  • Updating the test to use the current Processor API
  • Removing the test if the functionality is covered elsewhere
  • Creating a tracking issue to revisit this later

Do you want me to help identify whether this test's functionality is covered by other tests, or open an issue to track this technical debt?

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b49d156 and f78f05b.

📒 Files selected for processing (2)
  • docs/src/content/en/reference/processors/processor-interface.mdx (7 hunks)
  • packages/memory/integration-tests-v5/src/processors.test.ts (14 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Run pnpm typecheck to validate TypeScript types across all packages

Files:

  • packages/memory/integration-tests-v5/src/processors.test.ts
**/*.{ts,tsx,js,jsx,json,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Run pnpm prettier:format to format code and pnpm format to run linting with auto-fix across all packages

Files:

  • packages/memory/integration-tests-v5/src/processors.test.ts
packages/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

packages/**/*.{ts,tsx}: All packages must use TypeScript with strict type checking enabled
Use telemetry decorators for observability across components

Files:

  • packages/memory/integration-tests-v5/src/processors.test.ts
**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{test,spec}.{ts,tsx}: Use Vitest for testing framework in test files
Co-locate test files with source code using naming patterns like *.test.ts or *.spec.ts

Files:

  • packages/memory/integration-tests-v5/src/processors.test.ts
**/*.{md,mdx}

📄 CodeRabbit inference engine (.cursor/rules/writing-documentation.mdc)

**/*.{md,mdx}: When writing developer documentation, do not use adjectives like 'powerful' or 'built-in' as these read like marketing copy and developers don't like that
When writing developer documentation, do not use 'complete', 'out-of-the-box', 'hands-on', or overly enthusiastic exhortations like 'Check out', 'Learn more', 'Explore'. Do not use words like 'essential' or 'offers'
When writing developer documentation, do not use 'your needs', 'production-ready', 'makes it easy', or 'choose the right...solution' as these are marketing jargon that developers dislike
When writing developer documentation, avoid phrasing like 'without changing your code' or 'automatically handles' that obscures implementation details
In developer documentation, avoid phrasing that glides between benefits without diving into details. Focus on technical specifics and implementation details rather than high-level benefits. For example, avoid sentences like: 'This makes it easy to build AI applications that maintain meaningful conversations and remember important details, whether you're building a simple chatbot or a sophisticated AI assistant'
All H1 headings (# Heading) must use title case format, capitalizing the first letter of each major word. Examples: 'Getting Started', 'Human In-the-Loop Workflow', 'Agent as a Step'

Files:

  • docs/src/content/en/reference/processors/processor-interface.mdx
**/*{docs,documentation}/**/*.{md,mdx}

📄 CodeRabbit inference engine (.windsurfrules)

**/*{docs,documentation}/**/*.{md,mdx}: When writing developer documentation, do not use adjectives like 'powerful' or 'built-in' as they read like marketing copy
When writing developer documentation, do not use words like 'complete', 'out-of-the-box', 'hands-on', or overly enthusiastic exhortations such as 'Check out', 'Learn more', 'Explore', 'essential', or 'offers'
When writing developer documentation, avoid marketing jargon such as 'your needs', 'production-ready', 'makes it easy', or 'choose the right...solution'
When writing developer documentation, do not use phrases like 'without changing your code' or 'automatically handles' as they glide over implementation details
In developer documentation, avoid phrasing that glides between benefits without diving into details; instead, focus on technical nuts and bolts with specific implementation details rather than abstract benefits

Files:

  • docs/src/content/en/reference/processors/processor-interface.mdx
🧠 Learnings (10)
📚 Learning: 2025-11-24T16:42:04.244Z
Learnt from: CR
Repo: mastra-ai/mastra PR: 0
File: packages/codemod/AGENTS.md:0-0
Timestamp: 2025-11-24T16:42:04.244Z
Learning: Applies to packages/codemod/src/test/**/*.test.ts : Include test cases for multiple occurrences of the transformation pattern, aliased imports, type imports, and mixed imports to verify the codemod works consistently

Applied to files:

  • packages/memory/integration-tests-v5/src/processors.test.ts
📚 Learning: 2025-11-24T16:42:04.244Z
Learnt from: CR
Repo: mastra-ai/mastra PR: 0
File: packages/codemod/AGENTS.md:0-0
Timestamp: 2025-11-24T16:42:04.244Z
Learning: Applies to packages/codemod/src/test/__fixtures__/**/*.ts : In output fixtures, ensure all NEGATIVE test cases remain EXACTLY IDENTICAL to their input fixture counterparts to verify the codemod only transforms intended patterns

Applied to files:

  • packages/memory/integration-tests-v5/src/processors.test.ts
📚 Learning: 2025-11-24T16:42:04.244Z
Learnt from: CR
Repo: mastra-ai/mastra PR: 0
File: packages/codemod/AGENTS.md:0-0
Timestamp: 2025-11-24T16:42:04.244Z
Learning: Applies to packages/codemod/src/codemods/v1/**/*.ts : When transforming TypeScript type imports and usages, check the parent node type to skip transforming identifiers that are part of import declarations to avoid transforming imports from other packages with the same type name

Applied to files:

  • packages/memory/integration-tests-v5/src/processors.test.ts
📚 Learning: 2025-11-24T16:42:04.244Z
Learnt from: CR
Repo: mastra-ai/mastra PR: 0
File: packages/codemod/AGENTS.md:0-0
Timestamp: 2025-11-24T16:42:04.244Z
Learning: Applies to packages/codemod/src/codemods/v1/**/*.ts : When renaming function/value imports and all their usages, use the `renameImportAndUsages()` utility which handles both the import declaration and usages in a single pass, and correctly handles aliased imports

Applied to files:

  • packages/memory/integration-tests-v5/src/processors.test.ts
📚 Learning: 2025-11-24T16:42:04.244Z
Learnt from: CR
Repo: mastra-ai/mastra PR: 0
File: packages/codemod/AGENTS.md:0-0
Timestamp: 2025-11-24T16:42:04.244Z
Learning: Applies to packages/codemod/src/codemods/v1/**/*.ts : Prefer using shared utility functions (`trackClassInstances()`, `renameMethod()`, `renameMethods()`, `transformMethodCalls()`, `renameImportAndUsages()`, `transformConstructorProperties()`, `transformObjectProperties()`) instead of writing custom AST transformation logic

Applied to files:

  • packages/memory/integration-tests-v5/src/processors.test.ts
📚 Learning: 2025-11-24T16:42:04.244Z
Learnt from: CR
Repo: mastra-ai/mastra PR: 0
File: packages/codemod/AGENTS.md:0-0
Timestamp: 2025-11-24T16:42:04.244Z
Learning: Applies to packages/codemod/**/*.ts : Always optimize for minimal AST traversals: use shared utility functions from `src/codemods/lib/utils.ts`, combine multiple operations into single passes, add early returns when no changes needed, and track instances once and reuse the Set

Applied to files:

  • packages/memory/integration-tests-v5/src/processors.test.ts
📚 Learning: 2025-11-24T16:42:04.244Z
Learnt from: CR
Repo: mastra-ai/mastra PR: 0
File: packages/codemod/AGENTS.md:0-0
Timestamp: 2025-11-24T16:42:04.244Z
Learning: Applies to packages/codemod/src/codemods/v1/**/*.ts : When tracking which package an import came from, verify the import source before transforming usages to avoid transforming identifiers from other packages with the same name

Applied to files:

  • packages/memory/integration-tests-v5/src/processors.test.ts
📚 Learning: 2025-11-24T16:42:04.244Z
Learnt from: CR
Repo: mastra-ai/mastra PR: 0
File: packages/codemod/AGENTS.md:0-0
Timestamp: 2025-11-24T16:42:04.244Z
Learning: Applies to packages/codemod/src/test/**/*.test.ts : Do NOT use `UPDATE_SNAPSHOT` to force tests to pass; instead fix the codemod implementation when tests fail

Applied to files:

  • packages/memory/integration-tests-v5/src/processors.test.ts
📚 Learning: 2025-11-24T16:42:04.244Z
Learnt from: CR
Repo: mastra-ai/mastra PR: 0
File: packages/codemod/AGENTS.md:0-0
Timestamp: 2025-11-24T16:42:04.244Z
Learning: Applies to packages/codemod/src/test/__fixtures__/**/*.ts : In input fixtures, include both POSITIVE test cases (patterns that should transform) and NEGATIVE test cases (unrelated code with similar names/patterns that should NOT transform)

Applied to files:

  • packages/memory/integration-tests-v5/src/processors.test.ts
📚 Learning: 2025-11-24T16:42:04.244Z
Learnt from: CR
Repo: mastra-ai/mastra PR: 0
File: packages/codemod/AGENTS.md:0-0
Timestamp: 2025-11-24T16:42:04.244Z
Learning: Applies to packages/codemod/src/test/__fixtures__/**/*.ts : Create test fixtures by copying examples DIRECTLY from migration guides in `docs/src/content/en/guides/migrations/upgrade-to-v1/` without hallucinating or inventing changes

Applied to files:

  • packages/memory/integration-tests-v5/src/processors.test.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Socket Security: Pull Request Alerts
  • GitHub Check: test
  • GitHub Check: test
  • GitHub Check: Check for deleted pages without redirects
  • GitHub Check: test
  • GitHub Check: test (express)
  • GitHub Check: test (hono)
  • GitHub Check: Validate peer dependencies
  • GitHub Check: Prebuild
  • GitHub Check: Lint
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (4)
docs/src/content/en/reference/processors/processor-interface.mdx (2)

376-491: Excellent addition of comprehensive processOutputStep documentation.

The new section clearly explains the per-step output processing capability, including the retry mechanism. The example is practical and demonstrates proper use of retryCount to limit retry attempts and structured metadata passing.

Verify that the abort signature with retry/metadata options is consistently implemented across all processor methods (particularly in processInputStep at line 236-239, which currently shows only (reason?: string) => never). If processInputStep also supports retry signaling, its documentation should match processOutputStep's abort signature for consistency.


139-148: Clear documentation of retryCount parameter.

The new retryCount parameter is well-documented in both ProcessInputArgs (lines 143-148) and its usage in the processOutputStep example is appropriate. However, ensure processInputStep documentation (around line 236-240) reflects the same parameter if it's available in both methods.

packages/memory/integration-tests-v5/src/processors.test.ts (2)

343-350: LGTM - Clean Processor implementation.

The ConversationOnlyFilter correctly implements the Processor interface with appropriate id, name, and processInput method. The simple filtering logic is appropriate for this test scenario.


557-600: LGTM - Well-structured processor testing.

This refactored section clearly tests processors directly with appropriate comments explaining the behavior. The sequential application of ToolCallFilter and TokenLimiter with explicit requestContext demonstrates proper processor composition.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/core/src/agent/agent.ts (1)

3118-3146: Extend tripwire handling to resumeStream as well.

resumeStream() calls #execute() and then has the same result.status !== 'success' check as stream(), but it also lacks an explicit 'tripwire' branch. If a resumed run trips a processor guardrail, it will currently surface as an “unknown error” instead of a structured TripWire.

Mirror the result.status === 'tripwire' handling suggested for generate/stream here too (throwing a TripWire built from result.tripwire) before the 'failed'/unknown fallbacks so all streaming entry points behave consistently.

♻️ Duplicate comments (3)
packages/core/src/loop/workflows/stream.ts (1)

182-195: Finish chunk semantics look good; consider aligning types to avoid @ts-expect-error.

Emitting a finish chunk even for tripwire/abort cases is a solid change and should make stream consumers more reliable, since they always see a terminal event driven by stepResult.reason.

The remaining rough edge here is the // @ts-expect-error around stepResult.reason. The comment explains that runtime values can be 'tripwire' | 'retry' from processors while the Zod-inferred type is just string/a narrower union. Long‑term, it would be cleaner to update the underlying StepResult/workflow result types (and/or the Zod schema driving them) so that reason formally includes the processor-driven values ('tripwire' | 'retry'), and then drop this suppression.

That keeps the runtime behavior you want while letting the type system accurately model it and catch regressions.

packages/core/src/workflows/workflow.test.ts (1)

17898-17905: Align vNext stream typing and drop unused chunks accumulator

run.streamVNext(...).fullStream yields the vNext workflow stream type (likely WorkflowStreamEvent or ChunkType), but here it’s collected into StreamEvent[], which is intended for the legacy streamLegacy path and may fail strict typechecking again. You also never assert on chunks, so the accumulator is effectively dead code.

Consider either:

  • Switching the type to the correct vNext event type (e.g. ChunkType[] or WorkflowStreamEvent[]), or
  • Removing the variable entirely and just draining the stream:
-      const chunks: StreamEvent[] = [];
       const streamResult = run.streamVNext({ inputData: { prompt: 'This has forbidden content' } });
 
       // Collect all chunks
-      for await (const chunk of streamResult.fullStream) {
-        chunks.push(chunk);
-      }
+      for await (const _chunk of streamResult.fullStream) {
+        // no-op – just drain the stream to completion
+      }

This keeps the test focused on the workflow-level tripwire assertion and avoids vNext/legacy type confusion.

#!/bin/bash
# From repo root, verify the vNext stream type for `run.streamVNext().fullStream`
rg -n "streamVNext\(" packages/core/src/workflows -C3
rg -n "type WorkflowStreamEvent" packages/core/src/workflows -C3
rg -n "type ChunkType" packages/core/src/workflows -C3
pnpm typecheck --filter @mastra/core
packages/core/src/agent/agent.ts (1)

2962-2990: Handle result.status === 'tripwire' explicitly in generate and stream.

The underlying workflow can now return a 'tripwire' status when processors abort via TripWire, but generate() and stream() still only branch on 'success' and 'failed', treating 'tripwire' as an unknown error and dropping structured tripwire data. This was already flagged earlier and remains unresolved.

You can map tripwire results back to a TripWire error so callers and UI layers can handle them predictably. For example:

   const result = await this.#execute(executeOptions);

-  if (result.status !== 'success') {
-    if (result.status === 'failed') {
+  if (result.status !== 'success') {
+    if (result.status === 'tripwire') {
+      const tripwire = (result as any).tripwire;
+      throw new TripWire(
+        tripwire?.reason ?? 'Agent tripwire triggered',
+        { retry: tripwire?.retry, metadata: tripwire?.metadata },
+        tripwire?.processorId,
+      );
+    }
+    if (result.status === 'failed') {
       throw new MastraError(
@@
-    }
-    throw new MastraError({
+    }
+    throw new MastraError({
       id: 'AGENT_GENERATE_UNKNOWN_ERROR',
@@
-    });
+    });
   }

Apply the same pattern in stream() before the generic unknown-error case, using an appropriate error id/message for streaming; this preserves processor tripwire details end‑to‑end.

Also applies to: 3051-3069

🧹 Nitpick comments (8)
packages/core/src/workflows/default.ts (1)

267-347: TripWire-to-plain-object conversion in executeStepWithRetry looks correct; consider de-duplicating mapping logic

The new return shape with an optional error.tripwire?: StepTripwireInfo and the e instanceof TripWire guard correctly preserves reason, retry, metadata, and processorId in a serializable form while still wrapping the error in MastraError for logging/telemetry. This aligns with the PR goal of exposing structured tripwire data without leaking class instances.

You now have very similar TripWire→POJO mapping logic here and again in fmtReturnValue. To avoid drift if the TripWire options shape or StepTripwireInfo evolve, consider extracting a small helper (e.g., toStepTripwireInfo(e: TripWire): StepTripwireInfo) and reusing it in both places.

packages/core/src/processors/runner.ts (1)

908-1111: Well-designed implementation of per-step output processing.

The new runProcessOutputStep method provides a solid foundation for guardrails and retry logic at each agentic loop step. The implementation correctly:

  • Handles both workflows and regular processors
  • Threads retryCount through for retry awareness
  • Captures tripwire metadata for observability (lines 1093-1102)
  • Provides comprehensive JSDoc explaining the use case

Minor improvement - Line 1076 uses (p: any) type assertion:

               const systemText =
                 (message.content.content as string | undefined) ??
-                message.content.parts?.map((p: any) => (p.type === 'text' ? p.text : '')).join('\n') ??
+                message.content.parts?.map(p => (p.type === 'text' ? p.text : '')).join('\n') ??
                 '';

The type can be inferred from message.content.parts rather than using any.

packages/core/src/loop/workflows/agentic-loop/index.ts (1)

128-145: Verify the step-finish emission logic handles all tripwire scenarios correctly.

The new emission logic introduces conditional step-finish events based on tripwire status and step presence. The condition reason !== 'tripwire' || hasSteps means:

  • Emit for all non-tripwire cases
  • Emit for tripwire only when steps exist
  • Skip emission only for tripwire with no steps

Ensure this logic properly handles all edge cases, particularly around max retry scenarios, processor rejections, and downstream stream consumers that depend on step-finish events.

Additionally, the @ts-ignore at line 141 indicates a type mismatch that should be resolved:

Apply proper typing to the payload instead of using @ts-ignore:

// Consider defining a proper type for the step-finish payload
type StepFinishPayload = {
  output: LLMIterationData<Tools, OUTPUT>['output'];
  stepResult: LLMIterationData<Tools, OUTPUT>['stepResult'];
  metadata: LLMIterationData<Tools, OUTPUT>['metadata'];
  messages: LLMIterationData<Tools, OUTPUT>['messages'];
};

controller.enqueue({
  type: 'step-finish',
  runId,
  from: ChunkFrom.AGENT,
  payload: typedInputData as StepFinishPayload,
});
packages/core/src/workflows/workflow.test.ts (1)

17993-17997: Close the new describe block consistently

The new describe('Agent TripWire in Workflow', ...) block currently ends with }) instead of });, whereas the rest of this file consistently terminates describes/its with a semicolon. Not a functional issue (ASI will handle it), but it will likely be auto‑fixed by Prettier.

I’d let the formatter normalize this or update manually to:

-  })
+  });
packages/core/src/processors/step-schema.ts (3)

151-167: Clarify comments for MessagePart usage and role enum

The comments here are slightly out of sync with the actual schemas:

  • The text says “uses the MessagePartSchema discriminated union”, but MessagePartSchema is a plain union of part shapes (with DataPartSchema.type being a refined string), not a Zod discriminatedUnion.
  • The “Key fields” section lists role: 'user' | 'assistant' | 'system', while the schema also allows 'tool'.

To avoid confusion for future readers, it’s worth tightening the comments to match the implementation.

-/**
- * Schema for message content in processor workflows.
- * Uses the MessagePartSchema discriminated union for proper UI rendering.
- */
+/**
+ * Schema for message content in processor workflows.
+ * Uses MessagePartSchema (a union of discriminated part types) for proper UI rendering.
+ */
@@
- * - role: 'user' | 'assistant' | 'system' - Message role
+ * - role: 'user' | 'assistant' | 'system' | 'tool' - Message role

Also applies to: 174-199


130-145: Factor out shared base for message content schemas to avoid drift

MessageContentSchema and ProcessorMessageContentSchema currently duplicate the same field structure, with the only behavioral difference being .passthrough() on the processor variant. Refactoring to a shared base reduces the chance of them drifting out of sync.

-/**
- * Message content structure (MastraMessageContentV2 format)
- * This is a documentation-friendly schema with properly typed parts.
- */
-export const MessageContentSchema = z.object({
-  /** Format version - 2 corresponds to AI SDK v4 UIMessage format */
-  format: z.literal(2),
-  /** Array of message parts (text, images, tool calls, etc.) */
-  parts: z.array(MessagePartSchema),
-  /** Legacy content field for backwards compatibility */
-  content: z.string().optional(),
-  /** Additional metadata */
-  metadata: z.record(z.unknown()).optional(),
-  /** Provider-specific metadata */
-  providerMetadata: z.record(z.unknown()).optional(),
-});
+/**
+ * Base message content schema (MastraMessageContentV2 format).
+ * Used by both docs-facing and processor-facing schemas.
+ */
+const BaseMessageContentSchema = z.object({
+  /** Format version - 2 corresponds to AI SDK v4 UIMessage format */
+  format: z.literal(2),
+  /** Array of message parts (text, images, tool calls, etc.) */
+  parts: z.array(MessagePartSchema),
+  /** Legacy content field for backwards compatibility */
+  content: z.string().optional(),
+  /** Additional metadata */
+  metadata: z.record(z.unknown()).optional(),
+  /** Provider-specific metadata */
+  providerMetadata: z.record(z.unknown()).optional(),
+});
+
+export const MessageContentSchema = BaseMessageContentSchema;
@@
-export const ProcessorMessageContentSchema = z
-  .object({
-    /** Format version - 2 corresponds to AI SDK v4 UIMessage format */
-    format: z.literal(2),
-    /** Array of message parts (text, images, tool calls, etc.) */
-    parts: z.array(MessagePartSchema),
-    /** Legacy content field for backwards compatibility */
-    content: z.string().optional(),
-    /** Additional metadata */
-    metadata: z.record(z.unknown()).optional(),
-    /** Provider-specific metadata */
-    providerMetadata: z.record(z.unknown()).optional(),
-  })
-  .passthrough();
+export const ProcessorMessageContentSchema = BaseMessageContentSchema.passthrough();

Please re-run pnpm typecheck after applying this to ensure all imports/uses of these schemas still typecheck cleanly.

Also applies to: 155-168


296-299: Encode invariants for retryCount and stepNumber as non‑negative integers

retryCount (a count of retries) and stepNumber (documented as 0‑indexed) are currently plain numbers. Tightening them to non‑negative integers better matches the semantics and can catch accidental misuse at validation time.

-/**
- * Number of times processors have triggered retry for this generation.
- */
-const retryCountSchema = z.number().optional();
+/**
+ * Number of times processors have triggered retry for this generation.
+ * Non-negative integer count.
+ */
+const retryCountSchema = z.number().int().min(0).optional();
@@
   phase: z.literal('inputStep'),
   messages: messagesSchema,
   messageList: messageListSchema,
-  stepNumber: z.number().describe('The current step number (0-indexed)'),
+  stepNumber: z
+    .number()
+    .int()
+    .min(0)
+    .describe('The current step number (0-indexed)'),
@@
   phase: z.literal('outputStep'),
   messages: messagesSchema,
   messageList: messageListSchema,
-  stepNumber: z.number().describe('The current step number (0-indexed)'),
+  stepNumber: z
+    .number()
+    .int()
+    .min(0)
+    .describe('The current step number (0-indexed)'),
@@
   // Retry count
-  retryCount: z.number().optional(),
+  retryCount: retryCountSchema,

After this change, please verify any existing test fixtures or mocked processor step payloads that might currently pass non-integer or negative values.

Also applies to: 321-328, 358-368, 400-424

packages/core/src/stream/types.ts (1)

29-34: Consider actually using MastraFinishReason or removing it.

MastraFinishReason is defined but not referenced; FinishPayload.stepResult.reason repeats the raw union instead. Either switch that field (and any other extended-reason sites) to MastraFinishReason or drop the alias to avoid dead types.

Also applies to: 190-201

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f78f05b and 3e4653a.

📒 Files selected for processing (22)
  • client-sdks/client-js/src/types.ts (1 hunks)
  • client-sdks/react/src/lib/ai-sdk/utils/toUIMessage.test.ts (2 hunks)
  • client-sdks/react/src/lib/ai-sdk/utils/toUIMessage.ts (2 hunks)
  • packages/core/src/agent/agent.ts (16 hunks)
  • packages/core/src/agent/agent.types.ts (2 hunks)
  • packages/core/src/agent/message-list/index.ts (2 hunks)
  • packages/core/src/llm/model/model.loop.ts (2 hunks)
  • packages/core/src/loop/test-utils/options.ts (8 hunks)
  • packages/core/src/loop/types.ts (3 hunks)
  • packages/core/src/loop/workflows/agentic-loop/index.ts (2 hunks)
  • packages/core/src/loop/workflows/stream.ts (2 hunks)
  • packages/core/src/processors/runner.ts (17 hunks)
  • packages/core/src/processors/step-schema.ts (1 hunks)
  • packages/core/src/stream/index.ts (1 hunks)
  • packages/core/src/stream/types.ts (8 hunks)
  • packages/core/src/workflows/default.ts (5 hunks)
  • packages/core/src/workflows/handlers/control-flow.ts (1 hunks)
  • packages/core/src/workflows/types.ts (8 hunks)
  • packages/core/src/workflows/workflow.test.ts (1 hunks)
  • packages/core/src/workflows/workflow.ts (12 hunks)
  • packages/server/src/server/handlers/agent.test.ts (1 hunks)
  • packages/server/src/server/handlers/agents.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (10)
  • client-sdks/react/src/lib/ai-sdk/utils/toUIMessage.ts
  • packages/core/src/llm/model/model.loop.ts
  • packages/server/src/server/handlers/agents.ts
  • client-sdks/client-js/src/types.ts
  • client-sdks/react/src/lib/ai-sdk/utils/toUIMessage.test.ts
  • packages/core/src/agent/message-list/index.ts
  • packages/core/src/loop/test-utils/options.ts
  • packages/core/src/loop/types.ts
  • packages/server/src/server/handlers/agent.test.ts
  • packages/core/src/workflows/types.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Run pnpm typecheck to validate TypeScript types across all packages

Files:

  • packages/core/src/loop/workflows/agentic-loop/index.ts
  • packages/core/src/workflows/handlers/control-flow.ts
  • packages/core/src/stream/index.ts
  • packages/core/src/processors/runner.ts
  • packages/core/src/agent/agent.types.ts
  • packages/core/src/stream/types.ts
  • packages/core/src/workflows/workflow.ts
  • packages/core/src/loop/workflows/stream.ts
  • packages/core/src/workflows/workflow.test.ts
  • packages/core/src/agent/agent.ts
  • packages/core/src/workflows/default.ts
  • packages/core/src/processors/step-schema.ts
**/*.{ts,tsx,js,jsx,json,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Run pnpm prettier:format to format code and pnpm format to run linting with auto-fix across all packages

Files:

  • packages/core/src/loop/workflows/agentic-loop/index.ts
  • packages/core/src/workflows/handlers/control-flow.ts
  • packages/core/src/stream/index.ts
  • packages/core/src/processors/runner.ts
  • packages/core/src/agent/agent.types.ts
  • packages/core/src/stream/types.ts
  • packages/core/src/workflows/workflow.ts
  • packages/core/src/loop/workflows/stream.ts
  • packages/core/src/workflows/workflow.test.ts
  • packages/core/src/agent/agent.ts
  • packages/core/src/workflows/default.ts
  • packages/core/src/processors/step-schema.ts
packages/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

packages/**/*.{ts,tsx}: All packages must use TypeScript with strict type checking enabled
Use telemetry decorators for observability across components

Files:

  • packages/core/src/loop/workflows/agentic-loop/index.ts
  • packages/core/src/workflows/handlers/control-flow.ts
  • packages/core/src/stream/index.ts
  • packages/core/src/processors/runner.ts
  • packages/core/src/agent/agent.types.ts
  • packages/core/src/stream/types.ts
  • packages/core/src/workflows/workflow.ts
  • packages/core/src/loop/workflows/stream.ts
  • packages/core/src/workflows/workflow.test.ts
  • packages/core/src/agent/agent.ts
  • packages/core/src/workflows/default.ts
  • packages/core/src/processors/step-schema.ts
**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{test,spec}.{ts,tsx}: Use Vitest for testing framework in test files
Co-locate test files with source code using naming patterns like *.test.ts or *.spec.ts

Files:

  • packages/core/src/workflows/workflow.test.ts
🧠 Learnings (7)
📚 Learning: 2025-10-29T09:37:59.778Z
Learnt from: TheIsrael1
Repo: mastra-ai/mastra PR: 9247
File: packages/core/src/loop/network/index.ts:357-393
Timestamp: 2025-10-29T09:37:59.778Z
Learning: In the agent network routing completion stream (packages/core/src/loop/network/index.ts), when `completionStream.error` is true, the first stream should not have written any text (currentTextIdx should be 0), so the fallback reset of currentText and currentTextIdx does not cause duplication.

Applied to files:

  • packages/core/src/loop/workflows/agentic-loop/index.ts
  • packages/core/src/agent/agent.types.ts
  • packages/core/src/workflows/workflow.ts
  • packages/core/src/loop/workflows/stream.ts
  • packages/core/src/workflows/workflow.test.ts
  • packages/core/src/agent/agent.ts
📚 Learning: 2025-11-24T16:42:04.244Z
Learnt from: CR
Repo: mastra-ai/mastra PR: 0
File: packages/codemod/AGENTS.md:0-0
Timestamp: 2025-11-24T16:42:04.244Z
Learning: Applies to packages/codemod/src/codemods/v1/**/*.ts : Use `context.hasChanges = true` flag and `context.messages.push()` to track and report what transformations were made in the codemod

Applied to files:

  • packages/core/src/workflows/workflow.ts
📚 Learning: 2025-11-24T16:42:04.244Z
Learnt from: CR
Repo: mastra-ai/mastra PR: 0
File: packages/codemod/AGENTS.md:0-0
Timestamp: 2025-11-24T16:42:04.244Z
Learning: Applies to packages/codemod/src/test/__fixtures__/**/*.ts : In output fixtures, ensure all NEGATIVE test cases remain EXACTLY IDENTICAL to their input fixture counterparts to verify the codemod only transforms intended patterns

Applied to files:

  • packages/core/src/workflows/workflow.test.ts
📚 Learning: 2025-11-24T16:42:04.244Z
Learnt from: CR
Repo: mastra-ai/mastra PR: 0
File: packages/codemod/AGENTS.md:0-0
Timestamp: 2025-11-24T16:42:04.244Z
Learning: Applies to packages/codemod/src/test/**/*.test.ts : Include test cases for multiple occurrences of the transformation pattern, aliased imports, type imports, and mixed imports to verify the codemod works consistently

Applied to files:

  • packages/core/src/workflows/workflow.test.ts
📚 Learning: 2025-11-24T16:42:04.244Z
Learnt from: CR
Repo: mastra-ai/mastra PR: 0
File: packages/codemod/AGENTS.md:0-0
Timestamp: 2025-11-24T16:42:04.244Z
Learning: Applies to packages/codemod/src/test/__fixtures__/**/*.ts : In input fixtures, include both POSITIVE test cases (patterns that should transform) and NEGATIVE test cases (unrelated code with similar names/patterns that should NOT transform)

Applied to files:

  • packages/core/src/workflows/workflow.test.ts
📚 Learning: 2025-11-24T16:42:04.244Z
Learnt from: CR
Repo: mastra-ai/mastra PR: 0
File: packages/codemod/AGENTS.md:0-0
Timestamp: 2025-11-24T16:42:04.244Z
Learning: Applies to packages/codemod/src/test/__fixtures__/**/*.ts : Create test fixtures by copying examples DIRECTLY from migration guides in `docs/src/content/en/guides/migrations/upgrade-to-v1/` without hallucinating or inventing changes

Applied to files:

  • packages/core/src/workflows/workflow.test.ts
📚 Learning: 2025-11-24T16:42:04.244Z
Learnt from: CR
Repo: mastra-ai/mastra PR: 0
File: packages/codemod/AGENTS.md:0-0
Timestamp: 2025-11-24T16:42:04.244Z
Learning: Applies to packages/codemod/src/codemods/v1/**/*.ts : When transforming TypeScript type imports and usages, check the parent node type to skip transforming identifiers that are part of import declarations to avoid transforming imports from other packages with the same type name

Applied to files:

  • packages/core/src/agent/agent.ts
🧬 Code graph analysis (6)
packages/core/src/processors/runner.ts (8)
packages/core/src/processors/index.ts (5)
  • Processor (130-184)
  • ProcessorWorkflow (215-215)
  • ProcessorStepOutput (286-286)
  • isProcessorWorkflow (235-252)
  • TripWireOptions (257-257)
packages/core/src/processors/step-schema.ts (1)
  • ProcessorStepOutput (453-453)
packages/core/src/observability/types/tracing.ts (1)
  • TracingContext (863-866)
packages/core/src/agent/trip-wire.ts (2)
  • TripWire (35-45)
  • TripWireOptions (14-25)
packages/core/src/agent/message-list/index.ts (2)
  • MessageList (124-3400)
  • MastraDBMessage (75-77)
packages/core/src/agent/types.ts (2)
  • MessageList (42-42)
  • MastraDBMessage (42-42)
packages/core/src/memory/types.ts (1)
  • MastraDBMessage (6-6)
packages/core/src/stream/types.ts (1)
  • ChunkType (646-646)
packages/core/src/agent/agent.types.ts (1)
packages/core/src/processors/index.ts (2)
  • InputProcessorOrWorkflow (220-222)
  • OutputProcessorOrWorkflow (227-229)
packages/core/src/stream/types.ts (3)
packages/core/src/llm/model/base.types.ts (1)
  • ToolSet (29-29)
packages/core/src/processors/index.ts (1)
  • OutputProcessorOrWorkflow (227-229)
packages/core/src/workflows/types.ts (1)
  • StepResult (124-129)
packages/core/src/workflows/workflow.ts (4)
packages/core/src/processors/index.ts (4)
  • Processor (130-184)
  • ProcessorStepSchema (259-259)
  • ProcessorStepOutputSchema (261-261)
  • ProcessorStepOutput (286-286)
packages/core/src/processors/step-schema.ts (3)
  • ProcessorStepSchema (430-430)
  • ProcessorStepOutputSchema (400-424)
  • ProcessorStepOutput (453-453)
packages/core/src/agent/trip-wire.ts (1)
  • TripWire (35-45)
packages/core/src/agent/types.ts (2)
  • MessageList (42-42)
  • MastraDBMessage (42-42)
packages/core/src/agent/agent.ts (3)
packages/core/src/processors/index.ts (4)
  • InputProcessorOrWorkflow (220-222)
  • OutputProcessorOrWorkflow (227-229)
  • isProcessorWorkflow (235-252)
  • ProcessorWorkflow (215-215)
packages/core/src/workflows/workflow.ts (2)
  • isProcessor (670-684)
  • createWorkflow (686-702)
packages/core/src/workflows/evented/workflow.ts (1)
  • createWorkflow (303-332)
packages/core/src/workflows/default.ts (2)
packages/core/src/workflows/types.ts (1)
  • StepTripwireInfo (69-74)
packages/core/src/agent/trip-wire.ts (1)
  • TripWire (35-45)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Socket Security: Pull Request Alerts
  • GitHub Check: test (express)
  • GitHub Check: Prebuild
  • GitHub Check: Lint
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (15)
packages/core/src/workflows/default.ts (2)

1-41: TripWire / StepTripwireInfo imports are appropriate for new tripwire support

Importing TripWire and StepTripwireInfo here makes sense given this engine now needs to recognize and serialize tripwire information at the workflow level. No issues from a dependency or typing standpoint as long as pnpm typecheck passes for the updated StepResult / StepFailure shapes.


375-395: fmtReturnValue tripwire handling matches the new workflow status semantics; verify all consumers handle status: 'tripwire'

The new branch that:

  • detects lastOutput.tripwire (both TripWire instances and plain objects),
  • switches base.status from 'failed' to 'tripwire', and
  • surfaces base.tripwire instead of base.error

is consistent with the design of treating processor rejections as a distinct terminal state. Non-tripwire failures still go through formatResultError, so legacy failure behavior is preserved.

Given this is a behavior change at the top-level workflow result shape, please double-check that all downstream consumers (SDKs, UI, any custom callers) now recognize status === 'tripwire' and prefer tripwire over error when present.

packages/core/src/processors/runner.ts (1)

66-69: Clean type system design for processor/workflow union.

The introduction of ProcessorOrWorkflow union type (lines 66-69) and consistent threading of retryCount parameter throughout the codebase provides a solid foundation for the retry mechanism. The approach:

  • Uses a clear type alias for the union
  • Maintains backward compatibility (retryCount defaults to 0)
  • Properly extends return types to include tripwire metadata
  • Enables workflows to be used anywhere processors are accepted

Also applies to: 154-154, 290-290, 511-511, 737-737

packages/core/src/stream/index.ts (1)

10-10: LGTM!

The addition of MastraFinishReason to the exported types properly expands the public API surface to support the tripwire and finish-state handling introduced in this PR.

packages/core/src/loop/workflows/agentic-loop/index.ts (1)

85-86: The finishReason cast is intentional but document the type extension more clearly.

The code casts typedInputData.stepResult?.reason (which includes 'tripwire' and 'retry' extensions for processor scenarios) to StepResult<Tools>['finishReason'] from ai-v5, which only recognizes standard LanguageModelV2FinishReason values. While the comment explains why the cast exists, consider clarifying that this is a deliberate extension of the ai-v5 type contract and update the comment to reference the MastraFinishReason type defined in packages/core/src/stream/types.ts (line 33), which documents the full set of supported finish reasons including 'tripwire' and 'retry'. This improves maintainability by making the type extension explicit.

packages/core/src/workflows/workflow.test.ts (1)

17923-17931: The test condition for processOutputStream is correct as written.

The concern in the original review comment is based on a misunderstanding of the chunk flow. While the mocked model does emit raw chunks with a delta field ({ type: 'text-delta', id: '1', delta: 'This is inappropriate content' }), these chunks are transformed before reaching the processor.

The flow is:

  1. MockLanguageModelV2 emits raw LanguageModelV2StreamPart chunks with delta
  2. AISDKV5LanguageModel transforms these chunks via convertFullStreamChunkToMastra
  3. The transformation converts value.deltapayload.text for text-delta chunks
  4. The processor receives the transformed ChunkType with the correct shape: { type: 'text-delta', payload: { text: '...' } }

Therefore, the processor's check part?.payload?.text?.includes('inappropriate') is correct and will properly detect the inappropriate content. The test will successfully exercise the tripwire path as intended.

Likely an incorrect or invalid review comment.

packages/core/src/processors/step-schema.ts (1)

1-453: Confirm pnpm typecheck, pnpm prettier:format, and pnpm format pass on your branch

Per project guidelines, this TypeScript file must pass the standard typecheck and formatting commands. Please run these from the repository root and confirm they complete without errors:

pnpm typecheck
pnpm prettier:format
pnpm format
packages/core/src/agent/agent.types.ts (1)

9-9: Processor/workflow unions and maxProcessorRetries docs look consistent.

Using InputProcessorOrWorkflow[] / OutputProcessorOrWorkflow[] here matches the processor/workflow unions, and the maxProcessorRetries JSDoc now correctly describes overriding the agent default and falling back to “no retries” when unset. No further changes needed.

Also applies to: 93-101

packages/core/src/stream/types.ts (1)

328-337: Tripwire data modelling and processor output wiring look coherent.

TripwirePayload, StepTripwireData, MastraStepResult, the added tripwire field on LLMStepResult, the workflow-step tripwire attachment, and MastraModelOutputOptions.outputProcessors?: OutputProcessorOrWorkflow[] are all shape‑compatible with the TripWire class (reason/retry/metadata/processorId) and should round‑trip tripwire information cleanly through streams and step results.

Also applies to: 633-635, 719-742, 777-779, 704-717

packages/core/src/workflows/workflow.ts (4)

274-319: Agent step tripwire handling in workflows is correctly wired.

The agent-backed step now detects tripwire chunks in both legacy and vNext streaming, writes the chunk through (vNext), then throws a TripWire with reason/retry/metadata/processorId. This cleanly aborts the workflow step while preserving a visible tripwire event for downstream consumers.


161-172: Processor-to-step wrapping covers all phases and result shapes consistently.

The new Processor overload for createStep plus the isProcessor branch correctly:

  • Enforces presence of messageList for message-based phases using MastraError.
  • Auto-creates a MessageList only when messages is an array (avoids prior type issues).
  • Handles all three allowed return shapes (MessageList, MastraDBMessage[], { messages, systemMessages }) for processInput, processInputStep, processOutputResult, and processOutputStep.
  • Passes through stream data/state for processOutputStream while allowing processors to drop or rewrite the part.

This aligns the workflow step contract with the Processor interface and previously fixed result-handling bugs.

Also applies to: 383-627


765-811: Workflow type flag is wired correctly for processor workflows.

Adding public type: WorkflowType = 'default' and initializing it from the WorkflowConfig (defaulting to 'default') gives processor workflows a clear 'processor' discriminator without affecting existing workflows. This plays nicely with the agent’s combineProcessorsIntoWorkflow helper.


666-684: isProcessor type guard is robust enough for runtime discrimination.

The guard checks for a string id, excludes Agent/Tool instances, and requires at least one processor method. That’s sufficient for safely routing objects into the Processor branch of createStep and for filtering in combineProcessorsIntoWorkflow.

packages/core/src/agent/agent.ts (2)

125-127: Processor/workflow composition and resolution logic are sound.

The agent now:

  • Stores input/output processors as DynamicArgument<InputProcessorOrWorkflow[] | OutputProcessorOrWorkflow[]> with an optional #maxProcessorRetries.
  • Resolves configured + memory processors, ordering memory first for input and last for output as documented.
  • Collapses multiple processors into a single processor-typed workflow via combineProcessorsIntoWorkflow, using ProcessorStepSchema I/O and type: 'processor', and wrapping bare Processor instances with createStep.
  • Exposes configured processor workflows (excluding memory) via getConfiguredProcessorWorkflows for Mastra to register centrally.

This design keeps runtime wiring consistent and avoids duplicating processor/workflow handling elsewhere.

Also applies to: 244-255, 305-327, 329-392, 399-442, 447-456, 458-497


1459-1469: Tripwire capture from input/output processors is correctly shaped.

__runInputProcessors and __runOutputProcessors now:

  • Respect overrides, only instantiating ProcessorRunner when needed.
  • Catch TripWire specifically, convert it into a { reason, retry, metadata, processorId } tripwire field on the return value, and rethrow other errors.
  • Preserve the updated MessageList in all cases.

This matches the TripWire shape used elsewhere and gives the higher-level workflow full control over retry and surfacing.

Also applies to: 1519-1529

Comment on lines +177 to +190
try {
await this.executeWorkflowAsProcessor(
processorOrWorkflow,
{
phase: 'outputResult',
messages: processableMessages,
messageList,
retryCount,
},
tracingContext,
requestContext,
);

ctx.abort = abort;
processorSpan?.end({ output: processableMessages });
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's check the structure of the file and understand the context
wc -l packages/core/src/processors/runner.ts

Repository: mastra-ai/mastra

Length of output: 103


🏁 Script executed:

# Search for all usages of executeWorkflowAsProcessor to verify result handling pattern
rg -n -A5 -B2 'executeWorkflowAsProcessor' --type ts

Repository: mastra-ai/mastra

Length of output: 3791


🏁 Script executed:

# Get the specific lines mentioned in the review (177-190 and 244-270)
sed -n '163,199p' packages/core/src/processors/runner.ts

Repository: mastra-ai/mastra

Length of output: 1295


🏁 Script executed:

# Also check lines 244-270 for processor handling comparison
sed -n '230,280p' packages/core/src/processors/runner.ts

Repository: mastra-ai/mastra

Length of output: 1793


🏁 Script executed:

# Get the executeWorkflowAsProcessor implementation
sed -n '98,140p' packages/core/src/processors/runner.ts

Repository: mastra-ai/mastra

Length of output: 1588


🏁 Script executed:

# Get context around line 327 to see how result is used
sed -n '320,350p' packages/core/src/processors/runner.ts

Repository: mastra-ai/mastra

Length of output: 1126


🏁 Script executed:

# Get the full executeWorkflowAsProcessor implementation including return statement
sed -n '98,160p' packages/core/src/processors/runner.ts

Repository: mastra-ai/mastra

Length of output: 2272


🏁 Script executed:

# Find the ProcessorStepOutput type definition
rg -n 'type ProcessorStepOutput\|interface ProcessorStepOutput' --type ts -A10

Repository: mastra-ai/mastra

Length of output: 42


🏁 Script executed:

# Search for ProcessorStepOutput definition with different approach
rg -n 'ProcessorStepOutput' --type ts packages/ | grep -E '(type|interface)' | head -5

Repository: mastra-ai/mastra

Length of output: 592


🏁 Script executed:

# Also check the actual file where it's defined
rg -n 'export (type|interface) ProcessorStepOutput' --type ts

Repository: mastra-ai/mastra

Length of output: 168


🏁 Script executed:

# Look at the broader context - what does the workflow return for outputResult phase
grep -r 'phase.*outputResult' packages/core/src --include='*.ts' -B2 -A2

Repository: mastra-ai/mastra

Length of output: 2997


🏁 Script executed:

# Verify what gets returned from processOutputResult in tests
rg -n 'processOutputResult' packages/core/src/workflows/processor-step.test.ts -A10 | head -40

Repository: mastra-ai/mastra

Length of output: 1508


🏁 Script executed:

# Check the ProcessorStepOutputSchema to see all possible fields
sed -n '400,460p' packages/core/src/processors/step-schema.ts

Repository: mastra-ai/mastra

Length of output: 1761


Workflow result handling is inconsistent - return value should be used at line 178.

The executeWorkflowAsProcessor method returns ProcessorStepOutput, which for the outputResult phase includes a messages field that may contain workflow modifications. However, at line 178, the return value is discarded via await without capturing it.

This is inconsistent with:

  • Line 327 (outputStream phase), which correctly captures the result and uses result.part
  • The regular processor handling pattern (lines 244-270), which uses returned values to update messages

The workflow at line 178 should capture and apply the returned messages:

-          await this.executeWorkflowAsProcessor(
+          const workflowResult = await this.executeWorkflowAsProcessor(
             processorOrWorkflow,
             {
               phase: 'outputResult',
               messages: processableMessages,
               messageList,
               retryCount,
             },
             tracingContext,
             requestContext,
           );
+          
+          if (workflowResult.messages) {
+            processableMessages = workflowResult.messages;
+          }
 
           processorSpan?.end({ output: processableMessages });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
await this.executeWorkflowAsProcessor(
processorOrWorkflow,
{
phase: 'outputResult',
messages: processableMessages,
messageList,
retryCount,
},
tracingContext,
requestContext,
);
ctx.abort = abort;
processorSpan?.end({ output: processableMessages });
try {
const workflowResult = await this.executeWorkflowAsProcessor(
processorOrWorkflow,
{
phase: 'outputResult',
messages: processableMessages,
messageList,
retryCount,
},
tracingContext,
requestContext,
);
if (workflowResult.messages) {
processableMessages = workflowResult.messages;
}
processorSpan?.end({ output: processableMessages });
🤖 Prompt for AI Agents
In packages/core/src/processors/runner.ts around lines 177 to 190, the call to
await this.executeWorkflowAsProcessor(..., { phase: 'outputResult', messages:
processableMessages, ... }) discards its ProcessorStepOutput return value;
capture the result into a const (e.g., result) and replace processableMessages
with the returned messages if present (e.g., processableMessages =
result.messages ?? processableMessages), preserving types and handling
undefined/null safely before ending processorSpan with the updated messages;
ensure any existing retryCount or tracingContext usage remains unchanged.

Comment on lines +168 to +173
// Preserve tripwire property for proper status conversion in fmtReturnValue
execResults = {
status: 'failed',
error: hasFailed.error,
tripwire: (hasFailed as any).tripwire,
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let me inspect the control-flow.ts file around the mentioned lines
cat -n packages/core/src/workflows/handlers/control-flow.ts | sed -n '160,180p'

Repository: mastra-ai/mastra

Length of output: 1004


🏁 Script executed:

# Now check executeConditional around lines 428-429
cat -n packages/core/src/workflows/handlers/control-flow.ts | sed -n '420,440p'

Repository: mastra-ai/mastra

Length of output: 980


🏁 Script executed:

# Search for all failure result constructions in the file
rg -n "status:\s*['\"]failed['\"]" packages/core/src/workflows/handlers/control-flow.ts -A 2 -B 2

Repository: mastra-ai/mastra

Length of output: 512


🏁 Script executed:

# Search for fmtReturnValue function to understand if tripwire is actually used
rg -n "fmtReturnValue" packages/core/src/workflows/handlers/control-flow.ts -B 2 -A 5

Repository: mastra-ai/mastra

Length of output: 395


🏁 Script executed:

# Check if tripwire is used elsewhere in the codebase
rg -l "tripwire" packages/core/src/workflows/ --type ts

Repository: mastra-ai/mastra

Length of output: 324


🏁 Script executed:

# Search for fmtReturnValue implementation to see if it uses tripwire
rg -n "fmtReturnValue" packages/core/src/workflows/ -B 2 -A 10 --type ts | head -80

Repository: mastra-ai/mastra

Length of output: 4302


🏁 Script executed:

# Find all control flow handler functions to check consistency
rg -n "^(async\s+)?function|^const.*=\s*(async\s*)?\(" packages/core/src/workflows/handlers/control-flow.ts | head -20

Repository: mastra-ai/mastra

Length of output: 42


🏁 Script executed:

# Check the function signatures and names
rg -n "^export (async )?function|^export const.*=" packages/core/src/workflows/handlers/control-flow.ts

Repository: mastra-ai/mastra

Length of output: 228


🏁 Script executed:

# Search for all failure result constructions to see complete pattern
rg -n "execResults\s*=" packages/core/src/workflows/handlers/control-flow.ts -A 3 | grep -E "(execResults|status.*failed|tripwire)"

Repository: mastra-ai/mastra

Length of output: 629


🏁 Script executed:

# Check tripwire type definition in types.ts
rg -n "tripwire" packages/core/src/workflows/types.ts -B 2 -A 2

Repository: mastra-ai/mastra

Length of output: 799


🏁 Script executed:

# Check executeLoop failure result construction
cat -n packages/core/src/workflows/handlers/control-flow.ts | sed -n '497,650p' | grep -A 20 "hasFailed"

Repository: mastra-ai/mastra

Length of output: 42


🏁 Script executed:

# Look at fmtReturnValue more carefully to see if tripwire is used
cat -n packages/core/src/workflows/default.ts | sed -n '363,410p'

Repository: mastra-ai/mastra

Length of output: 2187


🏁 Script executed:

# Check if tripwire is accessed in fmtReturnValue
rg -n "tripwire" packages/core/src/workflows/default.ts -B 3 -A 3

Repository: mastra-ai/mastra

Length of output: 1570


🏁 Script executed:

# Search for how the result object from handlers is used in fmtReturnValue
rg -n "lastOutput\.result\." packages/core/src/workflows/default.ts | head -20

Repository: mastra-ai/mastra

Length of output: 328


🏁 Script executed:

# Check executeLoop failure result construction - search more broadly
rg -n "executeLoop" packages/core/src/workflows/handlers/control-flow.ts -A 150 | grep -A 5 "hasFailed\|status.*failed" | head -40

Repository: mastra-ai/mastra

Length of output: 42


🏁 Script executed:

# Simpler approach - get executeLoop function and look for all execResults assignments
sed -n '497,690p' packages/core/src/workflows/handlers/control-flow.ts | grep -n "execResults\|status.*failed" -A 3

Repository: mastra-ai/mastra

Length of output: 42


🏁 Script executed:

# Check executeForeach for failure handling
sed -n '690,900p' packages/core/src/workflows/handlers/control-flow.ts | grep -n "execResults\|hasFailed\|status.*failed" -B 2 -A 3

Repository: mastra-ai/mastra

Length of output: 659


🏁 Script executed:

# Read executeLoop function directly to see failure handling
sed -n '497,689p' packages/core/src/workflows/handlers/control-flow.ts

Repository: mastra-ai/mastra

Length of output: 5268


Add tripwire preservation to executeConditional and executeForeach for consistent status conversion.

The executeParallel function preserves the tripwire property when handling failures, enabling fmtReturnValue to properly convert failed results to 'tripwire' status. However, executeConditional (line 429) and executeForeach (line 814) do not preserve it, causing tripwire data to be lost and failures to be reported as 'failed' instead of 'tripwire'.

Apply these fixes:

executeConditional (line 429):

   if (hasFailed) {
-    execResults = { status: 'failed', error: hasFailed.error };
+    execResults = { 
+      status: 'failed', 
+      error: hasFailed.error,
+      tripwire: (hasFailed as any).tripwire,
+    };
   }

executeForeach (line 814):

-        const execResults = { status, error, suspendPayload, suspendedAt, endedAt, output };
+        const execResults = { status, error, suspendPayload, suspendedAt, endedAt, output, tripwire };

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
packages/core/src/workflows/handlers/control-flow.ts around lines 168-173 (and
similarly at ~429 and ~814): the failure branches in executeConditional and
executeForeach are not preserving the tripwire property, causing fmtReturnValue
to misclassify tripwire failures as plain 'failed'; update the failure result
construction so it mirrors executeParallel by adding tripwire: (hasFailed as
any).tripwire to the execResults object whenever you set status: 'failed' and
include error: hasFailed.error.

execResults = {
status: 'failed',
error: hasFailed.error,
tripwire: (hasFailed as any).tripwire,
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Type cast to any undermines type safety.

Using (hasFailed as any).tripwire bypasses TypeScript's strict type checking. If tripwire is a valid property that should be propagated from failed step results, it should be properly typed in the StepFailure interface rather than accessed through a type cast.

Consider updating the type definition to include the tripwire property:

-      tripwire: (hasFailed as any).tripwire,
+      tripwire: hasFailed.tripwire,

And ensure the StepFailure type in the workflow types includes the tripwire property with the correct type signature (based on the AI summary, this should be an object with reason, retry, metadata, and processorId fields).

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/core/src/workflows/handlers/control-flow.ts around line 172, the
code uses a type cast `(hasFailed as any).tripwire` which bypasses TypeScript
safety; update the StepFailure/failed-step result type to include a properly
typed `tripwire` property (an object with fields: reason: string, retry: boolean
or number as appropriate, metadata: Record<string, unknown>, and processorId:
string), replace the any cast by referencing the typed property (e.g.,
hasFailed.tripwire), and adjust any upstream type imports/usage so the
`tripwire` field is carried through the failure shape instead of being accessed
via `any`.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
packages/core/src/loop/workflows/stream.ts (1)

193-194: Type suppression improved but underlying issue remains.

The change from @ts-ignore to @ts-expect-error is a good practice improvement (it will error if the type issue is ever fixed), and the comment is more informative. However, the core recommendation from the previous review still applies: the type definitions should be updated to properly reflect that stepResult.reason can be 'tripwire' | 'retry' in addition to other values.

Please run the following script to verify whether the type definitions have been updated:

#!/bin/bash
# Description: Check if stepResult.reason type includes 'tripwire' and 'retry' as valid values

# Search for StepResult type definitions
echo "=== Searching for StepResult type definitions ==="
rg -nP --type=ts -C5 'type\s+StepResult|interface\s+StepResult' 

# Search for reason field definitions in step-related types
echo -e "\n=== Searching for reason field definitions ==="
rg -nP --type=ts -C3 '\breason\s*[?:].*\b(FinishReason|string)'

# Check for any recent additions of 'tripwire' or 'retry' to type unions
echo -e "\n=== Checking for tripwire/retry in type definitions ==="
rg -nP --type=ts "'tripwire'|'retry'" -g '**/types.ts' -g '**/loop/**/*.ts'

As per the previous review comment and coding guidelines requiring strict TypeScript type checking, consider updating the type definitions to include 'tripwire' | 'retry' as valid values for stepResult.reason.

packages/core/src/workflows/workflow.test.ts (1)

17898-17905: Align streaming test with vNext event type and remove unused chunk collection

In the streaming tripwire test you’re still collecting streamVNext output into a StreamEvent[], but never assert on chunks:

// Use streaming to verify workflow returns tripwire status
const chunks: StreamEvent[] = [];
const streamResult = run.streamVNext({ inputData: { prompt: 'This has forbidden content' } });

// Collect all chunks
for await (const chunk of streamResult.fullStream) {
  chunks.push(chunk);
}

streamVNext produces vNext workflow events (e.g. WorkflowStreamEvent / ChunkType), not legacy StreamEvent, and since chunks is unused this also adds noise to the test. You can both fix the type mismatch and simplify by just draining the stream:

-      // Use streaming to verify workflow returns tripwire status
-      const chunks: StreamEvent[] = [];
-      const streamResult = run.streamVNext({ inputData: { prompt: 'This has forbidden content' } });
-
-      // Collect all chunks
-      for await (const chunk of streamResult.fullStream) {
-        chunks.push(chunk);
-      }
+      // Use streaming to verify workflow returns tripwire status
+      const streamResult = run.streamVNext({ inputData: { prompt: 'This has forbidden content' } });
+
+      // Drain the stream
+      for await (const _chunk of streamResult.fullStream) {
+        // no-op
+      }

This matches the vNext API and keeps the test focused on the result.status === 'tripwire' assertions.

packages/core/src/agent/agent.ts (1)

2972-2990: Add explicit tripwire status handling in generate/stream result processing.

Both generate (lines 2972-2990) and stream (lines 3051-3069) only check for 'success' and 'failed' status. If the underlying workflow returns status === 'tripwire' (as enabled by DefaultExecutionEngine.fmtReturnValue), these methods fall into the generic "unknown error" case, losing the structured tripwire payload (reason, retry, metadata, processorId).

Add an explicit tripwire branch before the generic error case in both methods:

  if (result.status !== 'success') {
+   if (result.status === 'tripwire') {
+     const tripwireResult = result as { 
+       tripwire?: { reason?: string; retry?: boolean; metadata?: unknown; processorId?: string } 
+     };
+     throw new TripWire(
+       tripwireResult.tripwire?.reason ?? 'Processor tripwire triggered',
+       { 
+         retry: tripwireResult.tripwire?.retry, 
+         metadata: tripwireResult.tripwire?.metadata 
+       },
+       tripwireResult.tripwire?.processorId,
+     );
+   }
    if (result.status === 'failed') {
      throw new MastraError(

This ensures callers, server layers, and UI can reliably detect and surface tripwire events with full context.

Note: This issue was previously flagged in past review comments but remains unaddressed.

Also applies to: 3051-3069

🧹 Nitpick comments (8)
client-sdks/ai-sdk/src/helpers.ts (1)

60-66: Consider defensive coding for consistency.

The finish case accesses chunk.payload.stepResult.reason directly, while middleware.ts uses optional chaining with a fallback (stepResult?.reason || 'stop'). If there's any scenario where stepResult could be undefined in this conversion context, this could throw at runtime.

If the type guarantees stepResult is always defined here, this is fine. Otherwise, consider aligning with the middleware pattern:

     case 'finish': {
       return {
         type: 'finish',
-        finishReason: toAISDKFinishReason(chunk.payload.stepResult.reason),
+        finishReason: toAISDKFinishReason(chunk.payload.stepResult?.reason || 'stop'),
         totalUsage: chunk.payload.output.usage,
       };
     }
packages/core/src/agent/agent.ts (1)

329-392: Consider defensive copying to avoid workflow mutation side effects.

Lines 350 and 367 directly mutate the input workflow by setting workflow.type = 'processor'. While the comment at lines 347-349 states this is intentional, mutation can cause subtle bugs if the same workflow instance is referenced elsewhere.

Consider creating a shallow copy or using a wrapper approach:

  if (processors.length === 1 && isProcessorWorkflow(processors[0]!)) {
    const workflow = processors[0]!;
-   if (!workflow.type) {
-     workflow.type = 'processor';
-   }
-   return [workflow];
+   // Return workflow with type set, using shallow copy to avoid mutation
+   return [Object.assign(Object.create(Object.getPrototypeOf(workflow)), workflow, { 
+     type: workflow.type || 'processor' 
+   }) as T];
  }

Alternatively, if the Workflow class supports it, add a dedicated .asProcessor() method that returns a properly marked instance without mutating the original.

packages/core/src/loop/workflows/agentic-loop/index.ts (1)

85-86: The finishReason cast is a necessary workaround for upstream type constraints

The cast to StepResult<Tools>['finishReason'] is required because LLMIterationStepResult.reason extends the base finish reasons with 'tripwire' | 'retry' (defined in packages/core/src/loop/workflows/schema.ts), but the upstream StepResult type does not include these processor-specific values. As a longer-term improvement, updating the core StepResult type definition to explicitly include these values would eliminate the cast and the 'unknown' fallback, providing stronger compile-time guarantees and removing the type friction.

packages/core/src/stream/types.ts (5)

16-16: Type-only import of OutputProcessorOrWorkflow is appropriate

Using import type here keeps this module purely type-level and avoids runtime cycles, which is ideal given it’s a shared types file. No changes needed; just ensure any future need for strongly typed tripwire metadata is handled by threading the generic parameter through the options rather than widening to any.


29-34: Reuse MastraFinishReason where the extended finish reasons are used

You define MastraFinishReason but still inline the same union in FinishPayload.stepResult.reason. Reusing the alias keeps the union in one place and avoids drift if you later add more Mastra-specific reasons.

 export type MastraFinishReason = LanguageModelV2FinishReason | 'tripwire' | 'retry';

 interface FinishPayload<Tools extends ToolSet = ToolSet> {
   stepResult: {
-    /** Includes 'tripwire' and 'retry' for processor scenarios */
-    reason: LanguageModelV2FinishReason | 'tripwire' | 'retry';
+    /** Includes 'tripwire' and 'retry' for processor scenarios */
+    reason: MastraFinishReason;
   };

If MastraFinishReason is not used anywhere else yet, this also guarantees it stays in sync with the actual payload shape.

Also applies to: 190-194


200-201: Tripwire-aware MastraStepResult wiring looks consistent and non-breaking

Switching output.steps in both FinishPayload and StepFinishPayload to MastraStepResult<Tools>[] and threading StepTripwireData into WorkflowStreamEvent and LLMStepResult gives you a single, backward-compatible extension point for per-step tripwire data while preserving the underlying AI SDK StepResult via intersection. This should make it straightforward for callers (and the UI) to detect processor rejections at both the streaming and workflow layers without breaking existing code that only understands the base StepResult shape.

Also applies to: 255-256, 633-635, 719-741, 777-778


328-337: Align TripwirePayload and StepTripwireData to avoid type drift

TripwirePayload<TMetadata> and StepTripwireData currently duplicate the same shape, but only TripwirePayload is generic over metadata. To reduce maintenance and keep the two in sync, consider making StepTripwireData an alias of TripwirePayload (with a default for metadata):

-interface TripwirePayload<TMetadata = unknown> {
+interface TripwirePayload<TMetadata = unknown> {
   /** The reason for the tripwire */
   reason: string;
   /** If true, the agent should retry with the tripwire reason as feedback */
   retry?: boolean;
   /** Strongly typed metadata from the processor */
   metadata?: TMetadata;
   /** The ID of the processor that triggered the tripwire */
   processorId?: string;
 }
 
 /**
  * Tripwire data attached to a step when a processor triggers a tripwire.
  * When a step has tripwire data, its text is excluded from the final output.
  */
-export interface StepTripwireData {
-  /** The tripwire reason */
-  reason: string;
-  /** Whether retry was requested */
-  retry?: boolean;
-  /** Additional metadata from the tripwire */
-  metadata?: unknown;
-  /** ID of the processor that triggered the tripwire */
-  processorId?: string;
-}
+export type StepTripwireData<TMetadata = unknown> = TripwirePayload<TMetadata>;

Existing uses without a type argument will continue to default to unknown, but this opens the door to threading typed metadata end-to-end later without another type change.

Also applies to: 719-731


711-711: outputProcessors now accepting workflows matches the new orchestration model

Changing outputProcessors to OutputProcessorOrWorkflow[] correctly reflects that callers can now pass either processors or processor workflows. This keeps the signature aligned with the new composition API; if you later want fully typed tripwire metadata, you can consider generic parameters on MastraModelOutputOptions (e.g., OutputProcessorOrWorkflow<TTripwireMetadata>[]) and thread that through, but it’s reasonable to keep it at the default unknown for now. Also, per the repo guidelines, please ensure pnpm typecheck, pnpm prettier:format, and pnpm format have been run after these TypeScript changes.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f78f05b and 5efae23.

📒 Files selected for processing (24)
  • client-sdks/ai-sdk/src/helpers.ts (4 hunks)
  • client-sdks/ai-sdk/src/middleware.ts (2 hunks)
  • client-sdks/client-js/src/types.ts (1 hunks)
  • client-sdks/react/src/lib/ai-sdk/utils/toUIMessage.test.ts (2 hunks)
  • client-sdks/react/src/lib/ai-sdk/utils/toUIMessage.ts (2 hunks)
  • packages/core/src/agent/agent.ts (16 hunks)
  • packages/core/src/agent/agent.types.ts (2 hunks)
  • packages/core/src/agent/message-list/index.ts (2 hunks)
  • packages/core/src/llm/model/model.loop.ts (2 hunks)
  • packages/core/src/loop/test-utils/options.ts (8 hunks)
  • packages/core/src/loop/types.ts (3 hunks)
  • packages/core/src/loop/workflows/agentic-loop/index.ts (2 hunks)
  • packages/core/src/loop/workflows/stream.ts (2 hunks)
  • packages/core/src/processors/runner.ts (17 hunks)
  • packages/core/src/processors/step-schema.ts (1 hunks)
  • packages/core/src/stream/index.ts (1 hunks)
  • packages/core/src/stream/types.ts (8 hunks)
  • packages/core/src/workflows/default.ts (5 hunks)
  • packages/core/src/workflows/handlers/control-flow.ts (1 hunks)
  • packages/core/src/workflows/types.ts (8 hunks)
  • packages/core/src/workflows/workflow.test.ts (1 hunks)
  • packages/core/src/workflows/workflow.ts (12 hunks)
  • packages/server/src/server/handlers/agent.test.ts (1 hunks)
  • packages/server/src/server/handlers/agents.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (9)
  • packages/core/src/llm/model/model.loop.ts
  • client-sdks/client-js/src/types.ts
  • packages/core/src/workflows/handlers/control-flow.ts
  • packages/server/src/server/handlers/agent.test.ts
  • packages/server/src/server/handlers/agents.ts
  • packages/core/src/stream/index.ts
  • client-sdks/react/src/lib/ai-sdk/utils/toUIMessage.test.ts
  • packages/core/src/agent/message-list/index.ts
  • packages/core/src/loop/types.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Run pnpm typecheck to validate TypeScript types across all packages

Files:

  • packages/core/src/loop/workflows/stream.ts
  • packages/core/src/loop/workflows/agentic-loop/index.ts
  • client-sdks/react/src/lib/ai-sdk/utils/toUIMessage.ts
  • packages/core/src/loop/test-utils/options.ts
  • client-sdks/ai-sdk/src/middleware.ts
  • packages/core/src/agent/agent.types.ts
  • packages/core/src/workflows/types.ts
  • packages/core/src/workflows/default.ts
  • packages/core/src/processors/runner.ts
  • client-sdks/ai-sdk/src/helpers.ts
  • packages/core/src/workflows/workflow.test.ts
  • packages/core/src/stream/types.ts
  • packages/core/src/agent/agent.ts
  • packages/core/src/workflows/workflow.ts
  • packages/core/src/processors/step-schema.ts
**/*.{ts,tsx,js,jsx,json,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Run pnpm prettier:format to format code and pnpm format to run linting with auto-fix across all packages

Files:

  • packages/core/src/loop/workflows/stream.ts
  • packages/core/src/loop/workflows/agentic-loop/index.ts
  • client-sdks/react/src/lib/ai-sdk/utils/toUIMessage.ts
  • packages/core/src/loop/test-utils/options.ts
  • client-sdks/ai-sdk/src/middleware.ts
  • packages/core/src/agent/agent.types.ts
  • packages/core/src/workflows/types.ts
  • packages/core/src/workflows/default.ts
  • packages/core/src/processors/runner.ts
  • client-sdks/ai-sdk/src/helpers.ts
  • packages/core/src/workflows/workflow.test.ts
  • packages/core/src/stream/types.ts
  • packages/core/src/agent/agent.ts
  • packages/core/src/workflows/workflow.ts
  • packages/core/src/processors/step-schema.ts
packages/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

packages/**/*.{ts,tsx}: All packages must use TypeScript with strict type checking enabled
Use telemetry decorators for observability across components

Files:

  • packages/core/src/loop/workflows/stream.ts
  • packages/core/src/loop/workflows/agentic-loop/index.ts
  • packages/core/src/loop/test-utils/options.ts
  • packages/core/src/agent/agent.types.ts
  • packages/core/src/workflows/types.ts
  • packages/core/src/workflows/default.ts
  • packages/core/src/processors/runner.ts
  • packages/core/src/workflows/workflow.test.ts
  • packages/core/src/stream/types.ts
  • packages/core/src/agent/agent.ts
  • packages/core/src/workflows/workflow.ts
  • packages/core/src/processors/step-schema.ts
**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{test,spec}.{ts,tsx}: Use Vitest for testing framework in test files
Co-locate test files with source code using naming patterns like *.test.ts or *.spec.ts

Files:

  • packages/core/src/workflows/workflow.test.ts
🧠 Learnings (8)
📚 Learning: 2025-10-29T09:37:59.778Z
Learnt from: TheIsrael1
Repo: mastra-ai/mastra PR: 9247
File: packages/core/src/loop/network/index.ts:357-393
Timestamp: 2025-10-29T09:37:59.778Z
Learning: In the agent network routing completion stream (packages/core/src/loop/network/index.ts), when `completionStream.error` is true, the first stream should not have written any text (currentTextIdx should be 0), so the fallback reset of currentText and currentTextIdx does not cause duplication.

Applied to files:

  • packages/core/src/loop/workflows/stream.ts
  • packages/core/src/loop/workflows/agentic-loop/index.ts
  • packages/core/src/agent/agent.types.ts
  • client-sdks/ai-sdk/src/helpers.ts
  • packages/core/src/workflows/workflow.test.ts
  • packages/core/src/agent/agent.ts
  • packages/core/src/workflows/workflow.ts
📚 Learning: 2025-11-24T16:42:04.244Z
Learnt from: CR
Repo: mastra-ai/mastra PR: 0
File: packages/codemod/AGENTS.md:0-0
Timestamp: 2025-11-24T16:42:04.244Z
Learning: Applies to packages/codemod/src/test/__fixtures__/**/*.ts : In output fixtures, ensure all NEGATIVE test cases remain EXACTLY IDENTICAL to their input fixture counterparts to verify the codemod only transforms intended patterns

Applied to files:

  • packages/core/src/loop/test-utils/options.ts
  • packages/core/src/workflows/workflow.test.ts
📚 Learning: 2025-11-24T16:42:04.244Z
Learnt from: CR
Repo: mastra-ai/mastra PR: 0
File: packages/codemod/AGENTS.md:0-0
Timestamp: 2025-11-24T16:42:04.244Z
Learning: Applies to packages/codemod/src/test/__fixtures__/**/*.ts : Create test fixtures by copying examples DIRECTLY from migration guides in `docs/src/content/en/guides/migrations/upgrade-to-v1/` without hallucinating or inventing changes

Applied to files:

  • packages/core/src/loop/test-utils/options.ts
  • packages/core/src/workflows/workflow.test.ts
📚 Learning: 2025-11-24T16:42:04.244Z
Learnt from: CR
Repo: mastra-ai/mastra PR: 0
File: packages/codemod/AGENTS.md:0-0
Timestamp: 2025-11-24T16:42:04.244Z
Learning: Applies to packages/codemod/src/test/**/*.test.ts : Include test cases for multiple occurrences of the transformation pattern, aliased imports, type imports, and mixed imports to verify the codemod works consistently

Applied to files:

  • packages/core/src/loop/test-utils/options.ts
  • packages/core/src/workflows/workflow.test.ts
📚 Learning: 2025-11-24T16:42:04.244Z
Learnt from: CR
Repo: mastra-ai/mastra PR: 0
File: packages/codemod/AGENTS.md:0-0
Timestamp: 2025-11-24T16:42:04.244Z
Learning: Applies to packages/codemod/src/test/__fixtures__/**/*.ts : In input fixtures, include both POSITIVE test cases (patterns that should transform) and NEGATIVE test cases (unrelated code with similar names/patterns that should NOT transform)

Applied to files:

  • packages/core/src/loop/test-utils/options.ts
  • packages/core/src/workflows/workflow.test.ts
📚 Learning: 2025-11-24T16:42:04.244Z
Learnt from: CR
Repo: mastra-ai/mastra PR: 0
File: packages/codemod/AGENTS.md:0-0
Timestamp: 2025-11-24T16:42:04.244Z
Learning: Applies to packages/codemod/src/test/**/*.test.ts : Do NOT use `UPDATE_SNAPSHOT` to force tests to pass; instead fix the codemod implementation when tests fail

Applied to files:

  • packages/core/src/loop/test-utils/options.ts
📚 Learning: 2025-11-24T16:42:04.244Z
Learnt from: CR
Repo: mastra-ai/mastra PR: 0
File: packages/codemod/AGENTS.md:0-0
Timestamp: 2025-11-24T16:42:04.244Z
Learning: Applies to packages/codemod/src/codemods/v1/**/*.ts : When transforming TypeScript type imports and usages, check the parent node type to skip transforming identifiers that are part of import declarations to avoid transforming imports from other packages with the same type name

Applied to files:

  • packages/core/src/agent/agent.ts
📚 Learning: 2025-11-24T16:42:04.244Z
Learnt from: CR
Repo: mastra-ai/mastra PR: 0
File: packages/codemod/AGENTS.md:0-0
Timestamp: 2025-11-24T16:42:04.244Z
Learning: Applies to packages/codemod/src/codemods/v1/**/*.ts : Use `context.hasChanges = true` flag and `context.messages.push()` to track and report what transformations were made in the codemod

Applied to files:

  • packages/core/src/workflows/workflow.ts
🧬 Code graph analysis (10)
client-sdks/react/src/lib/ai-sdk/utils/toUIMessage.ts (1)
client-sdks/react/src/lib/ai-sdk/types.ts (1)
  • MastraUIMessage (42-42)
client-sdks/ai-sdk/src/middleware.ts (1)
client-sdks/ai-sdk/src/helpers.ts (1)
  • toAISDKFinishReason (18-23)
packages/core/src/agent/agent.types.ts (1)
packages/core/src/processors/index.ts (2)
  • InputProcessorOrWorkflow (220-222)
  • OutputProcessorOrWorkflow (227-229)
packages/core/src/workflows/types.ts (1)
packages/core/src/observability/types/tracing.ts (1)
  • TracingProperties (871-874)
packages/core/src/workflows/default.ts (2)
packages/core/src/workflows/types.ts (1)
  • StepTripwireInfo (69-74)
packages/core/src/agent/trip-wire.ts (1)
  • TripWire (35-45)
client-sdks/ai-sdk/src/helpers.ts (3)
packages/core/src/stream/index.ts (1)
  • MastraFinishReason (10-10)
packages/core/src/stream/types.ts (1)
  • MastraFinishReason (33-33)
packages/core/src/stream/base/output.ts (1)
  • chunk (1242-1245)
packages/core/src/workflows/workflow.test.ts (2)
packages/core/src/workflows/workflow.ts (2)
  • createWorkflow (686-702)
  • createStep (174-645)
packages/core/src/workflows/evented/workflow.ts (2)
  • createWorkflow (303-332)
  • createStep (135-301)
packages/core/src/stream/types.ts (4)
packages/core/src/llm/model/base.types.ts (1)
  • ToolSet (29-29)
packages/core/src/processors/index.ts (1)
  • OutputProcessorOrWorkflow (227-229)
packages/core/src/observability/types/tracing.ts (1)
  • TracingContext (863-866)
packages/core/src/workflows/types.ts (1)
  • StepResult (124-129)
packages/core/src/agent/agent.ts (3)
packages/core/src/processors/index.ts (5)
  • InputProcessorOrWorkflow (220-222)
  • OutputProcessorOrWorkflow (227-229)
  • isProcessorWorkflow (235-252)
  • ProcessorStepSchema (259-259)
  • ProcessorWorkflow (215-215)
packages/core/src/workflows/workflow.ts (2)
  • isProcessor (670-684)
  • createWorkflow (686-702)
packages/core/src/processors/step-schema.ts (1)
  • ProcessorStepSchema (430-430)
packages/core/src/processors/step-schema.ts (1)
packages/core/src/agent/types.ts (2)
  • MastraMessageContentV2 (42-42)
  • MessageList (42-42)
🔇 Additional comments (35)
packages/core/src/loop/workflows/stream.ts (1)

182-184: LGTM! Improved stream completion semantics.

The updated comments clearly document the important behavioral change to always emit a finish chunk, even for tripwire cases. This ensures proper stream completion and promise resolution, which aligns with the broader tripwire improvements in this PR.

client-sdks/ai-sdk/src/middleware.ts (1)

24-24: LGTM! Proper integration of finish reason mapping.

The import and usage of toAISDKFinishReason correctly maps Mastra's extended finish reasons ('tripwire', 'retry') to AI SDK-compatible values. The defensive coding with optional chaining and fallback to 'stop' ensures robustness when stepResult is undefined.

Also applies to: 741-741

client-sdks/ai-sdk/src/helpers.ts (2)

13-23: Well-designed helper for finish reason normalization.

The toAISDKFinishReason helper cleanly addresses the mapping of Mastra-specific finish reasons to AI SDK-compatible values. The documentation is clear about the purpose and the implementation is straightforward.


263-272: Tripwire payload restructuring looks correct.

The updated payload structure properly exposes the new tripwire API fields (reason, retry, metadata, processorId) to downstream consumers. This aligns well with the PR's extended tripwire API objectives.

packages/core/src/loop/test-utils/options.ts (3)

1667-1667: LGTM! Tripwire field properly threaded through test expectations.

The explicit "tripwire": undefined additions ensure the tripwire property is consistently present in step result snapshots, aligning with the new structured tripwire object introduced in this PR.

Also applies to: 1727-1727, 1814-1814, 1874-1874


6840-6848: Verify token accumulation for multi-step abort.

The finish event shows totalUsage with accumulated tokens (inputTokens: 3, outputTokens: 10, totalTokens: 13) from the first step before the abort in the second step. Confirm this matches the expected behavior where partial progress is preserved.

The token usage matches the testUsage from line 6619, suggesting the first step's usage is correctly preserved. Verify this aligns with the PR's goal of preserving rejected steps at result.steps[n].tripwire.


6527-6535: Clarify whether user-initiated abort should emit finishReason "tripwire".

The test shows a user abort (AbortError from the browser API) emitting finishReason: "tripwire" with zero usage, but the type definition explicitly documents "tripwire" as for "processor scenarios." This semantic mismatch needs resolution: either the "tripwire" finish reason scope should be expanded to include user aborts, or the user abort should map to a standard LanguageModelV2FinishReason value instead.

packages/core/src/processors/step-schema.ts (1)

1-453: LGTM! Well-structured schema definitions for processor workflows.

The file provides comprehensive Zod schemas with proper discriminated unions, clear documentation, and appropriate use of passthrough() for extensibility. The phase-based discriminated union (lines 385-391) is particularly well-designed for type inference.

packages/core/src/agent/agent.types.ts (2)

9-9: LGTM! Type updates align with processor-workflow unification.

The import and field type changes properly support the new InputProcessorOrWorkflow/OutputProcessorOrWorkflow union types, enabling processors and workflows to be used interchangeably.

Also applies to: 93-95


96-101: LGTM! JSDoc correctly describes retry defaults.

The documentation now accurately reflects that retries are opt-in, defaulting to the agent's maxProcessorRetries when unset. This addresses the previous review concern about unclear default behavior.

packages/core/src/workflows/default.ts (2)

277-338: LGTM! Proper TripWire serialization for workflow results.

The executeStepWithRetry method correctly extracts TripWire instance data into a plain object at the final retry, ensuring tripwire metadata survives serialization. The return type union properly captures both success and error cases with optional tripwire data.


378-395: LGTM! Dual tripwire format handling for flexibility.

The fmtReturnValue method properly distinguishes between TripWire instances and serialized tripwire objects, setting the workflow status to 'tripwire' instead of 'failed'. This enables UI and downstream code to differentiate processor rejections from actual failures.

packages/core/src/processors/runner.ts (3)

94-148: LGTM! Workflow-as-processor execution with proper error handling.

The executeWorkflowAsProcessor method correctly:

  • Executes workflows using the standard workflow API
  • Distinguishes 'tripwire' status from 'failed' status
  • Re-throws TripWire errors for proper agent handling
  • Handles missing output gracefully by returning input unchanged

The type assertion at lines 114-116 uses explicit type definition rather than any, which was addressed in previous review iterations.


150-279: LGTM! Unified output processor handling for workflows and processors.

The runOutputProcessors method cleanly separates workflow and processor execution paths while maintaining consistent:

  • Span creation for observability
  • TripWire error propagation
  • Processor/workflow type discrimination

The workflow branch correctly passes phase: 'outputResult' and all necessary context.


908-1111: LGTM! New per-step output processing enables retry-capable guardrails.

The runProcessOutputStep method introduces a new execution hook that runs after each LLM response but before tool execution. This is ideal for implementing guardrails that can trigger retries by calling abort(reason, { retry: true }).

Key features:

  • Passes rich context: stepNumber, finishReason, toolCalls, text
  • Supports both workflow and processor implementations
  • Properly handles TripWire errors with retry metadata
  • Consistent span creation and mutation tracking
packages/core/src/agent/agent.ts (3)

463-497: LGTM! Processor workflow exposure for Mastra registration.

The getConfiguredProcessorWorkflows method correctly:

  • Excludes memory-derived processors to avoid triggering factory functions
  • Handles both static and function-based processor configs
  • Combines processors into workflows for consistent handling
  • Filters results to return only workflow instances

This enables Mastra to auto-register processor workflows for the agent.


1462-1503: LGTM! Processor methods now return structured tripwire data.

Both __runInputProcessors and __runOutputProcessors correctly:

  • Catch TripWire exceptions from processor/workflow execution
  • Extract structured tripwire data (reason, retry, metadata, processorId)
  • Return tripwire alongside messageList for upstream handling
  • Preserve other errors as MastraError

This enables the retry mechanism and proper tripwire event propagation to the UI.

Also applies to: 1510-1558


2926-3003: LGTM! Centralized processor retry configuration.

Both generate and stream methods correctly pass maxProcessorRetries to execution workflow (lines 2967, 3046), with proper precedence: options override > agent default. This enables per-execution and per-agent retry control.

Also applies to: 3005-3072

packages/core/src/workflows/workflow.ts (6)

4-4: LGTM! Necessary imports for processor integration.

All required types and utilities are imported to support processor-as-step functionality, tripwire handling, and message processing.

Also applies to: 8-10, 19-21, 25-25, 53-53


274-318: Excellent tripwire handling across streaming paths.

The implementation correctly detects tripwire chunks in both legacy and vNext streaming paths, halts stream processing, and throws a structured TripWire error with all necessary payload details (reason, retry options, metadata, and processorId).


161-172: Processor overload correctly typed.

The overload properly uses ProcessorStepOutputSchema for the output schema, ensuring TypeScript inference aligns with the actual execute function return type.


383-631: Comprehensive and well-structured processor-as-step implementation.

This processor integration is excellent:

  • Phase-based execution: Cleanly separates concerns across input, inputStep, outputStream, outputResult, and outputStep phases
  • Consistent result normalization: All message-handling phases properly handle the three return types (MessageList, array, object with messages/systemMessages)
  • Defensive programming: Required messageList presence is validated with clear error messages
  • Proper data flow: The passThrough object enables processor workflow chaining
  • Safe auto-creation: MessageList is auto-created from messages only when Array.isArray(messages) is true
  • TripWire propagation: Intentionally allows TripWire errors to bubble up and halt workflow execution

All previously identified issues have been resolved. The implementation follows best practices and maintains type safety through schema validation.


666-684: Robust type guard for processor detection.

The isProcessor function is well-designed:

  • Validates object structure and id property
  • Explicitly excludes Agent and Tool to prevent false positives
  • Requires at least one processor method to be present
  • Properly exported for use throughout the codebase

765-766: Clean addition of workflow type discriminator.

The type field enables distinguishing processor workflows from default workflows. The implementation follows TypeScript best practices with proper initialization and documentation.

Also applies to: 795-795, 810-810

client-sdks/react/src/lib/ai-sdk/utils/toUIMessage.ts (1)

150-173: Code looks good. All accessed fields (reason, retry, metadata, processorId) are present in the TripwirePayload type definition, with reason as a required field and the others as optional.

packages/core/src/loop/workflows/agentic-loop/index.ts (2)

128-145: Tripwire emission rule looks right; verify output.steps is always populated for “steps + tripwire” cases

The shouldEmitStepFinish condition (“emit unless reason === 'tripwire' and there are zero output.steps”) matches the intended behavior: no step-finish when a pure tripwire occurs before any steps, but still emitting when tripwire happens after one or more steps (e.g., max retries exceeded). This relies on typedInputData.output.steps being the canonical list of executed steps in all retry/tripwire paths; if any processor/workflow path can accumulate steps without populating output.steps, we’d incorrectly suppress step-finish in those cases.

To double‑check that assumption across the codebase:

#!/bin/bash
set -euo pipefail

# Find where `output.steps` is written to or shaped for LLMIterationData
rg -n "output\.steps" --type ts -C4

# Inspect LLMIterationData and related shaping logic
ast-grep --pattern $'type LLMIterationData<$$_> = $_' || true
ast-grep --pattern $'interface LLMIterationData<$$_> $_' || true

1-156: Run TypeScript typecheck and format/lint for this change

Since this is a packages/core TypeScript file, please ensure it passes strict type checking and project formatting/linting (pnpm typecheck, pnpm prettier:format, pnpm format) before merging to catch any subtle typing or style regressions introduced by the new tripwire logic.

packages/core/src/workflows/types.ts (8)

21-26: LGTM! Clean type definition with clear documentation.

The WorkflowType discriminator is well-defined with appropriate JSDoc explaining the distinction between default and processor workflows.


68-74: Well-designed tripwire interface.

The StepTripwireInfo interface appropriately balances required vs optional fields. The reason is required (essential context), while retry, metadata, and processorId are optional. The flexible Record<string, unknown> type for metadata accommodates dynamic processor data.


88-89: LGTM! Consistent integration with StepFailure.

The optional tripwire field is appropriately added to StepFailure, with clear documentation indicating it's present when a step fails due to processor rejection.


219-219: LGTM! Appropriate status extension.

The 'tripwire' status is correctly added to the WorkflowRunStatus union, enabling workflows to distinguish processor rejections from other failure states.


285-286: LGTM! Properly typed state extension.

The optional tripwire field on WorkflowRunState is correctly typed and documented. While TypeScript doesn't enforce that tripwire is present when status is 'tripwire', this is a runtime concern that implementations must handle.


308-309: LGTM! Clear processor workflow indicator.

The isProcessorWorkflow flag provides a straightforward way to identify processor-generated workflows, with clear documentation about its purpose.


562-563: LGTM! Consistent workflow configuration extension.

The optional type field properly extends WorkflowConfig with the WorkflowType discriminator, maintaining backward compatibility while enabling processor workflow classification.


483-500: Tripwire variant is well-structured and properly integrated.

The new 'tripwire' variant of WorkflowResult is correctly designed:

  • Mirrors the structure of other variants (success, failed, suspended) consistently
  • Correctly requires the tripwire field (containing StepTripwireInfo with reason, retry flag, metadata, and processor ID)
  • Appropriately excludes error field (tripwire rejection is distinct from error status) and result field (workflow didn't complete)
  • Steps mapping is identical across all variants for consistency
  • Properly includes TracingProperties via intersection for observability

Note: Telemetry decorator requirements apply to consuming components and classes, not to type definition files. The type definitions themselves have proper tracing support through TracingProperties.

Comment on lines +45 to +47
: finalStatus === 'tripwire' && chunk.payload.tripwire
? { tripwire: chunk.payload.tripwire }
: {}),
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

fd -e ts -e tsx packages/core/src | head -20

Repository: mastra-ai/mastra

Length of output: 433


🏁 Script executed:

# Find ChunkType definition
rg -nP --type=ts 'type\s+ChunkType|interface\s+ChunkType' --max-count=10

Repository: mastra-ai/mastra

Length of output: 186


🏁 Script executed:

# Search for ChunkType and tripwire definitions more broadly
rg -nP --type=ts 'ChunkType.*=|payload.*tripwire' --max-count=20

Repository: mastra-ai/mastra

Length of output: 22628


🏁 Script executed:

cat -n packages/core/src/stream/types.ts | sed -n '630,660p'

Repository: mastra-ai/mastra

Length of output: 1659


🏁 Script executed:

# Find TypedChunkType definition
cat -n packages/core/src/stream/types.ts | sed -n '639,645p'

Repository: mastra-ai/mastra

Length of output: 437


🏁 Script executed:

# Search for payload structure and tripwire in the types file
rg -n 'payload.*tripwire|type.*Payload|interface.*Payload' packages/core/src/stream/types.ts

Repository: mastra-ai/mastra

Length of output: 6248


🏁 Script executed:

cat -n packages/core/src/stream/types.ts | sed -n '328,340p'

Repository: mastra-ai/mastra

Length of output: 591


🏁 Script executed:

# Also check StepTripwireData which is referenced in BaseChunkType
rg -n 'type StepTripwireData|interface StepTripwireData' packages/core/src/stream/types.ts

Repository: mastra-ai/mastra

Length of output: 99


🏁 Script executed:

cat -n client-sdks/react/src/lib/ai-sdk/utils/toUIMessage.ts | sed -n '30,60p'

Repository: mastra-ai/mastra

Length of output: 1154


🏁 Script executed:

# Also check around line 150-173 mentioned in the original comment
cat -n client-sdks/react/src/lib/ai-sdk/utils/toUIMessage.ts | sed -n '140,180p'

Repository: mastra-ai/mastra

Length of output: 1426


🏁 Script executed:

rg -n 'WorkflowFinishPayload|workflow-finish' packages/core/src/stream/types.ts | head -20

Repository: mastra-ai/mastra

Length of output: 94


🏁 Script executed:

# Find the payload structure for workflow-finish
cat -n packages/core/src/stream/types.ts | sed -n '415,435p'

Repository: mastra-ai/mastra

Length of output: 608


🏁 Script executed:

cat -n packages/core/src/stream/types.ts | sed -n '560,575p'

Repository: mastra-ai/mastra

Length of output: 546


🏁 Script executed:

# Also search for any payload type that has tripwire field
rg -n 'tripwire:' packages/core/src/stream/types.ts

Repository: mastra-ai/mastra

Length of output: 42


🏁 Script executed:

cat -n packages/core/src/stream/types.ts | sed -n '564,585p'

Repository: mastra-ai/mastra

Length of output: 718


🏁 Script executed:

# Search for tripwire in workflow-finish context more broadly
rg -B5 -A5 'workflow-finish' packages/core/src/stream/types.ts

Repository: mastra-ai/mastra

Length of output: 304


🏁 Script executed:

rg -n 'workflow-finish.*tripwire|tripwire.*workflow-finish' client-sdks/react/src/lib/ai-sdk/utils/

Repository: mastra-ai/mastra

Length of output: 42


🏁 Script executed:

# Check the test file for toUIMessage
rg -B3 -A3 'workflow-finish' client-sdks/react/src/lib/ai-sdk/utils/toUIMessage.test.ts | head -40

Repository: mastra-ai/mastra

Length of output: 1064


🏁 Script executed:

rg -n "workflowStatus.*tripwire|'tripwire'" client-sdks/react/src/lib/ai-sdk/utils/toUIMessage.test.ts

Repository: mastra-ai/mastra

Length of output: 187


🏁 Script executed:

# Find WorkflowRunStatus type definition
rg -n 'type WorkflowRunStatus|enum WorkflowRunStatus' packages/core/src/

Repository: mastra-ai/mastra

Length of output: 132


🏁 Script executed:

# Search for tripwire in workflow context
rg -n "workflowStatus.*=.*'tripwire'" packages/core/src/

Repository: mastra-ai/mastra

Length of output: 42


🏁 Script executed:

cat -n packages/core/src/workflows/types.ts | sed -n '215,225p'

Repository: mastra-ai/mastra

Length of output: 298


🏁 Script executed:

# Also check the tripwire test cases in toUIMessage.test.ts
rg -B5 -A10 "type: 'tripwire'" client-sdks/react/src/lib/ai-sdk/utils/toUIMessage.test.ts | head -50

Repository: mastra-ai/mastra

Length of output: 1036


🏁 Script executed:

rg -B5 -A15 "workflow-finish.*tripwire|tripwire.*workflow-finish" client-sdks/react/src/lib/ai-sdk/utils/toUIMessage.test.ts

Repository: mastra-ai/mastra

Length of output: 42


🏁 Script executed:

# Search more broadly for workflow-finish with tripwire in tests
rg -B10 "workflowStatus.*'tripwire'|'tripwire'.*workflow" client-sdks/react/src/lib/ai-sdk/utils/toUIMessage.test.ts

Repository: mastra-ai/mastra

Length of output: 42


🏁 Script executed:

cat -n client-sdks/react/src/lib/ai-sdk/utils/toUIMessage.ts | sed -n '34,50p'

Repository: mastra-ai/mastra

Length of output: 803


🏁 Script executed:

# Check if there are any recent changes to workflow-finish handling
git log --oneline -n 20 -- client-sdks/react/src/lib/ai-sdk/utils/toUIMessage.ts

Repository: mastra-ai/mastra

Length of output: 73


🏁 Script executed:

# Check git diff to see what was changed in this PR
git diff HEAD~1 client-sdks/react/src/lib/ai-sdk/utils/toUIMessage.ts | head -100

Repository: mastra-ai/mastra

Length of output: 246


🏁 Script executed:

# Look at the workflow-finish in the actual code to see what metadata contains
rg -B5 -A5 'workflowStatus.*tripwire' packages/core/src/

Repository: mastra-ai/mastra

Length of output: 42


🏁 Script executed:

# Check if there's any tripwire data being added to workflow-finish metadata
rg -n 'workflow-finish' packages/core/src/workflows/ | grep -i tripwire

Repository: mastra-ai/mastra

Length of output: 42


🏁 Script executed:

# Look at step-finish handling which might show the pattern
cat -n client-sdks/react/src/lib/ai-sdk/utils/toUIMessage.ts | sed -n '60,120p'

Repository: mastra-ai/mastra

Length of output: 2088


🏁 Script executed:

# Get the full workflow-finish payload structure including all fields
cat -n packages/core/src/stream/types.ts | sed -n '564,580p'

Repository: mastra-ai/mastra

Length of output: 583


🏁 Script executed:

# Check if tripwire was supposed to be added to WorkflowFinishPayload
rg -A20 "type: 'workflow-finish'" packages/core/src/stream/types.ts

Repository: mastra-ai/mastra

Length of output: 541


🏁 Script executed:

# Verify what the actual current state of the file is
head -1 client-sdks/react/src/lib/ai-sdk/utils/toUIMessage.ts

Repository: mastra-ai/mastra

Length of output: 124


🏁 Script executed:

# Verify the tripwire chunk type definition
rg -B2 -A2 "type: 'tripwire'" packages/core/src/stream/types.ts

Repository: mastra-ai/mastra

Length of output: 301


Remove the non-existent chunk.payload.tripwire check from the workflow-finish handler.

The workflow-finish chunk payload does not include a tripwire field. The payload structure contains only workflowStatus, output, and metadata. When workflowStatus is 'tripwire', the tripwire data is delivered via a separate chunk with type: 'tripwire' (already correctly handled at lines 150-173), not within the workflow-finish payload.

Lines 45-46 should be simplified to:

: {}

The condition finalStatus === 'tripwire' && chunk.payload.tripwire will never evaluate to true since chunk.payload.tripwire does not exist in the WorkflowFinishPayload type definition.

🤖 Prompt for AI Agents
In client-sdks/react/src/lib/ai-sdk/utils/toUIMessage.ts around lines 45-47,
remove the non-existent check for chunk.payload.tripwire inside the
workflow-finish handler and replace that conditional branch with an empty
object; specifically simplify the ternary that currently uses "finalStatus ===
'tripwire' && chunk.payload.tripwire ? { tripwire: ... } : {}" (or similar) so
it always yields {} for the workflow-finish path, since tripwire data comes from
separate 'tripwire' chunks and WorkflowFinishPayload contains only
workflowStatus, output, and metadata.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
packages/core/src/workflows/workflow.ts (2)

468-523: Keep messageList in sync when processors return a new MessageList.

In the result instanceof MessageList branches across input, inputStep, outputResult, and outputStep, you return updated messages/systemMessages but leave passThrough.messageList pointing at the original list. If a processor chooses to return a new MessageList instance instead of mutating the passed-in one, downstream processors will see stale messageList while messages/systemMessages reflect the new state.

Consider updating messageList in the return value:

-              if (result instanceof MessageList) {
-                return {
-                  ...passThrough,
-                  messages: result.get.all.db(),
-                  systemMessages: result.getAllSystemMessages(),
-                };
-              }
+              if (result instanceof MessageList) {
+                return {
+                  ...passThrough,
+                  messageList: result,
+                  messages: result.get.all.db(),
+                  systemMessages: result.getAllSystemMessages(),
+                };
+              }

(And mirror this in the other phases.) This keeps the canonical messageList and the derived arrays consistent for chained processors.

Also applies to: 539-593, 621-673, 687-743


468-523: Factor out shared “apply processor messages to MessageList” logic.

The message reconciliation logic (remove existing IDs, re-add messages with role-based handling, update/replace system messages) is largely duplicated across:

  • phase: 'input' (array and { messages, systemMessages } branches),
  • phase: 'inputStep',
  • phase: 'outputResult',
  • phase: 'outputStep' (with only the source 'input' vs 'response' differing).

Extracting small helpers like applyMessagesToMessageList(messageList, oldMessages, newMessages, source) and applyResultWithSystemMessages(...) would reduce repetition and make it harder for these branches to drift behavior-wise.

Also applies to: 539-593, 611-673, 687-743

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5efae23 and 1d95c64.

📒 Files selected for processing (1)
  • packages/core/src/workflows/workflow.ts (12 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Run pnpm typecheck to validate TypeScript types across all packages

Files:

  • packages/core/src/workflows/workflow.ts
**/*.{ts,tsx,js,jsx,json,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Run pnpm prettier:format to format code and pnpm format to run linting with auto-fix across all packages

Files:

  • packages/core/src/workflows/workflow.ts
packages/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

packages/**/*.{ts,tsx}: All packages must use TypeScript with strict type checking enabled
Use telemetry decorators for observability across components

Files:

  • packages/core/src/workflows/workflow.ts
🧠 Learnings (2)
📚 Learning: 2025-11-24T16:42:04.244Z
Learnt from: CR
Repo: mastra-ai/mastra PR: 0
File: packages/codemod/AGENTS.md:0-0
Timestamp: 2025-11-24T16:42:04.244Z
Learning: Applies to packages/codemod/src/codemods/v1/**/*.ts : Use `context.hasChanges = true` flag and `context.messages.push()` to track and report what transformations were made in the codemod

Applied to files:

  • packages/core/src/workflows/workflow.ts
📚 Learning: 2025-10-29T09:37:59.778Z
Learnt from: TheIsrael1
Repo: mastra-ai/mastra PR: 9247
File: packages/core/src/loop/network/index.ts:357-393
Timestamp: 2025-10-29T09:37:59.778Z
Learning: In the agent network routing completion stream (packages/core/src/loop/network/index.ts), when `completionStream.error` is true, the first stream should not have written any text (currentTextIdx should be 0), so the fallback reset of currentText and currentTextIdx does not cause duplication.

Applied to files:

  • packages/core/src/workflows/workflow.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: Socket Security: Pull Request Alerts
  • GitHub Check: test
  • GitHub Check: test
  • GitHub Check: test
  • GitHub Check: Lint
  • GitHub Check: Validate examples packages.json
  • GitHub Check: Prebuild
  • GitHub Check: test
  • GitHub Check: test (hono)
  • GitHub Check: test
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (actions)
🔇 Additional comments (5)
packages/core/src/workflows/workflow.ts (5)

4-5: New imports and type wiring are consistent with downstream usage.

The added imports for CoreMessage, MastraDBMessage, MessageList, TripWire, Processor, ProcessorStepSchema/ProcessorStepOutputSchema, ProcessorStepOutput, ChunkType, and WorkflowType are all used in the processor step implementation and workflow typing below; nothing looks unused or mis-typed here.

As per coding guidelines, please ensure pnpm typecheck, pnpm prettier:format, and pnpm format have been run after these changes.

Also applies to: 8-11, 19-21, 25-26, 53-53


161-172: Processor createStep overload matches ProcessorStep schemas and implementation.

The processor overload correctly exposes a Step<\processor:${TProcessorId}`, any, typeof ProcessorStepSchema, typeof ProcessorStepOutputSchema, any, any, DefaultEngineType>, and the union in the implementation signature includes Processor, with dispatch via isProcessor(params)`. This aligns the public type with the actual execute implementation further down.

Also applies to: 182-188


274-276: Tripwire chunk handling in agent streaming looks correct.

Capturing tripwire chunks in both legacy and vNext streaming paths, short-circuiting the loop, and then throwing a TripWire with { reason, retry, metadata, processorId } cleanly aborts the step without attempting to resolve streamPromise or return a normal result. Propagating the raw chunk in the vNext writer before throwing is also consistent with exposing tripwire events to the client.

Also applies to: 281-285, 299-305, 308-319


420-431: Processor base context and stream handling are well-shaped.

The abort implementation throwing TripWire with processor.id, the inclusion of retryCount and requestContext in baseContext, and the optional messageList on processOutputStream all match the processor API described in the PR objectives and keep responsibilities clean between workflow and processor.

Also applies to: 597-607


789-807: isProcessor type guard is robust and avoids Agent/Tool collisions.

The guard checks for an object with a string id, excludes Agent and Tool instances, and requires at least one processor method. This should safely distinguish processors from other step-like constructs for the createStep implementation branch.

Comment on lines +383 to +455
// Handle Processor - wrap it as a workflow step
if (isProcessor(params)) {
const processor = params;
return {
// @ts-ignore - processor overload has specific id type
id: `processor:${processor.id}`,
description: processor.name ?? `Processor ${processor.id}`,
// @ts-ignore - Use discriminated union for input (better UI experience)
inputSchema: ProcessorStepSchema,
// @ts-ignore - Use flexible schema for output (allows any phase combination)
outputSchema: ProcessorStepOutputSchema,
execute: async ({
inputData,
requestContext,
}: {
inputData: z.infer<typeof ProcessorStepSchema>;
requestContext: RequestContext;
}): Promise<ProcessorStepOutput> => {
// Cast to output type for easier property access - the discriminated union
// ensures type safety at the schema level, but inside the execute function
// we need access to all possible properties
const input = inputData as ProcessorStepOutput;
const {
phase,
messages,
messageList,
stepNumber,
systemMessages,
part,
streamParts,
state,
finishReason,
toolCalls,
text,
retryCount,
} = input;

// Create a minimal abort function that throws TripWire
const abort = (reason?: string, options?: { retry?: boolean; metadata?: unknown }): never => {
throw new TripWire(reason || `Tripwire triggered by ${processor.id}`, options, processor.id);
};

// Base context for all processor methods - includes requestContext for memory processors
const baseContext = {
abort,
retryCount: retryCount ?? 0,
requestContext,
};

// Pass-through data that should flow to the next processor in a chain
// This enables processor workflows to use .then(), .parallel(), .branch(), etc.
const passThrough = {
phase,
// Auto-create MessageList from messages if not provided
// This enables running processor workflows from the UI where messageList can't be serialized
messageList:
messageList ??
(Array.isArray(messages)
? new MessageList()
.add(messages as MastraDBMessage[], 'input')
.addSystem((systemMessages ?? []) as CoreMessage[])
: undefined),
stepNumber,
systemMessages,
streamParts,
state,
finishReason,
toolCalls,
text,
retryCount,
};

// TripWire errors are NOT caught here - they bubble up to halt the workflow
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard against messages being undefined before mapping IDs in processor phases.

In several branches you do:

const existingIds = (messages as MastraDBMessage[]).map(m => m.id);
passThrough.messageList.removeByIds(existingIds);

If a processor workflow is invoked with a messageList but without a messages array (which is allowed by the schema), messages will be undefined and this will throw at runtime in:

  • processInput array/object branches (Line 486 and 503),
  • processInputStep array/object branches (Line 555 and 572),
  • processOutputResult array/object branches (Line 635 and 652),
  • processOutputStep array/object branches (Line 706 and 723).

You already enforce passThrough.messageList but not messages, so this is a real edge case.

Recommend guarding the map or deriving IDs from the MessageList instead. Minimal fix example for one site:

-                const existingIds = (messages as MastraDBMessage[]).map(m => m.id);
+                const existingIds = Array.isArray(messages)
+                  ? (messages as MastraDBMessage[]).map(m => m.id)
+                  : [];

Apply the same pattern in the corresponding branches for inputStep, outputResult, and outputStep.

Also applies to: 468-523, 539-593, 611-673, 687-743

🤖 Prompt for AI Agents
In packages/core/src/workflows/workflow.ts around lines 383-455 (and also in the
other processor-phase sections at 468-523, 539-593, 611-673, 687-743), the code
assumes messages is an array and calls (messages as MastraDBMessage[]).map(...),
which will throw if messages is undefined; guard against that by deriving
existingIds only when messages is a defined array (e.g., const existingIds =
Array.isArray(messages) ? messages.map(m => m.id) : passThrough.messageList ?
passThrough.messageList.getIds() /* or [] if MessageList has no getter */ :
[];), and only call passThrough.messageList.removeByIds(existingIds) with a
proper array (possibly empty); apply this same defensive check/derivation to
each of the four branches listed (processInput array/object, processInputStep
array/object, processOutputResult array/object, processOutputStep array/object)
so removal calls never receive undefined.

Comment on lines +888 to +889
/** Type of workflow - 'processor' for processor workflows, 'default' otherwise */
public type: WorkflowType = 'default';
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Preserve workflow.type when cloning workflows.

You added a type: WorkflowType field and wire it from WorkflowConfig into the constructor with this.type = type ?? 'default', but cloneWorkflow doesn’t pass workflow.type into the new Workflow config. Cloned processor workflows will therefore be downgraded to type: 'default'.

Recommend copying type in cloneWorkflow:

   const wf: Workflow<DefaultEngineType, TSteps, TWorkflowId, TState, TInput, TOutput, TPrevSchema> = new Workflow({
     id: opts.id,
     inputSchema: workflow.inputSchema,
     outputSchema: workflow.outputSchema,
     steps: workflow.stepDefs,
     mastra: workflow.mastra,
-    options: workflow.options,
+    options: workflow.options,
+    type: workflow.type,
   });

This keeps cloned workflows’ type semantics aligned with the original.

Also applies to: 918-919, 933-933, 843-853

🤖 Prompt for AI Agents
In packages/core/src/workflows/workflow.ts around lines 843-853, 888-889,
918-919, and 933, cloneWorkflow currently omits copying workflow.type into the
new Workflow config so cloned processor workflows become 'default'; update
cloneWorkflow to pass the source workflow.type (e.g., type: workflow.type) into
the new WorkflowConfig for all clone paths so the constructed Workflow preserves
the original type.

DanielSLew and others added 2 commits December 9, 2025 16:44
- Add runtime validation for ProcessorStepOutput shape before casting
- Add defensive 'part' in result check before extracting processed part

PR comments:
- #10947 (comment)
- #10947 (comment)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Add tripwire preservation to executeConditional for proper status
conversion in fmtReturnValue, matching the pattern used in executeParallel.

PR comment: #10947 (comment)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Comment on lines +394 to +403
execute: async ({
inputData,
requestContext,
}: {
inputData: z.infer<typeof ProcessorStepSchema>;
requestContext: RequestContext;
}): Promise<ProcessorStepOutput> => {
// Cast to output type for easier property access - the discriminated union
// ensures type safety at the schema level, but inside the execute function
// we need access to all possible properties
Copy link
Contributor

@CalebBarnes CalebBarnes Dec 9, 2025

Choose a reason for hiding this comment

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

I wonder if it'd be better if this whole execute function lived in the ../processors dir and just import here executeProcessorWorkflowStep or something. Just so the processor related code is separated from the rest of generic workflows stuff since a lot of this code used to live in the processor runner. Not a huge deal though.

Copy link
Contributor

Choose a reason for hiding this comment

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

maybe even ProcessorRunner.executeWorkflow({ inputData, requestContext }) ?

@@ -0,0 +1,425 @@
# Processor Enhancements Implementation Plan
Copy link
Contributor

Choose a reason for hiding this comment

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

oh yeah we can remove this

tripwire: true,
tripwireReason,
}),
tripwire,
Copy link
Contributor

Choose a reason for hiding this comment

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

for agent legacy should we just leave it as the old tripwireReason? 🤷‍♂️

payload: {},
},
};
} else if (result.status === 'tripwire') {
Copy link
Contributor

Choose a reason for hiding this comment

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

Did you try out tripwire in inngest? It might need some special handling since inngest has its own error retrying built in. If you throw any Error that is not a NonRetriableError, it will retry the step by default. Might need to throw a NonRetriableError with cause of tripwire data or something. Worth testing out though.

memory: new Set(Array.from(this.memoryMessages.values()).map(m => m.id)),
output: new Set(Array.from(this.newResponseMessages.values()).map(m => m.id)),
input: new Set(Array.from(this.newUserMessages.values()).map(m => m.id)),
context: new Set(Array.from(this.userContextMessages.values()).map(m => m.id)),
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you remember why this was needed?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants