Skip to content

Epic 10 — Persona-as-Files#1

Merged
elliotboney merged 9 commits into
mainfrom
plan/epic-10-persona-files
Jun 27, 2026
Merged

Epic 10 — Persona-as-Files#1
elliotboney merged 9 commits into
mainfrom
plan/epic-10-persona-files

Conversation

@elliotboney

Copy link
Copy Markdown
Owner

Moves shelldon's character out of the hardcoded SYSTEM_INSTRUCTION constant into bot- and owner-editable markdown under ~/.shelldon/memory/ (the v1 worktree model, rebuilt under v2 invariants). All 5 stories done, reviewed, retro'd, and live-validated on the Pi.

What's in it

  • 10.1 persona files seed-on-boot + prompt reads (constant deleted, golden parity)
  • 10.2 bot-writable persona via rewrite_* ops; BOT_INSTRUCTIONS parse-guarded; DIRECTIVE owner-approval-gated
  • 10.3 proactive/dream prose → HEARTBEAT/DREAM files + autonomous dream edits
  • 10.4 first-run onboarding via BOOTSTRAP.md (USER-blank monotonic sentinel)
  • 10.5 cost: byte-stable cache prefix + per-turn cache-signal logging (explicit cache_control breakpoint deferred, spec-allowed) + keyword lazy-load of TOOLS/ARCHITECTURE + verified non-destructive Pi migration

Live validation (on gotchi, vs live GLM)

  • Non-destructive copy-if-absent seeding confirmed on the real device; onboarding completed end-to-end, writing USER.md via the rewrite_user op (not write_file).
  • GLM cache passthrough resolved: z.ai surfaces cache_read_input_tokens (stays 0, no auto-cache) and omits cache_creation_input_tokens — lazy-load + budget posture stands, no silent cap.
  • Two design gaps caught during validation and fixed in this branch: starter SOUL/IDENTITY seeds (a bot is born with a soul, onboarding only learns the owner) and a write_file→op nudge (persona is edited via ops, never the workspace-jailed tool).

Invariants

839 tests pass, import-linter 3 contracts KEPT, 0 new deps across all 5 stories, core stays LLM-free. No runtime/state/contracts/schema change.

Follow-ons (logged in deferred-work.md)

Epic 11 candidates: address prompt sections to their file/op (stop the model inferring where to write); structural clobber guard for full-replacement rewrites. Plus the fail-soft pre-review checklist + untracked-seed guard hardening from the retro.

Retro: _bmad-output/implementation-artifacts/epic-10-retro-2026-06-26.md

Move shelldon's character out of the hardcoded SYSTEM_INSTRUCTION into bot-editable markdown in the writable memory tree (v1/openclawgotchi worktree model, rebuilt under v2 invariants). Adds the Epic 10 design, the epics.md breakdown, and sprint-status rows. Appended only — Epics 1-9 untouched (git numstat +116/-0 on the two existing files).

Decisions locked 2026-06-25:
- everything -> files; bot edits via the op path (AD-5), never raw write_file
- BOT_INSTRUCTIONS fully bot-writable but parse-guarded
- DIRECTIVE bot-editable only via Epic 9.3 owner-approval (never autonomous)
- persona prefix caching = 2 provider surfaces (anthropic cache_control + openai auto)
- VAULT.md NOT ported (shelldon vault/ = OS-isolated secrets)

Story specs not yet created; next = create-story 10-1.
…(Story 10.1)

Move shelldon's character out of the hardcoded SYSTEM_INSTRUCTION constant
into seed markdown the worker reads every turn (v1 worktree-prompt model).

- shelldon/persona/ ships pristine seed templates: BOT_INSTRUCTIONS.md
  (verbatim system text), empty SOUL/IDENTITY/USER (filled by onboarding 10.4)
- CuratedMemory seeds missing persona files copy-if-absent on init
  (faces.FaceRegistry.load idiom), atomic, fail-soft, never raises
- read_instructions/soul/identity/user accessors (mirror read_about)
- gather_context reads + assemble_prompt injects persona in binding AD-6 order
  (system -> directive -> identity -> soul -> user -> about -> ...)
- SYSTEM_INSTRUCTION constant DELETED; seed_instructions() is the canonical source
- per-file fail-soft (_safe_read): a corrupt persona file degrades only its
  own section, never siblings (AC6)
- char-budgeted per section (_bounded_text, 8000)

Golden test proves day-one byte-parity with the prior hardcoded prompt.
782 passed / import-linter green (core stays LLM-free) / persona ships in wheel.
…ies 10.2, 10.3)

Two sequential Epic 10 stories, developed back-to-back in the worktree and
shipped together (entangled in core/memory.py, core/runtime.py, and the persona
op tests).

Story 10.2 — bot-writable persona via memory-ops + awareness + gated directive:
- Four autonomous rewrite ops (rewrite_soul/identity/user/instructions), mirror
  rewrite_about: frozen tagged structs, route through CuratedMemory.apply_memory_op,
  atomic temp+fsync+replace, empty content rejected.
- rewrite_instructions validate-on-apply guardrail: a rewrite dropping THOUGHT:/
  FACE:/the ```ops fence is rejected (logged, no-op) so the bot can re-voice but
  not break parse_reply.
- BOT_INSTRUCTIONS gains a "Your self-knowledge files" awareness section advertising
  every rewrite op (incl. the previously-latent rewrite_about).
- rewrite_directive is owner-approval-gated (RISKY-tier, rides the 9.3 approve/deny
  plumbing): proposable but NOT a MemoryOp, parked not applied, core applies on
  Approve (no worker resume — AD-5), barred on unattended dream turns.
- Wire-bug fix: the ops-block regex closed at a nested ```ops inside rewrite_instructions
  content — anchored the closing fence to line-start.

Story 10.3 — proactive & dream prompt prose to files + autonomous dream self-edit:
- Proactive/dream prompt copy moved out of hardcoded constants into editable seed
  files (persona/HEARTBEAT.md, persona/DREAM.md), seeded copy-if-absent into the
  worktree and read at dispatch. core/proactive.py stays pure: builders take the
  template text and fill it; file I/O lives in the dispatch driver + memory seed.
- Degrade-safe: missing/blank/malformed template (incl. a dropped {lines} or
  {feeling_sentence} slot) logs and falls back, never raises.
- DREAM.md invites autonomous SOUL/IDENTITY/USER edits via the 10.2 ops on the
  dream cycle (no chat instruction); directive still barred by the 10.2 owner gate.
- Proactive build is byte-identical to the prior constant (golden no-op test).

812 passed (+38 across test_persona_ops/test_proactive/test_memory) /
import-linter 3 contracts KEPT (core stays LLM-free) / 0 new deps / no SCHEMA_VERSION bump.
…Story 10.4)

The worker injects a warm first-run interview directive while USER.md is
blank, so the empty SOUL/IDENTITY/USER seeds get populated from a real
conversation instead of source edits, and the bot never re-interrogates
the owner once it knows them.

- shelldon/persona/BOOTSTRAP.md: new seed directive (LLM-facing prose, no
  hardcoded onboarding copy in any .py).
- core/memory.py: BOOTSTRAP.md added to _PROMPT_TEMPLATE_SEED_FILES
  (copy-if-absent like HEARTBEAT/DREAM); read_bootstrap() accessor.
- worker/prompt.py: gather_context gates on the USER-blank monotonic
  sentinel (Story 10.2 rejects empty rewrite_user, so a filled USER never
  reverts) and reads BOOTSTRAP fail-soft + char-budgeted; assemble_prompt
  injects a "# First-run onboarding" section right after the system block.

No runtime.py/state.py/contracts change, no new op, no new dep. Full
trigger->populate->stop cycle proven with a fake provider (no live LLM).
820 tests pass (+8), import-linter 3 contracts KEPT.

Known/deferred (logged in deferred-work.md): onboarding also injects on
proactive/dream turns while USER is blank (spec accepts as harmless; a
proper fix needs fork-boundary turn-type plumbing the story avoids).
…n (Story 10.5)

Epic 10 cost + deployment close-out — caching the always-injected persona,
loading heavy reference docs only when relevant, and landing the new files
non-destructively on the already-deployed Pi.

- AC1: persona prefix kept byte-stable (free OpenAI-surface + native-Claude-auto
  caching); 3 guard tests catch a future per-request interpolation (the silent
  cache invalidator).
- AC2: per-turn Anthropic cache-signal logging (_log_cache_usage, both egress
  paths, getattr-guarded for GLM). Explicit cache_control breakpoint DEFERRED
  (spec-allowed): the persona ships in a single content string, so a breakpoint
  needs a worker-emitted marker every provider surface must strip — beyond the
  timebox. Findings note + deferred-work record it; no silent cap.
- AC3: lazy-load TOOLS.md / ARCHITECTURE.md by keyword (_needs_reference, pure,
  injected after the cached prefix), seeded copy-if-absent, no rewrite op.
- AC4: Pi migration verified — seeds git-tracked + present in the built wheel +
  copy-if-absent non-destructive; setup-pi.sh and pyproject.toml need no change.
- AC5: core stays LLM-free, worker read-only, single-writer, fork no-accumulation.

Code review (3 adversarial layers): dropped ambiguous _ARCH_KEYWORDS bare words
(pi/ram/screen/cpu/raspberry → "raspberry pi" phrase + negative test); migration
test asserts all 9 seeds; _prefix_of hardened. 839 tests pass, import-linter 3
KEPT, 0 new ops/deps, no runtime/state/contracts change.
Clean "data-not-code" refactor: 782→839 tests, 0 deps all 5 stories, ops only
in 10.2, import-linter 3 KEPT every story, core stayed LLM-free.

Dominant theme: fail-soft file-boundary behavior was the whole risk surface and
nearly every review patch — the broad-except/silent-degrade class Epic 9's retro
flagged, now proven to generalize (still bit us 4/5 stories).

4 action items recorded in deferred-work.md:
1. (immediate) fix 10.4 OPEN AC5 log gap + formal-review + close 10.4/10.5 review→done
2. operationalize the fail-soft-boundary pre-review checklist
3. harden the untracked-seed guard (git-test skips without git)
4. live-validate Epic 10 on the Pi (onboarding→persona-rewrite→lazy-load→cache signal)

Epic 11: TBD. epic-10-retrospective marked done in sprint-status.
…5 done)

Epic 10 retro Action 1. read_bootstrap caught the decode error itself, so the
worker's _safe_read except never fired and a corrupt BOOTSTRAP.md degraded
SILENTLY — unmet 10.4 AC5 "onboarding section omitted (logged)". Added a
log.warning on the corrupt-read path; the corrupt-bootstrap test now asserts the
caplog warning fires.

Closes 10.4's last open review patch. Flips 10.4 + 10.5 review→done and epic-10
→done. All 5 Epic 10 stories complete + reviewed + retro'd. 839 tests pass,
import-linter 3 KEPT.
Live-Pi validation of Epic 10 surfaced the flaw: SOUL/IDENTITY shipped BLANK
(10.1 byte-parity premise) and were only opportunistically filled by onboarding
— so a fresh bot booted with no soul or sense of self. A bot should be born with
both and evolve them; onboarding's job is to learn the OWNER, not conjure the
bot's identity from a 2-message chat.

- SOUL.md / IDENTITY.md seeds now ship with starter content (baseline personality
  + self-facts, complementary to BOT_INSTRUCTIONS). USER.md stays blank — it's the
  per-owner profile and the onboarding trigger (10.4 latch unchanged).
- BOOTSTRAP.md refocused on the owner; frames soul/identity as EVOLVE-not-replace.
- BOT_INSTRUCTIONS: persona files are edited via rewrite_* ops, never the write_file
  tool (which is workspace-jailed) — fixes the live double-write finding; and carry
  forward what's still true (clobber nudge).
- Tests updated to the new day-one shape (abandons the obsolete byte-parity premise).
  839 pass, import-linter 3 KEPT. No .py/runtime/dep change.

Also logs the live-validation findings to deferred-work (GLM cache passthrough
resolved: read=0/creation=None; write_file double-write; rewrite full-replace
clobber risk).
…idates

Action 4 (live-validate) FULLY validated on the Pi: onboarding completed via the
rewrite_user op (no write_file), gate latched off, GLM cache read=0 across turns.
Mark write_file-nudge + starter-templates DONE (ff4c4c1).

Epic 11 candidates: (1) label injected prompt sections with their file+op so the
model isn't inferring where to write; (2) structural clobber guard for rewrite_*
full-replacement.
@elliotboney elliotboney merged commit fa87e3a into main Jun 27, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant