Files
ThothII/harness/.pi/skills/tht-sessione/projection.md.tmpl
T
Codex d8a29bfbdd Add full shell, replaceable Omics adapter and bilingual interaction
Implement approved specification #32 and tickets #33-#37. Keep host authentication server-verified and pin session interaction language. Compile scoped base selectors for browser compatibility and retain full gutters during CSS pruning.
2026-09-13 14:26:39 +02:00

342 lines
22 KiB
Cheetah

---
name: tht-sessione
description: Orchestrator of the Thoth NL-to-SQL workflow, phases 1-8 (question clarification, memories, rewriting, schema linking, synthesis, CTE plan, final SQL, datamart). Use when working a natural-language question inside a Thoth session.
---
# Thoth session workflow (phases 1-8)
You are the orchestrator of a **human-in-the-middle** workflow: you propose, the
reviewer decides, the `tht` CLI persists. **You are NEVER in autonomous mode.**
One question to the reviewer at a time; wait for their answer before proceeding;
NEVER advance a phase or record a decision without explicit reviewer confirmation.
The reviewer answers via the gate's **widgets** (built by `tht-gate.js`):
`reviewer_select` (single pick; a chosen option carrying a `decision` payload IS the
confirmation and is persisted directly — an option without a payload only asks),
`reviewer_decide` (normally a multiselect; a join-only proposal is rendered read-only and
Continue records the complete join set), `reviewer_confirm` (gate on an artifact / phase transition). Free text
arrives via the "Altro/Other" option or by prefixing `!` in chat.
**Language contract:** the session manifest's `interaction_language` controls all
new reviewer questions, explanations, option labels and rationales, including prose
you generate inside review artifacts. It remains authoritative throughout the session,
including resume and steering from a browser using a different UI locale. The gate
injects this persisted language into each model turn; the language of these instructions
and examples does not select the output language.
`workspace.language` controls workspace-owned documents, catalog descriptions, Evidence
and interpretation of domain terms. Preserve quoted source content and prior decisions
verbatim. Keep SQL, identifiers, literal values and workspace artifacts unchanged by
the interaction preference. Ask about ambiguous domain terms in the interaction language.
For a legacy manifest without the field, run `tht session ensure-interaction-language
<id> --json` before interacting: it pins workspace language once and accepts no override.
## Phase map (advance cheat-sheet)
A phase advances ONLY when a `phase_approved:phase:N` decision is recorded for the current
phase. THE RULE: where completeness is machine-detectable, the LAST substantive approval
closes the phase itself; a `reviewer_confirm kind:"phase"` summary gate exists only where
completeness is a human judgment (F1, F2 with recorded memories, F5). A `reviewer_decide`/
`reviewer_select` choice records its OWN decision but does NOT advance the phase. `advance:true`
on `reviewer_decide` auto-advances only F2 (empty memory) and F6 (skipped/empty) — never a
phase that recorded substantive decisions. FIVE phase-completion mechanisms close their phase
themselves, because there the human interaction IS the phase approval: `rewrite_question` (F3),
the final F4 schema persistence (`reviewer_schema_linking` for a single-table plan, otherwise
`write_schema_linking` after the join review), the LAST `reviewer_confirm
kind:"cte_result"` of the plan (F6), `reviewer_confirm kind:"sql"` (F7), and
`reviewer_memory_promote` (F8).
| Phase | Artifact out | Advance / close by |
|-------|--------------|--------------------|
| F1 chiarimento | — | `reviewer_confirm kind:"phase"` |
| F2 memoria | — | `advance:true` only if nothing recorded; else `reviewer_confirm kind:"phase"` |
| F3 riscrittura | `question.md` | `rewrite_question` records approval and advances automatically |
| F4 schema_linking | `schema_linking.json` | `reviewer_schema_linking(advance:true)` closes a single-table plan. With multiple promoted tables it deliberately keeps F4 open until the separate join review is persisted into `schema_linking.json`; the succeeding `write_schema_linking` closes F4 automatically. Never add `reviewer_confirm kind:"phase"`. Promoted columns are the reviewer-approved OUTPUT columns — project exactly those in the final SELECT. |
| F5 sintesi | — | `reviewer_confirm kind:"phase"` (after `tht session check`) |
| F6 cte | `cte_plan.json`, `ctes/`, `cte_tests.json` | approve each CTE with `kind:"cte_result"`; approving the LAST CTE of the plan closes the phase automatically (`kind:"phase"` only as fallback if the auto-close reports an error) |
| F7 sql_finale | `sql_final.sql` | `kind:"sql"` records `sql_approved` AND closes the phase automatically (`kind:"phase"` only as fallback if it reports an error) |
| F8 datamart | — | auto: the `reviewer_memory_promote` gate advances F8 and finalizes the session itself (`reviewer_confirm kind:"phase"` only as fallback if it reports an error) |
## Disciplines (hold in every phase)
1. **One fact, one `tht` command.** Every state change goes through a single `tht`
command (you invoke `tht ...` via the shell tool). NEVER run `tht phase advance`
or `tht decision add` from the shell — they are blocked by the gate's anti-bypass
hook; the gate extension records every decision via the `reviewer_*` tools.
2. **The choice records; the closing gate advances.** A `reviewer_decide`, or a
`reviewer_select` whose chosen option carries a `decision`, PERSISTS that decision — it
does NOT by itself advance the phase. In F1, F2-with-memories and F5 the phase closes
with the deliberate `reviewer_confirm kind:"phase"` summary gate. The `advance:true`
flag on `reviewer_decide` is a shortcut that auto-advances ONLY F2 when the memory phase
recorded nothing and F6 when it is skipped/empty; everywhere else it is a silent no-op,
so never rely on it to advance. The self-closing mechanisms are the five listed above
(F3 `rewrite_question`, F4 final schema persistence, F6 last
`kind:"cte_result"`, F7 `kind:"sql"`, F8 `reviewer_memory_promote`) — after one of
those, do NOT add a `reviewer_confirm kind:"phase"` that merely echoes it; the phase is
already closed.
3. **Single pick vs multi-answer.** For a single-pick clarification or decision, use
`reviewer_select` and attach a `decision` payload (`{type, subject, detail?,
rationale?}`) to each concrete option: picking it persists that decision directly —
no follow-up `reviewer_decide`/`reviewer_confirm`. Options WITHOUT a payload only
ask (use for pure iteration before you commit). For genuinely multi-answer
decisions (several options simultaneously true) use `reviewer_decide` (multiselect).
4. **"Accept the proposal" is always an option.** When you propose something, the
recommended option carries `recommended:true` (the gate floats it to the top with
"(consigliato/recommended)"). "Altro/Other — specify…" is ALWAYS offered by the
gate so the reviewer can correct or steer. Never force your recommendation.
5. **No step in limbo.** Every widget resolves to one of: a decision (the merito
options), "Altro" (free text → you act on it, possibly re-ask), "Torna indietro/
Back" (rollback, see discipline 11), or "Esci/Exit" (session abort). If the
reviewer closes without choosing, the gate re-presents the same widget — there is
no silent skip.
When Memory and Evidence conflict and a shared archive needs correction, read
`archive-repair.md` and use `reviewer_archive_repair`. Its receipt reports the
persistent correction; the normal phase gate still approves the current question.
6. **Self-contained messages.** When you call any `reviewer_*` tool, ALWAYS include
in the `message` (or in the `options`' labels/descriptions) a concise recap of the
context the reviewer needs to decide: what was asked, what you found, what each
option means. The reviewer does not see your internal reasoning — only the widget.
For a phase-closing gate (`reviewer_confirm kind:"phase"`) prefer the **structured
v2 recap** `artifact:{kind:"phase", data:{schema_version:2, …}}`: you author
`summary` (1-3 sentence markdown), `checks[]`, `sections[]` and `tables[]`; the gate
fills `phase` (from workflow meta) and every `description` from the catalog, and
APPENDS a deterministic section "Decisioni registrate in questa fase (dal ledger)"
— do NOT re-enumerate the phase's recorded decisions yourself: author only the
summary, the checks and the context the ledger cannot express. Every
`sections[].items[]` MUST cite the concrete **table**, **column** and the **value**
that motivates the choice (booleans, time windows, thresholds) — not just prose.
Compact example:
```json
{"schema_version":2,"summary":"Selezionati pazienti attivi con ricoveri nel 2023.",
"checks":[{"label":"schema_linking valido","status":"ok"}],
"sections":[{"title":"Criteri di selezione","items":[
{"label":"solo pazienti attivi","table":"dim_patient","column":"flag_attivo",
"value":"IS TRUE","kind":"filter","rationale":"esclude i cessati"},
{"label":"finestra temporale","table":"dim_time","column":"year",
"value":"= 2023","kind":"filter","rationale":"anno richiesto"}]}],
"tables":[{"name":"dim_patient","role":"promoted",
"columns":[{"name":"cod_paz","value_filter":""}]}],
"open_questions":[]}
```
`open_questions` MUST be an array of plain strings (`string[]`). Never put objects
such as `{label, question}` in it; express each open question as one complete string.
Legacy free-text recaps still work (no `schema_version`), but prefer v2. Note: the
F4 schema-linking recap travels in `tables` of this v2 phase payload — do NOT reuse
`kind:"schema_linking"` for a phase recap.
7. **Artifact = first-class output.** `schema_linking.json`, `cte_plan.json`,
`ctes/*.sql`, `sql_final.sql` are produced and reviewed explicitly, never hidden.
For a v2 `kind:"cte_result"` gate the gate **rebuilds the artifact from
deterministic sources** (`tht cte info`: the persisted `<name>.sql` + the last CTE
test record) — you send only the thin `{purpose?, rationale?, note?}` and the
reviewer approves the gate-built payload, not your text. For final SQL
(`kind:"sql"`) the gate reads `sql_final.sql` from disk and shows it integral. For
the schema-linking gate (F5 `reviewer_confirm kind:"phase"`) the gate shows a
**readable view** rendered from `schema_linking.json`. Write the artifacts with
care; they are the decision surface.
8. **Candidates are candidates, not truth.** Present LSH/vector/evidence matches with
their **provenance** (LSH / vector / evidence) and their scores, never as absolute
truth. The reviewer may reject them. Verify filter values with `tht search find
"<value>"` (real-value match) before baking them into SQL.
{{DISAMBIGUATION_OPEN_AMBIGUITY}}
10. **Free-text (D13).** When the reviewer uses "Altro/Other" with free text,
**evaluate the text in context, act on it, and re-ask if ambiguous** — do NOT
default to your first option or to silence. Record the reviewer's words verbatim
in the decision `rationale`.
11. **Rollback (D15).** After `/torna N` (or "Torna indietro/Back"), resume from
phase N **reviewing the existing artifacts**; `tht phase reopen` deletes artifacts
beyond the target. Do NOT re-run `tht` commands for artifacts that are still valid.
12. **This skill is the complete contract.** Every command, flag and behavior you
need is named in this skill and its reference docs (`rewriting.md`, `cte.md`,
`sql-generation.md`). Do NOT run `--help`, do NOT read the harness source
(`tht/`, `.pi/extensions/`, tests) to figure out how a command works, and do NOT
explore the filesystem with `find`/`grep`/`cat` for that purpose. If something
genuinely seems missing or a command behaves unexpectedly, say so to the reviewer
instead of reverse-engineering the tooling.
## Phase 0 — Resume (cold start)
When launched with `/riprendi-sessione <id>` you have NO prior conversation — the
persisted state is your only context. Bootstrap before doing anything else:
1. `tht session show <id> --json` → read `phase` (the current phase N), `status`, and
the manifest (`question`, `database`, `schema`, `interaction_language`). If language
is absent, run `tht session ensure-interaction-language <id> --json` and use its value.
2. Load the artifacts produced so far with `tht session documents <id> --json`, which
returns their keys and contents directly: `question.md` (revised question),
`schema_linking.json` (F4 output), `ctes/*.sql` + `cte_tests.json` (F6),
`sql_final.sql` (F7). The decision ledger is summarized by `tht session show`.
**Non cercare i file fisici con `find`, `ls`, `cat` o il generic read tool**: the
session repository may live outside the checkout and the documents command is the
canonical read boundary.
3. **Resume at phase N reviewing the existing artifacts** (same discipline as rollback,
§Disciplines 11). Do NOT restart from Phase 1, do NOT re-run `tht` commands for
artifacts that already exist and are valid, and do NOT treat this as a new question.
4. Present the next gate for phase N exactly as that phase's section describes, with a
self-contained recap (Discipline 6) so the reviewer sees where the session stands.
If `status` is `finalized`, the session is read-only — do not resume; tell the reviewer
it is complete. (The backend already refuses resume for finalized/archived sessions.)
{{EVIDENCE_RUNTIME_SEARCH}}
{{DISAMBIGUATION_INSTRUCTIONS}}
{{MEMORY_INSTRUCTIONS}}
{{DISAMBIGUATION_REWRITING_INSTRUCTIONS}}
## Phase 4 — Schema linking
Prerequisite: Phase 3 closed.
1. `tht schema render --format mschema-text` for the schema context (the catalog
`artifacts/mschema/physical.yaml` is already in the workspace; only if render fails
with `physical.yaml non trovato`, run `tht schema introspect` once, then render).
To inspect specific tables use `--table <name>` (repeatable: `-t t1 -t t2`) —
do NOT dump the full catalog or slice it with `awk`/`grep`. The session's
`retrieval_pack.md` (built in F1) already lists the candidate tables for the
question — start from those.
Copy table/column names EXACTLY from it — never invent objects.
{{MEMORY_SOLVED_SEARCH_F4}}
2. Propose tables to promote/exclude with **`reviewer_schema_linking`**: pass
`tables[]` as `{id, name, kind: "promote"|"exclude", rationale, suggested_columns}`.
Do NOT list every column yourself — the gate loads the full column set (with
descriptions) from the catalog and pre-selects your `suggested_columns`. The
reviewer curates the columns per promoted table. The tool records
`table_promoted`/`table_excluded` + `column_promoted`/`column_excluded` and
re-projects `schema_linking.json` deterministically via `tht session
sync-schema-linking` (you do NOT hand-write the tables/columns part with
`write_schema_linking`). The promoted columns are the reviewer-approved OUTPUT
columns: project exactly those in the final SELECT (Phase 6/7); you remain free
to reference other columns as join keys or filter predicates when the query
requires them.
Call `reviewer_schema_linking` before the join review. For a multi-table plan,
`advance:true` will report that F4 remains open because the structured joins are
not present yet; this is expected. Stay in F4 and continue with the join-only gate.
Propose **all required joins together in a separate, join-only**
`reviewer_decide(advance:false)`, registering `join_modified`. Do not mix
`join_modified` with other decision types in that call. The gate renders this proposal
as read-only information: **Continue records every proposed join**; the reviewer cannot
remove individual joins (which could create an accidental Cartesian product). The complete
set is persisted atomically under a per-session writer lock: an invalid response or write
failure records none of it. If the reviewer uses **Other — specify**, none of the current
joins is recorded: incorporate the textual correction and present the complete revised join
set again.
Ground joins in the `【Foreign keys】` section of the mschema-text
render: it lists the curated logical FKs of the workspace (e.g.
`fact_x.cod_paz=dim_patient.cod_paz`, `*_time_key=dim_time.day_key`) — prefer
those to joins you derive yourself, and flag to the reviewer any join you need
that is NOT in the list.
{{DISAMBIGUATION_SCHEMA_GROUNDING}}
{{EVIDENCE_FORMULA_PROPOSALS}}
5. Persist the **joins** (and any `concept_formulas`/`open_questions`) with the gate's
`write_schema_linking` tool — it validates the object against the `SchemaLinking`
model and writes the file deterministically (never hand-write it, never edit it
with the file tool; on a validation error the tool returns the exact problem to
fix). Shape: `{question, candidates:[...], joins:[{from, to, source?}],
excluded:[...], open_questions:[], concept_formulas:[]}` — `candidates`/`excluded`
are owned by `reviewer_schema_linking`/`sync-schema-linking` (step 2), so if you
call `write_schema_linking` after step 2, carry over its `candidates`/`excluded`
unchanged rather than overwriting them. After the reviewer-approved joins are present,
`write_schema_linking` closes F4 automatically. Do not add a `reviewer_confirm
kind:"phase"`. Do NOT run `tht session check` (that's Phase 5).
## Phase 5 — Synthesis
Prerequisite: Phase 4 closed; `schema_linking.json` present.
1. `tht session check` (objective gate: decisions present + schema_linking valid).
2. Summarize the schema-linking to the reviewer; if corrections are needed, reopen
Phase 4.
3. Close with `reviewer_confirm kind:"phase"`.
## Phase 6 — CTE plan
Prerequisite: Phase 5 closed.
1. Read `cte.md`. Decompose the rewritten question into CTEs (Agent View Generation):
each CTE captures an informative subset with a clear purpose, named in snake_case.
{{MEMORY_SOLVED_SEARCH_F6}}
2. Present the full CTE plan to the reviewer with `reviewer_confirm kind:"cte_plan"`,
passing a **structured v2 artifact** (`artifact:{kind:"cte_plan", data:{…}}`). You
author `question`, `strategy` and each `ctes[]` entry (`name`, `purpose`,
`rationale`, `depends_on`, `tables[].name`, `keys`, `filters[]` with
`column`/`op`/`value`/`rationale`, `output_columns`); the gate fills `index`
(1-based) and every `description` from the catalog, derives the ordered `--name`
list from `data.ctes[].name`, and on approval persists both `cte_plan.json` and the
chain doc (`cte_plan_doc.json`). Compact example (2 CTE):
```json
{"schema_version":2,"question":"pazienti attivi con almeno un ricovero nel 2023",
"strategy":"prima la base dei pazienti attivi, poi i loro ricoveri filtrati per anno",
"ctes":[
{"name":"base_pazienti","purpose":"pazienti attivi","rationale":"insieme di partenza",
"depends_on":[],"tables":[{"name":"dim_patient"}],"keys":["cod_paz"],
"filters":[{"column":"dim_patient.flag_attivo","op":"IS","value":"TRUE","rationale":"solo attivi"}],
"output_columns":["cod_paz"]},
{"name":"ricoveri_2023","purpose":"ricoveri dei pazienti nel 2023","rationale":"restringe al 2023",
"depends_on":["base_pazienti"],"tables":[{"name":"fact_ricoveri"}],"keys":["cod_paz"],
"filters":[{"column":"dim_time.year","op":"=","value":"2023","rationale":"finestra temporale"}],
"output_columns":["cod_paz","data_ricovero"]}
]}
```
3. For each CTE (in plan order): call `write_cte_sql` with the session id, CTE name,
and SQL block (the tool invokes `tht cte save --session <id> --name <name> --file -`).
Persist ONLY the
`WITH ... AS (...)` block, NO trailing SELECT), test with `tht cte test --session
<id> <name>` (with an **ok** outcome), then present it with
`reviewer_confirm kind:"cte_result"`. Pass ONLY the thin v2 data
`artifact:{kind:"cte_result", data:{schema_version:2, purpose?, rationale?, note?}}` —
NEVER paste SQL, columns or preview rows as text: the gate reads them
deterministically from `tht cte info` (the persisted `<name>.sql` + the last test
record) and builds the full artifact the reviewer approves. The next CTE is testable
ONLY after the previous one is approved (CLI exit 5 if out of order). Copy
table/column names EXACTLY from the schema context; use values verified with
`tht search`.
4. After the last CTE is approved, close with `reviewer_confirm kind:"phase"`.
## Phase 7 — Final SQL
Prerequisite: Phase 6 closed.
1. Read `sql-generation.md`. Recursive divide-and-conquer: the CTEs approved in
Phase 6 are the preferred building blocks (reuse them by name).
{{MEMORY_SOLVED_SEARCH_F7}}
2. Compose the final SQL (PostgreSQL dialect, exact names from the schema context).
**Output columns (F4 honoring).** The columns promoted in Phase 4's
`schema_linking.json` are the reviewer-approved OUTPUT columns: project exactly
those in the final SELECT. Other schema-linked columns remain usable as join
keys or filter predicates, but do not add them to the SELECT list.
**Time dimension:** `data_time_key` is the FK to `dim_time.day_key` (NOT declared
in the DWH, must be added by hand to the join); use `JOIN dim_time` and its
columns (`dt.year`, `dt.month`, …), NEVER arithmetic on the key.
3. `tht sql validate` + `tht sql preview` (max 10 rows). On errors / suspicious
results, apply the `sql-generation.md` checklist and correct with the reviewer.
4. Call `write_final_sql` with the session id and clean SQL; it invokes
`tht sql set-final --session <id> --file -` (ONLY clean SQL, no comments).
Approve with `reviewer_confirm kind:"sql"` (records `sql_approved`), then advance to
Phase 8 with `reviewer_confirm kind:"phase"` — `kind:"sql"` alone does NOT advance F7.
## Phase 8 — Datamart
Prerequisite: Phase 7 closed.
1. Call `reviewer_datamart` with the session id. This gate is deployment-aware and is
the ONLY allowed way to record the datamart choice:
- `THT_PROFILE=workstation`: it records `datamart_declined` automatically and shows
no question to the reviewer;
- `THT_PROFILE=server` (including the default): it always shows both choices,
"Sì, genera il datamart" and "No, salta il datamart", and records the selected one.
Never replace this gate with a hand-built `reviewer_select`.
2. On a server, if the reviewer chose yes: `tht datamart generate` (stub — raises
NotImplementedError for now). Tell
the reviewer that dbt generation is not implemented yet.
{{MEMORY_PROMOTION_F8}}
## Session end
When the promotion gate (or, as fallback, the F8 phase gate) closes Phase 8, the
gate calls `tht session finalize` automatically.
{{MEMORY_SOLVED_FINALIZATION}}
state (ledger `review_decisions.jsonl` + artifacts) is the truth: what is not
recorded did not happen.