Seed the v5 thesis into the Architect Workshop (v0.1.0:53)

backend/thesis_seed.py builds the starting "living messaging source of truth"
from docs/thesis-seed-v5.md: a core line (throughline; the open Option A/B banner
as a competing variant group; the three pillars; the proof; voice rules), one line
per LP segment carrying that segment's angle, and the five segment definitions.

ensure_thesis_seed(conn) runs from init_db, seeding ONLY when the Workshop is empty
(no thesis lines) — idempotent and non-destructive, so it bootstraps once and never
overwrites partner edits. Everything lands draft/candidate; nothing is made canonical
(that stays the partners' dual-approval action, guardrail #4). Content is Ten31's own
messaging, not LP data.

Test: backend/test_thesis_seed.py runs init_db and asserts the core line, 5 segment
lines, the 2-member Option A/B variant group, 3 pillars, segment_cuts, and segment
defs, plus re-seed-is-a-no-op (11/11).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Keysat
2026-06-05 15:19:44 -05:00
parent 2afed210cb
commit 49d384a0fb
6 changed files with 314 additions and 4 deletions
+7
View File
@@ -469,6 +469,13 @@ def init_db():
except Exception as _e: except Exception as _e:
print(f"[backfill] grid contact_id backfill warning: {_e}") print(f"[backfill] grid contact_id backfill warning: {_e}")
# One-time: seed the v5 thesis into the Architect's Workshop if it is empty.
try:
from thesis_seed import ensure_thesis_seed as _ensure_thesis_seed
_ensure_thesis_seed(conn)
except Exception as _e:
print(f"[thesis] seed warning: {_e}")
conn.close() conn.close()
print(f"Database initialized at {DB_PATH}") print(f"Database initialized at {DB_PATH}")
+78
View File
@@ -0,0 +1,78 @@
#!/usr/bin/env python3
"""Offline test for the v5 thesis seed (init_db auto-seed + idempotency).
Runs the real init_db against a throwaway DB (applies migration 0002 and the
auto-seed), then asserts the Workshop substrate: a core line, one line per segment,
the Option A/B banner as a 2-member variant group, the pillars/proof, and the
segments table — and that re-seeding is a no-op.
Run: cd backend && python3 test_thesis_seed.py
"""
import os
import sqlite3
import sys
import tempfile
_tmp = tempfile.mkdtemp()
os.environ["CRM_DATA_DIR"] = _tmp
os.environ["CRM_DB_PATH"] = os.path.join(_tmp, "crm.db")
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import server # noqa: E402
import thesis_seed # noqa: E402
FAILS = []
def check(cond, msg):
print((" PASS " if cond else " FAIL ") + msg)
if not cond:
FAILS.append(msg)
def main():
server.init_db()
conn = sqlite3.connect(server.DB_PATH)
conn.row_factory = sqlite3.Row
lines = {r["line_key"]: dict(r) for r in conn.execute("SELECT * FROM thesis_lines WHERE deleted_at IS NULL")}
check("core" in lines and lines["core"]["is_core"] == 1, "core thesis line seeded (is_core)")
seg_lines = [k for k in lines if k.startswith("seg_")]
check(len(seg_lines) == 5, f"five per-segment lines seeded (got {len(seg_lines)}: {sorted(seg_lines)})")
check(len(lines) == 6, f"exactly 6 lines total (got {len(lines)})")
core_id = lines["core"]["id"]
nodes = [dict(r) for r in conn.execute("SELECT * FROM thesis_nodes WHERE line_id=?", (core_id,))]
types = [n["node_type"] for n in nodes]
check("throughline" in types, "core has a throughline node")
check("proof_point" in types, "core has a proof_point node")
variants = [n for n in nodes if n["variant_group"] == "positioning"]
check(len(variants) == 2, f"Option A/B banner is a 2-member variant group (got {len(variants)})")
pillars = [n for n in nodes if n["node_type"] == "claim" and n["title"] and n["title"][0] in "123"]
check(len(pillars) == 3, f"three pillar claims (got {len(pillars)})")
segs = {r["segment_key"] for r in conn.execute("SELECT segment_key FROM segments WHERE status='active'")}
check(segs == {"btc_native_hnwi", "institution", "family_office", "smaller_accredited", "ai_energy_operator"},
f"five active segments (got {sorted(segs)})")
# segment lines each carry a segment_cut angle
cut = conn.execute("""SELECT COUNT(*) FROM thesis_nodes n JOIN thesis_lines l ON l.id=n.line_id
WHERE n.node_type='segment_cut' AND l.line_key LIKE 'seg_%'""").fetchone()[0]
check(cut == 5, f"each segment line has a segment_cut angle (got {cut})")
# idempotency: re-seeding does nothing (thesis not empty)
thesis_seed.ensure_thesis_seed(conn)
conn.commit()
n2 = conn.execute("SELECT COUNT(*) FROM thesis_lines WHERE deleted_at IS NULL").fetchone()[0]
check(n2 == 6, f"re-seed is a no-op when thesis already exists (got {n2})")
conn.close()
print()
if FAILS:
print(f"FAILED ({len(FAILS)})")
sys.exit(1)
print("ALL PASS (v5 thesis seed)")
if __name__ == "__main__":
main()
+202
View File
@@ -0,0 +1,202 @@
"""Seed the Ten31 v5 thesis into the Architect's substrate (Phase 1).
Builds the starting "living messaging source of truth" the partners iterate on in
the Thesis Workshop: a CORE line (throughline, the open Option A/B banner debate as
competing variants, the three pillars, the proof, and the voice rules) plus one
LINE PER SEGMENT carrying that segment's angle, and the segment definitions.
Content is Ten31's OWN messaging (docs/thesis-seed-v5.md) — not LP data — so it is
safe to ship in code. Everything lands as DRAFT/CANDIDATE nodes: nothing is made
canonical here (that is the partners' dual-approval action — guardrail #4).
`ensure_thesis_seed(conn)` is idempotent: it seeds ONLY when no thesis lines exist,
so it bootstraps an empty Workshop once and never clobbers partner edits afterward.
"""
import json
import sqlite3
import uuid
from datetime import datetime, timezone
def _now():
return datetime.now(timezone.utc).replace(tzinfo=None).isoformat() + "Z"
def _eid(prefix):
return f"{prefix}_{uuid.uuid4().hex[:16]}"
def _log(conn, action, target_id, payload):
conn.execute(
"""INSERT INTO interaction_log
(id, ts, actor_type, actor_id, action, target_type, target_id, payload, source, created_at)
VALUES (?,?, 'system', 'thesis_seed', ?, 'thesis', ?, ?, 'seed', ?)""",
(str(uuid.uuid4()), _now(), action, target_id, json.dumps(payload) if payload is not None else None, _now()),
)
def _line(conn, line_key, name, segment_key=None, is_core=False, description=None):
lid = _eid("thl")
conn.execute(
"""INSERT INTO thesis_lines (id, line_key, name, segment_key, is_core, description, created_at, updated_at)
VALUES (?,?,?,?,?,?,?,?)""",
(lid, line_key, name, segment_key, 1 if is_core else 0, description, _now(), _now()),
)
_log(conn, "thesis.line_seeded", lid, {"line_key": line_key, "is_core": bool(is_core)})
return lid
def _node(conn, line_id, parent_id, node_type, ordn, title, body, status="draft", variant_group=None):
nid = _eid("thn")
conn.execute(
"""INSERT INTO thesis_nodes (id, line_id, parent_id, node_type, ord, title, body, status, variant_group, created_at, updated_at)
VALUES (?,?,?,?,?,?,?,?,?,?,?)""",
(nid, line_id, parent_id, node_type, float(ordn), title, body, status, variant_group, _now(), _now()),
)
return nid
# ── v5 content (docs/thesis-seed-v5.md) ──────────────────────────────────────
THROUGHLINE = (
"Bitcoin, AI, and energy are three of the largest growth markets of the next decade, "
"and they depend on the same scarce resources: cheap energy and computing power. We "
"believe that energy, compute, and AI infrastructure will settle on money that is hard "
"to produce. That is not the case today, and connecting these markets to bitcoin is the "
"part of the thesis that very few others are making, even as broader crypto tries to "
"attach itself to AI and energy. Ten31 invests in that infrastructure with strong conviction."
)
OPTION_A = (
"Ten31 invests in the infrastructure of scarcity. We back the bitcoin, energy, and AI "
"companies that produce and secure the scarce resources these markets are built on."
)
OPTION_B = (
"Ten31 invests in freedom technology. We back the bitcoin, energy, and AI companies "
"building the foundation for a more sovereign, less centralized economy."
)
PILLAR_1 = (
"Every one of these markets is bottlenecked on something scarce. AI and bitcoin both "
"compete for cheap energy and compute. And we believe energy, compute, and AI "
"infrastructure will increasingly settle on money that is hard to produce, which points "
"directly at bitcoin. The companies that own and supply the scarce side of that equation "
"capture the value as demand grows. That is where we invest. (The bitcoin connection is a "
"forward-looking conviction, not a description of today. That gap is the opportunity.)"
)
PILLAR_2 = (
"We invest in foundational infrastructure with real revenue: the companies that generate "
"energy, secure capital, and power computation. Real businesses earning real money from "
"real demand today."
)
PILLAR_3 = (
"Founders come to us because of our experience and our genuine alignment with bitcoin. We "
"pursue and lead opportunities exclusive to us. People in this ecosystem know our track "
"record and want us on their side."
)
PROOF = (
"$200M+ deployed across two funds into 30+ of the strongest companies in the space "
"(Strike, Start9, energy and mining infrastructure). A six-year track record that includes "
"large-scale M&A and public-markets activity unmatched by others in this space. Fund III "
"continues the same strategy."
)
VOICE = (
"Direct, concrete, conviction-driven. No \"betting\" language, no em dashes, no \"X, not Y\" "
"phrasing, no kitchen-sink lists. Plain sentences a serious LP can verify in their head."
)
POSITIONING_NOTE = (
"Open partner decision: which banner do we lead with? Option A frames Ten31 around "
"scarcity; Option B frames it around freedom technology (a banner in the spirit of a16z's "
"\"American Dynamism\"). Both wordings are still being refined — use the Architect to draft "
"more options."
)
# segment_key -> (display name, definition, needs_to_hear angle)
SEGMENTS = [
("btc_native_hnwi", "Bitcoin-native HNWIs (OGs)",
"Long-time bitcoin holders with significant net worth who care about the ecosystem succeeding.",
"Bitcoin only wins if people build on it. Holding is not enough. You care about making "
"bitcoin succeed, and so do we. We put capital behind the companies that turn bitcoin into "
"a working economy.",
"Don't lecture OGs or imply they don't understand bitcoin; don't suggest that simply holding is the goal."),
("institution", "Institutions",
"Institutional allocators evaluating exposure to the bitcoin, energy, and AI buildout.",
"Exposure to the bitcoin, energy, and AI buildout through a team with a six-year "
"institutional track record, including large-scale M&A and public-markets activity unmatched "
"by others in this space (and Grant's prior institutional experience on top of that).",
"No hype or moonshot framing. Lead with the verifiable track record, not vision."),
("family_office", "Family offices",
"Multi-generational capital seeking durable, long-horizon allocations.",
"A long-horizon allocation grounded in real businesses, run by a team with deep credibility "
"and a real track record.",
"Avoid short-term or trader framing; emphasize durability, real businesses, and horizon."),
("smaller_accredited", "Smaller accredited ($100k)",
"Accredited investors entering at a more accessible commitment size.",
"The same thesis our most convicted investors back, at an accessible entry point.",
"Don't talk down to them; it is the same thesis, just an accessible entry point."),
("ai_energy_operator", "AI & energy operators",
"Operators in AI and energy who are not yet focused on bitcoin.",
"You may not be focused on bitcoin today, and that is exactly the point. We believe bitcoin "
"becomes a larger component of energy and compute over time, and most operators in your "
"space are not yet positioned for it. We are, and we invest across the stack that connects them.",
"Don't assume they're bitcoin-focused and don't preach; connect bitcoin as a growing "
"component of their world over time."),
]
def seed_v5(conn):
"""Insert the full v5 thesis (core line + per-segment lines + segments). Assumes
the caller has confirmed the thesis is empty; uses fresh ids."""
# ── segments table ──
for key, name, definition, needs, avoid in SEGMENTS:
sid = _eid("seg")
conn.execute(
"""INSERT INTO segments (id, segment_key, name, definition, needs_to_hear, avoid, version_no, status, created_at, updated_at)
VALUES (?,?,?,?,?,?,1,'active',?,?)""",
(sid, key, name, definition, needs, avoid, _now(), _now()),
)
# ── core line ──
core = _line(conn, "core", "Core Thesis", is_core=True,
description="The shared spine of the Ten31 thesis — throughline, banner, pillars, and proof.")
root = _node(conn, core, None, "thesis_root", 0, "Ten31 — Core Thesis", "")
_node(conn, core, root, "throughline", 1, "Throughline", THROUGHLINE)
pos = _node(conn, core, root, "section", 2, "Positioning / Banner (open debate)", POSITIONING_NOTE)
_node(conn, core, pos, "claim", 1, "Option A — Scarcity-forward", OPTION_A, status="candidate", variant_group="positioning")
_node(conn, core, pos, "claim", 2, "Option B — Freedom tech as the banner", OPTION_B, status="candidate", variant_group="positioning")
pillars = _node(conn, core, root, "section", 3, "Pillars", "")
_node(conn, core, pillars, "claim", 1, "1. Scarcity is the whole opportunity", PILLAR_1)
_node(conn, core, pillars, "claim", 2, "2. Foundational infrastructure with real revenue", PILLAR_2)
_node(conn, core, pillars, "claim", 3, "3. Founders seek us out, and we lead deals others never see", PILLAR_3)
_node(conn, core, root, "proof_point", 4, "The proof", PROOF)
_node(conn, core, root, "section", 5, "Voice", VOICE)
# ── per-segment lines ──
for key, name, _definition, needs, _avoid in SEGMENTS:
lid = _line(conn, f"seg_{key}", name, segment_key=key, is_core=False,
description=f"Segment-specific angle for {name}.")
sroot = _node(conn, lid, None, "thesis_root", 0, name, "")
_node(conn, lid, sroot, "segment_cut", 1, "Angle", needs)
return {"core_line": core, "segments": len(SEGMENTS)}
def ensure_thesis_seed(conn):
"""Seed the v5 thesis once, only when the Workshop is empty (no thesis lines).
Idempotent and non-destructive: never overwrites partner edits."""
try:
n = conn.execute("SELECT COUNT(*) FROM thesis_lines WHERE deleted_at IS NULL").fetchone()[0]
except sqlite3.OperationalError:
return # thesis tables not present yet (migration 0002 not applied)
if n:
return
out = seed_v5(conn)
_log(conn, "thesis.seeded_v5", out["core_line"], {"segments": out["segments"], "source": "thesis-seed-v5"})
conn.commit()
print(f"[thesis] seeded v5 thesis (core line + {out['segments']} segment lines)")
+3 -2
View File
@@ -17,8 +17,9 @@ export const PACKAGE_TITLE = 'Ten31 Database'
// * 0.1.0:49 (Architect: Claude thesis generation + Thesis Workshop screen) // * 0.1.0:49 (Architect: Claude thesis generation + Thesis Workshop screen)
// * 0.1.0:50 (Set Anthropic API Key UI action — no terminal needed) // * 0.1.0:50 (Set Anthropic API Key UI action — no terminal needed)
// * 0.1.0:51 (entity-resolution fix: people double-count + duplicate queue) // * 0.1.0:51 (entity-resolution fix: people double-count + duplicate queue)
// * Current: 0.1.0:52 (grid/contacts unification: contact_id link + grid as front door) // * 0.1.0:52 (grid/contacts unification: contact_id link + grid as front door)
export const PACKAGE_VERSION = '0.1.0:52' // * Current: 0.1.0:53 (seed v5 thesis into the Architect Workshop)
export const PACKAGE_VERSION = '0.1.0:53'
export const DATA_MOUNT_PATH = '/data' export const DATA_MOUNT_PATH = '/data'
export const WEB_PORT = 8080 export const WEB_PORT = 8080
+3 -2
View File
@@ -13,8 +13,9 @@ import { v_0_1_0_49 } from './v0.1.0.49'
import { v_0_1_0_50 } from './v0.1.0.50' import { v_0_1_0_50 } from './v0.1.0.50'
import { v_0_1_0_51 } from './v0.1.0.51' import { v_0_1_0_51 } from './v0.1.0.51'
import { v_0_1_0_52 } from './v0.1.0.52' import { v_0_1_0_52 } from './v0.1.0.52'
import { v_0_1_0_53 } from './v0.1.0.53'
export const versionGraph = VersionGraph.of({ export const versionGraph = VersionGraph.of({
current: v_0_1_0_52, current: v_0_1_0_53,
other: [v_0_1_0_39, v_0_1_0_40, v_0_1_0_41, v_0_1_0_42, v_0_1_0_43, v_0_1_0_44, v_0_1_0_45, v_0_1_0_46, v_0_1_0_47, v_0_1_0_48, v_0_1_0_49, v_0_1_0_50, v_0_1_0_51], other: [v_0_1_0_39, v_0_1_0_40, v_0_1_0_41, v_0_1_0_42, v_0_1_0_43, v_0_1_0_44, v_0_1_0_45, v_0_1_0_46, v_0_1_0_47, v_0_1_0_48, v_0_1_0_49, v_0_1_0_50, v_0_1_0_51, v_0_1_0_52],
}) })
+21
View File
@@ -0,0 +1,21 @@
import { VersionInfo } from '@start9labs/start-sdk'
// Seed the v5 thesis into the Architect's Thesis Workshop. On upgrade, if the
// Workshop is empty, it is populated once with the core line (throughline, the
// open Option A/B banner debate as competing variants, the three pillars, the
// proof, voice rules), one line per LP segment carrying that segment's angle, and
// the segment definitions. Everything lands as draft/candidate — nothing is made
// canonical (that stays the partners' dual-approval action). Idempotent: never
// overwrites partner edits. No schema migration.
export const v_0_1_0_53 = VersionInfo.of({
version: '0.1.0:53',
releaseNotes: {
en_US: [
'Seeds the v5 thesis into the Thesis Workshop so you and your partner can start',
'iterating immediately: the core line (throughline, the Option A/B banner as',
'competing options, three pillars, the proof) plus a line per LP segment with its',
'angle. Seeds once when the Workshop is empty and never overwrites your edits.',
].join(' '),
},
migrations: { up: async () => {}, down: async () => {} },
})