Skip to content
Français

Share files and reports

Store task outputs as artifacts and pass references between tasks or processes.

Use an artifact when a task produces a file or report that you want to store separately from its result. publishArtifact() encodes and stores the content, then returns a reference that readStoredArtifact() can use to read it back.

import {
  defineJsonArtifact,
  createArtifactStore,
  createLocalTransport,
} from "@elie-laloum/outpost";
import { z } from "zod";

export const coverage = defineJsonArtifact({
  name: "coverage-report",
  version: "1",
  schema: z.object({ lines: z.number(), files: z.array(z.string()) }),
});
export const store = createArtifactStore({
  transporter: createLocalTransport({ directory: ".outpost/storage" }),
});
import { reportValue } from "./reporter.ts";
import { publishArtifact, readStoredArtifact } from "@elie-laloum/outpost";
import { store, coverage } from "./coverage.ts";

export const reference = await publishArtifact(
  store,
  coverage,
  { lines: 87.5, files: ["src/parser.ts"] },
  { producer: { executionId: "nightly-42", taskKey: "coverage", attempt: 1 } },
);
reportValue(await readStoredArtifact(store, coverage, reference));
// Example output: { lines: 87.5, files: [ 'src/parser.ts' ] }

The payload lands in .outpost/storage/artifacts/<id>.blob. The reference is a small JSON object: id, a SHA-256 digest, size, contract, producer and parents. Reading checks the contract, the size and the digest before decoding.

Task outputArtifact
HoldsLossless JSON in a checkpointed runJSON checked by a schema, or bytes
SizeThe whole checkpoint is capped, 16 MiBEach artifact up to 16 MiB by default (maxBytes)
Readable byDependent tasks of the same runAny process with the store and the reference
CheckedNot validated when readContract, size and digest on every read

Keep small results as task outputs. Publish an artifact for a large report, a binary file or a result another process reads: the checkpoint then keeps only the reference.

ContractValueOn publish and read
defineJsonArtifact({ name, version, schema })Lossless JSONValidates with a Standard Schema (Zod, Valibot…) or a function that throws
defineBinaryArtifact({ name, version })Uint8ArrayCopies the bytes as they are

Change version when the format changes. A contract with another name, version or encoding rejects the reference with Artifact contract mismatch.

defineArtifactTask() takes the usual task options plus store, contract and produce(context). Its output is the reference; readArtifact() reads it from a task listed in after.

import {
  defineJsonArtifact,
  createArtifactStore,
  createLocalTransport,
} from "@elie-laloum/outpost";
import { z } from "zod";

export const findings = defineJsonArtifact({
  name: "audit-findings",
  version: "1",
  schema: z.array(z.object({ file: z.string(), issue: z.string() })),
});
export const store = createArtifactStore({
  transporter: createLocalTransport({ directory: ".outpost/storage" }),
});
import { defineArtifactTask } from "@elie-laloum/outpost";
import { store, findings } from "./findings.ts";

export const audit = defineArtifactTask({
  key: "audit",
  store,
  contract: findings,
  produce: () => [{ file: "src/parser.ts", issue: "Unchecked input length" }],
});
import { defineTask, readArtifact } from "@elie-laloum/outpost";
import { audit } from "./audit.ts";
import { findings, store } from "./findings.ts";

export const summary = defineTask({
  key: "summary",
  after: [audit],
  perform: async (context) => {
    const items = await readArtifact(context, audit, findings, store);
    return `${items.length} finding(s) in ${items[0]?.file}`;
  },
});
import { reportValue } from "./reporter.ts";
import { defineWorkflow } from "@elie-laloum/outpost";
import { audit } from "./audit.ts";
import { summary } from "./summary.ts";

export const result = await defineWorkflow("audit", [audit, summary]).start();
result.unwrap();
reportValue(result.value(summary));
// Example output: 1 finding(s) in src/parser.ts

The task sets producer from the execution, its key and the attempt. readArtifact() rejects a reference produced by another execution or another task, then verifies and decodes the payload. In a real workflow, produce reads an agent task’s result with context.value().

parents lists, in order, the references an artifact was derived from. In defineArtifactTask(), pass parents: (context) => [context.value(audit)].

import { readFile } from "node:fs/promises";
import { defineBinaryArtifact, publishArtifact } from "@elie-laloum/outpost";
import type { ArtifactReference, ArtifactStore } from "@elie-laloum/outpost";

declare const store: ArtifactStore;
declare const report: ArtifactReference;

const archive = defineBinaryArtifact({ name: "coverage-html", version: "1" });
const html = await publishArtifact(
  store,
  archive,
  await readFile("coverage.tar.gz"),
  {
    producer: { executionId: "nightly-42", taskKey: "html", attempt: 1 },
    parents: [report],
  },
);

The producer and the parents are part of the reference and of its id. Pass producer or parents to readStoredArtifact() to reject a reference that does not match them.

A reference is plain JSON: save it in a checkpoint, a queue job or a file. Another process opens a store over the same transport and reads it with the same contract.

import { readFile } from "node:fs/promises";
import { readStoredArtifact } from "@elie-laloum/outpost";
import type { ArtifactContract, ArtifactStore } from "@elie-laloum/outpost";

declare const store: ArtifactStore;
declare const coverage: ArtifactContract<{ lines: number }>;

const saved: unknown = JSON.parse(await readFile("coverage.json", "utf8"));
const report = await readStoredArtifact(store, coverage, saved);

readStoredArtifact() validates the reference itself, so an edited id, digest or producer is rejected.

createArtifactStore({ transporter }) accepts any Transport. Use an S3 or R2 transport to share artifacts between machines; Where data lives covers the local layout.

  • An artifact’s contents are held in memory during publication and reading. To choose a size limit, see ArtifactStoreOptions.
  • A published artifact is immutable. Outpost never deletes one: expire old objects with your storage’s own rules.

API: defineJsonArtifact · defineBinaryArtifact · createArtifactStore · publishArtifact · readStoredArtifact · defineArtifactTask · readArtifact · ArtifactReference