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

pulse api process

Audience: CLI users running a single processing request against a cohort.

pulse api process executes one types.Request against a .pulse file and prints the result. It’s the most-used leaf in the binary.

LLM agents using MCP: the equivalent surface is the pulse_process MCP tool — see skills/session-bootstrap.md and skills/aggregation-design.md for request authoring guidance.

Synopsis

pulse api process --request FILE [--json] [--stream] [--no-defaults]
                                 [--strict] [--echo-request]

Flags

FlagAliasTypeDefaultPurpose
--request-rstring(required)Path to the request JSON file
--jsonboolfalseEmit the result wrapped in the JSON envelope
--streamboolfalseStream rows as NDJSON (one per line) instead of buffering
--no-defaultsboolfalseDisable smart operator-type inference; require explicit Type on every aggregation and grouper
--strictboolfalsePromote request-validation warnings (e.g. numeric aggregation on a categorical field) into hard errors
--echo-requestboolfalseInclude the normalized (post-defaults) request on envelope.request. Ignored under --stream because NDJSON has no envelope

--stream and --json are mutually exclusive in spirit — --stream emits one JSON object per line; --json emits the full envelope.

--strict is the post-execute companion to pulse api predict --strict: predict refuses to declare the request valid in the face of any warning; process refuses to run it.

Request file shape

The request file is a types.Request serialised to JSON. Minimal example:

{
  "cohort": {"filename": "sales.pulse"},
  "aggregations": [
    {"type": "AGG_SUM", "field": "revenue", "label": "total_revenue"}
  ]
}

The full request grammar is one JSON object whose top-level keys mirror types.Request: cohort, filterers, features, attributes, groups, aggregations, windows, sort, tests, post_tests, outputs. A canonical filter-group-aggregate example:

{
  "cohort": {"filename": "data.pulse"},
  "filterers": [
    {"type": "FILTER_INCLUDE", "field": "status", "values": ["active"]}
  ],
  "groups": [
    {"type": "GROUP_CATEGORY", "field": "region"}
  ],
  "aggregations": [
    {"type": "AGG_COUNT",   "field": "id",    "label": "n"},
    {"type": "AGG_AVERAGE", "field": "score", "label": "mean_score"}
  ],
  "outputs": [{"format": "json"}]
}

The full grammar — windows, sort, tests, post-tests — is documented in types.Request; the LLM-facing companion is skills/aggregation-design.md.

Pipeline order

Pulse executes a request in a fixed sequence:

Load -> Features -> Filter -> Attributes -> Group -> Aggregate -> Windows -> Sort -> Output

Features run before filterers, so derived columns are addressable as filter, group, attribute, and window inputs. Windows run after aggregation, on the post-aggregate row set. Request.Sort runs last.

Smart defaults

When an aggregations[] or groups[] slot names a field but omits type, the engine infers the operator from the named field’s schema type at request time. --no-defaults (or pulse.Options{DisableDefaults: true}) turns this off and requires every slot to be source-of-truth. The full defaults table is documented on pulse api predict.

Output

Text mode (default)

Pretty-printed JSON of the Response struct: a data array of result rows plus a metadata block with total_rows, filtered_rows, and cohort_file.

--json

The standard envelope:

{
  "format_version": "1.1",
  "data": {
    "data": [ /* result rows */ ],
    "metadata": { "total_rows": 1000, "filtered_rows": 800, "cohort_file": "sales.pulse" },
    "components": {
      "aggregations": [{"label": "n", "n": 800, "n_null": 0}],
      "filterers":    [{"n_in": 1000, "n_out": 800, "n_null_input": 0}],
      "run":          {"total_records": 1000, "filtered_records": 800, "null_records": 0}
    }
  },
  "errors": [],
  "warnings": []
}

data.components is the additive Response.Components slot documented in CLAUDE.md → Output Format Contract and skills/response-components.md. The slot is omitempty: a run that emits no components-shaped state (a streaming --stream chunk between the first and last on a non-mergeable aggregator, for example) marshals without the components key at all — byte-identical to the pre-0.20 wire shape. format_version stays at "1.0" because the slot is additive.

--stream

NDJSON of result rows, one per line. No envelope, no metadata footer. Pair with pulse api predict ahead of time to confirm Streamable=true; predict-buffered shapes still emit via this path, but they materialise inside the engine first.

Exit codes

CodeMeaning
0Success
1Any error — wrapped in the envelope’s errors array under --json, or printed to stderr otherwise

Examples

Quick aggregation

cat > req.json <<'EOF'
{
  "cohort": {"filename": "sales.pulse"},
  "aggregations": [{"type": "AGG_COUNT", "field": "id", "label": "n"}]
}
EOF

pulse api process --request req.json

Filter, group, and aggregate

cat > req.json <<'EOF'
{
  "cohort": {"filename": "sales.pulse"},
  "filterers": [{"type": "FILTER_RANGE", "field": "revenue", "values": ["100", "10000"]}],
  "groups":    [{"type": "GROUP_CATEGORY", "field": "region"}],
  "aggregations": [
    {"type": "AGG_COUNT",   "field": "id",      "label": "orders"},
    {"type": "AGG_AVERAGE", "field": "revenue", "label": "avg_rev"}
  ]
}
EOF

pulse api process --request req.json --json

Stream rows into a downstream pipeline

pulse api process --request req.json --stream | \
    jq -c 'select(.avg_rev > 500)'