# Reproducible Leo identity ## Definition of Working - **Working target:** generate one pinned Leo identity manifest, compile a disposable profile, answer the fixed identity query in a fresh process, restart in a second process, and reject any drift before an answer. - **Operator path:** run `python -m scripts.run_leo_identity_reconstruction_canary` with the committed identity fixtures from a clean checkout. - **Done means:** the T2 receipt reports two distinct stopped processes with the same manifest, identity inputs, query, answer hash, and output provenance; the second process starts from a newly compiled empty profile; the drift attempt exits before answering; every process group and temporary profile is removed. - **Not done:** a hand-edited `SOUL.md`, a manifest self-hash, unit tests without process restart, or prior VPS restart evidence that did not reconstruct from this manifest. - **Required tier:** `T2_runtime`. T3 VPS restart/readback is a post-merge graduation target. ## Identity input inventory `GatewayRunner` and the surrounding Hermes profile can change an answer through the following surfaces. The manifest either pins each surface or explicitly keeps it outside identity authority. | Surface | Binding | Authority | | --- | --- | --- | | Model provider, route, limits, and reasoning settings | secret-free semantic config hash | runtime input; the actual model remains a per-turn receipt | | Hosted model weights | provider-managed, explicitly not locally hashable | never claimed as a local hash | | Hermes and Teleo code | clean Git commits plus executable source-tree hashes | runtime input | | Python ABI and imported runtime packages | exact implementation/version and curated package versions | runtime input | | Skills, plugins, and database tools | content hashes | runtime input | | Gateway/tool permissions | strict allowlisted config hash plus exact no-send/read-only policy | runtime input; this local compiler does not claim an authorizer readback | | Static constitutional rules | committed source path, byte hash, and semantic hash | static authority | | Database snapshot version | database/user/system identity plus content, row, structure, and capture-provenance hashes; only WAL position is excluded | T2 pins a noncanonical fixture; it cannot grant canonical authority | | Database-derived identity rows | typed table/row/kind/rank/evidence bindings plus source mode | synthetic fixtures are explicitly noncanonical; T3 requires independently verified database-exporter output | | `SOUL.md` and `identity.json` | deterministic compiled-view hashes | generated views, never authorities | | Sessions, state, and memory | excluded from the identity input hash and labeled temporary | continuity only; never evidence | The disposable profile is compiled from a strict non-secret configuration allowlist; unrecognized transport/credential fields are not copied. Credential values are omitted rather than hashed. Rotating a bot token changes the broad operational behavior observation but does not change Leo's identity. Changing a non-secret permission, model setting, skill, code file, database fingerprint, constitutional rule, database identity row, or compiled view does change a pinned input and fails closed. ### Current `GatewayRunner` consumption map The T2 compiler contract was checked against the current live runtime source. This inventory defines what the post-merge T3 receipt must observe rather than silently assuming that a profile file is the whole prompt: - Startup resolves `-p leoclean` to `HERMES_HOME`, then loads profile and project environment files before `config.yaml`; environment expansion, legacy `gateway.json`, and defaults can change the effective configuration. The T2 compiler therefore enforces an exact top-level profile allowlist (including rejection of dangling symlinks) rather than relying on a filename denylist. - Routing consumes the requested default model, the top-level `smart_model_routing` switch, provider endpoints/defaults, auth pool choice, reasoning settings, token/turn limits, and fallbacks. The pinned top-level routing switch must be a boolean; arbitrary nested routing data is rejected. Credentials are runtime capability, not identity, and their values are never retained. - Prompt construction consumes generated `SOUL.md`, the gateway/agent system message, persistent `MEMORY.md`/`USER.md`, compiled skills, project-context files, current time/timezone, and the effective model/provider. T2 requires memory and project context absent; T3 records the effective system-prompt and compiled-skills-prompt hashes. - Plugin discovery can include profile plugins, optional project plugins, and installed entry points. The live database-context hook can inject a query-bound contract before the model and can reject or replace the reply afterward, so T3 retains plugin registry, contract, retrieval, and delivered response hashes. - Effective tools come from platform toolsets and the runtime registry, not a descriptive fixture field. T3 must read back exact tool schemas, prove the send tool absent, and keep terminal restricted to the clone-bound read-only wrapper. - Authorization consumes chat/user allowlists and pairing-store state. T2 starts with pairing absent; T3 records the fixed synthetic source and the actual authorization decision without exposing IDs or tokens. - Session keys consume platform/chat/user/thread/topic fields. Auto-skill, reply/media/file context, persisted transcripts, and a reused system prompt can all change a turn. Verification therefore happens before every child starts, and T3 binds session key, persisted session ID, message-context absence, and prompt hashes. The committed T2 database/identity inputs are synthetic fixtures and the compiled view labels them `synthetic_noncanonical_fixture`; they do not stand in for approved production rows. This T2 schema never grants canonical authority from caller-supplied or merely self-hashed JSON. T3 must use output that is independently verified by the database-owning same-transaction exporter and bound to the database fingerprint, database user, system identifier, and row digest. Every pinned source tree and profile component is structurally revalidated: the verifier recomputes its file count, total bytes, sorted unique paths, and canonical content hash, and rejects missing files, unreadable entries, or symlinks. Rehashing forged structural metadata cannot make it authoritative. The current live Hermes checkout is not clean, so its Git SHA alone is not a reproducible source bundle. This T2 receipt intentionally uses the committed clean local runtime and the committed database/identity snapshot fixture. It does not claim to reconstruct the current dirty VPS runtime. T3 must run only after the merged identity code is deployed and must bind the deployed clean content (or a content-addressed dirty-source bundle if the live checkout has not yet been repaired). ## Commands The complete canary is the preferred operator command: ```bash python -m scripts.run_leo_identity_reconstruction_canary \ --profile-template fixtures/working-leo/leo-identity-v1/profile-template \ --hermes-root fixtures/working-leo/leo-identity-v1/hermes-runtime \ --deployment-root . \ --source-root . \ --database-fingerprint fixtures/working-leo/leo-identity-v1/leo-database-fingerprint-v1.json \ --constitution fixtures/working-leo/leo-identity-v1/leo-constitution-v1.json \ --database-identity fixtures/working-leo/leo-identity-v1/leo-database-identity-v1.json \ --output docs/reports/leo-working-state-20260709/leo-reproducible-identity-t2-current.json ``` The negative lifecycle is separately runnable. It proves drift is caught before the second process starts: ```bash python -m scripts.run_leo_identity_reconstruction_canary \ --profile-template fixtures/working-leo/leo-identity-v1/profile-template \ --hermes-root fixtures/working-leo/leo-identity-v1/hermes-runtime \ --deployment-root . \ --source-root . \ --database-fingerprint fixtures/working-leo/leo-identity-v1/leo-database-fingerprint-v1.json \ --constitution fixtures/working-leo/leo-identity-v1/leo-constitution-v1.json \ --database-identity fixtures/working-leo/leo-identity-v1/leo-database-identity-v1.json \ --inject-drift-before-restart \ --output /tmp/leo-identity-drift-rejection.json ``` For inspection, the lifecycle can be decomposed into explicit manifest and compile commands: ```bash python -m scripts.leo_behavior_manifest \ --profile fixtures/working-leo/leo-identity-v1/profile-template \ --hermes-root fixtures/working-leo/leo-identity-v1/hermes-runtime \ --deployment-root . \ --output /tmp/leo-behavior.json python -m scripts.leo_identity_manifest generate \ --behavior-manifest /tmp/leo-behavior.json \ --database-fingerprint fixtures/working-leo/leo-identity-v1/leo-database-fingerprint-v1.json \ --constitution fixtures/working-leo/leo-identity-v1/leo-constitution-v1.json \ --database-identity fixtures/working-leo/leo-identity-v1/leo-database-identity-v1.json \ --source-root . \ --output /tmp/leo-identity-manifest.json python -m scripts.leo_identity_profile compile \ --manifest /tmp/leo-identity-manifest.json \ --source-root . \ --profile-template fixtures/working-leo/leo-identity-v1/profile-template \ --profile /tmp/leo-disposable-profile \ --hermes-root fixtures/working-leo/leo-identity-v1/hermes-runtime \ --deployment-root . ``` All commands require a clean Git worktree because a commit plus an unretained dirty source tree is not reproducible. ## State inventory and transitions | State | Meaning | Next valid transition | | --- | --- | --- | | `inputs_unverified` | source paths exist but are not yet bound | validate clean Git, runtime, DB, rules, rows, and permissions | | `manifest_pinned` | every material identity input has a stable hash | compile deterministic views | | `profile_compiled` | static profile copied and generated views match the manifest | verify immediately before start | | `runtime_verified` | runtime/code/view hashes match and session state is non-authoritative | answer fixed query | | `stopped_cleanly` | child and process group are absent | compile a new empty profile from the manifest | | `restart_reproduced` | child from the newly compiled profile has the same identity/query/provenance inputs | retain receipt and clean every profile | | `drift_detected` | any input or generated view differs | fail closed; no answer and no next start | The irreversible boundary is not a database or production mutation: this T2 canary is a local compiler/process runtime, no-send, database-read-only, and disposable. The successful-restart receipt reaches `T2_runtime`; the standalone pre-restart drift receipt is a passing negative component but reports `T1_model` and `goal_tier_satisfied=false`. Neither proves the live VPS `GatewayRunner`, Telegram delivery, hosted-model behavior, production database state, or T3 restart recovery. Here `T2_runtime` uses the required Capability Tier Proof vocabulary: local runtime behavior with restart. It does not imply Hermes/GatewayRunner or a hosted model; those exclusions are explicit in the receipt's `runtime_variant`, `tier_basis`, and `claim_ceiling`. ## T3 graduation After merge and rollback proof, extend the schema with independent verification of the database-owning same-transaction exporter, generate the manifest from that live read-only canonical capture and deployed clean commits, compile a disposable VPS profile, invoke the real no-send `GatewayRunner`, terminate that child, open a new child from the same manifest, and retain model-call, DB-read, service, cleanup, and drift-negative readbacks. Do not replace the production profile during that graduation.