Skip to content

feat: motia quality of life improvements#1282

Open
sergiofilhowz wants to merge 5 commits intomainfrom
feat/motia-improvements
Open

feat: motia quality of life improvements#1282
sergiofilhowz wants to merge 5 commits intomainfrom
feat/motia-improvements

Conversation

@sergiofilhowz
Copy link
Contributor

@sergiofilhowz sergiofilhowz commented Mar 10, 2026

Summary

Remove type generation and decouple logger, enqueue, stateManager, and streams from FlowContext into standalone imports across both the JS and Python SDKs.

Problem

  1. Type generation caused runtime side effects -- the type generation process imported and evaluated stream/step files, which triggered runtime checks (environment variable validation, service connections, etc.) at build time. This made motia dev and motia build unreliable in environments where those runtime dependencies weren't available.

  2. Type generation was slow -- generating types for all streams and steps added significant latency to the development loop.

Solution

Remove type generation entirely. Since streams were the primary consumer of generated types (accessed via ctx.streams.name), removing typegen meant removing streams from FlowContext. Once streams was removed, keeping logger, enqueue, and state on the context no longer made sense -- they aren't execution context, they're utilities. We moved all four to standalone imports, leaving FlowContext with only actual execution context:

Before:

export const handler = async (input, { logger, enqueue, state, streams, traceId, trigger }) => { ... }

After:

import { enqueue, logger, stateManager } from 'motia'
import { todoStream } from './todo.stream'

export const handler = async (input, { traceId, trigger }) => { ... }

FlowContext now only contains: traceId, trigger, is, getData(), match().

Changes

SDK (TypeScript)

  • Removed type generation (generate-index.ts and related build utilities)
  • Created standalone logger, enqueue exports in motia package
  • Stream class now infers data types from StreamConfig['schema'] via generics (no codegen needed)
  • Removed state, enqueue, logger, streams from FlowContext interface
  • stateManager was already a standalone export

SDK (Python)

  • Created standalone logger and enqueue modules
  • Removed state, enqueue, logger, streams from FlowContext
  • Simplified _flow_context() in runtime (no longer needs Motia instance)
  • Fixed ctx.emit bug in 2 playground files (should have been enqueue)

Playground (TS + Python)

  • Updated all step files to use standalone imports
  • Removed ctx parameter from handler signatures where no longer needed
  • Stream instances are now created in .stream.ts files and imported by steps

Documentation (22 files)

  • Repurposed handler-migration-guide.mdx to cover both HTTP handler and context API migration
  • Updated migration-guide.mdx with context API section and checklist
  • Updated all code examples (TypeScript, Python, JavaScript) across the docs to use standalone imports
  • Updated HANDLER_MIGRATION_GUIDE.md (plain Markdown copy)

Migration

See the Handler & Context Migration Guide for detailed before/after examples and checklist.

Old New
ctx.logger.info(...) import { logger } from 'motia'
ctx.enqueue({...}) import { enqueue } from 'motia'
ctx.state.set(...) import { stateManager } from 'motia'
ctx.streams.todo.get(...) import { todoStream } from './todo.stream'

Feel free to adjust the wording or add any additional context about the typegen removal itself (I focused on the downstream changes since those are what the diff shows). If you want, switch to Agent mode and I can help generate the actual gh pr create command with this body.

Summary by CodeRabbit

  • New Features

    • New module-level utilities: logger and enqueue; exported stateManager and named Stream instances for direct import.
  • API Changes

    • Handler signatures simplified: context no longer injects logger/enqueue/state/streams.
    • HTTP/SSE handlers now receive structured request args (and response for streaming); FlowContext surface trimmed.
  • Documentation

    • Migration guides, examples, and checklists updated to reflect new imports and usage patterns.

@vercel
Copy link
Contributor

vercel bot commented Mar 10, 2026

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

Project Deployment Actions Updated (UTC)
iii-docs Ready Ready Preview, Comment Mar 10, 2026 10:54pm
motia-docs Ready Ready Preview, Comment Mar 10, 2026 10:54pm

Request Review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 10, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3d5262de-7751-4a5a-b9c9-9473360fd6ab

📥 Commits

Reviewing files that changed from the base of the PR and between 5e1d8f7 and d560495.

📒 Files selected for processing (1)
  • frameworks/motia/motia-py/packages/motia/tests/test_tracing_integration.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • frameworks/motia/motia-py/packages/motia/tests/test_tracing_integration.py

📝 Walkthrough

Walkthrough

FlowContext no longer provides logger, enqueue, state, or streams. New standalone exports (logger, enqueue, stateManager, Stream instances) were added and examples updated. HTTP handlers now receive MotiaHttpArgs-like first argument ({ request } or { request, response } for SSE). Docs, runtime, playgrounds, and tests adjusted accordingly.

Changes

Cohort / File(s) Summary
Migration & Guides
frameworks/motia/HANDLER_MIGRATION_GUIDE.md, frameworks/motia/docs/content/docs/getting-started/handler-migration-guide.mdx, frameworks/motia/docs/content/docs/getting-started/migration-guide.mdx, frameworks/motia/docs/content/docs/getting-started/migration-guide.mdx
Expanded migration guide: documents MotiaHttpArgs, SSE changes, middleware relocation, and removal of logger/enqueue/state/streams from FlowContext with concrete old→new examples.
Docs — Core & API
frameworks/motia/docs/content/docs/api-reference.mdx, frameworks/motia/docs/content/docs/concepts/*, frameworks/motia/docs/content/docs/development-guide/*, frameworks/motia/docs/content/docs/advanced-features/*
Trimmed FlowContext type (traceId/trigger only); updated Handlers type/examples to use standalone logger, enqueue, stateManager, and per-file stream instances; multiple examples and tables updated.
Docs — Examples & Showcases
frameworks/motia/docs/content/docs/examples/*, frameworks/motia/docs/content/docs/product-showcase/*
Updated examples to import module-level utilities and explicit stream modules (e.g., todoStream, deploymentStream); simplified handler signatures across TS/JS/Python.
TypeScript core (motia-js)
frameworks/motia/motia-js/packages/motia/src/index.ts, .../src/new/{enqueue,logger,stream}.ts, .../src/new/build/{dev,utils,cli}.ts, .../src/new/build/typegen.ts (removed), .../src/types.ts
Exported enqueue and logger; added Stream generic/config inference and instance pattern; removed typegen module/CLI command and typegen step from dev; simplified internal flowContext wiring so runtime no longer injects enqueue/state/logger/streams.
Python core (motia-py)
frameworks/motia/motia-py/packages/motia/src/motia/__init__.py, .../enqueue.py, .../logger.py, .../runtime.py, .../types.py
Added standalone enqueue and logger modules and exports; removed enqueue/state/logger/streams from FlowContext; simplified runtime context construction.
Playground — TypeScript examples
frameworks/motia/motia-js/playground/steps/**
Converted handlers to import and use logger, enqueue, stateManager, and dedicated stream instances; migrated stream/state calls to instance-based APIs (e.g., todoStream.set, stateManager.update).
Playground — Python examples
frameworks/motia/motia-py/playground/steps/**
Dropped FlowContext parameter in handlers; added imports for logger, enqueue, state_manager; replaced ctx.* usages with module-level APIs.
Tests & Test Helpers
frameworks/motia/motia-py/packages/motia/tests/*, frameworks/motia/motia-js/packages/motia/__tests__/motia-utils.test.ts
Adjusted tests to remove context-provided logger mocks, add/verify standalone enqueue behavior, update patching/mocking targets, and adapt assertions to new flow/context shapes.
Build/dev tooling
frameworks/motia/motia-js/packages/motia/src/new/build/dev.ts, .../cli.ts
Removed typegen invocation from dev flow and deleted the typegen CLI subcommand and module.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client (HTTP / SSE)
  participant Runtime as Motia Runtime
  participant Handler as User Handler
  participant State as stateManager
  participant Enq as enqueue
  participant Logger as logger

  Client->>Runtime: HTTP request (MotiaHttpArgs)
  Runtime->>Handler: invoke handler({ request }) or handler({ request, response })
  Handler->>Logger: logger.info(...)
  Handler->>State: stateManager.get/set/update(...)
  Handler->>Enq: enqueue(...)
  Handler-->>Runtime: return ApiResponse or stream via response
  Runtime-->>Client: HTTP response / SSE stream
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • ytallo
  • andersonleal

"A rabbit's refactoring ode:
Context lightened, imports take the stage,
Handlers leaner, streams step from their cage.
Logger hums, enqueue gives a hop,
StateManager tends — the pieces all hop. 🥕"

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/motia-improvements

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 (11)
frameworks/motia/motia-js/playground/steps/multi-trigger-example.step.ts (1)

143-154: ⚠️ Potential issue | 🟠 Major

This cron job will continuously replay the same orders without cleanup logic.

Lines 143-154 list from pending-orders and enqueue order.processed events on every minute (per the * * * * * schedule on line 67), but there is no code anywhere in the repository that:

  1. Populates the pending-orders state
  2. Removes or marks entries as processed after successful enqueue

Without a cleanup mechanism, if pending-orders were ever populated, the same orders would be re-enqueued on every cron tick, causing duplicate processing. Either implement atomic deletion of items after enqueue succeeds, or reconsider the state management approach for this batch processing pattern.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frameworks/motia/motia-js/playground/steps/multi-trigger-example.step.ts`
around lines 143 - 154, The cron job reads from pending-orders via
stateManager.list and re-enqueues order.processed on every tick but never clears
or marks items processed; change the flow so after a successful enqueue(order)
you atomically remove or mark that order in the same transactional/atomic
operation (e.g., use stateManager.delete or stateManager.update to set a
processed flag for the id) to prevent replays, or alternatively switch to a
pattern where pending-orders is a queue that pop()s items; ensure error handling
only deletes/marks on success and retains on failure so duplicates are avoided
while preserving reliability.
frameworks/motia/motia-py/packages/motia/src/motia/runtime.py (1)

304-311: ⚠️ Potential issue | 🔴 Critical

Support single-parameter handler signatures at the runtime boundary.

Several Python steps migrated in this PR now define handler(request) with no ctx, but every trigger path here still calls handler(payload, context); the middleware lambda does the same. Those handlers will fail immediately with TypeError on first execution. Please funnel invocation through one arity-aware helper so both handler(request) and handler(request, ctx) remain supported during the migration.

Also applies to: 375-376, 412-413, 447-448, 479-480

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frameworks/motia/motia-py/packages/motia/src/motia/runtime.py` around lines
304 - 311, The runtime currently always calls handlers with two arguments
(handler(motia_request, context)) which breaks handlers that accept only a
single parameter; introduce a small arity-aware invoker (e.g.,
call_handler(handler, motia_request, context)) and use it wherever handlers are
invoked (the direct calls and the middleware lambda created from
_compose_middleware), so call_handler inspects handler.__code__.co_argcount or
uses try/except to call handler(request) if it only accepts one arg and
handler(request, ctx) otherwise; update the usages around the composed
invocation and the direct await handler(...) sites (the blocks that create
composed = _compose_middleware(middlewares) and the else branches) to use
call_handler instead of calling handler directly.
frameworks/motia/docs/content/docs/development-guide/project-structure.mdx (1)

335-340: ⚠️ Potential issue | 🔴 Critical

Update Python example to use standard datetime instead of non-existent ctx.utils.dates.

The Python handler example at line 338 references ctx.utils.dates.now(), but FlowContext only provides trace_id, trigger, input_value, and is_queue(). The utils attribute does not exist. Replace ctx.utils.dates.now().isoformat() with Python's standard library, e.g., datetime.now(timezone.utc).isoformat() or similar.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frameworks/motia/docs/content/docs/development-guide/project-structure.mdx`
around lines 335 - 340, The example handler uses a nonexistent
ctx.utils.dates.now(); update the code in async def handler(input_data, ctx) to
import and use Python's standard datetime functions: replace
ctx.utils.dates.now().isoformat() with an expression using
datetime.now(timezone.utc).isoformat() (and add the necessary from datetime
import datetime, timezone import at top) so the handler and FlowContext (ctx) no
longer rely on a non-existent ctx.utils attribute.
frameworks/motia/motia-js/playground/steps/streamParallelMerge/parallel-merge.stream.ts (1)

10-16: ⚠️ Potential issue | 🟡 Minor

Preserve schema inference by using satisfies instead of explicit type annotation.

Typing config as StreamConfig widens the type to the base interface, causing new Stream(config) to infer unknown for the item type instead of the concrete Zod-derived shape. Use satisfies StreamConfig to preserve the literal object type while maintaining validation.

♻️ Minimal change
 export const config: StreamConfig = {
   baseConfig: { storageType: 'default' },
   name: 'parallelMerge',
   schema: parallelMergeSchema,
-}
+} satisfies StreamConfig
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frameworks/motia/motia-js/playground/steps/streamParallelMerge/parallel-merge.stream.ts`
around lines 10 - 16, The explicit type annotation on the config object (config:
StreamConfig) widens its type and causes new Stream(config) to lose the concrete
Zod-derived item type; remove the explicit annotation and instead declare the
object using TypeScript's satisfies operator (e.g., let config = { ... }
satisfies StreamConfig) so the literal shape (including parallelMergeSchema) is
preserved while still conforming to StreamConfig, then keep parallelMergeStream
= new Stream(config) as before.
frameworks/motia/docs/content/docs/examples/human-in-the-loop-workflows.mdx (1)

61-74: ⚠️ Potential issue | 🟠 Major

Add a bodySchema to the TypeScript HTTP examples.

With Handlers<typeof config>, the HTTP request body is inferred from bodySchema. The examples at lines 67 and 446 omit it but still access request.body.items, request.body.total, and destructure request.body, so they do not type-check as written.

🧩 Minimal fix
+import { z } from 'zod'
+
+const submitOrderBody = z.object({
+  items: z.array(z.object({
+    name: z.string(),
+    price: z.number(),
+    quantity: z.number(),
+  })),
+  total: z.number(),
+})
+
 export const config = {
   name: 'SubmitOrder',
   description: 'Submit an order for processing',
   triggers: [
-    { type: 'http', path: '/orders', method: 'POST' },
+    { type: 'http', path: '/orders', method: 'POST', bodySchema: submitOrderBody },
   ],
   enqueues: ['order.submitted'],
   flows: ['order-approval'],
 } as const satisfies StepConfig

Apply the same pattern to the approval webhook example at line 446.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frameworks/motia/docs/content/docs/examples/human-in-the-loop-workflows.mdx`
around lines 61 - 74, The HTTP step config (export const config) lacks a
bodySchema so Handlers<typeof config> cannot infer request.body types; add a
bodySchema property to the SubmitOrder config that defines the request body
shape (e.g., items array, total number, and any other fields accessed by
handler) so request.body.items and request.body.total are typed, and apply the
same pattern to the approval webhook step (the other config referenced at the
approval example) so its handler's destructured request.body is also
type-checked.
frameworks/motia/HANDLER_MIGRATION_GUIDE.md (1)

595-623: ⚠️ Potential issue | 🟡 Minor

Python checklist contradicts the examples above.

The simple HTTP example shows a single-argument handler (async def handler(request: ApiRequest[Any]) -> ApiResponse[Any]), but the Python checklist still instructs developers to add FlowContext as a second parameter. Since logger, enqueue, and state are now standalone imports, most handlers don't need ctx at all.

📝 Suggested doc fix
-- [ ] Add imports: `from motia import ApiRequest, ApiResponse, FlowContext, http`
+- [ ] Add imports: `from motia import ApiRequest, ApiResponse, http` (or `MotiaHttpArgs` for SSE)
@@
-- [ ] Change handler signature to `handler(request: ApiRequest[Any], ctx: FlowContext[Any]) -> ApiResponse[Any]`
+- [ ] Change handler signature to `handler(request: ApiRequest[Any]) -> ApiResponse[Any]` when `ctx` is unused; keep `FlowContext[Any]` only when you need `traceId`, `trigger`, `is`, `getData()`, or `match()`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frameworks/motia/HANDLER_MIGRATION_GUIDE.md` around lines 595 - 623, Update
the Python checklist to match the examples by removing the mandatory FlowContext
from the canonical handler signature: change the line that says to "Change
handler signature to handler(request: ApiRequest[Any], ctx: FlowContext[Any]) ->
ApiResponse[Any]" to instead state the primary signature is "handler(request:
ApiRequest[Any]) -> ApiResponse[Any]" and note that FlowContext (ctx) may be
added as a second optional parameter only when context features (logger,
enqueue, state, streams) are actually needed; reference the symbols ApiRequest,
ApiResponse, FlowContext, and handler so readers know which signature to apply.
frameworks/motia/motia-js/packages/motia/src/new/stream.ts (1)

57-67: ⚠️ Potential issue | 🔴 Critical

Await the delete RPC call so failures hit the catch block.

Line 63 returns the promise directly without awaiting. This causes rejections to skip the catch block, preventing span.setStatus(...) and span.recordException(...) from executing. All other methods in this class (get, set, list, update) correctly await the RPC call before returning.

🛠 Suggested fix
   async delete(groupId: string, itemId: string): Promise<void> {
     return withSpan('stream::delete', {}, async (span) => {
       span.setAttribute('motia.stream.name', this.config.name)
       span.setAttribute('motia.stream.group_id', groupId)
       span.setAttribute('motia.stream.item_id', itemId)
       try {
-        return getInstance().call('stream::delete', {
+        await getInstance().call('stream::delete', {
           stream_name: this.config.name,
           group_id: groupId,
           item_id: itemId,
         })
       } catch (err) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frameworks/motia/motia-js/packages/motia/src/new/stream.ts` around lines 57 -
67, In the delete method, the RPC promise from
getInstance().call('stream::delete', ...) is returned directly so rejections
bypass the try/catch; change it to await the call inside the try (i.e., await
getInstance().call(...)) so errors are caught and the span error handling
(span.setStatus and span.recordException) in the surrounding withSpan block will
execute; locate the async delete(groupId: string, itemId: string) function and
replace the direct return of getInstance().call with an awaited call and then
return (or simply return after awaiting).
frameworks/motia/docs/content/docs/getting-started/migration-guide.mdx (1)

650-670: ⚠️ Potential issue | 🟠 Major

Remove : StreamConfig annotation to preserve schema-driven typing.

When config is explicitly annotated as StreamConfig, the type parameter TConfig becomes the base interface type, causing InferStreamData<TConfig> to evaluate its conditional to unknown. This collapses the Zod schema inference, making get/set/list/update methods return unknown instead of the typed data.

Use as const satisfies StreamConfig instead to allow TypeScript to infer the specific literal object type while validating it against the interface:

Suggested fix
-export const config: StreamConfig = {
+export const config = {
   name: 'deployment',
   baseConfig: { storageType: 'default' },
   schema: z.object({
@@ -14,7 +14,7 @@
   onLeave: async (subscription, _context, authContext) => {
     logger.info('Client left stream', { subscription, authContext })
   },
-}
+} as const satisfies StreamConfig
 
 export const deploymentStream = new Stream(config)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frameworks/motia/docs/content/docs/getting-started/migration-guide.mdx`
around lines 650 - 670, Remove the explicit ": StreamConfig" annotation on the
config object and instead let TypeScript infer the literal shape by applying "as
const satisfies StreamConfig" to the config variable; update the declaration of
config (used by deploymentStream and passed to new Stream(...)) to drop the type
annotation and use the "as const satisfies StreamConfig" pattern so
InferStreamData<TConfig> resolves from the Zod schema and methods like
get/set/list/update retain proper typed data.
frameworks/motia/docs/content/docs/api-reference.mdx (2)

802-815: ⚠️ Potential issue | 🟡 Minor

FlowContext generic signature inconsistent with migration guide.

This shows FlowContext<TInput> with one generic, but the migration guide (handler-migration-guide.mdx line 35) shows FlowContext<TEnqueueData = never, TInput = unknown> with two generics. Consider aligning these definitions for consistency across documentation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frameworks/motia/docs/content/docs/api-reference.mdx` around lines 802 - 815,
Update the FlowContext interface to match the migration guide by adding the
missing enqueue-data generic: change the declaration from FlowContext<TInput> to
FlowContext<TEnqueueData = never, TInput = unknown>, update any internal uses
(getData, match, and the is.* handlers) to reference the new generics where
appropriate, and ensure default types (never and unknown) are preserved so
existing usages remain backwards-compatible.

1131-1155: ⚠️ Potential issue | 🟠 Major

Fix Python example method names to use snake_case.

The Python example uses incorrect camelCase method names that don't match the actual SDK. The Motia Python SDK uses snake_case naming (e.g., get_group, not getGroup). Additionally, line 1145 calls send(), which doesn't exist in the Python SDK. Update the example to use the correct method names:

  • getGroup()get_group()
  • send() → remove or replace with a valid SDK method
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frameworks/motia/docs/content/docs/api-reference.mdx` around lines 1131 -
1155, Update the Python example to use snake_case SDK methods instead of
camelCase: replace chat_messages_stream.getGroup with
chat_messages_stream.get_group, get with get, set with set, delete with delete,
and update with update (ensure all method calls follow snake_case naming like
chat_messages_stream.get_group and chat_messages_stream.set); also remove the
chat_messages_stream.send call (or replace it with a valid Motia Python SDK
method if you intend to demonstrate events) so the example only uses existing
Python SDK methods.
frameworks/motia/docs/content/docs/getting-started/handler-migration-guide.mdx (1)

670-671: ⚠️ Potential issue | 🟡 Minor

Migration checklist contradicts example code.

The checklist says to change Python handler signature to handler(request: ApiRequest[Any], ctx: FlowContext[Any]) with context, but the example at lines 432-436 shows async def handler(request: ApiRequest[Any]) -> ApiResponse[Any]: without context. These should be aligned.

Based on the migration direction (moving away from context), the checklist item should likely be updated to show the context is optional or only needed when accessing traceId.

♻️ Suggested fix
-- Change handler signature to `handler(request: ApiRequest[Any], ctx: FlowContext[Any]) -> ApiResponse[Any]`
+- Change handler signature to `handler(request: ApiRequest[Any]) -> ApiResponse[Any]` (add `ctx: FlowContext[Any]` only if you need `ctx.trace_id`)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frameworks/motia/docs/content/docs/getting-started/handler-migration-guide.mdx`
around lines 670 - 671, The migration docs are inconsistent: update the
checklist and example so both reflect that the Python handler signature can be
either with or without context; change the checklist line about handler
signature to state "handler(request: ApiRequest[Any], ctx: FlowContext[Any] |
None) -> ApiResponse[Any]" or note that ctx is optional and only required to
access ctx.traceId, and update the example at the async def handler to either
include ctx or document that traceId access requires ctx; also ensure the
guidance to replace req.get("body", {}) with request.body remains present.
🧹 Nitpick comments (6)
frameworks/motia/motia-py/playground/steps/stream_example/on_todo_event_step.py (1)

20-21: Unnecessary f-string prefix.

f"Todo stream event" doesn't contain any interpolated expressions. Consider removing the f prefix for clarity.

✨ Suggested fix
-    logger.info(
-        f"Todo stream event",
+    logger.info(
+        "Todo stream event",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frameworks/motia/motia-py/playground/steps/stream_example/on_todo_event_step.py`
around lines 20 - 21, The logger.info call in on_todo_event_step.py uses an
unnecessary f-string: update the logger.info invocation (the logger.info("Todo
stream event") call in the on_todo_event_step module/function) to remove the f
prefix so it becomes a regular string literal; scan nearby logger.* usages in
the same function to ensure no other non-interpolated f-strings remain and
replace them similarly.
frameworks/motia/motia-js/packages/motia/src/new/build/utils.ts (1)

180-197: Consider preserving generic type information in the cast.

The type casts as FlowContext at lines 186 and 197 discard the generic type parameters. If FlowContext is generic (FlowContext<EnqueueData, TInput>), casting to the non-generic form may lose type safety in downstream code. Consider using the properly typed context variable directly if possible.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frameworks/motia/motia-js/packages/motia/src/new/build/utils.ts` around lines
180 - 197, The casts "as FlowContext" on context passed into
composeMiddleware(...) and step.handler(...) strip generic parameters and lose
type information; instead remove those non-generic casts and preserve the
specific generic type on context (e.g., use the concrete
FlowContext<EnqueueData, TInput> or the generic inferred type from
flowContext/motiaRequest) so the compiler retains the full type parameters when
calling composeMiddleware(...) and step.handler(...); update the
declaration/annotation of context (from flowContext(...)) or use a properly
typed local like const context: FlowContext<...> = flowContext(...) and pass
that to composeMiddleware and step.handler without casting.
frameworks/motia/motia-js/playground/steps/parallelMerge/join-complete.step.ts (1)

31-52: Consider using logger.info for the summary output.

The ASCII art banner uses console.log directly. While this may be intentional for visual formatting in the console, consider whether using the structured logger would provide better observability (traceability, log aggregation). If the visual output is deliberately separate from structured logs, this can remain as-is.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frameworks/motia/motia-js/playground/steps/parallelMerge/join-complete.step.ts`
around lines 31 - 52, Replace the direct console.log calls in the
join-complete.step.ts banner and summary with the structured logger (e.g.,
logger.info) so outputs like the ASCII banner and the summary lines that
reference result.totalSteps, endTime - result.startedAt, and traceId are emitted
via logger.info rather than console.log; keep the exact message
strings/formatting but call logger.info for each line (or, if the banner must
remain unstructured, add a short comment explaining why and only swap the
summary lines that use result.totalSteps, endTime, result.startedAt, and traceId
to logger.info).
frameworks/motia/docs/content/docs/advanced-features/atomic-updates.mdx (1)

12-29: Consider updating the conceptual code blocks for consistency.

The "Problem" (Lines 12-17) and "Solution" (Lines 25-29) sections use state.get/state.set/state.update without imports, which may confuse readers since all other examples now use stateManager. While these are conceptual illustrations, updating them to use stateManager would maintain consistency throughout the document.

Suggested update for consistency
 The traditional get-then-set pattern is not safe when multiple Steps run concurrently:
 
 ```typescript
-const order = await state.get('orders', orderId)
+const order = await stateManager.get('orders', orderId)
 order.completedSteps += 1
 order.status = 'progress'
-await state.set('orders', orderId, order)
+await stateManager.set('orders', orderId, order)

```diff
 Use `update()` with `UpdateOp[]` for atomic operations:
 
 ```typescript
-await state.update('orders', orderId, [
+await stateManager.update('orders', orderId, [
   { type: 'increment', path: 'completedSteps', by: 1 },
   { type: 'set', path: 'status', value: 'progress' },
 ])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frameworks/motia/docs/content/docs/advanced-features/atomic-updates.mdx`
around lines 12 - 29, Update the conceptual code examples to use the same
identifier as the rest of the docs by replacing calls to state.get, state.set
and state.update with stateManager.get, stateManager.set and stateManager.update
respectively (so the "Problem" block uses stateManager.get/stateManager.set and
the "Solution" block uses stateManager.update) to keep examples consistent with
other examples that reference stateManager.
frameworks/motia/motia-js/playground/steps/todo/create-todo.step.ts (1)

47-53: Clarify the intent of dual storage in stream and state.

The todo is stored in both todoStream (Line 47) and stateManager (Line 49). If this is intentional (e.g., stream for real-time updates, state for persistence/querying), consider adding a brief comment explaining the rationale. If not intentional, one of these may be redundant.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frameworks/motia/motia-js/playground/steps/todo/create-todo.step.ts` around
lines 47 - 53, The code writes the same todo to both todoStream.set('inbox',
todoId, newTodo) and stateManager.set('todos', todoId, newTodo) which is
ambiguous; either add a one-line comment above these calls explaining the intent
(e.g., "write to todoStream for real-time/event subscribers and to stateManager
for durable/queryable state") or remove the redundant write if only one storage
is intended—update the logic around todoStream.set and stateManager.set
accordingly so the purpose is explicit.
frameworks/motia/docs/content/docs/api-reference.mdx (1)

612-630: Inconsistent Python handler signature.

The handler on line 614 accepts (input_data, context) but never uses context. Other Python examples in this file (e.g., line 694 for cron handlers) omit the context parameter entirely. For consistency with the migration to standalone imports, consider removing the unused context parameter.

♻️ Suggested fix
-async def handler(input_data, context):
+async def handler(input_data):
     order_id = input_data.get("order_id")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frameworks/motia/docs/content/docs/api-reference.mdx` around lines 612 - 630,
The handler function signature uses an unused context parameter—update the
handler declaration (the async function named handler) to remove the unused
context parameter so it only accepts input_data (async def
handler(input_data):), and ensure any references in adjacent examples or
documentation for enqueue, logger, and state_manager remain consistent with the
new signature; no other logic changes required.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frameworks/motia/docs/content/docs/concepts/overview.mdx`:
- Around line 259-267: Add clarification that state and streams are available on
the handler context: update the overview list of ctx properties to include
`state` and `streams`, and explain they are accessed via the handler context
(e.g., use ctx.state.set/get and ctx.streams.notifications.set) rather than as
globals or separate imports; mention the `state` API for persistent key/value
operations and the `streams` namespace for real-time channel operations so
readers know to call these from the `ctx` object (referencing symbols: ctx,
state.set/get, streams.notifications.set).

In `@frameworks/motia/docs/content/docs/concepts/steps.mdx`:
- Around line 680-684: Update the Python example to use the documented
snake_case name: change the import and usages from stateManager to state_manager
so the snippet reads use of state_manager.set(...) and state_manager.get(...) to
match other examples; ensure both the from motia import ... line and subsequent
calls reference state_manager instead of stateManager.

In `@frameworks/motia/docs/content/docs/examples/human-in-the-loop-workflows.mdx`:
- Around line 213-215: The handler function currently dereferences order
immediately after await stateManager.get('orders', orderId); add an early
null/undefined guard right after that call (e.g., if (!order) return appropriate
404/early-exit) before accessing order.completedSteps or order.completed_steps
to avoid runtime errors; apply the same fix to the other snippets that load
orders (the blocks using stateManager.get('orders', orderId) and then reading
completedSteps/completed_steps) so each checks the fetched order and
returns/throws gracefully when missing.

In
`@frameworks/motia/docs/content/docs/getting-started/handler-migration-guide.mdx`:
- Around line 597-601: The handler example uses the signature "async def
handler(args: MotiaHttpArgs[Any]) -> None" which is inconsistent with the
api-reference signature that includes a FlowContext; update the example to
"async def handler(args: MotiaHttpArgs[Any], ctx: FlowContext[Any]) -> None"
(keep the names MotiaHttpArgs and FlowContext intact), and if the example uses
no ctx values add a short unused parameter comment or reference (e.g., use ctx
or _ctx) so the signature and docs are consistent with the api-reference.
- Around line 34-47: The FlowContext interface currently declares two generics
(TEnqueueData, TInput) but the simplified API uses only TInput; remove the
unused TEnqueueData generic from the FlowContext declaration (change to
FlowContext<TInput = unknown>) and update the dependent type usages inside the
interface: drop any reliance on TEnqueueData and adjust the match signature to
use the single-generic form of MatchHandlers (or pass never for the enqueue
generic if MatchHandlers still expects it). Update referenced symbols:
FlowContext, MatchHandlers, ExtractQueueInput, ExtractApiInput,
ExtractStateInput, ExtractStreamInput, getData, and match so all types align
with the single generic TInput.

In `@frameworks/motia/docs/content/docs/getting-started/migration-guide.mdx`:
- Around line 217-223: The logger call in authenticateStream currently logs the
entire req object (logger.info('Authenticating stream', { req })), which may
include sensitive tokens; change it to avoid printing req directly by either
logging only a non-sensitive identifier (e.g., req.id or req.path) or a redacted
summary (e.g., { method: req.method, path: req.url }) or remove the req param
entirely and keep a generic message; ensure authenticateStream and the
logger.info invocation are updated accordingly so no full request/headers/query
params are emitted.

In `@frameworks/motia/motia-js/packages/motia/src/new/enqueue.ts`:
- Around line 4-5: The enqueue wrapper currently calls
getInstance().call('enqueue', queue) without telemetry; update the enqueue
function to use withSpan('enqueue', {}, async (span) => { ... }) and mirror
other wrappers: import SpanStatusCode and withSpan from 'iii-sdk/telemetry',
perform the getInstance().call('enqueue', queue) inside the async span, catch
errors to call span.setStatus({ code: SpanStatusCode.ERROR, message: String(err)
}) and span.recordException(err as Error) before rethrowing the error so enqueue
has the same span instrumentation and error handling as stream/state operations.

In `@frameworks/motia/motia-js/playground/steps/todo/inbox.stream.ts`:
- Line 14: The config variable is explicitly annotated as StreamConfig which
widens the type and prevents Stream<TConfig> from inferring the concrete schema;
remove the explicit ": StreamConfig" annotation on the config used to construct
inboxStream and instead keep the literal config and append "as const satisfies
StreamConfig" so the object remains a literal for type inference while still
conforming to StreamConfig; update the code around the inboxStream construction
(referenced symbols: inboxStream, Stream, StreamConfig, InferSchema, get, set)
so inboxStream methods regain proper payload types.

In `@frameworks/motia/motia-py/packages/motia/src/motia/enqueue.py`:
- Around line 11-12: The enqueue span is being stamped with an empty
motia.step.name via operation_span("enqueue", **{"motia.step.name": ""}); remove
the hardcoded empty attribute and only include motia.step.name when a non-empty
step name is available (i.e., call operation_span("enqueue") or build the kwargs
conditionally before calling operation_span). Update the code around
operation_span and get_instance().call("enqueue", event) so motia.step.name is
omitted instead of set to "" when no current step name exists.

In `@frameworks/motia/motia-py/packages/motia/src/motia/runtime.py`:
- Around line 565-566: Replace the incorrect TriggerInfo type "queue" used when
creating flow contexts for stream lifecycle and auth callbacks with an
appropriate stream-specific trigger (e.g., "stream" or a dedicated lifecycle
trigger) so ctx.trigger, ctx.is(...) and ctx.match() reflect stream semantics;
update the TriggerInfo construction used before calling _flow_context in the
FlowContext creation sites (see usages around StreamSubscription creation and
functions join, leave, authenticate and the similar spots at the other indicated
locations) so they pass TriggerInfo(type="stream") (or your chosen lifecycle
trigger) instead of "queue".

---

Outside diff comments:
In `@frameworks/motia/docs/content/docs/api-reference.mdx`:
- Around line 802-815: Update the FlowContext interface to match the migration
guide by adding the missing enqueue-data generic: change the declaration from
FlowContext<TInput> to FlowContext<TEnqueueData = never, TInput = unknown>,
update any internal uses (getData, match, and the is.* handlers) to reference
the new generics where appropriate, and ensure default types (never and unknown)
are preserved so existing usages remain backwards-compatible.
- Around line 1131-1155: Update the Python example to use snake_case SDK methods
instead of camelCase: replace chat_messages_stream.getGroup with
chat_messages_stream.get_group, get with get, set with set, delete with delete,
and update with update (ensure all method calls follow snake_case naming like
chat_messages_stream.get_group and chat_messages_stream.set); also remove the
chat_messages_stream.send call (or replace it with a valid Motia Python SDK
method if you intend to demonstrate events) so the example only uses existing
Python SDK methods.

In `@frameworks/motia/docs/content/docs/development-guide/project-structure.mdx`:
- Around line 335-340: The example handler uses a nonexistent
ctx.utils.dates.now(); update the code in async def handler(input_data, ctx) to
import and use Python's standard datetime functions: replace
ctx.utils.dates.now().isoformat() with an expression using
datetime.now(timezone.utc).isoformat() (and add the necessary from datetime
import datetime, timezone import at top) so the handler and FlowContext (ctx) no
longer rely on a non-existent ctx.utils attribute.

In `@frameworks/motia/docs/content/docs/examples/human-in-the-loop-workflows.mdx`:
- Around line 61-74: The HTTP step config (export const config) lacks a
bodySchema so Handlers<typeof config> cannot infer request.body types; add a
bodySchema property to the SubmitOrder config that defines the request body
shape (e.g., items array, total number, and any other fields accessed by
handler) so request.body.items and request.body.total are typed, and apply the
same pattern to the approval webhook step (the other config referenced at the
approval example) so its handler's destructured request.body is also
type-checked.

In
`@frameworks/motia/docs/content/docs/getting-started/handler-migration-guide.mdx`:
- Around line 670-671: The migration docs are inconsistent: update the checklist
and example so both reflect that the Python handler signature can be either with
or without context; change the checklist line about handler signature to state
"handler(request: ApiRequest[Any], ctx: FlowContext[Any] | None) ->
ApiResponse[Any]" or note that ctx is optional and only required to access
ctx.traceId, and update the example at the async def handler to either include
ctx or document that traceId access requires ctx; also ensure the guidance to
replace req.get("body", {}) with request.body remains present.

In `@frameworks/motia/docs/content/docs/getting-started/migration-guide.mdx`:
- Around line 650-670: Remove the explicit ": StreamConfig" annotation on the
config object and instead let TypeScript infer the literal shape by applying "as
const satisfies StreamConfig" to the config variable; update the declaration of
config (used by deploymentStream and passed to new Stream(...)) to drop the type
annotation and use the "as const satisfies StreamConfig" pattern so
InferStreamData<TConfig> resolves from the Zod schema and methods like
get/set/list/update retain proper typed data.

In `@frameworks/motia/HANDLER_MIGRATION_GUIDE.md`:
- Around line 595-623: Update the Python checklist to match the examples by
removing the mandatory FlowContext from the canonical handler signature: change
the line that says to "Change handler signature to handler(request:
ApiRequest[Any], ctx: FlowContext[Any]) -> ApiResponse[Any]" to instead state
the primary signature is "handler(request: ApiRequest[Any]) -> ApiResponse[Any]"
and note that FlowContext (ctx) may be added as a second optional parameter only
when context features (logger, enqueue, state, streams) are actually needed;
reference the symbols ApiRequest, ApiResponse, FlowContext, and handler so
readers know which signature to apply.

In `@frameworks/motia/motia-js/packages/motia/src/new/stream.ts`:
- Around line 57-67: In the delete method, the RPC promise from
getInstance().call('stream::delete', ...) is returned directly so rejections
bypass the try/catch; change it to await the call inside the try (i.e., await
getInstance().call(...)) so errors are caught and the span error handling
(span.setStatus and span.recordException) in the surrounding withSpan block will
execute; locate the async delete(groupId: string, itemId: string) function and
replace the direct return of getInstance().call with an awaited call and then
return (or simply return after awaiting).

In `@frameworks/motia/motia-js/playground/steps/multi-trigger-example.step.ts`:
- Around line 143-154: The cron job reads from pending-orders via
stateManager.list and re-enqueues order.processed on every tick but never clears
or marks items processed; change the flow so after a successful enqueue(order)
you atomically remove or mark that order in the same transactional/atomic
operation (e.g., use stateManager.delete or stateManager.update to set a
processed flag for the id) to prevent replays, or alternatively switch to a
pattern where pending-orders is a queue that pop()s items; ensure error handling
only deletes/marks on success and retains on failure so duplicates are avoided
while preserving reliability.

In
`@frameworks/motia/motia-js/playground/steps/streamParallelMerge/parallel-merge.stream.ts`:
- Around line 10-16: The explicit type annotation on the config object (config:
StreamConfig) widens its type and causes new Stream(config) to lose the concrete
Zod-derived item type; remove the explicit annotation and instead declare the
object using TypeScript's satisfies operator (e.g., let config = { ... }
satisfies StreamConfig) so the literal shape (including parallelMergeSchema) is
preserved while still conforming to StreamConfig, then keep parallelMergeStream
= new Stream(config) as before.

In `@frameworks/motia/motia-py/packages/motia/src/motia/runtime.py`:
- Around line 304-311: The runtime currently always calls handlers with two
arguments (handler(motia_request, context)) which breaks handlers that accept
only a single parameter; introduce a small arity-aware invoker (e.g.,
call_handler(handler, motia_request, context)) and use it wherever handlers are
invoked (the direct calls and the middleware lambda created from
_compose_middleware), so call_handler inspects handler.__code__.co_argcount or
uses try/except to call handler(request) if it only accepts one arg and
handler(request, ctx) otherwise; update the usages around the composed
invocation and the direct await handler(...) sites (the blocks that create
composed = _compose_middleware(middlewares) and the else branches) to use
call_handler instead of calling handler directly.

---

Nitpick comments:
In `@frameworks/motia/docs/content/docs/advanced-features/atomic-updates.mdx`:
- Around line 12-29: Update the conceptual code examples to use the same
identifier as the rest of the docs by replacing calls to state.get, state.set
and state.update with stateManager.get, stateManager.set and stateManager.update
respectively (so the "Problem" block uses stateManager.get/stateManager.set and
the "Solution" block uses stateManager.update) to keep examples consistent with
other examples that reference stateManager.

In `@frameworks/motia/docs/content/docs/api-reference.mdx`:
- Around line 612-630: The handler function signature uses an unused context
parameter—update the handler declaration (the async function named handler) to
remove the unused context parameter so it only accepts input_data (async def
handler(input_data):), and ensure any references in adjacent examples or
documentation for enqueue, logger, and state_manager remain consistent with the
new signature; no other logic changes required.

In `@frameworks/motia/motia-js/packages/motia/src/new/build/utils.ts`:
- Around line 180-197: The casts "as FlowContext" on context passed into
composeMiddleware(...) and step.handler(...) strip generic parameters and lose
type information; instead remove those non-generic casts and preserve the
specific generic type on context (e.g., use the concrete
FlowContext<EnqueueData, TInput> or the generic inferred type from
flowContext/motiaRequest) so the compiler retains the full type parameters when
calling composeMiddleware(...) and step.handler(...); update the
declaration/annotation of context (from flowContext(...)) or use a properly
typed local like const context: FlowContext<...> = flowContext(...) and pass
that to composeMiddleware and step.handler without casting.

In
`@frameworks/motia/motia-js/playground/steps/parallelMerge/join-complete.step.ts`:
- Around line 31-52: Replace the direct console.log calls in the
join-complete.step.ts banner and summary with the structured logger (e.g.,
logger.info) so outputs like the ASCII banner and the summary lines that
reference result.totalSteps, endTime - result.startedAt, and traceId are emitted
via logger.info rather than console.log; keep the exact message
strings/formatting but call logger.info for each line (or, if the banner must
remain unstructured, add a short comment explaining why and only swap the
summary lines that use result.totalSteps, endTime, result.startedAt, and traceId
to logger.info).

In `@frameworks/motia/motia-js/playground/steps/todo/create-todo.step.ts`:
- Around line 47-53: The code writes the same todo to both
todoStream.set('inbox', todoId, newTodo) and stateManager.set('todos', todoId,
newTodo) which is ambiguous; either add a one-line comment above these calls
explaining the intent (e.g., "write to todoStream for real-time/event
subscribers and to stateManager for durable/queryable state") or remove the
redundant write if only one storage is intended—update the logic around
todoStream.set and stateManager.set accordingly so the purpose is explicit.

In
`@frameworks/motia/motia-py/playground/steps/stream_example/on_todo_event_step.py`:
- Around line 20-21: The logger.info call in on_todo_event_step.py uses an
unnecessary f-string: update the logger.info invocation (the logger.info("Todo
stream event") call in the on_todo_event_step module/function) to remove the f
prefix so it becomes a regular string literal; scan nearby logger.* usages in
the same function to ensure no other non-interpolated f-strings remain and
replace them similarly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 717b4337-5ec9-4d66-94c4-ee648c35b80a

📥 Commits

Reviewing files that changed from the base of the PR and between 76952f9 and 27fc3d8.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (89)
  • frameworks/motia/HANDLER_MIGRATION_GUIDE.md
  • frameworks/motia/docs/content/docs/advanced-features/atomic-updates.mdx
  • frameworks/motia/docs/content/docs/advanced-features/multi-language.mdx
  • frameworks/motia/docs/content/docs/advanced-features/multi-trigger-steps.mdx
  • frameworks/motia/docs/content/docs/advanced-features/step-helper.mdx
  • frameworks/motia/docs/content/docs/api-reference.mdx
  • frameworks/motia/docs/content/docs/concepts/overview.mdx
  • frameworks/motia/docs/content/docs/concepts/steps.mdx
  • frameworks/motia/docs/content/docs/development-guide/authentication.mdx
  • frameworks/motia/docs/content/docs/development-guide/environment-variables.mdx
  • frameworks/motia/docs/content/docs/development-guide/flows.mdx
  • frameworks/motia/docs/content/docs/development-guide/middleware.mdx
  • frameworks/motia/docs/content/docs/development-guide/observability.mdx
  • frameworks/motia/docs/content/docs/development-guide/project-structure.mdx
  • frameworks/motia/docs/content/docs/development-guide/queue-config.mdx
  • frameworks/motia/docs/content/docs/development-guide/state-management.mdx
  • frameworks/motia/docs/content/docs/development-guide/streams.mdx
  • frameworks/motia/docs/content/docs/examples/human-in-the-loop-workflows.mdx
  • frameworks/motia/docs/content/docs/examples/multi-language-data-processing.mdx
  • frameworks/motia/docs/content/docs/examples/uptime-discord-monitor.mdx
  • frameworks/motia/docs/content/docs/getting-started/handler-migration-guide.mdx
  • frameworks/motia/docs/content/docs/getting-started/migration-guide.mdx
  • frameworks/motia/docs/content/docs/product-showcase/chessarena-ai.mdx
  • frameworks/motia/motia-js/packages/motia/src/index.ts
  • frameworks/motia/motia-js/packages/motia/src/new/build/dev.ts
  • frameworks/motia/motia-js/packages/motia/src/new/build/typegen.ts
  • frameworks/motia/motia-js/packages/motia/src/new/build/utils.ts
  • frameworks/motia/motia-js/packages/motia/src/new/cli.ts
  • frameworks/motia/motia-js/packages/motia/src/new/enqueue.ts
  • frameworks/motia/motia-js/packages/motia/src/new/logger.ts
  • frameworks/motia/motia-js/packages/motia/src/new/stream.ts
  • frameworks/motia/motia-js/packages/motia/src/types.ts
  • frameworks/motia/motia-js/playground/steps/api-steps/array.step.ts
  • frameworks/motia/motia-js/playground/steps/api-steps/sse.step.ts
  • frameworks/motia/motia-js/playground/steps/basic-tutorial/api.step.ts
  • frameworks/motia/motia-js/playground/steps/basic-tutorial/notification.step.ts
  • frameworks/motia/motia-js/playground/steps/basic-tutorial/process-food-order.step.ts
  • frameworks/motia/motia-js/playground/steps/basic-tutorial/state-audit-cron.step.ts
  • frameworks/motia/motia-js/playground/steps/cronExample/handlePeriodicJob.step.ts
  • frameworks/motia/motia-js/playground/steps/cronExample/periodicJobHandled.step.ts
  • frameworks/motia/motia-js/playground/steps/dotenv-example/dotenv-check.step.ts
  • frameworks/motia/motia-js/playground/steps/hello/hello-api.step.ts
  • frameworks/motia/motia-js/playground/steps/hello/process-greeting.step.ts
  • frameworks/motia/motia-js/playground/steps/multi-trigger-example.step.ts
  • frameworks/motia/motia-js/playground/steps/parallelMerge/join-complete.step.ts
  • frameworks/motia/motia-js/playground/steps/parallelMerge/start-parallel-merge.step.ts
  • frameworks/motia/motia-js/playground/steps/parallelMerge/step-process.step.ts
  • frameworks/motia/motia-js/playground/steps/streamParallelMerge/join-complete.step.ts
  • frameworks/motia/motia-js/playground/steps/streamParallelMerge/parallel-merge.stream.ts
  • frameworks/motia/motia-js/playground/steps/streamParallelMerge/start-stream-parallel-merge.step.ts
  • frameworks/motia/motia-js/playground/steps/streamParallelMerge/step-process.step.ts
  • frameworks/motia/motia-js/playground/steps/todo/create-todo.step.ts
  • frameworks/motia/motia-js/playground/steps/todo/delete-todo.step.ts
  • frameworks/motia/motia-js/playground/steps/todo/inbox.stream.ts
  • frameworks/motia/motia-js/playground/steps/todo/todo.stream.ts
  • frameworks/motia/motia-js/playground/steps/todo/update-todo.step.ts
  • frameworks/motia/motia-py/packages/motia/src/motia/__init__.py
  • frameworks/motia/motia-py/packages/motia/src/motia/enqueue.py
  • frameworks/motia/motia-py/packages/motia/src/motia/logger.py
  • frameworks/motia/motia-py/packages/motia/src/motia/runtime.py
  • frameworks/motia/motia-py/packages/motia/src/motia/types.py
  • frameworks/motia/motia-py/playground/steps/api_steps/sse_step.py
  • frameworks/motia/motia-py/playground/steps/conditions/api_with_conditions_step.py
  • frameworks/motia/motia-py/playground/steps/conditions/cron_with_condition_step.py
  • frameworks/motia/motia-py/playground/steps/conditions/mixed_triggers_step.py
  • frameworks/motia/motia-py/playground/steps/conditions/multiple_conditions_step.py
  • frameworks/motia/motia-py/playground/steps/conditions/single_condition_step.py
  • frameworks/motia/motia-py/playground/steps/dotenv_example/dotenv_check_step.py
  • frameworks/motia/motia-py/playground/steps/greetings/delete_step.py
  • frameworks/motia/motia-py/playground/steps/greetings/greet_step.py
  • frameworks/motia/motia-py/playground/steps/greetings/list_step.py
  • frameworks/motia/motia-py/playground/steps/greetings/summary_step.py
  • frameworks/motia/motia-py/playground/steps/multi_trigger/dual_trigger_step.py
  • frameworks/motia/motia-py/playground/steps/multi_trigger/multiple_apis_step.py
  • frameworks/motia/motia-py/playground/steps/multi_trigger/multiple_events_step.py
  • frameworks/motia/motia-py/playground/steps/multi_trigger/single_api_step.py
  • frameworks/motia/motia-py/playground/steps/multi_trigger/single_cron_step.py
  • frameworks/motia/motia-py/playground/steps/multi_trigger/single_event_step.py
  • frameworks/motia/motia-py/playground/steps/multi_trigger/triple_trigger_step.py
  • frameworks/motia/motia-py/playground/steps/otel_example/create_order_step.py
  • frameworks/motia/motia-py/playground/steps/otel_example/notify_order_step.py
  • frameworks/motia/motia-py/playground/steps/otel_example/process_order_step.py
  • frameworks/motia/motia-py/playground/steps/state_example/on_state_change_step.py
  • frameworks/motia/motia-py/playground/steps/state_example/update_state_step.py
  • frameworks/motia/motia-py/playground/steps/stream_example/create_todo_step.py
  • frameworks/motia/motia-py/playground/steps/stream_example/on_todo_event_step.py
  • frameworks/motia/motia-py/playground/steps/todo/create_todo_step.py
  • frameworks/motia/motia-py/playground/steps/todo/delete_todo_step.py
  • frameworks/motia/motia-py/playground/steps/todo/update_todo_step.py
💤 Files with no reviewable changes (3)
  • frameworks/motia/motia-py/packages/motia/src/motia/types.py
  • frameworks/motia/motia-js/packages/motia/src/new/build/typegen.ts
  • frameworks/motia/motia-js/packages/motia/src/new/cli.ts

Comment on lines +259 to +267
Every handler gets a context object with:

| Property | What It Does |
|----------|--------------|
| `logger` | Structured logging |
| `enqueue` | Trigger other Steps |
| `state` | Persistent storage |
| `streams` | Real-time updates |
| `traceId` | Request tracing |
| `trigger` | Trigger metadata |
| `is` | Type guards for trigger types |
| `getData()` | Get typed trigger data |
| `match()` | Pattern matching on trigger |
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:

wc -l frameworks/motia/docs/content/docs/concepts/overview.mdx

Repository: iii-hq/iii

Length of output: 114


🏁 Script executed:

sed -n '250,280p' frameworks/motia/docs/content/docs/concepts/overview.mdx

Repository: iii-hq/iii

Length of output: 1391


🏁 Script executed:

rg "State Management|Real-Time Streams" frameworks/motia/docs/content/docs/concepts/overview.mdx -B 2 -A 10

Repository: iii-hq/iii

Length of output: 746


🏁 Script executed:

sed -n '200,290p' frameworks/motia/docs/content/docs/concepts/overview.mdx

Repository: iii-hq/iii

Length of output: 3210


🏁 Script executed:

sed -n '1,150p' frameworks/motia/docs/content/docs/concepts/overview.mdx

Repository: iii-hq/iii

Length of output: 3983


🏁 Script executed:

sed -n '140,210p' frameworks/motia/docs/content/docs/concepts/overview.mdx

Repository: iii-hq/iii

Length of output: 2044


🏁 Script executed:

rg "import.*state|import.*streams|from.*motia" frameworks/motia/docs/content/docs/concepts/overview.mdx

Repository: iii-hq/iii

Length of output: 230


🏁 Script executed:

sed -n '250,312p' frameworks/motia/docs/content/docs/concepts/overview.mdx

Repository: iii-hq/iii

Length of output: 2232


Clarify how to access state and streams within handlers.

Lines 259-267 show the ctx object properties (traceId, trigger, is, getData(), match()), but earlier "State Management" and "Real-Time Streams" sections (under Core Concepts) show state.set/get and streams.notifications.set without explaining how these are imported or accessed. Readers will be unclear whether state and streams are handler parameters, global imports, or part of a different API entirely.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frameworks/motia/docs/content/docs/concepts/overview.mdx` around lines 259 -
267, Add clarification that state and streams are available on the handler
context: update the overview list of ctx properties to include `state` and
`streams`, and explain they are accessed via the handler context (e.g., use
ctx.state.set/get and ctx.streams.notifications.set) rather than as globals or
separate imports; mention the `state` API for persistent key/value operations
and the `streams` namespace for real-time channel operations so readers know to
call these from the `ctx` object (referencing symbols: ctx, state.set/get,
streams.notifications.set).

Comment on lines 680 to +684
```python
result = await context.state.set("settings", "preferences", {"theme": "dark"})
prefs = await context.state.get("settings", "preferences")
from motia import stateManager

result = await stateManager.set("settings", "preferences", {"theme": "dark"})
prefs = await stateManager.get("settings", "preferences")
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 file at the specified lines
cat -n frameworks/motia/docs/content/docs/concepts/steps.mdx | sed -n '675,690p'

Repository: iii-hq/iii

Length of output: 423


🏁 Script executed:

# Search for other Python examples with "state_manager" or "stateManager" in the docs
rg "state_manager|stateManager" frameworks/motia/docs/ -A 2 -B 2

Repository: iii-hq/iii

Length of output: 50368


🏁 Script executed:

# Look for the Python SDK definition to find the actual export name
find . -type f -name "*.py" | head -20 | xargs grep -l "state_manager\|stateManager" 2>/dev/null

Repository: iii-hq/iii

Length of output: 180


🏁 Script executed:

# Check the Python SDK's __init__.py or main module for exports
fd -e py -p "sdk|motia" frameworks/ | head -20

Repository: iii-hq/iii

Length of output: 1310


🏁 Script executed:

# Check what the Python SDK exports
cat frameworks/motia/motia-py/packages/motia/src/motia/__init__.py

Repository: iii-hq/iii

Length of output: 3358


🏁 Script executed:

cat frameworks/motia/motia-py/packages/motia/src/motia/state.py | head -100

Repository: iii-hq/iii

Length of output: 3676


🏁 Script executed:

tail -20 frameworks/motia/motia-py/packages/motia/src/motia/state.py

Repository: iii-hq/iii

Length of output: 679


Fix Python import to match documentation pattern.

This snippet uses stateManager, but every other Python example throughout the documentation uses state_manager. Python readers will be confused by the inconsistency.

Suggested fix
-from motia import stateManager
+from motia import state_manager
 
-result = await stateManager.set("settings", "preferences", {"theme": "dark"})
-prefs = await stateManager.get("settings", "preferences")
+result = await state_manager.set("settings", "preferences", {"theme": "dark"})
+prefs = await state_manager.get("settings", "preferences")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frameworks/motia/docs/content/docs/concepts/steps.mdx` around lines 680 -
684, Update the Python example to use the documented snake_case name: change the
import and usages from stateManager to state_manager so the snippet reads use of
state_manager.set(...) and state_manager.get(...) to match other examples;
ensure both the from motia import ... line and subsequent calls reference
state_manager instead of stateManager.

Comment on lines +213 to +215
export const handler: Handlers<typeof config> = async (input) => {
const { orderId } = input
const order = await state.get('orders', orderId)
const order = await stateManager.get('orders', orderId)
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 missing state before dereferencing it.

These snippets load the order and immediately read completedSteps or completed_steps. If the state was deleted, expired, or the step is replayed with a stale orderId, the example throws before reaching its idempotency guard. Please add an early null check in each snippet.

Also applies to: 283-285, 355-357, 628-630, 672-675, 707-710

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frameworks/motia/docs/content/docs/examples/human-in-the-loop-workflows.mdx`
around lines 213 - 215, The handler function currently dereferences order
immediately after await stateManager.get('orders', orderId); add an early
null/undefined guard right after that call (e.g., if (!order) return appropriate
404/early-exit) before accessing order.completedSteps or order.completed_steps
to avoid runtime errors; apply the same fix to the other snippets that load
orders (the blocks using stateManager.get('orders', orderId) and then reading
completedSteps/completed_steps) so each checks the fetched order and
returns/throws gracefully when missing.

Comment on lines +34 to +47
```typescript
interface FlowContext<TEnqueueData = never, TInput = unknown> {
traceId: string
trigger: TriggerInfo
is: {
queue: (input: TInput) => input is ExtractQueueInput<TInput>
http: (input: TInput) => input is ExtractApiInput<TInput>
cron: (input: TInput) => input is never
state: (input: TInput) => input is ExtractStateInput<TInput>
stream: (input: TInput) => input is ExtractStreamInput<TInput>
}
getData: () => ExtractDataPayload<TInput>
match: <TResult>(handlers: MatchHandlers<TInput, TEnqueueData, TResult>) => Promise<TResult | undefined>
}
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

FlowContext interface shows two generics but simplified version has one.

The interface shows FlowContext<TEnqueueData = never, TInput = unknown> with two generics, but line 454 in api-reference.mdx shows FlowContext<TInput> with only one. The TEnqueueData generic seems unnecessary if enqueue is now a standalone import. Consider clarifying or aligning these definitions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frameworks/motia/docs/content/docs/getting-started/handler-migration-guide.mdx`
around lines 34 - 47, The FlowContext interface currently declares two generics
(TEnqueueData, TInput) but the simplified API uses only TInput; remove the
unused TEnqueueData generic from the FlowContext declaration (change to
FlowContext<TInput = unknown>) and update the dependent type usages inside the
interface: drop any reliance on TEnqueueData and adjust the match signature to
use the single-generic form of MatchHandlers (or pass never for the enqueue
generic if MatchHandlers still expects it). Update referenced symbols:
FlowContext, MatchHandlers, ExtractQueueInput, ExtractApiInput,
ExtractStateInput, ExtractStreamInput, getData, and match so all types align
with the single generic TInput.

Comment on lines +597 to +601
async def handler(args: MotiaHttpArgs[Any]) -> None:
request = args.request
response = args.response

ctx.logger.info("SSE request received")
logger.info("SSE request received")
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

SSE Python handler signature inconsistent with api-reference.

This example shows async def handler(args: MotiaHttpArgs[Any]) -> None: without FlowContext, but api-reference.mdx (line 1548) shows async def handler(args: MotiaHttpArgs[Any], ctx: FlowContext[Any]) -> None: with FlowContext. These should be consistent across documentation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frameworks/motia/docs/content/docs/getting-started/handler-migration-guide.mdx`
around lines 597 - 601, The handler example uses the signature "async def
handler(args: MotiaHttpArgs[Any]) -> None" which is inconsistent with the
api-reference signature that includes a FlowContext; update the example to
"async def handler(args: MotiaHttpArgs[Any], ctx: FlowContext[Any]) -> None"
(keep the names MotiaHttpArgs and FlowContext intact), and if the example uses
no ctx values add a short unused parameter comment or reference (e.g., use ctx
or _ctx) so the signature and docs are consistent with the api-reference.

Comment on lines 217 to 223
import type { AuthenticateStream } from 'motia'
import { logger } from 'motia'

export const authenticateStream: AuthenticateStream = async (req, context) => {
context.logger.info('Authenticating stream', { req })
export const authenticateStream: AuthenticateStream = async (req) => {
logger.info('Authenticating stream', { req })
return { context: { userId: 'sergio' } }
}
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

Don't log the full stream auth request.

Line 221 logs { req }, which will usually include the token being authenticated in headers or query params. That turns the migration guide into an example of credential leakage.

🔒 Suggested doc fix
 export const authenticateStream: AuthenticateStream = async (req) => {
-  logger.info('Authenticating stream', { req })
+  logger.info('Authenticating stream', { url: req.url })
   return { context: { userId: 'sergio' } }
 }
📝 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
import type { AuthenticateStream } from 'motia'
import { logger } from 'motia'
export const authenticateStream: AuthenticateStream = async (req, context) => {
context.logger.info('Authenticating stream', { req })
export const authenticateStream: AuthenticateStream = async (req) => {
logger.info('Authenticating stream', { req })
return { context: { userId: 'sergio' } }
}
import type { AuthenticateStream } from 'motia'
import { logger } from 'motia'
export const authenticateStream: AuthenticateStream = async (req) => {
logger.info('Authenticating stream', { url: req.url })
return { context: { userId: 'sergio' } }
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frameworks/motia/docs/content/docs/getting-started/migration-guide.mdx`
around lines 217 - 223, The logger call in authenticateStream currently logs the
entire req object (logger.info('Authenticating stream', { req })), which may
include sensitive tokens; change it to avoid printing req directly by either
logging only a non-sensitive identifier (e.g., req.id or req.path) or a redacted
summary (e.g., { method: req.method, path: req.url }) or remove the req param
entirely and keep a generic message; ensure authenticateStream and the
logger.info invocation are updated accordingly so no full request/headers/query
params are emitted.

Comment on lines +4 to +5
export const enqueue: Enqueuer<EnqueueData> = async (queue: EnqueueData): Promise<void> => {
return getInstance().call('enqueue', queue)
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:

#!/bin/bash
set -euo pipefail

printf '\n--- enqueue wrapper ---\n'
sed -n '1,80p' frameworks/motia/motia-js/packages/motia/src/new/enqueue.ts
printf '\n--- iii runtime ---\n'
sed -n '1,220p' frameworks/motia/motia-js/packages/motia/src/new/iii.ts
printf '\n--- related runtime calls ---\n'
rg -n -C3 --type=ts '\bcall\s*\(|enqueue' frameworks/motia/motia-js/packages/motia/src/new

Repository: iii-hq/iii

Length of output: 11969


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find where withSpan is defined
printf '\n--- withSpan definition ---\n'
rg -n 'withSpan' frameworks/motia/motia-js/packages/motia/src --type=ts -B2 -A8 | head -100

# Check if enqueue had prior instrumentation
printf '\n--- Files importing withSpan ---\n'
rg -l 'withSpan' frameworks/motia/motia-js/packages/motia/src --type=ts

# Check enqueue imports
printf '\n--- enqueue.ts full content ---\n'
cat frameworks/motia/motia-js/packages/motia/src/new/enqueue.ts

Repository: iii-hq/iii

Length of output: 10293


Add span instrumentation to enqueue wrapper to match other operations.

The enqueue function lacks the withSpan() wrapper used consistently across stream and state operations. All other getInstance().call() invocations are instrumented with span attributes and error handling; enqueue should follow the same pattern.

Wrap the function with:

import { SpanStatusCode, withSpan } from 'iii-sdk/telemetry'

export const enqueue: Enqueuer<EnqueueData> = async (queue: EnqueueData): Promise<void> => {
  return withSpan('enqueue', {}, async (span) => {
    try {
      return await getInstance().call('enqueue', queue)
    } catch (err) {
      span.setStatus({ code: SpanStatusCode.ERROR, message: String(err) })
      span.recordException(err as Error)
      throw err
    }
  })
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frameworks/motia/motia-js/packages/motia/src/new/enqueue.ts` around lines 4 -
5, The enqueue wrapper currently calls getInstance().call('enqueue', queue)
without telemetry; update the enqueue function to use withSpan('enqueue', {},
async (span) => { ... }) and mirror other wrappers: import SpanStatusCode and
withSpan from 'iii-sdk/telemetry', perform the getInstance().call('enqueue',
queue) inside the async span, catch errors to call span.setStatus({ code:
SpanStatusCode.ERROR, message: String(err) }) and span.recordException(err as
Error) before rethrowing the error so enqueue has the same span instrumentation
and error handling as stream/state operations.

schema: inbox,
}

export const inboxStream = new Stream(config)
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, find the file mentioned in the review comment
find . -name "inbox.stream.ts" -type f

Repository: iii-hq/iii

Length of output: 119


🏁 Script executed:

# Read the inbox.stream.ts file to see the current code
cat -n "./frameworks/motia/motia-js/playground/steps/todo/inbox.stream.ts"

Repository: iii-hq/iii

Length of output: 491


🏁 Script executed:

# Find the Stream class and StreamConfig definition
find . -name "*.ts" -type f | xargs grep -l "class Stream\|interface Stream\|type Stream" | grep -E "(motia|stream)" | head -20

Repository: iii-hq/iii

Length of output: 1436


🏁 Script executed:

# Check the main types-stream.ts file for StreamConfig and Stream definitions
wc -l "./frameworks/motia/motia-js/packages/motia/src/types-stream.ts"

Repository: iii-hq/iii

Length of output: 119


🏁 Script executed:

# Read the types-stream.ts file
cat -n "./frameworks/motia/motia-js/packages/motia/src/types-stream.ts"

Repository: iii-hq/iii

Length of output: 2439


🏁 Script executed:

# Check the Stream constructor in the main stream.ts (motia package)
wc -l "./frameworks/motia/motia-js/packages/motia/src/new/stream.ts"

Repository: iii-hq/iii

Length of output: 118


🏁 Script executed:

# Read the Stream class definition
cat -n "./frameworks/motia/motia-js/packages/motia/src/new/stream.ts"

Repository: iii-hq/iii

Length of output: 6461


🏁 Script executed:

# Check the InferSchema type definition
grep -n "type InferSchema" "./frameworks/motia/motia-js/packages/motia/src/types.ts"

Repository: iii-hq/iii

Length of output: 107


🏁 Script executed:

# Read the InferSchema definition from types.ts
sed -n '307,320p' "./frameworks/motia/motia-js/packages/motia/src/types.ts"

Repository: iii-hq/iii

Length of output: 618


🏁 Script executed:

# Let me verify by checking if there are any tests or examples showing the intended behavior
grep -r "as const satisfies" "./frameworks/motia/motia-js" --include="*.ts" | head -10

Repository: iii-hq/iii

Length of output: 1140


🏁 Script executed:

# Check if StreamConfig uses the same pattern anywhere
grep -r "as const satisfies StreamConfig" "./frameworks/motia/motia-js" --include="*.ts"

Repository: iii-hq/iii

Length of output: 36


🏁 Script executed:

# Look for other stream config examples in the playground
find "./frameworks/motia/motia-js/playground/steps" -name "*.stream.ts" -type f

Repository: iii-hq/iii

Length of output: 274


🏁 Script executed:

# Check the other stream config examples
cat -n "./frameworks/motia/motia-js/playground/steps/todo/todo.stream.ts"

Repository: iii-hq/iii

Length of output: 1330


🏁 Script executed:

# Let me verify the type inference behavior by checking if there are TypeScript tests or documentation
grep -r "InferStreamData" "./frameworks/motia/motia-js/packages/motia/src" --include="*.ts"

Repository: iii-hq/iii

Length of output: 1319


🏁 Script executed:

# Verify the type inference problem by creating a test TypeScript snippet to understand the behavior
cat > /tmp/type_test.ts << 'EOF'
// Simulating the Stream type inference issue
type StreamConfig = {
  name: string
  schema: any
  baseConfig: { storageType: 'default' }
}

type InferSchema<T> = T extends { readonly type: string } ? string : never

type InferStreamData<TConfig extends StreamConfig> = StreamConfig extends TConfig
  ? unknown
  : InferSchema<TConfig['schema']>

class Stream<TConfig extends StreamConfig = StreamConfig> {
  constructor(readonly config: TConfig) {}
  
  getData(): InferStreamData<TConfig> {
    return undefined as any
  }
}

// Current approach - config explicitly typed as StreamConfig
const config1: StreamConfig = {
  name: 'inbox',
  schema: { type: 'object' },
  baseConfig: { storageType: 'default' }
}
const stream1 = new Stream(config1)
// stream1.getData() returns 'unknown' because TConfig is inferred as StreamConfig

// Suggested approach - using as const satisfies
const config2 = {
  name: 'inbox',
  schema: { type: 'object' },
  baseConfig: { storageType: 'default' }
} as const satisfies StreamConfig
const stream2 = new Stream(config2)
// stream2.getData() can infer specific type from config2's narrowed type
EOF
cat /tmp/type_test.ts

Repository: iii-hq/iii

Length of output: 1184


🏁 Script executed:

# Check the parallel-merge.stream.ts to confirm it has the same issue
cat -n "./frameworks/motia/motia-js/playground/steps/streamParallelMerge/parallel-merge.stream.ts"

Repository: iii-hq/iii

Length of output: 638


Remove explicit StreamConfig type annotation to preserve schema type inference.

The config: StreamConfig annotation widens the config type, causing Stream<TConfig> to infer TConfig as the base StreamConfig rather than the concrete config type. This triggers the conditional StreamConfig extends TConfig ? unknown : InferSchema<...> to return unknown, so inboxStream.get(), set(), and other methods lose their inferred payload types.

Use as const satisfies StreamConfig instead to preserve the literal config type while maintaining interface compliance.

Suggested fix
-export const config: StreamConfig = {
+export const config = {
   baseConfig: { storageType: 'default' },
   name: 'inbox',
   schema: inbox,
-}
+} as const satisfies StreamConfig
 
 export const inboxStream = new Stream(config)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frameworks/motia/motia-js/playground/steps/todo/inbox.stream.ts` at line 14,
The config variable is explicitly annotated as StreamConfig which widens the
type and prevents Stream<TConfig> from inferring the concrete schema; remove the
explicit ": StreamConfig" annotation on the config used to construct inboxStream
and instead keep the literal config and append "as const satisfies StreamConfig"
so the object remains a literal for type inference while still conforming to
StreamConfig; update the code around the inboxStream construction (referenced
symbols: inboxStream, Stream, StreamConfig, InferSchema, get, set) so
inboxStream methods regain proper payload types.

Comment on lines +11 to +12
with operation_span("enqueue", **{"motia.step.name": ""}):
await get_instance().call("enqueue", event)
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

Don't stamp enqueue spans with an empty step name.

Hardcoding motia.step.name to "" makes every standalone enqueue span anonymous. That regresses trace attribution for dashboards and filters that relied on step identity. If the current step name is not available here yet, it's safer to omit the attribute than to write a blank value.

Safer fallback
 async def enqueue(event: dict[str, Any]) -> None:
     """Enqueue an event to a topic."""
-    with operation_span("enqueue", **{"motia.step.name": ""}):
+    with operation_span("enqueue"):
         await get_instance().call("enqueue", event)
📝 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
with operation_span("enqueue", **{"motia.step.name": ""}):
await get_instance().call("enqueue", event)
with operation_span("enqueue"):
await get_instance().call("enqueue", event)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frameworks/motia/motia-py/packages/motia/src/motia/enqueue.py` around lines
11 - 12, The enqueue span is being stamped with an empty motia.step.name via
operation_span("enqueue", **{"motia.step.name": ""}); remove the hardcoded empty
attribute and only include motia.step.name when a non-empty step name is
available (i.e., call operation_span("enqueue") or build the kwargs
conditionally before calling operation_span). Update the code around
operation_span and get_instance().call("enqueue", event) so motia.step.name is
omitted instead of set to "" when no current step name exists.

Comment on lines +565 to 566
context = _flow_context(trigger_info)
subscription = StreamSubscription(group_id=group_id, id=client_id)
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

Do not label stream lifecycle/auth contexts as queue.

join, leave, and authenticate still build TriggerInfo(type="queue") before creating FlowContext. With trigger now one of the few remaining context fields, this makes ctx.trigger, ctx.is(...), and ctx.match() incorrect for stream callbacks. Use stream or a dedicated lifecycle trigger type here instead.

Also applies to: 585-586, 606-607

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frameworks/motia/motia-py/packages/motia/src/motia/runtime.py` around lines
565 - 566, Replace the incorrect TriggerInfo type "queue" used when creating
flow contexts for stream lifecycle and auth callbacks with an appropriate
stream-specific trigger (e.g., "stream" or a dedicated lifecycle trigger) so
ctx.trigger, ctx.is(...) and ctx.match() reflect stream semantics; update the
TriggerInfo construction used before calling _flow_context in the FlowContext
creation sites (see usages around StreamSubscription creation and functions
join, leave, authenticate and the similar spots at the other indicated
locations) so they pass TriggerInfo(type="stream") (or your chosen lifecycle
trigger) instead of "queue".

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.

Caution

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

⚠️ Outside diff range comments (1)
frameworks/motia/motia-py/packages/motia/src/motia/runtime.py (1)

304-311: ⚠️ Potential issue | 🔴 Critical

Allow Python step handlers to omit ctx.

The migration removes ctx from Python handlers when it is not needed, but these call sites still always invoke handler(payload, context). Any handler rewritten to async def handler(input): will now fail with TypeError on first execution.

💡 Minimal fix
+def _expects_context(fn: Callable[..., Any]) -> bool:
+    signature = inspect.signature(fn)
+    positional_params = [
+        p
+        for p in signature.parameters.values()
+        if p.kind in (inspect.Parameter.POSITIONAL_ONLY, inspect.Parameter.POSITIONAL_OR_KEYWORD)
+    ]
+    return any(
+        p.kind == inspect.Parameter.VAR_POSITIONAL
+        for p in signature.parameters.values()
+    ) or len(positional_params) >= 2
+
+
+async def _invoke_step_handler(
+    handler: Callable[..., Any],
+    payload: Any,
+    context: FlowContext[Any],
+) -> Any:
+    result = handler(payload, context) if _expects_context(handler) else handler(payload)
+    return await result if inspect.isawaitable(result) else result
+
 ...
-                        result = await composed(motia_request, context, lambda: handler(motia_request, context))
+                        result = await composed(
+                            motia_request,
+                            context,
+                            lambda: _invoke_step_handler(handler, motia_request, context),
+                        )
                     else:
-                        result = await handler(motia_request, context)
+                        result = await _invoke_step_handler(handler, motia_request, context)
 ...
-                    result = await handler(input_data, context)
+                    result = await _invoke_step_handler(handler, input_data, context)

Apply the same replacement to the cron/state/stream trigger call sites.

Also applies to: 375-376, 412-413, 447-448, 479-480

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frameworks/motia/motia-py/packages/motia/src/motia/runtime.py` around lines
304 - 311, The call sites always pass (motia_request, context) into Python step
handlers but handlers may now be declared without the ctx parameter; update the
invocation logic (both direct and via composed middleware created by
_compose_middleware) to detect and call the handler with the correct arity—use
Python introspection (e.g., inspect.signature or callable.__code__.co_argcount)
to decide whether to call handler(motia_request) or handler(motia_request,
context) and apply the same change for composed(...) invocations; make the same
fix for the cron/state/stream trigger call sites and reference the handler,
composed, _compose_middleware, _flow_context, motia_request and context symbols
when locating the code to change.
♻️ Duplicate comments (1)
frameworks/motia/motia-py/packages/motia/src/motia/runtime.py (1)

564-566: ⚠️ Potential issue | 🟠 Major

Use stream trigger metadata for stream lifecycle/auth callbacks.

These FlowContexts are still created with TriggerInfo(type="queue"), so ctx.is_stream()/ctx.is_queue() will report the wrong trigger inside on_join, on_leave, and authenticate handlers.

🔧 Fix
-                    trigger_info = TriggerInfo(type="queue")
+                    trigger_info = TriggerInfo(type="stream")

Apply the same change at all three sites.

Also applies to: 584-586, 605-606

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frameworks/motia/motia-py/packages/motia/src/motia/runtime.py` around lines
564 - 566, The FlowContext is being created with TriggerInfo(type="queue") which
makes ctx.is_stream()/ctx.is_queue() return incorrect values for stream
lifecycle/auth callbacks; update the three occurrences where
TriggerInfo(type="queue") is used (near creation of trigger_info before calling
_flow_context and creating StreamSubscription with group_id and client_id) to
use TriggerInfo(type="stream") instead so on_join, on_leave, and authenticate
handlers see the correct trigger type. Ensure you apply this same change at the
other two sites (the other TriggerInfo creations referenced in the comment).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@frameworks/motia/motia-py/packages/motia/src/motia/runtime.py`:
- Around line 304-311: The call sites always pass (motia_request, context) into
Python step handlers but handlers may now be declared without the ctx parameter;
update the invocation logic (both direct and via composed middleware created by
_compose_middleware) to detect and call the handler with the correct arity—use
Python introspection (e.g., inspect.signature or callable.__code__.co_argcount)
to decide whether to call handler(motia_request) or handler(motia_request,
context) and apply the same change for composed(...) invocations; make the same
fix for the cron/state/stream trigger call sites and reference the handler,
composed, _compose_middleware, _flow_context, motia_request and context symbols
when locating the code to change.

---

Duplicate comments:
In `@frameworks/motia/motia-py/packages/motia/src/motia/runtime.py`:
- Around line 564-566: The FlowContext is being created with
TriggerInfo(type="queue") which makes ctx.is_stream()/ctx.is_queue() return
incorrect values for stream lifecycle/auth callbacks; update the three
occurrences where TriggerInfo(type="queue") is used (near creation of
trigger_info before calling _flow_context and creating StreamSubscription with
group_id and client_id) to use TriggerInfo(type="stream") instead so on_join,
on_leave, and authenticate handlers see the correct trigger type. Ensure you
apply this same change at the other two sites (the other TriggerInfo creations
referenced in the comment).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b02264bd-7881-44b8-804e-3168e9285618

📥 Commits

Reviewing files that changed from the base of the PR and between 27fc3d8 and 043c708.

⛔ Files ignored due to path filters (1)
  • frameworks/motia/motia-py/packages/motia/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • frameworks/motia/motia-js/packages/motia/__tests__/motia-utils.test.ts
  • frameworks/motia/motia-py/packages/motia/src/motia/__init__.py
  • frameworks/motia/motia-py/packages/motia/src/motia/runtime.py

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frameworks/motia/motia-py/packages/motia/tests/test_event_queue_compat.py`:
- Around line 93-105: The test handler only asserts that FlowContext is passed;
update test_api_handler_invoked_with_flow_context to also assert that the first
argument provided to the user handler is a MotiaHttpArgs instance (and/or has
the expected properties) rather than the raw request payload: capture the first
parameter in the local handler (the param named req) and add assertions
validating its type and key fields (e.g., method, path, headers/body) against
the simulated request; apply the same additional assertion change to the other
similar test mentioned (lines ~133-135) so both ensure MotiaHttpArgs is supplied
to the handler.
- Around line 47-52: The test currently accesses sys.modules["motia.enqueue"]
before ensuring the module is imported, which can raise KeyError; modify the
test to import motia.enqueue first (e.g., import motia.enqueue) before
referencing sys.modules["motia.enqueue"] or, simpler, replace the patch.object
call with a dotted-path patch such as patch("motia.enqueue.get_instance",
return_value=mock_bridge) and then import enqueue via from motia.enqueue import
enqueue within the patched context; update references to _enqueue_mod,
patch.object, get_instance and enqueue accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7560b2fd-e47b-4bdd-adaf-227a4be105ee

📥 Commits

Reviewing files that changed from the base of the PR and between 043c708 and 5571cc8.

📒 Files selected for processing (5)
  • frameworks/motia/motia-py/packages/motia/tests/test_event_queue_compat.py
  • frameworks/motia/motia-py/packages/motia/tests/test_queue_integration.py
  • frameworks/motia/motia-py/packages/motia/tests/test_stream_state_group_compat.py
  • frameworks/motia/motia-py/packages/motia/tests/test_tracing_state.py
  • frameworks/motia/motia-py/packages/motia/tests/test_tracing_steps.py

Comment on lines +47 to +52
import sys

_enqueue_mod = sys.modules["motia.enqueue"]

with patch.object(_enqueue_mod, "get_instance", return_value=mock_bridge):
from motia.enqueue import enqueue
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

Import motia.enqueue directly before patching it.

sys.modules["motia.enqueue"] is order-dependent and will raise KeyError if this test runs before anything imports that module. Import the module directly, or patch "motia.enqueue.get_instance" by dotted path instead.

Suggested fix
-    import sys
-
-    _enqueue_mod = sys.modules["motia.enqueue"]
-
-    with patch.object(_enqueue_mod, "get_instance", return_value=mock_bridge):
-        from motia.enqueue import enqueue
+    from motia.enqueue import enqueue
+
+    with patch("motia.enqueue.get_instance", return_value=mock_bridge):
         await enqueue({"topic": "orders.processed", "data": {"order_id": "123"}})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frameworks/motia/motia-py/packages/motia/tests/test_event_queue_compat.py`
around lines 47 - 52, The test currently accesses sys.modules["motia.enqueue"]
before ensuring the module is imported, which can raise KeyError; modify the
test to import motia.enqueue first (e.g., import motia.enqueue) before
referencing sys.modules["motia.enqueue"] or, simpler, replace the patch.object
call with a dotted-path patch such as patch("motia.enqueue.get_instance",
return_value=mock_bridge) and then import enqueue via from motia.enqueue import
enqueue within the patched context; update references to _enqueue_mod,
patch.object, get_instance and enqueue accordingly.

Comment on lines +93 to 105
async def test_api_handler_invoked_with_flow_context(mock_bridge: MagicMock) -> None:
"""Registered API handler should pass MotiaHttpArgs and FlowContext to the user handler."""
captured_ctx = None

config = StepConfig(
name="api-emit-compat",
name="api-handler-ctx",
triggers=[http("POST", "/orders")],
)

async def handler(req: ApiRequest[dict], ctx: FlowContext[dict]) -> ApiResponse[dict]:
_ = req
await ctx.enqueue(
{
"topic": "orders.created",
"data": {"order_id": "abc"},
}
)
async def handler(req: object, ctx: FlowContext[dict]) -> ApiResponse[dict]:
nonlocal captured_ctx
captured_ctx = ctx
return ApiResponse(status=200, body={"ok": True})
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

Assert the first API handler argument too.

This currently proves only that FlowContext is passed. If the runtime regressed from MotiaHttpArgs back to the raw request payload, this test would still stay green.

Suggested fix
-    captured_ctx = None
+    captured_ctx = None
+    captured_req = None
@@
-    async def handler(req: object, ctx: FlowContext[dict]) -> ApiResponse[dict]:
-        nonlocal captured_ctx
+    async def handler(req: object, ctx: FlowContext[dict]) -> ApiResponse[dict]:
+        nonlocal captured_ctx, captured_req
+        captured_req = req
         captured_ctx = ctx
         return ApiResponse(status=200, body={"ok": True})
@@
     assert captured_ctx is not None
     assert captured_ctx.trigger.type == "http"
+    assert captured_req is not None
+    assert captured_req.request.method == "POST"
+    assert captured_req.request.body == {"hello": "world"}

Also applies to: 133-135

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frameworks/motia/motia-py/packages/motia/tests/test_event_queue_compat.py`
around lines 93 - 105, The test handler only asserts that FlowContext is passed;
update test_api_handler_invoked_with_flow_context to also assert that the first
argument provided to the user handler is a MotiaHttpArgs instance (and/or has
the expected properties) rather than the raw request payload: capture the first
parameter in the local handler (the param named req) and add assertions
validating its type and key fields (e.g., method, path, headers/body) against
the simulated request; apply the same additional assertion change to the other
similar test mentioned (lines ~133-135) so both ensure MotiaHttpArgs is supplied
to the handler.

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.

2 participants