teleo-infrastructure/docs/leo-reproducible-identity.md
2026-07-15 04:45:57 +02:00

12 KiB

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:

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:

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:

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.

Child processes execute the real interpreter and temporary-profile paths only in process-local memory. Retained receipts persist a repo-relative logical_command, the stable <python> and <temporary-profile> placeholders, and an explicit profile_role; they never serialize the checkout, interpreter, or temporary directory path.

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.