Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

name: compose-requests
description: ComposedRequest batch semantics — order-preserving slot-by-index dispatch, optional parallel execution, slot labels, post-slot Compose-overlay fold that decorates results without mutating per-slot Components.
type: guide
kind: design
applies_to: compose, predict
covers: [ComposedRequest, pulse_compose, OverlayLayer]
requires: [capability:compose]

Compose requests

ComposedRequest bundles N independent Request objects into one round-trip (pulse_compose, pulse api compose, pulse.Compose). Each slot is a complete Request — its own cohort, filters, groups and aggregations — and runs exactly as it would alone.

When to compose

  • Several independent questions in one call — a summary, a breakdown and a crosstab of one survey, or one request over several cohorts (a parameter grid: compose-sweeps).
  • Comparing slots — a Compose-host overlay reads two or more finished slots and decorates the comparison (reference vs target, or a multi-slot panel).
  • One model, many slots — fit once, every slot reads it by name (compose-fits).

Not a fit when a later step consumes an earlier step’s OUTPUT rows — that is a chain (process-chain). Every slot opens and decodes its own cohort: composing saves round-trips, not decode work.

{
  "requests": [
    {"label": "a_grades",
     "cohort": {"filename": "students.pulse"},
     "aggregations": [{"type": "<aggregator>", "field": "score"}],
     "filterers":    [{"type": "<value filter>", "field": "grade", "values": ["A"]}]},
    {"label": "by_dept",
     "cohort": {"filename": "students.pulse"},
     "aggregations": [{"field": "score"}],
     "groups":       [{"field": "department"}]}
  ]
}

<…> = any operator the manifest lists for that slot. Slot 2 leans on smart defaults: a field with no type gets the default for its schema type (request-envelope).

Order-preserving slot dispatch

The response is a ComposedResponse object {responses, overlays}: responses[i] answers requests[i], whatever order slots finished in. Each slot carries its own full Response — metadata, data, crosstab, components. Filter state in slot i never affects slot j. A slot’s return shapes responses[i]; a top-level return takes overlays… paths only.

{
  "responses": [
    {"data": [...], "metadata": {"total_rows": 1000, "filtered_rows": 50}},
    {"data": [...], "metadata": {"total_rows": 1000, "filtered_rows": 1000}}
  ],
  "overlays": [ /* one OverlayLayer per Compose overlay spec; key omitted when there are none */ ]
}

Slot labels

Each Request takes an optional label. An empty label is auto-filled with request_<index+1> (1-based) on a clone — the caller’s request is never mutated. Labels are how a Compose overlay names its Reference / Target slots, so set them explicitly whenever an overlay refers to a slot.

Two slots resolving to the same final label (duplicates, or a caller label colliding with an auto label) ⇒ PULSE_COMPOSE_LABEL_COLLISION before any slot runs. Omitting labels keeps wire bytes and CanonicalHash identical to label-free callers.

Parallel execution

pulse.ComposeParallel with ComposeOptions{MaxWorkers, PerRequestTimeout, FailFast} runs slots on a bounded pool (MaxWorkers 0 ⇒ GOMAXPROCS; 1 ⇒ serial). CLI: pulse api compose --parallel N (default 1 = serial, 0 = GOMAXPROCS) and --no-fail-fast. FailFast cancels in-flight siblings on the first error; without it every slot runs and failures fold into one SERVICE_INTERNAL {failed_indices, first_error}. Response order always matches request order.

Located refusals. A slot’s own refusal (an unsupported zone, more than one join) keeps its code and gains details.request — the 0-based slot index — serial and parallel alike.

Post-slot Compose-overlay fold

ComposedRequest.Overlays runs AFTER every slot finalises. The fold reads each slot’s finished crosstab / data / components (read-only) and writes one sibling overlays[i] layer per spec. It never mutates a slot’s payload or components — overlays are additive decorations keyed to host coordinates (overlay-system).

Compose-host overlay kinds resolve Reference.SlotLabel / Target.SlotLabel against the labels above; find them among the manifest overlays[] entries serving compare_groups or benchmark and read their atomic skills. The usual refusal is schema divergence — the reference and target slots group or crosstab on different axes; keep the compared slots’ shapes identical and vary only the filter or cohort. Per-layer diagnostics land on overlays[i].warnings.

Library knobs on OverlaySpec.Options: DictPrefixFast (match slot schemas by byte-equal dictionary PREFIX; only when verified) and MaxPanelTargets (default 16; overflow ⇒ PULSE_OVERLAY_PANEL_TARGETS_OVER_CAP).

Multiplicity

ComposedRequest.multiplicity is every slot’s default; compose family pools all slots and Compose-host layers, the rest stay per slot; serial == parallel, --stream emits no corrections (multiplicity-correction).

Per-slot Components contract

Every slot’s components is emitted independently — the universal floor ({n, n_null} per aggregator, {total_n, n_null} per grouper, {n_in, n_out, n_null_input} per filterer) plus each operator’s own keys. The overlay fold runs AFTER that emission and treats it as read-only input, so a client can render per-slot components immediately and the overlay layer on top. --no-components / DisableComponents suppresses them on every slot (response-components).

Validate before executing

pulse_predict takes one Request: loop it over the slots to catch field typos, missing dictionaries and type mismatches before paying for pulse_compose. Compose overlay specs are checked when the fold runs, after the slots — a mislabelled overlay costs the full batch; check every SlotLabel against your labels.

See

response-components (per-slot components shape) · overlay-system (overlay hosts and resolution) · streaming-and-watching (Request.Hash() per-slot cache keys; --stream emits per-row events and skips the overlay fold) · process-chain (when slots depend on each other).