Skip to content

18 — The ~/.codogotchi Disk Contract

Goal: the lookup card for every file the app reads or writes. Chapters 01–02 teach the idea of the disk contract and Chapter 09 narrates how v2 uses it (Chapter 13 for the v3 overlay); this page owns the tables. Come back here whenever you need “who writes this file, who reads it, which timer governs it, and what breaks if I delete it.”

Everything lives under ~/.codogotchi/, overridable with the CODOGOTCHI_HOME env var (every path below resolves through CodogotchiFolders — tests and demo mode point it at sandboxes). Demo mode additionally polls a separate fixture directory and never touches the live one.


FileWritten byRead byGoverning clock(s)
state.d/<origin>:<session>.jsonHooks (producer); app writes back narrowlyStateJsonReader (1 Hz)reader 2h mtime · pruner 24h · pool dismiss-TTL
state.d/….gate.json / ….context.jsonson-of-anton gate writerGateJsonReaderreader staleness
customization.jsonSettings VM · right-click handlersCustomizationJsonReader (pool, per tick)none — settings
app-state.jsonApp (AppStateStore)Appnone — app state
session-labels.jsonApp (SessionLabelStore)App (pool, menu)orphan sweep w/ pruner
rpg-state.jsonCLI (RPG engine)App (HUD, hearts, level)none
prompt-attention.jsonHooksPromptAttentionReaderpayload expiry
assignments.jsonSettings (Pet tab) · pet installsAssignmentsJsonReadernone — settings
config.jsonSettings (PetConfig)Appnone — settings
pets/Pet installs (gallery, hatch)Pet loaders (CodexPet, CodogotchiPet)none
state-transitions.log / gate-transitions.logApp (TransitionLog, NDJSON)Humans / toolingappend-only + heartbeat
state.json (legacy, v1)— (retired)— (LegacyStateFileCleanup deletes it)one-time cleanup

Producer/consumer rule of thumb: hooks and the CLI write the left half of the app’s world; the app writes the right half. The one deliberate exception is the app’s narrow write-backs into state.d/ (below).


state.d/ — the live state (the contract that matters most)

Section titled “state.d/ — the live state (the contract that matters most)”

One JSON slice per (origin, session), named origin:session_id.jsonthe filename is authoritative (StateJsonReader.parseSliceFilename; a plain origin.json parses as session "default"). Inside: the schema-6 shape from Chapter 02 (activity_state, updated_at, source_event, optional attention).

Writers. Hooks own these files. The app writes back in exactly three narrow cases (StateJsonWriter), always scoped to the clicked window’s slice(s), never the whole directory:

Write-backTriggerTouches stale slices?
forceIdleright-click Force Idleno (would resurrect aged-out pets)
dismissAttentiondismissing the bubbleno
refreshForShowmenubar “Show …” / “Show All”yes — deliberately (restarts the TTL clock; stale slices re-show as idle)

Clocks. Three, independent (the lifecycle table in Chapter 09):

ClockValueEffect
Pool dismiss-TTLidle_dismiss_ttl_seconds (default 300s, 0 = never)continuously-idle pet leaves the screen; idle-frozen (busy pets never age)
Reader staleness2h mtime, hard-codedslice becomes invisible to both readers
SlicePruner24h mtime, 30-min sweep timerfile deleted; orphaned session-labels.json keys swept with it

Sidecars. origin:session.gate.json / .context.json carry SoA ticket/gate badges and conflict context — separate polling path (GateJsonReader), same filename identity, excluded from slice parsing.

Delete one by hand? Safe. The pet vanishes (menu entry culled at next menu-open), rename label swept later. This is exactly what right-click “Prune Session” does, plus label/number cleanup.


Full key table (defaults in CustomizationJsonReader):

KeyMeaningDefault
platform_modesper origin: own · combined · minimalist · offown
session_pets_enabledper origin: session-keyed panelsoff
session_capper origin: max session panels; 0 = unlimited3
session_pets_activated_at / session_pets_grandfathered_session_idthe grandfather/activity gate (Ch.09)
evict_session_pets_enablednewcomer may evict lower-ranked incumbenttrue
idle_dismiss_ttl_secondsdismiss-TTL; 0 = never300
idle_impatient_seconds / idle_frustrated_secondsbadge escalation; 0 = never300 / 600
combined_minimalist_enabledcombined window renders as a stripfalse
minimalist_badge_scaleglobal strip scale (clamped 0.75–~1.16)1.0

Writes are read-merge-write (ConfigFileWriter.merge) so unknown keys survive. Two writer families: the Settings tab’s view model, and the right-click affordances (mode switch, Panel Size) via short-lived view models

  • the .customizationDidChangeExternally notification. The pool never subscribes — it re-reads per tick. Hand-edit freely; malformed content degrades to defaults, never crashes.

app-state.json (AppStateStore) — floating-window frames per window key, the hidden window keys set (written through on every hide/show toggle, because crash-exit is normal for a menubar app), onboarding completion, hook install status. Delete it: windows return to default positions, hidden pets un-hide, onboarding may re-offer. Harmless.

session-labels.json (SessionLabelStore) — right-click renames, keyed by render key (origin:session_id, plain origin, or combined). Session keys are swept when their slice disappears; plain-origin/combined renames live until you change them.

config.json (PetConfig) — active pet name, RPG HUD toggle.

assignments.json — which installed pet renders for which platform (Settings → Pet tab; also touched by pet installs).

pets/ — the pet store: one directory per installed pet (sprite sheets + metadata) consumed by the pet loaders. Deleting one uninstalls it.


rpg-state.json — hearts/level/active-minutes progression, written by the CLI’s RPG engine; the app only renders it (HUD, sickness, ghost).

prompt-attention.json — latest submitted-prompt summaries per session, written by hooks; drives session-badge tooltips and attention subtitles. Payloads carry their own expiry.


Observability (append-only, safe to delete anytime)

Section titled “Observability (append-only, safe to delete anytime)”

state-transitions.log and gate-transitions.log — NDJSON, one line per observed transition plus a periodic heartbeat (TransitionLog). Nothing reads them programmatically; they exist for humans debugging “what did the app think happened.”


The whole app is a conversation through one folder. Your AI tools leave notes about what they’re doing (state.d/, prompt summaries, RPG progress); you leave instructions about how you want things shown (customization, pet choice, renames); the app remembers its own housekeeping (window positions, what you’ve hidden) and keeps a diary nobody has to read. Almost everything is safe to peek at, hand-edit, or delete — the app is built to shrug and carry on. The only files with real rules are the state.d/ notes, where three separate timers decide when a note is current, dormant, or trash.