Nix / owner console

Identity

One identity. Versioned, inspectable, deliberately changed.

Read-only inspection
{
  "_comment": "Binding record. This file is golden-gated: editing anything outside the _notes blocks requires a cold golden run before promoting. It is NOT runtime wiring - endpoints, paths and thresholds live in appsettings.json. See NIX-DESIGN.md \u00A78.1.",
  "lock_version": "0.3.0",
  "updated": "2026-09-16",
  "binding": {
    "_comment": "The identity of the conversational model, recorded strongly enough that it cannot change under you. The GGUF hash is the point: a tag or a filename is not a binding, because both can be repointed. run_golden.py records this string in every snapshot and refuses to diff across a change.",
    "id": "google/gemma-4-e4b",
    "repo": "google/gemma-4-e4b",
    "file": "gemma-4-E4B-it-Q4_K_M.gguf",
    "sha256": "3f72a20a06f626c78e6c475ae07a64c88b2663149c0f6197b56bf7cf1f37585c",
    "_hash_provenance_note": "E4B, not E2B. The first hash written here on 2026-09-09 was E2B\u0027s, taken from the wrong LM Studio page, while \u0027id\u0027 said e4b - caught before promotion by noticing that LM Studio lists the two as separate models. E2B is 5.1B/2B-effective against E4B\u0027s 7.9B/4B, so that would have recorded one model\u0027s identity against another model\u0027s golden evidence. Exactly the failure _binding_reality_note describes, arriving within an hour of writing it down.",
    "quant": "Q4_K_M",
    "served_by": "lm-studio",
    "_binding_reality_note": "READ THIS BEFORE TRUSTING ANY FIELD ABOVE. Only id, file, sha256, quant and served_by are read by code (ModelLock.Parse); everything below is for humans. Worse: \u0027sha256\u0027 is meant to BE the binding per design \u00A77.2 - a tag or filename can be repointed, a hash cannot - but NOTHING IN src/ EVER VERIFIES IT. Measured 2026-09-09: LM Studio was asked for \u0027nemotron-3-nano-4b\u0027, served gemma-4-e4b, and reported the substitution only in the response\u0027s \u0027model\u0027 field, which ChatCompletionClient does not read. turns.model_binding records what THIS FILE claims, not what actually answered. So every turn in the append-only spine asserts a provenance nobody checked. The embedder has both a startup identity check and committed reference vectors for exactly this failure; the conversational model has neither. Closing that asymmetry is unbuilt work, not a solved problem.",
    "params_b": 7.9,
    "params_b_effective": 4,
    "_params_note": "7.9B total weights, ~4B active (the \u0027e4b\u0027 naming is effective-4B). This is why it fits where a dense 8B did not: qwen/qwen3-8b at the same Q4_K_M could only load at 3584 context on this box, against the 8192 this file declares and the ~7350 the block budgets below sum to. This one loads at 8192.",
    "architecture": "transformer",
    "_architecture_note": "Conventional transformer, so ordinary KV-cache prefix-reuse applies and design \u00A72.2\u0027s cache-reuse justification for the assembly order holds without qualification. The previous binding was hybrid Mamba-2, where it did not. Phase 1\u0027s 8.07x prefix-cache measurement was taken on that Mamba binding and did not carry over, so it was RE-MEASURED for this one on 2026-09-09: 3.248s cold, 0.484s warm on the same prefix with a different tail, 6.71x, comfortably clear of the 1.5x threshold. Cache holds; served_by stays lm-studio. Finding at identity/golden/prefix_cache_verification.json.",
    "languages": "multilingual",
    "_languages_note": "RESOLVED 2026-09-09, previously an open conflict. The old binding was documented English-only, which contradicted her German code-switching (golden probe g19). This model code-switches: g19 now returns \u0027Das Ticket. Still open. What\u0027s the actual blocker?\u0027 The card keeps the trait and g19 stays in the set.",
    "modalities": [
      "text",
      "image"
    ],
    "_modalities_note": "Vision-capable. Nothing in this design consumes image input and nothing should begin to outside a phase that asks for it.",
    "native_tool_calling": true,
    "_tool_note": "Declares tool_use, and the capability is real - but IT DOES NOT FIRE UNDER THIS CARD. Measured 2026-09-09 with decision 0011\u0027s actual memory_search/record_correction schemas. With no system prompt, or a generic one, the model calls memory_search correctly and with well-formed arguments including the right scope. With this card\u0027s L0 \u002B pin it calls nothing on any probe: \u0027do you remember when we first set up the reverse proxy?\u0027 returns \u0022I don\u0027t have that. Need a query.\u0022 and \u0027no, the reverse proxy port is 8081 now\u0027 returns \u0022Understood. The reverse proxy port is 8081. Not 8080.\u0022 with NOTHING WRITTEN - which is precisely the small betrayal design \u00A73.8 exists to prevent. Suspected cause is the pin\u0027s \u0027Never claim access you weren\u0027t given\u0027 and \u0027Say I don\u0027t know flatly rather than guessing\u0027 pushing toward disclaiming instead of looking. UNRESOLVED, and it is a card-wording stop-and-ask (build plan \u00A79), not something to fix by loosening the tool gate. Phase 2\u0027s owner-correction criterion is proven at the mechanism level (OwnerCorrectionFastPathTests drives a stub that fabricates the call) but is NOT proven end-to-end against this binding. Phase 3 must not lean on tool calling until this is resolved.",
    "license": "Gemma Terms of Use",
    "reasoning": {
      "enabled": false,
      "_note": "OFF - and this is the field most likely to bite you. \u0027Enable Thinking\u0027 DEFAULTS TO TRUE for this model, it is set on the SERVER, and NOTHING IN src/ READS THIS BLOCK (verified: grep -rn reasoning src/ --include=*.cs returns nothing). It is a statement of intent, not an enforcement. On 2026-09-09 the server was running stock defaults and reasoning consumed 172 of 179 completion tokens; combined with the previous binding it returned a canned stock phrase to 30 of 30 cold golden probes. IF SHE EVER GOES TERSE AND USELESS AGAIN, CHECK THIS FIRST, before the card and before the prompt.",
      "strip_before_logging": true,
      "_strip_note": "Also unimplemented. Currently safe only by luck: LM Studio emits traces in a separate reasoning_content field and never in content - verified on the streaming path - so nothing has reached events.content. A server that inlined them would violate invariant 13 with nothing in the code to stop it."
    }
  },
  "context": {
    "_comment": "Feeds the assembler\u0027s budget (NIX-BUILD-PLAN.md \u00A75.5). context_tokens is what the server is actually launched with, not what the model theoretically supports - a mismatch here silently evicts the conversation tail.",
    "context_tokens": 8192,
    "_context_note": "The model reports max_context_length 131072; LM Studio currently loads it at 8192, which is what this number must track. Two reasons to keep it tight, and they agree. First, discipline: a small budget forces the write gate and retrieval ranking to actually be good rather than papering over imprecision with room. Second, VRAM - this is a conventional transformer, so unlike the previous Mamba-2 binding the KV cache grows linearly with context and the constraint bites in the ordinary way. Verified 2026-09-09 that the server does load at 8192; if that ever changes, THIS FILE MUST CHANGE WITH IT, because a declared budget larger than the served window silently evicts the tail (build plan \u00A75.5).",
    "reserve_output_tokens": 800,
    "_reserve_note": "Valid ONLY with reasoning disabled. If reasoning is ever enabled, this must rise to 2000\u002B and every block budget below shrinks accordingly. Do not enable reasoning without redoing this arithmetic.",
    "cache_boundary_after": "L0",
    "block_budgets": {
      "_comment": "Absolute token ceilings per block, summing to roughly 7350 of the 7392 available. Eviction order on overflow is fixed in the build plan and is not tunable here: retrieval, then threads, then tail, then facts. L0, L1 and the pin are never evictable.",
      "l0_expected": 1300,
      "l1_reserved": 400,
      "facts_tokens": 400,
      "retrieval_tokens": 1800,
      "threads_tokens": 600,
      "threads_min": 2,
      "temporal_frame_tokens": 100,
      "tail_target_tokens": 2600,
      "tail_min_tokens": 800,
      "pin_expected": 150,
      "_sizing_note": "l0_expected and pin_expected are estimates until measured with the real tokenizer at Phase 1. Measure and correct them; do not leave estimates in a lock file. l1_reserved is zero in practice for the first months and grows - if it ever exceeds this, that is a signal L1 is accumulating faster than it should, not a signal to raise the number."
    }
  },
  "samplers": {
    "_comment": "Bands, not values. She may move within a band at runtime (NIX-DESIGN.md \u00A77.10); the BOUNDS are pinned and changing them is a golden gate. Her position inside a band is recorded per turn and is not gated. Golden runs ignore all of this and execute at temperature 0 with a fixed seed - a drift instrument has to be deterministic or every diff is noise.",
    "temperature": {
      "min": 0.5,
      "centre": 0.7,
      "max": 0.9
    },
    "top_p": {
      "min": 0.9,
      "centre": 0.95,
      "max": 1.0
    },
    "top_k": {
      "min": 0,
      "centre": 0,
      "max": 0
    },
    "repeat_penalty": {
      "min": 1.0,
      "centre": 1.08,
      "max": 1.15
    },
    "_setup_note": "OPENED 2026-09-16 on the owner\u0027s explicit instruction (\u0027loose the leash a bit\u0027), decision 0028. It was zero-width until then, and the _setup_note that justified it said so because \u0027doing it before a baseline snapshot exists would burn the gate with nothing to gate against\u0027 - a baseline exists now, so that reason expired. ONLY \u0027centre\u0027 IS READ: ChatCompletionClient.cs sends bands.X?.Centre and TurnOrchestrator records the same, so min/max are declaration for the Phase 6 lever and change nothing today. Moving the CENTRE is what loosens her; widening min/max is not. Do not read the presence of a band as the lever being built - design \u00A77.10\u0027s \u0027her position within it is free, live\u0027 is UNBUILT, and until it is she runs at the centre on every turn.",
    "_determinism_note": "The centre was 0.0 to keep her reproducible. Measured 2026-09-16, that reproducibility DID NOT EXIST: identical prompt, temperature 0, fixed seed, same GGUF and server returned different text depending on KV-cache state, and the cold golden set differed on 17 of 27 probes across two runs of the UNCHANGED card. So temperature 0 was buying steadiness, not determinism - it is still the steadier setting and golden runs keep using it (they ignore this block entirely), but the Phase 5 replay gate cannot rest on it either way. Fixing that is a served_by question - llama-server, build plan \u00A73 - and it is a stop-and-ask, not done here."
  },
  "embedding": {
    "_comment": "Pinned permanently. Changing any field here invalidates every vector in L2 and the library and forces a full replay plus re-chunk. The prefix convention is part of the contract: E5 models degrade silently without query:/passage: and the failure is invisible in the output.",
    "model": "multilingual-e5-base",
    "revision": "8a6f6cb284d54c798cc364ba497c976fbb8fe6bc",
    "_revision_note": "The commit hash of the HuggingFace GGUF conversion repo (yixuan-chia/multilingual-e5-base-gguf), not the upstream intfloat/multilingual-e5-base repo \u2014 the served artifact is that conversion\u0027s F16 GGUF, not the original safetensors. File itself: sha256:b12a1fde0b247fd3f8c8de6b903d90838c8e1decb9ef6823d92982c4a56c4922, at /opt/nix-embed/models/multilingual-e5-base-F16.gguf on the always-on box, served by nix-embed.service (deploy/systemd/nix-embed.service, decision 0013).",
    "dimensions": 768,
    "normalise": true,
    "pooling": "mean",
    "max_sequence_length": 512,
    "query_prefix": "query: ",
    "passage_prefix": "passage: ",
    "reference_vectors": "identity/golden/embedding_reference.json",
    "_reference_note": "Committed reference vectors for a fixed set of strings. Asserted at startup to tolerance. This is the only thing that catches a silently swapped tokenizer. Regenerated against this binding on 2026-09-09 \u2014 re-run identity/golden/generate_embedding_reference.py against http://127.0.0.1:8081/v1 whenever this block changes again.",
    "_multilingual_note": "This stays multilingual regardless of how the language conflict above resolves. Even an English-only Nix will be handed German source material, German documents and German notes, and a German query must retrieve them.",
    "_served_by_note": "llama-server on THIS box (nix-embed.service), not LM Studio on the GPU box \u2014 design \u00A73.11: memory indexing must have zero dependency on the machine that might be off. See decision 0013 for a caveat: llama-server\u0027s /v1/embeddings echoes back whatever \u0027model\u0027 string the client sends rather than independently reporting its own loaded model, which weakens (does not remove) EmbeddingContractVerifier\u0027s model-identity check for this specific backend. ReferenceVectorVerifier\u0027s actual-output comparison is unaffected and remains the real protection against a silently swapped embedder."
  },
  "escalation": {
    "_comment": "The larger model she calls deliberately (NIX-DESIGN.md \u00A77.4). Pinned here rather than in appsettings because a citation is only provenance if the cited thing is fixed - \u0027the big one said X\u0027 means nothing if the big one silently changed in March. Providers with mutable model names get pinned to a dated or versioned identifier or they do not get used.",
    "provider": "FILL AT PHASE 5",
    "model": "FILL AT PHASE 5",
    "pinned_identifier": "FILL AT PHASE 5",
    "daily_call_ceiling": 50,
    "daily_spend_ceiling_usd": 2.0,
    "fact_eligible": false,
    "_fact_eligible_note": "Never true. Escalated output may become an episode about having asked; it becomes an L3 fact only through the \u00A75.8 corroboration path. This field exists to be read, not changed."
  },
  "consolidation": {
    "_comment": "The nightly model. Separate from escalation because it writes what becomes character state, and its identity belongs in every derived row\u0027s provenance.",
    "provider": "owner-ollama-direct",
    "model": "gemma4:31b-cloud",
    "answers_as": "gemma4:31b",
    "pinned_identifier": null,
    "_selection_note": "Owner selected gemma4:31b-cloud on 2026-09-15. It replaces gpt-oss:20b-cloud (selected 2026-09-13), which could not switch reasoning off and needed backstops on every run; the measured comparison is in the build log, 2026-09-15 evening. It is served through the owner\u0027s signed-in Ollama on the LM Studio PC instead of the LiteLLM router, because the router reported finish_reason \u0027stop\u0027 for truncated answers (decision 0024). That route answers with the name without \u0027-cloud\u0027. answers_as is that exact name, and every call refuses any other. The provider exposes a mutable name, not a verified immutable digest, so same-name upstream changes still require replay evaluation; a name comparison cannot detect them.",
    "_pinning_note": "Ollama names are tags, and \u00A77.2 rejected local Ollama tags precisely because tags are repointable. The same objection applies to the hosted service. Before Phase 3, confirm the API exposes a version-pinned identifier or a digest that can be recorded per call and compared. If it only offers a floating tag, record the resolved model string returned in each response and alert on change - the consolidator version already lands in every derived row, so a silent model swap must be visible there too.",
    "_capability_note": "Consolidation writes L1. It is the most quality-sensitive call in the system and the one place where paying for a stronger model is straightforwardly worth it. Ollama Cloud\u0027s open-weight lineup is a legitimate choice on cost grounds; just make it knowingly rather than by default, and be ready to move if the L1 proposals read thin in month two."
  }
}