Commit Graph
3 Commits
Author SHA1 Message Date
Omar SobhandClaude Opus 5 f4adc8d0f9 fix(podcast): stop reading identifiers aloud, and pitch the episode at a teenager
deploy / test (push) Successful in 4m11s
deploy / build (push) Successful in 5m28s
Two things the operator found by listening to a real episode.

**1. Identifiers were spoken as digit soup.** The script genuinely said
"arxiv 2608.12888", which the voice reads as "two six zero eight point one two
eight eight eight". Same for three-decimal values: "0.506" and "0.004" became
long strings of spoken digits. A listener on a treadmill cannot write an
identifier down and does not need a third decimal place.

`speakable()` strips arXiv references and bare identifier-shaped numbers, and
rounds decimals to two places — with a carve-out that matters: 0.004 rounds to
0.00, which would claim the value was ZERO when the whole point was that it
collapsed to nearly nothing, so it says "under 0.01" instead.

Deliberately narrow: it removes identifiers and shortens over-precise decimals,
and does not paraphrase, reorder or summarise. The agents' words are still the
episode. It also preserves the sentence's full stop — swallowing it turned
"…financial retrieval, arxiv 2608.00183. This one's a catch." into one run-on
sentence, and the pause is how a listener knows a thought ended.

Note that `podcast-dialogue-writing.md` ALREADY said "no arXiv ids" and the
writer included them anyway. That is this project's recurring lesson restated:
an instruction is a request, and a listener deserves a guarantee. The prose asks
and the code enforces.

**2. It was written for someone who already knew the field.** The skill and the
script phase's task now target a bright sixteen-year-old: define an acronym in
the sentence that first uses it, describe the mechanism rather than naming it
("a road map with motorways and side streets" instead of "a hierarchical
navigable small world graph"), one idea per sentence. The test offered is
whether the listener could explain the finding to a friend afterwards.

That is not dumbing down — it is the constraint that forces a writer to say what
a thing actually does rather than what it is called.

Tested against the exact lines from the episode that was listened to.
366 tests pass.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-18 10:42:48 -07:00
Omar SobhandClaude Opus 5 a02e0cba69 feat(missions): Continuous Research harvests at launch, and cards launch by clicking
deploy / test (push) Successful in 4m21s
deploy / build (push) Successful in 5m14s
The card shipped in e20b321 could not actually be used. Three things were
missing, each of which failed at a different distance from its cause.

**1. `default_team_template` was parsed and never read.** Every recipe declares
one; `WorkflowRecipe` carries the field; nothing consumed it. A mission created
from a card with no explicitly chosen team was rejected at LAUNCH with "no
team_id, no team_template_id, no config.phase_teams" — one step removed from the
real cause, which is that creation ignored the recipe. Create now resolves it
via `team_templates::get_by_key`, only when the caller named no team of any
kind, so an explicit choice still wins. A test asserts every shipped recipe
names a template that has a `templates/teams/<key>.toml`, because a mismatch
there produces an unlaunchable card.

**2. The harvest ran nowhere.** `harvest_for_mission` existed and nothing called
it. `on_launch` now runs it for `continuous_research` missions, before the
phases start, and threads the blob store through from `main` (the route already
had it on `AppState`; the scheduler needed it). Deliberately non-fatal: a
harvest that fails still starts the phases, because the phase is what reports
whether today was quiet or broken and those must stay distinguishable — but
never silent, so both outcomes log their counts.

**3. Nothing wrote the manifest.** `templates/teams/continuous_research.toml`
has pointed its reader role at `ContinuousResearch/<date>/harvest.jsonl` since it
was authored, and the file did not exist — agents aimed at a path nothing
produced. `run_to_vault` now writes it beside the notes and stages it, but only
for a mission-attributed run. `Harvest` carries the shelved `Paper`s to build
it; re-parsing the notes we had just written would have been a parse of our own
output and one more place for the two to drift.

Also: the blob root. `storage.data_dir` defaults to "./data" and the container's
cwd is `/`, so the server tried to create `/data` as uid 65532 and EVERY shelve
failed with "storage io: Permission denied". The image now creates
/var/lib/clawmates-blobs owned by 65532 so a mounted volume inherits it rather
than arriving root:root. Kept off /var/lib/clawmates-missions on purpose: that
tree is swept, and a paper shelved there would be deleted out from under its own
catalogue note.

Proven end to end on a real mission: 15 candidates, 2 already held, 13 shelved,
0 failed; branch auto-merged as additive-only; manifest on vault `main` with
every documented key. The "already held" counts are the seen-set deduping across
topics within a single run, which is the behaviour the whole design exists for.

The project brief now comes from the mission description — `phase_task_text`
already places it under BRIEF verbatim, so no new field was needed.

346 tests pass.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-17 15:05:37 -07:00
Omar SobhandClaude Opus 5 e20b321055 feat(missions): Continuous Research is a mission type, not just a team checkbox
deploy / test (push) Successful in 4m6s
deploy / build (push) Successful in 5m32s
`templates/teams/continuous_research.toml` has existed with three well-written
roles since it was authored, but no workflow recipe pointed at it — every
recipe in templates/workflows/ defaults `default_team_template = "rust_sdlc"`.
So the only way to reach it was as a checkbox under Advanced. It is now a
Step-1 card: the registry loads it at boot and `GET /api/workflows` serves it,
with no frontend change (MissionWizard renders whatever the endpoint returns).

Both phases are kind `research`, deliberately, rather than new `read`/`script`
kinds. An unrecognised kind falls through `purposes_for`'s `_ => ["mission"]`
and is absent from `PRODUCING_KINDS`, so it would get the generic directive AND
be exempt from the empty-delivery rule — a phase that produces nothing and
still passes. That is the shape this codebase keeps paying for; two `research`
phases differentiated by `task` keep both guards.

`commit_policy = "always"`, not `on_green_tests`: the vault is prose with no
suite, so a test gate would find nothing to run and land every branch `-wip`.

The harvest is NOT an agent phase. `continuous_research.rs` calls the existing
`library::run_to_vault` — arXiv search, seen-set check, PDF shelf, vault note,
attributed by `mission_id` — because that path is deterministic, takes seconds,
and owns the `corpus_items` seen-set that is the whole reason a recurring
mission knows what it already covered. An agent redoing it would be slower and
would lose that.

The manifest path is not invented either: the team template has told
`signal_harvester` to write `ContinuousResearch/<date>/harvest.jsonl` all along.
This makes the code produce what the prompt already promised, and a test pins
the path and every documented key so the two cannot drift into an agent reading
a file nothing writes.

DEFAULT_CORPUS / DEFAULT_VAULT_URL exported rather than duplicated, so the
route and the launch hook cannot disagree about which vault.

344 tests pass.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-17 14:18:35 -07:00