A really good place to get work done. Catamorphic puts everything you need for real work in one place: browser, terminals, editors, notes, and agents that help on the same surfaces. It is free, open source, and local-first: your projects, notes, config, and agent state are files and databases you own, on your disk.
Catamorphic is one vision with two surfaces:
- The desktop app (
apps/desktop): a local-first workspace where AI agents (Claude Code, Codex, or any API model: bring your own, side by side) do real work on surfaces you can watch: browser tabs, terminals, editors. You can take over any surface at any moment, and anything an agent produces (code, prose, config) is inspectable on demand: diffs and track-changes when a change deserves your eyes, trust when it doesn't. Projects are plain folders; every agent turn is a git checkpoint; sync rides on git. The app is also the framework's reference implementation: a working demo of what embedders can build with the packages below. Read the product philosophy and decision log inapps/desktop/DESIGN.md. - The embeddable framework (everything under
packages/): the engine underneath, also usable standalone: embed AI-built work environments inside your own product. Projects that hold any kind of work, git-native work tracking, multi-harness coding agents, durable TypeScript workflows, sandboxed user-built apps, and the copilot plumbing (durable agent sessions, agent registry, drop-in chat components) that lets any product ship a real companion agent with the host's own skills and tools plugged in. SeeINTEGRATION.md.
Code is the source of truth. Everything is stored as plain files (TypeScript, markdown, whatever the work is) in a git repository, never a proprietary DSL or an opaque store. When a project holds workflows, the parser renders the workflow code as an intuitive visual graph for non-technical users, while technical users and AI agents work directly with the code. A project is just a git repo.
Greenfield: no production users yet. Nothing here is deployed to real users, so there is no installed base to preserve. Prefer the correct design over a compatible one: change schemas, rename APIs, and delete dead paths outright rather than adding migrations-on-migrations, compatibility shims, deprecation aliases, or feature flags to protect callers that do not exist. Breaking changes are cheap right now and get expensive the day we ship. Spend that budget while it is free. (This does not license skipping tests or leaving things half-finished; it is about not paying for backwards compatibility nobody needs.)
Six capabilities, co-equal. Each is shipped and verifiable in this repo; the ADR column is the settled design record.
A project is a folder that can hold documents, notes, data, plans, code,
automations, and apps, in any mix. A blank project is a git repository, a
.catamorphic/project.json manifest, and hidden seed skills; nothing in
the visible tree claims the project is about code. The workflow/app
workspace (a bun workspace: contracts/, workflows/, apps/*) is
scaffolded on demand, the first time someone asks for an automation or
app. Imported repositories are adopted as-is; existing files are never
overwritten. (ADRs 0032,
0043)
Work is tracked without anyone performing git. Every agent turn that
changed files ends in a checkpoint commit, its sha stamped on the chat
message, so every reply's diff is addressable forever. Linked projects
sync with their remote automatically (fetch, fast-forward, merge, push);
a conflicting divergence lands on a rescue branch instead of a stuck
state, so no work is ever stranded. Everything provider-specific sits
behind one CodeHost interface; GitHub is the first implementation
(connect, repo import, pull requests). Agents get explicit git verbs
(sync_project, create_pull_request) rather than raw git.
(ADR 0044)
One agent-session engine over pluggable harnesses: the built-in
@catamorphic/ai-sdk tool loop (any API model), Claude Code
(@catamorphic/claude-code), and Codex (@catamorphic/codex), selectable
per session and switchable mid-session, with a normalized effort scale.
Agents run either in a sandbox or directly on the host filesystem.
ask_user questions, durable sessions that survive restarts, and
per-profile agent rosters work across harnesses. MCP goes both directions:
agents consume MCP connectors (registry search, plugin marketplaces,
elicitation via @catamorphic/mcp), and a project's workflows are served
as MCP tools to any MCP client. In the desktop, agents also drive the
workspace itself: browser tabs, terminals, open_surface, point_at.
(ADRs 0038,
0042)
Project agent definitions: an agent can be a work product. Committed
agents/<slug>.json files (plus an optional agents/<slug>.md persona)
version with the project and appear in every collaborator's picker. A
committed definition never runs on your personal credentials until you
consent, and consent is bound to a hash of what you approved; definitions
using a project secret need no personal consent and work headlessly.
(ADR 0050)
TypeScript automations in git, rendered as a visual graph for
non-technical users, executed durably on Postgres. One model: every
workflow is an exported defineWorkflow value, every run executes a
deployed commit. Boundaries (atomic retry scopes), batch scopes, pauses
and signals, correlation keys, shared rate budgets, retention, and
triggers, including host-defined trigger kinds with typed payloads and
sync-until-first-wait firing. Full authoring model below.
Real frontends users (and agents) build on top of workflows: sandboxed
React bundles wired through a typed contract that cannot drift from the
workflows it calls (same repo, same commit). An app's callable workflow
set is frozen per published version and re-authorized on every call. Apps
interoperate with MCP Apps in both directions: the desktop renders MCP
Apps from connectors, and /projects/:id/apps-mcp serves your apps to MCP
hosts like Claude, unchanged. Apps get persistent app-local storage per
(app, user), and the @catamorphic/app/ui kit gives agent-built apps
polished, accessible components with zero CSS.
(ADRs 0035 through
0037,
0048)
Import a real monorepo and use the desktop as your daily driver. The
Claude Code harness runs at full fidelity: the SDK's own preset system
prompt, and the repo's CLAUDE.md, .claude/ skills, agents, commands, and
settings load exactly as in the CLI. Worktrees are first-class (discovered,
listed, diffed). Diff tabs render in Monaco; the sidebar has Changes and
Pull Requests sections; PR review opens per-file diffs through the
CodeHost seam. Terminals are real PTYs with shell integration (OSC 133);
the embedded browser and the command palette round out the shell. All of
it degrades quietly for non-technical users.
(ADR 0045)
Apps and agents inside an embedder's product are unmistakably the
embedder's. Feel: the app kit ships structure and behavior only; every
aesthetic decision flows from host theme tokens with neutral defaults,
plus hostCss and kit: false for total control. Doctrine: two
createCatamorphic hooks (projectSeeds, standingAgentPrompt) receive
the framework defaults and return the host-final set, so seeded skills
and the agents' standing prompt are both replaceable. The desktop
consumes the same hooks and passes nothing: the proof the defaults are
real defaults.
(ADRs 0048,
0049)
- The company brain. One shared project holds the team's docs, data, and automations. Teammates work in it through agents; committed project agents give everyone the same tuned personas; apps become the internal tools; git carries the history.
- A daily-driver dev shell. Import your monorepo. Your CLAUDE.md and
.claude/conventions load as-is, PRs and diffs are a sidebar click away, and worktrees, terminals, and the browser live in one window. - An embedded copilot inside a SaaS. Mount the framework in your backend, drop in the chat components, plug in your skills, tools, and trigger kinds. Your users get an agent that does real work in your product, with your look and your doctrine.
- AI-built per-customer tools over MCP. One project per customer; agents build the workflows and apps; each customer's tools are served over the project's MCP endpoint to whatever client they use.
- Docs-first teams that never see git. Projects full of notes and plans, checkpointed and synced automatically, with plain-language reporting from the agents. Nobody types a git command.
Catamorphic's engine ships as libraries a host application mounts
in-process. The desktop app is itself such a host. The host provides auth,
the user/org model, the database, and the deployment surface. There is no
default identity or tenant: every request carries identity from the host's
auth context. See INTEGRATION.md for the host
integration flow.
Durable agent-and-workflow infrastructure that does not assume a server. Every dependency is an axis with a heavy and a light end. Pick per axis:
| Axis | Heavy end | Light end |
|---|---|---|
| Database | Network Postgres ({ pool } / { connectionString }) |
Embedded pglite (Kysely PGliteDialect via database: { db }; migrations run statement-by-statement so single-connection dialects just work) |
| Execution | Cloud sandboxes: @catamorphic/cloudflare, @catamorphic/daytona |
Local sandboxes (@catamorphic/microsandbox), plain local processes (@catamorphic/local-process, trusted single-tenant hosts only), or none (read-only embed) |
| Code storage | S3-compatible bucket (@catamorphic/s3) or Cloudflare Artifacts |
Two writable directories |
| Identity | Host org/user per request | One fixed tenant/user |
| Surface | HTTP API + React UI | In-process SDK calls, or migrations-only |
The desktop app is the proof: it runs the lightest column end to end
(pglite, local sandboxes, filesystem storage, no server) by design
(apps/desktop/src/main/server/boot.ts).
No durable-execution vendor can run entirely inside a desktop app; this one
does, and the same substrate is what offline-first agents need. Full matrix
and host shapes: INTEGRATION.md.
Also worth knowing, because it's easy to miss from the package list:
- The product teaches agents from the inside. Every project is seeded
with hidden skills (
.agents/skills/): the project model and on-demand workspace scaffold (catamorphic-projects), workflow authoring (writing-workflows,batch-workflows,durable-workflows), and app building split into mechanics (building-apps) and replaceable design doctrine (designing-apps). Coding agents learn Catamorphic's authoring model at the moment they need it. The publicskills/embed-catamorphicskill extends the same idea to integrating Catamorphic itself. - One Run model. Boundary and batch workflows all share the same Runs API, hooks, and UI: capabilities, not categories. Every run executes a deployed commit.
- Observability is free for hosts. Everything instruments against
@opentelemetry/api; register your SDK and Catamorphic's spans appear in your traces.
| Package | What it is |
|---|---|
@catamorphic/server-sdk |
The core SDK for your Node/Bun backend. Takes a Postgres connection (or pg.Pool), manages its own schema-scoped tables and migrations, and exposes projects, workflows, files, runs, triggers, agent sessions, and GitHub. |
@catamorphic/fastify-plugin |
A mountable Fastify plugin (app.register(catamorphicPlugin, { core, prefix: "/api" })) exposing the standard HTTP API for frontends, plus the per-project MCP endpoints. Also exports a standalone createApp factory for sidecar deployments. |
@catamorphic/react |
Headless React bindings: CatamorphicProvider, TanStack Query hooks, and jotai atoms. Build a fully custom UI on top of these. |
@catamorphic/ui |
Ready-made components: the React Flow workflow canvas, detail panel, history sidebar, AI bar, and AppMount (the sandboxed app iframe host). Every piece is opt-in. |
@catamorphic/registry |
shadcn-style copy-paste components for hosts that want to own and customize the component source (project browser, git panel, runs panel, agent chat, Monaco editor). |
@catamorphic/api-client |
Generated OpenAPI types + openapi-fetch client for the HTTP API. |
@catamorphic/workflow |
Typed workflow-authoring primitives. Projects opt in directly, or a SaaS can wrap it and re-export only its approved surface. |
@catamorphic/app |
The guest-side app runtime bundled into every user-built app: typed workflow client, persistent app-local storage shim, dual-dialect MCP Apps support, and the @catamorphic/app/ui component kit styled entirely by host theme tokens. |
Supporting packages (consumed through the surface above, importable directly for advanced wiring):
| Package | What it is |
|---|---|
@catamorphic/core |
Framework-agnostic service layer: projects, workflows, runs, deployments, triggers, apps, app storage, plugins, secrets, agent sessions, agent definitions, remote sync, and the CodeHost seam. The kernel behind server-sdk and fastify-plugin. |
@catamorphic/db |
Kysely + Postgres. Schema-scoped (default schema catamorphic), raw SQL migrations, programmatic migrateToLatest. |
@catamorphic/git |
Git-backed project storage (isomorphic-git): per-user working copies, pluggable origin remotes (RemoteBackend), and the remote sync engine (syncWithNetworkRemote: fetch, fast-forward, merge, push, rescue branches). |
@catamorphic/github |
GitHub as a code host: OAuth + device-flow auth helpers, the REST API client, token stores. Feeds core's GithubService and its CodeHost implementation. |
@catamorphic/parser |
ts-morph AST → WorkflowGraph parser + dagre layout; also powers the seeded project check script. |
@catamorphic/sandbox |
Vendor-neutral sandbox + coding-agent contracts (SandboxProvider, SandboxManager, RunExecutor, CodingAgentProvider), the stdio supervisor transport, OTel instrumentation. |
@catamorphic/microsandbox |
Local sandbox provider over the microsandbox SDK: the desktop's default execution. |
@catamorphic/local-process |
Sandboxless execution as plain subprocesses with an explicit env. Trusted single-tenant hosts only (ADR 0047). |
@catamorphic/cloudflare |
Cloudflare backend plugin: CloudflareSandboxProvider (execution via Bridge Worker) + ArtifactsRemoteBackend (Cloudflare-native code storage when available). |
@catamorphic/s3 |
S3-compatible git origin backend for Cloudflare R2, AWS S3, MinIO, and similar stores. |
@catamorphic/daytona |
Daytona backend plugin: DaytonaSandboxProvider + experimental Daytona git storage. |
@catamorphic/ai-sdk |
Built-in coding-agent harness: Vercel AI SDK tool loop on any API model, running in the host and driving the dev sandbox remotely. |
@catamorphic/claude-code |
Coding-agent harness backed by the Claude Code (Claude Agent SDK) CLI, with per-session MCP servers and full settings-source fidelity. |
@catamorphic/codex |
Coding-agent harness backed by the OpenAI Codex SDK. |
@catamorphic/mcp |
MCP client infrastructure: both protocol generations with auto-negotiation, elicitation, the official MCP registry search, and plugin-marketplace install. |
@catamorphic/otel |
Tiny OpenTelemetry helpers (@opentelemetry/api only: the host owns the SDK/exporters). |
@catamorphic/runtime |
Execution harness that runs inside the sandbox and reports step results. |
@catamorphic/plugins |
Plugin manifest contract + resolvers for host-provided packages and secrets. |
@catamorphic/cloudflare-sandbox-bridge |
Deployable Cloudflare Worker exposing Cloudflare Sandbox over HTTP. |
The in-repo reference host is the Catamorphic desktop app (apps/desktop): an Electron app that embeds the server in-process (src/main/server/boot.ts).
- Workflows are regular code. User-defined workflows run like normal apps: full IO, real npm dependencies, no crippled JS runtime. Execution happens inside a sandbox (or a local process, where the host shape allows it) using Bun to run and bundle.
- Code stays simple. Both AI agents and humans must be able to write, edit, and understand workflows, and the parser must render them intuitively for non-technical users. See the code format below.
- Host-injectable everything. Database connections, storage backends, sandbox credentials, LLM credentials, telemetry, trigger kinds, seeds, and doctrine are all injected by the host: nothing is hard-coded.
- Postgres is authoritative for runs, retries, pauses, batch-item state, queues, and scheduling via
SKIP LOCKED. Cloudflare Sandbox is the default cloud execution provider; backends ship as vendor plugin packages so hosts install only what they use. - OpenTelemetry throughout. Libraries instrument against
@opentelemetry/apionly; the host registers the SDK and exporters and gets full traces for free.
Settled design decisions are recorded as ADRs in docs/decisions/.
import { CloudflareSandboxProvider } from "@catamorphic/cloudflare";
import { createCatamorphic } from "@catamorphic/server-sdk";
// Boot once per process
const catamorphic = createCatamorphic({
database: { connectionString: process.env.DATABASE_URL! }, // or { pool }
storage: {
projectsPath: process.env.CATAMORPHIC_PROJECTS_PATH!,
remotesPath: process.env.CATAMORPHIC_REMOTES_PATH!,
},
// Backend plugins: @catamorphic/cloudflare, @catamorphic/daytona,
// @catamorphic/microsandbox, or @catamorphic/local-process
sandboxProvider: new CloudflareSandboxProvider({
apiUrl: process.env.CLOUDFLARE_SANDBOX_API_URL!,
apiKey: process.env.CLOUDFLARE_SANDBOX_API_KEY,
}),
});
await catamorphic.migrate(); // idempotent, schema-scoped
// Worker startup is explicit and host-owned.
const executionWorker = catamorphic.startExecutionWorker({ concurrency: 4 });
// Per request: bind the host's org + user
const client = catamorphic
.forTenant({ tenantId: orgId })
.forUser({ externalUserId: userId });
const project = await client.projects.create({ name: "Onboarding" });
const run = await client.runs.triggerProduction({
projectId: project.id,
workflowName: "welcomeUser",
input: { email: "ada@example.com" },
});To expose the HTTP API to your frontend:
import { catamorphicPlugin } from "@catamorphic/fastify-plugin";
app.register(catamorphicPlugin, { core: catamorphic.core, prefix: "/api" });And on the frontend, wrap your tree with CatamorphicProvider from @catamorphic/react and drop in WorkflowEditor from @catamorphic/ui (or build your own UI from the hooks). See INTEGRATION.md.
There is one public Workflow model and one public Run model: every workflow is
an exported defineWorkflow(({ defineBoundary, defineBatch }) => ({ steps: [...] })) value from @catamorphic/workflow, and every run executes a
deployed commit.
A boundary is one atomic retry scope: if its callback fails, all operations in
that callback retry together. Orchestration code lives in boundary run
bodies; IO and business operations live in "use step" functions: plain
async functions with the exact "use step" directive, called from boundary
bodies. All functions take one destructured object parameter and carry JSDoc
display metadata.
import { type BoundaryContext, defineWorkflow } from "@catamorphic/workflow";
/**
* @displayname Welcome New User
* @description Onboard a new user
*/
export const welcomeUser = defineWorkflow(({ defineBoundary }) => ({
steps: [
defineBoundary({
run: async ({
input,
}: BoundaryContext<{ email: string; name: string }>) => {
const user = await createUser({ email: input.email, name: input.name });
await sendWelcomeEmail({ to: user.email, name: user.name });
if (user.plan === "premium") {
await assignPremiumBenefits({ userId: user.id });
}
await sendFollowUpEmail({ to: user.email });
return { status: "complete", userId: user.id };
},
}),
],
}));A batch scope is paged per-item processing with an optional sink. defineBatchStep is a
physical coalescing primitive for compatible calls inside defineBatch.process;
it does not define a Workflow or a separate logical step scope.
import { defineWorkflow } from "@catamorphic/workflow";
export const processAccount = defineWorkflow(
({ defineBoundary, defineBatch }) => ({
steps: [
defineBoundary<{ accountId: string }, { accountId: string }>({
retry: { maxAttempts: 3 },
run: async ({ input }) => prepareAccount({ accountId: input.accountId }),
}),
defineBatch({
source: async ({ input }: { input: { accountId: string } }) => ({
source: recordsSource,
config: { accountId: input.accountId },
}),
process: async ({ item }: { item: AccountRecord }) =>
processRecord({ record: item }),
sink: resultSink,
}),
],
}),
);The parser and UI expose capabilities such as batch processing and cancellation. There is no public stage concept, category switch, or separate Run family for these capabilities. A workflow with no pause, retry backoff, rate limit, batch, or child call settles inline when triggered synchronously: inline request-response is a property of execution, not a separate authoring form.
Workflows subscribe to trigger kinds in code (triggers: [trigger("kind", config)]). Hosts define their own kinds with
defineTriggerKind (typed payloads and configs via zod, generated
catamorphic-triggers.d.ts per project) and fire them sync or async; a kind
whose payload varies per workflow uses typed holes, and kinds declared via
mcpToolKinds are served as MCP tools from POST /projects/:id/mcp. See
INTEGRATION.md and ADRs
0039 /
0042.
A run may carry a correlation key: a host-meaningful identity for its
subject (a contact, an account, a subscription). It is unique among live runs of
the same workflow, which makes it at once an enrollment idempotency key and the
address external events use to reach the run. A boundary declares the shared
third-party budgets it draws on; buckets are keyed per tenant, so every workflow
naming the same globalKey draws on one budget.
export const nurtureContact = defineWorkflow(({ defineBoundary }) => ({
controls: { cancel: true },
steps: [
defineBoundary({
rateLimits: [
{ globalKey: "email", capacity: 500, refillRatePerSecond: 100 },
],
run: async ({ input, pause }: BoundaryContext<{ contactId: string }>) => {
await sendWelcomeEmail({ contactId: input.contactId });
return pause<{ clicked: boolean }, { contactId: string }>({
signal: "reply",
timeout: "72h",
state: { contactId: input.contactId },
});
},
}),
defineBoundary({
// A different step, a different provider, a different budget.
rateLimits: [
{ globalKey: "whatsapp", partitionKey: "sender-1", capacity: 80, refillRatePerSecond: 20 },
],
run: async ({ input }) => sendWhatsApp({ contactId: input.state.contactId }),
}),
],
}));// Enrolling twice for the same contact is a no-op, so webhook redelivery is safe.
await client.runs.triggerProduction({
projectId, workflowName: "nurtureContact",
input: { contactId: "contact-42" },
correlationKey: "contact-42", // onConflict: "ignore" | "error" | "restart"
});
// External events address the contact, not a run id.
await client.runs.signalByKey({
projectId, workflowName: "nurtureContact",
correlationKey: "contact-42",
signal: "reply", idempotencyKey: replyEventId, value: { clicked: true },
});
// Opting out ends the journey wherever it sits. Resolves null if none was live.
await client.runs.cancelByKey({
projectId, workflowName: "nurtureContact",
correlationKey: "contact-42", reason: "unsubscribed",
});Waiting for capacity is not failure: a boundary that cannot reserve is
rescheduled without consuming a retry and without holding a sandbox. When a
provider answers with a 429, report it with rateLimited({ retryAfterMs }):
that blocks every workflow sharing the account, not just the run that hit it.
Hosts bound what any one tenant may consume from these shared resources via
catamorphic.tenantPolicies. It is SDK-only and never exposed over HTTP, so a
tenant cannot raise its own limits:
await catamorphic.tenantPolicies.upsert({
tenantId,
maxConcurrentJobs: 32, // ceiling on simultaneously leased jobs
maxActiveRuns: 50_000, // ceiling on live runs; bounds enrollment fan-out
queueWeight: 4, // relative share of each claim batch
retentionDays: 365, // overrides the installation retention window
rateLimitOverrides: { // can tighten an author's bucket, never loosen it
whatsapp: { capacity: 40, refillRatePerSecond: 10 },
},
});Finished runs are purged after 90 days by default, along with everything hanging off them: jobs, events, step attempts, batch items and their steps. Without this, a daily 100k-item batch adds on the order of 1.5M rows a day and never gives any back.
The sweep runs inside the execution worker, so it needs no wiring. Change the window, or turn it off entirely, at construction:
const catamorphic = createCatamorphic({
database,
storage,
retention: { runRetentionDays: 30 }, // or { enabled: false } to keep forever
});Individual tenants can be given a longer or shorter window with
retentionDays above.
See ADR 0027, ADR 0028, and ADR 0030.
bun install
# Start local dev services: Postgres, plus an OTel collector (:4317/:4318)
# backed by ClickHouse (:8124 HTTP, :19001 native) for trace storage
docker compose up -d
# Apply migrations + regenerate Kysely types (scoped to the `catamorphic` schema)
DATABASE_URL="postgresql://catamorphic:catamorphic@localhost:5432/catamorphic" bun run db:migrate
DATABASE_URL="postgresql://catamorphic:catamorphic@localhost:5432/catamorphic" bun run db:codegen
# Build everything
bun run build
# Regenerate the OpenAPI spec + typed api-client after route/DTO changes
cd packages/fastify-plugin && bun run generate-spec
cd ../api-client && bun run generateCatamorphic itself is embed-only: in production you run a host app that boots it in-process. For local development, the root bun run dev boots the dev infra: docker compose up -d --wait (Postgres + OTel collector + ClickHouse) plus the Cloudflare sandbox bridge (:8787). The desktop app runs with bun run dev:desktop. To iterate on catamorphic alongside your own host instead, link the packages via file: (see .cursor/skills/using-catamorphic/SKILL.md → "Local dev linking").
bun run dev # Dev infra: docker compose (Postgres, OTel, ClickHouse) + sandbox bridge
bun run dev:desktop # Run the desktop app in dev mode
bun run build # Build all packages
bun run test # Run all tests
bun run typecheck # Typecheck all packages with tsgo
bun run lint # Lint with Biome
bun run lint:fix # Auto-fix lint issues
bun run db:migrate # Apply migrations to the DB pointed at by DATABASE_URL
bun run db:codegen # Regenerate Kysely types from the `catamorphic` schema
bun run db:reset # Drop + recreate the catamorphic schema (dev only)
bun run db:status # Show applied / pending migrationsTests use Vitest, orchestrated by Turborepo.
bun run test # everything
bun run --filter @catamorphic/parser test # one package
cd packages/parser && bun run test src/__tests__/parser.test.ts # one fileIntegration tests hit the real services using the keys in the repo root .env (loaded automatically by the vitest config) and skip themselves when credentials are absent:
- Daytona (
packages/daytona): runs wheneverDAYTONA_API_KEYis set. - Cloudflare Sandbox (
packages/cloudflare,packages/core): needsCLOUDFLARE_SANDBOX_API_URLplus the explicit opt-inCF_SANDBOX_INTEGRATION=1(start the bridge first:bun run devinpackages/cloudflare-sandbox-bridge). - Cloudflare Artifacts (
packages/cloudflare): runs whenever theCLOUDFLARE_ACCOUNT_ID/CLOUDFLARE_API_TOKEN/CLOUDFLARE_ARTIFACTS_NAMESPACEkeys are set and the account has Artifacts beta access; skips with a warning while feature-gated.
Unit tests run with no setup. The desktop app additionally has an e2e suite
(cd apps/desktop && bun run test:e2e) that drives the real Electron binary
over CDP with a deterministic fake agent.
- Bun: runtime, package manager, bundler (also inside sandboxes)
- TypeScript: tsgo for typechecking
- Postgres: all state, schema-scoped; run queues, retries, pauses, and scheduling use the same DB
- Electron + electron-vite: the desktop reference implementation
- Fastify: HTTP surface with Zod + OpenAPI (mounted by the host)
- React Flow: workflow visualization; Jotai + TanStack Query: frontend state
- ts-morph: TypeScript AST parsing
- Kysely: type-safe SQL
- isomorphic-git: portable git, no native CLI dependency (system git is a soft dependency for the desktop's read surfaces)
- OpenTelemetry:
@opentelemetry/apiinstrumentation throughout - Turborepo: build orchestration
Direction, not shipped. Tracked in TODO.md:
- A self-hostable reference server: a stock Catamorphic server people run on their own infra, with remote agents and MCP endpoints, users and roles, and internal + external users. The desktop would connect to it and call workflows/MCPs remotely.
- ACP harness: project agent definitions already accept
kind: "acp"; the Agent Client Protocol client (local command and remote endpoint transports) is the planned harness behind it. - TS
defineAgent: a typed authoring layer that compiles to the committedagents/<slug>.jsonsubstrate. - Review-mode collaboration: PR-first sync for shared projects, invites, native PR review depth (comments, approvals, merge).
- Remote blob storage: user-connected stores (S3/R2/Drive-style) for large binaries beside git-tracked text.
