Implements:
artifact-sdk/v1· Emits:evaluable-artifact/v2· Spec:SPEC.md· Why:MOTIVATION.md
A small, vendor-neutral authoring API for research artifacts. A producer that already has the structured facts about its run — which experiments it ran, what each produced, which claim each result supports, the container it ran in, the paper — declares them directly, and the SDK writes a conforming submission: no prose round-trip, no scraping. A downstream evaluator then reads that submission.
npm install @microsoft/universal-artifact-sdkdist/ is built automatically on publish (via the prepare script), so the installed package is
ready to import — no build step on your side.
import {
createArtifact, addEnvironment, addDataset, addExperiment,
addTrace, addResult, addClaim, attachPaper, writeSubmission,
} from "@microsoft/universal-artifact-sdk";import {
createArtifact, addEnvironment, addDataset, addExperiment,
addResult, addClaim, writeSubmission,
} from "@microsoft/universal-artifact-sdk";
const a = createArtifact({ id: "expt-42", title: "Feedback-driven contraction" });
addEnvironment(a, {
name: "primary",
image: { reference: "reg/artifact:1.2", digest: "sha256:abc…" },
});
addDataset(a, {
id: "sample_corpus",
location: { kind: "external", uri: "https://example.org/corpus.tar.gz", sha256: "def…", access: "public" },
prepare: "tar -xzf corpus.tar.gz -C ./data",
});
addExperiment(a, {
slug: "posterior_contraction",
directory: "experiments/posterior_contraction",
run: { command: "python run.py --seed 0", entrypoint: "run.py", seed: 0 },
uses_data: [{ dataset: "sample_corpus", at: "data/corpus.parquet" }],
runs_in: "primary",
});
addResult(a, {
id: "R1",
produced_by: "posterior_contraction",
validates: ["C1"],
evidence: "experiments/posterior_contraction/contraction.csv",
kind: "metrics",
locators: {
kl_final: { column: "kl_divergence", row: "final" },
kl_initial: { column: "kl_divergence", row: "0" },
},
});
addClaim(a, {
id: "C1",
statement: "Posterior contraction improves with more feedback rounds.",
paper_ref: { section: "5.2", figure: "3" },
// `validator` (singular) is sugar for a one-element `validators` list
validator: {
kind: "builtin", name: "monotonic",
input: { result: "R1", series: ["kl_initial", "kl_final"] },
params: { direction: "decreasing" },
},
});
const report = writeSubmission(a, "./out", { stageFrom: "." });
// → ./out/{manifest.yml, claims.yml, results.yml, datasets.yml, SHA256SUMS, .sdk/state.json, …}
if (report.incomplete) console.warn("incomplete:", report.warnings);A fuller example — a qualitative claim with a trace and attest-gates-inspect validators — is
in test/worked-example.test.ts (mirrors SPEC Appendix A).
examples/quickstart.mjs is a standalone, copy-pasteable script that
builds the minimal artifact above and writes a conforming submission. After building the SDK, run it:
npm install && npm run build
node examples/quickstart.mjsIt prints the files written and the generated manifest.yml.
| Call | What it declares |
|---|---|
createArtifact({ id, title }) |
new, empty artifact (in memory) |
openSubmission(dir) |
reopen an existing submission to iterate (§4.1) |
addEnvironment(a, {…}) |
the container image, by reference (§2.5) |
addDataset(a, {…}) |
an input data source: in_artifact | in_container | external, optional study ethics block (§2.7) |
addExperiment(a, {…}) |
a runnable unit + exact run instructions (§2.2) |
addTrace(a, {…}) |
the record of what actually ran (§2.2.1) |
addResult(a, {…}) |
an output: produced_by + validates + evidence + locators/support (§2.3) |
addExhibit(a, {…}) |
a typed exhibit connecting a claim to a figure/table/proof/derivation/listing; path (or a statement for a proof) + validates (§2.3.1) |
addClaim(a, {…}) |
a statement + one or more validators; optional stance (hypothesis|finding) + tested_by (§2.4, §2.4.1, §3) |
addAssessment(a, {…}) |
a whole-submission dimension (usually evaluator-written) (§2.1.1) |
attachPaper(a, {…}) |
the paper PDF + optional source / claims / references export (§2.6) |
attachResearchAgent(a, {…}) |
optional producer Q&A witness: pinned model + grounding_sources (§2.9) |
configureJournal(a, { policy, actor, onMissingRationale? }) |
set the rationale-elicitation policy for the edit history (§2.8, §4.2) |
abandonExperiment / failExperiment / supersedeExperiment / purgeExperiment |
record an experiment's disposition — what was tried and why it was dropped (§2.2.2) |
listAbandonedExperiments(a) / listJournal(a) |
inspect the retained non-active experiments + the edit history |
writeSubmission(a, dir, opts?) |
structural validation + serialize to evaluable-artifact/v2 (§5, §7) |
add*is upsert-by-id: calling it with an existing id/slug replaces that element, so an artifact is edited across iterations, not rebuilt.remove*/get*/list*complete the editor surface.- Every mutation is journaled with a rationale (§2.8, §4.2). Each mutating call accepts an
optional
ctx = { actor?, rationale? };configureJournalsets whether a missing rationale isrequired(throw),prompt(ask anonMissingRationalecallback — the seam a producing harness wires its agent into), orwarn(default: record + flag). This captures why an experiment was added or dropped — intent a VCS diff cannot recover. removeExperimentis a soft-remove: it tags the experimentabandonedand retains it (with aDISPOSITION.mdmarker) so what was tried is never lost;purgeExperimentis the rationale-gated hard delete.- Claims carry an author
stance(§2.4.1):finding(default) asserts a demonstrated result and is assessed on substantiation;hypothesisis a proposed statement — pair it withtested_by+ experiment disposition so a refuted hypothesis reads as an honest negative result, not an unsubstantiated claim. - A submission may ship a research agent (§2.9):
attachResearchAgentrecords an optional producer Q&A witness (persona fileresearch-agent.md+ a pinnedmodelandgrounding_sourcesin the manifest). It is advisory and audited by a downstream evaluator — an evidence source, never an authority. Optional: omit it and the correspondingagent_faithfulnessdimension is simply unassessable. - A claim can point at a typed exhibit (§2.3.1):
addExhibitconnects a claim to the non-numeric artifact that substantiates it — afigure,table,proof,derivation, orlisting. Unlike a Result (a re-analyzable measurement), an exhibit defaults to theinspectvalidation mode (a reviewer reads the rendering); aproofdefaults tore-executeand may carry a formalstatementin place of a renderedpath. Optional and additive — a submission with no exhibits emits noexhibits.yml. writeSubmissionis idempotent: re-emitting an unchanged artifact rewrites the same bytes; it never clobbers producer-authored blobs (paper, evidence, traces, reflection).stageFrom(optional): a root the SDK copies referenced blob files from into the submission. Omit it if you place blobs into the output directory yourself.
The SDK does structural validation only (SPEC §7): required fields, legal enums, and
referential integrity (result→experiment/claim, experiment→dataset, trace covers→experiment,
claim tested_by→experiment, validator gated_by→an attest validator on the same claim), and
that external datasets carry a sha256. Hard problems throw StructuralError; "incomplete but
legal" cases (a claim with no validator, a not-yet-present evidence file) are warnings in the
returned report.
Semantic judgment — do results actually support claims, do validators pass — stays with a downstream evaluator. Keeping the SDK's checks structural (schema-derived, identical across bindings) avoids cross-language drift.
Implemented: the full object model, the builder API, writeSubmission (serialization + evidence
inventory + SHA256SUMS + .sdk/state.json), structural validation, and openSubmission.
The canonical JSON Schema is authored here and the emitted model is tested against it. The SDK only declares validators; executing them is the job of a downstream evaluator.
npm run typecheck # tsc --noEmit
npm test # vitest (114 tests)
npm run test:coverage # vitest + v8 coverage
npm run build # emit dist/This project welcomes contributions and suggestions. See CONTRIBUTING.md
for details, including the Contributor License Agreement (CLA). See SECURITY.md
for reporting security issues, and SUPPORT.md for how to get help.
This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact opencode@microsoft.com with any additional questions or comments.
This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow Microsoft's Trademark & Brand Guidelines. Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship.
MIT © Microsoft Corporation