Tracked slice docs for MR-F5: the poll->score->capture->brief loop made safe to fire with no operator present. Records the 2026-06-24 re-evaluation gate against v1.0.0 maturity and the operator's explicit decision to open it (2026-08-10), plus the Desktop Scheduled Tasks vs. hand-rolled cron design decision. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y2qHoS4FrkabuD1bCg8Nr2
12 KiB
Brief — RE-R3f: unattended AI discovery (--headless, slice e)
Slice: RE-R3f (research-engine rung-2, R3 slice (e) in the operator's
(a)→(c)→(b)→(d)→(e)sequence — the doc is numbered r3f by creation order, the concept is slice (e)). Closes the production gap named indocs/research-engine-concepts.local.md§5: RE-R3c/RE-R3e built a deterministic headless entry (schedule/run-daily.sh) that re-renders the brief on a schedule with no new discovery — a near-no-op without a prior human-run poll. RE-R3f makes the actual discovery pass —/linkedin:trends's poll → score → capture → brief loop, including thetrend-spotteragent's web research — safe to fire with no operator present. Origin: Maskinrommet production feedback, register row MR-F5 — Sunday-morning discovery needs to run without the operator present. Elicited viaCLAUDE.local.md's feedback-intake protocol; validated against live plugin structure before any code/prose was touched. The gate this slice opens:docs/research-engine-concepts.local.md§5 recorded a 2026-06-24 operator decision that slice (e) sits behind an explicit re-evaluation gate against product maturity (v1.0.0) — research is "rung-2." The plugin is at v0.8.0 (CLAUDE.md header); the gate had not been re-evaluated. Surfaced to the operator 2026-08-10 as a real conflict, not silently built around or silently deferred; the operator opened the gate explicitly (AskUserQuestion, "Open the gate now") after seeing the trade-off stated plainly. Architectural decision — CONFIRMED (operator, AskUserQuestion 2026-08-10): Desktop Scheduled Tasks (Claude Code's own first-party local scheduler) is the trigger mechanism, not a hand-rolled cron/launchd wrapper mirroring RE-R3c. A-p-modeclaudeinvocation cannot be replicated by a shell script (it needs a full agent turn, incl. thetrend-spottersubagent), and Claude Code's own headless docs state a hand-rolledcron+claude -pwrapper loses observability/session-resumption/error-recovery relative to the first-party mechanism. The recipe for the fallback (hand-rolledclaude -p) path is documented for portability, but not built as repo code.
1. The gap — grounded in code and docs
run-daily.sh(RE-R3c) is deterministic-only by design (run-daily.sh:1-8,docs/ research-engine/brief-re-r3c.mdC1): it re-renders the brief from the current store — no poll, no capture. Its own comment names the gap: "The (e) slice will insert a pre-brief AI capture step here … before thebriefcall below." Nothing has inserted it.- The attended pass blocks on operator input in two places.
commands/trends.mdStep 1 asks the user for pillars when${DATA}/profile/user-profile.mdis missing; Step 5 (Triage) callsAskUserQuestionup to 4× per batch. Fired unattended, either would hang or (in Claude Code'sdontAskpermission mode, the correct mode for an unattended run) abort the run —AskUserQuestionis auto-denied unconditionally in that mode regardless of any--allowedToolsentry (verified against Claude Code'spermission-modesdocs, 2026-08-10). - No documented recipe for firing a plugin command unattended existed in this repo. RE-R3c's
scheduleCLI verb only ever targets the deterministicbriefsubcommand (run-daily.sh:33,cli.tsschedulebranch) — it has no path to a fullclaudeagent turn. - Exit-code trust was unstated. Claude Code's headless docs are explicit that a
-prun's process exit code reflects the CLI process, not the pass's semantic outcome (a tool denial or auth failure can still print to stdout without a non-zero exit). No existing doc in this repo told a scheduled caller what to check instead.
2. Scope — what is IN (RE-R3f)
commands/trends.md (EDIT) — the --headless contract
- New flag
--headless, documented in the Step 0 flags table + a new Step 0.5 section stating the contract explicitly (so it reads as a rule, not something buried three steps down):--demand --headlesstogether → refused (one line, exit; never silently falls back to supply-side discovery).- Step 1 missing profile → print a one-line error and stop; never ask. The source-list fallback (shipped defaults) is unaffected — it already needs no operator input.
- Step 5 (Triage) → skipped unconditionally. Every kept candidate stays
pending; the next attended session (or the next morning brief) surfaces it for a human to resolve. - Steps 2–4 and 6 (poll, score, capture, render,
.last-runmarker) run exactly as attended —--headlesschanges who can be asked, never what gets persisted. - States plainly: ground success in the same artifacts Step 3 already verifies (
CLI status --jsoncapture delta, today's dated brief file) — never in this command's own exit code or prose.
- No frontmatter change (
allowed-toolsalready listsAskUserQuestionfor the attended path; leaving it is harmless belt-and-suspenders —dontAskmode denies it unconditionally regardless of what a command's frontmatter requests).
scripts/trends/README.md (EDIT) — the unattended-discovery section
- A new
## Unattended AI discovery — --headless (RE-R3f, slice e)section, positioned after the existing RE-R3c section, explicit that this is a different mechanism (a full agent turn, not a bash/cron wrapper): Desktop Scheduled Tasks as the recommended trigger; the verifiedclaude -precipe (--bare --plugin-dir … --permission-mode dontAsk --allowedTools "Read,Bash,Task,WebSearch, WebFetch" --output-format json) as the documented fallback for a machine without Desktop Scheduled Tasks; theANTHROPIC_API_KEYrequirement under--bare; the background-subagent wait ceiling (CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS, default 10 min) since the research subagent can run long; the artifact-grounded success check (never the exit code); thetotal_cost_usdcost visibility note from--output-format json.
CLAUDE.md (EDIT) — command table
/linkedin:trendsrow gains a--headlessmention (one clause, no restructuring).
CHANGELOG.md (EDIT)
[Unreleased]entry: MR-F5 landed, gate opened, scope summary.
Docs (this file + plan-re-r3f.md, NEW, tracked)
3. Non-goals — what is OUT (deferred)
- A hand-rolled cron/launchd wrapper invoking
claude -p— OUT as repo code. The recipe is documented (README) for portability to a machine without Desktop Scheduled Tasks, but nothing in this repo shells out toclaudeitself; Desktop Scheduled Tasks is the built mechanism. schedule.ts/run-daily.shchanges — OUT. RE-R3c's deterministic brief-only wrapper is untouched and remains valid for a plain "re-render today's brief" use case distinct from discovery.- A cost cap / per-run candidate ceiling enforced in code — OUT.
trend-spotter's existing guidance (Anti-Patterns: "max 2-3 trend posts per week") is advisory prose, not a mechanism; enforcing a hard cap on an unattended AI research run is a real design question, deferred rather than guessed at under this slice.total_cost_usdvisibility (README) is the mitigant landed now. --demand --headless— explicitly refused, not built. The demand-sweep's judgment calls are a later slice's problem, if ever.- A new store field / schema bump — none.
SCHEMA_VERSION(4) /BRIEF_SCHEMA_VERSION(2) untouched —--headlesschanges command-level control flow only. - A new agent, a new CLI verb, a new source/test file — none. RE-R3f is a prompt-contract EDIT plus documentation; there is no new TypeScript/bash to unit-test.
4. Boundaries / invariants (must hold)
--headlessnever changes what gets persisted, only who can be asked. A headless run and an attended run that both clear Step 1 must capture/brief identically (Step 5's skip is the only behavioral divergence, and it is additive-safe:pendingcandidates are exactly what an attended triage would leave un-triaged if the operator picked "Leave" on all of them).- No silent degrade.
--demand --headlessrefuses rather than silently running supply-side discovery instead; a missing profile refuses rather than silently scoring against no pillars. - Success is artifact-grounded, never prose-grounded — documented explicitly in both the command file and the README, because Claude Code's own docs establish the exit-code trap is real.
- Domain-general — no vendor/sector token in any edit (Section 17 de-niche stays green); the
README's
claude -precipe names no user-specific path (the plugin-dir is a placeholder the operator fills at their own install). - Counts unchanged — no new command (
/linkedin:trendsgains a flag, not a sibling command), no new agent, no new reference file. Recounted live at land.
5. Success criteria (verifiable — no test suite; this is a prompt/doc change)
- SC1 —
commands/trends.mddocuments--headlessin the flags table AND a dedicated Step 0.5 contract section;grep -c '\-\-headless' commands/trends.md≥ 5 (table + Step 0.5 + the three step-level cross-references). - SC2 — Step 1's missing-profile branch explicitly forks on
--headless(fail vs. ask); Step 2D explicitly refuses--demand --headless; Step 5's header states the--headlessskip condition inline (not only in Step 0.5) — each verified by direct read of the edited sections. - SC3 —
scripts/trends/README.mddocuments the Desktop Scheduled Tasks recommendation, the fallbackclaude -precipe with all four required flags (--bare,--plugin-dir,--permission-mode dontAsk,--allowedTools), theANTHROPIC_API_KEYrequirement, and the artifact-grounded success check — each present as literal text, verified by grep. - SC4 —
CLAUDE.md's/linkedin:trendsrow mentions--headless;CHANGELOG.md[Unreleased]names MR-F5. - SC5 — Section 17 de-niche guard stays green (no vendor/sector token introduced); command/ agent/reference counts unchanged (recounted live).
- SC6 (explicitly NOT claimed) — an actual unattended end-to-end run. Per the
plugin-runtime-is-consumer-sideconstraint, a plugin command does not resolve in this dev-repo session; the real Sunday-discovery proof happens consumer-side, after the operator updates + reloads + configures a Desktop Scheduled Task. This brief does not claim that proof; it claims the command-file contract and the documentation are correct and internally consistent.
6. Verification
This session (dev-repo): direct read-through of every edited section against SC1–SC5;
bash scripts/test-runner.sh for regression sanity (RE-R3f touches no source/test file the gate
tracks, so FAIL=0 and every floor unchanged is the expectation, not a new floor); Section 17
de-niche green; counts recounted live.
Consumer-side (deferred, not gated on this session): update + reload the plugin, run
/linkedin:trends --headless once attended-but-silent (verify it does not prompt), then configure
a Desktop Scheduled Task and confirm a real unattended Sunday pass produces a capture delta + a
dated brief.
7. Open questions for the go-gate
Two decisions are CONFIRMED (operator, AskUserQuestion 2026-08-10): the gate is opened now (production need outweighs waiting for v1.0.0); Desktop Scheduled Tasks is the mechanism, not a hand-rolled wrapper (with the fallback recipe documented, not built). Residual decisions, baked to the recommended default — confirm or redirect:
- D1 —
--headlessdefault mode is long-form (inherits the command's own default)? YES (rec). Unattended discovery is for the Sunday chronicle/newsletter pipeline per MR-F5's own framing; an operator wanting kortform passes--mode kortform --headlessexplicitly. Re-open only if the unattended default should differ from the attended default. - D2 — no hard cost/candidate cap enforced in code this slice? YES (rec, per §3 non-goal) — cost
visibility (
total_cost_usd) is landed; a cap is a real question deferred rather than guessed. Re-open only if a cap should land now. - D3 — the fallback
claude -precipe is documented but not wrapped in a repo script? YES (rec). Building an unused wrapper for a path the operator isn't using (Desktop Scheduled Tasks covers the real machine) would be speculative code. Re-open only if the fallback path is the one actually needed.