Tool reference

Every description below is the text your client actually receives on tools/list, verbatim. It is rendered from the running registry rather than transcribed, so this page cannot describe a tool differently from the way the server describes it, and cannot list one the server does not register.

For which tools exist but are not served, and which are only proposed, see Status: served vs planned.

The engine ranks ONE binary outcome per entity. A reading tells it what shape your data is in and reduces it to one leak-safe row per entity as of a cutoff — you supply the outcome, or a reading derives it from your events; the engine never guesses which token means the outcome occurred. Free text in any column is engineered into signals on every run.

reading.kind what you hand it, and what comes back
as-is@1 a row per entity already — no reduction, and no cost to saying so
sequential@1 an event log → per-entity counts, tenure, top event types and transitions
trace@1 agent/job runs → each run gains its prior failure rate over STRICTLY EARLIER runs
windowed@1 a metric history → trailing-window aggregates, row count preserved
panel@1 entity × period → the outcome DERIVED by the engine, from a future window or a trend
stream@1 a sensor stream + fault intervals → snapshots with trailing aggregates
sequential@2 an event log → per-entity counts, tenure, top event types and transitions
windowed@2 a metric history → trailing-window aggregates, row count preserved
stream@2 a sensor stream + fault intervals → snapshots with trailing aggregates

Every kind states its own required roles (—, identifier + time_axis, identifier + time_axis + outcome, identifier + time_axis + numeric_metric, identifier + time_axis, identifier + time_axis + signals, identifier + time_axis, identifier + time_axis + numeric_metric, identifier + time_axis + signals respectively) and its param bounds; hs_describe_capabilities publishes all of them, so a caller constructs a valid reading without guessing.

hs_describe_capabilities

Describe capabilities — Free; no engine run.

Free; no engine run. Returns the input contract, supported problem shapes, trust guarantees (determinism, provenance, honest-empty, leak guard), limits (inline caps, k max), the reading kinds for event tables, the free sample dataset ids, and worked examples from several domains. The engine ranks ONE binary outcome per entity. A reading tells it what shape your data is in and reduces it to one leak-safe row per entity as of a cutoff — you supply the outcome, or a reading derives it from your events; the engine never guesses which token means the outcome occurred. Free text in any column is engineered into signals on every run.

as-is@1 — a row per entity already — no reduction, and no cost to saying so sequential@1 — an event log → per-entity counts, tenure, top event types and transitions trace@1 — agent/job runs → each run gains its prior failure rate over STRICTLY EARLIER runs windowed@1 — a metric history → trailing-window aggregates, row count preserved panel@1 — entity × period → the outcome DERIVED by the engine, from a future window or a trend stream@1 — a sensor stream + fault intervals → snapshots with trailing aggregates sequential@2 — an event log → per-entity counts, tenure, top event types and transitions windowed@2 — a metric history → trailing-window aggregates, row count preserved stream@2 — a sensor stream + fault intervals → snapshots with trailing aggregates USE WHEN you are unsure whether the user's problem is a yes/no ranking problem, how to shape the input, or which sample dataset to demonstrate with. NOT FOR running anything — it reads nothing but the contract, and reports no finding. RETURNS: contract, trust, limits, readings.kinds[], samples[], examples[], provenance. Every field describes what the engine CAN do, never what any run found. EXAMPLE: call with no arguments; then hs_rank_topk({ data: { dataset_id: "sample:saas_churn" }, entity_column: "customer_id", outcome_column: "churned", subject_kind: "org" }).

hs_provide_dataset

Provide dataset — Free; no engine run.

Free; no engine run. Register a dataset, then rank it by dataset_id. Two ways in: call with no arguments to receive { dataset_id, upload_url } and PUT the CSV yourself (curl -X PUT --data-binary @file.csv ""; the bytes stream straight to storage, so there is no row cap — ~1M rows is routine); or pass fetch_url and the server downloads a public https CSV. USE WHEN the table is bigger than a small paste, or you will rank the same data more than once. NOT FOR small tables (inline them as data.rows or data.csv in hs_rank_topk); private, internal, localhost or non-https URLs, which are refused. Note hs_rank_topk also accepts data.fetch_url directly, collapsing the two calls when you only need one run. RETURNS: dataset_id, plus upload_url in upload mode. Runs on a dataset_id are ASYNC: hs_rank_topk returns { status: "pending", task_id } — poll hs_poll_task. LIFETIME: a dataset_id is SINGLE-USE and short-lived. Its raw data is deleted when hs_poll_task first returns the run's terminal result — a ranking, an honest-empty, or a failure — and after that poll another hs_rank_topk on the same dataset_id returns unknown_dataset. That is not an error and not an expiry. Before that poll the id still resolves, so a repeat hs_rank_topk without the same idempotency_key starts a second, separately billed run whose data the first run's poll may delete before it is read — pass idempotency_key on retries instead. Always poll a run to completion: that poll is what deletes the data; a run nobody polls leaves its data in storage. A registration that is never run stops resolving after 24 hours. To rank the same table again, register it again; you do not need to, and should not, re-register in order to ask more questions about a ranking you already have — hs_explain_drivers, hs_explain_levers, hs_model_quality and hs_context_brief all reuse the completed analysis by ranking_ref for 24 hours, free, and only for the workspace whose credential ran it. EXAMPLE: hs_provide_dataset({}) then PUT the file, then hs_rank_topk({ data: { dataset_id }, entity_column: "customer_id", outcome_column: "churned", subject_kind: "org" }). MISTAKES: forgetting to PUT after receiving upload_url, which leaves the run with no data; tight-polling hs_poll_task instead of respecting retry_after_ms; holding a dataset_id and expecting to re-rank it later — it is consumed when hs_poll_task returns its first terminal result; re-submitting it before that poll without the same idempotency_key, which starts and bills a second run.

hs_append_rows

Append rows — Free; no engine run.

Free; no engine run. Send a large table one chunk at a time, over this connection. THE DOOR THAT ALWAYS WORKS: the other two large-data paths need the internet from YOUR side — a presigned upload_url goes to s3.amazonaws.com, and a fetch_url means hosting a public URL of your own — so if you are sandboxed, behind an egress proxy, or simply have no HTTP tool, this is the one that reaches us, because you are already talking to us. It is slower than a single PUT and that is the right trade: minutes instead of impossible. USE WHEN a table is too large to send inline (over the ~4MB inline budget, or your client's per-message budget) AND you cannot reach s3.amazonaws.com or publish a URL. If you CAN, hs_provide_dataset's upload_url is faster — prefer it. HOW: call with this chunk's data (rows as JSON objects, or csv as text) and chunk_index 0 and NO dataset_id; you get a dataset_id back. If you already hold CSV, send it as csv — INCLUDE THE HEADER ROW ON EVERY CHUNK (it is what checks the chunk against chunk 0, and it is stripped before storage) — it is about a third the size of the same rows as JSON. Send the rest as chunk_index 1, 2, 3 … passing that dataset_id each time, ~1500 rows a chunk. Then call hs_rank_topk with data.dataset_id. Ranking seals whatever has arrived, so send every chunk BEFORE you rank. TWO RULES, both refused rather than repaired. Every chunk must carry the same keys in the same ORDER as chunk 0 — a mismatch is an error, because silently reordering would change what each column means. And chunks are assembled in chunk_index order, so send them in the order your table is in: row order affects which rows the engine holds back to test itself. RESUMING: re-sending a chunk_index that already landed is a no-op success. A dropped connection costs you that one chunk, never the whole upload — retry the same index with the same rows. RETURNS: dataset_id, chunks_received, rows_received, the locked columns, and the chunk_index to send next. NOT FOR small tables (inline them as data.rows in hs_rank_topk); appending to a dataset_id that was minted for a presigned PUT or a fetch_url, which already holds its data and is refused. EXAMPLE: hs_append_rows({ rows: firstFifteenHundred, chunk_index: 0 }) → { dataset_id } ; hs_append_rows({ dataset_id, rows: next, chunk_index: 1 }) ; … ; hs_rank_topk({ data: { dataset_id }, entity_column: "customer_id", outcome_column: "churned", subject_kind: "org" }). MISTAKES: ranking before every chunk has landed, which ranks a short table; changing key order between chunks; guessing a dataset_id instead of using the one chunk 0 returned.

hs_rank_topk

Rank top-k by outcome likelihood — Costs ONE RUN from quota — the only tool that does.

Costs ONE RUN from quota — the only tool that does. Rank the rows of a table by likelihood of a binary outcome and return the top-k entities with calibrated scores, an engine-authored band per entity, a model_ref for decision-time scoring, and a signed Verdict. USE WHEN the user has tabular data (or an event log, via reading) with one column holding a historical yes/no result and asks who is most likely to convert, churn, renew, default, fail, respond, no-show, upsell, win or drop out — or which customers, accounts, leads, users, subscribers, machines, applications or tickets to prioritise, triage, target or work first. Lead scoring, propensity, risk scoring, target lists. Domain-general — business, operations, health-adjacent, education, sports, research, personal — provided the table has one binary outcome column that was historically known before the outcome occurred. NOT FOR predicting a continuous number; data with no historical outcome column; pure time-series forecasting; causal guarantees. COST NOTES: the run is refunded on honest-empty or error, so you are billed only for a ranking you received. The billing gate is the REALISED lift bar (>= 1.5, measured on every analysed row). A run can clear that and still return model_ref: null, because the engine caches a scorecard only when top_decile_lift clears 1.5 as well — you get the ranking you paid for, but the decision tools are unavailable for it. Sample dataset_ids are free and never touch quota. PARAMS: exactly one of data.rows | data.csv (inline; synchronous up to 2,000 rows, async above — returns { status: "pending", task_id }; your client's payload budget binds before the server's ~4MB / 64 columns) | data.dataset_id | data.fetch_url (async: returns { status: "pending", task_id }; poll hs_poll_task). outcome_column must be knowable BEFORE the outcome occurs — the leak guard quarantines post-outcome columns and says why. subject_kind "person" with a regulated outcome requires acknowledge_decision_support: true. reading reduces an event table to one row per entity first (kinds from hs_describe_capabilities). refit_of: a prior model_ref of the SAME analysis declares this run a refresh, so the response carries drift; an unknown, expired or foreign ref is refused before the run starts and nothing is billed. idempotency_key dedupes retries onto one run. RETURNS: entities[] { entity_id, score, tier, band, max_autonomy }, ranking_ref (24 hours, for the free interrogation tools), model_ref (90 days, for hs_score_entity), verdict + signature, drift (only when refit_of was passed), top_decile_lift, calibration, validation { scheme, split_fraction, n_train, n_holdout, reason? }, leak_guard[], gate_verdicts, entity_replication; or a structured honest_empty { reasons[] } with no verdict and no model_ref. Never thresholds, weights, or arm names. A null statistic means no finding, NOT a pending field. EXAMPLE: hs_rank_topk({ data: { dataset_id: "sample:saas_churn" }, entity_column: "customer_id", outcome_column: "churned", subject_kind: "org", page: { k: 20 } }). MISTAKES: retrying an honest-empty instead of reading its reasons; reading a null statistic as zero; choosing an outcome column derived after the fact; comparing two scores for exact equality — compare bands.

hs_poll_task

Poll ranking task — Free to call; the run it polls is the billable one.

Free to call; the run it polls is the billable one. Check a long-running ranking started by hs_rank_topk in an async mode (a dataset_id or fetch_url run). Returns status "pending" (poll again after the suggested interval; do not tight-loop) or the completed ranking envelope. A pending response may also carry a stage + append-only facts_so_far (leak-firewalled progress — never a partial ranking) and an optional status_url: a short-TTL signed link to a live status page a human can open to watch staged progress in real time. Common mistakes: polling in a tight loop - respect retry_after_ms; treating "pending" as failure.

hs_explain_levers

Explain counterfactual levers — Free; no engine run.

Free; no engine run. For one or more entities already ranked by hs_rank_topk, compute the minimal set of feature changes - counterfactual levers - that would move each entity out of (or into) the pattern. It reuses the analysis behind the ranking_ref, so there is no new run and no extra cost. USE WHEN the user asks what would have to change for this customer not to churn; how to intervene; what to do about this account; what would move this lead; what is driving this entity's risk; which retention play, save offer, discount or next-best-action to take; or "what if" for one named entity. NOT FOR entities absent from a prior ranking; population-level "what drives this outcome" questions (use hs_explain_drivers); or any guarantee of real-world causal effect - these are minimal model-associated flips, not proven interventions. RETURNS: per-entity minimal feature changes (each a feature label + a direction: increase / decrease / change), a coarse magnitude (substantial / notable / slight), and likelihood_direction (lower / higher / unchanged) - which way the change moves PREDICTED LIKELIHOOD of the outcome - plus provenance. Every lever is labeled association_not_causal. No raw scores, score deltas, thresholds or weights are returned. The list is NOT aligned to your request: an entity with no disclosable lever is omitted, and named in no_lever_for - read that rather than matching by position. likelihood_direction is a FACT, not a recommendation, and it is NOT fixed to "lower" - READ IT PER LEVER. Which way it reads follows the polarity the engine resolved for this outcome: on an ADVERSE outcome (churn, default, failure) the levers move an entity OUT of the high-likelihood pattern and read "lower"; on a DESIRABLE outcome (converted, renewed, closed) the engine returns COMPLETION levers that move an entity INTO it, and those read "higher". Assuming "lower" on a desirable outcome inverts every lever you present. Whether the direction you get is the direction you WANT depends on whether the outcome is desirable or adverse - you know which, and Hunter-Seeker does not infer it. Decide the good/bad reading yourself, or ask the user, before presenting a lever as an improvement. HONEST-EMPTY IS A REAL ANSWER HERE, AND A STRONGLY-CLEARED RANKING IS NOT EXEMPT. A run can clear the lift bar by a wide margin and still return levers: [] with a reason. Recourse is gated on the JOINT all-conditions cell having held-out support: if no held-out row actually sits in the completed pattern, that state is not demonstrably reachable by anyone, and the engine returns nothing rather than extrapolating. Measured on the published samples: sample:agent_traces (pattern coverage 3.25% of entities) returned levers, while sample:saas_churn (1.0%) and sample:b2b_leads (1.25%) both cleared strongly and returned none. It tracks how NARROW the joint pattern is, not the lift and not the polarity - an adverse outcome is just as affected as a desirable one. WHAT THAT COSTS YOU: hs_attest_action requires a lever_token, a token is minted per lever, and a lever carries the field only when the engine minted one - so no lever means no token means the attest -> hs_report_outcome -> hs_action_evidence arm is unavailable FOR THAT RANKING. Check 'lever_token' in lever before you plan an evidence loop around it; the field is ABSENT rather than null when there is none. You still have the ranking, the drivers and the scores - what you do not have is a way to prove to the engine which entities you acted on. EXAMPLE: hs_explain_levers({ ranking_ref, entity_ids: ["acct_1042"] }). MISTAKES: interpreting levers as causal guarantees - present them as "what the model associates with a different outcome", especially in regulated or person-level domains; assuming the direction instead of reading likelihood_direction per lever.

hs_explain_drivers

Explain outcome drivers — Free; no engine run.

Free; no engine run. For a ranking already produced by hs_rank_topk (pass its ranking_ref), return THE PATTERN the engine found: the COMBINATION of feature-conditions that, TOGETHER, predict the outcome - not independent per-feature effects. It reuses the analysis behind the ranking_ref, so there is no new run and no extra cost. USE WHEN the user asks why - what drives churn, conversion, default or failure here; what the high-risk group has in common; what distinguishes the top decile; what converting customers share; what characterises the accounts that renew; what profile to target; what to segment on; or when you are priming context before acting, or writing a deep-dive on a dataset. NOT FOR per-entity "what would have to change" questions (use hs_explain_levers); causal claims of any kind; ranking the conditions against each other; or reporting one condition on its own. RETURNS: pattern.conditions[] - read as ONE joint profile - plus pattern.coverage (the exact share of entities matching the FULL pattern) and pattern.lift. Engine-authored facts passed through verbatim: never a raw arm name, a weight, or the search objective. Every condition is association_not_causal, the same firewall as levers. A NUMERIC condition is a feature + direction (higher / lower) + the threshold where it turns (e.g. months_supply lower than 2.1), and it carries the exact predicate alongside: operator ("<=" or ">") and missing_values ("included" or "excluded"). BUILD YOUR FILTER FROM operator AND missing_values, NOT FROM THE DIRECTION WORD - the boundary is ASYMMETRIC and the English reading of "lower" is wrong. "lower" means operator "<=" with missing_values "included": it holds AT the threshold as well as below it, and also covers rows where that value is MISSING. "higher" means operator ">" with missing_values "excluded": strictly above, missing rows out. So "lower than t" names a WIDER cohort than a naive "< t" filter, and coverage counts those extra rows. This is not a rounding detail: on a dataset whose threshold lands on a common value, reading "lower than 168" as "< 168" selects ZERO rows while coverage reports 0.279 - a gate that fires on nothing while the envelope looks healthy. Both fields are absent on a categorical condition. A CATEGORICAL condition has direction "different" (set membership has no high/low) and instead carries categories (the values involved) plus category_match, either "is_one_of" or "is_not_one_of" - take it LITERALLY and never drop the negation, because "is_not_one_of" ["annual"] is the opposite cohort from "is_one_of" ["annual"]. Both fields are absent on numeric conditions. Describe the whole combination as a unit - "entities that are X and Y and Z together". Hunter-Seeker never phrases it for you. EXAMPLE: hs_explain_drivers({ ranking_ref }) using the ranking_ref a cleared hs_rank_topk returned. MISTAKES: passing a ranking_ref that has expired (cached 24 hours) or never cleared the bar - call hs_rank_topk first and use its ranking_ref verbatim; reading a driver as a cause; re-ordering the conditions or quoting one alone.

hs_model_quality

Model quality diagnostics — Free; no engine run.

Free; no engine run. For a ranking already produced by hs_rank_topk (pass its ranking_ref), return the model DIAGNOSTICS so you can judge how much to trust it BEFORE acting on it. Returns: top_decile_lift (how concentrated the outcome is in the top-ranked group), calibration_error (ECE - lower is better-calibrated), validation (scheme: out-of-time, holdout, or none when no rows could be held back; n_holdout is null when nothing was held back; plus n_train and a plain-English reason), lift_curve (relative cumulative lift per decile), and leak_guard (columns quarantined as likely target leakage, each with a plain-English reason). These are validation statistics - never a threshold, weight, score, or arm. Best for: due diligence before acting, a governance / trust check, or a model-quality section in a report. A low top_decile_lift, a high calibration_error, or a populated leak_guard is a signal to be cautious. It reuses the analysis behind the ranking_ref - no new engine run. Common mistakes: passing an expired or never-cleared ranking_ref (call hs_rank_topk first); treating a null field as zero - it means there was no finding.

hs_context_brief

Portable context brief — Free; no engine run.

Free; no engine run. For a ranking already produced by hs_rank_topk (pass its ranking_ref), return a PORTABLE BRIEF you can drop straight into your own agent's context - the whole analysis as one compact artifact instead of three separate calls. Returns: provenance (engine version, core hash, the ranking_ref, the analysis identity, and when the analysis was computed), outcome.label, trust (top-decile lift, calibration error, validation scheme, and any columns the leak guard quarantined), driver_group (the drivers the engine found, each with a direction, to be read as ONE combination), and limits. Best for: handing an analysis to another agent (the brief is self-contained; its provenance.ranking_ref resolves only for the workspace whose credential ran it), filing an analysis in your own store so you can recognise the same analysis later, or building a domain expert on top of Hunter-Seeker - we supply the governed prediction, you supply the domain. It leads with usability — "actionable" | "unjudged" | "refused" — which is the go/no-go and is the SAME word, with the same meaning, that hs_rank_topk and hs_poll_task return; when it is not "actionable" the brief also carries unjudged_note and, for a partitioned run, trust.clearance_frequency so you can see the evidence rather than trust the label. Read it BEFORE the driver group: trust.honest_empty answers a different question (did the engine isolate a driver combination?) and a brief can carry four drivers, honest_empty false, and usability "unjudged" all at once — the drivers are reported because the engine reported them, not because the run was certified. Set format to "markdown" for prose instead of JSON; both carry identical numbers. It reuses the analysis behind the ranking_ref, so it costs nothing and can be called as often as you like - only hs_rank_topk consumes a run. Drivers are ASSOCIATIONS, not causes, and are only meaningful together: never re-order them, never rank one above another, never report one on its own. Common mistakes: passing a ranking_ref older than 24 hours (the analysis is cached for 24 hours, then you must re-run hs_rank_topk); treating a null statistic as zero - null means the engine did not report it; inventing drivers when the brief returns an empty driver group, which is a real result and not a gap; and reading honest_empty: false as permission to act — that is what usability is for. Never restate a number this brief does not contain, and never compute change over time by comparing two briefs - that is you authoring a direction the engine never gave. If change over time matters, re-run.

hs_score_entity

Score one entity at decision time — Costs ONE DECISION from your quota (never a run).

Costs ONE DECISION from your quota (never a run). Score ONE row against a scorecard the engine already fitted — use the model_ref that a cleared hs_rank_topk returned. Returns the calibrated score (0–1), an engine-authored band (act = certified likely; escalate = uncertain, ask a human; refuse = the engine will not vouch, with a reason), a max_autonomy hint (L0–L4: how much rope THIS decision earns), up to four principal_reasons (engine-ordered levers in the shape adverse-action rules require), and a SIGNED Verdict you can hand to a reviewer or verify with hs_verify_verdict. The score is the one the originating run assigned this row, from the same fitted scorecard — a cache of a deterministic fit, not a second model. It can differ in the last bit of the float (measured: 7 of 12 entities identical, the rest by one ULP, never more) because the run scores the whole table at once while this scores one row; the band, the reasons and the ordering are unaffected. Do not compare two scores for exact equality — compare bands. Best for: 'should my agent act on this lead / account / ticket right now'. Not for: rows from a different table than the model_ref was fitted on. The real requirement is narrower than 'columns must match': the scorecard reads ONLY its own arm features, so extra columns are ignored and a subset carrying every arm feature scores identically - a missing one is a 422 naming the column, never a quiet wrong score. Firewall: no thresholds, weights, or condition counts are returned — read band and principal_reasons, never try to reconstruct the rule. subject_kind 'person' REQUIRES acknowledge_decision_support: true and caps max_autonomy at L2 (L0 without a human principal). A refuse band bills nothing. Common mistakes: reusing an expired model_ref (404 model_ref_expired → re-run hs_rank_topk, do not retry); treating band as advice — it is a fact about likelihood; on an adverse outcome 'act' means likely to churn/default/fail, on a desirable one likely to convert/renew — read verdict.outcome.polarity. The response also carries next.then, which names the step that turns this decision into evidence: report the observed outcome under this entity_id, after which hs_action_evidence and hs_drift_status are free.

hs_score_batch

Score many rows against one scorecard — Costs ONE DECISION PER ROW that is not refused.

Costs ONE DECISION PER ROW that is not refused. Score up to 10,000 rows against a model_ref in one call — for backfills, nightly re-scoring, or replaying a historical cohort. Same arithmetic, same firewall, same band semantics as hs_score_entity; returns one signed Verdict covering every entity. Common mistakes: sending rows that omit a column the analysis used (extra columns are fine; a missing arm feature is a 422 naming it); expecting per-row signatures — there is one signature over the whole Verdict.

hs_verify_verdict

Verify a Verdict's signature — Free; no engine run.

Free; no engine run. Check that a Verdict was issued by Hunter-Seeker and has not been altered. Verification is credential-free on the REST endpoint POST https://hunter-seeker.io/api/v1/verify-verdict and in the published hs-verify / @hunter-seeker/verify libraries, which check a signature against the public JWKS with no server at all. Over MCP it still needs a credential like every other tool, because MCP authenticates the whole connection rather than each call — so hand a reviewer the REST endpoint or the library, not an MCP session. Pass the verdict object and its signature exactly as received. Returns status: valid | invalid_signature | expired | unknown_key — and nothing else, so the endpoint cannot be probed. Best for: an approval step that must prove the score was not authored by the agent; an auditor; another vendor's agent. The same check runs offline with the open-source libraries hs-verify (pip) and @hunter-seeker/verify (npm) against the public keys at hunter-seeker.io/.well-known/jwks.json. Common mistakes: re-serialising the verdict before verifying (pass the object, the verifier canonicalises it — RFC 8785); treating 'expired' as valid — an expired verdict is re-scored, never reused.

hs_report_outcome

Report observed outcomes — Free; writes YOUR observed outcomes (never fires an action, never retrains).

Free; writes YOUR observed outcomes (never fires an action, never retrains). For entities scored under a model_ref, append what actually happened: outcome is a binary (0/1, true/false, yes/no); observed_at is when you observed it; event_id is your idempotency key (send the same id on retry and the row is written once). Outcomes are EVIDENCE for hs_action_evidence. They do NOT feed hs_drift_status, which compares the fits you link with refit_of and nothing else — reporting outcomes will never move it. They never change the scorecard, the band, or the pattern either; a refit is an explicit hs_rank_topk with refit_of. Free text is refused (422 outcome_not_binary) so this ledger cannot become a transcript store. Best for: closing the loop after acting, so the engine can tell you whether acting worked. Common mistakes: inferring the outcome from the trace or the agent's own judgement — report the real-world binary you observed; sending prose.

hs_attest_action

Attest that a lever was pulled — Free; records that you acted on a lever.

Free; records that you acted on a lever. Pass the lever_token from hs_explain_levers and the entity's NEW value for that feature after your action. The engine evaluates compliance inside its boundary — was the target crossed, and how far did the entity move (dose_fraction 0–1) — and returns {compliant, dose_fraction, evaluated_feature, evaluated_direction}; the threshold itself never crosses the wire. Read evaluated_feature before reading compliant: a lever with two changes carries ONE token, minted for one of them, so a false here often means you moved a different feature than the one this token evaluates — not that the change failed. The attestation is what puts this entity in hs_action_evidence's acted group, which is compared with every other entity that has a reported outcome under the model_ref — no pattern filter, no control-arm input. Idempotent by event_id. Common mistakes: attesting before the change actually happened; passing the OLD value.

hs_action_evidence

Did acting on this pattern work? — Free; no engine run.

Free; no engine run. For a model_ref, returns the live evidence block (from outcomes you reported, comparing entities with a compliant hs_attest_action against every other entity with a reported outcome under the model_ref, a non-compliant attestation included — no pattern filter, no control-arm input). It carries rates, n, the difference, and a Newcombe hybrid-score confidence interval. live is NULL until each cell has at least 30 rows, and small_n is true below 100 per cell — a null here means 'not enough evidence yet', not zero effect. Best for: deciding whether to keep acting on a pattern; a governance report. Common mistakes: treating small_n evidence as settled; reading a null live block as zero effect rather than as not enough evidence yet.

hs_drift_status

Has the pattern moved? — Free; no engine run.

Free; no engine run. For a model_ref that has been refit at least once (hs_rank_topk with refit_of), compare the current pattern with the prior: pattern is unchanged | changed | no_prior; pattern_diff names conditions added or removed and thresholds that moved (feature + direction only — values never leave the engine); cusum_fired says whether the calibration coefficient drifted; recommendation is keep | refit | abandon. One honest-empty on refresh is NOT drift — abandon requires two consecutive honest-empties on the same analysis. Never infer drift by comparing two briefs yourself; this tool is the engine's comparison. Common mistakes: refitting on 'changed' alone — refit is recommended only when the coefficient also drifted or across ≥ 2 cycles.

hs_preflight

Is this table worth a run? — Free; no engine run, no model fitted, nothing written.

Free; no engine run, no model fitted, nothing written. Ask this BEFORE hs_rank_topk on a table you have not ranked, because rank-topk is the call that costs a run and this is the call that says whether the run is worth spending. Three answers, each a fact: grain.verdict says whether one row is one KIND of thing (multiple_populations means the table holds two — entities and the claims or vehicles belonging to them, loaded by two writers — so a count over it counts two things); resolutions says what each column's VALUES are rather than how they are stored, and a column reported ambiguous is one whose values support two readings and MUST NOT be used as a predictor until you resolve it — a US state code and an ISO country code are both two letters, and CA is California and Canada, DE Delaware and Germany, IL Illinois and Israel; fitness is the arithmetic of positives per usable predictor over the LABELLED rows only, where a predictor is usable when it is actually populated, so nine declared and three populated is three. blocking means a number built from this table would not be believable; advisory is worth reading and stops nothing. It reports and refuses nothing — the decision to proceed is yours, and proceeding past a blocking entry is a choice you are making on the record. Best for: a table assembled from more than one source; a first run on a dataset you did not build; deciding whether to spend the run at all. Common mistakes: treating a clean preflight as a promise the ranking will clear (it says the table is worth trying, never that a pattern exists); using an ambiguous column anyway because it 'looks like' a state; reading unlabelled rows as negatives — fitness does not, and neither should you.


Provenance


See also: The two loops · Refusal is a result · llms-full.txt