Summary
The takes/calibration pipeline writes and reads takes under two different hardcoded holder values, so the calibration loop can never see its own data on any brain that isn't the upstream owner's:
- The dream
consolidate phase inserts takes with holder: 'self' — src/core/cycle/phases/consolidate.ts:194 (INSERT into takes(kind='fact', holder='self', …)).
- The dream
calibration_profile phase reads the scorecard with holder ?? 'garry' — src/core/cycle/calibration-profile.ts:229.
Both run in the same cycle. One writes self, the sibling reads garry, so getScorecard({holder:'garry'}) returns resolved: 0, the phase takes its cold-brain branch, and you get:
✓ calibration_profile holder=garry has only 0 resolved takes (need >=5 for a profile)
…even on a brain with many takes. get_calibration_profile then always returns null.
Why this is a placeholder bug, not config
'garry' is hardcoded as a personal default across the calibration/forecasting surface:
src/core/cycle/calibration-profile.ts:98,229
src/core/cycle/emotional-weight.ts:50 — export const DEFAULT_USER_HOLDER = 'garry'
src/core/think/index.ts:296
src/commands/calibration.ts:161,243,248
src/commands/doctor.ts:1206 — health check hardcodes WHERE holder = 'garry', so the doctor surface is also blind on forks
A registered config key already exists for exactly this — emotional_weight.user_holder (src/core/config.ts:884), which brainstorm/orchestrator.ts:626 honors (config.emotional_weight?.user_holder ?? 'garry'). The calibration phase, the think path, and doctor ignore it and hardcode the literal.
There's also a third convention: extract-takes-from-pages.ts:128 defaults holder ?? 'system'. So three holder vocabularies (self, garry, system) coexist with no single source of truth.
Reproduction
- On any brain that isn't the upstream owner's, let
consolidate run (writes holder='self' takes).
- Run the
calibration_profile phase (or gbrain dream).
- Observe the
holder=garry has only 0 resolved takes skip despite gbrain takes-list showing many takes with holder='self'.
get_calibration_profile → null forever.
Expected
The phase that reads takes for calibration should query the same holder convention the pipeline writes. The profile should build once ≥5 resolved bets exist, regardless of whose brain it is.
Proposed fix
Single source of truth for "the brain owner's holder," resolved in one place and consumed everywhere (calibration_profile, think, doctor, emotional-weight):
opts.holder ?? config.emotional_weight?.user_holder ?? brainIdentityHolder ?? 'self'
i.e. make consolidate's 'self' and the calibration default agree (either both self, or both resolved from emotional_weight.user_holder / brain identity), and route doctor.ts:1206 + think through the same resolver instead of the bare literal.
Related
Stacked behind #2079 (takes list slug footgun) and #2411 (takes propose promotion path missing) — all three sit between "proposals exist" and "a profile appears." Holder alignment is necessary even after those land. Model-resolution sibling fixes in #2451 / #2452. Verified against master v0.42.53.0.
Summary
The takes/calibration pipeline writes and reads takes under two different hardcoded holder values, so the calibration loop can never see its own data on any brain that isn't the upstream owner's:
consolidatephase inserts takes withholder: 'self'—src/core/cycle/phases/consolidate.ts:194(INSERT into takes(kind='fact', holder='self', …)).calibration_profilephase reads the scorecard withholder ?? 'garry'—src/core/cycle/calibration-profile.ts:229.Both run in the same cycle. One writes
self, the sibling readsgarry, sogetScorecard({holder:'garry'})returnsresolved: 0, the phase takes its cold-brain branch, and you get:…even on a brain with many takes.
get_calibration_profilethen always returnsnull.Why this is a placeholder bug, not config
'garry'is hardcoded as a personal default across the calibration/forecasting surface:src/core/cycle/calibration-profile.ts:98,229src/core/cycle/emotional-weight.ts:50—export const DEFAULT_USER_HOLDER = 'garry'src/core/think/index.ts:296src/commands/calibration.ts:161,243,248src/commands/doctor.ts:1206— health check hardcodesWHERE holder = 'garry', so the doctor surface is also blind on forksA registered config key already exists for exactly this —
emotional_weight.user_holder(src/core/config.ts:884), whichbrainstorm/orchestrator.ts:626honors (config.emotional_weight?.user_holder ?? 'garry'). The calibration phase, thethinkpath, anddoctorignore it and hardcode the literal.There's also a third convention:
extract-takes-from-pages.ts:128defaultsholder ?? 'system'. So three holder vocabularies (self,garry,system) coexist with no single source of truth.Reproduction
consolidaterun (writesholder='self'takes).calibration_profilephase (orgbrain dream).holder=garry has only 0 resolved takesskip despitegbrain takes-listshowing many takes withholder='self'.get_calibration_profile→nullforever.Expected
The phase that reads takes for calibration should query the same holder convention the pipeline writes. The profile should build once ≥5 resolved bets exist, regardless of whose brain it is.
Proposed fix
Single source of truth for "the brain owner's holder," resolved in one place and consumed everywhere (calibration_profile, think, doctor, emotional-weight):
i.e. make
consolidate's'self'and the calibration default agree (either bothself, or both resolved fromemotional_weight.user_holder/ brain identity), and routedoctor.ts:1206+thinkthrough the same resolver instead of the bare literal.Related
Stacked behind #2079 (
takes listslug footgun) and #2411 (takes proposepromotion path missing) — all three sit between "proposals exist" and "a profile appears." Holder alignment is necessary even after those land. Model-resolution sibling fixes in #2451 / #2452. Verified againstmasterv0.42.53.0.