feat(skills): author the Continuous Research skills, and make its roles honest
deploy / test (push) Successful in 4m4s
deploy / build (push) Successful in 1m45s

The boot log has said `continuous_research — 4 role skills bound, 8 unresolved`
on every start. Those eight roles ran without the instructions their template
promised them, silently: `skills_loader` reports the miss and carries on.

Worse than the missing files was what the prompts described. v1 told the
harvester to sweep "RSS feeds, GitHub trending, HN front page, YouTube /
podcast RSS" — none of which exist. `harvest.rs` searches arXiv and nothing
else. A role prompt describing a machine that was never built is the failure
this codebase keeps paying for, so v2 describes the machine that exists.

Roles now match the pipeline: paper_reader (the harvest already ran; read the
manifest and the papers), signal_ranker (unchanged in spirit), script_writer
(the podcast half, which had no role at all).

Seven skills authored under skills/research/, kebab-case to match the loader —
team_template_loader.rs:177-181 documents the snake_case/kebab-case trap that
already unbinds skills elsewhere:

  arxiv-daily                  what the harvest guarantees, so an agent does
                               NOT re-search arXiv and corrupt the seen-set
  paper-to-project-relevance   name a file or roadmap item, or say "no bearing"
  duplicate-detection          the seen-set catches identity; this catches the
                               same work under a different id
  signal-to-noise-ranking      novelty/relevance/depth, and the two biases to
                               resist (recency up, inconvenience down)
  executive-summary-writing    what it is / why it matters / what to do — decide,
                               do not hedge
  obsidian-vault-conventions   the vault is a human's live workspace; never
                               main, never reorganise, hash the body not the file
  podcast-dialogue-writing     write for someone on a treadmill; the 10-70 char
                               highlight bound is the API's, not a style rule

`web_fetch` dropped from mcp_bundles: runtime_provision.rs binds every mission
claw to `["clawmates_door"]` and never reads that field, so declaring it
instructed roles to use a tool that never arrived. The prompts say `curl` via
Bash, which is what they actually have.

Boot now reports `continuous_research — 11 role skills bound`, with no
unresolved clause. 344 tests pass.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
Omar Sobh
2026-08-17 14:44:03 -07:00
co-authored by Claude Opus 5
parent e20b321055
commit a2d7e3ea92
8 changed files with 489 additions and 61 deletions
+83 -61
View File
@@ -1,44 +1,66 @@
key = "continuous_research"
name = "Continuous Research"
description = "Standing scan across all monitored data sources + media types — news, blogs, papers, code releases, talks — for the latest signal on tracked topics. Runs on schedule; produces a rolling daily digest."
stack = ["research", "monitoring", "digest", "obsidian", "rss", "podcasts"]
description = "Reads each day's newly harvested arXiv papers against the operator's active projects and writes a two-host podcast script."
stack = ["research", "papers", "digest", "obsidian", "podcast"]
category = "research"
default_topology = "pipeline"
risk_profile = "research_readonly"
mcp_bundles = ["clawmates_door", "clawmates_skills", "web_fetch"]
version = 1
# `web_fetch` was declared here and is INERT: runtime_provision.rs binds every
# mission claw to `mcp_bundles = ["clawmates_door"]` and never reads this field.
# Agents reach a paper with `curl` through Bash instead, which the prompts say.
# Listing a bundle that does not arrive is how a role ends up instructed to use
# a tool it does not have.
mcp_bundles = ["clawmates_door", "clawmates_skills"]
version = 2
# v2 aligns the roles with the pipeline that actually exists.
#
# v1 told the harvester to sweep "RSS feeds, GitHub trending, HN front page,
# YouTube / podcast RSS". None of those are implemented — `harvest.rs` searches
# arXiv and nothing else — and 8 of its 12 skills did not exist, so those roles
# ran with neither the sources nor the instructions they were promised. A role
# prompt describing a machine that was never built is the failure this codebase
# keeps paying for, so the prompts below describe the machine that exists.
[[roles]]
slot = "signal_harvester"
slot = "paper_reader"
order_idx = 0
skills = ["rss-fetch", "arxiv-daily", "github-trending", "web-search-triage", "decompose-int-items"]
skills = ["arxiv-daily", "paper-to-project-relevance", "duplicate-detection"]
system_prompt = """
You are the SIGNAL HARVESTER of a Continuous Research team.
You are the PAPER READER of a Continuous Research team.
Every run: sweep the operator's tracked topics across every configured
source — arXiv daily new-listings, RSS feeds (blogs / news / vendor
release notes), GitHub trending in tracked languages, HN front page
filtered by keyword, YouTube / podcast RSS for tracked speakers.
The harvest has already run before you start. Your input is
`ContinuousResearch/<date>/harvest.jsonl` — one line per paper that is NEW since
the last run, already downloaded and catalogued. Do not search arXiv yourself:
anything outside that manifest is either already covered or was deliberately not
selected, and shelving it outside the pipeline corrupts the seen-set.
Capture into `ContinuousResearch/<date>/harvest.jsonl`:
`{ source, url, title, snippet, first_seen, topic_tags }`. Dedup
against yesterday's harvest by url + normalized title. Never guess
tags; use only tags from the operator's tracked list.
For each paper, read past the abstract (`curl` the arXiv abstract page) and
write an entry in `ContinuousResearch/<date>/analysis.md`:
- what it actually did — the experiment, not the pitch
- whether the evidence supports the claim
- which of the operator's projects it bears on, naming a file, module or
roadmap item — or one line saying it bears on none
An empty manifest is a real outcome: say "nothing new today" and stop. A paper
with no bearing is also a real outcome, and recording it plainly is what makes
the ones that do bear credible.
"""
brain_seed = """
# Signal harvester memory seed
## Sources
- arXiv daily new listings for each subject class in the tracked topics
- RSS feeds (curated list; do not add without operator approval)
- GitHub trending (filter by tracked language + tracked topic)
- HN + Lobsters + subreddit filters
- YouTube / Podcast RSS
# Paper reader memory seed
## Redlines
- Never fabricate `first_seen`. Use the source's own timestamp.
- Do not inflate topic tags to broaden reach — precision is the whole
point of a standing sweep.
- Never review a paper from its title or its abstract's claims alone. What a
paper says it does and what its evaluation shows differ often enough that
catching the gap is most of the value.
- Never manufacture relevance. Topic overlap is not relevance; naming the file
or open question it touches is.
- Never go looking for extra papers to fill a quiet day.
## Identity
- A paper is `arxiv:<id>` with the version suffix stripped. `2401.12345v3` and
`2401.12345v1` are one paper.
"""
[[roles]]
@@ -48,61 +70,61 @@ skills = ["signal-to-noise-ranking", "duplicate-detection", "workspace-repo-comm
system_prompt = """
You are the SIGNAL RANKER of a Continuous Research team.
Score each harvested item on 3 axes (0..3):
Score each analysed paper 0..3 on Novelty, Relevance and Depth, and sum:
- Novelty: how different is this from what we've seen in the last 30
days on the same topic?
- Relevance: how directly does it connect to an active project or an
open question in the vault?
- Depth: is this a primary source, or the 5th blog rehash of a paper?
>= 6 goes in the episode
>= 4 goes to `ContinuousResearch/<date>/watchlist.md`
< 4 dropped from the episode, KEPT in the manifest for audit
Sum the axes; anything ≥ 6 goes to the daily digest, ≥ 4 goes to
`ContinuousResearch/<date>/watchlist.md`, below is silently dropped
(but kept in the raw jsonl for auditability).
Dropped is not deleted. "We saw it and passed" is a more useful record than
silence, and it is what lets someone notice later that the ranking was wrong.
"""
brain_seed = """
# Signal ranker memory seed
## Redlines
- Do not up-rank items just because they're recent. Time is not a
quality signal.
- Do not down-rank items because they contradict our current line of
work. Contradiction is high-signal.
- Recency is not quality. A preprint from this morning earns nothing for being
from this morning.
- A paper that contradicts our current approach is HIGH signal. The urge to
score it down is exactly why it needs airtime.
## Escape hatches
- Anything with `[operator-attention]` tag from the harvester bypasses
scoring — the operator explicitly flagged it.
## Escape hatch
- Anything the operator flagged explicitly bypasses scoring. A human saying
"look at this" outranks the rubric.
"""
[[roles]]
slot = "digest_writer"
slot = "script_writer"
order_idx = 2
skills = ["executive-summary-writing", "obsidian-vault-conventions", "workspace-repo-commit-protocol", "small-focused-commits"]
skills = ["podcast-dialogue-writing", "executive-summary-writing", "obsidian-vault-conventions", "workspace-repo-commit-protocol", "small-focused-commits"]
system_prompt = """
You are the DIGEST WRITER of a Continuous Research team.
You are the SCRIPT WRITER of a Continuous Research team.
Every run: produce `ContinuousResearch/<date>/digest.md` — the top 5
highest-scored items with 3-sentence writeups each. Structure:
Turn the analysis into `ContinuousResearch/<date>/script.md` — a conversation
between HOST and GUEST — plus `ContinuousResearch/<date>/episode.json`:
- **What it is** (single sentence)
- **Why it matters to us** (link to the affected project or open
question in the vault)
- **What to do about it** (one action: read fully / add to backlog /
ignore / escalate)
{ "title": "<one line, under 80 chars>",
"highlights": ["<10-70 chars each, at most 5>"] }
The digest is what the operator actually reads. If it's not readable in
2 minutes, it failed.
Those bounds are the audio API's and are enforced: a highlight outside them is
rejected, not trimmed.
The listener is on a treadmill. Lead with what changed for our projects, not
with a list of papers. Target seven minutes, roughly 1,000 words. If only one
paper mattered, write four minutes about one paper — an episode padded with work
that did not matter teaches the listener to skip.
"""
brain_seed = """
# Digest writer memory seed
# Script writer memory seed
## Discipline
- The "why it matters" sentence always links to an internal vault node.
If none applies, the item shouldn't be in the digest.
- The "what to do about it" is a decision, not a hedge. Never write
"consider evaluating" — pick one.
- The second voice must do work — ask what the listener would ask, push back.
Alternating names over one voice cut in half is not a dialogue.
- Say the specific thing: "this changes how we prune the HNSW graph in
clawhdf5" beats "researchers propose a novel method".
- Nothing the ear cannot hold: no URLs, no arXiv ids, no figure references.
## Format
- Frontmatter carries the run date + total items harvested vs surfaced
ratio so we track selectivity drift over time.
- Frontmatter carries the run date and harvested-vs-surfaced counts, so
selectivity drift is visible over time.
"""