Files
small-scripts/memory/log/2026-04-12.002212.md
Paul O'Reilly 143b32597e Add claude-profile engagement mode picker with statusline and session-start integration
- claude-profile: phase 1-3 picker (profile, mode, launch) with preset support,
  dryrun, WezTerm theming, and --append-system-prompt mode body injection
- 5 mode files (chat/quick/deep/hybrid/orch) with YAML frontmatter + prose body;
  new escalates_to field drives statusline →Opus arrow for deep and hybrid
- statusline.sh reads CLAUDE_CONFIG_DIR/active-mode.env to show
  [Sonnet→Opus] deep · topic format when launched via claude-profile
- Root CLAUDE.md session-start: auto-selects project from cwd or CLAUDE_PROJECT
  in active-mode.env, skipping the interactive picker when context is clear
- Spec, tests (37 assertions, 9 test files, all passing), context docs, and
  preset example included

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 00:36:38 +12:00

7.0 KiB

Session Log -- 2026-04-12

Summary

Designed and implemented engagement modes for claude-profile: 5 modes (chat / quick / deep / hybrid / orch) bundling driver model, subagent policy, async tolerance, and workflow stance. Wrote a full OpenSpec, replaced the 104-line script with a ~280-line three-phase picker, added a 37-assertion test suite (all passing), and seeded FUTURE.md with five follow-ups including a MiniMax-driven inverted architecture.

Decisions

  • Decision: Mode files live in data/claude-profile/modes/<name>.md in the repo, not in profile directories -- Rationale: source of truth must be version-controlled; profile dirs are for user state, not script data. Established data/ as a new repo top-level convention.
  • Decision: Driver enforcement is real via claude --model <full-id>, not advisory via system prompt -- Rationale: confirmed --model is supported and session-scoped via the claude-code-guide agent. Removes the contradiction of "you are Sonnet" being injected into an Opus session.
  • Decision: Use full model IDs (claude-sonnet-4-6, claude-haiku-4-5-20251001) not aliases -- Rationale: removes ambiguity about which version is selected; the claude-code-guide agent only confirmed sonnet/opus aliases, haiku alias is unverified.
  • Decision: v1 of claude-profile does NOT ask for a project interactively -- Rationale: would duplicate the existing CLAUDE.md project picker. --project flag still works for non-interactive use; CLAUDE.md integration is in FUTURE.md.
  • Decision: Mode 3 (Deep Work) drives with Sonnet, not Opus -- Rationale: Sonnet is the workhorse; Opus is the consultant. Opus only enters via named workflows (plan review, spec review, dual-model second opinion, gnarly-bug consult, architecture review). Default reasoning is "think hard", not ultrathink.
  • Decision: Frontmatter and presets.yaml parsed by pure awk, no yq dependency -- Rationale: small-scripts targets a clean Linux env; adding yq just for one launcher is overkill. Parsing is simple key:value with one-level nesting.
  • Decision: bg-model-call wrapper deferred to FUTURE.md, not built this session -- Rationale: Out of scope for the launcher; the modes reference it as a pattern but don't require it to ship.

Gotchas Discovered

  • [bash] Symptom: read -rp under set -e exits silently with code 1 when stdin is closed (no TTY), making --dryrun unusable from scripts -- Fix: guard every interactive read with [[ -t 0 ]]; fall back to default value when not on a terminal. Affected the profile picker, mode picker, and time-horizon question.
  • [claude-profile] Symptom: Original script's CONTEXT_LOADER="${SCRIPT_DIR}/context-load" failed because context-load lives in claude-foundations/scripts/, not as a sibling -- Fix: use command -v context-load (it's symlinked into ~/sbin), with the sibling location as a fallback for testing isolation. Used readlink -f "$0" so the lookup also works when claude-profile is symlinked.
  • [claude-code] Symptom: Unsure whether claude --model was supported, considered fragile workarounds (per-driver profile dirs, settings.json rewriting) -- Fix: 30-second check via the claude-code-guide agent confirmed --model is session-scoped and accepts both aliases and full IDs. Saved building 3 workarounds.
  • [shell] Symptom: eval "$preset_data" would have been a code-injection footgun if presets.yaml contained shell metacharacters -- Fix: parse preset blocks into PRESET_* variables via awk, then map them to known field names with a case statement. Never eval untrusted content.

Open Questions

  • Does CLAUDE.md (the top-level session-start instructions in ~/dev/claude/CLAUDE.md) have a clean place to read shell env files at startup? Needed for the project-picker integration follow-up.
  • Where exactly does the status-line script live in claude-foundations, and is it bash or another language? Needed before scoping the mode-tag rendering follow-up.
  • Does MiniMax M2 ship a usable agentic CLI/harness in 2026, or would building one be a precondition for the inverted-architecture follow-up?
  • Token-budget sketch under both architectures (claude-profile hybrid vs. minimax-profile) -- the inverted architecture needs >30% projected savings to be worth pursuing, or it's a research project not a productivity win.
  • For the switch mode skill: literal phrase ("switch mode") matched by a UserPromptSubmit hook, or a slash command /switch-mode? Mode files all assume the literal phrase.

Key Context

  • New data/ directory in small-scripts is a new top-level convention; documented in CLAUDE.md and README.md.
  • Driver mapping table in spec and script: haiku → claude-haiku-4-5-20251001, sonnet → claude-sonnet-4-6, opus → claude-opus-4-6, opus-1m → claude-opus-4-6[1m].
  • active-mode.env is written to $CLAUDE_CONFIG_DIR/active-mode.env; survives /clear because the path is stable. Read by CLAUDE.md session-start (future) and the status-line script (future).
  • last-mode file at $CLAUDE_CONFIG_DIR/last-mode records the most recent mode for picker default; falls back to quick if missing or invalid.
  • Presets at $CLAUDE_CONFIG_DIR/presets.yaml (per-profile, not global). Example template shipped at data/claude-profile/presets.yaml.example.
  • Documented v1 limitation: parallel claude-profile sessions against the same profile race on active-mode.env (last writer wins).
  • Test suite: tests/test-claude-profile.sh exercises 10 test groups, 37 assertions, all dryrun-based so it never launches real claude.

Process Notes

  • File-based result passing (background bash + sentinel files) emerged as the unifying design principle across this session: it ties together bg-model-call, ScheduleWakeup patterns, and the MiniMax inverted architecture. Whenever a subagent or container produces a non-trivial result, write it to a file and only Read it into context if you actually need to. This keeps the driver's context lean even when subagents do heavy work.
  • Always verify harness capabilities (like claude --model) before designing around them. A 30-second claude-code-guide agent check prevented committing to fragile workarounds. The pattern: if you're about to design a workaround for something the harness "doesn't support", verify the assumption first.
  • Spec-first paid off when the driver-enforcement question turned out to have a clean answer. The spec was already structured to accept the change cleanly (one Edit on Phase 3, one new "Driver mapping" subsection); had implementation gone first, it would have been a more invasive rewrite.
  • When extending an old script, check whether its existing references actually work in the current environment. The original CONTEXT_LOADER="${SCRIPT_DIR}/context-load" was broken in the live environment but had been masked by never running the script in dryrun mode. Tests would have caught this immediately if they'd existed.
  • For interactive bash scripts that also need to be testable: [[ -t 0 ]] guard around every read. Allows the same code path to be interactive or scripted without set -e blowups.