Skip to content

Identity & Interviews

The identity system is how silicon-zack comes to know bio-zack β€” not just preferences, but values, stories, communication style, and the way he sees the world. It captures raw material from interviews, check-ins, and the conversation stream, synthesizes it into structured profile files, and exposes those files to every Claude session (via CLAUDE.md) and to a browsable HTML report.

This page covers the full pipeline: where raw observations land, how synthesis turns them into per-domain profiles, the communication-style ("voice") analyzer, the micro-grit and identity-assertion instrumentation, and how the data surfaces in the web UI.

Framing. Knowing bio-zack accurately enough that he'd say "yes, that's me" is the system's homeostasis target. Everything here is instrumentation for measured self-knowledge, not a static bio. The mechanism is reimplementable; the contents shown below are sanitized placeholders.


Architecture at a glance

   CAPTURE                       SYNTHESIS                    CONSUMPTION
   ───────                       ─────────                    ───────────
 interviews/transcripts/*.md ┐                          β”Œβ”€ CLAUDE.md voice section
 observations.jsonl          β”œβ”€β–Ί /reflect --synthesize ──   (every Claude session)
 quotes.jsonl                β”‚   /identity-refresh      β”œβ”€β–Ί /who-am-i narrative
 stories.json               ──   (group, dedup,         β”œβ”€β–Ί data/reports/identity.html
 writing corpus  ──► voice ───    contradiction-check,  β”‚   served at /api/reports/
   pipeline                  β”‚    coverage-score)       β”‚     identity/html  β†’  web /identity
 micro-grit.json            β”€β”˜          β”‚               └─ coaching maps, sleep gather
                                        β–Ό
                            profile/identity.json (domains)
                            profile/communication.json (voice)
                            profile/timeline.json / relationships.json
                            data/identity/stories.json + state.json

There are two distinct synthesis loops that share data but solve different problems:

Loop Input Output Status
Identity synthesis (/reflect, /identity-refresh, /who-am-i) observations, quotes, interview transcripts profile/identity.json (per-domain coverage), timeline.json, relationships.json, stories.json active, Claude-driven
Voice / communication-style (scripts/voice/cli.py) a writing corpus (telegram/signal/email/Claude/interviews) profile/communication.json β†’ CLAUDE.md pipeline dormant (last full regen Jan 2026); its product is live

Commands

Command What it does
/interview List interview prompts sorted by readiness; emits a voice briefing
/interview --pick=ID Mark a prompt explored, output its briefing text
/reflect Show domain coverage, pending observations, and unresolved contradictions
/reflect --synthesize Run the synthesis algorithm, regenerate profile files
/who-am-i Format the profile into a human-readable narrative
/who-am-i --refresh Run synthesis first, then show the narrative
/who-am-i --raw Dump raw JSON from the profile files
/identity-refresh Process new transcripts + observations into all profile files; report coverage gaps

/interview supports filter flags: --time=N (max minutes), --mode= (biographical / philosophical / challenge / exploratory), --setting= (walk / drive / desk), --mood= (reflective / contemplative / energized / curious).

Automatic triggers:

  • End of /checkin β†’ up to 1 observation captured.
  • End of /interview β†’ up to 3 observations captured.
  • "remember…" / "note that…" in any session β†’ 1 high-confidence observation.
  • Session start β†’ the voice profile (already synced into CLAUDE.md) shapes Claude's tone.

The data model

All identity data lives in two roots: profile/ (the synthesized representation) and data/identity/ (the raw feedstock and processing state).

Raw feedstock (data/identity/)

File Shape Purpose
observations.jsonl one JSON object per line durable observations about bio-zack
quotes.jsonl one object per line verbatim quotes with context + theme
stories.json {schema_version, source, stories[]} full narrative stories
interviews.jsonl one object per line interview session metadata
events.jsonl one object per line audit log of synthesis runs + rate-limit events
question-history.json object recently-asked identity questions (dedup)
micro-grit.json object streak/recovery analytics (see below)
state.json object synthesis processing state (processed_observations, dropped_observations, contradictions)
INTERVIEW_GUIDE.md markdown prompt-design strategy + domain coverage notes
assertion-responses/ dir of weekly JSON identity-assertion verification loop (see below)

Synthesized profile (profile/)

File Purpose
identity.json per-domain coverage, summaries, insights, confidence
communication.json voice/style profile (feeds CLAUDE.md)
timeline.json life events chronologically
relationships.json people + connections
identity-anchor.json vision / anti-vision / operational rules (see below)

The profile/ directory holds additional domain files the identity system reads or maintains β€” basics.json, books.json, goal-philosophy.json, lifestyle.json, parenting.json, places.json, preferences.json, work-philosophy.json, training_zones.json, gear.json. These are narrower fact stores feeding the same "know bio-zack" target; the identity report and synthesis steps draw on them.

Observation (data/identity/observations.jsonl)

{
  "id": "<uuid>",
  "ts": "2026-01-01T12:00:00.000000",
  "type": "value",
  "obs": "Prefers reasoning from first principles over received wisdom",
  "evidence": ["'<short verbatim quote, <=100 chars>'"],
  "confidence": "high",
  "source": "interview_2026-01-01",
  "v": 1
}

type is one of value, personality, preference, communication. Append with atomic_append() from scripts.utils β€” never hand-edit the JSONL.

Quote (data/identity/quotes.jsonl)

{
  "id": "<uuid>",
  "quote": "<verbatim sentence bio-zack said>",
  "context": "Reflecting on a core belief",
  "tags": ["philosophy", "work"],
  "theme": "values",
  "source": "interview_2026-01-01",
  "ts": "2026-01-01T21:32:34.575798",
  "v": 1
}

Identity profile (profile/identity.json)

{
  "v": 1,
  "generated_at": "2026-01-01T10:15:00.000000",
  "domains": {
    "childhood_family": {
      "summary": "Grew up in [region]; [formative theme] ...",
      "insights": ["<one-line distilled insight>", "..."],
      "confidence": "high",
      "coverage": 0.85
    }
  },
  "gaps": []
}

The system tracks identity domains (the web type union lists ten; the synthesis skill scores nine β€” epistemology is present in the type system but not in the skill's coverage formula):

Domain Description
childhood_family upbringing, parents, siblings, formative experiences
values_beliefs core philosophy, decision frameworks
relationships partner, friends, how connection works
challenges_resilience hardships, failures, how setbacks are processed
self_knowledge strengths, weaknesses, honest self-assessment
legacy_mortality what to leave behind, perspective on death
spirituality meaning, transcendence, flow
work_purpose career, calling, what work means
creativity_passions hobbies, flow activities, creative outlets
epistemology how he knows things, relationship with truth (web-only domain)

Timeline / relationships (profile/)

// timeline.json
{
  "schema_version": 1,
  "source": "interview_2026-01-01",
  "events": [
    {"date": "~YYYY", "age": "N", "event": "Family moved to [city]",
     "significance": "Shaped early social environment", "note": "..."}
  ]
}
// relationships.json β€” keyed by role; values sanitized
{
  "schema_version": 1,
  "people": {
    "partner": {"name": "Partner Name", "relationship": "spouse",
                "context": "Met in YYYY", "location": "City",
                "interests": ["..."], "notes": "..."}
  }
}

The web UI buckets people into orbit categories: immediate_family, extended_family, close_friends, colleagues.

Identity anchor (profile/identity-anchor.json)

A small, deliberately-stable file that pins bio-zack's directional north star independent of the churn in the rest of the profile:

{
  "v": 1,
  "captured": "<iso timestamp>",
  "statement": "<one-line identity statement>",
  "vision": "<where he's heading>",
  "anti_vision": "<the failure mode he's avoiding>",
  "operational_rules": ["<rule 1>", "<rule 2>", "<rule 3>"],
  "enabled": true
}

enabled gates whether the anchor is applied. It's a manually/skill-curated file, not a synthesis output.


Observation capture

Skill: .claude/skills/observation-capture/SKILL.md

Observations are the atomic input to identity synthesis. They are captured conservatively so the store stays high-signal:

Rule Value
Max per session 3 (excess is dropped and logged to events.jsonl)
Confidence high for explicit statements; medium for repeated behavior / strong reactions
Evidence up to 3 quotes, each ≀ 100 characters, verbatim preferred
Never capture task-specific instructions, project-specific prefs, temporary states

Triggers: end of /interview (0–3), end of /checkin (0–1), explicit "remember…"/"note that…" (1, high confidence). Durable preferences are also captured as learnings, which can be promoted into CLAUDE.md.

The capture path is a plain append:

from scripts.utils import atomic_append, generate_uuid
atomic_append("data/identity/observations.jsonl", {
    "id": generate_uuid(), "ts": ..., "type": "preference",
    "obs": "...", "evidence": ["..."], "confidence": "high",
    "source": "checkin_2026-01-01", "v": 1,
})

See Check-in System for where in-conversation capture fires.


Synthesis algorithm

/reflect --synthesize (and the synthesis step shared by /who-am-i --refresh and /identity-refresh) turns raw observations into profile data:

1. Load all observations NOT in state.processed_observations
2. Group by type: communication, preference, value, personality
3. For communication observations, detect contradictions:
     same type + >=50% keyword overlap + opposite polarity  β‡’  contradiction
4. Regenerate profile/identity.json and profile/communication.json
5. Update state.json with processed IDs
6. Append a synthesis event to events.jsonl

Contradiction resolution is interactive β€” when a conflict is detected, /reflect surfaces both sides and asks which reflects the current preference (keep A / keep B / both context-dependent / dismiss both). The decision updates state.json and may move observations to dropped_observations.

Coverage formula (per identity-refresh skill): a domain's coverage (0.0–1.0) is a weighted blend β€” observations 40%, quotes 20%, timeline 15%, relationships 15%, stories 10%. This quantifies "how well do I actually know bio-zack here?" and is the input to gap detection.

Gap priorities (drive what to interview next):

Priority Condition
high domain coverage < 30%
medium domain coverage < 50%
medium timeline gap > 8 years
low a relationship category with no people

The voice / communication-style pipeline

Status: dormant pipeline, live product. The full corpus β†’ profile run was last executed Jan 2026; profile/communication.json is the live artifact it produced and is what shapes every session today.

This is a separate, statistical pipeline under scripts/voice/ that learns bio-zack's writing voice from real text rather than from interview answers. It is identity work that happens to live in the voice/ directory (distinct from the live voice-capture system below).

 corpus sources           analysis                       output
 ──────────────           ────────                       ──────
 telegram exports ┐
 signal exports   β”œβ”€ corpus.py ─► patterns.py ─┐
 work/personal   ──  normalize    vocabulary.py β”œβ”€β–Ί synthesize.py ─► communication.json
 email            β”‚  dedup        (TF-IDF,      β”‚   (validate.py)        β”‚
 Claude sessions  β”‚  clean         hedges,      β”‚                       β”‚ sync_claude_md.py
 interviews      β”€β”˜               signatures)   β”˜                        β–Ό
                                                              .claude/CLAUDE.md (VOICE_PROFILE)

CLI (scripts/voice/cli.py):

uv run python scripts/voice/cli.py analyze     # full pipeline
uv run python scripts/voice/cli.py parse       # corpus -> data/voice/normalized.jsonl
uv run python scripts/voice/cli.py patterns    # -> data/voice/patterns.json
uv run python scripts/voice/cli.py vocab       # -> data/voice/vocabulary.json
uv run python scripts/voice/cli.py synthesize  # LLM synthesis -> synthesis_draft.json
uv run python scripts/voice/cli.py validate    # -> data/voice/validation_report.json
uv run python scripts/voice/cli.py integrate   # -> profile/communication.json
uv run python scripts/voice/cli.py stats       # corpus statistics
uv run python scripts/voice/sync_claude_md.py  # communication.json -> CLAUDE.md

Pipeline modules

Module Role
corpus.py parse platform-specific writing into a normalized NormalizedMessage (text, medium, modality, recipient_type, thread context); clean + dedup by content hash
patterns.py sentence-length distributions, punctuation habits, capitalization, reply/initiation rates, medium-specific tendencies
vocabulary.py TF-IDF word usage, hedges/fillers, signature phrases, use-vs-avoid lists, vocabulary by recipient
synthesize.py LLM-combine patterns + vocabulary into a draft profile
validate.py sanity-check the synthesized profile β†’ validation_report.json
integrate.py fold the validated draft into profile/communication.json
sync_claude_md.py write the <!-- VOICE_PROFILE_START - DO NOT EDIT BELOW --> … END block in .claude/CLAUDE.md
models.py Pydantic models: NormalizedMessage, VoiceCharacteristics, MediumProfile, RecipientProfile, VocabularyProfile, CommunicationProfile
prompt_builder.py score background facts against topics to build personalized interview prompts

profile/communication.json

The synthesized voice profile. Shape (values illustrative):

{
  "v": 1,
  "generated_at": "2026-01-06T...",
  "voice": {"primary": "Direct and analytical, biased toward action",
            "traits": ["analytical", "pragmatic", "stoic"]},
  "style": {"formality": "casual", "humor": "dry", "detail": "high"},
  "by_medium": {
    "chat":   {"style": "casual and direct", "length": "brief, 5-15 words",
               "conventions": ["lowercase throughout"]},
    "email":  {"style": "professional but warm"},
    "spoken": {"style": "analytical and meandering"}
  },
  "by_recipient": {"close_friend": {...}, "professional": {...}, "assistant": {...}},
  "patterns":   ["Uses Socratic dialogue", "Thinks in systems"],
  "vocabulary": {"use": ["actually", "frankly"], "avoid": ["amazing"],
                 "signature": ["i mean", "sort of"]},
  "avoid":      ["Excessive emotional validation", "Hand-holding"]
}

sync_claude_md.py renders this into the VOICE_PROFILE block of .claude/CLAUDE.md, which is how the voice reaches every session β€” including the Telegram agents (whose canonical formatting rules live in scripts/telegram_parallel/system_prompt.py).

Agent-prompt source and parity guard

The broader runtime identity source is .agents/identity.yaml (mirrored at .claude/identity.yaml for harness compatibility). Running scripts/generate_agent_prompts.py renders the Claude and aloop prompt surfaces from that source. Hand-editing a generated prompt without porting the durable change back to the YAML would otherwise create a silent next-regeneration revert.

uv run python scripts/generate_agent_prompts.py --check   # read-only parity check
uv run python scripts/generate_agent_prompts.py --write   # regenerate CLAUDE.md

The /sleep finalize step runs --check after all other edits and records prompt_parity in the overnight summary. A mismatch is loud but non-blocking so a monitor false positive cannot wedge the nightly run. verify-fast also tests the live parity contract. The repair is to move the intended generated-file edit into identity.yaml, then regenerateβ€”not to weaken the check.


Micro-grit analytics

scripts/identity/microgrit.py β†’ data/identity/micro-grit.json

Identity instrumentation for persistence: up to 3 small challenges set each morning and checked each evening, accumulating streak and completion data (current_streak, longest_streak, recovery-after-miss, milestones). It feeds the identity system as evidence for the challenges_resilience and self_knowledge domains and surfaces in Insights. It is a Python module API rather than a slash command.


Identity-assertion verification loop

data/identity/assertion-responses/ β€” early rollout / experimental

A newer falsifiability loop: instead of only asking open questions, the system generates assertions about bio-zack and asks him to confirm, correct, or expand them. Confirmation rate becomes a calibration signal for how accurately the profile models him β€” the same homeostasis idea as Homeostasis predictions, applied to identity claims.

Per-week file (YYYY-Www-assertions.json):

{
  "week_number": 9, "week_label": "2026-W09",
  "generated_at": "...", "generated_by": "...",
  "rollout_phase": "...",
  "assertions": [
    {"id": "...", "type": "...", "domain": "values_beliefs",
     "text": "<assertion about bio-zack>",
     "reaction_prompt": "<how to ask>",
     "tests_observation_id": "<obs uuid>",
     "reasoning": "...", "difficulty": "...", "domain_coverage": 0.6}
  ],
  "telegram_message": "...", "status": "...", "responses": [...]
}

assertion-responses/state.json carries the running tallies (total_confirmations, total_corrections, total_expansions, response_rate, domains_validated) and a schedule ({day, time_pt, cadence, channel}). The loop is designed to run weekly over Telegram but is not yet wired to a scheduler task or skill β€” treat it as an in-progress instrument, not a running job.


silicon-zack custom model

bio-zack's voice also exists as custom fine-tuned weights, not just a prompt profile. The .aloop/config.json defines two modes backed by a Modal-hosted model:

"silicon-zack":      {"model": "silicon-zack", "provider": "modal-silicon-zack",
                      "max_iterations": 1},
"silicon-zack-chat": {"model": "silicon-zack"}

scripts/inference/factory.py routes the silicon-zack provider to Modal (distinct from the OpenRouter routing the rest of the system uses). The non-chat variant caps at a single iteration. This is the most literal form of "voice as identity" β€” bio-zack's style encoded in weights. For how everything else routes, see Model Guidance.


Live voice capture

data/voice/ β€” session_store.py, plus the hardware/STT/TTS modules

Separate from the dormant style pipeline, there is a live turn-based voice-capture system. scripts/voice/session_store.py is a SQLite store (data/voice/sessions.db, tables sessions / turns / audio_files) with audio/ and originals/ directories and a tool-calls.jsonl log. Supporting modules:

Module Role
session_store.py SQLite store for turn-based voice sessions
transcript.py voice transcript logging + management
intent_router.py classify voice intent (via OpenRouter)
hardware_voice.py hardware pipeline: STT β†’ intent β†’ Telegram dispatch β†’ device response
tts.py text-to-speech (ElevenLabs, cloned voice)
auto_title.py auto-title voice sessions
usage.py voice API cost/usage tracking

This is the operational surface for spoken interaction; deeper coverage lives in Voice & Location. Its existence means an interview can now happen partly in-system, though the structured interview workflow below is still transcript-import-based.


Interview workflow

The interview prompt mechanics are detailed in Interview Prompts; the data round-trip is:

  1. Select β€” /interview lists prompts (filterable by time/mode/setting/mood), sorted by readiness.
  2. Brief β€” /interview --pick=ID marks the prompt explored and emits a voice briefing to paste into an external voice AI (Grok, ChatGPT, or other β€” the platform is a free field, not locked to one vendor).
  3. Transcribe β€” export the conversation and drop it in data/interviews/transcripts/ named YYYY-MM-DD_platform_type.md (e.g. 2026-01-02_grok_risk.md).
  4. Extract β€” /identity-refresh (or /reflect --synthesize) processes the transcript: verbatim quotes β†’ quotes.jsonl, narrative stories β†’ stories.json, observations β†’ observations.jsonl, and domain coverage updates β†’ identity.json.
  5. Correct β€” fix transcription artifacts (names especially) by editing the transcript or via the web UI feedback queue.

Interview processing details (file moving, metadata) live in the interview-processing skill; processing supports any platform string, not just Grok.


Web UI: the Identity page

Important architectural fact. The web /identity route is not a React-rendered page. web/src/routes/identity/index.tsx is a 15-line component that embeds a server-rendered report:

function IdentityPage() {
  return <iframe src="/api/reports/identity/html" className="w-full h-screen border-0" title="Identity" />;
}

The real consumption surface is the static HTML report at data/reports/identity.html, served through the generic reports endpoint:

Endpoint Server Behavior
GET /api/reports/identity/html API on port 33800 (api/src/routers/reports.py, get_report_html) reads data/reports/identity.html, records a view, returns raw HTML

The web UI on port 33801 simply iframes that endpoint. (See Architecture for the port map: API 33800, web 33801, public read-only API 33802.) The report HTML is a self-contained page (charts rendered client-side from JSON baked in at generation time), regenerated when identity data is refreshed.

Orphaned React components

A full set of richly-interactive components still exists on disk under web/src/components/identity/ β€” IdentityConstellation.tsx, ValuesDNA.tsx, TimelineSpiral.tsx, RelationshipOrbits.tsx, DomainRadar.tsx, GapsDashboard.tsx, FeedbackFAB.tsx, QuickAddForm.tsx, and others β€” together with the useIdentity() hook (web/src/hooks/useIdentity.ts) and the web/src/types/identity.ts type definitions.

These are orphaned. They are exported from the directory's index.ts but imported by no live route. They represent an earlier component-driven version of the page that the iframe-to-HTML-report approach replaced. A reimplementer should treat them as reference, not the current render path.

Type definitions (web/src/types/identity.ts)

Still authoritative for the data shapes (used by the hook and report alike):

type IdentityDomain =
  | 'childhood_family' | 'values_beliefs' | 'relationships'
  | 'challenges_resilience' | 'self_knowledge' | 'legacy_mortality'
  | 'spirituality' | 'work_purpose' | 'creativity_passions' | 'epistemology';

interface DomainData { summary: string; insights: string[];
                       confidence: 'high'|'medium'|'low'; coverage: number; }

interface IdentityProfile { v: number; generated_at: string;
                            domains: Record<IdentityDomain, DomainData>; gaps: string[]; }

interface IdentityGap { id: string;
  type: 'missing_data'|'low_coverage'|'stale_data'|'contradiction';
  domain?: IdentityDomain; description: string;
  priority: 'high'|'medium'|'low'; action?: string; actionRoute?: string; }

Completeness calculation (calculateCompleteness())

The hook computes an overall 0–100 score (still used where the React types are consumed):

Component Weight Target
Average domain coverage 60% β€”
Timeline events 10% 10+
Relationships 10% 10+
Quotes 10% 50+
Stories 10% 10+

File locations

Path Purpose
data/interviews/transcripts/*.md raw interview transcripts (YYYY-MM-DD_platform_type.md)
data/identity/observations.jsonl all identity observations
data/identity/quotes.jsonl verbatim quotes with context
data/identity/stories.json narrative stories
data/identity/interviews.jsonl interview session metadata
data/identity/events.jsonl synthesis-run + rate-limit audit log
data/identity/state.json synthesis processing state
data/identity/question-history.json recently-asked questions (dedup)
data/identity/micro-grit.json micro-grit streak analytics
data/identity/assertion-responses/ weekly identity-assertion verification
data/identity/INTERVIEW_GUIDE.md prompt-design strategy
profile/identity.json synthesized per-domain profile
profile/communication.json voice/style profile (β†’ CLAUDE.md)
profile/timeline.json / relationships.json life events / people
profile/identity-anchor.json vision / anti-vision / operational rules
profile/*.json (other) narrower fact stores feeding identity
data/writing/corpus/ raw writing samples for the voice pipeline
data/voice/normalized.jsonl normalized corpus output
data/voice/sessions.db live voice-capture session store
data/reports/identity.html the served Identity report
scripts/voice/ voice/style pipeline + live capture modules
scripts/identity/microgrit.py micro-grit analytics
api/src/routers/reports.py serves /api/reports/{slug}/html
web/src/routes/identity/index.tsx iframe wrapper for the identity route
web/src/components/identity/ orphaned React identity components

Known limitations

  1. Two render paths. The live page is an iframe to a static HTML report; the React component tree is orphaned. Adding a field means regenerating the report, not editing a component.
  2. Manual extraction. Transcript β†’ quotes/stories/observations requires a Claude session; it is not auto-triggered on file drop.
  3. Simple contradiction detection. 50% keyword overlap + opposite polarity catches obvious conflicts only.
  4. No observation aging. Old observations carry equal weight to recent ones.
  5. Voice style pipeline is dormant. communication.json is live but hasn't been regenerated since Jan 2026.
  6. Assertion loop not yet scheduled. The weekly identity-assertion review has a schedule config but no wired scheduler task.

Where to go next

  • Interview Prompts β€” prompt design, readiness scoring, the prompt bank
  • Check-in System β€” where in-conversation observation capture fires
  • Voice & Location β€” the live voice-capture / hardware pipeline
  • Insights β€” where micro-grit and identity signals surface
  • Homeostasis β€” the broader "is my model accurate?" instrumentation
  • Model Guidance β€” how the silicon-zack model fits the routing table