linkedin-studio/docs/research-engine/brief-re-r3f.md
Kjell Tore Guttormsen 63745ddcab docs(linkedin-studio): RE-R3f brief + plan — unattended AI discovery (headless, slice e)
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
2026-08-10 20:57:20 +02:00

175 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 24 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 SC1SC5;
`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.