neuron-explorer
Steering-first neuron explorer for Qwen3.5-4B: push every MLP neuron, keep every rollout, browse the results
The software for work.
Developer toolsTypeScript · CSSGitHub @opencx-labsOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Work, the open-source desktop app for getting work done with AI agents (work.software), and Catamorphic, the framework underneath for embedding agents, workflows and apps in your own products
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
The open-source core for agentic work environments. Catamorphic brings
projects, agents, documents, browser tabs, terminals, apps, and automations
into products whose identity and deployment belong to their host.
Work is the desktop, company brain, and mobile product
built on Catamorphic. It is designed for anyone with work to do. This repository
contains the framework and Work's reference applications.
Work supports Apple silicon Macs running macOS 12 or newer. The current
preview is the download on work.software, or:
After the first Work Stable is published:Read the full README ↗The open-source core for agentic work environments. Catamorphic brings projects, agents, documents, browser tabs, terminals, apps, and automations into products whose identity and deployment belong to their host.
Work is the desktop, company brain, and mobile product built on Catamorphic. It is designed for anyone with work to do. This repository contains the framework and Work's reference applications.
Work supports Apple silicon Macs running macOS 12 or newer. The current preview is the download on work.software, or:
brew install --cask opencx-labs/tap/work@alpha
After the first Work Stable is published:
brew install --cask opencx-labs/tap/work
You can also download the signed and notarized DMG from
GitHub Releases and drag
Work into Applications. Installed builds let you choose when to download and
restart, and a restart never waits on running agents: they continue where they
left off once Work reopens. Switch channels under Help > Update Channel, or
use brew upgrade with the same cask you installed. Work keeps a
pre-migration database backup.
Work uses a fresh application identity and storage directory. Alpha data and credentials from the previous app are not migrated automatically. Project folders remain ordinary files you can import.
The desktop app (apps/desktop) is a local-first workspace and
the framework's reference implementation. Claude Code, Codex, Hermes, and API
models can work side by side in durable sessions. Agents use the browser, terminals,
editors, project files, apps, and connectors you can also use yourself. You can
take over a surface at any moment, inspect diffs when a change deserves your
eyes, or trust routine work to continue.
Projects are ordinary folders and git repositories. Every agent turn that changes files creates a checkpoint commit, and any turn can be undone with its files. A session is a durable log of turns that every client folds the same way, so a turn whose machine stopped continues elsewhere on the agent's own conversation. Local execution uses an embedded database and local sandboxes, so the desktop does not depend on a hosted Catamorphic service.
Chat links and images from agents ask before reaching an untrusted host. Executable sidebar, palette and chip modules run confined on macOS and Linux with declared access to files, hosts and Work's lists. Work asks before their access grows, and keeps the person's approvals signed in their profile.
The Work server (apps/server) gives a personal or company
brain an always-on home. Install the pinned @catamorphic/work-server library
in your own Bun project, compose its hooks, and build your own image
(deployment guide). There is
no stock server image. It serves the mobile PWA, remote agents, project apps,
and MCP endpoints. Desktop and mobile clients sign in through OAuth with PKCE;
the Work server supports local credentials and configured OAuth or OIDC
providers. Invitations grant admission after sign-in, and committed project
roles decide what each member and agent may do.
The zero-service setup keeps PGlite, git origins, credentials, and project data
under one data directory. A deployment can opt into real Postgres as it grows.
The accepted multi-machine architecture uses server instances sharing one
Postgres and one authority, with machines exposed as permitted Environments.
Postgres mode shares origins, artifacts, credentials, auth, and leased execution.
Each Environment can choose its sandbox image, give agents their own Docker,
and limit which hosts they reach, with a wider list for its setup alone
(package registries) while agents reach only the gateway; an agent's
sandboxing decides what leaves its sandbox, apart from its harness's own
permission mode. Claude Code, Codex and ACP agents such as OpenCode run in
those sandboxes on the organization's model keys, held by the gateway. New model
calls require the turn's exact attempt and live lease; Git and HTTP
background access keep their normal grant lifetime. The gateway
can pin the providers an aggregator routes to; work that needs a machine can
wait for one, and machine rules can create machines on demand and remove
them when idle. See
the machine setup and recovery model.
The Work server is single-tenant because its local-process execution can access
the host machine. Run one trusted organization or household per deployment.
See the server guide or give an agent
skills/setup-work-server
to provision it.
Framework libraries publish to npm with the same version as Work. Preview
releases use the alpha tag:
bun add @catamorphic/server-sdk@alpha @catamorphic/fastify-plugin@alpha
Use one release version across @catamorphic/* dependencies. Stable releases
use npm's latest tag. The deployed Cloudflare bridge is a Worker application,
rather than an npm library.
The packages under packages/ let a host application mount
Catamorphic in-process without adopting Catamorphic's identity, deployment, or
design choices. A host supplies auth, users and organizations, database,
storage, execution providers, credentials, telemetry, skills, and doctrine.
The framework supplies general-purpose projects, git-native work tracking,
multi-harness coding agents, durable TypeScript workflows, sandboxed
user-built apps, generated API clients, headless React state, and composable
UI. See INTEGRATION.md.
Desktop and server are designed to compose. A linked project's files sync over git after settled turns. Desktop conversations mirror to the server, so the mobile PWA can open the same chat and continue with the server's always-on agent. If someone continues remotely, the server owns that conversation fork instead of pretending two writers have one history. Incognito desktop chats stay local and are never mirrored.
Projects can shape the shared experience without hard-coding company personas
into the app. Committed role files grant scoped artifacts and project
permissions such as program:write or sessions:read; the shared sidebar and
up to six New Tab starting actions can target the caller's resolved
permissions. If a project does
not configure an action, the desktop adds no placeholder or empty surface.
(ADR 0092)
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.
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
.work/project.json manifest, and hidden seed skills; nothing in
the visible tree claims the project is about code. The workflow/app
workspace (an independent Bun workspace under .work/: 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. Work never updates
the default branch of a repository it did not create, or any branch there
it did not make: sync fetches and fast-forwards, and local work reaches a
shared repository as a work/ branch plus a pull request. A repository
Work created itself syncs automatically (fetch, fast-forward, merge, push),
and a conflicting divergence lands on a rescue branch instead of a stuck
state. A code host acts through an ordinary connection: the person's own
account, or the organization's service connection (a GitHub App
installation), with tokens minted per call. GitHub is the first code host
(repository import, publish, pull requests, reviews). Agents get explicit
git verbs (sync_project, create_pull_request) rather than raw git.
(ADRs 0044,
0170,
0177)
One agent-session engine over pluggable harnesses: the built-in
@catamorphic/ai-sdk tool loop (any API model), Claude Code
(@catamorphic/claude-code), Codex (@catamorphic/codex), and any Agent
Client Protocol agent such as Hermes or OpenCode (@catamorphic/acp, on
the desktop or in a Work server's sandbox), 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. A chat can change agents across
harnesses while keeping its history, with a bounded handoff of missing context.
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 (real input, uploads, downloads, and a page's
console and network for web development), terminals, open_surface,
point_at. Local harnesses get the person's login-shell PATH, so they reach
the same tools as their terminal.
(ADRs 0038,
0042)
Concurrent sessions start in the same visible project checkout. Before a
turn, each agent sees the other active sessions in that project and can read
their bounded transcripts. It can keep sharing the folder, wait, or use the
same harness-neutral tools to create or adopt a Git worktree. Per-agent
coordination doctrine ranges from shared-first to isolation-required, so
non-technical roles can stay in one familiar folder while engineering agents
isolate only when needed. Shared sessions deliberately share files, Git
state, checkpoint commits, and rollback; Catamorphic does not pretend those
changes belong to one agent. (ADR
0063)
People choose too: the desktop's "New chat in a worktree", or the chat's
status popup, gives a chat its own worktree at the project folder's last
commit. Its first turn copies the ignored files .worktreeinclude lists and
runs the Environment's setup; archiving the chat puts the folder away on
its branch, and the popup brings its changes back into the project folder
or discards them. (ADR 0215)
Project agent definitions: an agent can be a work product. Committed
.work/agents/<slug>.json files (plus an optional .work/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)
Delegation is also first-class. A subagent works in an ordinary durable child session with explicit delegation authority, its own transcript, and the same message, interrupt, attention, and policy machinery as any other session. Its reports reach the parent's working turn automatically and are acknowledged there. Routine progress folds into expandable activity, with full reports and source chats one click away; failures and requests for your input stay visible. A result starts a new turn if the parent has finished. An agent starts a copy of itself, or another agent it is granted, on the model and effort the work needs, or read-only for a review (on harnesses with a read-only mode); grants in its definition limit whom it may start and what it may choose, and by default it may start copies of itself on any model and effort. Archive recursively stops active work only after reporting its impact, while keeping the session tree restorable and searchable. Session source records whether a conversation began on desktop, mobile, Slack, Claude, MCP, or API. (ADRs 0089, 0090, 0271)
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 (retry scopes), batch scopes, pauses
and buffered named signals fenced to a run generation, correlation keys, shared rate budgets, retention, and
triggers, including host-defined trigger kinds with typed payloads and
sync-until-first-wait firing, project-defined kinds, and declarative where
filters. Durable waits give back physical workspace capacity after teardown;
continuations use fresh admission and the same deployed revision. A boundary
callback and its returned host, document or connection call share one persisted
five-minute attempt deadline, with cancellation passed to providers. Accepted
external effects still need application reconciliation. Full authoring model
below.
Role access is separate from unattended consent. Each member previews and enables an exact deployed workflow with its trigger, Environment, agent, and connection requirements. Connecting an account may complete that chosen flow; it never silently enables every compatible automation.
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, assignable by agents when concurrent work needs isolation,
and a chat's own worktree is a palette command away, set up before its first
turn and brought back from the chat's status popup. Chats you start in Claude
Code or Codex outside Work can appear among the project's chats, ready to
continue with any agent, including another harness through a context handoff.
Checkout management is harness-neutral, so Claude Code, Codex,
and the built-in agent follow the same policy. Diff tabs render in Monaco;
changed files and pull requests are sources any sidebar section or palette
mode can list (Find changed files searches them); PR review opens per-file
diffs through the CodeHost seam. Terminals are real PTYs with shell
integration (OSC 133); the embedded browser, which installs Chrome
extensions from the Chrome Web Store, and the command palette round out the
shell. On macOS, Settings > Appearance lets you choose a custom Dock icon while
Work runs, shared by all profiles, and reset to the default at any time.
All of it degrades quietly for non-technical users.
(ADRs 0045,
0063,
0203)
The signed app ships the audited Claude Code and Codex adapters, then downloads each large, platform-specific executable only on first use. Every component is versioned and SHA-512 pinned by the app release, installed atomically, and reused offline. (ADR 0091)
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)
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 an external 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 | Local sandboxes (@catamorphic/microsandbox, or @catamorphic/container under gVisor on any Linux machine with Docker and gVisor), 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 external 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:
.work/skills/): the project model and on-demand
workspace scaffold (work-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 public
skills/setup-work-server
skill extends the same idea to Work server setup and integrating
Catamorphic into an existing app without replacing its auth or deployment.@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, connections, and code hosts (githubCodeHost over the github connection provider). |
@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, member review and consent, the Runs panel, 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): members' drafts as refs in the project origin (ADR 0191), local folder checkouts, pluggable origin remotes (RemoteBackend), and the remote sync engine (syncWithNetworkRemote: fetch and fast-forward; merge, push, and rescue branches only for repositories Work created), with the one push guard every network push passes (work/ branches only on attached repositories). |
@catamorphic/github | GitHub mechanics: OAuth + device-flow helpers, GitHub App auth (app JWTs, installation tokens, manifest registration), and the REST API client. The server SDK builds the github connection provider and code host on it. |
@catamorphic/parser | ts-morph AST → WorkflowGraph parser + dagre layout; also powers the seeded project check script. |
@catamorphic/sandbox | Vendor-neutral sandbox contracts (SandboxProvider, SandboxManager, RunExecutor), the stdio supervisor transport, OTel instrumentation, and plugin-doc staging and tool-policy helpers for harnesses. |
@catamorphic/agent-protocol | The agent session log (turns, attempts, items, requests, provider threads), its events, commands and shared reducer, and the runner protocol every harness adapter speaks (HarnessAdapter). |
@catamorphic/agent-runner | The harness-agnostic runner that drives one attempt of a turn on an adapter, in-process or over stdio. |
@catamorphic/runner-bundle | The runner and its Claude Code and Codex adapters as one hash-addressed file a sandbox runs with Bun or Node. |
@catamorphic/microsandbox | Local sandbox provider over the microsandbox SDK: the desktop's default execution. |
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).
SKIP LOCKED. Cloudflare Sandbox is the default cloud execution provider; backends ship as vendor plugin packages so hosts install only what they use.@opentelemetry/api only; 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,
defineStaticEnvironments,
} from "@catamorphic/server-sdk";
const sandboxProvider = new CloudflareSandboxProvider({
apiUrl: process.env.CLOUDFLARE_SANDBOX_API_URL!,
apiKey: process.env.CLOUDFLARE_SANDBOX_API_KEY,
});
const environmentProvider = defineStaticEnvironments([
{
descriptor: {
id: "local",
label: "Managed execution",
trust: "managed",
isolation: "sandbox",
workloads: ["agent", "workflow"],
agentTopologies: ["controller"],
capabilities: ["network.egress"],
resources: {},
},
sandboxProvider,
},
]);
// 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/microsandbox,
// @catamorphic/container, or @catamorphic/local-process
sandboxProvider,
environmentProvider,
});
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: "[email protected]" },
});
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
work-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.
Projects define their own kinds on top of the host's in .work/triggers/
(defineTrigger({ name, from: trigger("webhook", { ... }), where })), and
every binding may add a where filter the host checks before a run starts,
so a GitHub or Slack integration is project code on the webhook kind, whose
verification schemes and handshakes are declared in its config. The
slack-threads pack is the worked example, which an agent installs into a
project and adapts: a chat per Slack thread, replies posted back through a
gateway connection whose named operations are its capabilities
(Connect Slack).
The pull-request-reviews pack is the larger one: a code and
security review of every pull request, in a chat per pull request on a
dedicated review pool, verified by running the change and reported to a
workflow outside the sandbox that calls a deployment-owned publisher hook.
The hook serializes commit-bound reviews, check runs and replies through an
application Postgres ledger, reconciles uncertain writes, and leaves mutable
PR descriptions, dismissals and thread resolution to people
(Review pull requests).
See INTEGRATION.md and ADRs
0039 /
0042 /
0171 /
0179 /
/
.
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.
const enrollment = await client.runs.triggerProduction({
projectId, workflowName: "nurtureContact",
input: { contactId: "contact-42" },
correlationKey: "contact-42", // onConflict: "ignore" | "error" | "restart"
});
// Carry the enrollment generation in the callback to fence late events.
await client.runs.signalByKey({
projectId, workflowName: "nurtureContact",
correlationKey: "contact-42", runId: enrollment.id,
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",
});
A named signal may arrive before its matching pause. It buffers only for the
specified active run generation; the oldest matching signal resumes one pause.
The receipt reports buffered, resumed with a pause id, or discarded after
termination. Retry the same event with the same idempotency key and JSON value;
changing its name or value conflicts. Signals never create a run or follow a
restart. A receipt does not prove that business effects have completed.
Signal values are limited to 64 KiB, with 256 pending and 1,024 lifetime receipts per run. Cancellation or completion clears pending values; normal run retention also deletes receipts. For larger event streams, use an application-owned ledger. Targeted pause-id resumption remains available for explicit approvals.
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,
environmentProvider,
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.
Catamorphic is issue-first. If you do not have repository write access, open a GitHub issue with the problem, concrete use case, constraints, and desired outcome. Public pull request creation is restricted to the core team and collaborators with write access.
AI makes producing code cheap, but it does not make a large generated change cheap to review. We review the compact source of intent first, then maintainers own the implementation with the repository's full context. Read CONTRIBUTING.md for the reasoning and issue guidance.
bun install
# Only for an explicitly provisioned host database. These are not part of the
# normal local app workflow; never point them at a database another session uses.
DATABASE_URL="<host-owned database URL>" bun run db:migrate
DATABASE_URL="<host-owned database URL>" 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 generate
Optional shared OTel, ClickHouse, and sandbox-bridge services run persistently. Start them in a separate terminal only when you need them:
bun run dev:infra
The framework packages are embed-only. In production, compose your own Work
server from @catamorphic/work-server, or boot Catamorphic in an existing
application. For local
development, bun run dev starts the combined desktop and reference-server manual
environment. bun run dev:desktop and bun run dev:server are focused
variants of the same orchestrator, which assigns each worktree its own data
directories and loopback ports. To iterate on Catamorphic alongside your own
host, link the packages via file: (see
.agents/skills/using-catamorphic/SKILL.md and its "Local dev linking"
section).
bun install # Install dependencies in this worktree
bun run dev # Combined desktop and reference-server manual environment
bun run dev:desktop # Desktop-focused development environment
bun run dev:server # Stock-server-focused development environment
bun run dev:infra # Optional OTel, ClickHouse, and sandbox-bridge services
bun run build # Build all packages
bun run test # Deterministic root and Postgres-complete workspace tests
bun run test:external # Explicit opt-in for credentialed external integrations
bun run check # 11-phase merge gate
bun run typecheck # Typecheck all packages with bun check
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 migrations
Manual development profiles live under ~/.catamorphic/dev/<worktree-instance>.
The runner assigns separate desktop/server profiles and ports per worktree.
Legacy temporary profiles are copied on first launch, with the originals retained.
Do not put manual project databases in the operating system temporary directory.
Tests use Vitest, orchestrated by Turborepo. Docker must be running:
bun run test and bun run check create an isolated disposable Postgres
database for every invocation. They do not need bun run dev:infra; that
command is optional shared observability and sandbox-bridge infrastructure,
not test infrastructure.
bun run test # deterministic root and Postgres-complete workspace tests
bun run check # 11 phases: lint, root/workspace types, build, migrations, root/workspace tests, and E2E
bun run test:external # explicit authority for credentialed integrations
bun run --filter @catamorphic/parser test # one package through repository Node
bun run --cwd packages/parser test src/__tests__/parser.test.ts # one file through repository Node
Each test file uses fresh temporary state and every root test invocation gets
its own Postgres database and temporary caches, so parallel worktrees do not
share test output. Install dependencies in each worktree and never copy a
credentialed .env wholesale. Add only the individual settings a task needs.
Root orchestration tests under scripts/**/*.test.ts run before the package
graph. Package-local Vitest commands also select the repository-pinned Node,
including when the ambient node is Node 20.
External integration tests never run merely because credentials are present.
They require the explicit bun run test:external authority, which sets
CATAMORPHIC_EXTERNAL_INTEGRATIONS=1, and skip when their service-specific
configuration is absent:
packages/cloudflare, packages/core): needs CLOUDFLARE_SANDBOX_API_URL, CLOUDFLARE_SANDBOX_API_KEY, and CF_SANDBOX_INTEGRATION=1. Start the sandbox bridge separately if the test needs it.packages/cloudflare): needs CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_API_TOKEN, and CLOUDFLARE_ARTIFACTS_NAMESPACE; it skips with a warning while feature-gated.packages/s3): needs S3_BUCKET, S3_ACCESS_KEY_ID, and S3_SECRET_ACCESS_KEY. Set S3_REGION when the provider requires it, S3_ENDPOINT for R2, MinIO, or another non-AWS endpoint, and S3_FORCE_PATH_STYLE=true when that endpoint requires path-style requests.apps/desktop/e2e/google-sign-in.e2e.ts): takes the first step of Google's sign-in in a Work tab and fails when Google refuses Work. bun run test:external does not run it; run it in the desktop suite: CATAMORPHIC_EXTERNAL_INTEGRATIONS=1 bun run --cwd apps/desktop test:e2e e2e/google-sign-in.e2e.ts. CI runs it daily on macOS, with real Chrome's answer beside Work's (.github/workflows/google-sign-in.yml).Unit tests run with no setup. bun run --cwd apps/desktop test:e2e runs the
complete desktop suite in Docker, using real Electron on a private Xvfb display
with Openbox and a deterministic fake agent. Windows use normal focus and
rendering without reaching your desktop. The first run builds a cached Linux
image; subsequent runs reuse dependencies. File filters and Vitest shard flags
pass through. Logs, screenshots, and JUnit results go to test-results/.
bun run check includes this isolated suite. Native macOS coverage runs in CI.
See desktop testing for runner and sharding details.
PR checks reuse successful package and shard tasks by content hash. Changing a package invalidates its downstream consumers; local commands and main still execute tests fresh. See test caching and selective execution for cache inputs, failure handling, and execution summaries.
The local development runner uses POSIX process groups and supports macOS and Linux. Windows development orchestration is not supported.
bun check for typechecking, tsc for declarations@opentelemetry/api instrumentation throughoutDirection, not shipped. Tracked in TODO.md:
kind: "acp" with acp.command, such as Hermes); the remote endpoint
transport and ACP agents on the Work server are next.defineAgent: a typed authoring layer that compiles to the
committed .work/agents/<slug>.json substrate.@catamorphic/container | Sandboxes as OCI containers through the Docker Engine API, under gVisor (runsc) or runc: images, Dockerfile builds, CPU and memory limits, egress policy through a proxy socket, nested Docker, and volumes (ADR 0204). |
@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/hetzner | Hetzner Cloud client and machine provisioner behind the Work server's hetzner-cloud machine classes (ADR 0205). |
@catamorphic/ai-sdk | Built-in harness adapter: Vercel AI SDK tool loop on any API model, running in the host and driving the session's sandbox through its tools. |
@catamorphic/claude-code | Harness adapter backed by the Claude Code (Claude Agent SDK) CLI, with per-turn MCP servers, portable native state and full settings-source fidelity. |
@catamorphic/codex | Harness adapter backed by the pinned OpenAI Codex app-server protocol. |
@catamorphic/acp | Harness adapter for Agent Client Protocol agents (Hermes and others): runs the agent's own command and serves it Work's tools over MCP. |
@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. |