Marbles
Agentic infrastructure, unpacked.
Write workflows in TypeScript, give agents a workspace, and decide what happens next. This is a whole workflow file.
Make a workflow.foundry/marbles/assess.tsimport { readFile } from "node:fs/promises";import { resolve } from "node:path";import { agent, artifact, monitor, step } from "@foundry.sh/marbles";import { z } from "zod"; // Another workflow collects reports and appends them to incidents.md.// This one watches that file and decides what happens next. const triage = agent({ provider: "codex", prompt: `Is the last entry in this file an incident? Reply with JSON only: {"isIncident": boolean, "reason": string}`,}); const verdict = z.object({ isIncident: z.boolean(), reason: z.string() });const brief = artifact({ name: "investigation-brief", type: "text/markdown" }); const assess = step("assess") .input(z.object({ entry: z.string() })) .do(async ({ input: { entry }, agents, report, log }) => { const session = await agents.session(triage); const reply = await session.generate(entry); const { isIncident, reason } = verdict.parse(JSON.parse(reply.text)); if (!isIncident) { log(`Not an incident: ${reason}`); return { isIncident }; } const draft = await session.generate("Write an investigation brief."); await report.artifact(brief, { "brief.md": draft.text }); return { isIncident }; }); monitor("incidents.md") .every("1m") .do(async ({ files, workspaces }) => { if (!files?.modified.length) return; const path = resolve(workspaces.current.root, "incidents.md"); return assess({}, { entry: await readFile(path, "utf8") }); });One workflow leaves a note.
Another workflow appends reports to
incidents.md.An agent makes the judgment.
Codex reads the entry and answers in JSON. The step validates the answer before trusting it.
Your code makes the decision.
An ordinary
iffinishes the run or writes an investigation brief.A monitor starts it.
Marbles polls
incidents.mdand startsassesswhen it changes.