Tutorial: store a full investigation as an artifact
Every tutorial so far has kept next_step_context short — a sentence or
two. Real investigation output is often much longer (a full writeup, a
compiled log excerpt), and stuffing that into next_step_context or
summary is exactly what artifacts
exist to avoid: content stored on disk by reference, pulled into a later
prompt by {{artifacts.step_name.key}} only when a step actually needs it.
Prerequisites
Section titled “Prerequisites”Fan out over multiple services
completed — ~/.vectorstep/service/pipelines/alert-triage.yaml is
identify-upstreams → check-upstreams (fan-out) → consolidate.
1. Enable artifact storage
Section titled “1. Enable artifact storage”Add an artifacts: block to ~/.vectorstep/service/config.yaml:
artifacts: dir: ./artifacts retention_days: 7dir is relative to wherever the service process runs from
(~/.vectorstep/service/, if you’ve been following this series). Directories
older than retention_days are removed daily at 02:00 — a failed run keeps
its artifacts for the same period, which is useful for debugging.
This needs a real restart, not POST /reload. The artifact store is
wired up once when the service process starts; /reload re-reads pipeline
YAML and a handful of other config keys, but not artifacts:. Stop the
service (Ctrl-C in the terminal it’s running in) and start it again:
cd ~/.vectorstep/servicesource .venv/bin/activateuvicorn src.main:app --reload --port 8000Uvicorn’s own --reload flag only watches Python source files, so this is a
manual stop/start either way — the same category of gotcha as the Gateway
restart in Build your first agent,
just on the service side this time.
2. Have consolidate write a full investigation writeup
Section titled “2. Have consolidate write a full investigation writeup”In ~/.vectorstep/service/pipelines/alert-triage.yaml, extend consolidate’s
prompt_template to also return an artifacts key — everything else in the
file is unchanged from the fan-out tutorial:
- name: consolidate executor: gateway executor_config: agent: generic-pipeline-step session_key: "agent:generic-pipeline-step:{{pipeline_run_id}}:consolidate" confidence_threshold: 0.70 on_low_confidence: escalate prompt_template: | A {{severity}} alert fired for {{labels.service}} in {{labels.environment}}. Two upstream dependencies were checked in parallel:
- {{steps['check-upstreams/0'].summary}} - {{steps['check-upstreams/1'].summary}}
Summarise the overall picture across both upstreams.
Return ONLY this JSON, no other text: { "confidence": 0.0, "summary": "One sentence: overall picture across both upstreams", "next_step_context": "", "reasoning": { "supports": "Evidence that makes this assessment credible", "contradicts": "Anything that complicates the picture", "assumptions": "What you're assuming in the absence of data" }, "artifacts": { "investigation_notes": "A full paragraph or two: what each upstream check found, why it does or doesn't implicate the alert, and what an on-call engineer would need to know beyond the one-sentence summary" } }artifacts is a free-form dict — each key is a name the agent chooses, each
value is the full text content. The runner intercepts it after the step
runs, writes investigation_notes to disk, and replaces the content with an
opaque local://... reference before anything is persisted to the database.
3. Add a step that consumes the artifact
Section titled “3. Add a step that consumes the artifact” - name: write-up executor: gateway executor_config: agent: generic-pipeline-step session_key: "agent:generic-pipeline-step:{{pipeline_run_id}}:write-up" confidence_threshold: 0.70 on_low_confidence: escalate prompt_template: | Turn the following full investigation into a short incident-channel message — plain text, no markdown, suitable for pasting into chat.
{{artifacts.consolidate.investigation_notes}}
Return ONLY this JSON, no other text: { "confidence": 0.0, "summary": "One sentence: what the incident-channel message says", "next_step_context": "", "incident_message": "The actual chat-ready message" }{{artifacts.consolidate.investigation_notes}} is resolved at render time —
the runner loads the real content from disk just for this step, right before
the prompt is sent. consolidate has no hyphen so this reads directly; a
step named e.g. check-upstreams would need
{{artifacts.check_upstreams.key}} — same hyphen-to-underscore rule as
{{steps.x.y}} from Writing good prompts.
The full pipeline is now four steps: identify-upstreams →
check-upstreams (fan-out) → consolidate → write-up. Reload and
re-trigger:
curl -X POST http://localhost:8000/reloadcurl -X POST "http://localhost:8000/webhook?source=alertmanager&allow_testing=true" \ -H "Content-Type: application/json" \ -d @tests/fixtures/alertmanager_critical.jsonWhat you should see
Section titled “What you should see”Open the run in http://localhost:8000/ui/. consolidate should show a
new Other fields row: artifacts: {"investigation_notes": "local://<run_id>/consolidate/investigation_notes"} — a reference, not the
full text. write-up should show completed with its own summary, and an
Other fields entry incident_message containing the composed
chat-ready message, built from the full writeup rather than just
consolidate’s one-sentence summary.
Note the <run_id> in that reference — it’s the same run_id from the
webhook’s {"status": "accepted", "run_id": "..."} response, or the one
shown at the top of the run detail page. Use it to look at the artifact on
disk directly:
cat ~/.vectorstep/service/artifacts/<run_id>/consolidate/investigation_notesThat’s the real, full writeup — multiple sentences, likely a full paragraph. Compare it against the database, which only ever holds the reference:
sqlite3 ~/.vectorstep/service/runs.db \ "SELECT artifacts FROM pipeline_steps WHERE run_id = '<run_id>' AND step_name = 'consolidate';"That’s the point of artifact storage: the long-form content lives on disk by
reference, not in the database, and is only ever pulled into a later
prompt’s context (here, write-up’s) when a step actually references it.
Where next
Section titled “Where next”Go to Route escalations to a real channel next — the next tutorial in the series.
Once you’re comfortable with the mechanics:
- Artifact storage — the full
reference, including the
pipeline_steps.artifactscolumn shape and cleanup/retention behaviour. samples/pipelines/research-brief.yamlin the VectorStep repo — a complete three-step worked example (gather → synthesise → proofread) that chains three artifacts end to end, rather than this tutorial’s one.