Skip to content

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.

Fan out over multiple services completed — ~/.vectorstep/service/pipelines/alert-triage.yaml is identify-upstreamscheck-upstreams (fan-out) → consolidate.

Add an artifacts: block to ~/.vectorstep/service/config.yaml:

artifacts:
dir: ./artifacts
retention_days: 7

dir 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:

Terminal window
cd ~/.vectorstep/service
source .venv/bin/activate
uvicorn src.main:app --reload --port 8000

Uvicorn’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.

- 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-upstreamscheck-upstreams (fan-out) → consolidatewrite-up. Reload and re-trigger:

Terminal window
curl -X POST http://localhost:8000/reload
curl -X POST "http://localhost:8000/webhook?source=alertmanager&allow_testing=true" \
-H "Content-Type: application/json" \
-d @tests/fixtures/alertmanager_critical.json

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:

Terminal window
cat ~/.vectorstep/service/artifacts/<run_id>/consolidate/investigation_notes

That’s the real, full writeup — multiple sentences, likely a full paragraph. Compare it against the database, which only ever holds the reference:

Terminal window
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.

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.artifacts column shape and cleanup/retention behaviour.
  • samples/pipelines/research-brief.yaml in 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.