Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit cd0ffcddd55c037370572899081ef6f407a99249
parent d7a2445848574eb70106bbbe3465bfa94b035a71
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Sat, 26 Sep 2026 14:27:09 -0400

mcp: the ask and sweep plans send clip media through fetch_clip, never a hand-run yt-dlp

One step, shared by both builders: after the citation step in ask, before
Finish in the sweep. It passes the plan's corpus as source (the Rumble
mapping reads it), offers full: true for a whole recording, and says the
README's yt-dlp command is the operator's fallback, not the agent's.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Diffstat:
Mmcp/src/instructions.test.ts | 26++++++++++++++++++++++++++
Mmcp/src/instructions.ts | 23+++++++++++++++++++++++
2 files changed, 49 insertions(+), 0 deletions(-)

diff --git a/mcp/src/instructions.test.ts b/mcp/src/instructions.test.ts @@ -188,3 +188,29 @@ test("the sweep reads operator corrections from a sibling manifest before the fi assert.ok(at > 0 && batch > at, `corrections step at ${at}, batch step at ${batch}`); assert.match(text, /never re-asserted/); }); + +// ─── Media for a citation ─── + +test("both builders send clip media through fetch_clip, never a hand-run yt-dlp", () => { + for (const text of [sweep("coffee channels=c"), ask("coffee")]) { + assert.match(text, /`fetch_clip`/); + assert.match(text, /NEVER run yt-dlp yourself/); + // The corpus rides along, so a Rumble embed id maps to the editor's dir. + assert.match(text, /pass source: "remote:https:\/\/site\.example" so a Rumble id/); + // The whole recording is on offer, but the window is the default. + assert.match(text, /full: true when the ask genuinely needs the whole recording/); + // Only the operator falls back to yt-dlp. + assert.match(text, /the operator's fallback, not yours/); + } + // In the sweep, before Finish: a report that needs a clip asks while the + // citation is in hand, not after the summary is written. + const s = sweep("coffee channels=c"); + const clip = s.indexOf("Media for a cited moment"); + const finish = s.indexOf("**Finish.**"); + assert.ok(clip > 0 && finish > clip, `clip step at ${clip}, Finish at ${finish}`); + // In the ask, after the answer's citation rule. + const a = ask("coffee"); + const cite = a.indexOf("Answer with citations"); + const askClip = a.indexOf("Media for a cited moment"); + assert.ok(cite > 0 && askClip > cite, `citations at ${cite}, clip step at ${askClip}`); +}); diff --git a/mcp/src/instructions.ts b/mcp/src/instructions.ts @@ -140,6 +140,25 @@ function batchStep(req: PromptRequest, ctx: PlanContext, reportPath: string): st ); } +// Media for a citation goes through the editor's fetch job, never a yt-dlp the +// agent runs itself (the operator's rule: every fetch through Archilyzer). One +// step, shared by both builders so the two cannot drift. `wait_seconds` and +// `full` stay un-backticked: they are arguments, not tools. +function clipStep(ctx: PlanContext): string { + return ( + `**Media for a cited moment — through the editor only.** When I ask for ` + + `the clip behind a citation (or a report needs one), call \`fetch_clip\` ` + + `with that citation's channel slug, video id, start/end in seconds (or ` + + `mm:ss) and a one-line reason (or full: true when the ask genuinely needs ` + + `the whole recording — a window is the default), and pass ` + + `source: "${ctx.corpus}" so a Rumble id resolves to the right directory. ` + + `NEVER run yt-dlp yourself, in any form. If the tool reports no editor is ` + + `configured, say so and stop — the README's yt-dlp command is the ` + + `operator's fallback, not yours. If it returns queued, call it again with ` + + `the job it names.` + ); +} + // The full sweep instructions. export function buildSweepInstructions( req: PromptRequest, @@ -236,6 +255,8 @@ export function buildSweepInstructions( steps.push(batchStep(req, ctx, reportPath)); + steps.push(clipStep(ctx)); + steps.push( `**Finish.** Work to the end of the worklist, then write a summary ` + `section: the scope swept, **how many of the N you actually read**, ` + @@ -329,6 +350,8 @@ export function buildAskInstructions( `corpus does NOT show — an absence of matches is a finding, not a gap ` + `to paper over.`, ); + + steps.push(clipStep(ctx)); } const numbered = steps.map((s, i) => `${i + 1}. ${s}`).join("\n\n");