Skip to content

Refactor model download into EnsureModelDownloadedTask#202

Merged
sroussey merged 5 commits into
mainfrom
claude/sec-task-idiom-refactor-55vlgt
Jul 17, 2026
Merged

Refactor model download into EnsureModelDownloadedTask#202
sroussey merged 5 commits into
mainfrom
claude/sec-task-idiom-refactor-55vlgt

Conversation

@sroussey

Copy link
Copy Markdown
Contributor

Moves the model download harness from a utility function (ensureModelDownloaded in src/config/ensureModelDownloaded.ts) into a proper Workglow task (EnsureModelDownloadedTask in src/task/model/EnsureModelDownloadedTask.ts), and inlines the oracle evaluation logic directly into EvalS1Task.

Summary

This refactor improves task graph visibility and progress reporting for model downloads by making them first-class tasks that integrate with the CLI's task-graph UI. It also consolidates the oracle evaluation implementation into the task that runs it, eliminating a separate runOracleEval function.

Key Changes

  • New task: EnsureModelDownloadedTask wraps ModelDownloadTask and derives the provider (and download config) from the model id shape alone via secModelRecord, eliminating the need to pass a resolved ModelConfig. The task:

    • Skips cloud models (no-op)
    • Skips bare-path GGUF ids without a model_url (assumed on disk)
    • Runs ModelDownloadTask for HuggingFace ONNX and remote GGUF ids
    • Forwards download progress to context.updateProgress so multi-GB fetches render live in the CLI UI
    • Is memoized per model id so a per-section sweep pays the download once
  • Inlined oracle logic: EvalS1Task.execute() now contains the full oracle evaluation implementation (previously in runOracleEval), including:

    • Section loading and filtering (golden vs. reference model)
    • Per-section model runs with latency/cost tracking
    • Scoring against reference extractions
    • Summary aggregation per model
    • Progress reporting via context.updateProgress
  • Removed files:

    • src/eval/runOracleEval.ts (logic moved to EvalS1Task)
    • src/config/ensureModelDownloaded.ts (replaced by task)
    • src/config/ensureModelDownloaded.test.ts (tests moved to task)
  • Updated imports: All callers of ensureModelDownloaded / prefetchModel now import from src/task/model/EnsureModelDownloadedTask

  • File reorganization: Fetch-related tasks moved from src/fetch/ to src/task/fetch/ for consistency with the task-based architecture

Implementation Details

  • EnsureModelDownloadedTask uses context.own() to register the download as a subtask, ensuring it inherits the running task's registry, abort signal, and progress UI
  • The oracle task now directly manages model prefetching before the timed section loop, so download time is not charged to latency measurements
  • Golden-label mode (reference = "golden") skips the reference model entirely and uses committed human-verified labels instead
  • Progress reporting mirrors to stderr when not on a TTY (background/JSON output) so long local-model runs aren't blind

https://claude.ai/code/session_01BhM8n9LKToUTHesCuYaXNT

claude and others added 5 commits July 17, 2026 01:20
The eval tasks split their logic into thin task wrappers around free
functions instead of living inside the task, as the libs task idiom
intends. EvalS1Task delegated its entire body to runOracleEval, which was
imported by nothing else and had no test of its own.

- Inline runOracleEval (the oracle comparison over real committed S-1
  sections) directly into EvalS1Task.execute, moving its helpers
  (runSection, summarize) and report types (OracleReport, OracleRunResult,
  OracleModelSummary, GOLDEN_REFERENCE) into the task file. The task now
  drives progress through its own execute context rather than an onProgress
  shim, and prefetches models through the real context. Delete the now-dead
  runOracleEval.ts; the CLI imports OracleReport from the task.

- Own the AI subtasks the extraction path instantiates. runStructured
  (StructuredGenerationTask) and ensureModelDownloaded (ModelDownloadTask)
  now wrap the instance in context.own(...), so when a task threads its real
  execute context down (form processing, the eval prefetch) the subtask is
  registered in that task's graph and inherits its registry + abort signal.
  Against the deliberate one-shot stub context (eval sweeps, unit tests),
  own is an identity no-op, so behavior there is unchanged.

The sibling run functions (runExtractionEval, runUnitTermsEval) are left as
standalone: they are unit-tested directly and export helpers consumed
elsewhere, so inlining them would break tests and duplicate shared code.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhM8n9LKToUTHesCuYaXNT
…der from the model id

The download seam was a free function handed a pre-resolved ModelConfig. Promote
it to a proper task and let it figure the provider out from the model name.

- EnsureModelDownloadedTask takes a model **id** and derives the provider (and its
  download config) from the id shape via secModelRecord — a gguf: id → node-llama-cpp,
  an org/name id → HuggingFace ONNX, a gpt-*/gemini-*/grok-* id → the matching cloud
  provider, else Anthropic — instead of requiring a resolved ModelConfig. It owns the
  ModelDownloadTask (context.own), so the download is registered in the running task's
  graph and inherits its registry + abort signal, and forwards the download's phase
  events to the CLI progress UI. Cloud ids no-op; a bare-path GGUF (no model_url) is
  skipped; the per-id memo still pays a download at most once per process.

- ensureModelDownloaded / prefetchModel become thin wrappers that own + run the task
  on the caller's context (prefetchModel best-effort, swallowing failures). Both now
  take a model id string rather than a ModelConfig.

- Update callers to pass the id they already have: the eval loops and EvalS1Task pass
  the candidate id (dropping a redundant repository resolve in the prefetch loop), the
  form processors pass their computed model_id, and runStructured's safety-net passes
  resolveModelId(model).

Tests rewritten to drive the task by id and to cover the provider-from-id dispatch,
the owned-download progress forwarding across the two task layers, memoization, and a
standalone EnsureModelDownloadedTask.run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhM8n9LKToUTHesCuYaXNT
…and name the file after the task

The download task was the one task file not named after its task and not living
under src/task/. Rename src/config/ensureModelDownloaded.ts to
src/task/model/EnsureModelDownloadedTask.ts (and its test alongside), matching the
convention every other task file already follows, and move it out of the DI-config
folder into a new model/ task domain. Update the internal secModelRecord import and
all nine importers to the new path; no behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhM8n9LKToUTHesCuYaXNT
The SEC fetch tasks (SecCachedFetchTask, SecFetchTask, SecFetchJob, SecJobQueue,
SecFetchFileOutputCache) are tasks/task infrastructure, so they belong under
src/task/ with the rest. Move the whole src/fetch directory to src/task/fetch,
rewrite the moved files' outward imports (../config|util|cli → ../../…) and every
importer's path (task-domain siblings ../../fetch → ../fetch; commands/index.ts
../fetch → ../task/fetch). Pure relocation, no behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhM8n9LKToUTHesCuYaXNT
@sroussey
sroussey merged commit ee9268b into main Jul 17, 2026
1 of 2 checks passed
@sroussey
sroussey deleted the claude/sec-task-idiom-refactor-55vlgt branch July 17, 2026 17:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants