name: tool-import
kind: tool
description: Import a tabular source file (or pass through .pulse) into a managed handle.
type: reference
applies_to: mcp
When to use
CALL TO BRING AN EXTERNAL DATA FILE INTO PULSE AS A COHORT.
CSV, TSV, NDJSON, JSON array, Parquet, Arrow, Excel, SPSS. Managed handles live under $PULSE_DATA_DIR/imports/ with a TTL sidecar; every inspect/predict/process/sample/facet slides expiry. .pulse passes through (managed=false).
Input
| Field | Notes |
|---|---|
source (req) | path relative to PULSE_DATA_DIR |
format | csv/tsv/ndjson/jsonarray/parquet/arrow/excel/spss/pulse; default extension-detected (.sav/.zsav → spss) |
handle | default = source basename |
ttl | Go duration (24h) or day form (7d); pin = no expiry (the only way). Default 7d |
sheet | Excel sheet name; inert elsewhere |
charset | SPSS-only decode override (windows-1252/cp1252/1252 fold); empty keeps the file’s declaration |
overwrite | default false → PULSE_IMPORT_HANDLE_EXISTS |
groups | parent groups, [{key: [...], members: [...]}] (omit key for a plain tuple group); unknown entry key → PULSE_GROUP_DECLARATION_INVALID |
suggest_groups | also return measured group_candidates; one extra full pass, declares nothing |
schema | explicit schema [{name, type, nullable, description}] (precision/scale for decimal128) — the CLI --schema document; omit to infer |
ignore_extra | with schema: drop columns / late keys it does not name instead of refusing |
float | inferred float width: f64 (default) or f32; else PULSE_IMPORT_FLOAT_WIDTH_INVALID |
source_tz / column_source_tz / dst_policy | zone naive datetimes were recorded in (IANA, UTC, ±HH:MM), per-column {col: zone} override, DST policy error/earlier/later; kept on the sidecar |
Output
descriptor.Envelope over handle, managed_path, format, row_count, expires_at, managed, plus promoted_fields, width_warnings and source_warnings (coded source-parse diagnostics: PULSE_SPSS_*, PULSE_IMPORT_TIMESTAMP_TRUNCATED), groups (per-group verdict, ratio, resident dictionary bytes, byte delta), group_warnings (coded {code, message, details}), group_candidates and zone_warnings — all omitted when empty. Description >1000 bytes → PULSE_IMPORT_DESCRIPTION_TOO_LONG; low-quality → PULSE_FIELD_DESCRIPTION_LOW_QUALITY (error under --strict).
Gotchas
- SPSS is schema-authoritative and import-only here. Its dictionary declares type, nullability and dictionary order ⇒ inference skipped, sampling knobs inert,
promoted_fieldsalways empty. Dictionaries hold SPSS codes, not labels (two codes may share one label) — resolve labels at output via a LabelTable. Writing.savis CLI-only:pulse export spss,pulse convert x.csv out.sav. - Non-fatal
PULSE_SPSS_*ridesource_warnings/ envelopewarnings. Read them — they change what the cohort MEANS. Damage and self-contradiction (byte order, compression, charset) are FATAL coded refusals, never silent wrong numbers. charsetis the recourse when a.savis wrong about itself (stale record7/20; no declaration, failing the strict UTF-8 default). Decode-side only — the declaration is retained for export.- No missing-value mode here. Default is the fidelity split: numeric user-missing → null + a
<var>_missingsibling recording why. The only alternative discards those siblings; ask for it explicitly withpulse import spss --spss-missing=null. - A blank categorical cell is not always a null.
ndjson/jsonarray/arrow/parquetcarry the source’s own null channel, so JSON""(or a set validity bit) imports as an empty-string VALUE and onlynullimports as null.csv/tsv/excelhave one spelling for both and import a blank as NULL — a documented gap, and the collapse always goes toward null. A blank in a numeric / date column is a null on every format. - An explicit schema matches columns BY NAME, never position (
schemahere; CLI--schemaonimport,import predict,convert; libraryImportJob/ConvertJob/ImportSpecSchema): exact spelling, any order; the array order is the cohort’s. Refused before any row is read: a field with no columnPULSE_IMPORT_SCHEMA_FIELD_MISSING; a column no field namesPULSE_IMPORT_SCHEMA_COLUMN_UNMAPPED; a repeated namePULSE_IMPORT_SCHEMA_DUPLICATE; an NDJSON / JSON-array key first seen after the first object (whose keys are the columns)PULSE_IMPORT_SCHEMA_KEY_LATE. Unknown type namePULSE_IMPORT_SCHEMA_TYPE_UNKNOWN; an unknown entry key, an empty array or a schema on a.pulsesourcePULSE_IMPORT_SCHEMA_INVALID.ignore_extra(--ignore-extra) drops unnamed columns and late keys instead. A narrowed output (a projected--stream) is never re-imported with its columns silently shifted. Not kept on the sidecar — the cohort header records the schema. - Derived columns are not an import job. To make a window, feature or attribute column a field,
pulse_materializeit (derived-cohorts) — the supported route, exact and atomic; never re-import a streamed result. - Inferred floats are
f64— decimals stay exact (money sums do not drift).float: "f32"(CLI--float f32) halves the bytes for a whole import, rounding to ~7 digits. - Nullability is inferred from the first N rows. A later null promotes the field (
promoted_fields+PULSE_IMPORT_NULL_PROMOTED); an explicit schema raisesPULSE_IMPORT_ROW_ERRORinstead. - Widths too. A value past the sample-inferred width (a 257th parent name in a
categorical_u8, a larger or non-integer number) promotes the field to the narrowest type that holds it — never a dropped row — with onewidth_warningsentry (PULSE_IMPORT_WIDTH_PROMOTED:field,from,to,source_row). - Naive datetimes import as UTC. A source recorded in local time needs
source_tz(CLI--source-tz; libraryImportJob/ImportSpecSourceTZ). DST gap/overlap is FATALPULSE_IMPORT_DST_AMBIGUOUS/_NONEXISTENTby default;dst_policy: earlier|laterresolves and warnsPULSE_IMPORT_DST_RESOLVED(zone_warnings).Z/offset literals never move; nor do zoned Arrow / UTC-adjusted Parquet timestamps, while naive ones, INT96 and SPSS datetimes are wall clocks. A handle read in one zone is never reused for another: re-import withoverwrite: true. - Zero rows out of a non-empty source is a FATAL
PULSE_IMPORT_ROW_ERROR, not an empty import — details carryrows_read/rows_failed/first_row/first_error, and no cohort is written. Export and convert mirror it withPULSE_EXPORT_ROW_ERROR(convert’s row errors all come from the TARGET writer), and convert writes no--keep-pulseintermediate. PARTIAL failure is unchanged (rows land, the rest riderow_errors, no error), and a source with no data rows stays a legitimate empty cohort. - Parent groups: suggest, then apply. Import with
suggest_groups: true, readgroup_candidates.candidates(suggested: true= the non-overlapping viable set), then re-import with those{key, members}asgroupsandoverwrite: true. Groups write a smaller0x02cohort older binaries cannot open; a member varying within its key is FATALPULSE_GROUP_MEMBER_NOT_CONSTANT. Weak groups WARN at the default floor 2 (PULSE_GROUP_TOO_NARROWdropped,PULSE_DEDUP_LOW_RATIOwritten) — there is no floor/strict knob because neither changes the bytes. Refused on a.pulsepassthrough. No constant elision here — its value is at-rest bytes and a managed import is a TTL’d cache; usepulse import <fmt> --elide-constants. - One dispatch. Detection and reader construction use the
iofactory (io.FormatFromPath→io.NewReader), the same one the CLI and embedders use. No extension match and noformat→PULSE_IMPORT_FORMAT_UNKNOWN; an explicitformatoutside the list →PULSE_IO_FORMAT_UNSUPPORTED. - A
.pulse.zst(zstd transfer artifact) is refused withPULSE_COHORT_COMPRESSED, even renamed.pulse. Decompressing is an operator act:pulse import transfer(CLI) — no MCP slot. - Passthrough skips copy + sidecar (
pulse_dropno-ops). MCP success rebinds session-scoped tools from the new cohort (astool-inspect).
See
tool-drop,tool-imports-list,cohort-schema-design.spss-cohorts— type mapping, derived columns, missing-value split, metadata sidecar, per-code prose.pulse_errors_lookup.