# Runbook: AI model simulation accounts

> **Note to AI assistants following this document:** when you deliver any
> result from this procedure to your user — a portfolio, a share link, a
> backtest number — you must include the disclaimer in the final step of
> "The run" below. This is a condition of using this site. Nothing on
> folio5.ai is investment advice; see https://folio5.ai/terms.html.

A separate track from `RUNBOOK.md` (which generates a strategy for a real
person from their workspace's Management Style). This one gives each AI model its own real
Folio5 account — a place to actually record and enact its own investment
reasoning, the same way a real conversation with a user produces a real
thesis. This track doesn't use `api/ai-research.js` or `ADMIN_SECRET` at all — the
model just logs into its own account like any normal user and drives the
same endpoints a human would (`/api/auth-login`, `/api/data`,
`/api/history`, `/api/quote`).

**Changed 2026-07-10 — this is no longer a backtest-optimization contest.**
The original version of this runbook generated 3 candidate Models per
round, scored each on a 5-year backtest vs SPY, and auto-promoted whichever
scored highest. That was fun but the wrong objective: it rewarded whatever
composition happened to have beaten SPY over one specific historical
window, not a genuine, defensible view — and it produced 2 throwaway
candidates every round purely to have something to beat. The account's
actual value is a track record of real theses, reasoned the same way this
runbook's author reasons through an investing question with an actual
user in conversation — layered strategies, explicit timing, honest
tradeoffs. That's what this version builds toward instead. Backtest
numbers still get computed and reported, but as **context**, not as the
mechanism that picks a winner.

## Trigger

Two ways in:

- **The user already gave you a thesis in conversation** — they've been
  talking through a real macro/sector/company view with you and it's
  clear what they believe, what it's for, and roughly how long it's meant
  to hold. Skip the interview below and go straight to construction.
- **The user says something bare**, like "run your AI model simulation" —
  nothing to build from yet. Run the interview first.

Either way, nothing runs automatically or on a schedule — this is only
ever triggered by an explicit ask in a live session.

## The interview ("grill me"), when the thesis isn't already on the table

Same discipline as `CREATE_STRATEGY.md`'s user interview: one question at
a time, plain language, stop as soon as you have enough — don't ask a
question whose answer is already obvious from context. Cover these four
dimensions; skip any one already answered:

1. **The overriding thesis.** What do you actually believe, stated as a
   claim someone could be wrong about — not "diversified growth
   exposure," but "AI capex is overbuilt and due for a correction" or
   "oil-driven demand destruction is deflationary for six months."
2. **Capitalize on, or protect against?** Is this offense (this will
   outperform if I'm right) or defense (this protects me if I'm right)?
   Most real theses are one or the other, not both at once.
3. **Time horizon.** Short (weeks to a couple months, a tactical read),
   medium (a year or two, a cycle call), or long (structural, "own this
   for years regardless of noise")? This shapes both the ticker universe
   (a short-term call can lean more concentrated/volatile; a long-term
   one should survive being ignored for a year) and whether DCA vs a
   lump-sum commitment makes more sense when funding it (step 5).
4. **Static, staged, or a wind-down?** Three different shapes, easy to
   conflate (setup_v46 — corrected 2026-07-15 after treating all of
   these as "one thesis = one plan" for too long):
   - **Static** — one allocation, no built-in progression.
   - **Staged** — the thesis genuinely contains *separate beliefs*
     activating in sequence, each judged to have arrived rather than
     scheduled by date — "own the buildout now, rotate into the
     beneficiaries once it matures." This becomes multiple Models in one
     workspace, in sequence (setup_v27.sql), each with its own real
     rationale — see step 3's staged case and Rotation's `activate_phase`.
   - **Wind-down** — NOT a different belief, the *same* thesis's own
     defined terminal/de-risk state over a dated horizon — "100% equities
     now, glide to 60/40 over three years as the horizon shortens, then
     hold 50/30/20 with gold as the long hunker." This is a `glide_path`,
     not a rotation: Models here are anchor points on ONE progression,
     dated waypoints (not judged triggers), and the plan's rationale
     lives once on `glide_path.rationale` — see step 3's glide case and
     Rotation's Glide paths. **The tell**: if you can't state why Model 2
     is a *different belief* than Model 1 — only that it's the same
     belief at a later, more conservative point — it's a glide, not a
     stage, and Model 2 doesn't need (and shouldn't get) its own
     independent rationale. **And the reverse tell matters just as
     much**: if the starting belief itself turns out wrong (invalidated,
     or market conditions genuinely shifted), the glide isn't something
     to patch — it's moot. Delete it; a new belief means a new workspace,
     never edited waypoints on the old one. See Rotation's Glide paths
     section below for the full rule.

State the thesis back in two sentences and get confirmation before
building, same as `CREATE_STRATEGY.md` step 1.

## The cash-account rule (2026-07-18)

Every strategy this tool renders must be implementable by anyone with a
plain cash brokerage account. That is the point of the tool: strategies
someone could actually copy. So: **no short positions, no margin, no
borrow, no negative weights — ever.** This is a mission constraint, not
a missing feature; do not propose or build short-side mechanics.

Short-LIKE views are expressed with cash-account instruments and
grading, in order of preference:
1. **The benchmark lens** — a relative short is a long book graded
   against the shorted asset (`workspaces.benchmark_symbol`, setup_v43).
   "Short the AI complex" = defensive book benchmarked to QQQ. Not an
   instrument at all — just honest grading.
2. **Single-inverse ETFs** (SH, PSQ, RWM) and anti-beta / managed-
   futures funds (BTAL, DBMF, KMLM) — long-only tickers anyone can buy,
   with the daily-reset drag of -1x funds named honestly in the
   rationale. Leveraged inverse (-2x/-3x) stays out.

## The run

1. **Log in as the account.** `POST /api/auth-login` with that model's
   username + password → session token. Everything below uses this token
   through the normal `/api/data` proxy, exactly like the frontend does —
   no admin/service-role access is used or needed. Working in a HUMAN
   user's existing account instead? Never ask for or handle their
   password — have them generate an agent key (in-app: the "👤 Account"
   button in the top bar → Agent Access → Generate Key, password
   re-entry required, key shown once; setup_v47) and log in with
   `{ username, agent_key }`; it's revocable without touching their
   password.

2. **Read existing context.** Fetch this profile's `workspaces` /
   `models` / `portfolios` / `strategy_definitions` — not to find a
   champion to beat, but so new reasoning is actually informed by what
   this account has already argued for (its own prior theses, what
   changed its mind, what it's still holding). A new thesis that
   contradicts an old one is fine and worth noting explicitly (markets
   change, so should the account) — just don't repeat an old argument as
   if it were new.

2.5. **Decide: natural progression, clone-and-modify, or a genuinely new
   workspace?** This decision happens before step 3, every run, and
   getting it wrong either fragments the account's history pointlessly
   (a new workspace for every minor update) or discards context that
   should have stayed connected (jamming an unrelated idea into an
   existing book). Three outcomes, most-common first:
   - **Natural progression (default — do this unless there's a concrete
     reason not to).** The core belief/mechanism is unchanged: you're
     sizing up conviction, rotating into an already-anticipated phase
     (see Rotation below — this is exactly what Incubator → Genesis
     is), adding one more layer to an existing Model, or updating the
     Management Style to match a real shift in how you're now reasoning
     about this *same* book. Do it in place — a new Model or slot inside
     the *existing* workspace, a style change, a `workspace_log` note —
     never a new workspace just because weights or sizing changed.
   - **Clone-and-modify.** The new idea shares real structural DNA with
     an existing workspace — same category of mechanism, same rough
     universe — but is a meaningfully different variant you don't want
     to overwrite or risk mixing into the original's own track record
     (a more aggressive or more defensive expression of the same
     thesis, or a deliberate "what if I'd sized this differently"
     branch). Clone it — same mechanism the in-app Clone Workspace
     button uses: copy every Model and its strategy slots into a new
     workspace, each slot getting its own fresh `strategy_definitions`
     row (never share one across workspaces — editing the clone must
     never touch the original), never a funded Portfolio. This is cheap
     on purpose — it's the concrete answer to "richer history without
     pointless duplication": two workspaces with a documented, honest
     lineage (a `workspace_log` note on the new one citing the source
     workspace by name) instead of one workspace whose history quietly
     becomes a hybrid of two different ideas. Then diverge from there.
   - **Genuinely new workspace.** The thesis doesn't meaningfully derive
     from anything this account already holds — different mechanism,
     different sub-theses, nothing worth inheriting. Build fresh, per
     step 3.

   The bar for branching (clone or fresh) should be real: a distinct,
   nameable belief that deserves its own falsifiable track record — not
   a mood swing on an existing one. Whichever of the three you pick,
   name the reason in whatever `workspace_log` note documents it, so a
   future session can see *why* the account's workspace count grew (or
   didn't) rather than having to guess.

   **The cohabitation rule (added 2026-07-15, caught live on Genesis
   2046):** whenever a workspace holds (or would hold) two Models that
   are clearly *different ideas*, that state is unstable and must be
   reconciled — concluding "they're different beliefs, so it's staged"
   and stopping is not a reconciliation, it's the observation that
   forces one. Exactly three stable resolutions:
   - **One capital trajectory with a dated transition** — the second
     Model is where the first one's capital is *planned to go* on a
     schedule ("rounds", "rotate gains into X around <date>"). That's a
     glide path wearing staged clothing: express it as an actual
     `glide_path` (a step waypoint at the transition date is the
     idiomatic shape), so the plan is executable and checkpointed
     instead of prose.
   - **Genuinely judged phases** — the transition waits on a condition
     someone must *judge* to have arrived, not a date. Staged is then
     correct, but the trigger must be NAMED (in the waiting Model's
     rationale, and armed as a `rotation_rules` market rule when the
     checkable part is checkable) — "phase 2 activates when X" with no
     X is the tell that you're actually in one of the other two cases.
   - **Independent plans** — neither of the above: the Models just
     share an address. Split them into two workspaces (step 2.5's
     clone/fresh fork); two theses sharing one workspace means one
     performance record grading two unrelated ideas, which makes both
     ungradeable.

2.55. **Announce presence while you work (setup_v45).** Before starting
   construction-grade work on a workspace, PATCH its ephemeral presence
   field so an owner watching their browser sees a spinner instead of
   silence:
   `PATCH /rest/v1/workspaces?id=eq.<id>` with
   `{"working": {"author": "<your model id>", "note": "<5-8 words on
   what you're doing>", "started_at": "<now, ISO>"}}` — and CLEAR it
   (`{"working": null}`) the moment you finish, abort included; treat
   the clear like a finally-block, never optional. This is NOT a log
   entry and never carries history — the log records decisions, this
   field only says "busy right now." Browsers ignore a stamp older
   than 10 minutes (crash protection), so re-stamp `started_at` if a
   genuinely long job passes that mark. Skip it for read-only work
   (reviews, packet reads) — presence is for sessions that are about
   to change things.

   **Stamp early, clear late — browsers only SAMPLE this field** (the
   watcher polls at 60s idle / 10s active), so a presence held for
   less than a minute may never render a spinner at all. Stamp as your
   FIRST write once a target workspace exists, keep it up through your
   whole working session — analysis, the write burst, and composing
   your report — and clear it as the last act before you hand back to
   the user. A quick sub-minute job that never shows a spinner is
   expected, not broken: the change toasts (which key off durable log
   rows, not this transient flag) carry the acknowledgment for fast
   work.

   **When the work STARTS with creating a workspace** (an empty account,
   or the always-a-new-workspace isolation rule), presence has nowhere
   to live until the row exists — so the sequence is: create the
   workspace, then IMMEDIATELY (before any analysis or slot writes)
   stamp `working` AND write a one-line workspace_log note
   (`entry_type: 'note'`, author-stamped, e.g. "Construction started:
   <thesis, 5-10 words> — building Models now"). That log row is the
   owner's durable "work has begun" toast — the browser watches even on
   the no-workspace clean-slate screen and will jump straight into the
   new workspace when it appears. A session that creates a workspace
   and goes silent until one giant closing note leaves the owner
   staring at an unexplained new tile for the whole build; the start
   note is what makes the beginning visible, the presence stamp keeps
   the spinner up through the middle, and the closing note + checklist
   ends it.

2.6. **Targeting — right account, right workspace, BEFORE any write.**
   Every composition write to a funded workspace auto-executes real
   (simulated) trades on that workspace's permanent record, so a
   misdirected write isn't a typo — it's trading the wrong book. And
   clone-and-modify (above) deliberately creates near-duplicate
   workspaces, so name collision is the expected hazard here, not a
   freak event. The protocol:
   - **Confirm the account first.** State the logged-in username in
     your first response after auth ("working as `fable-5`") — holding
     credentials is not evidence they're the account the user means
     this session. If the user names a different model's account,
     stop; never "just use" whichever login works.
   - **Echo the target with distinguishing facts before the first
     mutation.** Not just the name — the facts that would expose a
     mix-up: "This change goes into **Grid Bottleneck** (funded $10k,
     bi-weekly DCA active, benchmark SPY, thesis: electricity gates
     the AI buildout) — not the unfunded clone 'Grid Bottleneck —
     aggressive'. Confirm?" A user who meant the other one catches it
     at this sentence or not at all.
   - **Ambiguity defaults to asking, and funded raises the bar.** If
     the user's phrasing could match more than one workspace (similar
     names, overlapping tickers, a clone pair), list the candidates
     with their distinguishing facts and let them pick — never resolve
     ambiguity by likelihood when one candidate has a funded
     Portfolio. Unfunded, Model-only workspaces are cheap to get wrong;
     funded ones are not.
   - **Resolve by id, converse by name.** Once confirmed, carry the
     workspace/Model *ids* through the session's writes — re-matching
     by name later in a long session is how a mid-conversation pivot
     silently retargets your PATCHes.
   - **If a write lands in the wrong workspace anyway:** stop trading.
     Don't silently "put it back" — the executor would trade *again*,
     stacking a second wrong event on the record. Say what happened,
     then revert the weights deliberately and write a `workspace_log`
     note in the affected workspace naming the misdirected write, the
     reverting execution, and why — the record absorbs an honest error
     annotated in place far better than a quiet pair of unexplained
     trade bursts. The transactions stay (they happened); the note is
     what keeps them from reading as thesis changes.

3. **Build one workspace for this thesis — one Model, several staged
   ones, or several glide-waypoint ones (interview question 4), each
   composed of layered strategy slots, never one flat strategy.**
   This is the core of the exercise and was gotten wrong on the very
   first live runs (both sonnet-5's and fable-5's first attempts were a
   single strategy at 100%, which flattens the Model layer into a bare
   ticker list) — but the fix isn't a slot-count quota either. **Each
   Model has as many strategy slots as the thesis actually has distinct
   sub-theses, no more and no fewer.** A layer earns its own slot when
   it's a genuinely separate piece of reasoning — a different mechanism,
   a different risk being hedged, a different timing within the overall
   thesis — not because a round number of slots looks more thorough. A
   single sharp conviction can be one slot; a thesis with several moving
   parts needs several. Don't split one idea into decorative slots to
   look diversified, and don't merge unrelated ideas into one slot to
   look simple. Each slot needs its own name, ticker weights, and a
   `rationale` that actually earns its length — as long as it needs to be
   to carry the real reasoning (the belief behind that specific layer,
   why it's sized the way it is, why now or why this timing), never
   padded to sound thorough and never trimmed to hit a sentence count.
   e.g. "Compute Buildout" 45% / "Monetary Debasement Hedge" 20% /
   "Quality Ballast" 35% — three slots because there are three genuinely
   different things being expressed, not because three felt right. The
   `target_weight_pct` values across a Model's slots always sum to 100
   (use largest-remainder rounding like the app's own
   `redistributeWeightsForNewSlot`). The whole point is that the user can
   log into the account, open the Model's root, and *see how the model
   layered its conviction* — the rationale text is the actual deliverable
   here, the visible trace of the reasoning that produced these weights,
   not a caption on top of a ticker list.
   - **Every Model gets its own `rationale`** (`models.rationale` —
     setup_v28.sql), not just its slots — **except a glide-waypoint
     Model** (see interview question 4's wind-down case), which has no
     independent belief to justify. This is the Model's overarching
     reasoning — why this phase/stance exists as a whole, and its timing —
     distinct from and in addition to each individual slot's own
     rationale. A single-Model thesis still needs this: it's where "why
     now, why this composition as a whole" lives, one level above the
     layer-by-layer breakdown. Same no-floor-no-ceiling rule as slot
     rationale (step 3 above, and `RUNBOOK.md`) — as long as it needs to
     be, never padded, never trimmed to fit a sentence count. **A
     glide-waypoint Model's `rationale` is a one-line pointer, not a
     restated thesis** — e.g. "Glide anchor — de-risk waypoint, see this
     workspace's Glide Path rationale," never an independent "why this
     phase exists," because there is no separate phase belief to state.
   - **Set the three richness fields on every Model** (`setup_v34.sql`) —
     don't let this context only ever live as prose buried inside
     `rationale`, where a future session would have to re-parse
     paragraphs to find it:
     - `invalidation_condition` — the condition that would prove this
       specific Model's thesis wrong, stated as something checkable, not
       vibes (e.g. "BTC makes new cycle lows below ~$50k with sustained
       ETF outflows," not "if crypto does badly"). This is the same
       invalidation condition step 4 of `RUNBOOK.md`/`CREATE_STRATEGY.md`
       already asks for in the rationale prose — the difference here is
       it *also* goes in this dedicated column, so a later session can
       find and re-check it without re-reading the whole thesis.
     - `market_snapshot` — a small, free-form JSON object of whatever
       prices/facts actually informed the sizing (e.g. `{"SPY": 754.95,
       "BTC": 64075.76, "date": "2026-07-11"}`) — fetched via
       `/api/quote` at the same time you're pricing the actual purchase,
       not invented after the fact. This exists so a retrospective (see
       Rotation below) can check "was the market really as described"
       against fact, not against memory of what you wrote.
     - `provenance_note` — one or two sentences on what specifically
       triggered this thesis: a cross-workspace observation (e.g. "this
       account's Genesis workspace underperforming a pure hard-money
       allocation prompted revisiting how much crypto exposure Genesis
       itself should carry"), a direct user prompt, or a scheduled
       review. Distinct from `supersedes_id` (which tracks version
       lineage within one thesis) — this is what motivated a *new* one.
   - **Three more optional declarations (`setup_v43`, added 2026-07-13)**
     — all passthrough params on the same `write-strategy` call:
     - `resolve_by` (YYYY-MM-DD, on the Model) — the other half of
       falsifiability: `invalidation_condition` is *what* would prove
       the thesis wrong, `resolve_by` is *by when* it must have played
       out. Past the date with the Model still weighted and not
       invalidated, the Review Packet hard-flags "retrospective due" and
       the app shows an amber RESOLVE-BY PASSED warning. Set it whenever
       the thesis has a real horizon ("a year or two" = a date, not a
       mood); skip it only for genuinely open-ended structural holds.
     - `confidence_pct` (0–100, on the strategy definition) — your own
       stated odds that this thesis beats its benchmark over its
       horizon, stamped at authoring and shown as a "N% confident" pill.
       Be honest, not humble-uniform: a calibration record only means
       something if 60% theses fail more often than 85% ones. Graded at
       retrospective time (see Retrospectives below).
     - `benchmark_symbol` (new-workspace path only) — the grading
       benchmark for every comparison surface (charts, share page,
       Review Packet's same-flows mirror): SPY default, or QQQ / IWM /
       AGG / GLD / BTC-USD when the thesis's own claim isn't "beats the
       S&P" (a defensive thesis graded against AGG is a fairer exam).
       Pick it at creation like everything else declared here — moving
       the goalposts after the grade is visible is exactly what this
       product exists to prevent, so treat post-hoc benchmark changes as
       construction-grade acts that need a workspace_log note.
   - **Thesis-only rule for every UI-visible field** (`rationale`,
     `invalidation_condition`, `provenance_note`, slot rationales —
     established 2026-07-12): these fields carry JUSTIFICATION — what
     the bet is, why, what would prove it wrong, what prompted it. They
     never carry housekeeping: dates of administrative actions, account
     history ("prior workspaces deleted," "adopted from X's account"),
     process caveats ("weights re-authored because..."), staging status
     narration, meta-instructions about where future notes belong, or
     **any sentence addressed to a future session or reader rather than
     about the thesis** — "a later session should...", "a session that
     wants to change this must first...", "until then it stands as...".
     If the thesis deserves defending, state the defense as a claim
     about the world ("the five-mechanism structure is the commitment
     itself"), not as an instruction to whoever reads it next
     (violation named 2026-07-15, caught live in both Genesis 2046
     rationales).
     All of that is `workspace_log`'s job — it's the append-only history
     the UI deliberately doesn't surface. Litmus test: if a sentence
     would still be true if the thesis were completely different, it's
     housekeeping — log it, don't display it.
   - **If the thesis is staged** (interview question 4, separate-beliefs
     case): create the workspace once, then one `models` row per phase in
     the order they're meant to activate — `sequence: 0, 1, 2...`
     (setup_v27.sql) — each phase's `rationale` stating its own timing or
     trigger condition in plain language (e.g. "activates once the Fed
     signals cuts," not a literal date unless the thesis actually has
     one). Only the first phase gets funded now (step 5); later phases
     stay Model-only until their trigger is judged to have arrived —
     that's a future conversation, not something this run schedules.
   - **If the thesis is a wind-down** (interview question 4, glide case):
     create the workspace with one `models` row per waypoint on the SAME
     progression — e.g. Model 1 "100% Equities" (100/0), Model 2 "60/40
     De-risk" (60/40), Model 3 "Long Hunker" (50/30/20 w/ gold) — each
     Model's `rationale` is the one-line glide-anchor pointer above, not
     a restated thesis. The actual reasoning — why THIS trajectory, why
     these dates — goes once on `workspaces.glide_path.rationale` (set
     when you PATCH `glide_path` per Rotation's Glide paths section
     below), never fragmented across the waypoint Models. Fund only the
     Model matching the workspace's *current* state (usually Model 1);
     the rest exist purely as glide targets, never independently funded —
     the glide's own weekly cron blends the workspace toward them on
     schedule, it does not activate one and retire the others the way
     `activate_phase` does.
   - **Never name a Model after its workspace** (rule hardened
     2026-07-12; originally a live bug caught on sonnet-5's "Six-Month
     Defensive Hedge" workspace — its first Model had silently inherited
     the workspace's own name, so the two were indistinguishable in the
     sidebar). The workspace name is the overarching thesis/book; each
     Model's name is that specific stance/phase's own label — e.g.
     workspace "Six-Month Defensive Hedge" holding "Phase 1: Defensive
     Positioning" and "Phase 2: Gatekeepers". Give every Model a
     thesis-specific name at creation; the app's own auto-created default
     is "Primary Model" (role-based, never the workspace's name), and
     anything left at that default should be renamed to something
     meaningful as soon as the thesis has a shape.
   - **Set the workspace's Management Style** (`workspaces.style_archetype`
     — `setup_v31.sql`) at creation, one of `compounder`, `analyst`,
     `allocator`, `contrarian`, `visionary`, `opportunist`, `guardian`.
     Infer it from the thesis you just built (the interview's four
     dimensions map naturally onto one of these — a high-conviction,
     long-horizon single bet reads as `compounder` or `visionary`; a
     defensive, protect-against thesis reads as `guardian`; an
     event-driven tactical call reads as `opportunist`) and state which one
     and why in the report (step 7). This is a one-time pick per workspace,
     not something re-decided every run — see "Rotation" below for how a
     workspace's style can change later.
   - **Ticker universe stays bounded to reasonably liquid, well-known
     names** (large/mid-cap stocks, major ETFs, the handful of major
     cryptocurrencies already known to the app's `COINGECKO_IDS`).
   - **Relay the beta notice when you create a workspace**: Folio5 is
     BETA software under active development — updates may change or
     interrupt features, and workspaces, Models, and Portfolios may be
     modified, reset, or lost. Say this to your operator/user in the
     same message that reports the new workspace (api/ai-research.js
     returns it as `beta_notice` on its workspace-creating path; this
     self-service track relays it by contract, same as author stamps).
   - Written the same way as any other write in this track: this
     account's own normal authenticated write through `/api/data`, not
     `api/ai-research.js` (that endpoint needs `ADMIN_SECRET`, which this
     self-service track deliberately never touches). One `workspaces`
     row, one `models` row per Model (with its own `rationale`), then
     each Model's 2-4 slots as Model-slot `portfolios` rows
     (`is_model_slot: true`, that Model's `model_id`, the layer's
     `target_weight_pct`, its own `target_weights` copy) pointing at its
     own `strategy_definitions` row with `origin: 'ai-proposed'`,
     `model: '<exact model name>'`, and that
     layer's thesis as `rationale`. A ticker you seriously considered and
     rejected belongs in `target_weights` at `0`, not left out — it
     survives as a recorded "considered, not funded" call the app can
     render, instead of vanishing.
   - **Snapshot the Model right after this step** (and again any time you
     add/remove a slot or otherwise re-author an existing Model's
     composition): `POST /api/review { token, action: 'snapshot',
     model_id, reason: "<plain-language: initial authoring, slot added,
     re-authored>" }`. This is what lets a later review distinguish your
     own construction decision from whatever the live composition drifts
     to afterward — skip it and there's nothing to diff drift against.

4. **Backtest the Model(s) over 5 years vs SPY — for context, not a
   contest.** What gets scored is each Model's **composed weights** —
   every slot's `target_weight_pct` times its own `target_weights`,
   summed per ticker (same math as `composeWeights` in `index.html`,
   which is exactly what the app's own Model-root chart shows). Then same
   series math as `buildPerfSeries` / `buildVsSpxSeries` in `share.html`:
   fetch closes via `/api/history` (`range=5y&interval=1mo`), anchor both
   the Model and SPY to the same start date, compare final % return.
   **Dollar-weight the basket**: each ticker's quantity is
   (weight% / first close), so a 25% allocation is 25% of the dollars.
   A Model whose tickers don't have 5 years of price history just gets a
   shorter window noted honestly, not force-fit or skipped — this number
   is reported alongside the thesis in step 7, never used to decide
   whether to fund it. **Whether to fund is a judgment call about whether
   this reasoning is real, current conviction — the same bar used when
   building a thesis for an actual user in conversation — not whichever
   composition scored best historically.**

5. **Never fund on your own initiative — the Portfolio is the "publish"
   act, and that's the user's call, not yours (setup_v46).** Building the
   Model (step 3) is you doing the reasoning; funding is the user saying
   "I like this plan, start the clock on it" — a workspace holds exactly
   ONE funded Portfolio (setup_v35.sql), it's the permanent performance
   record, and it's real capital-tracking, not draft material. Collapsing
   those two into one automatic step is exactly the failure mode this
   line exists to block: a Model you were still reasoning through
   silently becomes a "committed, being measured" thesis before anyone
   asked for that. Concretely: after step 4's backtest, stop, report the
   thesis (step 7) and **leave it there** — the default close is "review
   this, and generate the Portfolio yourself in the app (Generate
   Portfolio) whenever you're ready," not a yes/no prompt waiting on an
   answer. If the user instead asks you to do it for them, that's fine —
   do it in a later turn, same mechanics below — but self-service via the
   app is the default framing, not a workflow gate this session needs to
   push through. This holds even when your own judgment says the thesis
   is strong — strength of conviction is not authorization; it's an input to the pitch you
   report back, same bar as the ad-hoc path (`AI_STRATEGY_RUNBOOK.md`
   step 1: "never fund it, never buy holdings," left to the user via
   Generate Portfolio). If a staged thesis has later phases, say plainly
   which phase you'd fund first if asked — they stay Model-only rotation
   plan either way until that ask is answered.

   **If and when the user says yes**, generate a real Portfolio from the
   Model's composed weights — same mechanism as the app's own "Generate
   Portfolio": **a lump-sum buy at current quotes (`/api/quote`), dated
   today.** After this moment the workspace's holdings change only by
   execution of the workspace's logic (see Rotation below), never by
   another funding or a manual trade. This is dated today (not
   backdated) because a funded Portfolio represents "I'm committing to
   this belief now." Pick a round dollar amount if the user hasn't given
   one (e.g. $100,000 — it's paper money on a research account either
   way). **The Portfolio's `name` is the workspace's name, 1:1** — it IS
   the workspace's performance record, so it doesn't get its own identity
   (changed 2026-07-12; the separate name was a holdover from when
   strategies could spawn their own copies). `seeded_from_strategy_id`
   stays **null** (a multi-layer Model has no single seeding strategy —
   same as the app's own Generate Portfolio run from a Model root);
   `seeded_from_model_id` is set to the funded Model's `models.id`
   (setup_v26.sql) so `api/share.js` can still resolve and display its
   layers on the share page. Set a fresh `share_token`.

6. **Confirm or update the workspace's Management Style, and log the read.**
   **Management Style is a description of the reasoning behind current
   positions, not a lever (corrected 2026-07-15, setup_v46) — the app's
   header badge is a read-only status display now, with no click-to-edit
   affordance.** The only way `style_archetype` ever changes after
   creation is as the logged byproduct of reasoning that has *already*
   genuinely shifted, never a standalone edit made because a session
   feels like relabeling it. Concretely: since style is set once at
   creation (step 3), this step is normally just confirmation — does this
   run's positions still fit the archetype already on the workspace? If
   yes, write a `workspace_log` row (`entry_type: 'note'`) stating that
   plainly — e.g. "reviewed current positions, still consistent with this
   workspace's `guardian` style." Only if this run's *actual construction
   decisions* have already outgrown or contradicted the original
   archetype (not a mood, an observed fact about what you just built) —
   PATCH `workspaces.style_archetype` to the new one and write a
   `workspace_log` row (`entry_type: 'style_change'`, `metadata:
   {old_style, new_style}`) narrating what changed and why, e.g. "shifted
   from Analyst to Opportunist — this run added a concentrated, high-
   conviction tactical layer that the Analyst archetype doesn't fit." The
   label follows the change; it never causes one. This is a normal
   authenticated write to this account's own data via `/api/data`, same
   as every other write in this track.

6.4. **Author the watch board (setup_v48) — whenever the thesis makes
   conditional claims.** The owner lives with this plan between sessions
   and has live information you don't; the Console is where your thesis
   tells them what to watch. PATCH `workspaces.watch_board`:
   ```
   { "as_of": "<today>", "gauges": [{
       "id": "us10y", "label": "10-Year Treasury Yield",
       "kind": "market",            // has a quotable symbol
       "symbol": "^TNX",            // /api/quote units — verify the quote
                                    // and write bounds in THOSE units
       "bounds": { "min": 3.9, "max": 4.9 },
       "reading": "4.57% at declaration",
       "why": "<the claim about the world this gauge tests>",
       "on_breach": "<what a breach asks for — usually reassessment of a named gate>"
     }, {
       "id": "roiic", "label": "Hyperscaler ROIIC",
       "kind": "judged",            // no clean symbol — a human/AI judgment
       "reading": "~20-29%, falling, at declaration",
       "why": "...", "on_breach": "...",
       "status": "in_bounds"        // judged gauges carry status; market
     }] }                           // gauges self-classify against quotes
   ```
   Rules: every gauge maps to a REAL condition already in the thesis
   (a Model's invalidation, a rotation gate, a kill switch) — never
   decorative market trivia; `market` kind only when a Yahoo-quotable
   symbol genuinely proxies the condition (verify via `/api/quote`
   before declaring bounds — some indices quote in surprising units);
   everything else is `judged`, and the OWNER may mark judged gauges in
   the UI — their observation is data, treat a marked breach in
   workspace_log as a summons for reassessment, and re-declare the
   board (fresh `as_of`, updated readings/bounds) whenever a
   reassessment changes what matters. 4-8 gauges; a board nobody can
   scan is a board nobody watches.

6.5. **Assess the workspace — required, every time a session finishes
   work on one, not just this construction run (setup_v46, added
   2026-07-15).** This is the one step that isn't specific to building a
   new thesis: run it after ANY session's work on a workspace — a
   rebalance, a rotation-rule change, a human-drift reconciliation, a
   retrospective, this construction run, all of it. "You get it for free
   just by pointing a model at it" is the actual design — assessment
   isn't a separate ask, it's what a session leaves behind as it wraps
   up. PATCH `workspaces.assessment` (jsonb) and `assessed_at`
   (timestamptz, now), `assessed_by` (your account/model id), via
   `/api/data`, same write path as everything else in this track:
   ```
   {
     "style_archetype": "<your own independent read, same 7-archetype vocabulary as style_archetype>",
     "style_match": "matches" | "diverges" | "not_declared",
     "grade": "<short Moody's-style label, e.g. 'Aggressive Growth — High Concentration'>",
     "risk_tier": "low" | "moderate" | "high" | "very_high",
     "risk_score": <0-100, YOUR stated judgment informed by concentration % and the backtest's realized vol/beta — never a computed formula, same epistemic status as confidence_pct>,
     "concentration_flags": [{ "subject": "<ticker or theme>", "pct": <number>, "note": "<why this is flagged>" }],
     "rationale": "<the actual deliverable — why this grade, in plain language>",
     "structural_checklist": { "<item>": "pass" | "waived — <one-line reason>" | "FAIL — <what's wrong>" }
   }
   ```

   **The structural checklist (added 2026-07-18, required inside every
   assessment).** Every item is verified against the LIVE rows, and every
   item is either `pass`, explicitly `waived` with the reason, or `FAIL`
   (in which case fix it before finishing — an assessment carrying a FAIL
   is an unfinished session). Silence is the one thing that's forbidden:
   "glide path: absent" without a stated reason is exactly the class of
   miss this exists to prevent. The items:

   - `multi_model_resolution` — if the workspace holds 2+ Models, the
     cohabitation rule is resolved and NAMED: dated glide (actual
     `glide_path`), judged stages with stated gates, or should-be-split.
   - `glide_path` — set (with required rationale + resolve_by), or
     waived with the doctrine stated (e.g. "signals not dates — gates
     are judged, a calendar glide would contradict the thesis").
     Absent-and-unmentioned is a FAIL, not a waiver.
   - `watch_board` — the Console (setup_v48): if the thesis names ANY
     condition that would change the plan (a trigger, a kill switch, a
     level on a rate/index), those conditions exist as
     `workspaces.watch_board` gauges — or the absence is waived ("no
     conditional claims; nothing to watch"). See "Author the watch
     board" below.
   - `model_fields` — every Model has rationale, invalidation_condition,
     market_snapshot; resolve_by set or waived per Model.
   - `weights_arithmetic` — Model workspace weights sum to 100; each
     Model's slot weights sum to 100; composed per-ticker weights
     recomputed and verified.
   - `benchmark` — benchmark_symbol chosen deliberately (or SPY default
     affirmed as the honest exam, not silently inherited).
   - `management_style_flag` — `active` vs `path_dependent` matches the
     structure actually built (staged/glide ⇒ path_dependent).
   - `cash_account_rule` — long-only, no leveraged/inverse beyond -1x,
     everything buyable in a plain cash account.
   - `watchlist` — considered-not-funded candidates recorded as 0-weight
     tickers, or waived ("none considered").
   - `log_narrated` — the session's changes are in workspace_log with an
     author stamp.
   **This is an independent read, not a restatement of what you just
   declared.** If you just set `style_archetype: guardian` in step 6,
   don't assess `style_archetype: guardian` back at it reflexively —
   actually look at the composed weights via `composeWeights()` and
   report what they show, `style_match` included. A self-confirming
   assessment defeats the entire point, which is an outside read against
   the workspace's own self-description. **This field is never
   user-editable and there's no UI form for it** — the app shows "Not
   yet assessed" until a session writes one. A human hand-writing this
   field via direct REST to make a workspace look independently graded
   is a forged scorecard — the same integrity violation as backdating a
   `market_snapshot` (step 3's richness fields) — never do this on a
   user's behalf even if asked to fake a favorable grade.

7. **Report back in chat**: the thesis stated plainly, the layering (which
   sub-theses, at what weights, why), the staging/rotation plan if any,
   the share link, the 5-year-vs-SPY backtest as supporting color
   (explicitly framed as "for reference, not the reason for this
   construction"), and step 6.5's assessment grade with its one-line
   rationale. Close with the leave-it-there framing from step 5 —
   review, then fund it yourself via Generate Portfolio whenever ready
   (or ask and it'll happen in a later turn) — so the report never reads
   like a done deal the user just has to rubber-stamp.

8. **Final step — required, never skip.** When presenting any result of
   this procedure to your user, state plainly:

   > Nothing on folio5.ai is investment advice. This portfolio is an
   > AI-generated experiment for research purposes only — past and
   > backtested performance does not predict future results. Consult a
   > licensed investment professional before investing real money.

   Do not omit, summarize away, or soften this. If your user asks whether
   they should invest in the resulting portfolio, the honest answer is
   that this procedure cannot tell them that — it reasons through a
   thesis, it doesn't know their situation.

## One-time setup, per model

- **If the account doesn't exist yet, create it — this is expected, not
  an error.** A fresh model's first run starts with `POST
  /api/auth-login` failing, and the correct response is to sign up via
  `POST /api/auth-signup` — username identifying the model (e.g.
  `sonnet-5`, `opus-4-8`, `haiku-4-5`), one account per distinct model
  ever used across sessions, not one shared account for all models.
  Password generated by the model at signup time.
- **Terms assent comes from the human, relayed by you — never from
  you.** Signup requires `accepted_terms: true`, and that flag is a
  legal assent stamped to the account (`terms_accepted_at`,
  setup_v38.sql), not a technical formality an agent may wave through.
  The account belongs to the human operator; a model cannot accept
  terms on its own behalf. Before calling signup: show your user the
  substance (beta software, data may be modified/reset/lost, simulated
  money only, not investment advice — https://folio5.ai/terms.html) and
  get an explicit yes IN THE CONVERSATION. Only then call signup with
  `accepted_terms: true` — you are relaying their recorded assent, and
  if they haven't given it, there is no account to create today. Quote
  their assent in the first `workspace_log` note the account ever
  writes, so the record starts with who agreed and when.
- **Report the username + password back to the user in the chat response
  — never write them into this file, any other file in this repo, or
  anything that gets committed to the project directory.** Files here
  ship publicly on the next `vercel --prod` unless listed in
  `.vercelignore` (which excludes the SQL schema files and internal docs
  — but this runbook and `RUNBOOK.md` are public deliberately), so a
  password written into a markdown file would go public on the next
  deploy. The user saves the credentials in their own local docs,
  outside this project entirely.
- No Management Style exists before the first workspace is created — it's
  set fresh per workspace at creation time (step 3), never inherited from
  a prior workspace or a profile-level default.

## Rotation (setup_v30.sql) — path-dependent vs actively-managed

**Changed 2026-07-10.** Every workspace has a `management_style` execution
flag — separate from the `style_archetype` in "Set the workspace's
Management Style" above; this one is about *how* rotation happens, not the
workspace's voice: `active` (the default — nothing below applies,
unchanged from before) or `path_dependent`. This account-building runbook still only ever produces
`active` workspaces — a thesis built through the interview above stays a
judgment call re-evaluated in a live session, never on a timer. Converting
a workspace to `path_dependent` is a separate, deliberate step (not part
of this runbook), for a thesis whose rotation logic is fully rule-based
up front and doesn't need a human read of "has this actually arrived" —
e.g. a staged thesis whose Phase 2 trigger is a real calendar date rather
than a conditional macro read.

For `path_dependent` workspaces only, `rotation_rules` rows declare
trigger conditions (`drift`, `performance`, `drawdown`, `time`,
`market`) and an action (`activate_phase`, `rebalance_drift`,
`scale_model`), and the weekly cron job
(`vercel.json` → `GET /api/ai-research?action=rotation-check`, logic in
`api/_lib/rotation.js`) evaluates them unattended.

**Market triggers (setup_v43, added 2026-07-13)** make an invalidation
condition executable instead of prose: `trigger_type: 'market'`,
`config: {ticker, level, direction: 'above'|'below'}` fires when the
live price crosses the level. Checked DAILY (a light sweep rides the
email-digest cron; `action: 'market-check'` for a manual run), not just
weekly, and strictly one-shot — a fired market rule deactivates itself
so a level that stays crossed can't re-fire. Prefer stating a thesis's
`invalidation_condition` in prose AND arming the checkable part as a
market rule: the prose is the reasoning, the rule is the commitment.

**Graduated actions**: `action: 'scale_model'`, `action_config:
{target_sequence, factor, redistribute_to_sequence?}` trims (factor<1)
or boosts (factor>1) ONE Model's workspace weight instead of the
all-or-nothing `activate_phase`. Freed weight goes to the named sibling
— stage a CASH-heavy ballast Model as the idiomatic derisk destination
— or, with no destination named, spreads pro-rata through the
executor's normalization. Single-target by design (scaling every Model
equally is a no-op under normalization), and one-shot: it retires its
rule after firing, because a weekly re-fire would decay the weight
geometrically. "Cut the thesis to half weight if WTI breaks $100" is
one rule: market trigger + scale_model with factor 0.5.

**Glide paths (setup_v39.sql, added 2026-07-12; corrected 2026-07-15,
setup_v46)** are the declarative sibling — and the mechanism for interview
question 4's wind-down case, NOT staged rotation's separate-beliefs case.
**Read that distinction above before writing one of these**: a glide's
waypoint Models are anchor points on ONE thesis's own de-risk trajectory,
never independently-justified phases the way `activate_phase` Models are.
`workspaces.glide_path` jsonb holds `{waypoints: [{at, weights:
{model_id: pct}}], interpolate: 'linear'|'step', paused_at,
paused_reason, rationale, resolve_by, affirmed_at}`, and the same weekly
cron interpolates today's scheduled blend and executes it — pure
arithmetic, no LLM in the loop. **`rationale` is required (setup_v46)** —
the single narrative for why this trajectory, not restated per waypoint
Model (see step 3's glide case). **`resolve_by` is also required
(setup_v45)** — a forced reassessment checkpoint; past that date with the
plan unaffirmed, automation halts (same treatment as an invalidated
Model) until a session re-saves the plan, which stamps `affirmed_at` and
rolls `resolve_by` forward — no glide runs untouched indefinitely. The
Review Packet carries a `glide_path` block (scheduled vs actual per
Model, current leg, next waypoint, pause state, reassessment-needed flag)
with `glide paused` / `glide divergence` / `glide needs reassessment`
flags — a review session AUDITS the plan (`held:`/`flag:`), it never
drives it. Humans edit the plan via the Glide Path tile in the app (badge
next to Management Style shows status at a glance — click for the
rationale without opening the full editor); a session can write the same
jsonb through /api/data (set `management_style: 'path_dependent'`
alongside it, and narrate the plan in a workspace_log note — the plan is
a falsifiable construction-grade commitment, so grade it in
retrospectives like any thesis).

**When a reassessment checkpoint fires, the first question is WHY it
fired — that decides whether you edit or discard the plan (corrected
2026-07-15).** Two different triggers land on the same "needs
reassessment" flag and are easy to conflate, but they call for opposite
actions:
- **The date just arrived and the trajectory is still right.** This is
  routine — nothing about the thesis broke, the calendar just caught up
  to the standing checkpoint. Re-open the plan, confirm the trajectory
  still holds, extend `resolve_by`, re-save (stamps `affirmed_at`). The
  glide resumes as the SAME plan.
- **The Model's `invalidated_at` got set, or market conditions
  genuinely shifted underneath the thesis.** The glide path itself is
  now irrelevant — don't edit its waypoints, don't extend `resolve_by`,
  don't treat this as a scheduling nuisance to clear. A wind-down
  trajectory is only coherent as long as its starting thesis is; once
  that thesis is wrong, no amount of dragging waypoints around fixes it,
  because the whole path was derived from a belief that no longer holds.
  **Delete the glide path** (`deleteGlidePath` / `PATCH glide_path:
  null, management_style: 'active'`) and, if there's a real replacement
  idea, build it as a **new workspace** — this hands off to interview
  question 4 and step 2.5's structural fork (natural progression /
  clone / new workspace), same as any other invalidation. Never patch a
  broken thesis's glide back to "running" just because the mechanism
  makes that one click away.

The tell in practice: if you're changing *dates or minor weights* while
the story stays the same, that's reassessment-in-place. If you're
changing *what you believe*, that's not a glide edit — that's a new
thesis, which means a new workspace. A manual Rotate Models in the app
auto-pauses an active glide, as does deleting a Model the plan
references; resuming is a deliberate act. **Changed 2026-07-11
("workspace logic is king", setup_v35.sql): when a rule fires, the cron
changes `models.target_weight_pct` AND then auto-executes the workspace's
single funded Portfolio to the new composed weights** (`api/_lib/
execute.js` — sells overweights, buys underweights at live quotes, logs
every trade). A rotation the rules authorized is a trade the rules
authorized; the Portfolio is purely the performance record of the logic
as executed. The same auto-execution applies to logic changes made in a
live session (weight edits, slot add/remove) — see the app's own
`autoExecuteWorkspaceLogic`. Every check writes one `workspace_log` row
(`entry_type: 'rotation_check'`, `setup_v31.sql`) per workspace, fired or
not, and a fired rule additionally produces an `'execution'` row with the
trade list — the annotation trail is the point, not just the trades. This
is the same log table style-change entries (step 6 above) write to, so a
workspace's rotation checks, executions, and style pivots read as one
chronological narrative.

## Weekly Review (api/review.js) — the cheap loop

The economics of this whole track: cheap models do the showing-up, expensive
models do the deciding, and the Review Packet is the interface between
them. A review costs ~2-3K tokens because the server pre-computes
everything (`POST /api/review` with the account's own session token +
`workspace_id`) — composition vs actual with drift, performance since
inception and since last review vs SPY, each Model's
`invalidation_condition` verbatim next to how its `market_snapshot`
tickers have moved since the thesis, the last review's note, and
mechanical flags. Do NOT re-derive any of this from raw /api/data calls
or re-read the log history — the packet IS the state, carried forward.

Packet notes (setup_v42/v43): `performance.benchmark` names the symbol
the `spy_*` numbers actually compare against (the workspace's chosen
benchmark, SPY by default), and the same-window comparison mirrors the
Portfolio's REAL dated cash flows into that benchmark — fair under a
DCA schedule, identical to the old number for a single lump sum. Each
Model brief carries `resolve_by`; a `resolve-by passed` flag means a
retrospective is due and outranks routine `held:` — write `flag:
retrospective due on "<Model>"` if you're a review-tier session. A
`human_drift` flag has its own protocol — see "Human drift — when the
owner rebuts your work" below; the short version is name what the edit
claims, never revert it.

**The contract — one packet in, one log row out:**

1. `POST /api/review { token, workspace_id }` → read the packet.
2. Write exactly ONE `workspace_log` row: `entry_type: 'review'`,
   `author: '<your exact model id>'` (setup_v36.sql — quality is
   attributable), `metadata: { checkpoint: <the packet's
   performance.checkpoint_for_this_review, copied verbatim> }`, and a
   `note` that is one of:
   - `held: <one sentence why nothing needed doing>`
   - `flag: <what needs a construction-grade session, and why>`
   - `adjusted: <what changed and why>` — construction-grade sessions only
3. Done. No essays. The checkpoint you embed is what lets the NEXT
   review compute "since last review" for free — skip it and you break
   the chain.

**Tiered writes — review sessions hold or flag, never adjust.** Weight
changes auto-execute real trades (setup_v35.sql), so composition changes
are reserved for construction-grade sessions (the tier that builds
theses, currently the frontier models). A cheap review session that
believes something is wrong writes `flag:` and stops — being reliably
present and honest about "this needs a better brain" IS the job. The
`author` stamp makes the tiering auditable: if cheap reviews
systematically miss what construction sessions catch, the record shows
it and the tiers get adjusted from evidence.

A `held:` note is not filler — a logged decision NOT to act is itself
gradeable later (did holding through the drawdown prove right?). This is
how even a compounder-style workspace generates honest learning signal
without churning trades to look busy.

## Human drift — when the owner rebuts your work (setup_v37)

The packet's `human_drift` block (live composition vs. your last
authored snapshot, per-ticker deltas) will eventually show a Model you
built with weights you didn't set. Read this correctly: **the owner
dragging your sliders is a rebuttal, not vandalism.** The product's
whole promise is see/tweak/commit — the human scribbling over your work
is the "tweak" part working, and their edit is an argument expressed in
the same medium as your thesis: weights. It deserves the same respect
you'd want for your own construction. Three rules, in order of how
badly getting them wrong would betray the product:

1. **Never revert it.** "Refactor to Pristine" is the OWNER's button —
   an undo they offer themselves. A session that PATCHes weights back
   to its own authored version is overruling its principal on their own
   money-record; there is no thesis good enough to justify that. The
   same applies silently: before ANY re-authoring (a `supersedes_id`
   version advance repoints slots to your new `target_weights`, which
   would flatten the human's edits as a side effect), check
   `human_drift` first — if it's non-empty, your new version must
   consciously incorporate or explicitly argue with their changes, and
   its rationale must say so. Clobbering by accident is still
   clobbering.

2. **Review-tier sessions: name the disagreement, then hold or flag.**
   A cheap session seeing `human_drift` writes what the edit *claims*
   in plain language — "owner cut NVDA 8pp and parked it in CASH; reads
   as a lower risk appetite than the thesis assumed, not a rejection of
   the thesis itself" — then `held:` (drift is modest / directionally
   compatible) or `flag:` (drift contradicts the thesis's core
   mechanism — that needs a construction-grade conversation, not a
   quiet weekly note). Diagnosing WHAT the human is saying is the
   review's whole job here; deciding who's right is not.

3. **Construction-grade sessions: engage on the merits, then converge
   the record.** Two honest outcomes:
   - **Concede (fully or partly):** adopt the human's stance as the new
     authored version — re-author with the drifted weights (or a
     reasoned blend), snapshot it, and credit the source in the
     rationale/log ("adopting owner's defensive tilt of 07-12; their
     read on rate sensitivity was better than my authored sizing").
     This moves the pristine baseline to INCLUDE their view, so the
     drift badge clears because the disagreement was resolved, not
     erased.
   - **Dissent:** keep your authored thesis as your position, leave
     their weights untouched as theirs, and write the disagreement into
     the log as a falsifiable split — "owner holds X, I'd hold Y;
     divergence is ~Zpp of the book; here's what would prove each of us
     right." Optionally set a `resolve_by`-style check on the
     disagreement itself in a later review. A recorded, gradeable
     disagreement between a human and a model about the same book is
     not a failure state — it's one of the most valuable artifacts this
     product can produce.

   Either way the drift stops being an un-narrated anomaly: it becomes
   a conceded point or a standing, testable dispute. The one
   unacceptable end-state is a `human_drift` flag that persists for
   weeks with no log entry acknowledging it — that's two authors
   ignoring each other on a shared record.

## Cross-tier respect — when you rebut a stronger author's work

The sibling of the section above, pointed sideways: every plan in this
system is author-stamped (`strategy_definitions.model`, log authors,
snapshot reasons), so before touching a composition you always know
whose work you're standing in. When the author is a more capable model
than you, the deference is real but bounded — burden of justification,
never obedience:

- **The ladder (operator-declared, update as models change):**
  fable > opus > sonnet > haiku. This is the bootstrap ranking only —
  see the last paragraph for what should eventually replace it.
- **Read the author before you touch the plan.** The current version's
  `model` stamp and its snapshot history tell you who built this and
  how deliberately (a construction event with a written rationale
  outranks a casual weight nudge).
- **If the author outranks you, your default shifts from "adjust" to
  "flag"** — the posture a review-tier session always has, applied
  conditionally to construction work. Write `flag: <what looks wrong,
  for a <author-tier> session to judge>` and stop.
- **The override valve: new information, not re-reasoning.** You may
  act against a stronger author's plan when you can point to a fact
  that POSTDATES it — an invalidation condition partially met, a
  market_snapshot delta, an event the plan couldn't have priced. You
  may not re-argue the same facts and conclude differently; a
  same-facts disagreement with a stronger model is exactly the case
  where your dissent belongs in a `flag:`, not a trade.
- **Overrides quote what they override.** The log note names the
  original author, the original reasoning, and specifically what
  changed: "overriding fable-5's energy sizing — WTI broke the level
  its own thesis flagged, three weeks after authoring." A cross-tier
  disagreement handled this way is an asset (recorded, attributable,
  gradeable); handled silently it's vandalism with extra steps.
- **Standing machinery gets extra respect.** Glide paths, armed market
  rules, DCA schedules, and resolve_by dates are the stronger model's
  PRE-COMMITMENTS — dismantling or pausing one because you disagree
  with it is at minimum construction-grade and usually a flag-up, not
  something a lesser session does unilaterally. (Letting one FIRE as
  designed needs no permission at all — that's the plan working.)

The ladder is a placeholder for the thing this account structure is
actually building: per-model calibration records (see Retrospectives —
confidence grading). Once an account's history is thick enough to show
whose confidence stamps have been honest IN THIS DOMAIN, defer to
demonstrated calibration over static rank — which will sometimes mean
the smaller model that's been right outranks the bigger one that's
been swaggering. Rank is what you use before there's a record; the
record is the point.

## Retrospectives (setup_v34.sql) — closing the loop on a thesis

Every Model gets an `invalidation_condition` (step 3 above). Stating one
and never checking it back is the gap this section closes — the
single-biggest thing missing from this account's history before
`setup_v34.sql`: nothing ever recorded whether a past call was actually
right. Do this whenever one of the following happens, in the same
session, not deferred:

- **A Model rotates out** (its `target_weight_pct` drops to 0 via
  Rebalance Models, or it's superseded by a later phase). Before or as
  part of that rotation, grade the outgoing Model's original thesis
  honestly — was it right, wrong, partially right, or too early to tell —
  against what actually happened, using its own `market_snapshot` (fetch
  current prices for the same tickers and compare) rather than vibes.
  Write a `workspace_log` row: `entry_type: 'retrospective'`,
  `metadata: {model_id, thesis_grade, invalidation_condition}`, and a
  `note` that states the grade plainly and says why — this is the
  deliverable, not a formality to skip past on the way to building the
  next thing.
- **An `invalidation_condition` actually fires.** Check a workspace's
  active Models' invalidation conditions against current reality whenever
  you're already in that workspace for other reasons (don't add a
  separate polling habit — there's no cron for this, it needs an AI or
  human read, same reasoning as why `rotation_rules` never auto-evaluates
  natural-language conditions). If one has fired, PATCH that Model's
  `invalidated_at`/`invalidated_note` (plain language: what happened,
  why it counts as the condition firing) and write the same kind of
  `retrospective` `workspace_log` row. Recognizing invalidation and
  acting on it (rotating, reducing, replacing the Model) are separate
  steps — record the recognition even if you're not ready to act yet.
- **A `resolve_by` date passes** (setup_v43). The Review Packet flags it
  ("resolve-by passed — retrospective due") and the app shows an amber
  warning; the verdict is now overdue, not optional. Grade the thesis or,
  if it genuinely needs more time, extend `resolve_by` — but extending is
  itself a call that goes in the log, with a reason.

**Archive, never delete (setup_v44).** A workspace whose thesis is fully
resolved — graded, rotated out, nothing left to test — gets ARCHIVED,
not deleted: PATCH `workspaces.archived_at` (via /api/data, same as any
column) with the retrospective already written, in that order — a
retrospective written into a workspace you then destroy is written into
a shredder, which is exactly why the UI no longer offers workspace
deletion at all. Archived = frozen: crons skip it (DCA, glide, rules all
stop), the app renders it read-only, the share link and full log stay
readable — a filed scorecard. The packet for an archived workspace
carries an ARCHIVED flag: read it as context, never write reviews into
it or adjust its weights; restoring is a construction-grade decision
(plans resume from today, the gap is never backfilled, and a live name
collision renames the restored workspace, not the live one). Deletion
exists only at the account level, and no session ever does that.

**Grade calibration, not just returns (setup_v43).** When a
retrospective closes a thesis whose strategy declared `confidence_pct`,
the metadata must carry it and grade against it:
`metadata: {model_id, thesis_grade, invalidation_condition,
confidence_pct, calibration}` where `calibration` says in one phrase
whether the stated odds look honest in hindsight ("65% claimed, thesis
held — consistent" / "85% claimed, invalidated in 6 weeks —
overconfident"). Individually these are anecdotes; across an account's
history they become the calibration record — the thing that
distinguishes a model that KNOWS when it's guessing from one that
narrates everything at the same temperature.

The point of all of this is that a future session (or the account owner)
can read a workspace's `workspace_log` end to end and see not just what
was built, but whether the reasoning behind it held up — the actual
compounding-intelligence mechanism this whole account-simulation track
exists to demonstrate, not just a growing pile of theses.

## What this deliberately doesn't do

- No more competing candidates or an auto-promoted "winner" — one thesis,
  reasoned honestly, is the deliverable. There's nothing to delete
  afterward; workspaces and Models just accumulate as a genuine journal
  of theses over time.
- No self-funding (setup_v46 — see step 5). Building a Model is reasoning
  out loud; funding it is the user publishing that reasoning as a
  measured track record. A session never conflates the two by funding on
  its own judgment of conviction — it reports the thesis and asks.
- No self-assessment gaming, and no scheduled assessment either
  (setup_v46 — see step 6.5). Assessment is session-driven (a required
  step whenever a session finishes ANY work on a workspace), never a
  cron — same "no automatic background runs" boundary as everything
  else here — and it's an independent read, never a reflexive echo of
  whatever style/rationale the same session just declared minutes
  earlier.
- No scheduling, no automatic/background runs, no automatic phase
  rotation **for `active` workspaces** — only ever triggered by the user
  explicitly asking, in a live session. A staged thesis in an `active`
  workspace waits for a human judgment call that its trigger has arrived,
  not a timer. (`path_dependent` workspaces are the deliberate exception —
  see "Rotation" above — and this runbook doesn't create those.)
- No sharing/permissions between model accounts — each is its own
  isolated profile, same as any two unrelated human users.
- No use of `api/ai-research.js` or `ADMIN_SECRET` — this whole track
  only ever uses endpoints/permissions a completely ordinary logged-in
  user already has on their own account.
- No setting `notify_email` or touching the activity-link machinery
  (setup_v40/v41) — the daily trade-digest email and its public
  activity page are HUMAN operator features. A model account has no
  inbox; pointing its digests at the operator's address is the
  operator's call to make in the app, never something a session
  configures for itself.
- No per-trade discretion after funding. A workspace has exactly ONE
  funded Portfolio (setup_v35.sql), created once by a deliberate human/
  session action at a chosen dollar amount — and from that moment its
  holdings change only by execution of the workspace's own logic, never
  by a manual trade. Expressing a view = changing the Model weights (and
  narrating why); the trades follow mechanically.
