Appearance
Artifacts
An artifact marks a durable output the agent produced: a reviewed PR URL, a generated report, a decision record. Sessions come and go; artifacts persist across them, capped and listable, so the people supervising an agent see what it shipped without replaying event streams.
Declare kinds
Author agent/artifacts.ts with defineArtifacts from @cursor/july/artifacts:
ts
import { z } from "zod";
import { defineArtifacts } from "@cursor/july/artifacts";
export default defineArtifacts({
kinds: {
"reviewed-pr": {
description: "A pull request this agent reviewed.",
schema: z.object({ url: z.string(), verdict: z.string() }),
},
report: { description: "A generated report." },
},
agentTool: true,
});defineArtifacts accepts three fields. kinds declares the artifact kinds: with kinds declared, tag accepts only these; with none, any kind string is accepted freeform. Each kind's description says what it holds and doubles as the model-facing prompt for tag_artifact. An optional Zod schema validates payloads before they persist (the parsed output is stored, so defaults and coercions apply). agentTool exposes the model-facing tag_artifact tool generated from the kinds registry; it requires at least one declared kind. max is the retention cap, default 1000: on insert past the cap, the oldest-updated artifact is evicted.
Tag from host code
Every handler surface carries ctx.artifacts (or args.artifacts), an ArtifactsApi with tag and list: tools, hooks, channel route handlers and onStart, schedule run handlers, and reminder run handlers. Tool and hook facades are session-bound, so tag auto-fills the sessionId (and turnId when known). Channel, schedule, and reminder facades are unbound; pass sessionId in the tag input to attribute one.
ts
await ctx.artifacts.tag({
kind: "reviewed-pr",
key: prUrl,
title: `Reviewed ${prUrl}`,
data: { url: prUrl, verdict: "approve" },
});key is the upsert handle: tagging the same key again replaces the record instead of creating a new one, so re-reviewing a PR updates one row. A contents payload (string or bytes, with an optional contentType) attaches a file or blob served at GET /v1/artifacts/:id/content; re-tagging a keyed artifact without contents keeps the existing payload.
Let the model tag
With agentTool: true, the tag_artifact server tool materializes from the kinds registry. Its description tells the model to tag notable outputs and lists each kind with its description, and its input schema is a discriminated union over the declared kinds, so a schema'd kind is validated exactly like a host-side tag. An authored tool named tag_artifact shadows the built-in, with a warning.
Observe and list
Tagging emits an artifact.tagged event on the attributed session's stream, carrying the record: id, kind, key, title, data, and source ("host" for host code, "model" for tag_artifact). Hooks, channel events, and evals see it like any other stream event.
Over HTTP:
bash
curl 'http://127.0.0.1:3000/<slug>/v1/artifacts?kind=reviewed-pr&limit=20'
curl 'http://127.0.0.1:3000/<slug>/v1/artifacts/<id>/content'GET /v1/artifacts returns records newest-updated first, filterable by kind and sessionId. Session ownership applies, same as /v1/sessions. The playground renders tagged artifacts too.
Gate evals on tagging
t.taggedArtifact(kind?, predicate?) gates an eval on at least one artifact tagged during the test turn, optionally of one kind and matching a predicate over the record:
ts
t.taggedArtifact("reviewed-pr", (record) => record.source === "model");What's next
Continue with these pages:
- Sessions and streaming: the
artifact.taggedevent in the full vocabulary - Tools: the
ctxthat carriesartifacts - Evals: the assertions
taggedArtifactsits beside