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.
@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.
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.
artifact.json
src/
tests/
fixtures/
preview/
Workflow artifact:
artifact.json
src/workflows/support-triage.workflow.ts
src/steps/
workflow.manifest.json
tests/workflow.test.ts
fixtures/sample-input.json
Generated UI artifact:
artifact.json
src/App.tsx
src/components/
src/styles.css
ui.manifest.json
tests/
preview/
Generated component artifact:
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.
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<ArtifactProject, ArtifactError>;
readonly readFile: (ref: ArtifactRef, path: string) => Effect.Effect<Uint8Array, ArtifactError>;
readonly writeFiles: (
input: WriteArtifactFilesInput,
) => Effect.Effect<ArtifactCommit, ArtifactError>;
readonly diff: (input: ArtifactDiffInput) => Effect.Effect<ArtifactDiff, ArtifactError>;
readonly forkProject: (
input: ForkArtifactProjectInput,
) => Effect.Effect<ArtifactProject, ArtifactError>;
}
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:
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, andworkflows.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.
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
ifstatements = branchesPromise.all= parallel branchessleep, 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:
- artifact protocol and provider
- workflow code conventions around
"use workflow"and"use step" - code indexer that derives a graph/manifest
- connector/action metadata for humans
- validation pipeline
- run records pinned to artifact commits
- 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:
// @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<ArtifactProject, ArtifactError>;
readonly readFile: (ref: ArtifactRef, path: string) => Effect.Effect<Uint8Array, ArtifactError>;
readonly writeFiles: (input: {
projectId: string;
baseRef: string;
branch: string;
message: string;
changes: readonly ArtifactFileChange[];
}) => Effect.Effect<ArtifactCommit, ArtifactError>;
readonly diff: (input: {
projectId: string;
baseRef: string;
headRef: string;
}) => Effect.Effect<ArtifactDiff, ArtifactError>;
readonly forkProject: (input: {
projectId: string;
name: string;
}) => Effect.Effect<ArtifactProject, ArtifactError>;
}
export type ArtifactsProtocolCtx = {
readonly artifacts: ArtifactStore;
};
Provider plugin:
// @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:
// @executor/workflows
import { definePlugin, type PluginCtx } from "@executor/sdk";
import { ArtifactStoreProtocol, type ArtifactsProtocolCtx } from "@executor/artifacts";
type WorkflowCtx<TStore> = PluginCtx<TStore, ArtifactsProtocolCtx>;
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:
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:
// src/workflows/support-triage.workflow.ts
import { createLinearIssue } from "../steps/linear";
import { summarizeTicket } from "../steps/summarize";
Lines 1–500 of 619. Load more
# 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<ArtifactProject, ArtifactError>; readonly readFile: (ref: ArtifactRef, path: string) => Effect.Effect<Uint8Array, ArtifactError>; readonly writeFiles: ( input: WriteArtifactFilesInput, ) => Effect.Effect<ArtifactCommit, ArtifactError>; readonly diff: (input: ArtifactDiffInput) => Effect.Effect<ArtifactDiff, ArtifactError>; readonly forkProject: ( input: ForkArtifactProjectInput, ) => Effect.Effect<ArtifactProject, ArtifactError>; } ``` 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<ArtifactProject, ArtifactError>; readonly readFile: (ref: ArtifactRef, path: string) => Effect.Effect<Uint8Array, ArtifactError>; readonly writeFiles: (input: { projectId: string; baseRef: string; branch: string; message: string; changes: readonly ArtifactFileChange[]; }) => Effect.Effect<ArtifactCommit, ArtifactError>; readonly diff: (input: { projectId: string; baseRef: string; headRef: string; }) => Effect.Effect<ArtifactDiff, ArtifactError>; readonly forkProject: (input: { projectId: string; name: string; }) => Effect.Effect<ArtifactProject, ArtifactError>; } 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<TStore> = PluginCtx<TStore, ArtifactsProtocolCtx>; 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";