{
  "schema_version": "2025-06-18",
  "name": "hunter-seeker",
  "display_name": "Hunter-Seeker",
  "description": "Deterministic decision engine for agents. Rank the rows of any table by likelihood of a yes/no outcome, then score individual entities at decision time and get back a SIGNED, verifiable Verdict with an engine-authored band and an autonomy ceiling — plus counterfactual levers, a provenance hash on every response, and a structured honest 'no' when the signal isn't there. Report what happened and it will tell you whether acting is working.",
  "version": "2.0.0",
  "documentation": "https://hunter-seeker.io/docs",
  "endpoint": "https://hunter-seeker.io/api/mcp",
  "transport": "streamable-http",
  "authorization": {
    "type": "oauth2",
    "flow": "authorization_code",
    "dynamic_client_registration": true,
    "resource_metadata": "https://hunter-seeker.io/.well-known/oauth-protected-resource/api/mcp",
    "notes": "OAuth 2.1 for directory-listed clients (access tokens ~1h, refresh ~30d). Tenant-scoped API keys are available for machine-to-machine REST access."
  },
  "provenance": {
    "engine_version": "0.3.5",
    "core_hash": "4d24e9fcfe4d6d42a4ece61ac02e72ef0b584b5304d204c9e6896cdad9ecfe0d",
    "reading_contract_hash": "40498adbf0ca2603307ee00c49dcd5c90b58c188318a9bf525d2923524351b91",
    "reading_grammar_version": 3
  },
  "tools": [
    {
      "name": "hs_describe_capabilities",
      "title": "Describe capabilities",
      "cost": "Free; no engine run.",
      "description": "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.\nThe 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.\n\n  as-is@1 — a row per entity already — no reduction, and no cost to saying so\n  sequential@1 — an event log → per-entity counts, tenure, top event types and transitions\n  trace@1 — agent/job runs → each run gains its prior failure rate over STRICTLY EARLIER runs\n  windowed@1 — a metric history → trailing-window aggregates, row count preserved\n  panel@1 — entity × period → the outcome DERIVED by the engine, from a future window or a trend\n  stream@1 — a sensor stream + fault intervals → snapshots with trailing aggregates\nUSE 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.\nNOT FOR running anything — it reads nothing but the contract, and reports no finding.\nRETURNS: contract, trust, limits, readings.kinds[], samples[], examples[], provenance. Every field describes what the engine CAN do, never what any run found.\nEXAMPLE: call with no arguments; then hs_rank_topk({ data: { dataset_id: \"sample:saas_churn\" }, entity_column: \"customer_id\", outcome_column: \"churned\", subject_kind: \"org\" })."
    },
    {
      "name": "hs_provide_dataset",
      "title": "Provide dataset",
      "cost": "Free; no engine run.",
      "description": "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 \"<upload_url>\"; 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.\nUSE WHEN the table is bigger than a small paste, or you will rank the same data more than once.\nNOT 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.\nRETURNS: 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.\nLIFETIME: a dataset_id is SINGLE-USE and short-lived. The raw data is dropped the moment its run reaches a terminal state — a ranking, an honest-empty, or a failure — so the SECOND hs_rank_topk on the same dataset_id returns unknown_dataset. That is the engine keeping no customer data it is not actively using, not an error and not an expiry. A registration that is never run expires 24 hours after it is created. 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 an hour, free.\nEXAMPLE: hs_provide_dataset({}) then PUT the file, then hs_rank_topk({ data: { dataset_id }, entity_column: \"customer_id\", outcome_column: \"churned\", subject_kind: \"org\" }).\nMISTAKES: 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 by its first completed run."
    },
    {
      "name": "hs_rank_topk",
      "title": "Rank top-k by outcome likelihood",
      "cost": "Costs ONE RUN from quota — the only tool that does.",
      "description": "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.\nUSE 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.\nNOT FOR predicting a continuous number; data with no historical outcome column; pure time-series forecasting; causal guarantees.\nCOST 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.\nPARAMS: exactly one of data.rows | data.csv (inline, synchronous, keep to a few hundred rows — your client's payload budget binds before the server's ~4MB / 2,000 rows / 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.\nRETURNS: entities[] { entity_id, score, tier, band, max_autonomy }, ranking_ref (1 hour, 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.\nEXAMPLE: hs_rank_topk({ data: { dataset_id: \"sample:saas_churn\" }, entity_column: \"customer_id\", outcome_column: \"churned\", subject_kind: \"org\", page: { k: 20 } }).\nMISTAKES: 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."
    },
    {
      "name": "hs_poll_task",
      "title": "Poll ranking task",
      "cost": "Free to call; the run it polls is the billable one.",
      "description": "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."
    },
    {
      "name": "hs_explain_levers",
      "title": "Explain counterfactual levers",
      "cost": "Free; no engine run.",
      "description": "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.\nUSE 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.\nNOT 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.\nRETURNS: 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.\nlikelihood_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.\nEXAMPLE: hs_explain_levers({ ranking_ref, entity_ids: [\"acct_1042\"] }).\nMISTAKES: 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."
    },
    {
      "name": "hs_explain_drivers",
      "title": "Explain outcome drivers",
      "cost": "Free; no engine run.",
      "description": "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.\nUSE 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.\nNOT 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.\nRETURNS: 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.\nA 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.\nA 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.\nDescribe the whole combination as a unit - \"entities that are X and Y and Z together\". Hunter-Seeker never phrases it for you.\nEXAMPLE: hs_explain_drivers({ ranking_ref }) using the ranking_ref a cleared hs_rank_topk returned.\nMISTAKES: passing a ranking_ref that has expired (cached one hour) 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."
    },
    {
      "name": "hs_model_quality",
      "title": "Model quality diagnostics",
      "cost": "Free; no engine run.",
      "description": "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."
    },
    {
      "name": "hs_context_brief",
      "title": "Portable context brief",
      "cost": "Free; no engine run.",
      "description": "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, 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. 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 an hour (the analysis is cached for one hour, then you must re-run hs_rank_topk); treating a null statistic as zero - null means the engine did not report it; and inventing drivers when the brief returns an empty driver group, which is a real result and not a gap. 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."
    },
    {
      "name": "hs_score_entity",
      "title": "Score one entity at decision time",
      "cost": "Costs ONE DECISION from your quota (never a run).",
      "description": "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 (columns must match). 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."
    },
    {
      "name": "hs_score_batch",
      "title": "Score many rows against one scorecard",
      "cost": "Costs ONE DECISION PER ROW that is not refused.",
      "description": "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 with different columns than the fitted table; expecting per-row signatures — there is one signature over the whole Verdict."
    },
    {
      "name": "hs_verify_verdict",
      "title": "Verify a Verdict's signature",
      "cost": "Free; no engine run.",
      "description": "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."
    },
    {
      "name": "hs_report_outcome",
      "title": "Report observed outcomes",
      "cost": "Free; writes YOUR observed outcomes (never fires an action, never retrains).",
      "description": "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 and drift signals for hs_drift_status; they never change the scorecard, the band, or the pattern — a refit is an explicit hs_rank_topk with refit_of. Free text is refused (422 outcome_malformed) 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."
    },
    {
      "name": "hs_attest_action",
      "title": "Attest that a lever was pulled",
      "cost": "Free; records that you acted on a lever.",
      "description": "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 only {compliant, dose_fraction}; the threshold never crosses the wire. The attestation is what lets hs_action_evidence compare acted vs not-acted entities within the same pattern. Idempotent by event_id. Common mistakes: attesting before the change actually happened; passing the OLD value."
    },
    {
      "name": "hs_action_evidence",
      "title": "Did acting on this pattern work?",
      "cost": "Free; no engine run.",
      "description": "Free; no engine run. For a model_ref, returns the live evidence block (from outcomes you reported, comparing entities with a compliant attestation against those without, within the same pattern). 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."
    },
    {
      "name": "hs_drift_status",
      "title": "Has the pattern moved?",
      "cost": "Free; no engine run.",
      "description": "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."
    }
  ],
  "limits": {
    "inline_rows_max": 2000,
    "inline_cols_max": 64,
    "inline_bytes_max": 4400000,
    "k_default": 20,
    "k_max": 100
  },
  "trust_contract": {
    "determinism": "Identical inputs return byte-identical envelopes.",
    "provenance": "Every response carries engine_version + core_hash.",
    "leak_guard": "Columns whose signal is so strong they are likely target leakage (univariate AUC > 0.98) are quarantined, dropped from the analysis, and each surfaced with a plain-English reason. Returned as a list of {column, reason} on a cleared finding and null on a non-finding (honest-null, NOT 'no leakage'). The lift ≥ 1.5 gate does not catch leakage, so the caller must still ensure the outcome column and features are historically known.",
    "honest_empty": "Below the published lift ≥ 1.5 bar, returns {result:'none', reasons[], gate_verdicts[], retry:'unproductive'} — a terminal result, not an error.",
    "honest_partial": "top_decile_lift, validation, leak_guard, and ranking-level top_factors are populated on a cleared finding and returned as null on a non-finding (honest-null) — never fabricated. A null means no finding, not that the feature is unbuilt.",
    "regulated": "Person-level outcomes in regulated domains — such as hiring, credit, education, insurance, benefits, justice, healthcare (e.g. readmission, triage, diagnosis) and immigration (visa, asylum) — require acknowledge_decision_support: true and are decision-support only. Social-scoring, emotion-recognition, biometric-categorization, and predictive-policing framings are refused with no acknowledgment path."
  },
  "resources": {
    "llms_txt": "https://hunter-seeker.io/llms.txt",
    "agents_md": "https://hunter-seeker.io/AGENTS.md",
    "openapi": "https://hunter-seeker.io/docs/openapi.json"
  },
  "samples": [
    {
      "dataset_id": "sample:saas_churn",
      "description": "800 synthetic SaaS accounts with tenure, support tickets, logins, plan and seats. Which accounts are most likely to churn?",
      "outcome_column": "churned",
      "entity_column": "customer_id",
      "outcome_is_desirable": false,
      "subject_kind": "org",
      "rows": 800,
      "synthetic": true,
      "cost": "free — a sample run never consumes a run from your quota",
      "lift": 4.4776,
      "top_decile_lift": 4.444444
    },
    {
      "dataset_id": "sample:b2b_leads",
      "description": "800 synthetic B2B leads with pages viewed, demo requests, company size and touch latency. Which leads are most likely to convert?",
      "outcome_column": "converted",
      "entity_column": "lead_id",
      "outcome_is_desirable": true,
      "subject_kind": "org",
      "rows": 800,
      "synthetic": true,
      "cost": "free — a sample run never consumes a run from your quota",
      "lift": 7.3469,
      "top_decile_lift": 5.263158
    },
    {
      "dataset_id": "sample:telco_churn",
      "description": "800 synthetic telecom accounts with contract type, tech support, tenure and charges. Which accounts are most likely to churn?",
      "outcome_column": "churned",
      "entity_column": "account_id",
      "outcome_is_desirable": false,
      "subject_kind": "org",
      "rows": 800,
      "synthetic": true,
      "cost": "free — a sample run never consumes a run from your quota",
      "lift": 3.9216,
      "top_decile_lift": 6.190476
    },
    {
      "dataset_id": "sample:agent_traces",
      "description": "800 synthetic AI-agent runs (agent, task, tool, 0/1 failed) with a tool that degrades mid-window. Built for reading {kind: \"trace\", version: 1} — ranked raw it returns an honest non-finding; reduced, the per-group prior failure rates clear high-tier. Which runs are most likely to fail?",
      "outcome_column": "failed",
      "entity_column": "run_id",
      "outcome_is_desirable": false,
      "subject_kind": "event",
      "rows": 800,
      "synthetic": true,
      "cost": "free — a sample run never consumes a run from your quota",
      "reading": {
        "kind": "trace",
        "version": 1,
        "roles": {
          "identifier": "run_id",
          "time_axis": "ts",
          "outcome": "failed",
          "agent": "agent",
          "task": "task",
          "tool": "tool"
        }
      },
      "lift": 4.1026,
      "top_decile_lift": 4.285714,
      "note": "Ranked raw this dataset returns an honest non-finding; the lift above is the REDUCED result, via the reading above."
    },
    {
      "dataset_id": "sample:usage_panel",
      "description": "9,600 rows: 800 synthetic SaaS accounts observed over 12 monthly periods, with usage, logins, feature adoption, support tickets and seats. The `usage_declining` column is a noisy per-period self-report and ranks to nothing on its own. Built for reading {kind: \"panel\", version: 1} with a `trend` label — the engine derives the real declining/not label from the shape of `usage` and clears. Which accounts are on their way out?",
      "outcome_column": "usage_declining",
      "entity_column": "account_id",
      "outcome_is_desirable": false,
      "subject_kind": "org",
      "rows": 9600,
      "synthetic": true,
      "cost": "free — a sample run never consumes a run from your quota",
      "reading": {
        "kind": "panel",
        "version": 1,
        "roles": {
          "identifier": "account_id",
          "time_axis": "period"
        },
        "params": {
          "label_horizon": 1,
          "label_agg": "max",
          "min_observation": 5,
          "trend_direction": "down",
          "trend_alpha": 0.1,
          "cutoff_rule": "last_minus_label",
          "label": {
            "kind": "trend",
            "column": "usage"
          }
        }
      },
      "lift": 2.5478,
      "top_decile_lift": 2.539683,
      "note": "Ranked raw this dataset returns an honest non-finding; the lift above is the REDUCED result, via the reading above."
    },
    {
      "dataset_id": "sample:sensor_stream",
      "description": "4,815 rows: four signals from 40 synthetic machines every six hours for 30 days, with the fault intervals in the same table (a row with an `event_type` is an interval, not a reading). The `maintenance_due` flag is noise and ranks to nothing. Built for reading {kind: \"stream\", version: 1} — reduced to daily snapshots with trailing window aggregates it clears high-tier. Which machines are about to fail?",
      "outcome_column": "maintenance_due",
      "entity_column": "asset_id",
      "outcome_is_desirable": false,
      "subject_kind": "object",
      "rows": 4815,
      "synthetic": true,
      "cost": "free — a sample run never consumes a run from your quota",
      "reading": {
        "kind": "stream",
        "version": 1,
        "roles": {
          "identifier": "asset_id",
          "time_axis": "ts",
          "signals": [
            "vibration",
            "temp_c",
            "pressure_psi",
            "current_a"
          ],
          "event_type": "event_type",
          "event_start": "event_start",
          "event_end": "event_end",
          "label_column": "maintenance_due"
        },
        "params": {
          "snapshot_period": "1d",
          "windows": [
            1,
            3,
            7
          ],
          "horizon": 3,
          "min_observation": 3
        }
      },
      "lift": 3.2448,
      "top_decile_lift": 7.518539,
      "note": "Ranked raw this dataset returns an honest non-finding; the lift above is the REDUCED result, via the reading above."
    }
  ],
  "shapes": {
    "summary": "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.",
    "kinds": [
      {
        "kind": "as-is",
        "version": 1,
        "takes": "a row per entity already — no reduction, and no cost to saying so",
        "required_roles": []
      },
      {
        "kind": "sequential",
        "version": 1,
        "takes": "an event log → per-entity counts, tenure, top event types and transitions",
        "required_roles": [
          "identifier",
          "time_axis"
        ]
      },
      {
        "kind": "trace",
        "version": 1,
        "takes": "agent/job runs → each run gains its prior failure rate over STRICTLY EARLIER runs",
        "required_roles": [
          "identifier",
          "time_axis",
          "outcome"
        ]
      },
      {
        "kind": "windowed",
        "version": 1,
        "takes": "a metric history → trailing-window aggregates, row count preserved",
        "required_roles": [
          "identifier",
          "time_axis",
          "numeric_metric"
        ]
      },
      {
        "kind": "panel",
        "version": 1,
        "takes": "entity × period → the outcome DERIVED by the engine, from a future window or a trend",
        "required_roles": [
          "identifier",
          "time_axis"
        ]
      },
      {
        "kind": "stream",
        "version": 1,
        "takes": "a sensor stream + fault intervals → snapshots with trailing aggregates",
        "required_roles": [
          "identifier",
          "time_axis",
          "signals"
        ]
      }
    ]
  }
}
