- CI warm: ccache dep, continue-on-error, 60min timeout, YAMS_DISABLE_FAISS - Faiss: YAMS_HAS_FAISS guard, Apple Clang disable, OpenMP detection - Metadata: retry constraint failures, deduplicate batch UPSERT entries - Docker: wget for Zig, gfortran, YAMS_DISABLE_FAISS=1 - CI config: free-disk-space v1.3.1, libomp-18-dev, experimental trigger - Weekly benchmarks: faiss disable, ccache, diagnostics capture - Simeon: bump to 7763a97, EntropyRouter, concept_count - CorpusStats: extraction readiness fields - Remove committed artifacts (.wrangler, .artifacts, dispatcher_external) - Fix .vscode/settings.json hardcoded path Signed-off-by: trvon <git@trevon.dev>
8 KiB
Daemon Download Jobs
Goal
Expose download tracking and resume to daemon clients without turning the daemon into a general purpose network fetch proxy.
The intended users are agents and automation flows that archive external artifacts into YAMS CAS and need:
- durable job identity
- progress/status visibility
- resumable downloads across daemon restarts
- optional post-indexing into the existing content pipeline
The design explicitly does not start with arbitrary remote download capability.
Current State
Implemented Now
- The downloader subsystem already supports:
- probe + range GET
- ETag-based resume
- staging file reopen and partial range persistence
- CAS finalization
- optional export
- The current resume store is local-manager scoped and keyed by URL in src/downloader/download_manager.cpp.
- The daemon exposes a guarded download path in
src/daemon/components/dispatcher/request_dispatcher_documents.cpp
behind
YAMS_ENABLE_DAEMON_DOWNLOAD. - The daemon now enforces the narrow policy boundary before network activity:
- feature gate enabled
- allowed scheme
- allowed host
- checksum requirement
- store-only enforcement
- checksum format validation
- Accepted daemon downloads are backgrounded as tracked jobs with:
jobId- persisted job metadata
queued/running/completed/failed/canceledstate transitions- status polling and list APIs
- cooperative cancel signaling
- The CLI exposes daemon job controls in
src/cli/commands/download_command.cpp:
yams download <url>yams download --status <job-id>yams download --list-jobsyams download --cancel <job-id>
- MCP exposes the same read/control surface in
src/mcp/mcp_server.cpp:
download_statusdownload_list_jobsdownload_cancel
Still Missing
- true job-oriented start/resume request types distinct from the compatibility
DownloadRequest maxFileBytesenforcementrateLimitRpsenforcement- sandbox/process isolation for daemon downloads
- resume-by-
jobIdsemantics stronger than the downloader's URL-based resume store - richer ownership/quota controls
Problem
If the daemon directly exposes the current downloader request shape, the daemon becomes a network boundary with new risk:
- SSRF and internal host reachability
- credential or custom-header abuse
- disk exhaustion via large or repeated downloads
- long-lived network tasks consuming daemon capacity
- cross-agent state collision when resume state is keyed only by URL
- arbitrary filesystem writes if export behavior leaks through
That is too much surface for a first daemonized version.
Design Principles
- Keep the daemon surface narrower than the local CLI/MCP surface.
- Treat downloads as managed jobs, not one-shot unary fetches.
- Enforce policy before any network activity.
- Keep first version store-only and CAS-rooted.
- Use durable daemon job IDs, not URL identity, for tracking and resume.
- Reuse the downloader manager internally rather than reimplement transport.
Target Model
Public Daemon API
The code currently uses a compatibility DownloadRequest plus separate status/list/cancel calls.
The intended end state is a fully job-oriented API instead of expanding the unary request forever.
StartDownloadJobRequestGetDownloadJobStatusRequestCancelDownloadJobRequestResumeDownloadJobRequestListDownloadJobsRequest
Matching responses/events:
StartDownloadJobResponseDownloadJobStatusResponseCancelDownloadJobResponseResumeDownloadJobResponseListDownloadJobsResponse- optional
DownloadJobEvent
Job States
queuedprobingdownloadingverifyingfinalizingindexingcompletedfailedcancelledblocked_policy
Job Record
Persist a daemon-owned record for each job:
download_idowneror caller identity- canonical URL
- request fingerprint
- policy decision snapshot
- created/updated timestamps
- bytes downloaded / total bytes
- attempt count
- ETag / Last-Modified
- CAS hash when completed
- last error
- indexing outcome
The downloader resume store can remain an implementation detail, but daemon logic should key work by
download_id.
Request Boundary
First Version Allowed Inputs
url- optional tags / metadata
- optional collection / snapshot indexing metadata
First Version Rejected Inputs
- custom headers
- proxy
- TLS insecure / custom CA
- export path / export dir
- overwrite controls
- arbitrary retry and transport tuning
Those can remain local CLI or MCP-only until the daemon has a stronger policy and audit model.
Policy Enforcement
Use the existing DaemonConfig::DownloadPolicy in
include/yams/daemon/daemon.h, but
enforce it in the handler before invoking the download service.
Implemented checks today:
- feature gate must be enabled
- allowed scheme check
- allowed host check
- checksum requirement check
- store-only hard enforcement
Not implemented yet:
- max file bytes enforcement
- rate limit enforcement
- sandbox strategy selection
Recommended defaults:
enable=falseallowedSchemes=["https"]requireChecksum=truestoreOnly=true- no export
Resume Identity
The current downloader resume identity is effectively URL + ETag. That is not sufficient for multi-agent daemon use.
Daemon job fingerprint should include:
- canonical URL
- effective caller identity
- checksum requirement / checksum value
- headers/auth fingerprint if those are ever supported
- store-only/export mode
Resume should only continue if:
- job fingerprint matches
- server validators are still compatible
- policy still allows the request
Execution Model
Implemented Baseline
- daemon-owned background job runner
- progress/status polling via status requests
- durable job metadata persisted by daemon
- cooperative cancellation support
Remaining Phases
- stronger bounded concurrency / quotas
- resume across daemon restart by job identity, not only downloader-local URL resume
- optional event streaming
- richer policy
- controlled auth/header support
- agent/job quotas
Security Boundaries
The daemon download path should be treated as its own trust boundary.
Threats to design against:
- SSRF to loopback, link-local, RFC1918, metadata endpoints
- open redirect escaping host allowlists
- oversized or unbounded bodies
- validator confusion on resumed jobs
- multi-tenant job collision
- content-type deception and indexing abuse
- progress/event amplification
Out of scope for first version:
- arbitrary authenticated internet fetches
- user-specified export to filesystem paths
- browser-equivalent cookie/session behavior
Integration with Existing Services
Use the existing downloader stack:
IDownloadManagerIResumeStoreIDiskWriterIHttpAdapter
Use existing indexing flow after successful CAS finalize:
- add document metadata
- apply tags and metadata
- optional collection/snapshot placement
Rollout Plan
Completed:
- Enforce current download policy in the existing daemon handler.
- Add job record type and persistence.
- Add
start/status/listjob API. - Add
cancel.
Remaining:
- Move the current handler to a compatibility wrapper or deprecate it.
- Add resume-by-job.
- Enforce
maxFileBytes, rate limits, and sandboxing. - Only then consider broadening request shape.
Open Questions
- What daemon caller identity is available for job ownership and quotas?
- Should checksum be mandatory for all daemon downloads or only for non-allowlisted hosts?
- Should MCP use the daemon job path later, or remain a separate local download path?
- Where should durable download-job state live: metadata DB, dedicated table, or sidecar state?