Getting Started
This guide walks through one complete Culsma run: write a representative flow-cytometry protocol, run it, read the terminal result, save execution artifacts, and replay the run.
Before starting, install Culsma and confirm that culsma --help works. See Install Culsma for the short install path.
What You Will Do
- create a protocol source file
- define containers with canonical content
kind,type, andattrs - model staining, washing, fixation, and acquisition preparation
- use
repeat,sep,with env,markers,stream, and readout schema syntax - run the protocol through the kernel pipeline
- inspect the returned acquisition sample and saved artifacts
- replay the runtime event stream
Create a Protocol File
Create a file named flow_cytometry_protocol.culs:
protocol FlowCytometryProtocol {
// A compact, generic flow-cytometry immunophenotyping protocol.
// The example demonstrates material setup, washing, staining, fixation,
// single-cell stream construction, and a structured readout declaration.
// Prepare the cell input and process reagents.
let cell_suspension = tube(
label = "PreparedCellSuspension",
capacity = 2000uL,
load = [content(kind = bio_cellular, type = cell_population, code = "CELLS1", attrs = { state: suspension }):500uL]
);
let lysis_buffer = tube(
label = "RedCellLysisBuffer",
capacity = 3000uL,
load = [content(kind = formulation, type = buffer, code = "RCLB", attrs = { role: lysis }):2500uL]
);
let staining_buffer = tube(
label = "StainingBuffer",
capacity = 5000uL,
load = [content(kind = formulation, type = buffer, code = "STAINBUF", attrs = { role: wash }):4500uL]
);
let acquisition_buffer = tube(
label = "AcquisitionBuffer",
capacity = 2000uL,
load = [content(kind = formulation, type = buffer, code = "ACQBUF", attrs = { role: resuspension }):1500uL]
);
let viability_dye = tube(
label = "ViabilityDye",
capacity = 200uL,
load = [content(kind = chemical, type = dye, code = "LIVEDEAD", attrs = { role: stain }):100uL]
);
let antibody_cocktail = tube(
label = "FluorAntibodyCocktail",
capacity = 500uL,
load = [content(kind = bio_molecule_or_virus, type = protein, code = "AB_CD45_CD3_CD19", attrs = { role: antibody }):300uL]
);
let fixation_buffer = tube(
label = "FixationBuffer",
capacity = 1000uL,
load = [content(kind = formulation, type = buffer, code = "FIX", attrs = { role: fixation }):800uL]
);
let waste = tube(label = "FlowWaste", capacity = 10000uL);
let working_cells = tube(label = "WorkingCellSuspension", capacity = 5000uL);
let acquisition_sample = tube(label = "AcquisitionSample", capacity = 3000uL);
// Combine cells with lysis buffer and incubate at room temperature.
working_cells << [cell_suspension:200uL, lysis_buffer:1800uL] with constraint(high_precision, low_carryover);
with env(thermal = 25C, duration = 10min) {
hold(working_cells);
}
// Retain the cell-containing fraction and route process liquid to waste.
let lysis_split = sep(sample = working_cells, program = filtration_program(membrane = "cell_retention", drive = "centrifuge"));
waste << [lysis_split[0]];
working_cells << [lysis_split[1]];
// Wash retained cells twice before staining.
repeat wash_cycle in schedule(start = 1, end = 2, step = 1) {
working_cells << [staining_buffer:1000uL];
let wash_split = sep(sample = working_cells, program = filtration_program(membrane = "cell_retention", drive = "centrifuge"));
waste << [wash_split[0]];
working_cells << [wash_split[1]];
}
// Add viability dye and antibody cocktail under dark-protected conditions.
working_cells << [viability_dye:5uL, antibody_cocktail:50uL] with constraint(high_precision, low_carryover, dark_protected);
with constraint(dark_protected) {
with env(thermal = 4C, duration = 30min) {
hold(working_cells);
}
}
// Fix stained cells, then prepare the acquisition sample.
working_cells << [fixation_buffer:300uL];
with constraint(dark_protected) {
with env(thermal = 25C, duration = 10min) {
hold(working_cells);
}
}
acquisition_sample << [working_cells:300uL, acquisition_buffer:700uL];
// Declare the marker panel, single-cell event stream, and readout schema.
let immunophenotyping_panel = markers([CD45, CD3, CD19, LiveDead]);
let cell_events = stream(sample = acquisition_sample, unit = single_cell, panel = immunophenotyping_panel);
let flow_event_schema = data_schema(
label = "FlowCytometryEventReadout",
fields = [event_id, sample_id, fsc_a, ssc_a, cd45_signal, cd3_signal, cd19_signal, viability_signal, population_label]
);
let flow_events = img(sample = cell_events, quantity = customized, schema_ref = flow_event_schema, save_raw = true);
return acquisition_sample;
}This example models a compact, generic flow-cytometry immunophenotyping protocol. It keeps the protocol small enough for a first run while exercising the same source surface used by larger protocols: canonical content taxonomy, metadata attrs, material transfer, separation, environment blocks, repeat scheduling, marker panels, stream construction, and readout declaration.
Run It
From the directory containing flow_cytometry_protocol.culs, run:
culsma flow_cytometry_protocol.culsThe shorthand above is equivalent to:
culsma run flow_cytometry_protocol.culsExpected terminal output:
FlowCytometryProtocol ok
return:
AcquisitionSample (tube)
volume: 1000 uL
mass: 1000 mg
execution: 46/46 steps completed, 0 diagnosticsRead the Result
The result has four useful signals:
| Output | Meaning |
|---|---|
FlowCytometryProtocol ok | The source parsed, validated, planned, and ran successfully. |
AcquisitionSample (tube) | The protocol returned the prepared acquisition sample container. |
volume: 1000 uL | The runtime tracked the final material amount in that container. |
46/46 steps completed, 0 diagnostics | Every generated execution step completed without diagnostics. |
The returned tube is the protocol output. It is separate from the generated run report and debug artifacts.
Read the Source
The protocol is a compact flow-cytometry protocol. These are the main source sections to inspect.
Content Initialization
load = [content(kind = bio_cellular, type = cell_population, code = "CELLS1", attrs = { state: suspension }):500uL]kind and type identify the broad material class and refinement. code is a stable component identifier. attrs records additional metadata such as preparation state or protocol role.
Material Preparation
working_cells << [cell_suspension:200uL, lysis_buffer:1800uL] with constraint(high_precision, low_carryover);
with env(thermal = 25C, duration = 10min) {
hold(working_cells);
}The << operator transfers material into a destination container. The with constraint(...) clause records execution requirements, and with env(...) applies temperature and duration context to the enclosed hold.
Separation and Washing
let lysis_split = sep(sample = working_cells, program = filtration_program(membrane = "cell_retention", drive = "centrifuge"));
waste << [lysis_split[0]];
working_cells << [lysis_split[1]];sep(...) produces indexed fraction outputs. In this example, slot 0 is routed to waste and slot 1 is retained as the working cell material. The repeat block applies the same wash pattern twice.
Staining and Fixation
working_cells << [viability_dye:5uL, antibody_cocktail:50uL] with constraint(high_precision, low_carryover, dark_protected);The viability dye is represented as chemical/dye with attrs = { role: stain }. The antibody cocktail is represented as bio_molecule_or_virus/protein with attrs = { role: antibody }. These roles are metadata for protocol intent; they do not imply biological prediction.
Stream and Readout
let immunophenotyping_panel = markers([CD45, CD3, CD19, LiveDead]);
let cell_events = stream(sample = acquisition_sample, unit = single_cell, panel = immunophenotyping_panel);The marker panel and stream declaration model the acquisition surface. The final img(...) call declares a structured readout schema for event-level signals.
Save Execution Artifacts
Run again with an artifacts directory:
culsma run flow_cytometry_protocol.culs --artifacts-dir tmp/flow-cytometry-runThe directory contains staged pipeline and runtime artifacts:
| File | Meaning |
|---|---|
ast.json | Parsed source program. |
ir.json | Canonical IR after compile. |
validate.json | Semantic validation diagnostics. |
typecheck.json | Type and unit diagnostics. |
plan.json | Executable plan. |
run.json | Runtime state, events, diagnostics, and user result. |
output.json | Machine-readable protocol return plus report. |
result.json | Derived lab_report_v1 report summary. |
Replay It
Replay reconstructs runtime state from the saved event stream:
culsma replay --run-json tmp/flow-cytometry-run/run.json --out tmp/flow-cytometry-run/replayed_state.jsonFor this example, replay reconstructs 46 executed steps from 94 runtime events.
Next Steps
- Read Containers and Content for content taxonomy and
attrs. - Read Separation and Fractionation for
sep(...)behavior. - Read Readout and Data Schema for the final stream/readout declarations.
