# Cyto Desk > Take one flow cytometry acquisition as it comes off the instrument - the FCS keyword block, the > antibody panel and the spillover matrix - and decide whether that file can carry the analysis > about to be done to it. Three lanes over one paste: audit the acquisition, review the panel and > the compensation arithmetic, then write the minimum-information reporting block. Live at https://cyto-desk.skillsafe.ai/ ยท API docs at https://cyto-desk.skillsafe.ai/api.html Derived from and credits the **@k-dense-ai/flowio** skill (parse FCS 2.0-3.1 files: read metadata and channel definitions, extract events, convert to tabular form, for flow cytometry validation). Cyto Desk is a derived work and not a republication of that skill. ## What it is for A flow cytometry file is usually analysed on the assumption that it is analysable. Most of the ways that assumption fails are visible in the file's own keyword block and in the compensation matrix before anybody draws a gate - and almost none of them make the file refuse to load. `$PAR` can disagree with the number of parameters actually present, which silently shifts every value in every event. `$PnE` can declare log amplification with a zero second field, which leaves the log transform without a defined origin so two analysis packages plot the same file differently. A spillover matrix can be singular, which means two detectors have been described as seeing indistinguishable mixtures and no arithmetic separates them. And 8,000 recorded events cannot measure a 0.1% population however carefully it is gated. Cyto Desk reads those things off the file, does the arithmetic exactly, and then asks a model to judge what they mean for the specific populations the user says they need to measure. ## The work object One paste, containing any combination of three things in any order: 1. **An FCS keyword block** - either one keyword per line (`$PAR 14`, `$PAR=14`, `$PAR: 14`) or the raw delimited TEXT segment. Dropping the `.fcs` file itself is the normal path: the browser reads it as bytes, decodes the 58-byte HEADER for its version string and six segment offsets, splits the TEXT segment on its own first byte with doubled-delimiter escapes handled, and never touches the DATA segment. A 400 MB acquisition costs a few kilobytes of work and the file never leaves the browser. 2. **A panel table** - CSV or TSV with a header row. Column names are matched loosely: marker/antigen/specificity/target, fluorochrome/dye/conjugate/label, detector/channel/parameter/filter, laser/excitation, expression/density/level. 3. **A spillover matrix** - either the FCS `$SPILLOVER` value form (n, then n channel names, then n*n coefficients, comma separated) or a labelled CSV grid. ## The three lanes Every lane takes the same paste. The `task` field routes. | task | question | body keys | | --- | --- | --- | | `acq` | Can this file be gated at all, and what will the gating be blind to? | `findings[]`, `channels[]` | | `panel` | Does this panel measure what it claims, and where will compensation eat the answer? | `pairs[]`, `markers[]`, `controls[]` | | `report` | What can the methods section honestly say? | `sections[]`, `checklist[]` | Lane A's result panel carries a button that pre-fills the next lane with lane A's conclusions and switches to it without a reload, so the second lane does not re-derive what the first already settled. ## The free in-browser half Runs cost credits. Everything below costs nothing, needs no account, and happens before any model is asked anything: - **FCS reader.** HEADER version and segment offsets; TEXT segment split on its own delimiter with doubled-delimiter escapes; keyword map built without reading DATA. - **Channel table** from `$PnN`, `$PnS`, `$PnB`, `$PnE`, `$PnR`, `$PnG`, `$PnV`, `$PnL`, `$PnF`, `$PnT`, with each parameter classified as scatter, fluorescence, viability, time or index. - **FCS 3.1 conformance.** The twelve required primary-TEXT keywords; the four required per-parameter keywords; `$PAR` against the highest parameter index actually present; `$MODE`, `$DATATYPE`, `$BYTEORD`, `$NEXTDATA`. - **Scale checks.** A log `$PnE` with a zero or absent second field (illegal under FCS 3.1, because that value is what channel 0 maps to); log amplification declared while `$DATATYPE` is `F`; a `$PnR` that cannot be represented in the declared `$PnB` bit width. - **Event statistics with their basis stated.** Duration and event rate from `$BTIM`, `$ETIM` and `$TOT`; abort and lost-event fractions from `$ABRT` and `$LOST`; a Poisson relative standard error for the rarest population the event count can support; implied concentration from `$VOL`. - **Spillover arithmetic.** The matrix is inverted by Gauss-Jordan elimination with partial pivoting and the infinity-norm condition number is the product of the infinity norms of the matrix and its inverse. Compensation multiplies every event by that inverse, so the condition number is exactly how much the inversion amplifies coefficient error: at 40, the one percent error a good single-stain control leaves can arrive as a forty percent error in a compensated value. - **Per-detector spreading magnitude** - the root sum of squares of each detector's spillover column, which ranks detectors by how much resolution the subtraction will cost them. - **Matrix cross-checks.** A diagonal that is not 1 (the matrix has been scaled or transposed); a negative coefficient (over-subtraction from a single-stain control dimmer than its own reference); a matrix channel absent from the file; a fluorescence detector the matrix never covers. - **Panel resolution.** Every marker resolved to a real `$PnN` detector; two markers on one detector reported as the unrecoverable error it is; the dim-antigen-on-dim-fluorochrome and dim-antigen-on-heavy-spread pairings named with the stain-index reasoning; tandem-conjugate load counted, because tandem spillover is lot-dependent and a stored matrix is not valid for a tandem-heavy panel. - **Exports.** Prescan as Markdown; channels, spillover, panel and findings as CSV. ## Grounding, in both directions The prescan's facts go into the run as a `prescan` object whose `flags` array carries an id per deterministic finding. The prompt requires one `coverage_check` entry per id, each `confirmed`, `set-aside` with a reason, or `contradicted` with evidence. Afterwards the page checks the answer back against those same facts and puts every disagreement on screen next to the answer: - a detector or marker name that appears nowhere in the paste; - a spillover percentage that is not the coefficient in the supplied matrix (tolerance is the larger of 0.06 points and 5% relative); - a reporting checklist item marked `stated` whose keyword is absent from the file; - a prescan flag at critical or high severity that `coverage_check` never mentions; - a `coverage_check` id the prescan never raised; - the `$OP` operator value echoed back, which the prompt forbids in every lane. ## The output contract One JSON object. Common envelope in every lane: `task`, `title`, `posture`, `confidence`, `verdict`, `exec_summary`, `assumptions[]`, `open_questions[]`, `coverage_check[]`, `artifacts[]`, `next_steps[]`, `summary`. Postures are per lane: `analysable` / `analysable-with-caveats` / `not-analysable`; `panel-sound` / `panel-workable` / `panel-redesign`; `report-ready` / `report-gaps` / `report-blocked`. Artifacts per lane: `acquisition-audit.md` + `channels.csv`; `panel-review.md` + `spillover-actions.csv`; `methods.md` + `miflowcyt-checklist.csv`. ## What it does not do - It never reads the DATA segment, so it cannot see a clog, a gate, a cluster or a real MFI. Nothing it says is derived from event-level data. - It never emits a command, a script or a CLI invocation. The prompt forbids it. Where a step needs computation, the measurement is described rather than automated. - It does not know whether the antibody titre was right or the cells were alive. - The `$OP` value is never echoed. The operator is referred to as "the operator". ## Model and metering Model `gpt-terra` (currently `gpt-5.6-terra`), `markup_bps` 1000, `price_credits` 0. Runs are metered and need a signed-in account; the estimate, the prescan, the exports and both bundled examples are free. History is a declared `reviews` collection scoped to the calling user, with `title`, `verdict` and `keywords` embedded for semantic search over past reviews.