types — contratti condivisi, namespace canonico
types è l'unico doc che elenca in modo autorevole tutti i tipi dati
che attraversano più di un componente di myclaw. Non li ridefinisce: li indicizza.
Ogni tipo ha un solo doc-proprietario; qui si traccia chi lo definisce,
chi lo consuma, e le regole per evitare drift.
PlannedAction (bridge policy↔constitution) e
TrustScore (autonomia adattiva).src/myclaw/types/, ma questo doc descrive il contratto, non il package).BudgetLedger sta in policy, SessionToken sta in gateway).
La revisione incrociata dei 17 doc di fase 0 ha mostrato che gli stessi tipi
(ExecutionTrace, ApprovalRequest, ViolationReport, …)
erano usati da 3-6 componenti ciascuno, ma definiti — o peggio, redefiniti
in piccolo — in doc diversi. Prima di scrivere una riga di codice, fissiamo una
regola:
file#ancoraN
ma non lo ridefiniscono. Il test di drift (§7) fallisce se due doc
contengono una class X: con lo stesso nome di tipo cross-componente.
Perché non un modulo Python con tutti i tipi? Perché creerebbe un
cerchio: agent_runtime importa da types, types importa da
constitution per ViolationReport, e così via. Preferiamo il
pattern "owner doc → package di componente": ogni tipo vive nel package del suo
componente (src/myclaw/constitution/types.py, src/myclaw/approval/types.py),
e src/myclaw/types/__init__.py li re-esporta per scrittura ergonomica
(from myclaw.types import ExecutionTrace, ApprovalRequest). Zero dipendenze
circolari, unica sorgente di verità.
La tabella sottostante è l'anagrafe canonica dei tipi cross-componente di myclaw. Ogni riga è la coppia <tipo, owner>. Modifiche al tipo richiedono edit del solo owner-doc e allineamento di questa tabella.
| Tipo | Owner doc § | Consumatori principali |
|---|---|---|
ExecutionTrace |
agent_runtime §5 | eval memory policy observability synapse synthesizer rl_offline |
TraceStep (union) |
agent_runtime §5 | eval approval_ux observability |
ThoughtStep, ActionStep, CriticStep |
agent_runtime §5 | eval observability rl_offline |
ApprovalRequest |
approval_ux §9 | gateway channel policy observability |
Approval |
approval_ux §9 | gateway agent_runtime observability |
BatchGrant |
approval_ux §9 | policy observability |
UndoResult |
approval_ux §9 | gateway channel |
PolicyDecision |
policy §7 | agent_runtime gateway observability |
BudgetStatus |
policy §7 | agent_runtime observability |
LawId |
constitution §10 | policy observability |
ConstitutionDoc |
constitution §10 | agent_runtime policy |
ViolationReport |
constitution §10 | policy agent_runtime observability |
ToolMeta |
tool §2 | agent_runtime policy |
ToolError |
tool §5 | agent_runtime observability |
OutboundMessage, Button |
channel §2 | approval_ux gateway |
ChannelCapabilities |
channel §2 | gateway approval_ux |
MemoryBundle |
memory §9 | agent_runtime |
Episode, SemanticFact |
memory §9 | rl_offline synthesizer |
NeuronManifest |
neuron §10 | synthesizer synapse agent_runtime |
NeuronHandle, NeuronState |
neuron §10 | synapse agent_runtime rl_offline |
PlannedAction nuovo |
types §4 | policy constitution agent_runtime |
TrustScore nuovo |
types §4 | policy synapse memory rl_offline |
class X: o X = Literal[...]. "Consumatori" indica i doc
che importano e/o descrivono l'uso, non che ridefiniscono.
Due tipi non hanno un owner naturale: attraversano più componenti senza appartenere a nessuno in particolare. Vivono qui.
PlannedAction — bridge policy ↔ constitution
constitution.html §10 dichiara Constitution.check(action: PlannedAction)
ma non definisce mai PlannedAction. È una lacuna della revisione incrociata
(gap semantico B2). Il tipo serve come richiesta strutturata che la policy
sottopone alla constitution prima di decidere il verdetto.
from dataclasses import dataclass
from datetime import datetime
from typing import Literal
from uuid import UUID
EffectKind = Literal[
"fs_read", "fs_write", "fs_delete",
"shell_exec", "network_out", "network_in",
"memory_write", "memory_delete",
"neuron_spawn", "neuron_invoke", "neuron_promote",
"outbound_message", # verso utente / terzi
"cost_spend", # LLM / API
"config_change", "self_modify",
]
@dataclass(frozen=True)
class PlannedAction:
"""
Descrizione canonica di un'azione che sta per avvenire.
Passata a Constitution.check() dalla Policy.evaluate().
Immutable: riflette l'intento registrato, non l'esito.
"""
kind: EffectKind
tool_name: str # tool richiesto (anche "neuron:<name>")
args_digest: str # SHA-256 dei parametri (no PII in log)
args_preview: dict # versione redatta per UI/prompt
sender: str # "cli:roberto" | "telegram:@roberto" | "cron:..."
trace_id: UUID
session_id: str
autonomy: Literal["read_only", "supervised", "full"]
targets: list[str] # path, URL, neuron name — quel che viene toccato
reversibility: Literal["reversible", "reversible_with_cost", "irreversible"]
est_cost_eur: float # 0 se non applicabile
planned_at: datetime
Perché qui e non in constitution? Perché è costruita dalla
policy, letta dalla constitution, persistita dall'observability,
e serializzata nella trace. Nessun owner unico naturale. Centralizzandola
in types evitiamo dipendenze circolari fra policy e constitution.
PlannedAction.args_digest è calcolato sull'intero
args, ma args_preview è redatto: niente segreti, token, path assoluti
di file utente. La policy e la constitution vedono lo stesso digest; l'audit
log e l'approval UX vedono il preview.
ApprovalRequestDraftgateway.html §4.1 introduce ApprovalRequestDraft come
output della Policy. I due tipi non sono sinonimi: hanno ruoli distinti e qui
li fissiamo canonicamente.
@dataclass(frozen=True)
class ApprovalRequestDraft:
"""
Restituito dalla Policy quando il verdetto è 'approve_required'.
È un PlannedAction arricchito di ciò che la Policy ha deciso ma che non
sa ancora rendere in UI: il Gateway lo completa con canale, sender, timeout
per produrre una ApprovalRequest finale.
"""
action: PlannedAction # riferimento immutabile all'intento
effect_class: str # es. "fs_write:workspace"
summary: str # linea leggibile per l'utente
risk: Literal["low", "medium", "high"]
policy_reasons: list[str] # perché richiesta (per explain-mode)
Regola: la Policy NON costruisce ApprovalRequest. Emette
solo il Draft. Il Gateway è l'unico che crea la request finale (vedi
gateway.html §4.1, decisione "owner unico del flusso"). Questo
risolve l'incongruenza §3.3 della review di coerenza.
TrustScore — asse di autonomia adattiva
La revisione cross-doc ha convergiato su un principio: ridurre il carico di
approvazione umana senza perdere sicurezza richiede un'autonomia
trust-weighted. Ogni coppia (neurone, effect_class) accumula uno score
calcolato da trace storiche; la policy usa lo score per promuovere dinamicamente
approve_required → allow sotto soglia di rischio. Nessun training, solo aggregazione.
from dataclasses import dataclass, field
from datetime import datetime
from typing import Literal
TrustSubject = tuple[str, str] # (neuron_name | "core", effect_class)
@dataclass
class TrustScore:
"""
Score aggregato di fiducia per una coppia (attore, classe d'effetto).
Non è gradient-learned: è media ponderata di segnali oggettivi.
Tutti i campi in [0.0, 1.0] salvo n_samples.
"""
subject: TrustSubject
# Componenti (aggiornate da rl_offline via trace reduce):
tool_success: float # % ActionStep con exec_result ok
human_approval_rate: float # approve / (approve + deny) storico
cost_efficiency: float # 1 − (cost_effettivo / cost_mediano_classe), clip
constitution_clean: float # 1 − (n_violation / n_samples)
undo_rate: float # % di Approval seguite da undo (invertito nello score)
# Aggregato:
score: float # 0 = sempre richiedi approvazione, 1 = massima fiducia
# Metadati:
n_samples: int # trace considerate
last_updated: datetime
last_decay_at: datetime # pesi esponenziali Ebbinghaus-like (decay 30g default)
window: Literal["7d", "30d", "90d", "lifetime"] = "30d"
@dataclass(frozen=True)
class TrustThreshold:
"""Soglia sotto cui la policy promuove approve_required → allow."""
effect_class: str
risk_band: Literal["low", "medium", "high"]
min_score: float # score minimo richiesto per promozione
min_samples: int # n_samples minimi richiesti
cap_autonomy: Literal[
"read_only", "supervised", "full",
] = "supervised" # non promuovere sopra questo livello
TrustScore è puramente aggregativo (media ponderata,
decay esponenziale). Nessun modello appreso in fase 1. La formula di aggregazione
è score = 0.30·tool_success + 0.25·human_approval_rate + 0.20·cost_efficiency + 0.15·constitution_clean + 0.10·(1−undo_rate).
Motivo: ogni componente è audit-friendly e direttamente debuggabile. La promozione a modello
appreso è deferrata (valutare se/quando in rl_offline.html §esperimenti).
TrustThreshold per ogni
effect_class sono un parametro di sicurezza. In v1 sono tutte cap a supervised
(cioè il trust abilita l'auto-approve dentro il batch window, ma non rimuove la
catena umana se l'autonomy è read_only). La promozione automatica a full
resta una scelta dell'utente. Rivalutare dopo 3 mesi di uso.
I due tipi sono agganciati: la policy costruisce una PlannedAction, legge il
TrustScore associato al subject = (tool_name, effect_class), e decide:
# Pseudocodice — la firma reale sta in policy.html §7
def evaluate(tc: dict, trace: ExecutionTrace, sender: str, autonomy: str) -> PolicyDecision:
action = PlannedAction.from_tool_call(tc, sender, trace, autonomy)
# 1. Leggi costituzionale, mai scavalcabile
viol = constitution.check(action)
if viol and viol.law in ("law.0", "law.1"):
return PolicyDecision(verdict="deny", reason=viol.reason, ...)
# 2. Trust-gating per azioni a side-effect
if action.kind in SIDE_EFFECT_KINDS:
score = trust_store.get(subject=(action.tool_name, effect_class_of(action)))
threshold = trust_thresholds.for_effect(effect_class_of(action), action.reversibility)
if score.n_samples >= threshold.min_samples \
and score.score >= threshold.min_score \
and autonomy_le(autonomy, threshold.cap_autonomy):
return PolicyDecision(verdict="allow", via="trust_promoted", ...)
return PolicyDecision(verdict="approve_required", ...)
return PolicyDecision(verdict="allow", ...)
Tutti i tipi che finiscono in workspace/.audit/ o in risposta REST seguono
un contratto comune.
schema_version obbligatorio in ogni oggetto top-level persistito (es. ExecutionTrace, ApprovalRequest, NeuronManifest). Valore semver corto: "1.0", "1.1", "2.0".datetime come ISO-8601 con Z, UUID come stringa, bytes come base64. Enum Literal sono stringhe..jsonl (una riga = un oggetto). Mai riscrivere, mai editare in-place. L'edit di una trace è una nuova trace.schema_version precedenti dello stesso major; campi sconosciuti vengono preservati in unknown_fields: dict per round-trip.~/private/, niente chiavi API. La redazione avviene prima della serializzazione (PlannedAction.args_preview è l'esempio canonico).| Cambio | Semver | Chi autorizza | Annuncio |
|---|---|---|---|
| Aggiungere un campo opzionale | minor | Owner-doc autore | Changelog nel doc-owner |
Aggiungere un valore a un Literal[...] |
minor | Owner-doc autore | Changelog nel doc-owner; deprecazione se rimpiazza un altro valore |
| Rimuovere un campo / rinominare | major | Gate approvazione umana esplicita | Annuncio in types.html changelog + periodo di overlap ≥ 1 release |
| Cambiare tipo di un campo | major | Gate approvazione umana esplicita | Stesso sopra |
| Aggiungere un nuovo tipo cross-componente | minor | Owner-doc + aggiornamento §3 di questo doc | Riga nella tabella §3 |
ExecutionTrace o
ApprovalRequest può invalidare scenari eval e trace archiviate. Non
succede da solo: è una decisione di progetto, merita frizione intenzionale.
Per evitare di trasformare types.html in un doppione universale, vale
questa lista di esclusione:
BudgetLedger (policy), SessionToken (gateway), WorkspaceLayout (workspace): restano nel loro doc.Policy, Channel, Memory: restano nel loro doc-proprietario. Qui elenchiamo solo i dati.TelegramChannel, HybridPolicy) vivono nel codice, non nei doc di architettura.FastAPI, httpx.Response, pydantic.BaseModel: non sono tipi myclaw.Exception interni a un componente (es. ToolNetworkError) restano in tool.html; qui citiamo solo ToolError (dataclass pubblico).
Il drift fra doc e codice è il peggior nemico di questa struttura. I test sotto
sono obbligatori in CI (come gate di eval.html) e contract-enforcing.
| Invariante | Test |
|---|---|
| Ogni tipo in §3 esiste nel codice con lo stesso nome | Per ogni riga della tabella §3: importlib.import_module("myclaw.types").__dict__ contiene il simbolo. |
| Ogni tipo ha un solo doc-proprietario | Grep nei 18 doc architettura: per ogni nome di tipo cross-componente, al massimo una class X: o X = Literal[...] esiste. Fallisce se duplicato. |
| Ogni consumatore dichiarato in §3 importa effettivamente il tipo | AST scan del codice: per ogni (tipo, consumatore) in §3, il package src/myclaw/<consumatore>/ ha almeno un from myclaw.types import ...<tipo>. |
schema_version presente in ogni oggetto persistito |
Rileggere 100 trace recenti da workspace/.audit/: json.loads(line)["schema_version"] non solleva. |
| Round-trip di un oggetto con campi sconosciuti | Serializzare una ExecutionTrace v2.0, deserializzarla con loader v1.9: i campi nuovi finiscono in unknown_fields, nessuna eccezione. |
Redazione di PlannedAction.args_preview |
Costruire una PlannedAction con args = {"api_key": "sk-..."}: il preview non deve contenere il valore. args_digest è stabile rispetto all'originale. |
Formula TrustScore.score è una combinazione lineare documentata |
Con componenti tutte a 1.0, score ≈ 1.0 (± 1e-9). Con una a 0.0, score cala di esattamente il peso dichiarato in §4.2. |
| Cambio major richiede firma | Fixture: un doc che sposta da str a int un campo esistente senza toccare schema_version major deve far fallire la CI. |
agent_runtime.html
§8) contengono Python eseguibile che ridichiara il tipo per autosufficienza
dell'esempio. La convenzione è: walking skeleton va in un blocco <pre> marcato
con data-skeleton="true" e il test lo ignora. Da implementare nel linter CI.
Ogni tipo linkato in §3 punta al doc-proprietario. Questa sezione è il riassunto per lettura rapida.
| Owner doc | Tipi canonici |
|---|---|
agent_runtime.html §5 |
ExecutionTrace, TraceStep, ThoughtStep, ActionStep, CriticStep |
approval_ux.html §9 |
ApprovalRequest, Approval, BatchGrant, UndoResult |
policy.html §7 |
PolicyDecision, BudgetStatus |
constitution.html §10 |
LawId, ConstitutionDoc, ViolationReport |
tool.html §2, §5 |
ToolMeta, ToolError |
channel.html §2 |
OutboundMessage, Button, ChannelCapabilities |
memory.html §9 |
MemoryBundle, Episode, SemanticFact |
neuron.html §10 |
NeuronManifest, NeuronHandle, NeuronState |
| questo doc §4 | PlannedAction, EffectKind, TrustScore, TrustThreshold, TrustSubject |
Per il razionale architetturale (perché esistono i 4 strati, perché le 4 Leggi, perché il trust-weighted) vedi i doc di Livello 1: Architettura Intro, Prospettive & Giudizio, Neuroni & Memoria.
PlannedAction, TrustScore, ViolationReport. È il primo utilizzatore di questi tipi.TrustScore. I 3 esperimenti trace-based, synthesizer reward, neuron self-play.
myclaw — types microprogettazione v1.0 — 2026-04-22
Doc trasversale. Sblocca B1 della revisione incrociata. Introduce PlannedAction e TrustScore.