# 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 in `docs/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 > the `trend-spotter` agent'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 via `CLAUDE.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`-mode `claude` invocation cannot be > replicated by a shell script (it needs a full agent turn, incl. the `trend-spotter` subagent), and > Claude Code's own headless docs state a hand-rolled `cron` + `claude -p` wrapper loses > observability/session-resumption/error-recovery relative to the first-party mechanism. The recipe > for the fallback (hand-rolled `claude -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.md` C1): 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 the `brief` call below."* Nothing has inserted it. - **The attended pass blocks on operator input in two places.** `commands/trends.md` Step 1 asks the user for pillars when `${DATA}/profile/user-profile.md` is missing; Step 5 (Triage) calls `AskUserQuestion` up to 4× per batch. Fired unattended, either would hang or (in Claude Code's `dontAsk` permission mode, the correct mode for an unattended run) abort the run — `AskUserQuestion` is auto-denied unconditionally in that mode regardless of any `--allowedTools` entry (verified against Claude Code's `permission-modes` docs, 2026-08-10). - **No documented recipe for firing a plugin command unattended existed in this repo.** RE-R3c's `schedule` CLI verb only ever targets the deterministic `brief` subcommand (`run-daily.sh:33`, `cli.ts` `schedule` branch) — it has no path to a full `claude` agent turn. - **Exit-code trust was unstated.** Claude Code's headless docs are explicit that a `-p` run'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 --headless` together → 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-run` marker) run exactly as attended — `--headless` changes who can be asked, never what gets persisted. - States plainly: ground success in the same artifacts Step 3 already verifies (`CLI status --json` capture delta, today's dated brief file) — never in this command's own exit code or prose. - No frontmatter change (`allowed-tools` already lists `AskUserQuestion` for the attended path; leaving it is harmless belt-and-suspenders — `dontAsk` mode 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 verified `claude -p` recipe (`--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; the `ANTHROPIC_API_KEY` requirement 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); the `total_cost_usd` cost visibility note from `--output-format json`. ### `CLAUDE.md` (EDIT) — command table - `/linkedin:trends` row gains a `--headless` mention (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 to `claude` itself; Desktop Scheduled Tasks is the built mechanism. - **`schedule.ts` / `run-daily.sh` changes** — 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_usd` visibility (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 — `--headless` changes 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) - **`--headless` never 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: `pending` candidates are exactly what an attended triage would leave un-triaged if the operator picked "Leave" on all of them). - **No silent degrade.** `--demand --headless` refuses 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 -p` recipe names no user-specific path (the plugin-dir is a placeholder the operator fills at their own install). - **Counts unchanged** — no new command (`/linkedin:trends` gains 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.md` documents `--headless` in 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 `--headless` skip condition inline (not only in Step 0.5) — each verified by direct read of the edited sections. - **SC3** — `scripts/trends/README.md` documents the Desktop Scheduled Tasks recommendation, the fallback `claude -p` recipe with all four required flags (`--bare`, `--plugin-dir`, `--permission-mode dontAsk`, `--allowedTools`), the `ANTHROPIC_API_KEY` requirement, and the artifact-grounded success check — each present as literal text, verified by grep. - **SC4** — `CLAUDE.md`'s `/linkedin:trends` row 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-side` constraint, 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 — `--headless` default 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 --headless` explicitly. 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 -p` recipe 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.