-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Current State Summary
The bbdev project is a Python-only Motia application at bbdev/api/ with:
- 28 step files across 7 flows:
verilator,compiler,firesim,marshal,sardine,palladium,workload - Every flow follows the same API + Event step pair pattern:
- API step (
*_api_step.py): receives HTTP POST, callscontext.emit(), then pollswait_for_result()in a loop - Event step (
*_event_step.py): subscribes to a topic, runs a shell command viastream_run_logger(), writes result to state viacheck_result()
- API step (
- 1 TypeScript config file:
motia.config.ts(usesdefineConfigwith plugins) - Dependencies:
motia@0.17.14-beta.196+ 8@motiadev/*plugin packages - Utility modules:
event_common.py,stream_run.py,path.py,port.py,search_workload.py - Services layer:
services/directory with tool classes (not Motia steps, just helper code)
Migration Plan
Phase 1: Project Infrastructure
| Task | Details |
|---|---|
Create config.yaml |
Define modules: RestApiModule, StateModule, QueueModule, PubSubModule, ExecModule (Python-only, using uv run motia dev --dir steps) |
Create pyproject.toml |
Add motia[otel], iii-sdk, pydantic>=2.0 as dependencies |
Handle motia.config.ts |
No stream auth is used — delete entirely |
Handle package.json |
Python-only project — delete (along with pnpm-lock.yaml, pnpm-workspace.yaml) |
| Delete old artifacts | Remove .motia/ directory, types.d.ts |
| Install iii engine | From https://iii.dev |
Phase 2: Migrate 14 API Steps (HTTP triggers)
All API steps follow an identical pattern. The transformation is mechanical:
Before:
config = {
"type": "api",
"name": "Verilator Clean",
"path": "/verilator/clean",
"method": "POST",
"emits": ["verilator.clean"],
"flows": ["verilator"],
}
async def handler(req, context):
body = req.get("body") or {}
await context.emit({"topic": "verilator.clean", "data": {...}})
while True:
result = await wait_for_result(context)
if result is not None:
return result
await asyncio.sleep(1)After:
from motia import ApiRequest, ApiResponse, FlowContext, http
config = {
"name": "Verilator Clean",
"description": "clean build directory",
"flows": ["verilator"],
"triggers": [http("POST", "/verilator/clean")],
"enqueues": ["verilator.clean"],
}
async def handler(request: ApiRequest, ctx: FlowContext) -> ApiResponse:
body = request.body or {}
await ctx.enqueue({"topic": "verilator.clean", "data": {...}})
while True:
result = await wait_for_result(ctx)
if result is not None:
return ApiResponse(status=result["status"], body=result["body"])
await asyncio.sleep(1)Key changes:
"type": "api"removed,"method"+"path"move intohttp()trigger"emits"becomes"enqueues"context.emit()becomesctx.enqueue()req.get("body")becomesrequest.body- Return dict becomes
ApiResponse(status=..., body=...)
Files (14 total):
verilator/:01_clean_api,02_verilog_api,03_build_api,04_sim_api,04_cosim_api,05_run_apicompiler/01_build_apifiresim/:01_buildbitstream_api,02_infrasetup_api,03_runworkload_apimarshal/:01_build_api,02_launch_apisardine/01_run_apipalladium/01_verilog_apiworkload/01_buidl_api
Phase 3: Migrate 14 Event Steps (Queue triggers)
Also mechanical:
Before:
config = {
"type": "event",
"name": "make clean",
"subscribes": ["verilator.run", "verilator.clean"],
"emits": ["verilator.verilog"],
"flows": ["verilator"],
}
async def handler(data, context):
...
await context.emit({"topic": "verilator.verilog", "data": {...}})After:
from motia import FlowContext, queue
config = {
"name": "make clean",
"description": "clean build directory",
"flows": ["verilator"],
"triggers": [
queue("verilator.run"),
queue("verilator.clean"),
],
"enqueues": ["verilator.verilog"],
}
async def handler(input_data: dict, ctx: FlowContext) -> None:
...
await ctx.enqueue({"topic": "verilator.verilog", "data": {...}})Key changes:
"type": "event"removed"subscribes": [...]becomestriggers: [queue(...)](multiple topics = multiplequeue()calls)"emits"becomes"enqueues"context.emit()becomesctx.enqueue()- Handler params
(data, context)become(input_data: dict, ctx: FlowContext)
Files (14 total):
verilator/:01_clean_event,02_verilog_event,03_build_event,04_sim_event,04_cosim_eventcompiler/01_build_eventfiresim/:01_buildbitstream_event,02_infrasetup_event,03_runworkload_eventmarshal/:01_build_event,02_launch_eventsardine/01_run_eventpalladium/01_verilog_eventworkload/01_build_event
Phase 4: Update Utility Modules
| File | Changes |
|---|---|
utils/event_common.py |
context.state → ctx.state, context.trace_id → ctx.trace_id, context.logger → ctx.logger. These are shared helpers used by all steps — parameter naming must match new convention. |
utils/stream_run.py |
No changes needed — pure Python subprocess utility, no Motia API usage |
utils/path.py |
No changes needed |
utils/port.py |
No changes needed |
utils/search_workload.py |
No changes needed |
services/* |
No changes needed — no Motia API usage |
types.d.ts |
Delete — auto-generated by old Motia, no longer used |
Phase 5: Cleanup
- Delete
motia.config.ts,package.json,pnpm-lock.yaml,pnpm-workspace.yaml - Delete
types.d.ts - Delete
.motia/directory - Evaluate whether
flake.nixneeds updates for iii engine and new commands
Change Summary
| Change | Count | Type |
|---|---|---|
"type": "api" → http() trigger |
14 files | Mechanical |
"type": "event" → queue() trigger |
14 files | Mechanical |
context.emit() → ctx.enqueue() |
~20 calls | Find-and-replace |
"emits" → "enqueues" |
28 configs | Find-and-replace |
req.get("body") → request.body |
14 API handlers | Mechanical |
Return dict → ApiResponse(...) |
14 API handlers | Mechanical |
context.* → ctx.* in utils |
2 files | Find-and-replace |
New config.yaml |
1 file | Create |
New pyproject.toml |
1 file | Create |
| Delete TS/Node files | 4-5 files | Delete |
Risks and Considerations
-
wait_for_result()polling pattern: API steps poll state in awhile Trueloop withasyncio.sleep(1). This should still work with new Motia, but needs verification on whether the new state API still wraps values in adatafield (old Motiastate.get()returns{"data": actual_value}). If the wrapping is removed, theresult["data"]unpacking logic inwait_for_result()needs adjustment. -
sys.pathhacks: Event steps manually addutils_pathtosys.path. The new Python runtime's directory handling may affect this — needs testing. -
iii engine system dependency: The new architecture requires the iii Rust engine. This needs to be incorporated into the Nix flake.
-
State parameter semantics:
context.state.set(trace_id, key, value)maps toctx.state.set(group, id, value)— parameter positions are identical, semantics map 1:1 (trace_id→ group,key→ id). Low risk.