Skip to content

Identity — the self

Path: cold (written) · hot (read) · Status: working at the identity tier on mem; the recombination tier unbuilt · Code: src/mem/selfname.py, src/mem/surfacer_store.py, src/mem/surfaces.py, src/alto/read/surfacing/surfacer.py

Alto does not know its own name until someone tells it. Identity is not a persona written into a prompt or trained into an adapter — it is a node that boots empty and accretes name, creator and type through testimony, each fact traceable to the moment it was said. The human on the other side is treated the same way: the first speaker begins as a nameless referent and earns a name. That symmetry is the organ.

This is where things get maybe a bit too wonky. And a commercial product Definitely couldn't be set up this way. But it is the most honest to the project in a pure research capacity. A commercial product would have to have a set of beliefs, identity, memories etc seeded into it.

Where it sits

On the write side, nothing writes identity as a fact. Every utterance is kept in mem as testimony, and Alto's name is derived from that testimony on read (mem/selfname.py, below). The AGE-graph self-node and the resolver detectors that wrote into it are archived at tag archive/age-brain-2026-10-11.

On the read side, the Surfacer takes the self node (mem's __self__ surface) as its POV anchor — it refuses to construct without one. alto/read/surfacing/verify/ reads the same earned name to decide how to attribute a clause.

Nothing else consumes identity directly, because there is nowhere else for it to go: Integration has no system prompt, so the only channels into the conversational call are the frozen LoRA and surfaced thoughts. Identity reaches behaviour through the Surfacer or not at all.

The intended design

Three specs, in build order.

SPEC-writer-self-node.md (implemented on the AGE graph, now archived; the design stands for the rebuild on mem) establishes the self_attr substrate: the self boots empty, learned facts attach as separate attribute nodes under five typed slots — identity, architecture, belief, norm, preference — and the self is deliberately not kind:'entity', keeping it out of the entity co-occurrence graph. It names three ascending payoffs, and is honest that only the third earns the word "self": tier 1 identity persistence (kills the amnesiac reply), tier 2 grounded self-talk instead of confabulation, tier 3 per-user behavioural coherence — norms and preferences derived by the unbuilt recombination phase.

The spec also runs its own delete-the-analogy check and answers the obvious objection. "Why not just write a system prompt?" does not apply here: the conversational call has an empty system prompt by design, so the self-model is not competing with a system prompt — it is the replacement for one, and the only per-user, inspectable, no-retrain channel available.

SPEC-speaker-identity.md (DESIGN v1, 2026-07-20) removes the last privileged node. The shipped "operator" was earned but special — one deterministically-keyed person node every organ reached for by slug. It is replaced by a generic current speaker: unknown until it identifies itself, named when a name is stated, reconciled with an existing person when they turn out to be the same. self goes back to meaning only Alto.

SPEC-self-schema-event-model.md (DESIGN) adds the relational backbone: identity is relational and attributive, not episodic — what it is (is_a), who made it (a reified creation event), who it serves. The longer-run identity stack, Nature → Dispositions → Beliefs, is indexed in docs/README.md.

What exists today

The cold-start property is enforced at the seed. mem inserts two sentinel surfaces, __self__ and __speaker__, both referential and both unnamed (src/mem/schema.sql:142-143). first_seen_at is 0 because they precede all testimony rather than being first seen in it. No name is seeded, and no row asserts that a particular person exists before anyone has spoken.

Alto's name comes from testimony, re-read at each episode start. The Surfacer holds the self node (SurfacerIdentity, src/alto/read/surfacing/identity.py) and re-reads it when a turn opens an episode (no spoken turn inside the idle gap). MemSurfacerStore.get_self_node (src/mem/surfacer_store.py:264) builds that node from the __self__ surface, and _singleton (:297) names it with selfname.self_name_from_testimony (src/mem/selfname.py:74). Every percept prompt carries one identity line made from it: the name, or "nobody has told me my name yet", and no other self fact. The line is context, not a licensed source, so a render that states the name is dropped by the verifier for now. mem keys surfaces by their case-folded norm, so "Your name is Juno" names Alto juno. A name given today reaches the next episode only after an enrichment batch has minted it (mem.enrich is off). Pinned by src/tests/test_alto_self_identity.py (live) and src/tests/test_alto_surfacer_identity.py.

The speaker stays a nameless referent. get_current_speaker_name (src/mem/surfacer_store.py:274) reads the __speaker__ surface through the same _singleton, but only the self branch derives a name (:309-313), so the speaker's name is None in this build. The render then addresses the speaker in second person or as "the speaker" and asserts no identity. Naming the speaker from testimony is the missing half of SPEC-speaker-identity.md on mem.

Reads defend the cold-start state rather than paper over it. The Surfacer takes get_self_node() as its POV anchor and raises without one (surfacer.py:253). The speaker's earned name is read through get_current_speaker_name and is None while they are still a nameless referent (src/alto/read/surfacing/speaker.py:24, resolved lazily at 18-27); the cache starts as None, the honest cold-start state (speaker.py:15). In verify/lexicon.py:32, None, blank and non-string all mean still a nameless referent, and that is what routes the render to second person instead of asserting an identity. There is a deliberate asymmetry to know about: a hard-coded OPERATOR_NAME = "Sawyer" survives at src/alto/read/surfacing/verify/lexicon.py:28 as a legacy recognizer only — a recognizer that is wrong costs a missed detection; a generator that is wrong states a falsehood about a person.

A corpus bug recorded in the map is closed. ALTO-INTERIORITY-MAP.md reconciliation #2 (2026-07-22) reports the identity-probe corpus leaking facts into weights — fc_id_002/fc_id_003 asserting the name and its provenance on an empty context. In the corpus the shipped model was trained on (lora_train.v6.jsonl) those rows now defer: "Nothing set on my end. What've you been calling me?" and "Not one I can point to." Verified by reading the file. The map's entry is stale on this point.

How mem derives the name. mem/selfname.py derives what Alto was told its name is from testimony on every read. There is no self_name table and no config key: either would be a value that outlives its evidence, and a config key would be the trained self-description pillar 4 rules out, filed under ops. It matches four second-person phrasings on the speaker axis ("your name is X", "you're called X", "I'll call you X" and their kin), and a candidate counts only if step ④ had already minted it as a referential surface — so a bad pattern can fail to find a name and cannot invent one. What comes back is a SelfName with its receipt (the segment and the evidence) and an n_candidates, so being renamed is surfaced rather than resolved silently. It feeds pronoun binding when an episode closes (src/mem/worker.py:708), the read side, where a cue naming Alto reaches Alto's own self, and the name the surfacer's identity line carries, re-read at each episode start (perception reads no name). The module calls its closed pattern list a deliberate limit: if it has to grow past a handful, that is evidence the approach is wrong and should be escalated. Its regexes are English, registered list debt; making them language-neutral is the next step after remove-grammar-rules.

The gap

Tier 3 — the part that earns the word "self". The architecture, belief, norm and preference slots were AGE-graph structures and went with it; mem has no self-schema producer, and MemSurfacerStore.get_self_neighborhood returns empty lists (the Surfacer no longer reads it). Until the recombination/introspection phase is built on mem, the self can hold its name, and little else.

Two smaller gaps, both designed-but-unbuilt rather than unimagined. The speaker's name has no producer on mem (above), and name-collision reconciliation is charged to SPEC-consolidation-loop.md, which is neither built nor currently spec'd for it.

Embodiment awareness — no body, no senses — is part of this self-model under pillar 4 and is the one piece already proven in the adapter: the v6 retrain's self_schema thoughts reason from the limit ("no body to log miles, but…") rather than performing around it.

Pillars this serves

  • 4 (cold-start, testimony-accreted self) — this organ is pillar 4; the seed writes no name, and both the self and the speaker are meant to earn theirs (only the self does today).
  • 3 (honesty by construction) — the guards are structural: a null name routes the render, and a self-name counts only if it was said and step ④ minted it as a surface.
  • 9 (repairability over precision) — a name collision is flagged and left for reconciliation, and a name is derived from testimony, so a revision re-derives it.
  • 5 (surfacing, not retrieval) — the self-node is the Surfacer's POV anchor; without it there is no first person to surface from.

Sources