Venture loop: ventures, not tools

Pitches carry a name, who it's for, a next version and a CTA. The board may
rank nothing and needs two seats behind a winner. Every venture gets a
`signal` issue (👍 = I want the next version) and a Want-more section on the
page pointing at it. After ship: a launch stage drafts posts for the chair
(never sent), and a measure stage 24h later queues an iterate card or parks
the venture. Seats that reply in prose get re-asked once.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpT7eWzeLhRwuYmsSQnbRb
This commit is contained in:
claude committed 2026-10-11 14:37:23 -07:00
1 parent a4970e3e2b
commit 5a185facd3
2 files changed
+98 -23

No files matched your search

+11
View File
@@ -278,6 +278,17 @@ votes = {"grok": '{"ranking": ["other", "other", "star-checker"], "reason": "r"}
vt.tm.think = lambda seat, model, effort, system, user: votes[seat] vt.tm.think = lambda seat, model, effort, system, user: votes[seat]
w, meta = vt.board(ps, ["grok", "kimi", "qwen"]) w, meta = vt.board(ps, ["grok", "kimi", "qwen"])
assert w == "other" and meta["score"] == {"star-checker": 5, "other": 6}, meta assert w == "other" and meta["score"] == {"star-checker": 5, "other": 6}, meta
# no quorum: only one seat wants the winner -> None
votes = {"grok": '{"ranking": ["other"], "reason": "r"}', "kimi": '{"ranking": [], "reason": "none worth it"}', "qwen": '{"ranking": [], "reason": "pass"}'}
vt.tm.think = lambda seat, model, effort, system, user: votes[seat]
assert vt.board(ps, ["grok", "kimi", "qwen"])[0] is None
# a seat that answers in prose first gets re-asked
calls = {"n": 0}
def flaky(seat, model, effort, system, user):
calls["n"] += 1
return "let me think..." if calls["n"] == 1 else answers["qwen"]
vt.tm.think = flaky
assert [p["slug"] for p in vt.pitch(repos, ["qwen"])] == ["star-checker"] and calls["n"] == 2
print("ALL OK 6") print("ALL OK 6")
vt.tm.think = lambda *a: 'Sure! Here you go:\n```json\n{"slug": "x", "title": "T", "one_liner": "o", "why_now": "w", "page": "p {with braces}"}\n```' vt.tm.think = lambda *a: 'Sure! Here you go:\n```json\n{"slug": "x", "title": "T", "one_liner": "o", "why_now": "w", "page": "p {with braces}"}\n```'
assert vt.ask_json("q", "s", "u")["page"] == "p {with braces}" assert vt.ask_json("q", "s", "u")["page"] == "p {with braces}"
+87 -23
View File
@@ -54,17 +54,23 @@ most stars on GitHub this week. They tell you what builders care about right now
{radar} {radar}
Pitch ONE small product the studio could ship as a single static web page today: a tool, calculator, explainer, Pitch ONE venture the studio could launch today as a single static web page: a tool, calculator, explainer, directory,
directory, checker, or playground that rides the same wave. It must be useful on its own, need no backend, and be checker or playground that rides the same wave. It must work on its own with no backend, be honest (no fake data,
honest (no fake data, no fake testimonials). Reply with ONLY JSON: no fake testimonials), and be a *venture*, not just a tool: it has a name, a specific person it is for, a reason to
{{"slug": "kebab-case", "title": "...", "one_liner": "...", "why_now": "which repo(s) and trend this rides", "page": "what the page does, concretely, in 3-5 sentences"}}""" come back, and a next version worth paying for. Reply with ONLY JSON:
{{"slug": "kebab-case", "name": "a brand name, one or two words", "title": "...", "one_liner": "...",
"for_whom": "the specific person this is for, in one sentence", "why_now": "which repo(s) and trend this rides",
"page": "what the free page does, concretely, in 3-5 sentences",
"next": "what the paid or bigger version would be, in one sentence",
"cta": "the one thing a visitor should do to say they want more (vote, star, join a list, request a feature)"}}"""
VOTE = """You are `{seat}`, voting on the board of an agent-run venture studio. Pitches: VOTE = """You are `{seat}`, voting on the board of an agent-run venture studio. Pitches:
{pitches} {pitches}
Rank your top three by: would a real person use this page this week, can one agent build it as a static page in an Rank up to three by: would the named person use this page this week, can one agent build it as a static page in an
hour, is it honest. Reply with ONLY JSON: {{"ranking": ["slug1", "slug2", "slug3"], "reason": "one sentence"}}""" hour, is there a believable next version someone would pay for, is it honest. If none is worth a day of the studio,
rank nothing. Reply with ONLY JSON: {{"ranking": ["slug1", "slug2"], "reason": "one sentence"}} (ranking may be [])."""
def ask_json(seat: str, system: str, user: str) -> dict | None: def ask_json(seat: str, system: str, user: str) -> dict | None:
@@ -92,7 +98,11 @@ def ask_json(seat: str, system: str, user: str) -> dict | None:
def pitch(repos: list[dict], seats: list[str]) -> list[dict]: def pitch(repos: list[dict], seats: list[str]) -> list[dict]:
out = [] out = []
for s in seats: for s in seats:
p = ask_json(s, f"You are `{s}`.", PITCH.format(seat=s, radar=radar(repos))) p = None
for _ in range(2): # a seat that answers in prose gets one more go
p = ask_json(s, f"You are `{s}`. Reply with JSON only.", PITCH.format(seat=s, radar=radar(repos)))
if p and p.get("slug") and p.get("title"):
break
if p and p.get("slug") and p.get("title"): if p and p.get("slug") and p.get("title"):
p["slug"] = re.sub(r"[^a-z0-9-]", "-", p["slug"].lower())[:40].strip("-") p["slug"] = re.sub(r"[^a-z0-9-]", "-", p["slug"].lower())[:40].strip("-")
p["by"] = s p["by"] = s
@@ -115,24 +125,46 @@ def board(pitches: list[dict], seats: list[str]) -> tuple[str | None, dict]:
for pts, slug in zip((3, 2, 1), seen): for pts, slug in zip((3, 2, 1), seen):
score[slug] += pts score[slug] += pts
if not any(score.values()): if not any(score.values()):
return None, votes return None, {"votes": votes, "score": score}
return max(score, key=lambda k: (score[k], -[p["slug"] for p in pitches].index(k))), {"votes": votes, "score": score} winner = max(score, key=lambda k: (score[k], -[p["slug"] for p in pitches].index(k)))
if sum(1 for v in votes.values() if winner in v.get("ranking", [])) < 2:
return None, {"votes": votes, "score": score} # no quorum: at least two seats must want it
return winner, {"votes": votes, "score": score}
# ---------- build and ship ---------- # ---------- build and ship ----------
BUILD_BODY = """Build the venture **{title}** as one static page. BUILD_BODY = """Build the venture **{name}** ({title}) as one static page.
{page}
For: {for_whom}
What the free page does: {page}
Why now: {why_now} Why now: {why_now}
Next version (not built today, but the page should point at it): {next}
Write `ventures/{slug}/index.html`: a complete, self-contained page (inline CSS and JS, no build step, no external Write `ventures/{slug}/index.html`: a complete, self-contained page (inline CSS and JS, no build step, no external
calls except optional public CDNs). Make it genuinely usable, mobile-friendly, and honest: no invented numbers, calls except optional public CDNs). Genuinely usable, mobile-friendly, honest: no invented numbers, reviews or logos.
reviews or logos. Add a one-line footer "built by the Time venture loop" linking to the repo. Also add a row for it to Brand it as **{name}** (title, header, page <title>). The tool is the hero; keep the page focused on using it.
`ventures/index.html` (create that index if missing: a plain list of ventures with title, one-liner and link).
DONE: ventures/{slug}/index.html renders and ventures/index.html links to it""" End the page with a short "Want more?" section, exactly this shape:
- one sentence on what the next version would do ({next});
- a primary button "{cta_label}" linking to {cta_url} (opens in a new tab);
- the line: "Built by an agent-run venture studio. If this is useful, say so: that is how it gets a next version."
No email forms (there is no backend). Also add or update `ventures/index.html`: a plain list of ventures with name,
one-liner, who it is for, and link.
DONE: ventures/{slug}/index.html renders with the Want-more section and ventures/index.html links to it"""
def signal_issue(w: dict) -> dict:
"""One issue per venture where interest is counted: 👍 reactions and comments are the signal."""
title = f"{w['name']}: want the next version?"
for i in tm.gh("GET", f"/repos/{REPO}/issues?state=open&labels=signal&per_page=50"):
if i["title"] == title:
return i
body = (f"**{w['name']}**: {w['one_liner']}\n\nFor: {w['for_whom']}\n\nFree page: {w.get('url', '(shipping)')}\n\n"
f"Next version: {w['next']}\n\n👍 this issue if you would use the next version. Comment with what it must do. "
f"That is the whole signup: the studio reads the count and decides whether to build it.")
return tm.gh("POST", f"/repos/{REPO}/issues", {"title": title, "body": body, "labels": ["signal"]})
def ensure_pages() -> str: def ensure_pages() -> str:
@@ -177,6 +209,11 @@ def tick() -> int:
return 0 return 0
n, st = card["number"], state_of(card) n, st = card["number"], state_of(card)
stage = st["beat"] stage = st["beat"]
if stage == "launched":
age_h = (dt.datetime.now(dt.timezone.utc) - dt.datetime.fromisoformat(st["at"].replace("Z", "+00:00"))).total_seconds() / 3600
if age_h < float(env("TIME_VENTURE_MEASURE_HOURS", "24")):
print(f"venture #{n}: launched {age_h:.1f}h ago, measuring at 24h")
return 0
print(f"venture #{n} stage={stage}") print(f"venture #{n} stage={stage}")
say = lambda text, meta: tm.comment(n, text, {"venture": True, **meta}) say = lambda text, meta: tm.comment(n, text, {"venture": True, **meta})
try: try:
@@ -197,19 +234,46 @@ def tick() -> int:
reasons = "\n".join(f"- `{s}`: {v.get('ranking')} ({v.get('reason', '')})" for s, v in meta["votes"].items()) reasons = "\n".join(f"- `{s}`: {v.get('ranking')} ({v.get('reason', '')})" for s, v in meta["votes"].items())
say(f"🗳️ Board picks **{w['title']}** (`{winner}`) with Borda {meta['score']}.\n\n{reasons}", {"beat": "picked", "winner": w}) say(f"🗳️ Board picks **{w['title']}** (`{winner}`) with Borda {meta['score']}.\n\n{reasons}", {"beat": "picked", "winner": w})
elif stage == "picked": elif stage == "picked":
w = st["winner"] w = dict(st["winner"])
w.setdefault("name", w["title"]); w.setdefault("for_whom", "builders"); w.setdefault("next", "a fuller version")
w.setdefault("cta", "vote for the next version")
site = ensure_pages() site = ensure_pages()
w["url"] = f"{site.rstrip('/')}/ventures/{w['slug']}/"
sig = signal_issue(w)
w["signal"] = sig["number"]
spec = env("TIME_WORKER", "venice:qwen coder") spec = env("TIME_WORKER", "venice:qwen coder")
if not spec.startswith("venice:"): if not spec.startswith("venice:"):
spec = "venice:qwen coder" spec = "venice:qwen coder"
fake = {"number": n, "title": w["title"], "body": BUILD_BODY.format(**w)} fake = {"number": n, "title": w["title"], "body": BUILD_BODY.format(**w, cta_label=f"👍 I want this", cta_url=sig["html_url"])}
# build straight onto gh-pages: the worker's branch is the Pages branch for ventures
r = _build_on_pages(fake, spec) r = _build_on_pages(fake, spec)
url = f"{site.rstrip('/')}/ventures/{w['slug']}/" say(f"🔨 `{r['seat']}` built **{w['name']}** in {r['steps']} steps ({r['model']}). Shipping to {w['url']} · signal: #{sig['number']}",
say(f"🔨 `{r['seat']}` built it in {r['steps']} steps ({r['model']}). Shipping to {url}", {"beat": "built", "winner": w, "url": url, **r}) {"beat": "built", "winner": w, "url": w["url"], **r})
elif stage == "built": elif stage == "built":
url = st["url"] w, url = st["winner"], st["url"]
say(f"🚀 Shipped: {url}\n\nPages can take a minute to publish. This card is done; tomorrow's loop starts a new one.", {"beat": "done", "url": url}) draft = tm.think(env("TIME_VENTURE_LAUNCH", "grok"), None, None,
"You write launch posts for a tiny agent-run venture studio. Plain, specific, no hype, no emoji. "
"Never invent numbers or users.",
f"Draft two posts for **{w['name']}** ({w['one_liner']}; for {w['for_whom']}; page {url}; "
f"signal issue https://github.com/{REPO}/issues/{w['signal']}):\n"
f"1. an X post under 280 characters\n2. a Show HN title and first comment (3-5 sentences)\n"
f"Why now: {w['why_now']}")
say(f"🚀 Shipped: {url}\n\nSignal: 👍 on #{w['signal']} means someone wants the next version.\n\n"
f"📣 Launch drafts (nothing is sent; the chair posts what they like):\n\n{draft}",
{"beat": "launched", "winner": w, "url": url})
elif stage == "launched":
w = st["winner"]
sig = tm.gh("GET", f"/repos/{REPO}/issues/{w['signal']}")
ups, comments = sig.get("reactions", {}).get("+1", 0), sig.get("comments", 0)
threshold = int(env("TIME_VENTURE_SIGNAL", "3"))
if ups + comments >= threshold:
tm.gh("POST", f"/repos/{REPO}/issues", {"title": f"Iterate {w['name']}: build the next version",
"body": f"{ups} 👍 and {comments} comments on #{w['signal']}. Next version: {w['next']}\n\nDONE: PR open, tests green",
"labels": ["queued"]})
say(f"📈 Measure: {ups} 👍, {comments} comments on #{w['signal']} (threshold {threshold}). Queued an iterate card.",
{"beat": "done", "url": st["url"], "signal": ups + comments})
else:
say(f"📉 Measure: {ups} 👍, {comments} comments on #{w['signal']} (threshold {threshold}). Parked; the signal issue "
f"stays open, and the loop re-checks it weekly.", {"beat": "done", "url": st["url"], "signal": ups + comments})
tm.gh("PATCH", f"/repos/{REPO}/issues/{n}", {"state": "closed"}) tm.gh("PATCH", f"/repos/{REPO}/issues/{n}", {"state": "closed"})
except Exception as e: # noqa: BLE001 except Exception as e: # noqa: BLE001
errs = int(st.get("errors", 0)) + 1 errs = int(st.get("errors", 0)) + 1