feat: motia quality of life improvements#1282
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughFlowContext 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
✨ Finishing Touches
🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
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 | 🟠 MajorThis cron job will continuously replay the same orders without cleanup logic.
Lines 143-154 list from
pending-ordersand enqueueorder.processedevents on every minute (per the* * * * *schedule on line 67), but there is no code anywhere in the repository that:
- Populates the
pending-ordersstate- Removes or marks entries as processed after successful enqueue
Without a cleanup mechanism, if
pending-orderswere 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 | 🔴 CriticalSupport single-parameter handler signatures at the runtime boundary.
Several Python steps migrated in this PR now define
handler(request)with noctx, but every trigger path here still callshandler(payload, context); the middleware lambda does the same. Those handlers will fail immediately withTypeErroron first execution. Please funnel invocation through one arity-aware helper so bothhandler(request)andhandler(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 | 🔴 CriticalUpdate Python example to use standard
datetimeinstead of non-existentctx.utils.dates.The Python handler example at line 338 references
ctx.utils.dates.now(), butFlowContextonly providestrace_id,trigger,input_value, andis_queue(). Theutilsattribute does not exist. Replacectx.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 | 🟡 MinorPreserve schema inference by using
satisfiesinstead of explicit type annotation.Typing
configasStreamConfigwidens the type to the base interface, causingnew Stream(config)to inferunknownfor the item type instead of the concrete Zod-derived shape. Usesatisfies StreamConfigto 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 | 🟠 MajorAdd a
bodySchemato the TypeScript HTTP examples.With
Handlers<typeof config>, the HTTP request body is inferred frombodySchema. The examples at lines 67 and 446 omit it but still accessrequest.body.items,request.body.total, and destructurerequest.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 StepConfigApply 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 | 🟡 MinorPython 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 addFlowContextas a second parameter. Sincelogger,enqueue, andstateare now standalone imports, most handlers don't needctxat 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 | 🔴 CriticalAwait 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(...)andspan.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 | 🟠 MajorRemove
: StreamConfigannotation to preserve schema-driven typing.When
configis explicitly annotated asStreamConfig, the type parameterTConfigbecomes the base interface type, causingInferStreamData<TConfig>to evaluate its conditional tounknown. This collapses the Zod schema inference, makingget/set/list/updatemethods returnunknowninstead of the typed data.Use
as const satisfies StreamConfiginstead 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 | 🟡 MinorFlowContext generic signature inconsistent with migration guide.
This shows
FlowContext<TInput>with one generic, but the migration guide (handler-migration-guide.mdx line 35) showsFlowContext<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 | 🟠 MajorFix 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, notgetGroup). Additionally, line 1145 callssend(), 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 | 🟡 MinorMigration 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 showsasync 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 thefprefix 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 FlowContextat lines 186 and 197 discard the generic type parameters. IfFlowContextis generic (FlowContext<EnqueueData, TInput>), casting to the non-generic form may lose type safety in downstream code. Consider using the properly typedcontextvariable 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 usinglogger.infofor the summary output.The ASCII art banner uses
console.logdirectly. While this may be intentional for visual formatting in the console, consider whether using the structuredloggerwould 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.updatewithout imports, which may confuse readers since all other examples now usestateManager. While these are conceptual illustrations, updating them to usestateManagerwould 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) andstateManager(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 usescontext. 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 unusedcontextparameter.♻️ 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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (89)
frameworks/motia/HANDLER_MIGRATION_GUIDE.mdframeworks/motia/docs/content/docs/advanced-features/atomic-updates.mdxframeworks/motia/docs/content/docs/advanced-features/multi-language.mdxframeworks/motia/docs/content/docs/advanced-features/multi-trigger-steps.mdxframeworks/motia/docs/content/docs/advanced-features/step-helper.mdxframeworks/motia/docs/content/docs/api-reference.mdxframeworks/motia/docs/content/docs/concepts/overview.mdxframeworks/motia/docs/content/docs/concepts/steps.mdxframeworks/motia/docs/content/docs/development-guide/authentication.mdxframeworks/motia/docs/content/docs/development-guide/environment-variables.mdxframeworks/motia/docs/content/docs/development-guide/flows.mdxframeworks/motia/docs/content/docs/development-guide/middleware.mdxframeworks/motia/docs/content/docs/development-guide/observability.mdxframeworks/motia/docs/content/docs/development-guide/project-structure.mdxframeworks/motia/docs/content/docs/development-guide/queue-config.mdxframeworks/motia/docs/content/docs/development-guide/state-management.mdxframeworks/motia/docs/content/docs/development-guide/streams.mdxframeworks/motia/docs/content/docs/examples/human-in-the-loop-workflows.mdxframeworks/motia/docs/content/docs/examples/multi-language-data-processing.mdxframeworks/motia/docs/content/docs/examples/uptime-discord-monitor.mdxframeworks/motia/docs/content/docs/getting-started/handler-migration-guide.mdxframeworks/motia/docs/content/docs/getting-started/migration-guide.mdxframeworks/motia/docs/content/docs/product-showcase/chessarena-ai.mdxframeworks/motia/motia-js/packages/motia/src/index.tsframeworks/motia/motia-js/packages/motia/src/new/build/dev.tsframeworks/motia/motia-js/packages/motia/src/new/build/typegen.tsframeworks/motia/motia-js/packages/motia/src/new/build/utils.tsframeworks/motia/motia-js/packages/motia/src/new/cli.tsframeworks/motia/motia-js/packages/motia/src/new/enqueue.tsframeworks/motia/motia-js/packages/motia/src/new/logger.tsframeworks/motia/motia-js/packages/motia/src/new/stream.tsframeworks/motia/motia-js/packages/motia/src/types.tsframeworks/motia/motia-js/playground/steps/api-steps/array.step.tsframeworks/motia/motia-js/playground/steps/api-steps/sse.step.tsframeworks/motia/motia-js/playground/steps/basic-tutorial/api.step.tsframeworks/motia/motia-js/playground/steps/basic-tutorial/notification.step.tsframeworks/motia/motia-js/playground/steps/basic-tutorial/process-food-order.step.tsframeworks/motia/motia-js/playground/steps/basic-tutorial/state-audit-cron.step.tsframeworks/motia/motia-js/playground/steps/cronExample/handlePeriodicJob.step.tsframeworks/motia/motia-js/playground/steps/cronExample/periodicJobHandled.step.tsframeworks/motia/motia-js/playground/steps/dotenv-example/dotenv-check.step.tsframeworks/motia/motia-js/playground/steps/hello/hello-api.step.tsframeworks/motia/motia-js/playground/steps/hello/process-greeting.step.tsframeworks/motia/motia-js/playground/steps/multi-trigger-example.step.tsframeworks/motia/motia-js/playground/steps/parallelMerge/join-complete.step.tsframeworks/motia/motia-js/playground/steps/parallelMerge/start-parallel-merge.step.tsframeworks/motia/motia-js/playground/steps/parallelMerge/step-process.step.tsframeworks/motia/motia-js/playground/steps/streamParallelMerge/join-complete.step.tsframeworks/motia/motia-js/playground/steps/streamParallelMerge/parallel-merge.stream.tsframeworks/motia/motia-js/playground/steps/streamParallelMerge/start-stream-parallel-merge.step.tsframeworks/motia/motia-js/playground/steps/streamParallelMerge/step-process.step.tsframeworks/motia/motia-js/playground/steps/todo/create-todo.step.tsframeworks/motia/motia-js/playground/steps/todo/delete-todo.step.tsframeworks/motia/motia-js/playground/steps/todo/inbox.stream.tsframeworks/motia/motia-js/playground/steps/todo/todo.stream.tsframeworks/motia/motia-js/playground/steps/todo/update-todo.step.tsframeworks/motia/motia-py/packages/motia/src/motia/__init__.pyframeworks/motia/motia-py/packages/motia/src/motia/enqueue.pyframeworks/motia/motia-py/packages/motia/src/motia/logger.pyframeworks/motia/motia-py/packages/motia/src/motia/runtime.pyframeworks/motia/motia-py/packages/motia/src/motia/types.pyframeworks/motia/motia-py/playground/steps/api_steps/sse_step.pyframeworks/motia/motia-py/playground/steps/conditions/api_with_conditions_step.pyframeworks/motia/motia-py/playground/steps/conditions/cron_with_condition_step.pyframeworks/motia/motia-py/playground/steps/conditions/mixed_triggers_step.pyframeworks/motia/motia-py/playground/steps/conditions/multiple_conditions_step.pyframeworks/motia/motia-py/playground/steps/conditions/single_condition_step.pyframeworks/motia/motia-py/playground/steps/dotenv_example/dotenv_check_step.pyframeworks/motia/motia-py/playground/steps/greetings/delete_step.pyframeworks/motia/motia-py/playground/steps/greetings/greet_step.pyframeworks/motia/motia-py/playground/steps/greetings/list_step.pyframeworks/motia/motia-py/playground/steps/greetings/summary_step.pyframeworks/motia/motia-py/playground/steps/multi_trigger/dual_trigger_step.pyframeworks/motia/motia-py/playground/steps/multi_trigger/multiple_apis_step.pyframeworks/motia/motia-py/playground/steps/multi_trigger/multiple_events_step.pyframeworks/motia/motia-py/playground/steps/multi_trigger/single_api_step.pyframeworks/motia/motia-py/playground/steps/multi_trigger/single_cron_step.pyframeworks/motia/motia-py/playground/steps/multi_trigger/single_event_step.pyframeworks/motia/motia-py/playground/steps/multi_trigger/triple_trigger_step.pyframeworks/motia/motia-py/playground/steps/otel_example/create_order_step.pyframeworks/motia/motia-py/playground/steps/otel_example/notify_order_step.pyframeworks/motia/motia-py/playground/steps/otel_example/process_order_step.pyframeworks/motia/motia-py/playground/steps/state_example/on_state_change_step.pyframeworks/motia/motia-py/playground/steps/state_example/update_state_step.pyframeworks/motia/motia-py/playground/steps/stream_example/create_todo_step.pyframeworks/motia/motia-py/playground/steps/stream_example/on_todo_event_step.pyframeworks/motia/motia-py/playground/steps/todo/create_todo_step.pyframeworks/motia/motia-py/playground/steps/todo/delete_todo_step.pyframeworks/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
| 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 | |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
wc -l frameworks/motia/docs/content/docs/concepts/overview.mdxRepository: iii-hq/iii
Length of output: 114
🏁 Script executed:
sed -n '250,280p' frameworks/motia/docs/content/docs/concepts/overview.mdxRepository: 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 10Repository: iii-hq/iii
Length of output: 746
🏁 Script executed:
sed -n '200,290p' frameworks/motia/docs/content/docs/concepts/overview.mdxRepository: iii-hq/iii
Length of output: 3210
🏁 Script executed:
sed -n '1,150p' frameworks/motia/docs/content/docs/concepts/overview.mdxRepository: iii-hq/iii
Length of output: 3983
🏁 Script executed:
sed -n '140,210p' frameworks/motia/docs/content/docs/concepts/overview.mdxRepository: iii-hq/iii
Length of output: 2044
🏁 Script executed:
rg "import.*state|import.*streams|from.*motia" frameworks/motia/docs/content/docs/concepts/overview.mdxRepository: iii-hq/iii
Length of output: 230
🏁 Script executed:
sed -n '250,312p' frameworks/motia/docs/content/docs/concepts/overview.mdxRepository: 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).
| ```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") |
There was a problem hiding this comment.
🧩 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 2Repository: 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/nullRepository: 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 -20Repository: 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__.pyRepository: iii-hq/iii
Length of output: 3358
🏁 Script executed:
cat frameworks/motia/motia-py/packages/motia/src/motia/state.py | head -100Repository: iii-hq/iii
Length of output: 3676
🏁 Script executed:
tail -20 frameworks/motia/motia-py/packages/motia/src/motia/state.pyRepository: 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.
| 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) |
There was a problem hiding this comment.
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.
| ```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> | ||
| } |
There was a problem hiding this comment.
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.
| async def handler(args: MotiaHttpArgs[Any]) -> None: | ||
| request = args.request | ||
| response = args.response | ||
|
|
||
| ctx.logger.info("SSE request received") | ||
| logger.info("SSE request received") |
There was a problem hiding this comment.
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.
| 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' } } | ||
| } |
There was a problem hiding this comment.
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.
| 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.
| export const enqueue: Enqueuer<EnqueueData> = async (queue: EnqueueData): Promise<void> => { | ||
| return getInstance().call('enqueue', queue) |
There was a problem hiding this comment.
🧩 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/newRepository: 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.tsRepository: 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) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, find the file mentioned in the review comment
find . -name "inbox.stream.ts" -type fRepository: 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 -20Repository: 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 -10Repository: 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 fRepository: 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.tsRepository: 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.
| with operation_span("enqueue", **{"motia.step.name": ""}): | ||
| await get_instance().call("enqueue", event) |
There was a problem hiding this comment.
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.
| 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.
| context = _flow_context(trigger_info) | ||
| subscription = StreamSubscription(group_id=group_id, id=client_id) |
There was a problem hiding this comment.
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".
…p iii-sdk and motia versions
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
frameworks/motia/motia-py/packages/motia/src/motia/runtime.py (1)
304-311:⚠️ Potential issue | 🔴 CriticalAllow Python step handlers to omit
ctx.The migration removes
ctxfrom Python handlers when it is not needed, but these call sites still always invokehandler(payload, context). Any handler rewritten toasync def handler(input):will now fail withTypeErroron 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 | 🟠 MajorUse
streamtrigger metadata for stream lifecycle/auth callbacks.These FlowContexts are still created with
TriggerInfo(type="queue"), soctx.is_stream()/ctx.is_queue()will report the wrong trigger insideon_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
⛔ Files ignored due to path filters (1)
frameworks/motia/motia-py/packages/motia/uv.lockis excluded by!**/*.lock
📒 Files selected for processing (3)
frameworks/motia/motia-js/packages/motia/__tests__/motia-utils.test.tsframeworks/motia/motia-py/packages/motia/src/motia/__init__.pyframeworks/motia/motia-py/packages/motia/src/motia/runtime.py
…ks and updating test function signatures
There was a problem hiding this comment.
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
📒 Files selected for processing (5)
frameworks/motia/motia-py/packages/motia/tests/test_event_queue_compat.pyframeworks/motia/motia-py/packages/motia/tests/test_queue_integration.pyframeworks/motia/motia-py/packages/motia/tests/test_stream_state_group_compat.pyframeworks/motia/motia-py/packages/motia/tests/test_tracing_state.pyframeworks/motia/motia-py/packages/motia/tests/test_tracing_steps.py
| import sys | ||
|
|
||
| _enqueue_mod = sys.modules["motia.enqueue"] | ||
|
|
||
| with patch.object(_enqueue_mod, "get_instance", return_value=mock_bridge): | ||
| from motia.enqueue import enqueue |
There was a problem hiding this comment.
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.
| 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}) |
There was a problem hiding this comment.
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.
Summary
Remove type generation and decouple
logger,enqueue,stateManager, andstreamsfromFlowContextinto standalone imports across both the JS and Python SDKs.Problem
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 devandmotia buildunreliable in environments where those runtime dependencies weren't available.Type generation was slow -- generating types for all streams and steps added significant latency to the development loop.
Solution
Remove type generation entirely. Since
streamswere the primary consumer of generated types (accessed viactx.streams.name), removing typegen meant removingstreamsfromFlowContext. Oncestreamswas removed, keepinglogger,enqueue, andstateon the context no longer made sense -- they aren't execution context, they're utilities. We moved all four to standalone imports, leavingFlowContextwith only actual execution context:Before:
After:
FlowContextnow only contains:traceId,trigger,is,getData(),match().Changes
SDK (TypeScript)
generate-index.tsand related build utilities)logger,enqueueexports inmotiapackageStreamclass now infers data types fromStreamConfig['schema']via generics (no codegen needed)state,enqueue,logger,streamsfromFlowContextinterfacestateManagerwas already a standalone exportSDK (Python)
loggerandenqueuemodulesstate,enqueue,logger,streamsfromFlowContext_flow_context()in runtime (no longer needsMotiainstance)ctx.emitbug in 2 playground files (should have beenenqueue)Playground (TS + Python)
ctxparameter from handler signatures where no longer needed.stream.tsfiles and imported by stepsDocumentation (22 files)
handler-migration-guide.mdxto cover both HTTP handler and context API migrationmigration-guide.mdxwith context API section and checklistHANDLER_MIGRATION_GUIDE.md(plain Markdown copy)Migration
See the Handler & Context Migration Guide for detailed before/after examples and checklist.
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 createcommand with this body.Summary by CodeRabbit
New Features
API Changes
Documentation