← Indice documentazione Architettura › lifecycle

Metnos

lifecycle — un solo oggetto per ogni cambiamento al sistema
Architettura canonica.
Moduli: runtime/change_intents.py, change_intent_adapters/,
change_applier.py, change_observer.py, change_rollback.py.

Pubblico: chi opera il cruscotto /admin/changes,
chi vuole capire dove va una proposta dopo l'accettazione.

Indice

  1. L'oggetto unico: change_intent
  2. Il ciclo di vita in 5 atti
  3. Le 6 sorgenti come adapter
  4. I 3 daemon: materializer, applier, observer
  5. Esempio dall'inizio alla fine: find_recipes
  6. Riferimenti operativi
proposto accettato applicato osservato finalizzato rami laterali: staged · rejected · failed · rolled_back
Figura 1 — Ciclo di vita di un cambiamento: una sola macchina a stati, dalla proposta alla finalizzazione, con rami laterali (in stage / respinto / fallito / annullato).

1. L'oggetto unico: change_intent

Tutti i cambiamenti al sistema convergono in un singolo schema in ~/.local/state/metnos/change_intents.sqlite:

id UUID
fingerprint sha256[:32] # deterministico per dedup fra sorgenti
state PROPOSED|ACCEPTED|APPLIED|OBSERVED|FINALIZED|
 STAGED|REJECTED|FAILED|ROLLED_BACK
origin_family telos|introvertiva|synt|user
origin_module scamper|dedupe|request_new_executor|feedback|...
intent_kind create_executor|extend_executor|dedupe_executors|
 reject_pattern
intent_target nome executor o pattern
intent_summary 1 frase rivolta all'utente
intent_body dict kind-specific (arg_name, tools_sequence,...)
score 0–1 normalizzato fra sorgenti
confidence 0–1
convergence N. sorgenti che propongono cose equivalenti
decision_* registro accept/reject/stage utente
applied_effect diff applicato + rollback_blob path
observed_metrics metriche periodo di grazia

Il fingerprint NON include origin_family: cosí due sorgenti diverse che propongono cose equivalenti (es. telos:scamper + synt sullo stesso executor) confluiscono in un solo record, con convergence incrementato.

2. Il ciclo di vita in 5 atti

 +-----------+
 | PROPOSED | ← qualche sorgente l'ha generata
 +-----------+
 / | \
 / | \
 (utente) | (utente decide piu' tardi)
 / | \
 v v v
+----------+ +--------+ +---------+
| ACCEPTED | | STAGED | |REJECTED |
+----------+ +--------+ +---------+
 | (daemon applier)
 v
+----------+ +----------+
| APPLIED | ----> | FAILED | (nuovo tentativo possibile)
+----------+ +----------+
 | (daemon observer, periodo di grazia 7gg di default)
 v
+----------+ +-------------+
| OBSERVED | ----> | ROLLED_BACK | (fisico, per kind)
+----------+ +-------------+
 |
 v
+-----------+
| FINALIZED | ← consolidato, rimosso dalle viste default
+-----------+

Da QUALSIASI stato si può transitare a ROLLED_BACK (via di fuga per la revisione umana). Da REJECTED è possibile riproporre (transizione a PROPOSED).

3. Le 4 sorgenti vive come adapter

Ogni sorgente ha un adapter in runtime/change_intent_adapters/ che proietta i propri record in ChangeIntent, senza effetti collaterali. Dal 2/7/2026 (ADR 0180, regola dei livelli) gli adapter attivi sono quattro:

SorgenteAdapterKind output
telos (10 lenti, teste di cluster)telos.py create_executor (new_valid) / extend_executor (parametric) / materialize_pipeline (existing_pipeline)
introvertiva (solo dedupe)introvertiva.py dedupe_executors (le shape storiche generalize/specialize restano leggibili, i generatori sono ritirati)
synt (request_new_executor)synt.py create_executor (importato come FINALIZED se già installato)
turn_feedback utenteuser_feedback.py reject_pattern (solo se ≥ 2 rifiuti)

L'adapter telos proietta le teste di cluster (recompose_clusters) e non le righe grezze: circa 27 intenti al posto di 500, punteggio = cluster_score (allineamento massimo + bonus di convergenza fra lenti distinte), e SOLO i name_status azionabili — il rumore (proposte ridondanti o con nome invalido) non arriva alla vista.

Adapter ritirati (2/7/2026, «se un meccanismo non serve non serve»): canonical.py (lo store canonical_query_log era scritto dal planner storico, disattivato — il suo ruolo è assorbito dal fastpath L0) e multi_tool.py (lo store multi_tool_paths non ha più uno scrittore — le catene reali le impara L1 autopath dai turni). I moduli restano nella storia di git; le righe residue sono state respinte con ragione esplicita.

Raccolta unificata delle proposte (runtime/proposals_unified.py): SUPPORTED_SOURCES = ("telos", "introvertiva") — le altre sorgenti scrivono direttamente senza passare per il centro di raccolta.

Normalizzazione del punteggio per famiglia:

Promozione fastpath: i cluster fastpath possono essere promossi a proposte executor tramite task_fastpath_promotion. Le proposte risultanti sono visibili in /admin/changes.

4. I 3 daemon: materializer, applier, observer

DaemonTriggerCosa fa
change_intent_materialize daily@01:00 Concatena gli adapter vivi → upsert con dedup via fingerprint → bump convergence.
change_applier every_10m Legge ACCEPTED, applica fisicamente per kind. Cap 20/fire.
change_observer daily@03:15 Legge APPLIED+OBSERVED, calcola metriche, transition a FINALIZED o ROLLED_BACK.

Applier handler per kind:

L'observer verifica le metriche dopo applied_at e applica un periodo di grazia (default 7 giorni, env METNOS_CHANGE_GRACE_DAYS). Inneschi del ripristino:

Il ripristino fisico è delegato a change_rollback.py (per kind: archivia la dir synth, ripristina il manifest dal blob + re-sign, rimuovi l'alias, declassa lo stato, rimuovi la riga jsonl).

5. Esempio dall'inizio alla fine: find_recipes

Il motore SCAMPER di telos suggerisce: «Estendi find_files con kind=recipe per far corrispondere i file .md in ~/Documents/Recipes

AttoStatoDove succede
1. Sistema crea ChangeIntentPROPOSED materializer @01:00 da telos_proposals.jsonl
2. Utente clicca ACCEPTED POST /admin/changes/{id}/accept
3. Applier modifica manifest + re-signAPPLIED change_applier @every_10m
4. 5 chiamate find_files(kind=recipe), tutte OK OBSERVEDchange_observer @03:15 quando age ≥ 0
5. Dopo 7gg senza problemiFINALIZED change_observer al settimo passaggio

Se invece dopo l'applicazione l'utente preme in chat su una risposta che usa il nuovo arg, l'observer vede new_rejects≥2 per la query interessata e transita a ROLLED_BACK — ripristino fisico del manifest dal blob.

6. Riferimenti operativi


— allineato con il codice.