Chapter 30 — Job Artifacts
Since v0.88.0.
Dispatch work to a thClaws worker over HTTP, then collect the files
it produced — declared per job, frozen with a hash the moment the run
finishes, downloadable with the same Bearer token you dispatched with.
Push input files the same way. Together these turn any --serve
instance into a worker node you can chain into pipelines:
coder (machine A) ──artifacts──▶ your orchestrator ──inputs──▶ reviewer (machine B)
If you’ve used GitHub Actions or GitLab CI artifacts, this is the same
idea — declared outputs of one job, retrievable by id — applied to
agent runs. The “job” is the session_id every /agent/run returns.
Why not workspace sync?
The sync surface (/workspace/sync/*, Chapter 27) mirrors a whole
workspace and was designed for trusted networks (a tunnel or
ForwardAuth in front). An external orchestrator holding only an API
token had three gaps: no supported auth, no way to know which files a
given job actually produced, and a race — the file list and the file
download were separate requests, so a file could change in between.
Job Artifacts closes all three: Bearer auth, per-job scoping, and a
manifest whose hashes are fixed at collection time.
Quick start
Start a worker:
THCLAWS_API_TOKEN=secret thclaws --serve --port 8443
1. (Optional) push input files before dispatching:
curl -X POST http://worker:8443/v1/inputs \
-H "Authorization: Bearer secret" -H "Content-Type: application/json" \
-d '{"files":[{"path":"inputs/brief.txt","content_base64":"'"$(base64 < brief.txt)"'"}]}'
Files land under inputs/ in the worker’s workspace (see
Input placement rules).
2. Dispatch with collect_files — glob patterns naming what counts
as this job’s output:
curl -X POST http://worker:8443/agent/run \
-H "Authorization: Bearer secret" -H "Content-Type: application/json" \
-d '{"prompt":"Read inputs/brief.txt and write a report to reports/summary.md",
"collect_files":["reports/*.md"]}'
# → { "session_id": "sess-abc…", "summary": "...", ... }
When the run finishes, matching files are copied into a per-session snapshot and sha256-hashed. From that moment the artifact is immutable — editing the live file later changes nothing you can download.
3. Fetch the manifest, then the files:
curl -H "Authorization: Bearer secret" \
http://worker:8443/v1/sessions/sess-abc…/artifacts
# → { "artifacts": [ { "id":"a1", "path":"reports/summary.md",
# "size":180, "sha256":"67aeef…" } ],
# "patterns": ["reports/*.md"], "collected_at": "…" }
curl -H "Authorization: Bearer secret" -o summary.md \
http://worker:8443/v1/sessions/sess-abc…/artifacts/a1
# response carries an `x-sha256` header — verify it matches the manifest
Endpoint reference
All under the same Bearer auth as the rest of /v1
(THCLAWS_API_TOKEN).
| Endpoint | What it does |
|---|---|
POST /agent/run + "collect_files": ["glob", …] |
Declare the run’s outputs; snapshot + hash at completion. Works on the sync, streaming (SSE), and async (x_callback) paths alike. |
GET /v1/sessions/{sid}/artifacts |
The frozen manifest: id, path, size, sha256 per artifact, plus the patterns used and a skipped list if caps truncated the collection. |
GET /v1/sessions/{sid}/artifacts/{aid} |
One file, served from the snapshot (never the live workspace). aid is the manifest id (a1, a2, …) or the exact path. |
POST /v1/inputs |
Place files into the workspace ahead of a dispatch. Body: {"workspace_dir"?: "...", "files": [{"path", "content_base64"}]}. |
Both GETs and /v1/inputs accept an optional workspace_dir
(query parameter for the GETs) with the same validation as
/agent/run; omitted, the daemon’s working directory is used.
Input placement rules
POST /v1/inputs is deliberately conservative:
- Paths must be relative, may not contain
.., and may never touch.thclaws/or.git/. - By default files may only land under
inputs/. Widen with an env allowlist on the worker:THCLAWS_INPUTS_PREFIXES="inputs/,data/"— orTHCLAWS_INPUTS_PREFIXES="*"for anywhere in the workspace (still excluding.thclaws/and.git/). - Limits: ≤ 100 files per request, ≤ 64 MB decoded total. The response
echoes each written file’s
sha256so the sender can verify.
Collection limits
Artifact snapshots cap at 256 files / 300 MB per run. Files that
matched your globs but fell past a cap appear in the manifest’s
skipped list — a truncated collection is always visible, never
silent. Collection skips .thclaws/, .git/, and node_modules/
entirely.
Securing workspace sync for orchestrators (Tier 1)
If you do want whole-workspace mirroring (Chapter 27) from an orchestrator that only holds the API token, opt the sync routes into Bearer auth on the worker:
THCLAWS_SYNC_REQUIRE_AUTH=1 THCLAWS_API_TOKEN=secret thclaws --serve
Every /workspace/sync/* request must then carry
Authorization: Bearer <token> — no tunnel or ForwardAuth needed.
Unset, sync behaves exactly as before (trusted-network model), so
existing deployments are unaffected.
A two-worker pipeline, end to end
# machine A writes code
SID_A=$(curl -s -X POST http://worker-a:8443/agent/run \
-H "Authorization: Bearer $TOK" -H "Content-Type: application/json" \
-d '{"prompt":"Implement the parser per spec.md","collect_files":["src/**/*.py"]}' \
| jq -r .session_id)
# pull A's artifacts, push them to B as inputs
curl -s -H "Authorization: Bearer $TOK" \
http://worker-a:8443/v1/sessions/$SID_A/artifacts | jq -c '.artifacts[]' |
while read -r art; do
aid=$(jq -r .id <<<"$art"); path=$(jq -r .path <<<"$art")
curl -s -H "Authorization: Bearer $TOK" \
http://worker-a:8443/v1/sessions/$SID_A/artifacts/$aid -o /tmp/f
curl -s -X POST http://worker-b:8443/v1/inputs \
-H "Authorization: Bearer $TOK" -H "Content-Type: application/json" \
-d "{\"files\":[{\"path\":\"inputs/$path\",\"content_base64\":\"$(base64 < /tmp/f)\"}]}"
done
# machine B reviews the actual files
curl -s -X POST http://worker-b:8443/agent/run \
-H "Authorization: Bearer $TOK" -H "Content-Type: application/json" \
-d '{"prompt":"Review the code under inputs/ and write findings to review.md",
"collect_files":["review.md"]}'
Where artifacts live on disk
Snapshots are stored inside the worker’s workspace at
.thclaws/state/artifacts/<session_id>/ (manifest.json + files/).
Like everything under .thclaws/state/, they’re gitignored and never
included when an agent is packed or published. They persist until you
delete them — prune old sessions’ directories if a long-lived worker
accumulates too many.
See also
- Chapter 3 —
--servemode andTHCLAWS_API_TOKEN. - Chapter 19 — heartbeat schedules; combine with artifacts for continuous produce-and-collect loops.
- Chapter 27 — workspace sync, the whole-workspace alternative.
- Technical manual:
job-artifacts.mdfor wire-level details.