feat(core): support actor agent propagation#17487
Conversation
🦋 Changeset detectedLatest commit: 435e240 The changes in this PR will be included in the next version bump. This PR includes changesets to release 21 packages
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 |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughThis PR adds support for trusted background actors in agent and workflow execution by threading an optional ChangesTrusted Actor Signal Threading
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install timed out. The project may have too many dependencies for the sandbox. Comment |
PR triageLinked issue check skipped for core contributor @graysonhicks. PR complexity score
Applied label: Changed test gateChanged tests failed against the base branch as expected. Label: |
d8df49a to
a3fb5f7
Compare
0fcfdb0 to
479037d
Compare
## Stack This is **1 of 3** for #17216. - **1 of 3:** #17483 - core FGA signal (this PR) - **2 of 3:** #17484 - direct FGA call-site propagation - **3 of 3:** #17487 - agent propagation and WorkOS guidance ## What changed This adds the core FGA primitive for trusted server-side system actors. `requireFGA` and `checkFGA` now accept an explicit `systemActor` option, require an `organizationId` in the request context before bypassing provider membership resolution, and emit FGA telemetry with `actor_kind: "system"` plus null user and membership IDs. This PR intentionally stays limited to the core primitive. The follow-up PRs in this stack plumb it through direct invocation boundaries and agent/background paths. ## Validation - `pnpm --filter ./packages/core exec vitest run src/auth/ee/__tests__/fga-check.test.ts src/auth/ee/__tests__/ee-telemetry.test.ts` - `git diff --check` - `pnpm build:core` - `coderabbit review --plain --type uncommitted --base origin/main --dir packages/core/src/auth/ee` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## ELI5 This PR lets trusted server-side processes (system actors) perform authorization checks without going through normal user membership lookup, but only when an organization ID is present so it can't be misused; it also logs these special accesses for auditing. ## Overview First of three PRs implementing core Fine-Grained Authorization (FGA) support for system actors. Adds a minimal, explicit signal for trusted server-side checks so core FGA primitives can bypass provider membership resolution safely and emit appropriate telemetry. Follow-ups will propagate this signal across call boundaries and agents/background jobs. ## Key Changes - New exported type: SystemActorSignal - export type SystemActorSignal = true | { actorKind: 'system'; sourceWorkflow?: string } - FGA function updates - checkFGA and requireFGA accept an optional systemActor option. - When a valid systemActor is provided: - requireFGA/checkFGA derive the requestContext (must include organizationId) and will throw if organizationId/tenant scope is missing. - Provider membership resolution is bypassed (no provider.require call). - Enterprise telemetry is emitted with actor_kind: "system", and user_id and organization_membership_id set to null. - sourceWorkflow is taken from systemActor.sourceWorkflow if present, otherwise from requestContext.metadata.sourceWorkflow. - Non-system behavior - Telemetry for non-system checks is tagged actor_kind: "user" and normalizes absent user or membership IDs to null. - Malformed or user-controlled systemActor values do not trigger the trusted bypass and follow the normal provider path. - Type/API adjustments - requestContext was moved from RequireFGAOptions into CheckFGAOptions. - CheckFGAOptions gains systemActor?: SystemActorSignal. - RequireFGAOptions now inherits requestContext via CheckFGAOptions. - index.ts re-exports SystemActorSignal. ## Tests & Validation - Added and updated tests in packages/core/src/auth/ee/__tests__: - fga-check.test.ts: covers tenant-scoped workflow system actor bypass, missing organizationId rejection, malformed/system-like-but-not-trusted values, and ensuring user-supplied values are not trusted signals. - ee-telemetry.test.ts: validates emitted ee_feature_used telemetry for system actors (actor_kind: "system", null user/membership IDs, correct source_workflow resolution). - Changeset added and standard validation steps included (build, vitest subset, git diff/check, coderabbit review). ## Scope & Next Steps Scope limited to the core FGA primitive. Follow-up PRs will: - Propagate the system actor signal at direct FGA call sites (`#17484`) - Support agent/background propagation and WorkOS guidance (`#17487`) <!-- end of auto-generated comment: release notes by coderabbit.ai -->
479037d to
dae00e8
Compare
dae00e8 to
fd560df
Compare
fd560df to
8df132c
Compare
## Stack This is **2 of 3** for #17216. - **1 of 3:** #17483 - core FGA actor signal - **2 of 3:** #17484 - direct FGA call-site propagation (this PR) - **3 of 3:** #17487 - agent propagation and WorkOS guidance ## What changed Stacked on #17483. This plumbs the core `actor` FGA signal into direct server-side invocation boundaries: - internal `Workflow.execute` FGA checks - direct tool execution through `CoreToolBuilder` - memory thread checks through `MastraMemory.checkThreadFGA` This intentionally leaves agent public options, agentic loop tool-call propagation, and workflow engine run/start propagation to #17487. ## Validation - `git diff --check` - `pnpm --filter ./packages/core exec vitest run src/auth/ee/__tests__/fga-check.test.ts src/auth/ee/__tests__/ee-telemetry.test.ts src/workflows/workflow.test.ts src/tools/tool-builder/builder.test.ts src/memory/memory-config.test.ts` - `pnpm --filter ./packages/core check` - `pnpm build:core` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## ELI5 This PR lets trusted internal "system" processes pass a short "actor" signal so workflows, tools, and memory checks can skip tenant membership resolution and run directly when appropriate. ## Overview Second of three PRs for issue `#17216`. It plumbs a trusted server-side actor signal into direct server-side invocation boundaries in core so tenant-scoped system actors can bypass nested FGA membership resolution for internal calls. Applies to: 1. Internal workflow execution (Workflow.execute) 2. Direct tool execution via CoreToolBuilder 3. Memory thread authorization checks (MastraMemory.checkThreadFGA) Agent-facing propagation and agentic loop/tool-call propagation are deferred to `#17487`. ## Changes Made ### Core FGA signal - Replaces the previous system-specific signal with a unified ActorSignal and supports passing an actor signal (actor: ActorSignal) into FGA checks and telemetry. ### Type updates - Added optional actor?: ActorSignal to: - MastraToolInvocationOptions (packages/core/src/tools/types.ts) - ToolExecutionContext (packages/core/src/tools/types.ts) - Exported ActorSignal from packages/core/src/auth/ee and updated re-exports. ### Workflow execution - Workflow.execute(...) now accepts an optional actor?: ActorSignal parameter and forwards it into requireFGA(...) so internal workflow invocation can use the trusted actor signal. - Added a test ensuring tenant-scoped system/trusted actors bypass membership resolution during direct workflow execution (packages/core/src/workflows/workflow.test.ts). ### Tool execution - CoreToolBuilder forwards execOptions.actor into the base tool execution context for non-Vercel (Mastra) tools. - FGA enforcement for tool execution includes actor: execOptions?.actor in the requireFGA payload. - Added a test verifying trusted-actor tool invocations bypass tenant-scoped FGA resolution while still executing and forwarding actor through tool execution context (packages/core/src/tools/tool-builder/builder.test.ts). ### Memory thread checks - MastraMemory.checkThreadFGA(options) now accepts actor?: ActorSignal and includes it in the requireFGA payload. - Added a test confirming thread membership resolution is bypassed for tenant-scoped trusted actors (packages/core/src/memory/memory-config.test.ts). ### FGA check behavior & telemetry - requireFGA and related types now use actor?: ActorSignal and isActorSignal. When the actor signal indicates a trusted/system actor, requireFGA enforces the presence of requestContext.organizationId, emits enterprise telemetry with actor_kind: 'system', and skips calling fgaProvider.require. Non-actor requests behave as before (user auth required and provider.require called), with user telemetry emitted. ### Tests & Validation - New/updated unit tests across workflows, tools, memory, and EE telemetry to cover trusted-actor bypass scenarios and ensure telemetry and FGA provider calls behave as expected. - Changesets updated to document the new minor release for `@mastra/core` and show examples passing an actor to workflow.execute, tool.execute, and MastraMemory.checkThreadFGA. ## API/Signature changes - Public API surface updates: - Workflow.execute(...) now accepts actor?: ActorSignal in its options. - MastraMemory.checkThreadFGA(...) options now accept actor?: ActorSignal. - MastraToolInvocationOptions and ToolExecutionContext types now include optional actor?: ActorSignal. - ActorSignal type exported from packages/core/src/auth/ee (replaces the prior system-specific type). ## Scope notes - This PR only handles direct server-side invocation boundaries; agent public options, agentic loop propagation, and engine run/start propagation are handled in follow-up PR `#17487`. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
8df132c to
435e240
Compare
There was a problem hiding this comment.
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/handlers/entry.ts (1)
333-352:⚠️ Potential issue | 🟠 Major | ⚡ Quick winForward
actorin the standard parallel dispatch path.At Line 333,
engine.executeParallel(...)is invoked withoutactor, while other branches pass it. This drops trusted-actor context for normal parallel blocks and can cause unexpected FGA denials inside parallel step execution.Proposed fix
} else if (entry.type === 'parallel') { execResults = await engine.executeParallel({ workflowId, runId, resourceId, entry, prevStep, stepResults, serializedStepGraph, timeTravel, restart, resume, executionContext, ...observabilityContext, pubsub, abortController, requestContext, + actor, outputWriter, disableScorers, perStep, }); } else if (resume?.resumePath?.length && entry.type === 'conditional') {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/workflows/handlers/entry.ts` around lines 333 - 352, engine.executeParallel is being called without forwarding the actor, dropping trusted-actor context for normal parallel execution; update the executeParallel invocation in the entry handler to include the actor property (i.e., add actor, like other branches that call engine.executeParallel) so the trusted actor is passed through to the parallel executor (ensure the local variable actor is in scope and forwarded in the arg object alongside workflowId, runId, resourceId, etc.).
🧹 Nitpick comments (2)
packages/core/src/loop/types.ts (1)
158-159: ⚡ Quick winConsider expanding the documentation for the
actorfield.Same as the comment in
agent.types.ts— the inline comment could better explain the use case (trusted background jobs, service tokens) to help developers understand when this parameter is appropriate.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/loop/types.ts` around lines 158 - 159, Update the inline doc comment for the actor?: ActorSignal field to explain it's a trusted server-side signal used for FGA/authorization checks (e.g., background jobs, service tokens, system-level operations), clarify that it should only be set by server-side code (not from user input), and mention any security considerations such as verifying/issuing tokens and least-privilege usage; mirror the explanatory style used for the actor comment in agent.types.ts to make intended usage clear.packages/core/src/agent/agent.types.ts (1)
477-478: ⚡ Quick winConsider expanding the documentation for the
actorfield.The inline comment is minimal for a public API parameter. Developers would benefit from understanding when to use this field (e.g., trusted background jobs, cron workflows without JWT/human membership).
Consider adding a JSDoc comment that explains the use case and links to relevant documentation.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/agent/agent.types.ts` around lines 477 - 478, Expand the documentation for the actor field by replacing the terse inline comment on the actor?: ActorSignal property with a JSDoc block that explains it is a trusted server-side signal used for FGA/authorization checks (e.g., background jobs, cron workflows, or other server-only callers that lack a user JWT or human membership), outlines when to supply ActorSignal vs. relying on user tokens, and includes a reference/link to the relevant docs or FGA policy page; update the comment above the actor property (actor?: ActorSignal) so callers clearly understand its intended use, security implications, and examples of appropriate scenarios.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@packages/core/src/workflows/handlers/entry.ts`:
- Around line 333-352: engine.executeParallel is being called without forwarding
the actor, dropping trusted-actor context for normal parallel execution; update
the executeParallel invocation in the entry handler to include the actor
property (i.e., add actor, like other branches that call engine.executeParallel)
so the trusted actor is passed through to the parallel executor (ensure the
local variable actor is in scope and forwarded in the arg object alongside
workflowId, runId, resourceId, etc.).
---
Nitpick comments:
In `@packages/core/src/agent/agent.types.ts`:
- Around line 477-478: Expand the documentation for the actor field by replacing
the terse inline comment on the actor?: ActorSignal property with a JSDoc block
that explains it is a trusted server-side signal used for FGA/authorization
checks (e.g., background jobs, cron workflows, or other server-only callers that
lack a user JWT or human membership), outlines when to supply ActorSignal vs.
relying on user tokens, and includes a reference/link to the relevant docs or
FGA policy page; update the comment above the actor property (actor?:
ActorSignal) so callers clearly understand its intended use, security
implications, and examples of appropriate scenarios.
In `@packages/core/src/loop/types.ts`:
- Around line 158-159: Update the inline doc comment for the actor?: ActorSignal
field to explain it's a trusted server-side signal used for FGA/authorization
checks (e.g., background jobs, service tokens, system-level operations), clarify
that it should only be set by server-side code (not from user input), and
mention any security considerations such as verifying/issuing tokens and
least-privilege usage; mirror the explanatory style used for the actor comment
in agent.types.ts to make intended usage clear.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: ab1bc5dc-063f-4a46-aa88-00fe665295ae
📒 Files selected for processing (21)
.changeset/funny-insects-love.md.changeset/soft-rocks-smash.mdauth/workos/README.mdpackages/core/src/agent/__tests__/agent-fga.test.tspackages/core/src/agent/agent.tspackages/core/src/agent/agent.types.tspackages/core/src/agent/types.tspackages/core/src/agent/workflows/prepare-stream/map-results-step.tspackages/core/src/llm/model/model.loop.tspackages/core/src/loop/types.tspackages/core/src/loop/workflows/agentic-execution/tool-call-step.test.tspackages/core/src/loop/workflows/agentic-execution/tool-call-step.tspackages/core/src/loop/workflows/stream.tspackages/core/src/workflows/default.tspackages/core/src/workflows/execution-engine.tspackages/core/src/workflows/handlers/control-flow.tspackages/core/src/workflows/handlers/entry.tspackages/core/src/workflows/handlers/step.tspackages/core/src/workflows/step.tspackages/core/src/workflows/types.tspackages/core/src/workflows/workflow.ts
The @mastra/inngest execution engine now accepts a trusted `actor` (ActorSignal) on its run/start path and re-threads it across durable step and nested-workflow boundaries, so trusted background workflows on the Inngest engine get the same FGA membership bypass the default engine already has. - run.ts: accept `actor` on start/startAsync/resume/resumeAsync/stream/ streamLegacy/timeTravel and include it in each Inngest event payload. - workflow.ts: read `actor` from event.data in the function handler and pass it to engine.execute(). - execution-engine.ts: forward `actor` into every nested inngestStep.invoke() so nested workflows re-thread it across the serialization boundary. Mirrors core PR mastra-ai#17487 (default engine) for the durable runtime. Refs mastra-ai#17216, mastra-ai#17487. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The @mastra/inngest execution engine now accepts a trusted `actor` (ActorSignal) on its run/start path and re-threads it across durable step and nested-workflow boundaries, so trusted background workflows on the Inngest engine get the same FGA membership bypass the default engine already has. - run.ts: accept `actor` on start/startAsync/resume/resumeAsync/stream/ streamLegacy/timeTravel and include it in each Inngest event payload. - workflow.ts: read `actor` from event.data in the function handler and pass it to engine.execute(). - execution-engine.ts: forward `actor` into every nested inngestStep.invoke() so nested workflows re-thread it across the serialization boundary. Mirrors core PR mastra-ai#17487 (default engine) for the durable runtime. Refs mastra-ai#17216, mastra-ai#17487. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stack
This is 3 of 3 for #17216.
What changed
Stacked on #17484.
This PR plumbs the explicit
actorFGA signal through agent invocations and the agentic loop so trusted background work can execute without manufacturing a human membership.actorto agent generate/stream option surfaces and loop options.RequestContextstate while still requiringorganizationIdfor the FGA bypass from PR scaffold cli #1.Validation
git diff --checkpnpm --filter ./packages/core exec vitest run src/agent/__tests__/agent-fga.test.ts src/loop/workflows/agentic-execution/tool-call-step.test.ts src/workflows/workflow.test.tspnpm --filter ./packages/core checkpnpm build:coreELI5
This PR adds support for "trusted actor" propagation throughout the agent and workflow execution system. Imagine you have a scheduled background job that needs to run without a human user logging in—this PR gives that job a special "actor" credential that lets it execute safely while still respecting security checks, rather than needing to create a fake human membership just to get permission to run.
Summary
This PR implements actor signal propagation for agent and workflow execution, enabling trusted background jobs (like scheduled WorkOS cron jobs or system-triggered workflows) to execute with FGA authorization checks without requiring a human JWT or membership.
Key Changes
Type Signature Updates Across Execution Layers:
actor?: ActorSignalfield to agent execution option surfaces (AgentGenerateOptions,AgentStreamOptions,AgentExecutionOptionsBase)LoopOptions) and model stream args to include theactorsignalWorkflowRunStartOptions,DefaultExecutionEngine.execute, execution handlers) to accept and propagateactorSignal Propagation Through Execution Paths:
generate(),stream(),resumeStream(), andresumeGenerate()now extractactorfrom options, pass it to FGA checks, and remove it from inner loop options to prevent duplicationtool-call-step.ts) receivesactorfrom execution context and forwards it into tool optionsactorthrough to the execution engineAuthorization Enforcement:
#requireAgentExecutionFGA()now accepts an optionalactorparameter and forwards it to therequireFGAcallcheckFGAwith bothrequestContextandactorcontext instead of local error constructionDocumentation & Testing:
actorfor trusted background jobs, emphasizing thatorganizationIdmust be present in request context for FGA bypassagent-fga.test.ts)Design Notes
The
actorsignal is kept separate from sharedRequestContextstate while still requiringorganizationIdfor the FGA bypass. This maintains clear separation between user-context and trusted-service-context while ensuring multi-tenant isolation.