Petri is a workflow generator for lab automation. It provides a visual node-based editor for creating laboratory protocols that execute on automation platforms like Opentrons. The system compiles high-level workflow definitions into specific robotic actions (pipetting, mixing, thermocycling, etc.).
This project uses Bazel as its primary build system with a polyglot stack (TypeScript, Rust, Protocol Buffers).
Build the application:
bazelisk build //petri:runnerRun the server locally:
bazelisk build //petri:runner && \
BAZEL_BINDIR='bazel-bin' sisyphus app run-config \
--binary bazel-bin/petri/runner_/runner \
--config petri/server.star \
--environment devRun all tests:
bazelisk test //petri/client/...Run a specific test file:
bazelisk test //petri/client/nodes:thermocycling_node_testRun TypeScript type checking:
TypeScript checking is integrated into the build. Use the tsconfig at the root which extends gts (Google TypeScript Style).
The codebase is divided into three main layers:
-
Server (
petri/server.ts): Fastify-based HTTP server that serves the web UI and provides Connect RPC services. Handles SSR (server-side rendering) of the React-like UI using the Corgi framework. -
Client (
petri/client/): Browser-based visual editor built with Corgi (a custom React-like framework). Contains:- Node system (
nodes/): Individual node types (mix, thermocycle, serial dilutions, etc.) - Execution engine (
execution/): Graph evaluation, action dispatching, and robot-specific handlers (Opentrons) - UI controllers: Graph editor, table editor, route controller
- Type system (
types/): Type definitions for materials, actions, units, lab equipment
- Node system (
-
Logic (
petri/logic/): Rust/WASM module compiled with wasm-bindgen for browser execution. Currently handles Starlark evaluation.
Graph-Based Execution Model:
- Workflows are represented as directed acyclic graphs (DAGs) of nodes
- Each node has typed inputs/outputs (materials, plates, volumes, etc.)
- The
Graphclass (execution/graph.ts) manages nodes, links, and constraints - Execution happens via the "pipes" system (
execution/pipes.ts) which propagates data through the graph
Node Specifications:
- All nodes are defined in
petri/client/nodes/ - Each node exports a
NODEconstant satisfying theNodeSpecinterface NODE_SPECSarray innodes/nodes.tsis the registry of all available nodes- Nodes define:
inputs/outputs: Pad specifications with typespipe(): Function that wires up data flow in the execution overlay
Action Dispatching:
- Abstract actions (pipette, mix, thermocycle) are defined in
types/actions.ts - Platform-specific handlers translate actions to robot commands
OpentronActionsHandler(execution/opentrons_actions_handler.ts) converts to Opentrons protocol steps- Handlers can be layered: logging, profiling, simulation
Type System:
- Strong typing for lab concepts:
MaterialValue,PlatedMaterialValue,PlateValue,VolumeValue - Types include units with automatic conversion (see
types/units.ts) - Constraints system ensures type safety across node connections
This project uses Corgi, a custom React-like framework from @dev_april_corgi:
- JSX with custom factory:
corgi.createVirtualElement - Component binding via
corgi.bind()with controller pattern - SSR support with hydration via
corgi.hydrateElement() - Controllers manage state and handle events
Bazel workspace structure:
MODULE.bazel: Defines Bazel module dependencies (rules_js, rules_ts, rules_rust, etc.)BUILD.bazelfiles define build targets in each directory- Uses Aspect Build's rules for JS/TS (
@aspect_rules_js,@aspect_rules_ts) - Rust compilation via
@rules_rustwith WASM target support
Dependencies:
- npm packages managed via pnpm (
pnpm-lock.yaml) - Bazel rule
npm_translate_lockconverts pnpm lock to Bazel targets - Rust crates managed via Cargo with
crate_universeextension
TypeScript compilation:
- Custom rule
c_ts_projectfrom@dev_april_corgi(wrapper around ts_project) - Bundling via
esbuild_binaryfor both client and server - Supports source maps for debugging
- User builds workflow graph in browser UI
- Graph is serialized to JSON (see
execution/json.ts) - On execution, graph is converted to "piped overlay" representation
- Nodes'
pipe()functions establish data sources and transformers - Sources are evaluated, triggering cascading computation
- Actions are dispatched to appropriate handler (e.g., Opentrons)
- Handler generates platform-specific protocol or executes directly
Tests use Jest with jsdom environment for DOM testing:
- Test files:
*.test.tsor*.test.tsx - Most tests in
petri/client/nodes/andpetri/client/execution/ - Run individual tests via Bazel targets (e.g.,
//petri/client/nodes:mixing_transformer_test)
This project depends on two external Bazel modules:
@dev_april_corgi: Provides the Corgi UI framework and build rules (c_ts_project,esbuild_binary)@dev_april_sisyphus: Provides deployment tooling (sisyphus_pushable, app runtime)
These are fetched via archive_override in MODULE.bazel.