# Artifacts, Workflows, and Generated UI codex resume 019ddfa8-00c6-7502-ae32-ceb6d85b2976 Executor core should stay small: tools, sources, secrets, permissions, and invocation. Workflows and generated UI are "just Git and code", but that should not pull a versioned filesystem into the core SDK. The shared foundation is an optional artifact protocol package. ```txt @executor/sdk tools sources secrets permissions invocation elicitation @executor/artifacts ArtifactStore contract artifact refs project/version/commit schemas diff/file/change types artifact errors @executor/artifacts-cloudflare provider plugin for Cloudflare Artifacts @executor/artifacts-local-git isomorphic-git-backed provider for tests and development @executor/workflows feature plugin that requires ArtifactStore @executor/generative-ui feature plugin that requires ArtifactStore ``` ## Why not core Artifacts are not required to call tools safely. A user who only wants tool execution should not install or configure artifact storage. Artifacts are still useful enough to standardize because multiple higher-level features need the same versioned file-tree abstraction: - workflows - generated components - generated full UIs - templates - previews - tests and fixtures - agent work branches - rollback and forks The protocol package is official, but it is not core. Use `@executor/artifacts` for the protocol package. Provider plugins import its constants and implement its contracts; feature plugins import the same constants and require them. ## Artifact projects Do not make this workflow-specific. Model the generic thing as an artifact project, with workflows as the first consumer. ```ts type ArtifactKind = "workflow" | "component" | "ui" | "template" | "connector" | "document"; type ArtifactProject = { id: string; ownerId: string; kind: ArtifactKind; name: string; defaultBranch: string; currentCommit: string; createdAt: Date; updatedAt: Date; }; ``` The app database indexes product metadata and permissions. The artifact store owns file trees and history. Database answers: - Which artifacts does this user or workspace own? - Which commit is current or published? - Who can read/write it? - Which runs, previews, or deployments point at it? Artifact storage answers: - What files existed at commit X? - What changed between two versions? - Can an agent fork/edit this tree? - Can we clone, export, replay, or roll back it? ## File layout Every artifact project should have a manifest at the root. ```txt artifact.json src/ tests/ fixtures/ preview/ ``` Workflow artifact: ```txt artifact.json src/workflows/support-triage.workflow.ts src/steps/ workflow.manifest.json tests/workflow.test.ts fixtures/sample-input.json ``` Generated UI artifact: ```txt artifact.json src/App.tsx src/components/ src/styles.css ui.manifest.json tests/ preview/ ``` Generated component artifact: ```txt artifact.json src/index.tsx src/demo.tsx props.schema.json component.manifest.json tests/ preview/ ``` ## ArtifactStore shape Keep the protocol boring. No workflow concepts, UI concepts, Cloudflare concepts, or agent concepts. ```ts export type ArtifactRef = { projectId: string; ref: string; // branch, tag, or commit }; export type ArtifactCommit = { id: string; message: string; parentIds: readonly string[]; createdAt: Date; }; export interface ArtifactStore { readonly createProject: ( input: CreateArtifactProjectInput, ) => Effect.Effect; readonly readFile: (ref: ArtifactRef, path: string) => Effect.Effect; readonly writeFiles: ( input: WriteArtifactFilesInput, ) => Effect.Effect; readonly diff: (input: ArtifactDiffInput) => Effect.Effect; readonly forkProject: ( input: ForkArtifactProjectInput, ) => Effect.Effect; } ``` Cloudflare Artifacts is the likely first production provider because it is versioned file-tree storage that speaks Git and can be accessed from Workers, REST, and Git clients. The Cloudflare package should implement the protocol, not define the product model. `@executor/artifacts-local-git` should likely use `isomorphic-git`, not shell out to a Git binary. Cloudflare Artifacts exposes standard Git smart HTTP remotes, and Cloudflare documents `isomorphic-git` as working with Artifacts in Workers when no Git binary or local disk is available. Using it locally gives us one Git implementation path that can also work in Workers with an in-memory filesystem. ## Protocol provider selection Secret storage is the closest existing pattern. Secret providers register unique provider keys, routes can pin a secret to a provider, and writes can name a provider explicitly. Protocol providers should reuse that shape instead of inventing a separate dependency system. The general shape: ```ts createExecutor({ plugins: [artifactsLocal(), artifactsCloudflare(), workflows()], protocols: { "executor.artifacts.store": "artifacts-cloudflare", }, }); ``` Rules: - provider plugins expose a unique plugin/provider id - protocol packages expose stable capability constants - feature plugins require protocol capabilities by importing those constants - if exactly one installed provider satisfies a required protocol, use it - if multiple installed providers satisfy a required protocol, require explicit selection in executor config - optional protocol requirements can be absent without failing startup This is intentionally stricter than secret writes. Picking the "first writable" secret provider is acceptable for convenience because each secret route is pinned after creation. Picking the first artifact store would be risky because workflows, generated UI, previews, and runs depend on stable file history. ## Workflows Workflows should be a feature plugin, not core. The workflow plugin owns: - code indexing for `"use workflow"` files - derived workflow graph/manifests - code transforms for visual edits - validation - code generation - run model - runtime adapter - workflow tools such as `workflows.create`, `workflows.update`, `workflows.validate`, and `workflows.run` The artifact store owns: - versioned workflow files - commit history - forks - diffs - rollback Workflow code is canonical. The graph is a derived UI/index, not the source of truth. ```txt code -> indexed graph -> visual editor visual edit -> code transform -> commit -> re-index graph commit -> run pinned to commit -> observable steps ``` Runs should always pin the artifact commit they executed. This gives reproducibility, rollback, and a clean audit trail. `"use workflow"` and `"use step"` are the syntax that make code indexable: - workflow function = graph root - awaited step calls = action nodes - arguments = data mappings - `if` statements = branches - `Promise.all` = parallel branches - `sleep`, hooks, or webhooks = wait/resume nodes - return value = workflow output The manifest is derived, like an index or lockfile. It can be stored for fast UI rendering/search, but source files are canonical. ## Generated UI Generated UI and generated components should use the same artifact foundation. Source files are canonical; manifests and previews are derived. The generative UI plugin owns: - UI/component manifests - preview build pipeline - test/validation pipeline - publish/deploy hooks - generated UI tools The shared artifact protocol means workflows, components, and full UIs can all use the same versioning, forking, diffing, preview, and rollback primitives. ## Zapier-style product foundation The workflow plugin is the start of a Zapier competitor, but the first landing should only establish the spine: 1. artifact protocol and provider 2. workflow code conventions around `"use workflow"` and `"use step"` 3. code indexer that derives a graph/manifest 4. connector/action metadata for humans 5. validation pipeline 6. run records pinned to artifact commits 7. basic run history and step logs Later product layers can add triggers, polling cursors, schedules, templates, branching, approvals, retries, replay, connector marketplaces, and richer data mapping. The durable `"use workflow"` model is the right canonical representation, but the Workflow SDK "world" concept should not leak to users. Users see workflows, runs, steps, approvals, waits, and logs. ## TypeScript sketch Protocol package: ```ts // @executor/artifacts import type { Effect } from "effect"; import type { PluginProtocolProvider } from "@executor/sdk"; export const ArtifactStoreProtocol = { key: "executor.artifacts.store", label: "Artifact Store", } as const satisfies PluginProtocolProvider<"executor.artifacts.store">; export type ArtifactRef = { projectId: string; ref: string; // branch, tag, or commit }; export type ArtifactProject = { id: string; kind: "workflow" | "component" | "ui" | "template" | "connector" | "document"; name: string; defaultBranch: string; currentCommit: string; }; export type ArtifactCommit = { id: string; message: string; parentIds: readonly string[]; createdAt: Date; }; export type ArtifactFileChange = | { type: "put"; path: string; contents: Uint8Array | string } | { type: "delete"; path: string }; export interface ArtifactStore { readonly createProject: (input: { kind: ArtifactProject["kind"]; name: string; }) => Effect.Effect; readonly readFile: (ref: ArtifactRef, path: string) => Effect.Effect; readonly writeFiles: (input: { projectId: string; baseRef: string; branch: string; message: string; changes: readonly ArtifactFileChange[]; }) => Effect.Effect; readonly diff: (input: { projectId: string; baseRef: string; headRef: string; }) => Effect.Effect; readonly forkProject: (input: { projectId: string; name: string; }) => Effect.Effect; } export type ArtifactsProtocolCtx = { readonly artifacts: ArtifactStore; }; ``` Provider plugin: ```ts // @executor/artifacts-local-git import { definePlugin } from "@executor/sdk"; import { ArtifactStoreProtocol, type ArtifactStore } from "@executor/artifacts"; export const artifactsLocalGitPlugin = (options: { rootDir: string }) => definePlugin(() => { const artifacts: ArtifactStore = makeIsomorphicGitArtifactStore(options); return { id: "artifacts-local-git" as const, provides: [ArtifactStoreProtocol], storage: () => ({}), protocols: () => ({ artifacts, }), }; })(); ``` Feature plugin: ```ts // @executor/workflows import { definePlugin, type PluginCtx } from "@executor/sdk"; import { ArtifactStoreProtocol, type ArtifactsProtocolCtx } from "@executor/artifacts"; type WorkflowCtx = PluginCtx; export const workflowsPlugin = definePlugin(() => ({ id: "workflows" as const, requires: [{ ...ArtifactStoreProtocol, reason: "persist workflow code" }], storage: () => ({}), extension: (ctx: WorkflowCtx<{}>) => ({ create: (input: { name: string; files: readonly ArtifactFileChange[] }) => Effect.gen(function* () { const project = yield* ctx.artifacts.createProject({ kind: "workflow", name: input.name, }); const commit = yield* ctx.artifacts.writeFiles({ projectId: project.id, baseRef: project.defaultBranch, branch: project.defaultBranch, message: `Create workflow ${input.name}`, changes: input.files, }); const manifest = yield* indexWorkflowCode({ projectId: project.id, ref: commit.id, artifacts: ctx.artifacts, }); return { project, commit, manifest }; }), }), })); ``` Executor composition: ```ts const executor = yield * createExecutor({ scopes: [userScope], adapter, blobs, plugins: [ artifactsLocalGitPlugin({ rootDir: ".executor-artifacts" }), workflowsPlugin(), ] as const, protocols: { "executor.artifacts.store": "artifacts-local-git", }, }); const workflow = yield * executor.workflows.create({ name: "Support triage", files: [ { type: "put", path: "artifact.json", contents: JSON.stringify({ schemaVersion: 1, kind: "workflow", entrypoints: ["src/workflows/support-triage.workflow.ts"], }), }, { type: "put", path: "src/workflows/support-triage.workflow.ts", contents: supportTriageWorkflowSource, }, { type: "put", path: "src/steps/summarize.ts", contents: summarizeStepSource, }, ], }); ``` Generated workflow code: ```ts // src/workflows/support-triage.workflow.ts import { createLinearIssue } from "../steps/linear"; import { summarizeTicket } from "../steps/summarize"; export async function supportTriage(input: SupportEmail) { "use workflow"; const summary = await summarizeTicket(input); if (summary.priority === "high") { return await createLinearIssue({ title: summary.title, body: summary.body, }); } return { status: "ignored", summary }; } ``` ```ts // src/steps/summarize.ts export async function summarizeTicket(input: SupportEmail) { "use step"; return await tools.openai.responses.create({ model: "gpt-5.1", input: `Summarize and classify: ${input.body}`, }); } ``` E2E flow: ```ts it.effect("creates a code-first workflow artifact and indexes a visual graph", () => Effect.gen(function* () { const executor = yield* createExecutor({ scopes: [testScope], adapter, blobs, plugins: [artifactsLocalGitPlugin({ rootDir: tempDir }), workflowsPlugin()] as const, protocols: { "executor.artifacts.store": "artifacts-local-git", }, }); const created = yield* executor.workflows.create({ name: "Support triage", files: [ { type: "put", path: "artifact.json", contents: JSON.stringify({ schemaVersion: 1, kind: "workflow", entrypoints: ["src/workflows/support-triage.workflow.ts"], }), }, { type: "put", path: "src/workflows/support-triage.workflow.ts", contents: supportTriageWorkflowSource, }, ], }); const source = yield* executor.artifacts.readFile( { projectId: created.project.id, ref: created.commit.id }, "src/workflows/support-triage.workflow.ts", ); expect(new TextDecoder().decode(source)).toContain('"use workflow"'); expect(created.manifest.entrypoints).toEqual(["src/workflows/support-triage.workflow.ts"]); expect(created.manifest.graph.nodes.map((node) => node.kind)).toContain("step"); }), ); ``` ## Todos - [ ] Rename protocol dependency keys to namespaced constants such as `executor.artifacts.store`. - [ ] Extend plugin dependency metadata so protocol constants can carry typed context fragments. - [ ] Add `PluginCtx` and support direct protocol fields such as `ctx.artifacts` without adding artifacts to base core ctx. - [ ] Add executor-level protocol provider selection config. - [ ] Fail startup when multiple providers satisfy a required protocol and no explicit provider selection exists. - [ ] Create `@executor/artifacts` protocol package. - [ ] Define `ArtifactStore`, artifact refs, commits, diffs, file changes, project metadata, manifest schema, and artifact errors. - [ ] Export `ArtifactStoreProtocol` and `ArtifactsProtocolCtx` from `@executor/artifacts`. - [ ] Create `@executor/artifacts-local-git` using `isomorphic-git`. - [ ] Add local Git provider tests for create project, write files, read files, diff, and fork. - [ ] Create `@executor/artifacts-cloudflare` provider against Cloudflare Artifacts. - [ ] Add Cloudflare provider support for repo creation, commit writes, refs, token handling, and clone/fetch/push flows. - [ ] Define required `artifact.json` root manifest format. - [ ] Add manifest validation shared by artifact providers/features. - [ ] Create `@executor/workflows` feature plugin. - [ ] Define code-first workflow conventions for `"use workflow"` entrypoints and `"use step"` functions. - [ ] Build a workflow code indexer that derives graph/manifest data from source files. - [ ] Add validation for workflow entrypoints, step references, tool calls, required connector metadata, and artifact manifest consistency. - [ ] Store derived `workflow.manifest.json` as an index, not canonical source. - [ ] Add workflow tools for create, update, validate, inspect, and preview. - [ ] Add code transform path for visual workflow edits. - [ ] Add run records that pin artifact project id, provider id, ref, and commit. - [ ] Defer full durable execution until build/runtime wiring for generated `"use workflow"` artifacts is clear. - [ ] Create `@executor/generative-ui` feature plugin after artifact protocol is stable. - [ ] Define generated UI/component manifests as derived indexes over source code. - [ ] Add preview/build/test pipeline hooks for generated UI artifacts.