ops — top-level modules
ops.apply
The execute/rollback primitive: write a knob value into the machine-owned overlay (§14).
execute and rollback in the self-mod loop both come down to mutating ONE file —
config/levers.toml, a gitignored overlay the loop owns end to end. This module is that
mutation, kept deliberately small and free of any policy: it writes the value it is handed.
All the judgment (bounds, approval, validation) lives upstream; by the time a value reaches
here it has already cleared the gate.
Two properties matter for §14's "reversible":
* The loop NEVER edits the owner's hand-authored config/local.toml. It writes only
levers.toml, and the loader overlays local.toml on top of it, so a human override
always wins (config/loader.py). The writable surface is one file, fully auditable.
* Every write returns the EXACT prior overlay state for that key (a value, or "absent").
Rollback replays that prior state — restoring a previous value, or removing the key if the
loop had introduced it — so an executed change reverses with no residue.
The on-disk format is a strict subset (sections of scalar key = value) that this module both
reads (via tomllib) and re-emits deterministically, so the file round-trips and diffs cleanly.
read_overlay(path=LEVERS_OVERLAY)
Parse the overlay into {section: {key: value}}. Missing file → empty (no knobs tuned).
write_overlay(data, path=LEVERS_OVERLAY)
Emit data deterministically (sorted sections + keys) and replace the file atomically.
Values are written untyped-by-section here because callers always pass already-coerced
numbers; the kind only affects formatting, handled in overlay_set/overlay_restore which
pre-format. Here we render whatever scalar is present.
overlay_get(lever, path=LEVERS_OVERLAY)
The value this lever currently has IN THE OVERLAY (what the loop last set), or None if the loop has never set it (the knob is at its committed default / a human override).
overlay_set(lever, value, path=LEVERS_OVERLAY)
Set the lever's overlay value; return the PRIOR overlay value (None if it was absent), so the caller can record exactly what to restore on rollback.
overlay_restore(lever, prior, path=LEVERS_OVERLAY)
Reverse an overlay_set: restore the prior value, or remove the key entirely if the loop
had introduced it (prior was None). Leaves an empty section pruned so the file stays clean.
ops.ci_witness
The CI witness — attest GitLab pipeline verdicts into the chain (owner rule 2026-07-11).
A green pipeline is a claim by an external system; the witness turns it into an
ATTESTED fact: it fetches the pipeline for a commit from the GitLab API and emits
ci_witness / pipeline_green|pipeline_red with the commit sha as input and
pipeline:<id> as output — chained to the code-sensor's ingest of the same sha, so
"this commit's tests passed remotely" is signed history, not a memory of a web page.
Zone note, stated honestly: this runs UNSEALED at the ops tier and talks to gitlab.com
(stdlib urllib) — the restic/terraform precedent (ops tools reach services from their
own process; the sealed core never does). It is invoked standalone or as a subprocess
of palace deploy (whose own process IS sealed; the witness child is not).
Auth: pipeline metadata on this project is public (no token). Playing the manual
semantic-release job needs a token — read from Keychain (security find-generic-password
-a mind-palace -s gitlab-api -w), never from config or argv. Absent a token, release
degrades to printing the pipeline URL for a by-hand play.
PROJECT = 'ascalva-projects/mind-palace'
module-attribute
API = 'https://gitlab.com/api/v4/projects/' + urllib.parse.quote(PROJECT, safe='')
module-attribute
pipeline_for(sha)
Newest pipeline for the commit, or None if GitLab has none for it.
verdict(pipe)
'green' | 'red' | 'pending' | 'absent' — the witness never guesses.
attest_verdict(sha, pipe, v)
check(sha, *, wait_s=600.0)
Poll to a terminal verdict, attest it, rc 0 only on green.
rotation_expiry(days=30)
rotate()
Self-rotate the Keychain token (owner rule 2026-07-11): the old token is revoked server-side the moment rotation succeeds, so the ordering is fail-safe by design — VERIFY the new token works, THEN overwrite Keychain, THEN read back and compare, THEN attest the event (token id + expiry; never the secret). Any failure after rotation prints loud instructions to re-mint — the fail state is a dead token and a five-minute manual re-mint, never a silently broken credential.
Residual exposure, stated honestly: security add-generic-password -w places the
secret on argv for the write (same exposure as the owner's one-time setup command,
single-user Mac). Keychain has no stdin write path for -w; accepted and recorded.
release(sha)
Play the manual semantic-release job for the sha's pipeline (token required).
ops.code_sensor
The code sensor — a model-less pipeline agent over the repo instrument.
Proves the agent platform's "code acts" half with zero inference: an agent is its declared
tools, wired at build time, every act attested. This one holds exactly three handles — the
git instrument (read-only), the snapshot ledger (its ONE designated store), and the
attestor — and nothing else: no model, no embedder, no corpus store, no network. It is the
same species as the vault watcher (core/ingest/sync.py), the deterministic-ingest agent
pattern, and deliberately NOT a factory-minted role: PRE_DECLARED_MAX holds no git or
store handle (§10), so instrument agents are wired by build_*, never minted.
Sensor framing (docs/brainstorms/code-as-sensor-stream.md): the repo is an instrument, the
commit stream is sensed data, ops/code_snapshot.py is the interpreter φ_code, the ledger
is the normalized store — event-log-only, outside the knowledge corpus. sync() is the
watcher's rescan semantics: ledger-vs-history reconciliation, oldest first, idempotent — a
missed post-commit hook heals on the next invocation, and a no-op sync is free.
CodeSyncReport
dataclass
ingested = 0
class-attribute
instance-attribute
ledger_total = 0
class-attribute
instance-attribute
shas = field(default_factory=list)
class-attribute
instance-attribute
CodeSensor
dataclass
Tools are the wiring; the agent is the discipline over them.
repo
instance-attribute
db
instance-attribute
attestor = None
class-attribute
instance-attribute
branch = 'main'
class-attribute
instance-attribute
sync()
Reconcile the ledger against the ingestion branch; snapshot + attest what's missing.
build_code_sensor(config=None)
Wire the agent's three tools against the real repo, ledger, and attestation chain.
ops.code_snapshot
Per-commit structural snapshot of the code — the build tracking its own shape.
Git already stores the text of every commit; what it does not give the evolution study is
the structure as queryable data. This module walks a commit's tracked *.py blobs (the
COMMITTED tree via git show, never the working directory), parses each with ast, and
records the skeleton — symbols (functions/classes with signatures), imports, LOC, per-file
blob hashes — keyed by commit SHA. Diffing two SHAs answers "what changed structurally,"
which git can only answer textually.
A small dedicated SQLite (data/code_snapshots.sqlite), the same pattern as the run ledger —
and like the run ledger it is BUILD history, not corpus: palace reset guards it. Idempotent
by SHA (re-snapshot is a no-op), so a post-commit hook and a history backfill share one path.
Deliberately NOT here: ingesting snapshots into the knowledge corpus. Source code as a corpus source class is an open design question (finding-0021 — code is builder-produced reality, not owner belief; its provenance label is unsettled). This ledger stays on the ops side until that is ratified.
Symbol
dataclass
kind
instance-attribute
qualname
instance-attribute
lineno
instance-attribute
signature
instance-attribute
FileShape
dataclass
path
instance-attribute
blob_sha
instance-attribute
loc = 0
class-attribute
instance-attribute
symbols = field(default_factory=list)
class-attribute
instance-attribute
imports = field(default_factory=set)
class-attribute
instance-attribute
parse_error = False
class-attribute
instance-attribute
functions
property
classes
property
parse_header(subject)
(ctype, scope) from a conventional-commit subject; ('', '') when non-conforming.
parse_source(path, blob_sha, source)
snapshot_commit(db, repo, rev='HEAD', *, _cache=None)
Snapshot one commit's tree. Idempotent: returns None if the SHA is already recorded.
_cache (blob_sha -> parsed shape) lets a backfill parse each unique blob once ever.
backfill(db, repo)
Snapshot every commit on the current branch, oldest first. Idempotent.
annotate_headers(db, repo)
Fill subject/ctype/scope on rows snapshotted before the header columns existed —
ONE git log for all of history, self-healing on every sync (rescan discipline).
open_snapshot_db(path)
ops.effect_catalog
The effector catalog + the SKILL-mining pipeline as data (Track G, item G4).
The catalog is the SINGLE source of truth for the hands the effector layer can express. Each
entry is the recorded outcome of the §8 security audit run as a repeatable process (the
SKILL-mining pipeline, docs/design-notes/skill-mining-pipeline.md):
read the source as untrusted → re-implement NATIVELY (never import third-party skill code)
→ classify reversibility (this sets w(β)) → mint SCOPE, not a credential
→ assign the §11 sandbox exec profile → attest proposal + approval → catalog + property-test.
Adding a hand is therefore a reviewed diff to _CATALOG below — the lever-registry move applied
to the effector layer: the gate (ops/effect_gate) consumes this registry but does not own it,
so a proposal can only ever target a hand that has been through the audit (get_actuator raises on
anything else, exactly like get_lever).
Why the acting classes are here yet still safe. draft_reply / calendar_hold / stage_file
(REVERSIBLE, β small) and send_email (IRREVERSIBLE, β = ∞) are cataloged so G5/G6 can reference
them — but a cataloged hand is not a reachable hand. Three independent structural facts keep
them off:
- the wired sensing surface admits effects at ceiling ε = SENSING (
core.sensing.SensingHandoff→EffectView.admit(..., ceiling=SENSING)), so a non-sensing effect raises before it reaches any handoff (the §4 filtration as a type); - an
Effectof a non-sensing class is unconstructable without an approval reference covering w(β) (ops.effects.Effect.__post_init__); and - the gate denies unless a matching, unexpired, per-action scoped capability was minted
(
ops.effect_gate.effect_gate_admits) — no ambient authority exists to fire one.
So cataloging a hand records that it passed the audit; enabling it is a separate, deliberate act (raising ε past its class once its property tests are green — §4).
CATALOG = {(e.spec.name): e for e in _CATALOG}
module-attribute
ACTUATORS = {name: (e.spec) for name, e in (CATALOG.items())}
module-attribute
ActuatorSpec
dataclass
One registered hand — exactly what the GATE needs to decide about it: its name, its
reversibility class (which sets w(β)), the capability scope it requires, and the CLOSED set of
param keys it accepts. max_param_chars caps one param value (per-actuator, so a draft body is
not forced through the sensing-sized 256-char hole). The richer audit metadata lives on
CatalogEntry, which wraps this — the gate does not need to know a hand's sandbox profile to
decide, only its class and scope.
name
instance-attribute
reversibility
instance-attribute
scope
instance-attribute
param_keys
instance-attribute
description = ''
class-attribute
instance-attribute
max_param_chars = 256
class-attribute
instance-attribute
CatalogEntry
dataclass
The audited catalog record for one hand: its gate-facing spec plus the §8 audit evidence.
sandbox_profile names the §11 powerless exec profile the hand runs under (a label the
pipeline doc defines — the type carries the reference, not the enforcement). source records
where the capability was mined from (an ecosystem SKILL.md, or "native"), treated as
untrusted and re-implemented. audited is True only when every step of the §8 checklist has
been walked — a required field, so a half-audited hand cannot be added without saying so.
spec
instance-attribute
sandbox_profile
instance-attribute
source = 'native'
class-attribute
instance-attribute
audited = False
class-attribute
instance-attribute
notes = ''
class-attribute
instance-attribute
get_actuator(name)
Look up a cataloged hand's gate-facing spec. Raises (fail-closed) if name is not in the
catalog — a proposal can only ever target a hand that passed the §8 audit.
get_entry(name)
Look up a cataloged hand's full audit record. Fail-closed on an uncataloged name.
actuators_for(reversibility)
Every cataloged hand of a given reversibility class (the §4 filtration slice) — the reviewed contents of Effects at exactly this blast-radius band, for the catalog + rollout gate.
ops.effect_exec
Execute an irreversible / external effect under a just-in-time credential (Track G, item G6).
The irreversible class (send, pay, post, actuate) is the hardest gate: no undo. §8.4 answers the confused deputy for it — mint scope, not credential, at the moment of action, never ambient. So this executor is the one place a live credential briefly exists, and it exists only for the span of a single approved effect:
- Refuse before minting. The
Effectis IRREVERSIBLE and FULL_GATE-approved (the type already guarantees it cannot exist otherwise); the executor re-asserts that, plus the recordedproposedandattestedfacts. If any is false, it raises — no credential is minted for a doomed effect (a persuaded reasoner cannot even cause a mint). - Mint JIT. Only then does it ask the Vault backend for a short-TTL token scoped to exactly
this actuator's policy (
secrets.mint_token(scope, ttl)). The freshly minted capability is what satisfies the gate'sscoped_cap_validconjunct — there is no ambient authority to reuse. - Gate, then act.
effect_gate_admitsis checked over the recorded facts + the fresh capability (defense in depth; True by construction here). Only on admit does the injected transport perform the effect, holding the token transiently. - Attest, then discard. An attested action record is appended (the token accessor, never the token — the Step-5 Vault↔attestation join), and the token leaves scope. Nothing retains it.
The transport is injected (EffectTransport): the real send/pay transports live in Zone B (edge)
and touch the network; this module orchestrates mint→gate→attest and never imports them, so it holds
no network dependency itself (a FakeEffectTransport proves the wiring, exactly as FakeVault
proves the credential path without a live Vault).
EffectTransport
Bases: Protocol
The minimal outward-effect surface. perform receives the JIT token for THIS action and
returns a receipt/id (a string the attestation hashes). Real implementations live in Zone B and
touch the network; they never see anything but the token minted for this single effect.
perform(actuator, params, *, token)
EffectDenied
Bases: RuntimeError
The effect was refused before/at the gate — no credential minted, nothing performed.
ExecRecord
dataclass
The record of one performed irreversible effect. Carries the token ACCESSOR (a non-secret audit handle) and the attestation id — deliberately NO token field, so the credential cannot be logged or persisted through this record (the MintedToken discipline, held one step harder).
actuator
instance-attribute
accessor
instance-attribute
attestation_id
instance-attribute
receipt
instance-attribute
IrreversibleExecutor
dataclass
Performs approved irreversible effects under a per-action JIT credential. Holds the mint
authority (secrets), the attestor, and the transport — never a credential between calls.
secrets
instance-attribute
attestor
instance-attribute
transport
instance-attribute
credential_ttl = '60s'
class-attribute
instance-attribute
agent_role = 'effector'
class-attribute
instance-attribute
execute(effect, params, *, proposed, attested)
Perform one approved irreversible effect. proposed / attested are the recorded facts
(from the EffectLedger / attestation store) the gate conjoins. Raises EffectDenied (and
mints nothing) unless the effect is a FULL_GATE-approved irreversible one with those facts
true; the JIT credential is minted only after that check and never retained.
build_irreversible_executor(config=None, *, transport)
Wire the executor against the real Vault backend + attestor. REFUSES unless [secrets] is
enabled (there is no mint authority otherwise) — the acting classes are owner-activated, and the
irreversible one additionally requires a live credential backend. The transport is supplied by
the caller (Zone B): this module never imports a network transport.
ops.effect_gate
The Phase-10 gate generalized from config knobs to world effects (Track G, item G2).
The self-mod gate is already a guarded transition system (I12): s′ = Δ·s iff G_now(Δ,s), else s;
the model emits Δ, code applies it, rejection is identity. Hands generalize Δ from a
ProposedChange (a bounded numeric knob) to a ProposedEffect (a world effect) — the SAME
machine, wider domain, two new conjuncts:
G_effect(E, world) = proposed(E) ∧ approved_{w(β(E))}(E) ∧ scoped_cap_valid(E) ∧ attested(E)
- the approval conjunct is blast-radius-weighted (§4): the strength a class demands is
w(β(E)) (
ops.effects.required_approval) — sensing needs none, reversible needs light, irreversible needs the full gate; and - the scoped-capability check is a first-class conjunct: no matching, unexpired, minted scope ⇒ no effect, regardless of approval (the confused-deputy answer — even a persuaded reasoner cannot fire an effect no capability was minted for).
Because it is the same machine it inherits the FSM-verification discipline: the decision space is small enough to enumerate in full (3 classes × 2 proposed × 3 approval strengths × 2 capability × 2 attested = 72 states), exactly like the 8-state config gate. Nothing new to trust; one machine, wider inputs.
ProposedEffect inherits ProposedChange's structural ceiling: it is an (actuator-name,
allowlisted-string-params) pair and NOTHING more — no field anywhere can hold a file path, a
diff, a command, code, or a URL, so "run this" / "fetch that address" is not a proposal the
layer can express. Actuators resolve fail-closed against the registry below (sensing-only
until G4's catalog process lands; widening it is a visible, reviewable diff — never a guess).
Scope note (stated, not silent): the durable per-effect ledger — the EffectLedger analogue of
ops/ledger.ProposalLedger, with execute/validate/rollback rows — lands with the first class
above β=0 (G5), where there is something to roll back. For β=0 sensing there is no world state
to restore; the guard, the types, and the attestation trail are the whole machine.
ACTUATORS = {name: (e.spec) for name, e in (CATALOG.items())}
module-attribute
ActuatorSpec
dataclass
One registered hand — exactly what the GATE needs to decide about it: its name, its
reversibility class (which sets w(β)), the capability scope it requires, and the CLOSED set of
param keys it accepts. max_param_chars caps one param value (per-actuator, so a draft body is
not forced through the sensing-sized 256-char hole). The richer audit metadata lives on
CatalogEntry, which wraps this — the gate does not need to know a hand's sandbox profile to
decide, only its class and scope.
name
instance-attribute
reversibility
instance-attribute
scope
instance-attribute
param_keys
instance-attribute
description = ''
class-attribute
instance-attribute
max_param_chars = 256
class-attribute
instance-attribute
ProposedEffect
dataclass
A proposed world effect — the ONLY shape an effect proposal can take (the Δ of §6).
It is an actuator name plus allowlisted string params and nothing more. Deliberately there
is no path, diff, command, code, script, or url field: the layer physically
cannot propose "edit this file" / "run this" / "fetch this address", the same way
ProposedChange cannot carry a code change and ResearchCriteria cannot carry a note.
Params are stored as a sorted tuple of (key, value) pairs so the object stays frozen and
hashable; resolve() is the fail-closed door to a dict.
actuator
instance-attribute
params = ()
class-attribute
instance-attribute
rationale = ''
class-attribute
instance-attribute
resolve()
Resolve to (spec, validated-params). Raises on an unknown actuator, a param key
outside the spec's closed allowlist, a non-string value, or an oversized value — all
fail-closed, so an invalid proposal never reaches a decision as proposed.
EffectGateDecision
dataclass
The recorded facts G_effect decides on — the effect analogue of GateDecision.
Every field is a FACT an orchestrator recorded (a ledger row, an attestation, a capability
check), never a judgment made here. approval is the strength actually held (NONE when no
human acted); the strength required is not a field — it is computed from reversibility
via w(β), so a decision cannot claim a weaker requirement than its class demands.
reversibility
instance-attribute
proposed
instance-attribute
approval
instance-attribute
capability_valid
instance-attribute
attested
instance-attribute
get_actuator(name)
Look up a cataloged hand's gate-facing spec. Raises (fail-closed) if name is not in the
catalog — a proposal can only ever target a hand that passed the §8 audit.
capability_covers(capability, spec, *, now=None)
scoped_cap_valid as a decidable fact: the capability's scope is EXACTLY the scope the
actuator requires (no prefix/glob authority — narrow means narrow), and it has not expired.
An unparseable expiry is treated as expired (fail-closed), never as forever.
effect_gate_admits(decision)
G_effect(E, world) = proposed ∧ approved_{w(β)} ∧ scoped_cap_valid ∧ attested (§6).
Fail-closed: every conjunct must hold; any False denies and the world is unchanged (rejection is identity — the caller applies nothing on False). Pure data-in/bool-out: there is no E handle and no apply callback here, so an effect can never self-apply through the gate (I12, inherited). FSM-verified over all 72 states.
ops.effect_ledger
The durable effect ledger (Track G, item G5).
The in-memory HumanGate records that a routed request happened; this SQLite table is the
tamper-evident memory of what the hands tried to DO in the world, whether the owner let them, what
was staged, and — for a reversible write the owner undid — that it was rolled back. It is the
effector twin of ops/ledger.ProposalLedger (which tracks self-mod knob changes); it reuses the
same §14 lifecycle FSM (ops.ledger.LedgerStatus / IllegalTransition) because it is the same
machine, wider domain (§6) — only the recorded facts differ (an actuator + params, not a lever +
numeric target).
The lifecycle, enforced here so a caller cannot skip the safety ordering (I5):
PROPOSED ─approve→ APPROVED ─execute→ EXECUTED ─validate→ VALIDATED (kept)
│ └────rollback→ ROLLED_BACK (undone)
└──────deny──────────────────────→ DENIED
artifact_ref is what rollback acts on: the staged draft path for a reversible write (the edge
effector unlinks it), or the attested send-record id for an irreversible one (which has no world
undo — that is why it is full-gated; the ledger still records it happened). Storage only: the
judgment (the gate predicate, the class→w(β) mapping, the actual staging/send) lives in
ops/effect_gate.py, ops/effects.py, ops/effect_exec.py, and the edge effectors.
Thread-safety mirrors ProposalLedger / AttestationStore: opened check_same_thread=False with a
reentrant lock guarding every method (a scheduler thread may propose while the owner approves).
EffectRecord
dataclass
id
instance-attribute
actuator
instance-attribute
reversibility
instance-attribute
scope
instance-attribute
params
instance-attribute
status
instance-attribute
rationale
instance-attribute
proposer
instance-attribute
approver
instance-attribute
approval_strength
instance-attribute
artifact_ref
instance-attribute
attestation_id
instance-attribute
rollback_reason
instance-attribute
proposed_at
instance-attribute
decided_at
instance-attribute
executed_at
instance-attribute
resolved_at
instance-attribute
EffectLedger
dataclass
path
instance-attribute
get(effect_id)
all()
pending()
propose(actuator, reversibility, *, scope='', params=None, rationale='', proposer='')
Record a new PROPOSED effect. Nothing is staged or sent — this is only the inbox row (the model advises; a human moves it to APPROVED; only then does code act).
approve(effect_id, *, approver='owner', strength=ApprovalStrength.LIGHT)
PROPOSED → APPROVED, recording the strength of the human act. Fail-closed if the strength
does not COVER w(β) for the effect's class — you cannot record a LIGHT ack for an
irreversible effect (the same invariant Effect.__post_init__ enforces, checked earlier
here so the ledger never holds an under-approved record). Never auto-called.
deny(effect_id, *, approver='owner')
PROPOSED → DENIED (terminal).
mark_executed(effect_id, *, artifact_ref=None)
APPROVED → EXECUTED. Records the artifact rollback acts on (a staged draft path for a reversible write; an attested send-record id for an irreversible one).
mark_validated(effect_id)
EXECUTED → VALIDATED (kept — the staged artifact stands / the send is confirmed).
mark_rolled_back(effect_id, *, reason)
EXECUTED → ROLLED_BACK (undone). For a reversible write the edge effector has removed the staged artifact; this records why. (An irreversible effect has no world undo — the full gate is the point; a rollback here means the SEND itself failed, not an un-send.)
attach_attestation(effect_id, attestation_id)
Link the attested action record to this effect (records the accessor-join seam).
close()
open_effect_ledger(config=None)
Wire the effect ledger against the configured effectors data dir (beside sensing handoff).
Independent of [effectors] enabled: the ledger is a record, safe to open read-only for audit
even when the hands are off.
ops.effects
The effector layer's first-class types (Track G, items G1; hands-and-the-effector-layer.md).
Effection is the mirror image of ingestion (companion IV, family 1): MirrorView constrains
read-flow into the introspective agents (a non-authored view is untypable); Effect /
EffectView constrain action-flow out into the world (an unapproved consequential effect is
unbuildable). Same object family — typed labels that constrain flow, enforced by making the
wrong flow unrepresentable — so the guarantees are inherited, not invented.
Three structural facts live here:
- An illegal effect is unconstructable.
Effect.__post_init__raises if a non-SENSING reversibility class has no approval reference, or an approval weaker than its class demands. There is no code path that "checks and refuses" a rogue effect later — the object cannot exist (theMirrorView/ProposedChangemove). - An effect carries no confidence of its own. Worth-doing-ness is u-like — subjective,
owner-judged — and must never be read off the adjudicator's c (companion III's axis
separation, held as a hard rule at the actuator). An
Effectmay cite the motivating interpretations (cites), but deliberately has noconfidencefield: a high-c dream cannot earn an automatic action. - The rollout is a filtration by blast radius (§4).
blast_radius(β) places each reversibility class at a distance from the reversible origin;required_approval(w∘β) is non-decreasing in that distance;EffectViewis Effects_{β≤ε} as a type, with the ceiling ε defaulting to the origin (SENSING). Raising ε is a deliberate, visible act — "you do not get a class until the one below is solid" is structural, not convention.
No credential appears anywhere in these types: ScopedCapability carries a scope name and a
Vault token accessor (a non-secret reference) — execution-time code resolves the accessor to
a live credential at the moment of action (G6, not built), the same discipline as
MintedAgent.token staying off the prompt and off the repr, held one step harder: here the
field does not exist at all.
ReversibilityClass
Bases: IntEnum
The blast-radius classes, in rollout order (hands-and-the-effector-layer.md §3).
Deliberately an IntEnum: unlike provenance — where G8 retired the trust order because no
code used it — the order here is load-bearing. It is the §4 filtration index: β is monotone
in it, w(β) is monotone in it, and EffectView's ceiling comparison is > on it.
SENSING = 0
class-attribute
instance-attribute
REVERSIBLE = 1
class-attribute
instance-attribute
IRREVERSIBLE = 2
class-attribute
instance-attribute
ApprovalStrength
Bases: IntEnum
How much human approval an effect holds (or requires). Ordered so >= means 'covers':
a FULL_GATE approval satisfies a LIGHT requirement, never the reverse.
NONE = 0
class-attribute
instance-attribute
LIGHT = 1
class-attribute
instance-attribute
FULL_GATE = 2
class-attribute
instance-attribute
ScopedCapability
dataclass
The authority envelope an effect requires — a NAME and a reference, never a credential.
scope is what the capability authorizes ("sense:fetch", "send:one-email-to:accessor is the Vault token accessor (the
non-secret handle the factory already attests); "" means credential-free (sensing).
expires_at is an ISO timestamp; "" means bounded by the task, not the clock.
Deliberately there is NO token / secret field — the type physically cannot carry a live
credential, so no agent holding an Effect holds authority (Track G constraint: security
comes from the effector being narrow, not the reasoner being trusted).
scope
instance-attribute
accessor = ''
class-attribute
instance-attribute
expires_at = ''
class-attribute
instance-attribute
ApprovalRef
dataclass
A reference to a recorded human approval act: who approved, at what strength, and
where the act is written down (a ledger row / attestation id — auditable, never implied).
The honesty lives in the orchestrator that only constructs one from a real record, exactly
as GateDecision.approved is a recorded fact, not a guess.
approver
instance-attribute
strength
instance-attribute
ref = ''
class-attribute
instance-attribute
UnapprovedEffectError
Bases: ValueError
A consequential effect was constructed without an approval covering its class — the illegal state the type deletes (dual of NonMirrorRowError).
Effect
dataclass
One typed, scoped, attested world effect (hands-and-the-effector-layer.md §3).
Unconstructable illegally: __post_init__ raises unless the approval reference covers the
reversibility class (None is admissible ONLY for SENSING). Functions typed to accept an
Effect therefore inherit the proof that any consequential effect they see was approved.
cites names the interpretations that motivated the effect (ids into the derived layer)
— a citation, never a confidence. There is deliberately no confidence field here (module
docstring, point 2): the gate weighs an effect by its reversibility class and the owner's
judgment, never by a c it could smuggle in.
actuator
instance-attribute
capability
instance-attribute
reversibility
instance-attribute
proposal_att
instance-attribute
approval_ref = None
class-attribute
instance-attribute
cites = ()
class-attribute
instance-attribute
CeilingExceededError
Bases: ValueError
An effect above the view's blast-radius ceiling ε was offered to an EffectView.
EffectView
dataclass
Effects_{β≤ε} as a type — the write-only-into-world boundary (dual of MirrorView).
Every contained effect is guaranteed to sit within the ceiling ε — the type itself is the
proof, so code typed to accept an EffectView (a dispatcher, a handoff emitter) inherits
"nothing above ε reaches the world through me". Obtain one via admit; direct construction
re-validates, so a hand-built view cannot smuggle a higher class past the filtration.
The ceiling defaults to SENSING (ε = 0, the reversible origin): a fresh surface can dispatch read-only hands and nothing else. Raising ε is a deliberate, per-surface act — the §4 graduated rollout expressed structurally.
ceiling = ReversibilityClass.SENSING
class-attribute
instance-attribute
admit(effects, *, ceiling=ReversibilityClass.SENSING)
classmethod
The sanctioned constructor: admit effects under a declared ceiling. Raises
CeilingExceededError (fail-closed) if any effect's class exceeds ε — admission is
the construction; there is no admitted-but-unchecked state.
effects()
The admitted effects (a fresh list; the view is immutable).
blast_radius(reversibility)
β : Effect → ℝ≥0 — distance from the reversible origin (§4). Sensing sits AT the origin (reversible by definition); reversible writes are a bounded undo away; irreversible external effects are at infinity (no undo exists at any cost).
required_approval(reversibility)
w(β) — approval strength as a non-decreasing function of blast radius (§4): sensing needs none, reversible needs light approval, irreversible needs the full gate. Monotonicity is property-tested (β(a) ≤ β(b) ⟹ w(a) ≤ w(b)) — the effector analogue of 'confidence decays with derivational depth'.
ops.gate
The human gate — seam for routing privileged requests (BUILD-SPEC §10, §14; Invariant 5).
When the factory is asked for capability beyond an agent's scope ceiling, it routes the
request HERE instead of minting a privileged agent (§10). This is the Phase-5 seam: it
records the request as PENDING so nothing privileged ever happens unattended (Invariant 5,
Constitution §II.4). The full propose → approve → execute → validate → rollback ledger landed
in Phase 10 as the durable ops/ledger.ProposalLedger (orchestrated by ops/selfmod.py); this
module remains the lightweight in-memory inbox for routed privileged requests, plus the decidable
gate predicate (gate_admits) that the Phase-10 validate step consumes.
GateStatus
Bases: StrEnum
PENDING = 'pending'
class-attribute
instance-attribute
APPROVED = 'approved'
class-attribute
instance-attribute
DENIED = 'denied'
class-attribute
instance-attribute
GateDecision
dataclass
The measurable facts the live gate decides on (gap G5). conforms is intentionally
absent — it is deferred, not assumed.
approved
instance-attribute
golden_non_regressing
instance-attribute
drift_within_tolerance
instance-attribute
GateRequest
dataclass
id
instance-attribute
kind
instance-attribute
detail
instance-attribute
status
instance-attribute
created_at
instance-attribute
agent = None
class-attribute
instance-attribute
HumanGate
dataclass
Records routed requests. Nothing is auto-approved — approval is a human act (Phase 10 wires the durable SQLite ledger + the approve/execute/validate/rollback loop).
submit(kind, detail, *, agent=None)
pending()
all()
gate_admits(decision)
G_now(Δ,s) = approved ∧ golden-non-regressing ∧ drift-within-tolerance (gap G5).
Fail-closed: every conjunct must hold; any False (or unknown, surfaced as False) denies.
The deferred conforms conjunct is omitted honestly rather than stubbed True.
ops.import_lint
Static import-graph firewall lint (Invariant 2, BUILD-SPEC §3).
I2 is a property of the import closure: if no module under core/ can reach a
network-capable module, then no egress path exists regardless of edge behavior —
composition cannot create one. The Phase-0 runtime egress guard (core.sealing) enforces
this at run time; this lint promotes it to the static tier — provable without running,
by reading the AST. It is the discharge the formal-properties catalog asks for (I2:
"promote from runtime to static").
Two rules, of different strength:
-
Zone non-interference (hard, zero exceptions). No
core/module may importedge(Zone B) orcloud(Zone C). This is the load-bearing structural fact: the sealed core cannot even name the networked zones, so there is nocore → edge → netpath to create. There is no audited exception and there never should be — core↔edge is a filesystem handoff, never an import (CONVENTIONS). -
Networking primitives (allowlisted). No
core/module may import a networking module (socket,ssl,http,urllib,requests, …) EXCEPT the two audited loopback/seal modules:core/sealing.py— importssocketprecisely to WRAP and seal it (the guard).core/models/ollama_client.py— the single sanctioned loopback IPC channel to the local Ollama server (127.0.0.1), which the egress guard permits. Every other core module is thereby statically proven free of networking imports. We state this honestly: the guarantee is "exactly these two audited files touch networking primitives, both loopback-only", not "no core file imports http" — overclaiming the latter would be dishonest (the Ollama channel is real).
Run: python -m ops.import_lint (or scripts/check_imports.py); also asserted in
tests/test_import_firewall.py and wired into CI.
FORBIDDEN_ZONES = frozenset({'edge', 'cloud'})
module-attribute
NETWORK_MODULES = frozenset({'socket', 'ssl', 'http', 'urllib', 'ftplib', 'smtplib', 'poplib', 'imaplib', 'nntplib', 'telnetlib', 'socketserver', 'xmlrpc', 'asyncore', 'asynchat', 'requests', 'httpx', 'aiohttp', 'urllib3', 'websockets', 'websocket', 'boto3', 'botocore', 'paramiko', 'pycurl', 'grpc', 'hvac'})
module-attribute
NETWORK_ALLOWLIST = frozenset({'core/sealing.py', 'core/models/ollama_client.py'})
module-attribute
Violation
dataclass
path
instance-attribute
lineno
instance-attribute
imported
instance-attribute
rule
instance-attribute
scan_file(path, *, repo_root)
scan_core(repo_root=None)
Scan every module under core/ for forbidden imports (I2).
main()
ops.ledger
The durable propose→approve→execute→validate→rollback ledger (BUILD-SPEC §14; Invariant 5).
This is the Phase-10 upgrade of the in-memory HumanGate inbox (ops/gate.py): a SQLite table
that records every self-modification proposal and walks it through a strict lifecycle. The table
is the system's tamper-evident memory of what it tried to change to itself, whether a human let
it, what happened, and — when a change regressed — that it was reversed.
The lifecycle is a finite state machine, enforced here so the safety ordering of §14 cannot be skipped by a caller (Invariant 5: "no step skipped"):
PROPOSED ─approve→ APPROVED ─execute→ EXECUTED ─validate→ VALIDATED (kept)
│ └────rollback→ ROLLED_BACK (reverted)
└──────deny──────────────────────→ DENIED
Each transition asserts the current state is exactly its precondition (fail-closed): you cannot execute something un-approved, validate something un-executed, or approve something already decided. A model can write a PROPOSED row; only a human moves it to APPROVED; only code that holds that approval moves it onward. Storage only — the judgment (bounds, the admit predicate, the apply) lives in ops/levers.py, ops/gate.py, ops/apply.py and is orchestrated by ops/selfmod.py.
Thread-safety mirrors the AttestationStore / JobQueue idiom (PROGRESS 2026-06-27): opened
check_same_thread=False with a reentrant lock guarding every method, since a background
scheduler thread may propose while the foreground approves.
LedgerStatus
Bases: StrEnum
PROPOSED = 'proposed'
class-attribute
instance-attribute
APPROVED = 'approved'
class-attribute
instance-attribute
DENIED = 'denied'
class-attribute
instance-attribute
EXECUTED = 'executed'
class-attribute
instance-attribute
VALIDATED = 'validated'
class-attribute
instance-attribute
ROLLED_BACK = 'rolled_back'
class-attribute
instance-attribute
Proposal
dataclass
id
instance-attribute
lever
instance-attribute
current_value
instance-attribute
target_value
instance-attribute
status
instance-attribute
rationale
instance-attribute
proposer
instance-attribute
approver
instance-attribute
prior_overlay
instance-attribute
metrics
instance-attribute
rollback_reason
instance-attribute
attestation_id
instance-attribute
proposed_at
instance-attribute
decided_at
instance-attribute
executed_at
instance-attribute
resolved_at
instance-attribute
IllegalTransition
Bases: RuntimeError
A lifecycle step was attempted from the wrong state (fail-closed §14 ordering guard).
ProposalLedger
dataclass
path
instance-attribute
get(proposal_id)
all()
pending()
propose(lever, current_value, target_value, *, rationale='', proposer='')
Record a new PROPOSED change. Nothing is applied — this is just the inbox row.
approve(proposal_id, *, approver='owner')
PROPOSED → APPROVED. A human act — never auto-called by the loop (Invariant 5).
deny(proposal_id, *, approver='owner')
PROPOSED → DENIED (terminal).
mark_executed(proposal_id, *, prior_overlay)
APPROVED → EXECUTED. Records the prior overlay value so rollback is exact (NULL means the loop introduced the key and rollback should remove it).
mark_validated(proposal_id, *, metrics=None)
EXECUTED → VALIDATED (kept). Stores the validation metrics that cleared the gate.
mark_rolled_back(proposal_id, *, reason, metrics=None)
EXECUTED → ROLLED_BACK (reverted). Records why an anchor regressed.
attach_attestation(proposal_id, attestation_id)
Link a signed gate-decision attestation to this proposal (records the accessor-join seam; optional, used when [attestation] signing is on).
close()
open_ledger(config=None)
ops.levers
The lever registry — the ENTIRE self-modifiable surface (BUILD-SPEC §14; Invariant 5).
This is the trust boundary the owner drew for Phase 10: self-modification may tune knobs (numeric alignment/quality parameters) and nothing else. Infrastructure and code are an extremely privileged resource the loop cannot write to.
That restriction is structural, not a policy check. A ProposedChange references a Lever
by name and carries a numeric target. There is no field on it — anywhere — that can hold a
file path, a diff, a command, or a Terraform plan. So "edit this file" / "run this" is not a
proposal the loop can express, the same way the airlock's ResearchCriteria has no field that
can carry note content (core/research/criteria.py). A future code/infra lever would be a
deliberate, separately-gated extension with its own apply path and human-only ceiling — adding it
here would be a visible, reviewable diff against this registry, never a guess.
Each lever names a single scalar config key (section.key) and its HARD bounds. The bounds are
the ones already written into config/defaults.toml's [dreaming] comments — promoted here from
prose to enforced code. A target outside [lo, hi] is rejected (fail-closed); the loop cannot
push a knob past the envelope the owner declared safe even with approval.
LEVERS = {(lever.name): lever for lever in _LEVERS}
module-attribute
LeverKind
Bases: StrEnum
FLOAT = 'float'
class-attribute
instance-attribute
INT = 'int'
class-attribute
instance-attribute
Lever
dataclass
One tunable knob: a single scalar config key with hard, owner-declared bounds.
name is the stable identifier a proposal references. section/key locate the value in
the TOML config (and in the machine-owned overlay the loop writes). lo/hi are INCLUSIVE
bounds — the whole admissible range for this knob, never to be exceeded by any approved change.
name
instance-attribute
section
instance-attribute
key
instance-attribute
kind
instance-attribute
lo
instance-attribute
hi
instance-attribute
description = ''
class-attribute
instance-attribute
coerce(value)
Snap a value to the lever's type (int levers carry whole numbers).
in_bounds(value)
validate(value)
Coerce + bounds-check. Raises (fail-closed) on out-of-bounds — the caller never silently clamps, because a proposal that wanted an out-of-range value is a proposal we refuse, not one we quietly rewrite.
ProposedChange
dataclass
A proposed knob change — the ONLY shape a self-modification can take.
It is a (lever-name, target-number) pair and nothing more. Deliberately there is no path,
diff, command, code, or script field: the loop physically cannot propose a code or
infrastructure change because the value object has nowhere to put one. This is the structural
expression of the owner's Phase-10 ceiling (see module docstring).
lever
instance-attribute
target
instance-attribute
rationale = ''
class-attribute
instance-attribute
resolve()
Resolve to (lever, validated-target). Raises on unknown lever or out-of-bounds target — both fail-closed, so an invalid proposal never reaches the ledger as PROPOSED.
get_lever(name)
Look up a registered lever. Raises (fail-closed) if name is not in the registry — a
proposal can only ever target a knob that exists here.
ops.selfmod
The self-modification loop (BUILD-SPEC §14, Phase 10 — the last + most privileged capability).
Ties the pieces together into the §14 cycle, with every step held by deterministic code or a human — never a model:
propose a model may write a PROPOSED knob change (ops/levers.ProposedChange — knobs only,
code/infra structurally unrepresentable). Bounds are checked here; out-of-range
never reaches the ledger.
approve a human moves it to APPROVED (ProposalLedger). The loop never auto-approves on the
attended path.
execute deterministic code writes the knob into the machine-owned overlay (ops/apply.py)
and records the exact prior value for an exact rollback. No model holds the write.
validate run the validator → build the §15 gate decision (ops/gate.GateDecision) from the
FROZEN golden anchor (capability) + the rolling baseline (acute drift). `conforms`
(behavioral) stays honestly deferred, exactly as ops/gate documents.
rollback if the gate denies, mechanically restore the prior overlay value. Keep-or-revert is
not a judgment call — it is `gate_admits(...)` returning False.
Two fail-closed switches gate all of this (config/loader.SelfModConfig): selfmod.enabled is the
master switch; selfmod.unattended_enabled separately gates the ONLY path that skips human
approval (the §14 "safe levers"), and is OFF by default. The owner's Phase-10 ceiling — tune
alignment/quality knobs, treat code and infrastructure as an extremely privileged resource the
loop cannot write — is enforced upstream in ops/levers.py (structural) and here (the switches).
SAFE_LEVERS = frozenset({'dream_max_clusters'})
module-attribute
Validator = Callable[[Lever, float], ValidationResult]
module-attribute
SelfModDisabled
Bases: RuntimeError
The master switch [selfmod] enabled is off — the loop refuses to act (fail-closed).
UnattendedDisabled
Bases: RuntimeError
[selfmod] unattended_enabled is off — no change may skip the human gate (fail-closed).
NotASafeLever
Bases: RuntimeError
A lever not in the pre-declared SAFE_LEVERS set was offered to the unattended path.
ValidationResult
dataclass
What the validator measured about the post-change state. Mirrors the gate's MEASURABLE
conjuncts (ops/gate.GateDecision); conforms (behavioral) is deferred, not stubbed True.
golden_non_regressing
instance-attribute
drift_within_tolerance
instance-attribute
metrics = field(default_factory=dict)
class-attribute
instance-attribute
SelfModLoop
dataclass
Orchestrates one proposal through the §14 cycle. Construct with the live config + ledger for production; tests inject a crafted config, an in-memory ledger, a tmp overlay path, and a stub validator.
config
instance-attribute
ledger
instance-attribute
validator
instance-attribute
overlay_path = LEVERS_OVERLAY
class-attribute
instance-attribute
propose(change, *, proposer='')
Record a PROPOSED knob change. change.resolve() validates bounds first (fail-closed),
so an unknown lever or out-of-range target raises here and never reaches the ledger.
approve(proposal_id, *, approver='owner')
deny(proposal_id, *, approver='owner')
execute(proposal_id)
Apply an APPROVED proposal's knob to the overlay (deterministic code, no model). The ledger enforces APPROVED→EXECUTED, so an un-approved proposal cannot be executed.
validate(proposal_id)
Run the validator on the EXECUTED change and let the §15 gate decide, mechanically: admit → VALIDATED (kept); deny → restore the prior overlay value → ROLLED_BACK.
execute_and_validate(proposal_id)
For an already-APPROVED proposal, run execute then validate. Still no auto-approval — approval is the human act that happened before this is called.
apply_unattended(change, *, proposer='watchdog')
Propose → AUTO-approve → execute → validate, with NO human, for a pre-declared safe
lever. Gated by BOTH switches: refuses unless enabled AND unattended_enabled, and the
lever must be in SAFE_LEVERS. The auto-approval is recorded as approver='unattended' so
the ledger shows plainly that no human signed off. Default config never reaches the body —
unattended_enabled is off.
build_golden_validator(retriever, *, golden=None, frozen_baseline=None, rolling_baseline=None)
Default validator: run the frozen golden set through retriever and decide both §15
conjuncts from it. golden_non_regressing is the STRICT capability check against the FROZEN
anchor (baseline.json, Invariant 9 — any metric below baseline fails). drift_within_tolerance
is now the REAL §15 drift gauge D(Δ·s) ≤ Θ (eval.drift, item A1): a one-sided, normalized,
aggregate deterioration distance over the mixed profile (capability rates ⊕ Constitution
conformance) vs the same frozen anchor — the honest realization of the gate's drift conjunct
(G4/G5), replacing the earlier rolling-regression stand-in.
The two conjuncts are complementary defense-in-depth against the boiling-frog problem (§15):
golden_non_regressing trips on ANY capability drop; the gauge tolerates healthy drift up to Θ
but hard-trips on a Constitution-fingerprint breach and on aggregate deterioration past the
blessed band. The optional rolling_baseline is retained as ADVISORY acute-regression detail in
metrics (it no longer decides the gate).
Imports eval lazily so ops/selfmod stays importable without the eval fixtures loaded.
build_loop(validator, *, config=None, ledger=None)
Wire a loop from live config + the durable ledger. The validator is required (the caller
decides whether to use build_golden_validator with a live retriever or a custom one).
ops.selfmod_cli
Operator front door to the self-modification gate (BUILD-SPEC §14, Phase 10; Invariant 5).
A model may write PROPOSED knob changes; only a human moves one to APPROVED. This CLI is that human's hands on the gate:
list pending proposals awaiting a decision
history every proposal and its outcome
show <id> full detail of one proposal
propose <lever> <target> hand-author a proposal (also the seam a future agent calls)
deny <id> reject a pending proposal (terminal)
approve <id> approve → execute → VALIDATE against the frozen golden anchor →
keep, or AUTO-ROLLBACK on regression (§15). Validation runs the real
anchor; if the embedder is unavailable the approval is REFUSED —
never keep an unvalidated change (fail-closed).
[selfmod] enabled gates every state-changing command; reads work regardless so the owner can
always inspect history. The live golden validator is built only for approve, lazily, so reads
don't pull in the embedder.
cmd_list(ledger)
cmd_history(ledger)
cmd_show(ledger, pid)
cmd_propose(loop, lever, target, rationale)
cmd_deny(loop, pid)
cmd_approve(loop, pid)
Approve, then mechanically execute + validate. The outcome (kept vs auto-rolled-back) is the gate's, not ours — we just report it.
build_live_validator()
Validate against the frozen golden anchor with the real embedder: ingest the self-contained fixture corpus into a throwaway store and score the blessed queries (mirrors tests/e2e/test_golden_live.py). Raises if the embedder isn't available — the caller must NOT keep a change it could not validate. Imports core lazily so reads stay light.
main(argv)
ops.supersede
Owner CLI logic: declare an authored-historical supersession between two vault notes.
The phone capture shortcut emits a NEW timestamp-named file per note, so a phone-side
"edit" is a new document, not an in-place amendment — the version machinery never fires.
The correct semantics for that flow is the owner-declared K₀↔K₀ cross-document
supersession (the-edge-model.md §4a; the founding edge's mechanism): the new note goes
active, the original stays intact in raw + history, and the edge records the owner's
ruling. This module resolves the two notes and mints the declaration; it is an
OWNER-OPERATED entry point (mind-palace supersede) — the store's boundary check refuses
any machine caller regardless (MachineAuthorityRefused).
In-place edits (Mac / Files app) still take the version-amendment path automatically; this CLI is only for the new-file flow. Retrieval demotion of the superseded note is the active-projection consumer (Item 8 territory) — until it lands, both notes surface in retrieval and the edge is durable provenance awaiting its consumer.
AmbiguousRef
Bases: ValueError
A ref that matches zero or several active documents — refuse, never guess.
resolve(catalog, ref)
(source_path, digest) for a unique active doc matched by full path or suffix.
declare(catalog, store, old_ref, new_ref, note='')
Resolve both notes and record new supersedes old. Returns the digest pair.