2026-07-19

BAZI AGI — Architecture & Roadmap

Finn Tang · Finn Tang's Reading Studio

Date: 2026-07-20 · Audience: Finn / engineering · Status: honest working memo, not marketing Rule of this document: every capability claim is tied to code that exists (cited file:line) or is explicitly marked NOT BUILT. Measured numbers are stated exactly, including the ugly ones.


1. Thesis

Bazi AGI is not a more fluent LLM. The engine is already fluent — fluency is currently its failure mode: on a hardened blind eval it writes confident, well-structured essays and scores statistically indistinguishable from chance (34.2% pre-fix, 23.7% post-fix, vs 25% chance; evals/contest8/clean/SUMMARY.md:8, evals/contest8/clean_v46/SUMMARY.md:9-11). Bazi AGI is a system whose interpretation is grounded in verified outcomes rather than classical texts: a deterministic calculation substrate, an LLM constrained to cite that substrate, and a learning loop in which real, blind-captured life outcomes — keyed to a canonical birth chart — continuously re-weight which structural markers are allowed to assert anything at all. Honesty is not a brand posture here; it is the mechanical enabling condition for learning. A system that rationalizes its misses ("that was correct on another level") destroys its own training signal; a system that records "wrong, here is the marker that misled us" (qa-compose/index.ts:126-132) produces exactly the labeled error data the flywheel consumes. No honest error signal → no gradient → no learning → no AGI. That is the whole thesis.


2. The Layered Architecture

Four layers. Each is only as trustworthy as the layer below it.

L1 — Deterministic substrate (the calculator)

What exists. All chart quantities are pure functions, computed server-side, never delegated to the LLM:

  • Pillars, 大運, 流年, and the lifetime 斷事 signal table (age 16 → current+5) in supabase/functions/compose-reading-async/index.ts (computeDuanShiTable, :2041-2045; twenty-year 大運/流年 grid, :2047+).
  • 用神 with 先天 (調候) / 後天 (扶抑) split and 大運 overlay: supabase/functions/_shared/yongshen.ts:20-21, 97, 156.
  • 十神 (tenGod, chart-facts-detail.ts:79), 藏干 (HIDDEN_STEMS, :96; deriveAllPillarShishen, :152), 地支關係 incl. 沖/合/刑/害 (detectBranchRelations, :270), 天干五合 (:238), 神殺 (:634, :771), plus the deterministic identity block (computed age, sex-correct 六親星, bound 當前大運; buildIdentityBlock, :961) and queried-year block (buildQueriedYearBlock, :1036).
  • These builders are ported verbatim into _shared/ so composer and Q&A can never disagree (qa-compose/index.ts:84-93).

What's fragile. Nothing structural — this layer is the healthiest asset. Known caveat: hour-pillar-dependent markers are noise wherever birth time is assumed (41/45 charts in the held-out audit used a 12:00 placeholder; evals/baziqa/LIFT_AUDIT.md:51-61).

What's missing. Nothing blocking. L1 is done enough; the problem is above it.

L2 — Grounded reasoning (the constrained essayist)

What exists. qa-compose/index.ts (deployed ~v47):

  • Grounding firewall: deterministic blocks injected as authoritative context the model must cite, never re-derive (:446-479, :211-213); full 命書 as anchor (:484-492); F.5 input screens (:51-79) and egress audit with refund-and-withhold (:914-920).
  • Honesty-under-feedback: when the customer supplies verified outcomes, the prompt suspends the reframing style and mandates admit → score honestly → diagnose the misleading marker → state what to down-weight (:126-132). Retrodiction against the computed 斷事 table with an explicit "don't Barnum" rule (:154-159).
  • Context mixture router (question axis): prior Q&A is topic-routed by coded_domain — same-domain memory in, off-domain memory dropped to prevent frame carryover (:426-441).

What's fragile. The measured reality: grounding fixes eliminated phantom ages, gender-star inversions, and fabricated branch relations (clean_v46/SUMMARY.md:44-51 — all three signature classes → ~0) but accuracy did not improve (34.2% → 23.7%, Δ within noise at n=38). The errors moved from scaffolding into judgment. The reasoning study of the transcripts (evals/contest8/clean*/chart_*.json, full_response) reached the blunt verdict: the engine is a fluent essayist, not a calculator — it story-fits a chosen answer rather than weighing evidence; best on cause/status questions, at-chance on dated events. L2 is honest and grounded, but grounded-and-wrong.

What's missing. Any mechanism that makes the model weigh markers rather than narrate them — which is precisely L4's output (learned marker weights) that doesn't yet feed back into the prompt. Also the domain router is a regex classifier (classifyDemand, :246-258), unevaluated against human labels.

L3 — Interaction manager (subject × topic memory)

What exists. The subject-id layer: interaction_subjects registry with order-normalized dedup keys, solo/dyad kinds, and get_reading_context() as the intended single door — Q&A and disclosed events scoped to subject_id by construction (supabase/migrations/20260718_interaction_subjects.sql:6-25, 92-144). Subject self-healing on legacy readings (qa-compose/index.ts:810-817).

What's fragile — the known leak. buildContext still seeds from the account owner's profile and then ??-patches the subject over it (qa-compose/index.ts:346-401): profile is fetched unconditionally (:346-349), and the additional-person / birth-chart overrides fall back to owner fields on any miss (?? subjectBirthDate, ?? subjectDayStem, :386-388, :375). One silent query failure and the owner's chart leaks into another subject's reading — the exact class of bug already hit once (:366-371). The required structural fix is the isolation inversion: resolve the subject source first, fetch only that source, and delete every cross-source fallback. NOT BUILT.

What's missing. Multi-birthday "expert routing" (per-chart expert packs) and a real interaction manager that compacts per-(subject × topic) memory into maintained summaries — today "memory" is raw rows truncated to 120/380 chars (:503-507), which caps how much accumulated context a long relationship can actually use. Both NOT BUILT.

L4 — The learning loop (the actual moat)

What exists (all keyed to birth_chart_id, all firewalled from is_test):

  • birth_charts — canonical, reversible "birthday id"; immutable raw inputs, regenerable chart_facts, fingerprint-deduped; FK'd from profiles, persons, jobs, outcomes (20260719_birth_charts_registry.sql:5-34).
  • flywheel_predictions (logged at compose time, compose-reading-async/index.ts:3727) ⋈ flywheel_outcomesflywheel_marker_weights with a demote-only recompute: Bayesian shrinkage toward base rate (α=40), demotion only after ≥20 live blind outcomes, promotion hard-gated off (20260718_n4_flywheel_live_loop.sql:27-58, 62-145). Event magnitude coding so firework markers aren't scored against life-turning labels (:189-222).
  • The blind firewall: captured_blind=TRUE can be set in exactly one place, ingest_blind_outcome(), and only when is_prediction_disclosed() proves no prior disclosure; callers can force FALSE, never TRUE (20260719_blind_capture.sql:12-19, 83-186). The contaminated path (captureDisclosedOutcomes in Q&A) hard-codes captured_blind=false because the customer has already read their reading (qa-compose/index.ts:520-609). disclosed_events gives typed subject-scoped memory; case_observations captures year-signal↔statement pairs (:938-956); engine_test_feedback is the CLI's own scoreboard, firewalled from customer truth (20260719_engine_test_feedback.sql).

What's fragile. The mark_prediction_disclosed hook is not yet wired into compose/QA disclosure moments (20260719_blind_capture.sql:50-53 says so explicitly — the chart_key fallback covers it conservatively). Chart-key matching can over-block look-alike charts (safe direction, but it shrinks the blind set).

What's missing. The data. The apparatus is real; the blind outcome set is approximately empty. There is no customer-facing outcome-first (judgment-shell) UX shipped, so nothing flows through ingest_blind_outcome at volume. The loop exists as plumbing, not as a loop.


3. The Learning Loop, Concretely

The core question: how do captured events become better interpretation?

Stored today (real schemas):

  • Prediction side: per (chart_key, event_year): engine_markers, engine_families, has_strong, tier, domain, and — critically — grammar_tier_used (what assertion strength the customer actually saw).
  • Outcome side: per (birth_chart_id, event_year): raw text, coded domain (家庭/事業/財富/感情/健康), valence [-1,1], magnitude [0,1], coder, captured_blind, rectification_derived.
  • Weights: per marker family: n_live, p_hat (shrunk), base_rate, lift, promotion_state ∈ {locked, abstention, colour, assertive}, evidence JSON.

From events to markers: the join is already written — recompute_flywheel_live_demote() unnests each prediction's families, joins blind non-rectification outcomes on (chart_key, year), and computes per-family precision vs base rate with α-shrinkage (n4_flywheel_live_loop.sql:83-112). At ≥20 blind outcomes with p̂ ≤ base rate, a family drops to abstention — the engine stops asserting on it. Promotion to assertive is deliberately impossible until a control battery exists (promote_enabled=0, :57). The missing refinement step — feeding promotion_state back into the composer/Q&A prompt so an abstention family is actually silenced and an eventual assertive family speaks with 斷定 grammar — is NOT BUILT. That single wire is what turns statistics into interpretation.

The statistics discipline (already practiced, must stay law):

  • Out-of-sample or it didn't happen. The 1.6× strong-marker lift measured in-sample collapsed to 1.10× (CI 0.98–1.22, p=0.078) on 45 held-out charts, and 0.91× on the 4 real-birth-time charts (LIFT_AUDIT.md:7-11, 45, 54-61).
  • Base-rate honesty. The "有訊號" flag fires on ~91% of years → lift 1.00× → worthless as a predictor; this replicated exactly across sets (LIFT_AUDIT.md:41, RUNLOG.md X10 entry).
  • Pre-registration and published nulls. X9/X10 were pre-registered and both null; the public 94.5%-recall claim was retracted and replaced with the honest number (evals/RUNLOG.md:20-33, evals/x10/PREREG.md).
  • Firewalls in SQL, not in policy. Blindness is computed server-side and can only be downgraded (blind_capture.sql:14-19); rectification-derived outcomes are excluded in the join itself (n4:92).

What does not exist yet: a per-domain marker→outcome model (current unit is marker family × any-event; domain-conditional precision has a column but no pipeline), a disclosure-ledger wiring, a magnitude-stratified scorer, and any feedback edge from weights to prompts.


4. The Honest Gap

ClaimStatusEvidence
Deterministic chart math (pillars, 十神, 藏干, relations, 神殺, 用神, 大運/流年)Proven (unit-consistent, shared builders)chart-facts-detail.ts, yongshen.ts
Grounding removes hallucinated ages / gender-stars / fabricated relationsProvensignature scan 7→0, 6→0, ~0 fabrications (clean_v46/SUMMARY.md:44-51)
Grounding improves predictive accuracyDisproven so far34.2% → 23.7% after fixes; both ≈ chance (25%) at n=38
Engine predicts blind life outcomesAt chance. "Fluent essayist, not a calculator": story-fits, best on cause/status, at-chance on dated events (dated-event 8-25%)contest8/clean*/SUMMARY.md
1.6× strong-marker timing liftDid not replicate out-of-sample (1.10×, CI includes 1.0; below 1.0 on clean-birth-time subset)LIFT_AUDIT.md
"有訊號" year flag has predictive valueDisproven (91% density, 1.00× lift, twice)LIFT_AUDIT.md:41, RUNLOG X10
Blind-capture firewall correctnessProven by construction (single door, downgrade-only)20260719_blind_capture.sql
Learning loop improves interpretationUntested — blind outcome set ≈ empty; weights→prompt edge unbuilt
Subject isolationKnown leak (owner-seed + ?? fallbacks)qa-compose/index.ts:346-401

Plainly: today the product's verified value is fluency + correct arithmetic + honesty. Predictive skill is not demonstrated. Everything below is the plan to change that or find out it can't be changed.


5. Roadmap

The binding constraint is verified outcomes at scale — not model quality. The demote gate alone needs ≥20 blind outcomes per marker family; dozens of families ⇒ order 10³ blind, dated, domain-coded outcomes before the flywheel says anything. Every phase is sequenced to protect and grow that asset.

Phase 0 — Isolation inversion (days). (a) Rewrite buildContext: resolve subject source first (job → additional_person | birth_chart | self), fetch only that source, delete every cross-source ?? fallback; route all context through get_reading_context. (b) Falsifiable metric: an adversarial cross-subject harness (owner + N additional persons + registry charts, induced query failures) shows 0 owner-field leaks in 100% of trials, and contest8-clean re-run is unchanged (no regression). (c) Depends on nothing; everything depends on it — dirty subject attribution poisons every outcome row captured afterward.

Phase 1 — Labeled expert packs + real router (1-2 weeks). (a) Per-birth-chart "expert pack" (compact, versioned digest of L1 facts + subject memory), replace regex classifyDemand with an evaluated coder. (b) Metric: router domain-coding ≥90% agreement with a 200-question human-labeled set (today: unmeasured); mis-routed context is the current silent failure of the mixture router. (c) Depends on Phase 0 (packs must be leak-free).

Phase 2 — Interaction manager + compaction (2-4 weeks). (a) Per-(subject × topic) rolling summaries maintained at write time (Cowork-style compaction) replacing 380-char truncation; disclosed_events folded into the pack. (b) Metric: on a scripted 12-turn multi-topic conversation eval, repetition/contradiction rate drops vs baseline at equal token budget, with no contest8 regression. (c) Depends on Phase 1 (compaction is per-topic; needs the router).

Phase 3 — Outcome capture at scale (the constraint-breaker; quarter). (a) Wire mark_prediction_disclosed at every disclosure moment; ship the judgment-shell UX (outcome-first: user narrates dated events before seeing predictions — the only door to captured_blind=TRUE); incentivize with credits. (b) Metric (volume, falsifiable): ≥50 blind outcomes/week sustained; gate to Phase 4 = first marker family reaches n_live ≥ 20 and recompute_flywheel_live_demote() produces its first live demotion. (c) Depends on Phases 0-2 for trustworthy keys, but should start immediately in parallel — it is the long pole.

Phase 4 — Marker learning, out-of-sample (2-3 quarters). (a) Domain-conditional, magnitude-stratified marker scoring; wire promotion_state into compose/Q&A grammar (abstention families silenced; nothing promoted without the X1 control battery). (b) Metric (pre-registered): on a frozen held-out blind-outcome set, ≥1 marker family shows lift with 95% CI excluding 1.0; and the weights-informed engine beats the frozen engine on calibration (it abstains where it has no signal). If no family clears the bar, publish the null — that is also an answer. (c) Depends on Phase 3 data.

Phase 5 — Closed loop (12+ months). (a) Interpretation continuously conditioned on live weights; per-chart error memory ("this chart's dated-event calls run hot") feeding the expert pack. (b) Metric: a powered blind contest (≥30 charts × 5 questions, n≥150, pre-registered) scoring above chance with CI excluding 25%. This is the first moment "bazi AGI" is a measured claim rather than a slogan. (c) Depends on Phase 4.


6. North Star & Monday

North star (one sentence): A reading system in which every assertion is traceable either to a deterministic chart computation or to a marker whose predictive lift survived pre-registered, out-of-sample, blind-outcome testing — and which honestly says "no signal" everywhere else.

Monday:

  1. Ship the isolation inversion in qa-compose/buildContext (subject-source-first, no cross-source fallbacks) plus the adversarial leak harness — Phase 0's metric, same day.
  2. Wire mark_prediction_disclosed into compose-reading-async and qa-compose at the moment flagged years are shown, closing the disclosure ledger gap (blind_capture.sql:50-53).
  3. Pre-register the powered contest eval (30 charts × 5, hardened rules from contest8/clean/harness.mjs) so that from now on every engine change gets an accuracy number we're forced to believe.

The uncomfortable, load-bearing truth: the substrate is solid, the honesty machinery is real, and the predictive layer is at chance. The plan above either produces the first at-scale evidence that bazi structural markers predict anything — or produces the honest null. Both outcomes are ones this architecture is built to survive; only one of them was possible for a system that flatters itself.