Part of #72, follows #168. The router's policy is still one function (`router.score`); a restless bandit needs rewards, and nothing recorded what a pick turned into. This card is the data, not the policy. - `router.py`: the shape of an outcome and the arithmetic over the log. `picks()` reads every live pick out of Astra's beats (a card picked twice keeps its first pick; a dry run picked nothing), `outcome()` settles one pick into a line and returns None while its card is open, `read_outcomes()`/`summary()`/`outcome_lines()` are the read side. - `housekeeping.py` step 4: one line per settled pick appended to `time/ledger/router-outcomes.jsonl` in one commit, dry-run writes nothing. A pick already in the file is never written twice. - `astra.py`: one summary line per kind (picks, merged share, median days, median $) over the ranked table, so the owner sees what a kind returns before changing its weight. Two notes on what the card asked for: - Spend proxy: the card offered the card's worker beats' `usd` or the runs' time window. Beats carry no `usd` today, so this uses the runs' time window — each `dispatched` beat until the next beat on the card — which attributes a run's spend to the card that ran it instead of charging it the whole factory's spend over the days it was open. Review and planning calls on the card are not counted; two cards whose runs overlap are each charged their own window. Documented in `time/ledger/README.md`. - The line carries `pr` as well as the shape the card listed: the card asks for the PR number and the line is the only place to keep it. - The file lives under `time/ledger/`, as the card says, since that is the one path `ci.yml` ignores and the sweep appends to it daily. No workflow change is needed: `housekeeping.yml` already passes `GITHUB_TOKEN` (the push) and `TIME_VENTURE_MEASURE_DAYS` (day 7). A venture pick waits until its venture's day 7 has passed and then carries the signups counted at that day; with no registry entry for the card the line goes in with no count rather than waiting forever. Tests: `test_housekeeping.py` covers a merged pick, an unmerged pick, a pick still in flight (no line), idempotence, the venture's day-7 count and dry-run; `test_time.py` section 172 covers the pure functions, the summary's arithmetic and the log's header. All three suites green. Closes #172 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0198aoK6ddbLN4d6q8r3J7vg
time
A Gitea-native agent factory, running on Teahub prod (teahub.xyz). The Gitea repo is the ledger, Gitea Actions are the clock (Time), the worker is a Claude or Venice seat with hands or a Cursor cloud agent, and chat is where you plan.
- Cards: an issue with a
DONE:line, labelledready. - Heartbeat (
heartbeat.yml, hourly and whenever a card is labelledready): moves cards forward. Indry-run(default) it only says what it would do; withTIME_MODE=liveit dispatches each card to the worker (TIME_WORKER: a seat such asclaude:sonnetorvenice:kimi, which edits the repo and opens the PR itself, or a Cursor cloud agent), which opens a PR that closes the card. An optional reviewer (TIME_REVIEW) checks the PR. At mostTIME_DAILY_CAPlaunches a day. Spend governor: every model call is booked totime/ledger/<YYYY-MM>.jsonl(tokens and dollars); once today's spend reachesTIME_DAILY_USD(default 5) or the month's reachesTIME_MONTHLY_USD(default 100), Time stops calling models, opens anandonissue, and a monthly breach also setsTIME_MODE=dry-run. No model decides whether to spend. - Mentions (
mention.yml):@sonnet,@opus,@kimi,@grok 4.7,@qwen max highin any issue, PR or comment gets that seat's reply. - Hands-off (
TIME_AUTOMERGE=on): label an issuespecand Time splits it into cards that run one at a time. A card's PR merges itself once the reviewer approves and checks are green, then the next card starts. Objections, red checks and protected paths (workflows, deploy, secrets) go to a human. - Venture loop (
venture.yml, every 20 min onceTIME_VENTURE=on): one venture card a day, or one right away from Actions → venture → Run workflow → "Start a new venture now". It scouts the repos that gained the most stars this week; seats pitch; the board votes (Borda, quorum of two); every board seat writes a launch plan (positioning, first users, pricing, channels, 7-day success and kill lines) and one seat synthesizes them intoLAUNCH.md; the worker builds the product as the page ongh-pages(the tool fills the first screen under a slim header with the one signup button; pricing, limits and the why sit below); deterministic checks and headless Chrome (phone and desktop) gate it, with two fix rounds before an andon; GitHub Pages ships it at/ventures/<slug>/. Signups go through an issue form (.github/ISSUE_TEMPLATE/early-access.yml), each one thanked on its issue; after 7 days the count of distinct people decides: build v2, kill, or watch a week more. The studio index lives at/ventures/. Launch posts are drafts for a human; nothing is posted outside the repo. - Batches (
TIME_VENTURE_BATCH=3, or Run workflow → "batch"): one scout pass, a field of pitches, and the board's top k become venture cards in one milestone, built side by side and launched together. A held child gets one batch retry before it's counted dead; a retro compares the cohort once every child is measured. - Seed (
seed.yml, manual): land a venture that was built somewhere else. Give it a slug, the URL of a .tgz whose top folder is that slug (or of its parts, in order) and the .tgz's sha256, plus a name and a one-liner for a new venture.seed.pychecks the bundle, copies it toventures/<slug>/ongh-pagesin one commit, adds or refreshes its line inventures/ventures.json, and asks Teahub pages to pull, so it is live at<slug>.yolopreneur.lolwithin the minute. Nothing in the bundle runs. - Refresh (
refresh.yml, manual): for a venture that lives on gh-pages and makes some of its own files (fonts, art, screenshots, generated code). It runsventures/<slug>/tools/build.shon the runner, in place and without credentials, then commits what changed to gh-pages in one commit and asks Teahub pages to pull.fromtakes the folder from another branch first, so a reworked venture lands whole. - Backends (optional,
CLOUDFLARE_API_TOKEN+CLOUDFLARE_ACCOUNT_ID): each venture also gets its own Cloudflare Worker, D1 database and Workers AI, serving its Pages folder at<slug>.<TIME_VENTURE_DOMAIN>. The page talks to it throughtime.js(share links that save a creation, a rate-limited small model, and accounts with passkeys: no passwords, no email); every call degrades gracefully, so products still work on Pages alone. Code:.github/time/cloudflare.pyandcloudflare/worker.js. - Doctor (
doctor.yml, manual): runs the tests and checks label, settings, keys and today's spend. - Update (
update.yml, weekly or manual): opens a PR with the latest engine from teahubxyz/time, so every project stays on one Time instead of drifting copies.
Setup
Started on GitHub as a proof of concept (teahubxyz/time, now read-only); ported to Gitea 2026-10-14.
Open this repo in Claude Code or Cursor and say "set up Time": the agent follows .claude/skills/time/SKILL.md. The recommended path needs one secret, ANTHROPIC_API_KEY: Claude seats plan, build and review (TIME_WORKER=claude:sonnet, TIME_REVIEW=claude:haiku; haiku, sonnet, opus, fable are the seats, priced in .github/time/seats.json). With VENICE_API_KEY instead, Venice seats do the same (TIME_WORKER=venice:qwen coder, TIME_REVIEW=venice:grok). A Cursor cloud agent can be the worker instead if you have a Cursor API key. The landing page walks through it: https://teahubxyz.github.io/time/
Status
For changes and release history, see CHANGELOG.md.