| name | ztn:resolve-clarifications |
| description | Interactive facilitator for the owner's CLARIFICATIONS queue. Pre-syncs via /ztn:sync-data so the queue reflects state from all devices, then loads open items, clusters by Type, presents one theme at a time as a numbered batch (≤5 items, adaptive — 3 for heavy types, 5 for light), reminds full situation + verbatim quotes already stored in the file, pre-checks values-bearing items against the constitution, proposes resolution with labelled options, applies confirmed actions (silent for archival ops, diff-first for content edits), archives resolved items and re-prioritises deferred ones. After resolutions, refreshes derived views (/ztn:regen-constitution if principle accepts; /ztn:maintain if registries / hubs touched) and reminds owner to run /ztn:save when the working tree is dirty. Manual, owner-driven — never dumps the whole queue, never asks the owner to recall meeting context unaided.
|
| disable-model-invocation | false |
/ztn:resolve-clarifications — Interactive Clarifications Facilitator
The owner's queue at _system/state/CLARIFICATIONS.md accumulates
ambiguities surfaced by producer skills. Reviewing it as a flat list is
high-friction: the owner has no fresh context for the underlying meeting,
items mix trivial (people-bare-name) with values-loaded (org-tension,
principle-candidate), and resolution mechanics differ per Type.
This skill turns review into a guided conversation: one theme per round,
numbered questions, full context reminded inline, decisions applied
mechanically, queue cleaned after.
Documentation convention: при любых edits этого SKILL соблюдай
_system/docs/CONVENTIONS.md — файл описывает current behavior без
version/phase/rename-history narratives.
Arguments
$ARGUMENTS supports:
--theme <type> — pick a specific Type cluster directly, skip the menu
--max <N> — override adaptive batch size (default: 3 heavy / 5 light)
--dry-run — render the round but do not apply any resolution
--no-constitution — skip constitution lookup even for values-bearing items
--continue — after closing one round, immediately offer the next theme
--no-sync — skip Step 0 pre-sync (offline review, known-current state)
--no-refresh — skip Step 9 post-resolution refresh (/ztn:regen-constitution,
/ztn:maintain); owner promises to run them manually
--no-save — skip the Step 9.5 save reminder (commit later by hand)
--auto-mode — non-interactive entry point dispatched by
/ztn:lint Step 7.5 inline (the lint nightly tick is the timer;
resolve is the engine). Runs Step A (lens hint ingestion + smart
curation + auto-resolve sweep) and exits silently. Skips Step 0
pre-sync (the dispatching scheduler tick already synced), skips
theme menu / round / save reminder. Residue clarifications stay
queued for owner; auto-applied actions write to the session log
under _system/state/resolve-sessions/. The most quality-sensitive
isolation — agent-lens vs resolve — IS preserved at the scheduler
level: lens runs are a separate scheduler tick, so the agent that
judges proposals in A.2/A.3 has not just produced lens body output.
Step 0: Pre-sync
Skip entirely under --auto-mode. The dispatching nightly chain
(scheduler → /ztn:sync-data → /ztn:lint → resolve) already synced
as its first step, AND lint has by now written invariant-scan
autofixes that leave the working tree dirty — re-running sync inside
auto-mode would either redundantly walk a clean tree or, more likely,
abort on the dirty tree and break the nightly chain. The auto-mode
caller (lint) owns sync; resolve trusts it.
Under interactive mode (/ztn:resolve-clarifications invoked by
owner), invoke /ztn:sync-data inline. Owner may be working from a
multi-device setup (phone captures, server processing, laptop A vs
laptop B); reviewing a stale CLARIFICATIONS queue wastes the owner's
attention on items already resolved elsewhere.
Behaviour:
- Up-to-date or no
origin → continue silently to the lock step.
- Pulled commits → report one-line recap («синхронизировался: pulled
N commits, K new clarifications») then continue.
- Working tree dirty →
/ztn:sync-data aborts with its own message;
surface it verbatim to the owner and exit (owner runs /ztn:save
first, then re-invokes this skill).
- Conflict during rebase →
/ztn:sync-data aborts and prints recovery
instructions; surface verbatim and exit.
- Unsupported in
--dry-run mode → still run sync (read-mostly; only
effect is moving local HEAD), since stale data poisons the dry-run
preview too.
Skip the sync only when --no-sync is passed (escape hatch for offline
review or known-current state). The skill never auto-syncs after Step 0
— the owner's session works against the snapshot taken here.
Concurrency Lock
This skill writes to CLARIFICATIONS.md and may edit profiles / records.
Producer skills (/ztn:process, /ztn:lint, /ztn:maintain) write to
the same files. Mutual exclusion required.
Read all four before starting:
_sources/.processing.lock — abort «/ztn:process running»
_sources/.maintain.lock — abort «/ztn:maintain running»
_sources/.lint.lock — abort «/ztn:lint running» (see auto-mode
exception below)
_sources/.agent-lens.lock — abort «/ztn:agent-lens running»
--auto-mode exception for .lint.lock. Auto-mode is dispatched
by /ztn:lint Step 7.5; lint holds .lint.lock for the duration of
the dispatch. Treating that lock as «competitor» would deadlock the
nightly chain. Under --auto-mode only, presence of .lint.lock is
proof the dispatcher is alive — proceed with resolve work, do not
abort. The other three locks (.processing.lock, .maintain.lock,
.agent-lens.lock) stay competitive (lint already cleared those at
its own Step 0.1; if any appears here, something has gone genuinely
wrong — abort silently and let the next nightly tick retry).
Interactive mode keeps the original four-lock check; the owner-driven
session has no dispatcher above it.
- Create
_sources/.resolve.lock with {ISO timestamp} — {session info}
- Delete it on every exit path (normal, error, abort, early exit)
- Stale lock (>2h) — warn and offer to clear
Views (constitution-core.md, CURRENT_CONTEXT.md, etc.) are read as-is
during Steps 1–7. Regeneration is deferred to Step 9 (post-resolution
refresh) and runs only when this session's resolutions actually touched
the underlying source of truth.
Step 1: Load Context
Read these system files (in parallel where possible). Default-on for
all rounds, regardless of Type — owner context is a hard prerequisite
for sane hypothesis forming, not a values-only concern.
| File | Why |
|---|
_system/docs/ENGINE_DOCTRINE.md | Operating philosophy, owner-LLM contract |
_system/docs/SYSTEM_CONFIG.md | CLARIFICATIONS schema, note formats |
_system/views/constitution-core.md | Tier-1 axioms — background frame for every hypothesis |
_system/SOUL.md | Identity, focus, working style — drives hypothesis defaults |
3_resources/people/PEOPLE.md | Mandatory for person-identity / people-bare-name resolution |
1_projects/PROJECTS.md | Mandatory for project-identity |
Conditional / on-demand (load only when round contains relevant Types):
| File | Trigger |
|---|
_system/state/OPEN_THREADS.md | thread-closure-suggested items |
_system/views/HUB_INDEX.md | thread-closure-suggested, cross-domain-link, hub-stale-vs-material |
_system/views/INDEX.md | index-missing, index-stale, index-frontmatter-malformed (heartbeat — usually resolves by running /ztn:maintain) |
_system/views/CURRENT_CONTEXT.md | values-bearing rounds (org-tension, decisions) |
5_meta/PROCESSING_PRINCIPLES.md | principle-candidate-batch rounds |
_system/scripts/query_constitution.py --domains <d> | escalate when constitution-core lacks the principle needed to form a confident hypothesis (typically values-bearing items) |
Concept and audience CLARIFICATIONs are never raised by the engine.
The autonomous-resolution layer (/ztn:lint Scan A.7 +
_system/scripts/lint_concept_audit.py + _common.py normalisers)
auto-fixes or silent-drops every concept/audience format issue. This
skill therefore does NOT receive concept-format-mismatch,
concept-type-prefix-in-name, concept-name-too-long,
audience-tag-unknown, audience-tag-reserved-conflict, or
audience-tag-format-mismatch items — they don't exist as
CLARIFICATIONs. The Extensions table in _system/registries/AUDIENCES.md
remains owner-curated outside the pipeline; if owner wants to widen the
audience whitelist they edit AUDIENCES.md directly, no resolve flow.
Context-only invariant. Files in this section are read but never
written by this skill. The only writes go through the «Output Files
Touched» list in Step 7.
Constitution as proposal accelerator, not validation gate. The point
of loading the constitution is to let the skill pre-form a confident
hypothesis (option a) so the owner can one-click confirm instead of
recalling values from memory. Missing a relevant principle is not a
safety failure — at worst the skill proposes a weaker a and the owner
overrides. So default to cheap (constitution-core.md always-on); only
escalate to query_constitution.py --domains <d> when forming the
hypothesis genuinely needs the deeper tree (typically values-bearing
items). Skipped entirely under --no-constitution.
Step A: Lens Action Hints + Smart-Resolve Sweep
Biometric rate limiting + new action types
When biometric pipeline is active (_records/biometric/ non-empty),
the smart-resolve sweep applies anti-DDoS calibration on biometric-origin
action hints:
- Read
_system/state/insights-config.yaml::biometric.biometric_max_hints_per_week
(default 3 if absent).
- For the current ISO week, count biometric-origin hints already
surfaced via this sweep (parse session logs under
_system/state/resolve-sessions/ for the current week or use the
CLARIFICATIONS queue's biometric-typed items).
- If exceeded, top-N by effect_size win; the remainder defer to next
week (drop with a one-line «rate-limited» log entry, do NOT queue).
- Effect-size proxy: for
threshold_tune_proposal, use
|observed - expected| / expected (calibration drift factor); for
wikilink_add from biometric lenses, use the cited correlation's r
if available, else 0.5.
New CLARIFICATION types and action types added by biometric pipeline:
| Type | Class | Apply mechanism |
|---|
metric-record-rerender | C (proposal) | metric_record_rerender_apply action — owner picks skip / append-update / recompute-baselines-forward |
biometric-baseline-cold-start | informational | One-time on first metric-day run; resolution = dismiss with «expected cold-start» note |
biometric-threshold-drift | C (proposal) | threshold_tune_proposal action — writes proposed σ to biometric_thresholds.yaml |
biometric-affect-lexicon-empty | review | Owner audits lexicon; may add affect_lexicon.local.yaml overlay entries |
Action handlers threshold_tune_proposal and
metric_record_rerender_apply live in
_system/scripts/lens_action_handlers.py and follow the existing
two-phase contract (validate → apply with TOCTOU defence). Per
ENGINE_DOCTRINE §3.6 spirit: no silent magic — every apply leaves an
inline <!-- from_lens: ... --> comment + session log entry; owner
sees the trail on the next interactive resolve session.
This step is the engine that turns implicit lens proposals into either
auto-applied additions or queued clarifications with rich reasoning.
Runs in both modes:
--auto-mode (called by /ztn:lint Pass 2) — Step A is the entire
body of work. Skip directly from here to Step A.4 (session log
flush) and the lock-release cleanup.
- interactive — Step A pre-curates. Steps 2-9 then continue against a
queue that has been triaged, deduplicated, and annotated with
smart_resolve reasoning.
The intelligence here is LLM-driven «human-with-experience» reasoning,
not deterministic threshold counting. The whole purpose is approximating
how the experienced owner would decide, given their values, declared
focus, and prior reasoning available as precedent.
Insurance during the sweep: if any sub-step fails (LLM error, handler
crash, file lock), recover gracefully — never abort the whole skill. A
crashed handler falls back to a clarification («attempted auto-apply,
validation failed because X — owner review»). A crashed sweep means
items stay in their pre-sweep state; the next nightly tick retries.
Per-step failure rules (explicit, not «recover gracefully»):
| Failure | Recovery |
|---|
| Step A.0 — both live + template missing | Use built-in defaults (posture: balanced, uncertainty_default: queue). Log to session skipped-log. Continue. |
Step A.1 — _system/agent-lens/ walk fails (FS error) | Skip ingestion entirely. Set last-resolve-tick.txt to current ts (so next tick doesn't reprocess the same window). Log error to session. Continue to A.2 with empty hint set. |
| Step A.1 — individual hint fails stale-check | Drop that hint with reason; log to «Skipped lens hints». Other hints proceed. |
| Step A.2 — LLM error / timeout / malformed JSON output | Pass all parsed hints through unchanged (no curation, no coalescing). Log to session. Continue to A.3 with raw + open clarifications. |
| Step A.3 — LLM error / timeout / malformed JSON output | NO auto-applies this tick. NO queue annotations. All items remain in their pre-sweep state (lens hints stay in their output files; clarifications unchanged). Log error. Update last-resolve-tick.txt ANYWAY (avoids reprocessing storm) and exit Step A. |
| Step A.3 — handler validation failure on apply | Demote to lens-action-apply-failed clarification (canonical Type per SYSTEM_CONFIG) with handler's reason text. Other auto-applies in the batch continue independently — apply is per-item atomic, not all-or-nothing. |
| Step A.4 — session log write fails | Best-effort: write to _system/state/resolve-sessions/{date}-{sid}.partial.md with whatever the accumulator holds. Log error. Continue. |
The principle: never poison the queue with half-done work, never
rewrite owner-curated state, never silently drop a hint. The
session log is the audit trail of choice — every drop / failure /
recovery has a row.
Step A.0 — One-shot bootstrap (idempotent)
Two checks before A.1; both noop on subsequent runs.
- insights-config seed. If
_system/state/insights-config.yaml
does NOT exist, copy _system/state/insights-config.yaml.template
to that path verbatim (no transformation). The live file is owner-
mutable thereafter — never rewritten. If the template ALSO is
missing (broken clone), proceed with built-in defaults
(posture: balanced, uncertainty_default: queue, no per-class
overrides) and log a single warning to the session accumulator's
skipped-log; do not block the sweep.
- Sessions dir seed. Ensure
_system/state/resolve-sessions/
exists (mkdir -p). The session log writer creates it lazily, but
doing it here keeps Step A.4's flush path branchless.
Step A.1 — Ingest fresh Action Hints (deterministic)
Scope:
- Determine the «since» marker. Read
_system/state/last-resolve-tick.txt
if present; else fall back to «files modified in the last 24h». Update
the marker to «now» at the END of Step A (only after the sweep
completes; failure leaves the old marker so next run retries).
- Walk
_system/agent-lens/{lens-id}/{date}.md for files modified
since the marker. Parse each via
_system/scripts/_common.py::parse_action_hints(body). Drops are
logged to the session accumulator («Skipped lens hints») not to
CLARIFICATIONS — malformed YAML is the lens's problem, surfaced via
Stage 3 validator separately.
- For every parsed
ActionHint, run the deterministic stale pre-check
via lens_action_handlers.VALIDATORS[hint.type](hint.params). If
validation fails (cited path missing, target already exists,
wikilink already bidirectional, etc.) — drop the hint with reason in
the «Skipped lens hints» section. The lens's body observation still
stands; only the hint is dropped.
- Carry the surviving hints forward to Step A.2 with provenance label
{lens-id}/{date} derived from the source file path.
This step is pure plumbing — no LLM call, no judgement. Runs even when
--auto-mode is off (interactive mode benefits from the same triage).
Step A.2 — Smart Curation (LLM)
A single Opus-grade LLM call. System prompt = curator role, framed as
«as the owner would triage if sitting down right now to review».
System inputs to load (read once, cache for Step A.3):
| File | Why |
|---|
_system/views/constitution-core.md (always) + 0_constitution/{axiom,principle,rule}/*.md | Full tree for veto + alignment reasoning |
_system/SOUL.md | Identity, focus, working style |
3_resources/people/PEOPLE.md | Relationship map (some hints reference people) |
1_projects/PROJECTS.md | Active projects |
_system/agent-lens/weekly-insights/*.md (newest 4 only) | Recent narrative context |
_system/state/CLARIFICATIONS.md (open + last 30d resolved) | Existing queue + recent resolution context |
_system/state/insights-config.yaml (or .template if missing) | Per-class overrides + posture |
| Recent git log paths (last 7 days, paths only) | Detect owner-edited areas to prefer / avoid |
User-message inputs:
- All surviving lens hints from Step A.1 (structured: lens, type,
params, confidence, brief_reasoning).
- All open clarifications (raw blocks).
LLM output (structured JSON):
For each hint: one of
keep — survive into Step A.3 with extracted salient_features
(cluster_tightness, time_span_months, cross_para, existing_hub_overlap,
domain_axis, cited_count, owner_recent_edits_overlap, ...).
drop — with brief reason (hint conflicts with constitution, hint
duplicates a recently-resolved clarification, hint touches an
area the owner edited yesterday in a way that contradicts).
coalesce-with-{ids} — merge with one or more other hints / open
clarifications into a single combined entry with multiple options.
For each open clarification: pass-through unchanged unless the LLM
detects it should coalesce with a fresh hint.
After the LLM:
- Coalesced groups become single rows. The combined item carries an
options: payload listing each branch.
- Dropped hints log to «Skipped lens hints (curator)» in the session
accumulator.
- Kept hints proceed to Step A.3 carrying their
salient_features.
Posture awareness. insights-config.yaml::reasoning.posture
(conservative / balanced / aggressive) tunes the curator's drop
aggressiveness. balanced is default. Owner overrides in the config
file directly; this skill never edits it.
Step A.3 — Auto-resolve Sweep (LLM)
A single Opus-grade LLM call. System prompt = experienced-owner role:
«given full context, would I — knowing what I know about my values,
focus, and prior reasoning — approve this NOW?»
System inputs (in addition to A.2's set):
- Last 10 session logs from
_system/state/resolve-sessions/ newest-
first — full reasoning narratives, not just counts.
- All entries in
_system/state/lens-resolution-history.jsonl (or the
most recent 200 if the file ever grows past that). The LLM uses
history as PRECEDENT CORPUS: «when I approved a similar proposal
before, what reasoning did I give? Does the current case fit?» —
not as deterministic threshold counting.
User-message inputs:
- All clarifications surviving Step A.2 (existing + curated).
LLM output per item (structured):
{
"item_ref": "<id or anchor>",
"decision": "auto-apply" | "queue" | "block-veto",
"reasoning": "<1-3 sentences referencing specific principle / past
session / SOUL focus when relevant>",
"action": { "type": "wikilink_add", "params": {...} }
"queue_reason": "uncertainty" | "anti-flip-flop" | "no-precedent" | "config-never-auto" | "other"
"veto_reason": "<which principle or SOUL element triggered>"
}
queue_reason semantics — what each value means and whether Step A.3.5
escalates it:
queue_reason | Meaning | Escalates in A.3.5? |
|---|
uncertainty | LLM was on the fence between auto-apply and queue (rule 6). The proposal IS values-touching and the LLM lacked clarity. | Yes — this is the canonical «would have asked the owner» case |
no-precedent | Cold-start (rule 2): action class never seen before. | Yes — escalate; aligned/no-match upgrade is exactly the «build precedent without bothering owner» path |
anti-flip-flop | Owner explicitly rejected a similar proposal in the last 30 days (rule 4). | No — owner's prior rejection is the source of truth; do not reroute to constitution. Stays in queue. |
config-never-auto | insights-config.yaml::classes::{class_key} = never_auto (rule 3). | No — owner's per-class config is the source of truth. Stays in queue. |
other | Catch-all for cases the LLM cannot classify into the four above. | No — fail-closed default; owner reviews. |
Decision rules the LLM is told to follow:
- Constitution / SOUL veto is absolute. Any axiom or principle
conflict →
block-veto. Any SOUL-declared «currently being
processed, no formalisation» topic → block-veto. Owner's
override path is editing insights-config.yaml, not the sweep.
- Auto-apply is earned, not assumed. Cold-start (no precedent in
history.jsonl) → default
queue for additive hints; block-veto
for anything that even smells like mutation (split-hub,
rewrite-hub, etc.) — though those types are not currently
whitelisted, so this is a forward-compatibility guard.
- Insights-config overrides win. If
classes::{class_key} is
auto, force auto-apply (still subject to veto). If never_auto,
force queue with queue_reason: "config-never-auto". Class-key
format: {lens-id}__{action-type}__{confidence}.
- Anti-flip-flop guard. If history.jsonl shows the owner
reject-ed a substantively-similar proposal in the last 30 days,
default to queue with queue_reason: "anti-flip-flop" and a
note linking the prior rejection.
- Additive-only restriction. Currently whitelisted types
(
wikilink_add, hub_stub_create, open_thread_add,
decision_update_section) are all additive — no auto type can
mutate or delete owner content. Resolver enforces by allowing
only ACTION_HINT_TYPES in action.type for auto-apply.
- Uncertainty default = queue with
queue_reason: "uncertainty".
When the LLM is unsure between auto and queue, route to queue and
record queue_reason: "uncertainty" so Step A.3.5 can escalate to
/ztn:check-decision (the constitution-tree proxy for the absent
owner). Cold-start with no precedent uses queue_reason: "no-precedent" (rule 2). Anything else uses "other".
- Carry the would-be
action on queue decisions too. Even when
routing to queue, populate the action field with the proposal's
full type + params. Step A.3.5 needs the structured action manifest
to invoke check-decision against a real artifact. action is only
omitted when the item has no structured action (curated CLARIFICATION
from Step A.2 with no lens-emitted hint).
After the LLM, deterministic execution:
For each auto-apply decision:
- Validate
action.type ∈ ACTION_HINT_TYPES. If not, demote to
queue with reason «type not whitelisted for auto» — defends
against LLM proposing types it shouldn't.
- Invoke
lens_action_handlers.APPLIERS[action.type](action.params, source_lens, base). The handler re-validates (TOCTOU) and either
applies or returns success=False.
- On
success=True: append to session accumulator's auto_applied
list. Targets and provenance label come from the handler return.
- On
success=False: handler validation failed inside apply (e.g.
another process created the target between Step A.1 stale-check and
here). Demote to queue with reason «attempted auto-apply,
validation failed because {handler.reason} — owner review».
For each block-veto decision: queue with prefix
**Constitution-veto:** and the veto_reason. Append to session's
constitution_vetoed list.
queue decisions are NOT immediately written to the queue — they pass
into Step A.3.5 first.
Step A.3.5 — Constitution escalation on queue decisions
Premise: queue from Step A.3 means the LLM would have asked the owner
to break the tie. Doctrine (ENGINE_DOCTRINE §3.6) keeps the owner-LLM
contract intact, but the contract permits a values-bearing tie to be
resolved by /ztn:check-decision — this is exactly the «proxy for the
owner when the owner is not in the loop» role the skill was built for.
A.3.5 is the escalation handler that shrinks the queue without lowering
the bar: it only converts a queue to auto-apply or block-veto
when the constitution gives an unambiguous verdict, and leaves the
genuinely-owner-needed cases (tradeoff, anti-flip-flop, config) in the
queue.
Eligibility — only escalate when ALL hold:
-
decision == "queue" from Step A.3.
-
queue_reason ∈ {uncertainty, no-precedent} per the table above.
Other reasons preserve owner authority unchanged.
-
action is present and action.type ∈ ACTION_HINT_TYPES. Items
without an action (curated CLARIFICATIONS from Step A.2 that lack
a structured action manifest) are not eligible — there is nothing
for /ztn:check-decision to evaluate.
-
The action touches a values-bearing surface — at least one holds:
action.type == "decision_update_section" (always values-bearing
by definition — the action targets a decision note).
- Any path in the action params (
note_a / note_b for
wikilink_add; cited_notes for hub_stub_create;
cited_records for open_thread_add) resolves to a knowledge
note or record whose frontmatter types: array contains
decision. Use _system/scripts/_common.py::read_frontmatter to
read each cited path; treat unparseable / missing frontmatter as
«not values-bearing» (fail-closed for inclusion).
- SOUL focus match (best-effort, optional layer). When a SOUL
focus extractor exists in
_system/scripts/, additionally check
whether the cited title / slug / update_reason text matches any
active focus identifier (case-insensitive substring). Until that
extractor lands, this third condition is a no-op — the first two
conditions cover the load-bearing cases (decisions are the
primary values-bearing surface).
Items that fail the values-touch heuristic stay in the queue with no
escalation. The heuristic errs toward not-escalating: false negative
means owner sees one extra CLARIFICATION; false positive means an
Opus call returning no-match. The asymmetry favours skipping.
Invocation — one /ztn:check-decision call per eligible item:
situation — single paragraph distilled from the proposed action:
"Lens {source_lens} proposes {action.type} on {targets}. Reason given: {action.params.update_reason or LLM reasoning}. Should applying this action without owner review align with the constitution?". Keep concrete: name the actual targets and the
actual proposed bullet / link / hub title.
record_ref — the most-cited path in the action params (decision
note for decision_update_section; first entry of cited list for
the others). When no single record is dominant, omit and let the
skill default.
dry_run: false — Evidence Trail citations are wanted; this is a
real autonomous decision, telemetry must reflect it.
--from-pipeline /ztn:resolve-clarifications — caller_class: mechanical accounting; resolve's session log owns the batch commit.
is_sensitive — propagate true if any cited path resolves to a
note / record with is_sensitive: true in frontmatter; defaults
false.
Verdict mapping — what each verdict does to the candidate:
| Verdict | Confidence | New decision | Target accumulator |
|---|
aligned | any | auto-apply | auto_applied (with from_escalation: true) |
no-match | any | auto-apply | auto_applied (with from_escalation: true) |
violated | ≥ 0.7 | block-veto (with veto_reason from check-decision) | constitution_vetoed (with from_escalation: true) |
violated | < 0.7 | stays queue (annotate **Smart_resolve reasoning:** with the borderline verdict) | normal queue |
tradeoff | any | stays queue (annotate with the two principles in tension) | normal queue |
| error / empty visible tree | — | stays queue (fail-closed) | normal queue |
The escalations accumulator captures one row per check-decision
invocation regardless of outcome (the audit trail). The
auto_applied / constitution_vetoed accumulators capture only the
flipped outcomes (the action trail). Both are written to the session
log; the cross-reference is item_ref.
The asymmetric thresholds — promote on ANY confidence for aligned /
no-match, demote on confidence ≥ 0.7 for violated — reflect the cost
asymmetry: a wrongly-promoted aligned-no-match action is bounded
(additive scaffolding, git-revertable); a wrongly-demoted violated
action is also bounded (ends up in queue, owner sees one extra item
they would have seen without escalation anyway). The dangerous case
would be promoting a violated action on low confidence — that's why
< 0.7 violated stays in queue.
Audit:
- Every escalation (regardless of outcome) appends a row to the
session's
escalations accumulator with {item_ref, source_lens, action_type, queue_reason_in, verdict, confidence, decision_out}.
- The check-decision call itself emits to
_system/state/check-decision-runs.jsonl per its own contract; the
session log cross-references via record_ref plus the accumulator
rows.
- An escalation that flipped the decision (queue → auto-apply OR queue
→ block-veto) appends the verdict reasoning as
**Escalation-resolved by check-decision:** annotation on the action's provenance comment
inside the apply target (auto-apply path) or on the CLARIFICATION row
(block-veto path), so the owner sees explicitly «this didn't reach
you because the constitution gave a clear verdict; here is which
principle it cited».
After A.3.5, the deterministic execution loop runs again, but only
on items the escalation just flipped:
- Promoted items (verdict
aligned / no-match) go through the
auto-apply block from Step A.3 (ACTION_HINT_TYPES validation +
handler dispatch + TOCTOU fallback). On TOCTOU success=False, the
item demotes to queue with the standard «attempted auto-apply,
validation failed» reason — the escalations row stays in place
(the check-decision call still happened) and an **Escalation resolved-but-toctou-failed:** annotation is added so the owner sees
the full trail.
- Demoted items (verdict
violated ≥ 0.7) get the
**Constitution-veto:** prefix treatment from Step A.3.
- Items left in the queue (low-confidence violated, tradeoff,
heuristic-skipped, not-eligible) go through the queue-write block
with
**Smart_resolve reasoning:** annotation, plus
**Escalation:** annotation naming the verdict + reason for those
that did pass through A.3.5 without flipping.
For each queue decision (post-A.3.5): rewrite the corresponding
clarification (or synthesise a new one for hints that didn't yet exist
as clarifications) to attach the LLM's reasoning text under a
**Smart_resolve reasoning:** field. When the item passed through
A.3.5 without flipping (tradeoff / low-confidence violated /
heuristic-skipped / not-eligible), additionally attach
**Escalation:** with the result so the owner sees why it stayed.
Owner sees this in interactive Step 5.
Cost / latency. One /ztn:check-decision call per eligible queue
item. Bounded: queue items per nightly tick are typically O(1)–O(10)
in a healthy steady state; the values-touch heuristic culls further.
The Opus calls are sequential, not parallel — sequencing keeps Evidence
Trail writes serialised against the per-record last_applied field
without coordination overhead.
Evidence Trail growth from autonomous citations. Every escalation
call writes a citation-aligned / citation-violated /
citation-tradeoff entry into the cited principle's ## Evidence Trail section, regardless of whether the candidate flipped. This
grows the trail at roughly «escalations per night × 1–3 principles
per call» rate. The bloat is bounded by /ztn:lint Scan F.7 (weekly
Evidence Trail compaction), so steady-state size stays manageable.
F.4 «Most-cited principles» (monthly health metric) will reflect
auto-mode citations alongside owner-driven ones — this is desired
signal, not noise: a principle cited often by the resolver is genuinely
load-bearing on the system's autonomy. F.1 stale-detection runs on
last_reviewed, not last_applied, so auto-mode citations do not
suppress legitimate staleness signals.
Step A.4 — Session log flush + early exit branch
- Build a
SessionState via resolve_session.new_session(mode, trigger) at Step A's entry. Mode is auto or interactive;
trigger is lint (when --auto-mode) or owner (otherwise).
- Throughout Steps A.1–A.3.5, accumulate
auto_applied,
constitution_vetoed, and escalations entries on the state
object. The escalations list carries one row per
/ztn:check-decision invocation made in A.3.5: {item_ref, source_lens, action_type, queue_reason_in, verdict, confidence, decision_out}. Promoted / demoted items also appear in the
auto_applied / constitution_vetoed lists with provenance
from_escalation: true so a single read of the session log shows
both the verdict trail and the final outcome. The escalations
accumulator is a new SessionState field — implementer adds
escalations: list[dict] = field(default_factory=list) alongside
the existing accumulators in _system/scripts/resolve_session.py,
plus an items_escalated count in _frontmatter and a
_render_escalations section.
- After A.3 completes, call
resolve_session.write_session_log(state).
- Update
_system/state/last-resolve-tick.txt with current ISO-Z
timestamp.
- Branch on mode:
--auto-mode → release lock, exit silently. Owner sees the
session log next time they open _system/state/resolve-sessions/
or run interactive resolve. No console output beyond a one-line
summary («auto-resolve: applied N, queued M, vetoed K»).
- interactive → continue to Step 2 with the queue now containing
curated + smart_resolve-annotated items. The remaining flow
(theme menu, rounds, owner clicks) operates against this richer
queue. Each owner decision in Step 5/6 calls
resolve_session.append_history with structured precedent fields,
and updates the same session log via state.owner_decisions.
No history.jsonl writes in --auto-mode. This is deliberate: the
engine never trains on engine. Only owner clicks accrete precedent.
Auto-mode applies are visible in the session log + target file
provenance, but they do not influence the next sweep's reasoning beyond
the «what's in the codebase right now» layer.
Failure handling. Any unhandled exception inside Step A logs to
the session accumulator's auto_applied list with success=False
shape, flushes the partial session log, and continues to the
appropriate branch (auto-mode exits; interactive continues with
remaining queue items unchanged). The next nightly tick retries —
state is forward-compatible.
Step 2: Load and Parse Queue
Read _system/state/CLARIFICATIONS.md. Parse all entries under
## Open Items (and any sub-section like ## maintain {batch-id} /
## lint {date} — those are open by convention until archived).
Each entry is a ### {date} — {title} block followed by labelled fields:
**Type:**, **Subject:**, **Source:**, **Action taken:**,
**Quote:**, **Context:**, **Uncertainty:**, **To resolve:**,
optionally **Confidence tier:**, **Suggested action:**, **Status:**.
Build an in-memory list of items with parsed fields and original raw
markdown block (for safe archiving later — never re-render, copy
verbatim).
If the schema deviates (missing fields, unexpected sub-section names) —
continue best-effort. Surface anomalies in the round-close report;
never delete an item that didn't parse cleanly.
Early exit: zero open items → report «Очередь пуста — ничего разбирать»
and stop (release lock).
Pre-resolve scan: any item whose title or Confidence tier already
contains RESOLVED — these were marked done by the producer but not yet
archived. Auto-archive them silently in Step 7 without asking the owner.
Report count in the menu («3 уже-RESOLVED закрою без вопроса»).
Step 3: Cluster by Type
Group remaining items by **Type:**. Compute per-cluster size and
weight. Standard Types (extend as new ones appear in producer skills):
| Type | Weight | Default batch |
|---|
people-bare-name | light | 5 |
person-identity | light | 5 |
content-pipeline-reminder | light | 5 |
process-compatibility | light | 5 |
thread-closure-suggested | medium | 4 |
idea-ambiguous-match | medium | 4 |
evidence-trail-anomaly | medium | 4 |
topic-classification | medium | 4 |
cross-domain-link | medium | 4 |
project-identity | medium | 4 |
org-structure-tension | heavy | 3 |
principle-candidate-batch | heavy | 3 |
decision-* | heavy | 3 |
lens-action-proposed | heavy | 3 |
lens-action-veto | heavy | 3 |
lens-action-apply-failed | medium | 4 |
| unknown / new | medium | 4 |
--max N overrides. Light = quick approve/dismiss; heavy = each item
demands reading + values-judgment.
Step 4: Theme Menu
Render single screen — numbered list with weight hints and recency:
Очередь CLARIFICATIONS — {N} open ({M} уже-RESOLVED закрою тихо)
Темы (выбери номер):
1. people-bare-name (4) — лёгкая · ~5 мин · самое старое 2026-04-23
2. person-identity (1) — лёгкая · ~2 мин · 2026-04-27
3. principle-candidate-batch (4) — тяжёлая · требует размышления · 2026-04-27
4. content-pipeline-reminder (1) — лёгкая · можно одной командой
5. process-compatibility (1) — техника · 2026-04-27
Введи номер темы (или `q` — выйти).
If --theme <type> was passed → skip menu, jump to Step 5 with that
cluster.
If owner picks q → release lock, exit. Pre-resolved items stay
archived (Step 7 already ran for them).
Step 5: Render Round
Take the chosen cluster. Slice to batch size (heavy 3 / light 5 / --max N).
If cluster larger than batch → first N items by recency (oldest first —
fresh items fade later); subsequent rounds will pick up the rest.
Smart_resolve enrichment. Items that came through Step A's
auto-resolve sweep carry an attached **Smart_resolve reasoning:**
field. When rendering, surface that reasoning in the «Procedural
context» block so the owner sees why this item was queued instead of
auto-applied. If the item has matching precedents in
_system/state/lens-resolution-history.jsonl, list 1-3 most-similar
prior session links with a one-line summary («2026-04-15 — approved
similar hub for cross-PARA cluster, owner reasoning: tight cluster +
no overlap»). The smart_resolve LLM has already produced this; render
verbatim — do not re-reason.
Class L items (lens-action-proposed / lens-action-veto /
lens-action-apply-failed) — adapted seven-block render. Same top-
down order (essence first, then files, then context, then options),
but the blocks pivot on the proposal shape instead of a free-form
question:
── Q{n} ── {date} — {action_type}: {one-line proposal summary}
🧩 Суть:
{1-2 sentences: what the lens proposes, what it would change, what
the resolver concluded. For lens-action-veto, lead with the veto
reason. For lens-action-apply-failed, lead with the handler error.}
📂 Files:
{Cited paths from action params — note_a/note_b for wikilink_add,
cited_notes for hub_stub_create, cited_records for open_thread_add,
decision_note_path for decision_update_section. Owner can open them
directly.}
🧠 Procedural context:
**Source lens:** {lens-id}/{date}
**Confidence (lens self-report):** {low|medium|high}
**Smart_resolve reasoning:** {verbatim from item}
**Veto reason:** {only on lens-action-veto}
**Handler error:** {only on lens-action-apply-failed}
**Action type:** {wikilink_add|hub_stub_create|...}
**Action params:** {YAML inline rendering of params dict}
🔗 Precedent:
{0-3 lines from lens-resolution-history.jsonl on similar past
proposals + how owner decided. Empty section is OK.}
❓ Options:
[apply] — invoke handler now; commits the targets listed above
[reject] — archive with `dismiss-lens-proposal` + reason
[modify] — edit params inline (typically slug rename or alternative
cited list), then apply
[defer] — push to next session
Class A/B items keep their original a/b/c/d/e options. Step 6
dispatches per item Type. (Class L is distinct from the existing
Step 9 Class C — auto-invoked-refresh classes; different letter to
avoid confusion.)
For each item in the batch, render with the mandatory seven blocks.
Order matters — owner reads top-down without prior session context, so
the essence must come first, then files for direct access, then
procedural context, citations, and proposals.
── Q{n} ── {date} — {short title from the entry header}
🧩 Суть:
{1–2 plain-language sentences: what is this about, what is the problem,
what decision is being asked. No jargon, no wikilinks, no file paths.
A reader who has never seen this item should grasp the question from
this block alone. MANDATORY on every item — light or heavy.
For person-identity: «В записи X (про Y) неизвестно кто Z. Нужно
подтвердить identity или dismiss.»
For thread-closure: «Thread X открыт N дней; есть/нет signals что
закрылся. Решаем — закрыть или нет.»
For principle-candidate: «N кандидатов в принципы накопилось. Решаем по
каждому — accept в constitution / reject / defer.»
For policy/values: «N records carry inconsistent state for {field};
решаем policy A/B/C про N.»}
📂 Files:
{Exact file paths the owner can open directly: record path, source
transcript path, any related profile / hub / view. Use repo-relative
paths. Include `:line` anchors when there is a specific line. Wikilinks
are fine ALONGSIDE paths but never instead of them.}
📍 Где это случилось:
{2–3 sentence procedural context derived from **Context:** field — when,
what meeting / call / pipeline run, what action the producer took. If
Context is absent, summarise Source path + Action taken. This is the
«timeline» block, not the «what is the problem» block — that's «Суть»
above.}
💬 Цитаты (verbatim):
{copy **Quote:** field as-is — already verbatim by item-format
contract. If Quote is `_(none — pipeline-level issue)_`, show that
literally.}
🧭 Конституция / soul:
{Render this block when there is an actual hit that strengthens the
hypothesis — not gated by Type. Procedure: scan constitution-core
(always loaded) for relevance to the item; for values-bearing items
where core has nothing or is too coarse, escalate to
query_constitution.py --domains <d>. If no principle is relevant —
omit the block entirely (do not render an empty header). When
rendering: list 1–3 principles with id + 1-line statement + verdict
(aligned / violated / tradeoff / no-match) + one-line rationale tying
the item to the principle. Goal of this block is to make option `a`
more confident, not to gatekeep the resolution.}
🎯 Гипотеза:
{Skill's pre-formed proposal — what the owner most likely wants to do,
with reasoning. For person-identity: best-match candidate from PEOPLE.md
+ alternatives. For thread-closure: signals fired vs not. For principle-
candidate: own assessment of whether it clears the bar. Always state
the reasoning ground; never just "I think X".}
✅ Варианты:
{For HEAVY items (cluster weight «heavy» per Step 3 — org-structure-tension,
principle-candidate-batch, decision-*, semantic-drift / policy), each
primary option (a/b/c) MUST include three micro-sections:}
a) {one-line label}
- **Что меняется:** {2–3 specifics — files, behaviour, schema, prompt}
- **Что приносит:** {2–3 positive outcomes — clarity, correctness, perf}
- **Риски / потери:** {1–2 things owner has to accept / things that may break}
b) {one-line label}
- **Что меняется:** ...
- **Что приносит:** ...
- **Риски / потери:** ...
c) {one-line label}
- ...
{For LIGHT items (person-identity, people-bare-name, process-compatibility,
content-pipeline-reminder): one-line options are sufficient; add a 1-line
clarifier only when the action is non-obvious.}
d) skip — оставить открытым в текущей форме (показать снова в следующий раз)
e) defer — оставить открытым с пометкой `**Last reviewed:** {today} — deferred`,
уйдёт в конец очереди при следующем выборе темы
f) dismiss — закрыть как not-actionable, в архив с пометкой `Resolution: dismissed`
Skip / defer / dismiss are universal escape hatches — never expanded with
micro-sections; they have fixed semantics.
Always include c/d/e on every item — they are universal escape hatches.
Number questions Q1, Q2, … sequentially across the batch (not per-cluster
restart). Owner answers like:
Q1: a
Q2: b — собеседник Ivan Petrov, добавь как primary speaker
Q3: dismiss
Q4: skip
Multi-line answers per question are normal — parse loosely.
Step 6: Apply Resolutions
For each answered question, classify the action:
Class A — silent ops (no diff, just do):
c skip — no-op, item stays as-is
d defer — append **Last reviewed:** {today} — deferred line to
the item block (in-place edit, append-only line)
e dismiss — move item to CLARIFICATIONS_ARCHIVE.md with
**Resolution:** {today} — dismissed: {one-line owner rationale or "not actionable"} appended to the block; remove from open file
a/b for process-compatibility, content-pipeline-reminder,
evidence-trail-anomaly archival cases (no content edit needed) —
same as dismiss but with substantive Resolution: text
Class B — content edits (diff-first):
- Profile creation (person-identity →
3_resources/people/{slug}.md +
PEOPLE.md row + people-frontmatter backfill in cited records)
- Note frontmatter edits (people:, projects:, threads:)
- Hub edits (open questions, current understanding bullets)
- OPEN_THREADS.md state changes (open → resolved)
- Decision-note creation (typically already created by the producer
skill; here only when owner explicitly requests)
- Constitution edits — never auto. Principle-candidate accepts
always render the proposed
0_constitution/{type}/{domain}/{slug}.md
body and ask for explicit confirm before write.
For Class B: render unified diff or new-file body, ask [y/N] per file.
On y → write. On N → re-prompt with the original options (a/b/c/d/e)
for that question. The diff gate is friction — but it's the only gate
where the owner can catch the skill misreading their answer.
Class L — lens-proposal apply (Step A residue, owner-driven):
Lens-action items routed to queue by Step A's smart_resolve sweep
(Type ∈ {lens-action-proposed, lens-action-veto,
lens-action-apply-failed}; underlying **Action type:** ∈
ACTION_HINT_TYPES) render with options [apply] / [reject] / [modify] / [defer].
apply → invoke
lens_action_handlers.APPLIERS[action_type](action_params, source_lens, base) directly — the same code path Step A.3 uses for
auto-apply. On success=True, archive the clarification with
Resolution-action: apply-lens-proposal, payload
{action_type, targets: handler.targets, from_lens, owner_modified: false}.
On handler failure inside apply, demote to deferred (do NOT archive).
reject → archive with Resolution-action: dismiss-lens-proposal,
payload {reason: "constitution-conflict | not-actionable | wrong-target | low-quality | <free-form>"}. The reason text feeds
the next sweep's precedent grounding via history.jsonl.
modify → render the proposed params, ask owner for an inline
edit (typically slug rename, alternative cited_notes set), then
call the applier with the modified params. Archive with
apply-lens-proposal payload {owner_modified: true} so future
precedent matching knows the LLM's params were not exactly what
the owner approved.
defer → same as Class A defer (append **Last reviewed:** {today} — deferred line, item stays in queue).
For lens-action-veto items the default option is reject (resolver
already concluded constitution conflict), but apply remains
available — owner override path is editing
_system/state/insights-config.yaml::classes to auto for the
class_key, then re-running auto-mode. Direct interactive apply of
a veto'd item is also valid (owner judgement supersedes resolver).
On every owner click for a Class L item (apply / reject / modify):
- Append a row to
_system/state/lens-resolution-history.jsonl via
resolve_session.append_history() with the canonical schema:
ts, session_ref (current session log path), class_key (via
resolve_session.class_key(lens_id, action_type, confidence)),
decision, proposal_summary (1-line), applied_target (when
applicable), salient_features (carried forward from Step A.2),
owner_comment (free text), inferred_pattern (LLM post-decision
call — see below).
- Append the human-readable narrative to the in-memory session state
state.owner_decisions for the session log flush.
- Inferred-pattern post-call. A small Haiku-grade LLM call takes
the proposal + salient_features + decision + (optional)
owner_comment and produces a 1-2 sentence reasoning hypothesis:
«Owner approves hub creation when cluster has tight thematic
overlap, span >2 months, no existing hub overlap.» This becomes
input for future smart_resolve precedent matching. Stored in the
history row's
inferred_pattern field. ~5s, ~50 tokens output.
- Auto-apply traces (Step A.3) do NOT write here. The history
layer accretes precedent only from owner clicks, never from engine-
approved actions. This keeps the precedent corpus owner-grounded.
When the session ends (Step 9.5), resolve_session.write_session_log
overwrites the markdown session file with the final accumulated state
(auto-applied + vetoed + owner_decisions). The file is sensitive
(captures conversational reasoning) — is_sensitive: true by default,
audience_tags: [], origin: personal.
Archive Contract enforcement (cross-cutting, applies to any Class A or Class B action whose effect is archival):
Per _system/docs/SYSTEM_CONFIG.md → Archive Contract, every archival event MUST carry a reason captured with the entity. When applying the actions below, the skill is responsible for writing the contract-required field in the same atomic operation as the archival flag. Skipping the field is a contract violation; a follow-up /ztn:lint Archive-contract scan will surface it as archive-note-missing / archive-reason-missing CLARIFICATION.
| Resolution-action | Archive Contract form | Required write |
|---|
archive-hub | Form A (file-based) | Move hub .md from 5_meta/mocs/ to 4_archive/ per target_path payload AND append ## Archive Note block at the end of the hub file with date: today, reason: {payload.reason}, triggered_by: /ztn:resolve-clarifications, optional superseded_by if the resolution mentioned one. Frontmatter status: archived + archived_at: today flipped if the hub file uses the archived status (knowledge-style); for hub-status resolved the Archive Note alone is the canonical record. |
dismiss | Form C (queue) | The **Rationale:** line on the resolved CLARIFICATION block (what Step 7 already writes). Required for archival-effect dismissals — never leave the rationale empty for these. |
dismiss-duplicate | Form C (queue) | Same as dismiss. |
merge-notes (the merged-away note) | Form A (file-based) | Append ## Archive Note to the merged-away note BEFORE removing it from active surface, with reason: "merged into [[kept-note-id]]", triggered_by: /ztn:resolve-clarifications, superseded_by: [[kept-note-id]]. Move the merged-away file to 4_archive/ (do not delete). |
close-thread | Form C (queue) | The resolution_text payload populates the Resolved-section entry in OPEN_THREADS.md. Required for every close-thread and pursue-or-close with choice: close. |
demote-tier (to stale) | Form B (registry-row) | Move the row from ## People to ## Stale People in PEOPLE.md and populate the Reason cell with payload.reason. Same atomic write — never leave the row half-moved. |
For all other Resolution-actions whose effect is non-archival (promotion, refresh, restructure), no Archive Contract write is required.
Class C — auto-invoked refresh (Step 9):
Some resolutions invalidate derived views or registries. Track which
classes of writes happened during the session (counters, not file lists):
| Counter | Incremented by | Triggers in Step 9 |
|---|
constitution_writes | principle-candidate accept (new file under 0_constitution/{type}/{domain}/) | /ztn:regen-constitution |
registry_writes | person-identity (new profile + PEOPLE.md row), project-identity (PROJECTS.md), thread-closure (OPEN_THREADS.md), hub edits (5_meta/mocs/) | /ztn:maintain |
content_pipeline_writes | content-pipeline-reminder accept | suggest /ztn:check-content (do NOT auto-invoke — content review is a separate owner gesture) |
These are surfaced + acted on by Step 9. The skill no longer leaves
them as text suggestions in the round-close report.
Step 7: Archive and Clean
Rewrite CLARIFICATIONS.md in place:
- Read current file fresh.
- Remove blocks for: pre-resolved items (Step 2 scan), all dismissed
items, all
a/b-resolved items.
- Update in-place: deferred items get
**Last reviewed:** line
appended; skipped items unchanged.
- Update top-of-file
**Last reviewed:** field to today's date with
short summary: (YYYY-MM-DD — resolve session: closed N, deferred M, dismissed K, skipped S).
- Append removed blocks to
CLARIFICATIONS_ARCHIVE.md under a new
## Archived YYYY-MM-DD (resolve session) section, in original order,
each with **Resolution:** line appended.
On any error during this step: do not leave the file half-written.
Restore via git checkout -- _system/state/CLARIFICATIONS.md and
report the failure. Owner is responsible for working from a clean tree;
git is the rollback mechanism.
Step 8: Round Close + Continue
Per-round report (printed after Step 7 each round, BEFORE Step 9):
Раунд закрыт — тема: {type}
✅ Закрыто: {N} {one-line list of what got resolved}
⏸ Deferred: {M} {short list}
⏭ Skip: {S} {short list}
🗑 Dismiss: {K} {short list}
🔁 Pre-resolved: {auto}
Осталось в очереди: {remaining} items в {remaining-themes} темах.
{If --continue passed: jump to Step 4 with refreshed counts.}
{Else: «Запустить ещё круг? — `y` или укажи тему: …». Wait for owner.}
When the owner declines further rounds (or queue empties) → proceed to
Step 9. Lock is held through Step 9 and released at the end.
Step 9: Post-resolution refresh + save reminder
Always runs after the rounds end (even pure defer/skip/dismiss sessions
leave CLARIFICATIONS.md / ARCHIVE.md dirty and need a save reminder).
Sub-steps gate independently on what actually happened.
9.1 — Release lock first
Delete _sources/.resolve.lock before any Step 9.2 / 9.3 invocation
and before printing the save reminder. Two reasons:
/ztn:save checks _sources/.resolve.lock and refuses if held —
the owner running save next would hit a confusing abort.
- Cleanliness — the session's writes are committed in CLARIFICATIONS.md
by Step 7 already; the lock has no further purpose.
/ztn:maintain and /ztn:regen-constitution do NOT check
.resolve.lock today, so the order of 9.1 vs 9.2/9.3 is not load-
bearing for them — but releasing first keeps the invariant simple
(no producer skill ever sees a stranded lock from this skill).
9.2 — Constitution refresh
Fires only if constitution_writes > 0 and --no-refresh not passed.
- Print: «Записал N новых принципов — обновляю constitution-core view…»
- Invoke
/ztn:regen-constitution inline.
- On failure → print the skill's error verbatim, continue to 9.3 (don't
block other refreshes on one failure).
9.3 — Registry / hub refresh
Fires only if registry_writes > 0 and --no-refresh not passed.
- Print: «Тронул N registries / hubs — запускаю /ztn:maintain для
пересборки HUB_INDEX / INDEX / CONCEPTS…»
- Invoke
/ztn:maintain --no-sync-check inline (Step 0 already synced;
double-sync is wasteful and may surprise the owner with a second
rebase preview).
- On failure → print the skill's error verbatim, continue.
9.4 — Content pipeline reminder
If content_pipeline_writes > 0:
- Print suggestion only, do NOT auto-invoke: «можешь прогнать
/ztn:check-content — есть свежие content candidates».
9.5 — Save reminder
/ztn:save per its contract is never auto-fired by other skills —
it stays an explicit owner gesture. This step only reminds, never
invokes.
Compute git status --porcelain to detect dirty files (any of:
CLARIFICATIONS.md / ARCHIVE.md from Step 7, profile / record edits
from Class B, regen / maintain outputs from 9.2 / 9.3).
If dirty and --no-save not passed:
Сессия закрыта. Изменено файлов: <K>
Закоммить и пушни когда готов:
/ztn:save
If clean → print «Working tree clean — nothing to commit». Exit.
9.6 — Final recap
Сессия завершена.
Резолюций: <closed> Deferred: <M> Dismissed: <K>
Refreshes: constitution=<y/n/skipped> maintain=<y/n/skipped>
Working tree: <dirty K files | clean>
Constraints
- Essence first, never optional. Every Q opens with
🧩 Суть: —
1–2 plain-language sentences. Owner is reading without session context
on items often a week+ old; without «суть» up-front the procedural
blocks below require mental reconstruction of «о чём это вообще» on
every item. This rule is load-bearing on round friction.
- Never recall meeting context unaided. Every Q also renders the
📍 Где это случилось: block. If the entry has no Context field and
Source path leads to a missing file — say so explicitly, do not
fabricate context.
- Quotes are verbatim. Never paraphrase the
**Quote:** field. If
it's empty / _(none)_ — render that as-is.
- Numbered questions across the whole round — Q1..Qn, not Q1..Qm
per cluster.
- One theme per round. No bulk operations across themes. Owner
decides whether to chain via
--continue.
- Diff-gate is non-negotiable for Class B. Even on unambiguous
a
answers, show the proposed write before applying.
- Constitution lookup is opt-out (
--no-constitution), default-on
via the always-loaded constitution-core.md. Deeper queries
(query_constitution.py) escalate when needed — typically on
values-bearing items, but the trigger is «hypothesis would benefit
from more context», not Type. The skill renders the 🧭 Конституция
block only when there is a real hit; omits silently otherwise.
- Non-personal-origin principle candidates always require manual
review. Items tagged
origin: work or origin: bootstrap-raw-scan
go through the diff gate even if the round logic could otherwise
auto-archive them. Constitution stays signal-only.
- No re-render of stored blocks. When archiving, the block markdown
is copied verbatim plus a
**Resolution:** line. Never normalise
fields, never strip whitespace — preserves audit trail.
- Sync before review (
--no-sync to opt out). Reviewing a stale
queue wastes attention on items already resolved on another device.
Step 0 always fronts the session unless owner opts out explicitly.
- Refresh fires only on material writes. Sub-steps 9.2 / 9.3 run
/ztn:regen-constitution / /ztn:maintain only when this session
produced principle accepts / registry edits respectively. Pure
defer/skip/dismiss sessions still trigger 9.1 (lock release) and
9.5 (save reminder) — the working tree is dirty either way.
- Save is never auto-invoked. Step 9.5 prints a reminder; the
owner runs
/ztn:save themselves. This preserves /ztn:save's
contract («never auto-fired by other skills»).
- Lock released before any 9.2/9.3/9.5 step.
/ztn:save checks
.resolve.lock and would refuse if held; releasing in 9.1 keeps
the invariant simple regardless of which sub-steps fire.
Output Files Touched
Direct writes by this skill (Steps 1–8):
_system/state/CLARIFICATIONS.md — items removed / deferred-line appended
_system/state/CLARIFICATIONS_ARCHIVE.md — resolved blocks appended
_sources/.resolve.lock — created/deleted
3_resources/people/{slug}.md — when person-identity → create-profile
3_resources/people/PEOPLE.md — registry rows added/updated
_records/{meetings,observations}/{note}.md — frontmatter people: / projects: / threads: updates
5_meta/mocs/{hub}.md — open questions / current understanding edits when thread-closure
_system/state/OPEN_THREADS.md — thread state moves
0_constitution/{type}/{domain}/{slug}.md — principle accepts (creates only)
Step A direct writes (lens-action handlers + session log):
_system/state/resolve-sessions/{date}-{sid}.md — per-session log
(auto + interactive). is_sensitive: true, origin: personal.
_system/state/lens-resolution-history.jsonl — append-only precedent
index. Interactive mode owner clicks only; auto-mode does not
write here.
_system/state/last-resolve-tick.txt — high-water marker for the
«modified since» scan.
_system/state/insights-config.yaml — created from
insights-config.yaml.template on first run when missing; never
rewritten thereafter (owner-mutable).
- Lens-action handler targets (additive only):
wikilink_add → ## Связи (auto) section in both notes
hub_stub_create → 5_meta/mocs/hub-{slug}.md + back-wikilinks
in each cited note's ## Связи (auto) section
open_thread_add → _system/state/OPEN_THREADS.md ## Active
decision_update_section → ## Update {today} section appended
to the cited decision note
Indirect writes via Step 9 chained skills:
/ztn:sync-data (Step 0) — git fetch + rebase / fast-forward against origin
/ztn:regen-constitution (Step 9.2) — _system/views/constitution-core.md,
_system/SOUL.md Values zone, _system/views/CONSTITUTION_INDEX.md
/ztn:maintain (Step 9.3) — _system/views/HUB_INDEX.md,
_system/views/INDEX.md, _system/registries/CONCEPTS.md, registry hygiene
/ztn:save (Step 9.5) — NOT invoked by this skill; reminder only,
owner runs explicitly per save's contract
(Concept/audience format issues do NOT pass through this skill —
autonomous resolution by /ztn:lint Scan A.7 handles them
upstream. AUDIENCES.md Extensions table is owner-curated outside
the pipeline.)
What This Skill Does NOT Do
- Does not invent new ambiguities — only resolves what producers wrote.
- Does not edit principle bodies in
0_constitution/ (only creates new
files on accept).
- Does not invoke
/ztn:save — Step 9.5 reminds, owner runs.
- Does not reorder or rewrite open items beyond the deferred-line append.
- Does not auto-resolve Class B without diff confirmation, even on
unambiguous owner answers.
- Does not back up CLARIFICATIONS.md before writing — git is the
rollback mechanism.
- Does not auto-invoke
/ztn:check-content — content review is a
separate owner gesture; Step 9.4 only suggests it.