{"slug":"agami-save-correction","title":"agami-save-correction","summary":"Saves a user correction so future queries learn from it. Always appends a (question, corrected_sql) pair to the subject area's example library under <artifacts_dir>/<profile>/prompt_examples/<area>/. Additionally, classifies the correction and — when applicable — applies a surgic","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-08-27T17:01:14.907235Z","repo":{"url":"https://github.com/AgamiAI/agami-core","stars":28,"forks":1,"license":null,"updatedAt":"2026-09-24T22:44:47Z"},"bodyHtml":"<hr>\n<h2>name: agami-save-correction\ndescription: \"Saves a user correction so future queries learn from it. Always appends a (question, corrected_sql) pair to the subject area's example library under &lt;artifacts_dir&gt;/</h2>\n<h1>agami save-correction</h1>\n<p><strong>Before suggesting any slash command in chat, read <a href=\"../../shared/invocation-conventions.md\"><code>shared/invocation-conventions.md</code></a>.</strong> Agami slash commands: <code>/agami-connect</code>, <code>/agami-query</code>, <code>/agami-model</code>, <code>/agami-save-correction</code>, <code>/agami-reconcile</code>. (<code>/agami-model</code>'s Review tab absorbed the former <code>/agami-review</code>.) Never write the un-prefixed forms (<code>/save-correction</code>, <code>/init</code>, etc.) or colon forms (<code>/agami:save-correction</code>) — those don't exist. <strong><code>/agami-init</code> was folded into <code>/agami-connect</code> Phase 0a.</strong> For chat replies, prefer natural language (\"say 'save this as a correction'\", \"say 'remember this'\") — the agami-save-correction skill's <code>when_to_use</code> matcher routes correctly.</p>\n<p>You are recording a user correction. Goal: persist the fix so similar questions get better answers next time.</p>\n<p>This skill does two things, in this order:</p>\n<ol>\n<li><strong>Always</strong>: append the <code>(question, corrected_sql)</code> pair to the subject area's example library at <code>&lt;artifacts_dir&gt;/&lt;profile&gt;/prompt_examples/&lt;area&gt;/examples.yaml</code>.</li>\n<li><strong>When applicable</strong>: surgically update the semantic model at <code>&lt;artifacts_dir&gt;/&lt;profile&gt;/</code> (a relationship/column/table edit, or a new metric) with the knowledge implied by the correction, <strong>via the curation engine</strong> (<code>semantic_model.cli curate</code>), which <strong>validates</strong> before write and reverts on failure. If the user's correction would break the model, refuse the model update (the example still gets saved).</li>\n</ol>\n<p>For the model format: <a href=\"../../../../packages/agami-core/src/semantic_model/__init__.py\"><code>semantic_model/__init__.py</code></a> (layout) + <code>packages/agami-core/src/semantic_model/models.py</code>. The curation engine is <code>packages/agami-core/src/semantic_model/curate.py</code>.\nFor SQL safety: <a href=\"../../shared/sql-generation-rules.md\"><code>shared/sql-generation-rules.md</code></a>.\nFor dialect rules: <a href=\"../../shared/dialect-rules.md\"><code>shared/dialect-rules.md</code></a>.\nFor DB error classification: <a href=\"../../shared/db_error_classifier.md\"><code>shared/db_error_classifier.md</code></a>.</p>\n<hr>\n<h2>Phase −1: Plan-mode check</h2>\n<p>Run the detection + ask logic from <a href=\"../../shared/plan-mode-check.md\"><code>shared/plan-mode-check.md</code></a>. agami-save-correction needs Write (examples + model edits) and Bash (EXPLAIN-validation) — both are blocked in plan mode.</p>\n<p><strong>If plan mode is active and the user picks <code>Stay in plan mode</code> (or this skill is invoked under an active plan-mode context):</strong> refuse and end the turn. <strong>DO NOT write a plan file. DO NOT call <code>ExitPlanMode</code>.</strong> Refusal text (verbatim):</p>\n<blockquote>\n<p>I can't save corrections in plan mode — switch to <strong>Auto</strong> or <strong>Edit Automatically</strong> mode (Shift+Tab to cycle) and re-invoke. The correction won't persist otherwise.</p>\n</blockquote>\n<p>If plan mode is not active, skip this phase silently and go to Phase 1.</p>\n<hr>\n<h2>Phase 1: Identify the correction</h2>\n<h3>1a — resolve the active profile and artifacts_dir</h3>\n<p>Resolve <code>&lt;profile&gt;</code> in this order: <code>AGAMI_PROFILE</code> env var → <code>active_profile</code> field in <code>&lt;artifacts_dir&gt;/local/.config</code> → literal string <code>\"default\"</code> (legacy fallback).</p>\n<p>Resolve <code>&lt;artifacts_dir&gt;</code> per <a href=\"../../shared/file-layout.md#configuring-artifacts_dir\"><code>shared/file-layout.md → Configuring artifacts_dir</code></a>: <code>AGAMI_ARTIFACTS_DIR</code> env var → <code>&lt;artifacts_dir&gt;/local/.config.artifacts_dir</code> → default <code>$HOME/agami-artifacts</code>. All examples / model / datasource.md paths in this skill resolve under <code>&lt;artifacts_dir&gt;/&lt;profile&gt;/</code>. USER_MEMORY.md is at <code>&lt;artifacts_dir&gt;/USER_MEMORY.md</code> (top-level, cross-database).</p>\n<p>For v1.0 / v1.1 fallback paths (<code>&lt;artifacts_dir&gt;/local/&lt;profile&gt;.yaml</code>, <code>&lt;artifacts_dir&gt;/local/&lt;profile&gt;-examples.yaml</code>, <code>&lt;artifacts_dir&gt;/&lt;profile&gt;/</code>), only read; never write. Migration is agami-connect's job — this skill assumes the user has already migrated by the time they're saving corrections.</p>\n<h3>1b — find the most recent query</h3>\n<p>Read <code>&lt;artifacts_dir&gt;/local/query_log.jsonl</code> and take the last entry <strong>whose <code>status</code> is <code>\"ok\"</code> or absent</strong>. Need <code>question</code> and <code>sql</code>.</p>\n<p>The log now records every execution, not only the successful ones: an entry with <code>\"status\": \"refused\"</code> (agami declined the statement) or <code>\"status\": \"failed\"</code> (the database rejected it) carries SQL that never returned a result, so attaching a correction to it would teach the example library from a query that never ran. Entries written before the log carried a <code>status</code> field have none — treat those as <code>ok</code>.</p>\n<p>If there is no such entry: \"I don't have a recent successful query to attach this correction to. Ask the question first, then save the correction.\" Stop.</p>\n<h3>1c — get the corrected SQL</h3>\n<p>Determine what the user gave:</p>\n<ul>\n<li><strong>They pasted SQL</strong> (<code>$ARGUMENTS</code> looks like a SELECT, contains <code>FROM</code> / <code>JOIN</code> / <code>GROUP BY</code>) → use directly as the corrected SQL.</li>\n<li><strong>They described what's wrong</strong> (\"the join should be on customer_id, not user_id\"; \"amount is in cents\") → regenerate SQL using the semantic model + the original question + their feedback as additional context. Same prompt assembly as <code>query-database</code> Phase 2b.</li>\n<li><strong>No arguments and no recent feedback</strong> → ask: \"Paste the corrected SQL, or tell me what's wrong with the result.\"</li>\n</ul>\n<h3>1d — EXPLAIN-validate the corrected SQL</h3>\n<p>Run <code>EXPLAIN &lt;sql&gt;</code> (or <code>EXPLAIN QUERY PLAN &lt;sql&gt;</code> for SQLite) via the cached database tool from <code>&lt;artifacts_dir&gt;/local/.config</code>. Same validate-then-save contract as <code>agami-connect/SKILL.md</code> Phase 5b:</p>\n<ul>\n<li>EXPLAIN succeeds → continue.</li>\n<li>EXPLAIN fails → route through <a href=\"../../shared/db_error_classifier.md\"><code>shared/db_error_classifier.md</code></a>. Surface the one-line remediation. Do <strong>not</strong> save anything. Ask the user to fix the SQL and try again.</li>\n</ul>\n<p>Apply <a href=\"../../shared/sql-generation-rules.md\"><code>shared/sql-generation-rules.md</code></a>:</p>\n<ul>\n<li>Refuse DDL/DML (DROP, DELETE, INSERT, UPDATE, ALTER, etc.).</li>\n<li>Refuse system-table queries unless the user explicitly asked about schema metadata.</li>\n</ul>\n<hr>\n<h2>Phase 2: Always append to the examples library</h2>\n<p>Examples live per subject area at <code>&lt;artifacts_dir&gt;/&lt;profile&gt;/prompt_examples/&lt;area&gt;/examples.yaml</code>. Pick <code>&lt;area&gt;</code> = the subject area whose <code>tables_defined</code> includes the table(s) the corrected SQL references (run <code>bash \"$AGAMI_PLUGIN_ROOT/scripts/sm\" areas \"$ROOT\"</code> and match, or <code>get_table_context</code> to confirm membership; if the SQL spans areas, use the area of the primary/driving table).</p>\n<p><strong>Use the packaged writer — don't Read/Edit the YAML by hand or grep the source for its schema.</strong> It creates the file if absent, appends, and <strong>dedups by <code>question</code></strong> (a same-question correction replaces the earlier answer). One entry in a JSON array:</p>\n<pre><code>bash \"$AGAMI_PLUGIN_ROOT/scripts/sm\" add-example \"$ROOT\" --area &lt;area&gt; --file /tmp/agami-correction-example.json\n</code></pre>\n<pre><code>[{\"question\": \"&lt;original NL question from query_log.jsonl&gt;\", \"sql\": \"&lt;corrected SQL&gt;\",\n  \"tables\": [\"...\"], \"source\": \"correction\", \"status\": \"confirmed\", \"created_at\": \"&lt;ISO8601 UTC&gt;\"}]\n</code></pre>\n<p>Required: <code>question</code>, <code>sql</code>. Optional scope tags (improve ranking): <code>tables</code>, <code>columns</code>, <code>metric</code>.</p>\n<p>This phase is <strong>non-conditional</strong> — every correction always lands in the examples library, even if Phase 5 (model update) declines or fails.</p>\n<p>Surface: <code>✓ Correction appended to &lt;artifacts_dir&gt;/&lt;profile&gt;/prompt_examples/&lt;area&gt;/examples.yaml.</code></p>\n<hr>\n<h2>Phase 3: Classify the correction</h2>\n<p>Compare the original SQL (from <code>query_log.jsonl</code>) to the corrected SQL. Identify what the user was teaching us, then route the correction to the right destination. <strong>Mis-routing is the most common failure</strong> — early-adopter feedback included three real cases where corrections landed in the wrong file:</p>\n<table>\n<thead>\n<tr>\n<th>Category</th>\n<th>What the user did (wrong)</th>\n<th>What should have happened</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Per-example commentary (\"order total can be negative on #12\")</td>\n<td>Captured in <code>examples.yaml</code> <code>notes[]</code></td>\n<td>Field description on <code>ORDERS.TOTAL</code> (<code>field_metadata</code>)</td>\n</tr>\n<tr>\n<td>Status normalization (active / ACTIVE / 1 → \"Active\")</td>\n<td><code>datasource.md</code></td>\n<td><code>agami.choice_field</code> on <code>CUSTOMERS.STATUS</code> (<code>field_metadata</code>)</td>\n</tr>\n<tr>\n<td>\"Format counts with commas in outputs\"</td>\n<td>Captured in a markdown file as prose</td>\n<td><code>user_preference</code> in <code>USER_MEMORY.md</code> AND <code>TO_CHAR(…)</code> / aliases IN the seed example's SQL</td>\n</tr>\n</tbody>\n</table>\n<p>The decision tree below corrects these failures. <strong>Walk it top to bottom — first match wins.</strong> Don't fall back to <code>org_context</code> as a default; it's the catch-all that produces the wrong outcome in practice.</p>\n<h3>Decision tree (top to bottom — first match wins)</h3>\n<pre><code>Is the correction about a single column's MEANING, UNIT, ENCODING, or VALUE NORMALIZATION?\n   (e.g., \"amount is in cents\", \"1 means active not true\", \"the order total can be\n    negative for refunds\", \"active/ACTIVE/1 all map to 'Active'\", \"STATUS='1' means active\")\n   → field_metadata\n       → if it's specifically about value → canonical-display mapping, write to\n         agami.choice_field on that field.\n       → if it's about how the column is interpreted (unit, sign convention,\n         encoding), update agami.unit + the field description prose.\n\nElse: is the correction about a JOIN — which columns connect two tables?\n   (e.g., \"join on customer_id, not user_id\"; \"products → categories via category_id\")\n   → relationship\n\nElse: is the correction about what a WHOLE TABLE represents, not one column?\n   (e.g., \"`orders` includes cancelled rows too\", \"`metrics_daily` is materialized\n    from `events`, use it for date ranges &gt; 7 days\")\n   → table_metadata\n\nElse: does the corrected SQL define a REUSABLE AGGREGATION that didn't exist?\n   (e.g., \"MRR = SUM(price) WHERE plan_type='subscription'\", \"active customers\n    means is_active AND last_login &gt; 30 days ago\")\n   → new_metric\n       → if also touches the predicate side (active customers), consider whether\n         the predicate alone deserves a named_filter — same Rule 1 sign-off.\n\nElse: is the correction a DISPLAY / FORMATTING / DEFAULT-FILTER preference?\n   **Classify it like everything else — don't reflexively ask. Route to the MOST\n   SPECIFIC home, structured-model-first:**\n   - It's the **currency/unit** of specific column(s) — \"amounts are in INR → show ₹\",\n     \"this is a percentage\", \"values are in days\" → set that column's **`unit`** field\n     (the ISO currency code, or `percent`/`cents`/`days`) via `cli curate`\n     (`{op:edit, kind:table, area, name:&lt;table&gt;, column:&lt;col&gt;, field:unit, value:\"INR\"}`).\n     The runtime + chart renderer format it **deterministically** (`units.py`). This IS\n     the org-wide home — it's in the shared model.\n   - Another column fact — \"amount is in cents\" (a scale fix), \"this code maps to\n     &lt;label&gt;\" → `field_metadata`: `value_transform` (`amount/100.0`) or `choice_field`\n     on that column. Also in the shared model, org-wide.\n   - It's a default filter on a table — \"exclude soft-deleted\", \"tenancy filter\" →\n     the table's `default_filters` (model), via `cli curate`.\n   - It's a cross-cutting presentation convention not tied to one column — \"present\n     money with lakh/crore grouping\" → `user_preference` → `USER_MEMORY.md` (it's a\n     presentation rule, not domain meaning; datasource.md is narrative-only now).\n   - It's a personal stylistic tic that would hold on ANY database — \"I like top-10\n     not top-5\", \"my date format\" → `user_preference` → `USER_MEMORY.md`.\n   Only when you genuinely can't tell personal vs org-wide → **AskUserQuestion** (the\n   ambiguity fallback below). Default a currency/unit/data fact to the **model**, not a\n   prose file.\n   → In all cases, if it changes how SQL renders results (TO_CHAR, ROUND, a symbol),\n     ALSO bake it into the affected seed example's SQL so future answers apply it.\n\nElse: is the correction about a BUSINESS TERM specific to this database's domain?\n   (e.g., \"gold tier means lifetime spend &gt; $10k\" — used as a category in many\n    queries; \"MRR\" — the abstract concept; \"we don't track refunds, those live\n    in Stripe\" — what the data fundamentally doesn't include)\n   → org_context. A term → `cli set-terminology` (the structured `key_terminology`\n     glossary). A higher-level narrative (\"we don't track refunds…\", \"who the users\n     are\") → a datasource.md prose line. See the `org_context` edit section for both.\n       → org_context is for ABSTRACT business concepts not tied to one specific\n         column. A correction tied to a specific column belongs in field_metadata,\n         NOT here. Re-check the first rule of the tree before landing here.\n\nElse: pure SQL syntax / typo with no domain knowledge implied\n   (e.g., \"missed the GROUP BY\", \"`customer_idx` is a typo of `customer_id`\")\n   → sql_fix\n</code></pre>\n<h3>Anti-patterns the LLM keeps producing (do NOT do these)</h3>\n<ol>\n<li><strong>Per-column rule → datasource.md.</strong> \"<code>CUSTOMERS.STATUS</code> values normalize to Active\" is NOT domain context — it's a column-value mapping. Route to <code>field_metadata</code> (<code>choice_field</code>).</li>\n<li><strong>Per-column rule → examples.yaml notes.</strong> This skill never writes to <code>examples.yaml.notes[]</code> (that path lives in agami-connect Phase 6d). If you find yourself wanting to write \"the order total can be negative\" as a note on example #12, route it to <code>field_metadata</code> on the actual column instead — the lesson applies to every future query, not just to one example.</li>\n<li><strong>Dumping a column-fact into a prose file (or USER_MEMORY).</strong> \"Amounts are in INR → show ₹\" is a fact about the <code>amount</code> column → a <code>caveat</code>/<code>value_transform</code> on that column (org-wide, structured, in the shared model) — NOT a USER_MEMORY line and NOT a datasource.md prose rule. Route data-facts to the column/table; reserve the prose files for cross-cutting conventions (datasource.md) and personal tics (USER_MEMORY). Don't reflexively ask — classify; ask only when personal-vs-org is genuinely unclear.</li>\n<li><strong>Display preference → prose without changing SQL.</strong> If the correction is \"always format like X,\" ALSO modify the seed example's SQL to demonstrate the formatting (so future answers actually apply it, not just describe it).</li>\n</ol>\n<h3>Diff-based hints (look at SQL changes for classification clues)</h3>\n<ul>\n<li>JOIN condition changed → likely <code>relationship</code>.</li>\n<li>Math applied to one column (<code>/100</code>, <code>* 100.0</code>, <code>CAST(...)</code>) → likely <code>field_metadata</code> (with a <code>unit</code> correction).</li>\n<li><code>CASE WHEN col = 'X' THEN 'Y' ELSE 'Z' END</code> for a column's display value → likely <code>field_metadata</code> with <code>choice_field</code> update.</li>\n<li>New WHERE clause referencing a specific business term (e.g., <code>plan_type='subscription'</code>) AND new aggregation → likely <code>new_metric</code>.</li>\n<li><code>TO_CHAR(...)</code>, <code>ROUND(...)</code>, <code>AS my_alias</code>, a currency symbol purely on the output side → a display rule. If it's about a specific column (a currency/unit) → that column's <code>caveat</code>/<code>value_transform</code> (model, org-wide). If it's a personal style tic → <code>user_preference</code>. ALSO update the seed example's SQL either way.</li>\n<li>Only structural / cosmetic SQL changes → <code>sql_fix</code>.</li>\n</ul>\n<h3>When ambiguous, AskUserQuestion (use the rubric above as your option set)</h3>\n<blockquote>\n<p>What kind of correction is this?</p>\n<ul>\n<li><strong>A SQL fix</strong> — the answer was wrong but the model is fine</li>\n<li><strong>A column meaning change</strong> — e.g., amount is in cents, status means something specific, Male/MALE/T all map to \"Male\"</li>\n<li><strong>A join correction</strong> — relationships in the model need updating</li>\n<li><strong>A table meaning change</strong> — the description / context for a whole table</li>\n<li><strong>A new business metric</strong> — let's add this as a reusable metric</li>\n<li><strong>A display / formatting rule</strong> — number formatting, currency/units, default filters (currency/units attach to the column; I only ask about scope if it's genuinely unclear)</li>\n<li><strong>Domain context for this database</strong> — abstract business concepts not tied to one column (e.g., \"gold tier means lifetime spend &gt; $10k\")</li>\n</ul>\n</blockquote>\n<p>The user's answer determines Phase 5 routing.</p>\n<p><strong>Distinguishing <code>org_context</code> vs <code>user_preference</code> vs <code>field_metadata</code>:</strong></p>\n<ul>\n<li>If the rule is tied to a specific column → <code>field_metadata</code>. <strong>Always check this first.</strong></li>\n<li>Else ask: \"would this guidance apply if I connected to a different database?\" If yes → <code>user_preference</code>. If no (it's specific to this domain) → <code>org_context</code>.</li>\n</ul>\n<h3>Phase 4 — surface classification + destination BEFORE Phase 5 writes anything</h3>\n<p>After classifying, <strong>always</strong> surface the decision to the user in chat as a one-line summary with explicit reasoning, then proceed with the edit. The contract:</p>\n<pre><code>Classification: &lt;kind&gt;\n  → routing to: &lt;destination file + the specific field/section that'll change&gt;\n  → reasoning: &lt;one sentence explaining which rule of the decision tree matched&gt;\n</code></pre>\n<p>Concrete examples:</p>\n<pre><code>Classification: field_metadata\n  → routing to: sales/ORDERS.yaml → fields[\"TOTAL\"].description\n  → reasoning: rule says \"correction about a single column's meaning/encoding/sign convention\" — you're teaching that this column can be negative because refunds carry a negative sign.\n\nClassification: field_metadata\n  → routing to: sales/CUSTOMERS.yaml → fields[\"STATUS\"].agami.choice_field\n  → reasoning: rule says \"value normalization mapping (active/ACTIVE/1 → 'Active') belongs in choice_field\" — not datasource.md.\n\nClassification: user_preference + seed-example update\n  → routing to: USER_MEMORY.md (the prose preference) AND examples.yaml example #N (TO_CHAR in SQL)\n  → reasoning: \"always format counts with commas\" is a display preference (cross-DB), and the seed example needs the formatting baked into its SQL so future answers actually apply it.\n</code></pre>\n<p>The user can override before any file is written. If they say \"no, that belongs in X instead,\" re-route to X and re-surface the new classification before proceeding.</p>\n<hr>\n<h2>Phase 5: Apply surgical model edits (when applicable)</h2>\n<p>If the correction kind is <code>sql_fix</code>: <strong>stop here</strong>. Phase 2 already saved the example. Surface the closing message and skip to Phase 6.</p>\n<p>For every other kind, you propose a model edit and run the validator BEFORE writing.</p>\n<h3>4a — propose the edit</h3>\n<p>Model edits go through the curation engine (<code>semantic_model.cli curate \"$ROOT\" --ops-file …</code>), which validates + commits + reverts on failure — you don't stage/validate/promote by hand. <code>ROOT=\"&lt;artifacts_dir&gt;/&lt;profile&gt;\"</code>. Resolve the subject <code>&lt;area&gt;</code> for an affected table the same way as Phase 2 (the area whose <code>tables_defined</code> holds it). The new-metric case uses <code>cli add --kind metric</code> (curate's <code>--ops-file</code> edits existing entries; <code>add</code> creates them) — same validate + commit + revert guarantees.</p>\n<p><strong>Fixing a column/table <code>description</code> marks it human-validated.</strong> A correction that rewrites a <code>description</code> via a curate <code>edit</code> op (no <code>source:\"ai\"</code>) automatically sets <code>description_source: \"human\"</code> — so a description agami had inferred is now trusted and stops surfacing as an \"assumption\" in answer receipts. You don't set <code>description_source</code> yourself; the curate engine does it.</p>\n<table>\n<thead>\n<tr>\n<th>Edit kind</th>\n<th>How</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>relationship</code></td>\n<td><code>cli curate</code> <code>edit</code> op(s) on the relationship in <code>&lt;area&gt;</code></td>\n</tr>\n<tr>\n<td><code>field_metadata</code></td>\n<td><code>cli curate</code> <code>edit</code> op(s) on the column (kind: table, + <code>column</code>)</td>\n</tr>\n<tr>\n<td><code>table_metadata</code></td>\n<td><code>cli curate</code> <code>edit</code> op(s) on the table</td>\n</tr>\n<tr>\n<td><code>new_metric</code></td>\n<td>Write a new <code>subject_areas/&lt;area&gt;/metrics/&lt;name&gt;.yaml</code>, then <code>cli validate \"$ROOT\"</code></td>\n</tr>\n<tr>\n<td><code>org_context</code></td>\n<td>a term → <code>cli set-terminology</code> (structured <code>key_terminology</code>, validated); a narrative line → append to <code>datasource.md</code> (no validator)</td>\n</tr>\n<tr>\n<td><code>user_preference</code></td>\n<td>append to <code>USER_MEMORY.md</code> (no validator)</td>\n</tr>\n</tbody>\n</table>\n<h4><code>relationship</code> edit</h4>\n<p>Find the relationship in <code>&lt;area&gt;</code>'s <code>relationships.yaml</code> (by <code>from_table</code>/<code>to_table</code>). The corrected SQL's <code>ON</code> clause tells you the fix:</p>\n<ul>\n<li><strong>Different columns</strong> → <code>edit</code> op setting <code>from_column</code> / <code>to_column</code> (or, for a CAST/compound join, set <code>on:</code> and null out <code>from_column</code>/<code>to_column</code> — the \"approve with fix\" shape).</li>\n<li><strong>Missing relationship</strong> → write a new entry into <code>relationships.yaml</code> (unique by from/to), with the <strong>required</strong> <code>relationship:</code> cardinality inferred from the join (many_to_one unless the keys are both unique), then <code>cli validate</code>.</li>\n<li><strong>Spans two areas</strong> → it's a <code>cross_subject_area_relationship</code> (org-level <code>cross_subject_area_relationships.yaml</code>), with <code>from_subject_area</code>/<code>to_subject_area</code> + <code>executable</code>.</li>\n<li><strong>Reverses direction</strong> → ask before flipping. Never delete a relationship unless the user explicitly says \"remove the relationship\".</li>\n</ul>\n<h4><code>table_metadata</code> edit</h4>\n<p><code>edit</code> op(s) on the table: set <code>description</code> (what the table represents) or append a <code>caveats[]</code> entry (a usage note / quirk). Never change <code>grain</code>, <code>columns</code>, <code>source_type</code>, or relationships from a table-metadata correction — those are structural.</p>\n<h4><code>field_metadata</code> edit</h4>\n<p><code>edit</code> op(s) on the column (<code>kind: table</code>, <code>area</code>, <code>name: &lt;table&gt;</code>, <code>column: &lt;col&gt;</code>):</p>\n<ul>\n<li>Set <code>description</code> (\"Order amount in cents.\").</li>\n<li>A unit / data-quality note → append a <code>caveats[]</code> entry (\"Amounts in cents; divide by 100 for dollars.\").</li>\n<li>An enum mapping (\"status <code>1</code> = active, <code>0</code> = inactive\") → set <code>choice_field</code>.</li>\n<li>A cleaning rule (\"strip the brackets\") → set <code>value_transform</code> (must parse as SQL — the validator checks).\nNever set a column's structural identity from a field-metadata correction.</li>\n</ul>\n<h4><code>new_metric</code> edit</h4>\n<p>Write <code>subject_areas/&lt;area&gt;/metrics/&lt;name&gt;.yaml</code> (snake_case name, unique in the area):</p>\n<pre><code>name: &lt;derived_from_user_request&gt;\ncalculation: &lt;one-sentence prose intent — REQUIRED, never empty&gt;\nbindings:\n  &lt;storage_type&gt;: &lt;the aggregation SQL from the corrected SQL&gt;\nsource_tables: [&lt;tables it reads&gt;]\nother_names: [&lt;the user's term + obvious variants&gt;]\nconfidence: proposed\nreview_state: unreviewed\n</code></pre>\n<p>Reference columns plainly (<code>&lt;table&gt;.&lt;column&gt;</code>). Strip user-specific WHERE filters (<code>WHERE customer_id = 42</code>); keep only definitional ones (<code>WHERE plan='subscription'</code>). Then <code>cli validate \"$ROOT\"</code>. A <code>proposed</code>/<code>unreviewed</code> metric needs sign-off on the <code>/agami-model</code> Review tab before the runtime will use it (Rule 1) — tell the user.</p>\n<h4><code>user_preference</code> edit</h4>\n<p>A <code>user_preference</code> correction does NOT touch the semantic model. It lands in <code>&lt;artifacts_dir&gt;/USER_MEMORY.md</code> (per <a href=\"../../shared/user-memory-format.md\"><code>shared/user-memory-format.md</code></a>) — the <strong>global</strong> preferences file that applies across every database. Steps:</p>\n<ol>\n<li><strong>Read</strong> <code>&lt;artifacts_dir&gt;/USER_MEMORY.md</code> (it exists — <code>init</code> seeds it).</li>\n<li><strong>Pick the right section</strong> (<code>Default filters</code>, <code>Naming and synonyms</code>, <code>Display preferences</code>, or <code>Avoid</code>) based on the policy's nature. Add a new section if none of the four fits — keep this rare.</li>\n<li><strong>Append the new bullet</strong> under that section, in plain English (the user's wording, lightly cleaned). Don't paraphrase aggressively — preserve their voice.</li>\n<li><strong>Show the user the diff</strong> (per Phase 4b below) before writing.</li>\n<li><strong>Strip nothing</strong> — USER_MEMORY.md is intentionally free-form, not schema-validated. The validator (Phase 4c) is a no-op for <code>user_preference</code> corrections; the semantic model is unchanged.</li>\n</ol>\n<p>The user's bullet should be self-contained — anyone reading USER_MEMORY.md should understand the policy without seeing the original conversation.</p>\n<h4><code>org_context</code> edit</h4>\n<p><code>org_context</code> splits by <strong>what kind of fact it is</strong> — each goes to its proper home (per <a href=\"../../shared/organization-context-format.md\"><code>shared/organization-context-format.md</code></a>). The two homes are deliberately separate; never write one kind into the other.</p>\n<ul>\n<li><strong>A term's meaning</strong> (the common case) — \"gold tier\" = lifetime spend &gt; $10k, \"MRR\" = monthly recurring revenue, or an acronym → its expansion. This goes to the <strong>structured glossary</strong>, NOT a prose file — <code>set-terminology</code> merges it onto <code>key_terminology</code> (validated, committed), and it then surfaces in the derived domain context on every query automatically (no file to re-render, nothing for a human to clobber):\n<pre><code>printf '{\"gold tier\": \"lifetime spend &gt; $10k\"}' &gt; /tmp/agami-term.json\nbash \"$AGAMI_PLUGIN_ROOT/scripts/sm\" set-terminology \"$ROOT\" --file /tmp/agami-term.json\n</code></pre>\nThe key is the term; the value is a <strong>self-contained</strong> definition (understandable without the original conversation). It merges by default — existing terms are never lost. <strong>Never</strong> hand-append <code>- \"term\" = definition</code> lines to datasource.md; that's the old prose home and is wrong now.</li>\n<li><strong>A higher-level narrative</strong> — what the data represents, who the users are, what's <em>not</em> in this database. Append a sentence/paragraph to <code>&lt;artifacts_dir&gt;/&lt;profile&gt;/datasource.md</code> under <code># About this database</code> (create it with the starter if missing — <code>cli org-draft \"$ROOT\" &gt; \"$ROOT/datasource.md\"</code>). This file is the human narrative <strong>only</strong> — no <code>term = definition</code> lines, no model facts.</li>\n<li><strong>A cross-cutting display/formatting convention</strong> (a currency symbol or number grouping everyone querying this DB should see) is a <em>presentation</em> preference, not domain meaning: route it to <code>user_preference</code> → <code>USER_MEMORY.md</code>, or — when it's really a fact about one column (units/currency) — to that column's <code>caveat</code>/<code>value_transform</code>. Do <strong>not</strong> invent a datasource.md \"conventions\" heading; the file is narrative-only.</li>\n</ul>\n<p><strong>Show the user the diff</strong> (Phase 4b) before writing. <code>set-terminology</code> is validated (reverts on failure); datasource.md prose is free-form (no validation).</p>\n<h4><code>mixed</code> edit</h4>\n<p>Apply each individual edit as above. Show the user the combined diff in 4b before validating. If the mix includes a <code>user_preference</code> or <code>org_context</code>, those parts skip the validator (USER_MEMORY.md / datasource.md aren't validated); the semantic-model parts still go through the validator.</p>\n<h3>4b — show the diff to the user, get approval</h3>\n<p>Build a unified diff (or a compact \"before / after\" summary) of the proposed change against the existing target file. Name the file in the prompt so the user knows what they're approving. Show via AskUserQuestion:</p>\n<blockquote>\n<p>I want to update the target file in `&lt;artifacts_dir&gt;/</p>\n<pre><code>[Relationship] orders_to_customers\n- from_columns: [user_id]\n+ from_columns: [customer_id]\n</code></pre>\n<p>Approve?</p>\n<ul>\n<li><strong>Yes (Recommended)</strong> — apply and validate</li>\n<li><strong>No</strong> — leave the model as-is, the example is still saved</li>\n<li><strong>Edit first</strong> — let me tweak before applying</li>\n</ul>\n</blockquote>\n<p>For <code>org_context</code> / <code>user_preference</code> the file is <code>datasource.md</code> / <code>USER_MEMORY.md</code> instead of a schema yaml — same prompt shape, just a different filename.</p>\n<p>Always include the validator step in 4c regardless of which option they pick (since \"Yes\" still has to validate, except for datasource.md / USER_MEMORY.md which aren't validated).</p>\n<h3>4c — apply with validation (the gate)</h3>\n<p><strong>For <code>relationship</code> / <code>field_metadata</code> / <code>table_metadata</code> edits</strong> (existing entries): build the ops array, <strong>write it with the Write tool</strong> (never a heredoc / shell variable / <code>python3 -c</code> — JSON quotes and <code>null</code> break those), then apply via the curation engine — it validates the whole model, commits to the profile git repo, logs to <code>curation_log.jsonl</code>, and <strong>reverts every change if validation fails</strong>:</p>\n<pre><code>bash \"$AGAMI_PLUGIN_ROOT/scripts/sm\" curate \"$ROOT\" --ops-file /tmp/agami-correction-ops.json\n</code></pre>\n<p>Stdout: <code>{applied, skipped, errors, validated, committed}</code>.</p>\n<ul>\n<li><code>validated: true</code> → surface <code>✓ Model updated and validated.</code></li>\n<li><code>validated: false</code> → the engine already reverted; surface <code>errors</code> verbatim: \"Your correction would break the model — here's what's wrong: …. The example is saved either way; the model wasn't updated.\"</li>\n</ul>\n<p><strong>For <code>new_metric</code></strong> (creating an entry): use the packaged <code>add</code> command — don't hand-write the YAML. It validates the item + the whole tree, writes <code>subject_areas/&lt;area&gt;/metrics/&lt;slug&gt;.yaml</code>, reverts on failure, and commits. Put the one metric in a JSON array:</p>\n<pre><code>bash \"$AGAMI_PLUGIN_ROOT/scripts/sm\" add \"$ROOT\" --kind metric --area &lt;area&gt; --file /tmp/agami-new-metric.json\n</code></pre>\n<p>Same <code>{applied, skipped, errors, validated, committed}</code> contract as above: <code>validated: false</code> → it already reverted, surface <code>errors</code>; <code>skipped</code> → the metric was structurally invalid (e.g. missing <code>calculation</code>), surface the reason.</p>\n<p>There is no override path — a model that fails validation is never persisted; <code>&lt;artifacts_dir&gt;/&lt;profile&gt;/</code> is left unchanged. The example library still got the correction (Phase 2 already happened).</p>\n<p>For <code>org_context</code> / <code>user_preference</code> corrections (datasource.md / USER_MEMORY.md only): no validator step. Write the file directly with <code>chmod 600</code>.</p>\n<h3>4d — confirmation</h3>\n<pre><code>✓ Correction appended to &lt;artifacts_dir&gt;/&lt;profile&gt;/prompt_examples/&lt;area&gt;/examples.yaml\n✓ Model updated in &lt;artifacts_dir&gt;/&lt;profile&gt;/subject_areas/&lt;area&gt;/:\n    - relationship orders_to_customers from_columns: [user_id] → [customer_id]\n✓ Validator passed.\n\nNext time someone asks \"&lt;question&gt;\" or anything similar, I'll use the corrected SQL AND know the right join is on customer_id.\n</code></pre>\n<hr>\n<p>(Phase 6 — telemetry emission on correction — has been removed in the current 0.x line. The skill no longer reads <code>analytics_consent</code> and no longer appends to <code>.telemetry-queue.jsonl</code>. agami has no telemetry — see <code>docs/privacy.md</code>.)</p>\n<hr>\n<h2>Edge cases</h2>\n<ul>\n<li><strong>Empty examples file</strong> — initialize it with the new entry as the only one.</li>\n<li><strong><code>examples.yaml</code> missing</strong> — invoke <code>agami-connect</code> first to seed, then append.</li>\n<li><strong>User pastes SQL referencing tables not in the model</strong> — EXPLAIN-validate catches it (<code>table_not_found</code>); surface the remediation, don't save.</li>\n<li><strong>User saves a duplicate of an existing seed</strong> — replace the seed (<code>source: correction</code>, fresh <code>created_at</code>).</li>\n<li><strong>Most-recent query is itself a correction</strong> — that's fine, attach to it.</li>\n<li><strong>Validator fails on a model edit but the user really wants it saved</strong> — they can hand-edit <code>&lt;artifacts_dir&gt;/&lt;profile&gt;/&lt;schema&gt;.yaml</code> directly and the next <code>query-database</code> will (try to) read it. The validator runs again from <code>connect verify</code> if they want to confirm. There is no \"skip validation\" path from this skill.</li>\n<li><strong>User says \"actually undo my last correction\"</strong> — they hand-edit the YAML / Markdown files; this skill doesn't track an undo log in v1.</li>\n<li><strong>Edit affects datasets in two different schemas</strong> — split into two separate edits, one per target file. The validator runs once per write (or once for the merged directory).</li>\n</ul>\n<hr>\n<h2>Hard rules</h2>\n<ol>\n<li><strong>Phase 2 (examples append) always runs.</strong> Even if the user later changes their mind on the model edit, the example is already saved.</li>\n<li><strong>Phase 5 model writes are gated by the validator.</strong> <code>cli curate</code> (edits existing entries) and <code>cli add</code> (creates a new metric/entity) are the only ways to write inside <code>&lt;artifacts_dir&gt;/&lt;profile&gt;/</code>, and both refuse / revert on a validation failure. No exceptions. datasource.md and USER_MEMORY.md edits skip the validator (free-form Markdown).</li>\n<li><strong>Edits stay valid against the model.</strong> Don't invent fields — the Pydantic models (<code>packages/agami-core/src/semantic_model/models.py</code>) forbid unknown keys, so an invalid edit is rejected by the validator. When you can't express a correction within the model shape, fall back to <code>sql_fix</code> (example only) and tell the user \"I can save this as a few-shot example but it doesn't fit a model edit.\"</li>\n<li><strong>Show the diff before mutating the model.</strong> The user always gets to see and approve the proposed change.</li>\n</ol>\n","files":[{"path":"SKILL.md","sizeBytes":32733,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"clean","suspicious":0,"notes":0,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-09-17T16:01:17.583034Z","sha256":"9C19599C675C7C3A85852545D69C1F5E46D7A044D45F7CDA79B59D983293B366","sizeBytes":12380},"review":null,"source":{"repositoryUrl":"https://github.com/AgamiAI/agami-core","path":"plugins/agami/skills/agami-save-correction","license":null,"commit":"6fbffd4102c7789593604e67da14871ee792047d","subtreeSha":"40AA763037F5A2105D0D0138A1BBC4324EB7E86EE0DECE507F7E96F6E2390922","lastSyncedAt":"2026-09-25T07:37:23.183057Z"},"reviewedAt":"2026-09-17T16:04:14.227931Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/AgamiAI/agami-core/tree/main/plugins/agami/skills/agami-save-correction"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install agamiai-agami-core@llmmart"},{"target":"git","command":"git clone https://github.com/AgamiAI/agami-core.git"}]}