lifecycle — un solo oggetto per ogni cambiamento al sistemachange_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 cross-source
state PROPOSED|ACCEPTED|APPLIED|OBSERVED|FINALIZED|
STAGED|REJECTED|FAILED|ROLLED_BACK
origin_family telos|introvertiva|synt|multi_tool|canonical|user
origin_module scamper|dedupe|request_new_executor|L1|L2|feedback|...
intent_kind create_executor|extend_executor|dedupe_executors|
materialize_pipeline|cache_pattern|reject_pattern
intent_target nome executor o pattern
intent_summary 1 frase user-facing
intent_body dict kind-specific (arg_name, tools_sequence,...)
score 0–1 normalizzato cross-source
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 grace period
Il fingerprint NON include origin_family: cosi'
due sorgenti diverse che propongono cose equivalenti (es. telos:scamper +
introvertiva:specialize sullo stesso executor) coalescono in un solo
record, con convergence bumpato.
+-----------+ | PROPOSED | ← qualche sorgente l'ha generata +-----------+ / | \ / | \ (utente) | (utente decide piu' tardi) / | \ v v v +----------+ +--------+ +---------+ | ACCEPTED | | STAGED | |REJECTED | +----------+ +--------+ +---------+ | (daemon applier) v +----------+ +----------+ | APPLIED | ----> | FAILED | (retry possibile) +----------+ +----------+ | (daemon observer, grace period default 7gg) v +----------+ +-------------+ | OBSERVED | ----> | ROLLED_BACK | (fisico, per kind) +----------+ +-------------+ | v +-----------+ | FINALIZED | ← consolidato, rimosso dalle viste default +-----------+
Da QUALSIASI stato si puo' transizionare a ROLLED_BACK
(escape hatch audit umano). Da REJECTED e' possibile
re-proporre (transition a PROPOSED).
Le sorgenti storiche non vengono cancellate: continuano ad esistere
come «input feed» che alimenta il materializer. Per ognuna c'e' un
adapter in runtime/change_intent_adapters/ che proietta i record
legacy in ChangeIntent.
| Sorgente | Adapter | Kind output |
|---|---|---|
| telos (10 lenti) | telos.py |
create_executor (default) / extend_executor (parametric) / materialize_pipeline |
| introvertiva | introvertiva.py |
dedupe_executors / extend_executor (generalize) / cache_pattern (specialize) |
| synt (request_new_executor) | synt.py |
create_executor (importato come FINALIZED se gia' installed) |
| multi_tool_paths (L2) | multi_tool.py |
materialize_pipeline |
| canonical_query_log (L1) | canonical.py |
cache_pattern |
| turn_feedback utente | user_feedback.py |
reject_pattern (solo se ≥ 2 rifiuti) |
Score normalization per family:
expected_alignment gia' 0–1uses via log-saturante mid=20
(es. uses=20 → score 0.50, uses=100 → 0.83)n_seen*0.05 + last_uses*0.005 (formula storica)installed=0.8, abandoned=0.3,
rejected*=0.05min(0.9, 0.3 + 0.15*n_rejections)| Daemon | Trigger | Cosa fa |
|---|---|---|
change_intent_materialize |
daily@01:00 | Concat 6 adapter → 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:
synth_request
esistente (~150s wall). Idempotente: short-circuit se gia' nel catalog.change_applier_extend.py.
Append della sezione [args.properties.<arg>] in fondo al
manifest TOML, backup pre-modifica in
~/.local/share/metnos/rollback_blobs/<sha8>-<name>.toml,
re-sign via sign.sign_executor.executor_aliases.json + deprecate del duplicato in
executor_stats.db.UPDATE multi_tool_paths SET
state='active'.UPDATE canonical_query_log SET
state='active'.~/.local/share/metnos/rejected_patterns.jsonl (agent_runtime
lo legge come HARD CONSTRAINT del planner).Observer verifica metrics post-applied_at e applica grace period (default
7 giorni, env METNOS_CHANGE_GRACE_DAYS). Trigger di rollback:
last_call_ok=False con ≥3 chiamate;
deprecato dall'executor_ager.demoted.Rollback fisico delegato a change_rollback.py (per kind:
archive synth dir, restore manifest da blob + re-sign, rimuovi alias,
demote state, rimuovi linea jsonl).
find_recipesTelos engine SCAMPER suggerisce: «Estendi find_files con
kind=recipe per matchare file .md in
~/Documents/Recipes.»
| Atto | Stato | Dove succede |
|---|---|---|
| 1. Sistema crea ChangeIntent | PROPOSED | materializer @01:00 da telos_proposals.jsonl |
2. Utente clicca ✓ | ACCEPTED | POST /admin/changes/{id}/accept |
| 3. Applier modifica manifest + re-sign | APPLIED | change_applier @every_10m |
4. 5 chiamate find_files(kind=recipe), tutte OK |
OBSERVED | change_observer @03:15 quando age ≥ 0 |
| 5. Dopo 7gg senza problemi | FINALIZED | change_observer al settimo passaggio |
Se invece dopo apply Roberto preme ✗ in chat su una
risposta usando il nuovo arg, observer vede new_rejects≥2 per la query
target e transiziona a ROLLED_BACK — physical restore
del manifest dal blob.
?limit=100|500).Accept: application/json (no text/html).python3 -m pytest runtime/tests/test_change_*.py -v
(55 test).python3 -m runtime.jobs.change_intent_materialize (idempotente).sqlite3 ~/.local/state/metnos/change_intents.sqlite.~/.local/share/metnos/audit/change_{intent_materialize,applier,observer}.jsonl.— allineato con il codice.