Skip to content

16 — 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; 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.