feat(models): download model weights before use, with on-screen progress#198
Merged
Merged
Conversation
Local model weights must be present on disk before generation, but the providers differ on when that happens: cloud models have nothing to download, HuggingFace ONNX auto-fetches on first generation, and node-llama-cpp (GGUF) loads its model_path directly and never fetches at generation. Add ensureModelDownloaded as the single seam that normalizes this — it runs ModelDownloadTask for the local providers (no-op for cloud, memoized per model id) and skips a bare-path GGUF whose file is assumed on disk. Wire it into runStructured (the chokepoint every extractor funnels through) so normal runs fetch weights before use, and prefetch in the eval loops before their timed sections so download time isn't charged to a model's measured latency. Make GGUF ids fetchable rather than pre-staged: a gguf: id may now be a remote URI (gguf:hf:org/repo:quant or an https URL), which secModelRecord turns into a model_url download source plus a local model_path/models_dir under the GGUF models dir. Plain gguf: paths stay load-directly local files, unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HoQKRtpU7mGpK8Dah9ySm
The download seam used a throwaway execute context, which silently discarded the download run-fn's progress events — a multi-GB GGUF/ONNX fetch looked like a hang. Thread the running task's real IExecuteContext into ensureModelDownloaded and on to ModelDownloadTask.execute: the download's phase events are forwarded to context.updateProgress, which the @workglow/cli progress UI (withCli) already renders, and context.signal now aborts a long download on Ctrl-C. Add prefetchModel, the best-effort wrapper the CLI-task boundaries call to fetch weights (with visible progress) before the work begins: - eval sweeps pass their task context so `sec eval` shows download progress; - the AI form processors (S-1, 424, merger-proxy, redemption, LOI) prefetch once after resolving their model, via a context threaded through the shared storageArgs in ProcessAccessionDocFormTask. runStructured keeps a context-less ensureModelDownloaded call as a per-section correctness safety-net (it downloads silently only if a model was never prefetched, e.g. a sub-extractor's distinct model); the progress-bearing fetch lives at the task boundary. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HoQKRtpU7mGpK8Dah9ySm
Thread the running task's IExecuteContext down the extraction stack (processFormS1 / runOfferingSections / the 424, merger-proxy, redemption and LOI processors → extract* → runGuardedExtraction → runStructured), so each section's AI generation reports progress on the CLI task row instead of running silently. The context alone is not enough: StructuredGenerationTask.execute() keeps only the finish event and drops the phase events, so a threaded context would still see nothing. runStructured now drives the task's executeStream itself — identical result (same retry/validation loop, same finish) — and forwards the Preparing/Generating phases to context.updateProgress. Each section's row now shows the model actively working; the download progress already added shows alongside it. context is optional throughout: the eval sweeps and unit tests call the extract* functions with the two-arg (no-context) signature and fall back to the self-contained stub, so behavior there is unchanged. All side effects are preserved, so the form-pipeline tests that pin processFormS1/ProcessAccessionDocFormTask stay green. Adds a regression test asserting a threaded context receives the phase progress. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HoQKRtpU7mGpK8Dah9ySm
Contributor
There was a problem hiding this comment.
Pull request overview
This PR introduces a unified “download before use” seam for local AI model providers and threads the running task IExecuteContext through form/eval pipelines so that both model download progress and per-section generation phases render on the CLI task row.
Changes:
- Added
ensureModelDownloaded/prefetchModelto normalize local-provider weight downloads (no-op for cloud providers) and forward download progress viaIExecuteContext. - Extended
gguf:model id parsing to support remote URIs (HFhf:andhttps://) by producingmodel_url+ local cachemodel_path. - Threaded
contextthrough eval and form extraction code paths and updated structured generation to forwardPreparing/Generatingphase progress.
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/task/forms/ProcessAccessionDocFormTask.ts | Threads the running task context into form storage args so downstream AI processors can surface download/progress. |
| src/task/eval/EvalUnitTermsTask.ts | Passes context into eval runner to enable progress rendering and prefetch behavior. |
| src/task/eval/EvalS1Task.ts | Passes context into eval runner to enable progress rendering and prefetch behavior. |
| src/task/eval/EvalExtractTask.ts | Passes context into eval runner to enable progress rendering and prefetch behavior. |
| src/sec/forms/registration-statements/s1/sectionExtractors.ts | Ensures models are downloaded before structured generation and forwards generation phase progress via executeStream. |
| src/sec/forms/registration-statements/s1/sectionExtractors.test.ts | Adds regression test asserting threaded context receives Preparing/Generating phase updates. |
| src/sec/forms/registration-statements/s1/offeringSections.ts | Threads optional context to per-section extraction calls for CLI progress. |
| src/sec/forms/registration-statements/Form_S_1.storage.ts | Adds best-effort prefetchModel and threads context through extractor calls. |
| src/sec/forms/registration-statements/Form_424.storage.ts | Adds best-effort prefetchModel and threads context through offering section runner. |
| src/sec/forms/proxies-information-statements/Form_DEFM14A.storage.ts | Adds best-effort prefetchModel and threads context into extraction. |
| src/sec/forms/miscellaneous-filings/redemption8k.ts | Adds best-effort prefetchModel and threads context into extraction. |
| src/sec/forms/miscellaneous-filings/loi8k.ts | Adds best-effort prefetchModel and threads context into extraction. |
| src/sec/forms/miscellaneous-filings/Form_8_K.storage.ts | Threads context into 8-K sub-processors. |
| src/eval/runUnitTermsEval.ts | Adds context option and prefetches model weights before timed evaluation runs. |
| src/eval/runOracleEval.ts | Adds context option and prefetches participating models up front when context is present. |
| src/eval/runExtractionEval.ts | Adds context option and prefetches model weights before timed fixture loops. |
| src/config/registerModels.ts | Allows gguf: ids to be remote URIs by generating model_url + cache path config. |
| src/config/registerModels.test.ts | Adds tests for GGUF id parsing (local path vs HF URI vs HTTPS URL). |
| src/config/ensureModelDownloaded.ts | Introduces centralized model download seam plus best-effort prefetch wrapper. |
| src/config/ensureModelDownloaded.test.ts | Adds unit coverage for no-op cloud behavior, GGUF skip, download attempts, memoization, and progress forwarding. |
| CLAUDE.md | Documents the new download-before-use harness and updated GGUF usage guidance. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+63
to
+65
| const modelId = (model as { model_id?: string }).model_id ?? ""; | ||
| if (modelId && ensured.has(modelId)) return; | ||
|
|
Comment on lines
+316
to
+329
| function ggufCacheFileName(uri: string): string { | ||
| let rest = uri.replace(/^https?:\/\//i, "").replace(/^hf:/i, ""); | ||
| // A trailing `:quant` (HF quant selector) is a filename hint, not a path segment. | ||
| let quant: string | undefined; | ||
| const quantMatch = rest.match(/:([^/:]+)$/); | ||
| if (quantMatch) { | ||
| quant = quantMatch[1]; | ||
| rest = rest.slice(0, rest.length - quant.length - 1); | ||
| } | ||
| const last = rest.split(/[/?#]/).filter(Boolean).pop() ?? "model"; | ||
| const base = last.toLowerCase().endsWith(".gguf") ? last.slice(0, -".gguf".length) : last; | ||
| const name = [base, quant].filter(Boolean).join("-") || "model"; | ||
| return `${name}.gguf`; | ||
| } |
Comment on lines
391
to
395
| } else if (event.type === "finish") { | ||
| result = (event as { data?: { object?: unknown } }).data; | ||
| } | ||
| } | ||
| return (result?.object as Record<string, unknown> | undefined) ?? {}; |
Use the task run() lifecycle instead of hand-driving execute()/executeStream
for the three model tasks:
- runStructured runs StructuredGenerationTask.run() and forwards its
Preparing/Generating phases to the caller's context.updateProgress via
the runConfig.updateProgress hook (caching disabled to match execute()'s
never-cache semantics). run() also fails loudly — a generation that can't
finish throws rather than returning {}, so the prior executeStream
finish-guard concern no longer applies.
- ensureModelDownloaded runs ModelDownloadTask.run(); unloadLocalModel runs
ModelDownloadRemoveTask.run() (dropping its throwaway stub context).
Address the PR review:
- Memoize ensureModelDownloaded on a robust key (model_id ?? model ??
provider_config.model_url ?? model_path), mirroring resolveModelId, so a
record without model_id still downloads once instead of every call.
- isRemoteGgufUri matches hf: case-insensitively.
- ggufCacheFileName folds the whole source path into the cache filename so
distinct remote GGUF sources (same repo name / different org, or two
repos each holding model.gguf) don't collide on disk.
Adds tests for the model-identified-by-`model` memo path, uppercase HF:,
and same-repo-name/different-org cache-target uniqueness.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011HoQKRtpU7mGpK8Dah9ySm
5 tasks
sroussey
added a commit
that referenced
this pull request
Jul 17, 2026
### Features - expose library surface for superset CLIs (#203) - add accredited-investor portal attribution for Form D filings (#197) - add SPAC consolidated report with lifecycle tracking (#164) - add Form 8-K parsing and event storage infrastructure (#68) - add CFPORTAL form parsing and Reg A query support (#133) - track company facts fetch/store outcomes with retry support (#132) - add offering terms, underwriters, and use-of-proceeds extraction (#128) - extend ReadOnlyTabularStorage with new getOffsetPage method and update getBulk signature - enhance ReadOnlyTabularStorage with pagination and query capabilities - enhance bootstrap tasks and error handling #### cli - run query and db commands as task graphs via the workflow renderer - honor --json flag in status and error output (#193) - sponsor-family alias management + alias-aware 'spac by-family' query - extend sec version coverage to resolver kind - add sec canonical {person|company} alias commands - add sec resolve --kind --version --all #### models - download model weights before use, with on-screen progress (#198) #### eval - default the S-1 oracle reference to opus - golden-truth oracle + reconcile table/bio roles - score field-values by F1 so over-production is penalized - surface per-row/field disagreements after a run - add OpenAI, Gemini, and xAI providers to the model harness - oracle comparison over real S-1 sections - model comparison harness + local model wiring #### spac - AI SPAC classifier, sponsor promote, de-SPAC linkage (#149, #150, #151) - investorpres slot, portal featured, family descriptions - target_description from merger proxies onto the deal + row - SPAC business profile + leadership bio/birth_year extraction #### xbrl - ISO date transforms + CIK/dimension query coverage (#154) (#195) #### s1 - skip nonce echo for local providers - treat a board chair as implying director (drop redundant "Director") - model person titles as a list of distinct roles (titles[]) - canonicalize management titles post-model - extract SPAC sponsors into legal-sponsor + family tiers with links - add extractSpacSponsors structured extractor - write deterministic SPAC classification from header SIC - add shared SGML-header + primary-document submission parser - add real S-1 fixture sampler and document the extraction flow - add dead-letter retry sweep, extractor CLI, and promote eligibility announce - add S-1 prospectus extraction pipeline and dispatch - add observation-provenance, beneficial-ownership, related-party, and dead-letter storage tiers #### config - node-llama-cpp GGUF local provider + workglow mega imports - register SEC AI models in the global model repository #### extractors - register AI providers + retry model-error dead-letters without a version bump #### sec - SPAC de-SPAC lifecycle — 8-K milestones, merger-proxy & redemption extraction (#166) #### forms - route F-1 / F-1/A / F-1MEF (foreign issuer) through the S-1 extractor - dispatch DRS/DRS/A to S-1 extractor; fetch registration forms as .txt - split fetch/parse failure domains in ProcessAccessionDocFormTask - ingest Section 16 (3/4/5) and Form 144 ownership filings (#116) #### canonical - add SponsorFamilyMembership + SpacSponsorLink tables with DI - add CanonicalSponsorFamily + alias tables with DI - emit current_canonical_* view DDL - add alias repos with single-hop invariant - add four canonical-level address/phone junction repos - add CompanyIdentityLinkRepo - add PersonIdentityLinkRepo - add CanonicalCompanyRepo - add CanonicalPersonRepo - add alias schemas - add canonical-level address/phone junction schemas - add identity-link schemas - add CanonicalCompanySchema - add CanonicalPersonSchema #### resolver - add SponsorFamilyResolver + register sponsor-family resolver kind - add EntityObserver helper for form storage modules - add CompanyResolver v1 with CIK/CRD/name rules + alias pass - add PersonResolver v1 with CIK/name rules + alias pass - add resolverIds module #### storage - add S1ClassificationRepo (filing-level SPAC record) + DI #### html - add form-agnostic EDGAR HTML to Document-tree converter #### versioning - add dropPrevious tests for extractor and resolver kinds - extend drop-previous to resolver rows + orphan cleanup - add ceremony CLI (start-dev, promote, rollback, drop-next, coverage, history); remove seed-test - add VersionCoverage and VersionHistory queries - add ceremonies module (startDev / promote / rollback / dropNext) - add getActiveSlot helper (next-if-exists routing) - add VersionEventRepo for ceremony audit log - add VersionEvent schema and DI wiring - add semver helpers (parse, major.minor, bump-progression validation) - add target_count column to component_versions - add ExtractorRunRepo helper - bootstrap extractor versions on db setup - add idempotent bootstrapExtractorVersions seeder - add FORM_TO_EXTRACTOR_ID mapping and ExtractorId type - add 'sec version status' and 'sec version seed-test' CLI - add VersionStatus query for CLI rendering - add VersionRegistry helper with slot accessors - register ComponentVersion and ExtractorRun repos in DI - add ExtractorRun schema and DI token - add ComponentVersion schema and DI token #### observation - add CompanyObservationRepo - add PersonObservationRepo with natural-key upsert - add CompanyObservationSchema - add PersonObservationSchema ### Bug Fixes - address xhigh code-review findings - update warning messages and improve test descriptions - DRSLTR dispatch, SPAC sponsor span verification, resolver test + RFC-9112 Content-Length (#127) - address code review issues — dropPrevious orphan check, DbStatus new tables, drop-previous CLI verb, resolve test coverage, CompanyResolver secondary keys - init #### sec - CLI validation, portal-attribute reporting, gguf path check (#201) - make local GGUF models usable in eval and exit cleanly - isolate redemption model-resolution + bulk-skip backfill candidates - record extractor_runs for redemption + idempotent, fast backfill #### query - reject empty CIK filters #### cli - report expected user-errors through task output ports; dedupe tier wiring - preserve JSON workflow error handling - make streamed query totalApprox a meaningful lower bound (#112) #### editorial - isolate multi-file import failures #### s1 - one owner per name; quote key lists in eval diffs - drop ownership subtotal rows; add beneficial-ownership golden truth - close the GBNF []-shortcut on nested titles[] too - harden sponsor extraction + sponsor-family storage from code review #### eval - fail loudly when an extractor has no fixtures - normalize commas and initial/suffix periods in name alignment - restore stderr progress for eval tasks when piped #### spac - make de-SPAC linkage a write-once close-time snapshot - stop deriving phantom deals after a completed combination - gate SQLite withSpacCikLock through process-wide mutex (avoid concurrent BEGIN IMMEDIATE crash) - allow caller-supplied pool client to recomputeSpacDeals to avoid deadlock with outer lock - atomic per-CIK rollup writes + monotonic history chain - record extractor_runs for merger-proxy + transactional recompute #### storage - implement no-op updateWhere on ReadOnlyTabularStorage #### resolver - stabilize person name parts across punctuation/glyph variants - instance-scope per-key mutex so multi-process race tests test what they claim (#160) - FamilyResolver — alias inside mutex + UPPER case-normalization (#129) - add readonly to PersonClaim/CompanyClaim; single timestamp per method #### s1,eval - director nominees + typographic name alignment #### eval,s1 - render titles arrays clearly in diffs; sharpen split prompt #### sec/424 - record deterministic SPAC IPO event even when AI model fails to resolve #### facts - derive fy sentinel from end_date to preserve PK width across period-agnostic facts - accept EDGAR facts with null fy/fp #### sec/extractors - move verify nonce out of untrusted fence into trusted preamble - add NONCE_MISMATCH dead-letter reason code - gate reapStaleObservations on version change to prevent LLM-variance data loss on same-version re-runs #### sec/html - strip title/svg/math to close prompt-injection bypass in body-level and foreign-content elements #### config - declare Anthropic model capabilities so StructuredGenerationTask resolves - init SQLite binding in setupAllDatabases before view DDL #### extractors - degrade gracefully when the AI model is unregistered #### submissions - store every filing row (proxy slice yielded undefined) #### review - clarify details doc + single-pass investorpres derivation - address code-review findings + prettier - close wave-2 (#177/#178/#179) review findings - close 6 findings from max-effort review of the consolidation #### canonical - serialize junction observation_count with KeyedMutex - align junction columns with address/phone schemas #### util - parseDate rejects calendar-invalid dates #### xbrl - XbrlFactRepo.replaceForAccession no-ops on 0 rows unless intentionalClear #### forms - close stripDoctype bypass via leading XML comment / PI - restore predefined-entity decoding via bounded processEntities + DOCTYPE strip - apply prompt-injection seal to merger-proxy + redemption extractors - treat empty fetch body as no-text and swallow dead-letter write failures - explicit null address_id/international_number in Form_1_Z signature observation - remove dead resolveCountryCode from Form_1_K.storage #### forms/s1 - close residual Unicode-invisible defang bypass - widen defang TAG_SHAPED to admit whitespace mid-tag (closes bypass) - wire new extractMergerDeal / extractRedemption to nonce-fence API - per-call nonce fence + raw-span cap + multi-stage defang #### forms/8-K - auto-resolve redemption-partial-oversized dead-letter (informational only) - cap redemption AI input bytes; drop oversized exhibits - persist redemption extraction even when SPAC has no deal yet - trust the actual repo nature, not the lingering SEC_DB_TYPE token - tx writes, versioned PK, accession unification, XML entity hardening #### versioning - partial-success outcome on extractor_runs - bump test timeout on multi-spawn CLI tests to 15s - address PR #109 review feedback - address final review items - reject all start-dev variants when next slot exists; drop dead check; comment promote atomicity - patch-gate listFilingsWithoutSuccessfulRun on major.minor prefix - address independent code review - address PR #107 review feedback - use cik: number in ExtractorRunRepo query API (matches codebase convention) - address PR #106 review feedback - enforce semver and coverage_complete invariants in putSlot - drop redundant PK-prefix indexes; wire resetAllDatabases; sort imports #### resolver,forms/s1 - PG unique-violation recognition + family-tier UNIQUE convergence + S-1/424 prompt-injection hardening (3× HIGH) (#163) #### forms,storage - Form 144/Ownership whitespace→null, PG dedup, Form_C/1-A stale-replay guards (#159) - 5 HIGH review findings — stale replay, point-in-time, undated guard, deal sort, Schedule A (#155) - Crowdfunding history on stale replay + CFPORTAL/A inheritance + docs (#135) - address Copilot review on PR #124 #### storage/canonical - enforce UNIQUE constraints to close resolver race (#158) #### forms,portal - undated 1-K/1-Z guard + deterministic stale-replay tie-break (2 HIGH from code review) (#156) #### forms,storage,cli - person-collision issuer guard + alias chain block + CLI input validation + coverage perf (#122) #### resolver,storage,cli - resolver race + observation_id PK + CSV NBSP + download leak (sec) (#121) #### section16 - preserve null vs 0 for empty numeric leaves on Forms 3/4/5 (#116 follow-up) (#117) #### observation - align raw_phone_id maxLength with PhoneSchema #### ci - set 30s global test timeout for multi-spawn CLI tests ### Refactors - remove unused fn #### task - move src/fetch to src/task/fetch - relocate EnsureModelDownloadedTask to src/task/model and name the file after the task #### config - make ensureModelDownloaded a task that infers provider from the model id - import node-llama-cpp via the workglow mega package - drop the spac narrative-column migration #### eval - inline oracle sweep into EvalS1Task; own AI subtasks - rename `sec eval s1 --candidates` to `--models` #### cli - graph-ify every command through the workflow renderer #### normalize - shared typographic-punctuation fold for names #### portal - drop the featured column #### canonical - extract shared CanonicalJunctionRepo base #### resolver - extract shared normalizeSponsorFamilyName for consistent family keys #### queue - integrate wrapQueueStorage for SecJobQueue components #### storage - delete legacy PersonRepo, CompanyRepo, and their schemas #### forms - rewrite Form_1_Z.storage onto EntityObserver - rewrite Form_1_K.storage onto EntityObserver - rewrite Form_1_A.storage onto EntityObserver - rewrite Form_C.storage onto EntityObserver - rewrite Form_D.storage onto EntityObserver - rewrite Form_D.storage onto EntityObserver #### versioning - route form-processing tasks via getActiveSlot (next-if-exists) - UpdateAllFormsTask reads extractor_runs, drops --force - write extractor_runs from ProcessAccessionDocFormTask - derive TypeBox literal unions from const arrays ### Tests - use vitest so we can try node when needed - add unit tests for SecFetchJob functionality #### 424 - isolate the priced-prospectus fixture from S-1 discovery globs - pin priced-prospectus pipeline with a real 424B4 golden fixture #### versioning - update registered-component assertions for sponsor-family resolver kind #### s1 - add synthetic DRS .txt fixture exercising header parse + DRS dispatch - end-to-end SPAC classification + sponsor family linkage + DRS dispatch #### forms - assert FETCH_ERROR status pending; clarify Domain 3 throw comment - cover fetch-layer dead-letter paths (PRIMARY_DOC_UNRESOLVED, FETCH_ERROR, parse rethrow) #### fixtures - add fetch-fixtures script for pulling real EDGAR data (#108) ### Documentation - update CLAUDE.md and new-module JSDoc for PR4 - update paths for design specs and plans in various skills and documentation #### eval - drop LFM2.5 references; default the sweeps to cloud models - document evaluating Bonsai 27B via the local GGUF path (#187) #### 8-K - note metadata items/report_date are authoritative #### s1 - document sponsor-text extraction strategy in processFormS1 ### Chores - rename bunsrc scripts for clarity - bump workglow/cli 0.3.26, compromise 14.16.0, fast-xml-parser 5.10.0 - keep typebox 1.3.6 from main after rebase - update deps - keep extractor versions at 1.0.0 (no data to re-extract) - update deps - update deps - ignore .worktrees directory - update deps - update deps - update dependencies and ESLint configuration - update dependencies and enhance CIK query functionality #### config - drop local HuggingFace/ONNX provider wiring - register observation/canonical repos in DI; add view DDL #### extractors - default AI model to claude-sonnet-5 via shared SecModelDefault #### deps - update @workglow/cli and related packages to version 0.3.13; add new domNodes.ts file for cheerio DOM node types #### versioning - rename bootstrapExtractorVersions; seed resolver components - register resolver:person and resolver:company - retire processed_filings; DbStatus reports extractor_runs ### CI - limit rebuilds ### Updated Dependencies - `@modelcontextprotocol/sdk`: ^1.29.0 - `@workglow/cli`: 0.3.26 - `commander`: ^15.0.0 - `compromise`: ^14.16.0 - `fast-xml-parser`: ^5.10.0 - `pdf2json`: ^4.0.3 - `pg`: ^8.22.0 - `typebox`: 1.3.6 - `workglow`: 0.3.26 - `@types/bun`: 1.3.14 - `bunset`: 1.0.13
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Commands that use a model must have its weights on disk before generation, but providers differ on when that happens: cloud models have nothing to download; HuggingFace ONNX auto-fetches on first generation; but node-llama-cpp (GGUF) loads its
model_pathdirectly and never fetches at generation. This PR adds a single seam that normalizes the download across providers, makes the download's progress render on screen, and surfaces per-section generation progress on the CLI task row.What changed
ensureModelDownloaded(src/config/ensureModelDownloaded.ts) — the single seam.ModelDownloadTaskfor the local providers (HF_TRANSFORMERS_ONNX,LOCAL_LLAMACPP); no-op for cloud (they register nomodel.downloadrun-fn, so dispatching would throw).model_pathonly, nomodel_url) — the file is assumed on disk.IExecuteContext: the download run-fn'sphaseevents are forwarded tocontext.updateProgress(viaDirectExecutionStrategy'sjob.onJobProgressbridge), which the@workglow/cliprogress UI (withCli) renders — so a multi-GB GGUF/ONNX fetch shows a live percentage.context.signalalso aborts it on Ctrl-C.Downloadable GGUF ids. A
gguf:id may now be a remote URI — a node-llama-cpp HuggingFace URI (gguf:hf:org/repo:Q4_K_M) or anhttps://URL — whichsecModelRecordturns into amodel_url(download source) plus a localmodel_path/models_dirunder the GGUF models dir. A plaingguf:path stays a load-directly local file, unchanged.Download prefetch wiring (
prefetchModel). Acontextthreaded through the eval loops and the sharedstorageArgsinProcessAccessionDocFormTaskreaches the AI processors (processFormS1,processForm424,processMergerProxy,processRedemption8K,processLoi8K), each prefetching once after resolving its model — sosec evalandsec fetch formshow download progress, and download time isn't charged to a model's measured latency.Per-section generation progress. The same
contextis threaded on down torunStructuredso each section's AI generation reports progress on the task row instead of running silently. The context alone isn't enough —StructuredGenerationTask.execute()keeps only thefinishevent and drops thephaseevents — sorunStructurednow drives the task'sexecuteStreamitself (identical result: same retry/validation loop, same finish) and forwards thePreparing/Generatingphases tocontext.updateProgress.contextis optional throughout: eval sweeps and unit tests use the two-arg (no-context) signature and fall back to a self-contained stub, so their behavior is unchanged and all side-effects are preserved.Testing
src/config/ensureModelDownloaded.test.ts): cloud no-op, bare-path GGUF skip, ONNX/remote-GGUF attempt, memoization, and a progress-forwarding test asserting a fakemodel.downloadrun-fn'sphaseprogress reachescontext.updateProgress.registerModels.test.ts(local path vshf:URI vshttps://URL).sectionExtractors.test.ts) asserting a threaded context receivesPreparing/Generatingduring extraction (guards againstexecute()silently dropping phases again).npx tsc --noEmitclean againstworkglow@0.3.26; config / eval / form-pipeline suites pass (the ~20 tests pinningprocessFormS1/ProcessAccessionDocFormTaskside-effects stay green).Scope note
Progress is surfaced by threading the real context (the download shows a live %; each section's row shows
Preparing/Generating). This deliberately stops short of a full graph-ification into one distinct CLI row per section — investigation showed nested sub-graphs render nothing and distinct rows would require restructuring the monolithicProcessAccessionDocFormTask(a much larger change against the ~20-test-pinned pipeline). That remains a possible follow-up.🤖 Generated with Claude Code
https://claude.ai/code/session_011HoQKRtpU7mGpK8Dah9ySm