Build workflows

Artifacts

Markdown documents that outlive the run that wrote them — reports, logs and briefs your workflows keep up to date, with every version kept.

A step's output lasts as long as its run, and a workspace is deleted the moment the run finishes. Artifacts are for what should stay: a weekly report, a running incident log, a brief a person reads in the morning. Each one is a markdown document that belongs to the account, has a slug such as ops-weekly, and keeps every version it has ever had.

The artifact tools are available to every account with nothing to set up, under the artifact. prefix, in tool steps and in an agent's tools list.

Writing one

steps:
  - key: incidents
    kind: tool
    tool: postgres.query
    args: { sql: "select title, severity, resolved_at from incidents where opened_at > now() - interval '7 days'" }
  - key: report
    kind: llm
    prompt: |
      Write this week's operations report in markdown, with a section per severity.
      {{ steps.incidents.output }}
  - key: save
    kind: tool
    tool: artifact.write
    args: { slug: ops-weekly, title: Operations weekly, body: "{{ steps.report.output.text }}" }

write replaces the whole document and creates it if the slug is new. The next run writes the same slug again, and the old report is still there in the artifact's history.

Changing part of one

Rewriting a long document to change one line wastes tokens and invites mistakes, so the tools can also work on a part:

  • append adds markdown to the end, or to the end of one section. A list item appended under a list stays part of that list.
  • edit_section replaces what is under one heading, up to the next heading of the same or a higher level, and leaves the rest alone.
  • read with section returns only that section, and always lists the document's headings.

A section named in append or edit_section that does not exist yet is added at the end.

  - key: log
    kind: tool
    tool: artifact.append
    args:
      slug: deploy-log
      section: "{{ trigger.repository.name }}"
      content: "- {{ trigger.head_commit.id }} deployed by {{ trigger.pusher.name }}"

Reading one in a template

Any step can read an artifact through a template, with no tool call: {{ artifacts.<slug>.body }}, and also .title, .version, .url and .updated_at.

steps:
  - key: brief
    kind: llm
    prompt: |
      Here is the brief as it stands. Rewrite the Today section for {{ trigger.scheduled_at }}.
      {{ artifacts.morning-brief.body }}
  - key: post
    kind: tool
    tool: slack.post_message
    args: { channel: C0123, text: "Brief updated: {{ artifacts.morning-brief.url }}" }

A step reads the artifact as it is when the step starts, and loads only the artifacts it names. An artifact that doesn't exist resolves to nothing, so a condition can check for it:

    if: { path: artifacts.morning-brief, exists: false }

Steps that write an artifact aren't linked automatically to steps that read it through a template. When one step of a run writes an artifact and a later step reads it, add the writer to the reader's depends_on.

From a workspace

When a coding agent or a script writes the document, artifact.import copies a text file out of the run's workspace into an artifact as its new body.

History and retries

Every change is a new version, recorded with the run and step that made it. The Artifacts page shows each document rendered, its history, and the diff between any version and the one before.

A step that is retried after an interruption does not apply its change twice: repeating the same call from the same step is recognised, and the version it already saved is returned.

Over MCP

Your own agents can use the same tools over MCP as artifact__list, artifact__read, artifact__write, artifact__append and artifact__edit_section. Changes made this way are recorded against the person the token belongs to.