acp-runtime
The ACP runtime is the domain service that serves the ACP API contract. It owns the host-scoped dependencies needed to run provider ACP sessions, but it should…
Emdash is a desktop app. Users may run an older version for weeks or months before updating. JSON blobs stored in SQLite columns must remain readable by any app version that could encounter them, so schema evolution must be explicit and backward compatible.
How it fires
How this agent gets triggered: by you, by Claude, or both.
Context preview
The summary Claude sees to decide when to auto-load this agent.
Emdash is a desktop app. Users may run an older version for weeks or months before updating. JSON blobs stored in SQLite columns must remain readable by any app version that could encounter them, so schema evolution must be explicit and backward compatible.
Emdash is a desktop app. Users may run an older version for weeks or months before updating. JSON blobs stored in SQLite columns must remain readable by any app version that could encounter them, so schema evolution must be explicit and backward compatible.
The versioned schema system handles this transparently:
the latest version is reached.
`future-version` result so the older app can degrade gracefully rather than corrupt.
production only the upgrade chain runs (no re-validation cost).
Add a `VersionedSchema` whenever:
If the column stores a plain non-structured string (e.g. a path, a status label, or a serialized non-JSON value), a versioned schema is not necessary.
| File | Purpose | |------|---------| | `packages/core/src/primitives/versioned-schema/api/versioned-schema.ts` | Core utility: `VersionedSchema`, `defineVersionedSchema`, `ParseResult` (imported as `@emdash/core/primitives/versioned-schema/api`) | | `src/core/services/app-db/node/versioned-column.ts` | Drizzle integration: `versionedJsonColumn`, `parseVersionedColumn`, `serializeVersionedColumn` |
Schema definitions live in the owning `src/core/primitives/<domain>/api/` slice so they can be imported by both Node and browser surfaces.
Use `.initial()` if the stored JSON always had a `version` field from the start:
// src/core/primitives/my-domain/api/my-config.ts
import z from 'zod';
import { defineVersionedSchema } from '@emdash/core/primitives/versioned-schema/api';
const v1Schema = z.object({
version: z.literal('1'),
name: z.string(),
});
export const myConfig = defineVersionedSchema()
.initial('1', v1Schema)
.build();
export type MyConfig = typeof myConfig.Type;Use `.unversioned()` when the column was first written before the versioning system existed (the data has no `version` field):
// src/core/primitives/my-domain/api/my-config.ts
const v0Schema = z.object({
name: z.string(),
value: z.number().optional(),
});
export const myConfig = defineVersionedSchema()
.unversioned(v0Schema)
.build();
export type MyConfig = typeof myConfig.Type;Chain `.version()` to add a new version. The upgrade function receives the **validated** previous-version object and must return the new-version object, or `null` if external context is required to upgrade:
const v2Schema = z.object({
version: z.literal('2'),
name: z.string(),
label: z.string(), // new required field
value: z.number().optional(),
});
export const myConfig = defineVersionedSchema()
.unversioned(v0Schema)
.version('2', v2Schema, (v0) => ({
version: '2' as const,
name: v0.name,
label: v0.name, // derive from existing data
value: v0.value,
}))
.build();Return `null` from an upgrade function when the caller must supply a context value not available in the stored data. `safeParse()` will return `{ status: 'needs-context' }`.
In `src/core/services/app-db/node/schema.ts`, replace `text('col_name')` with `versionedJsonColumn`:
import { versionedJsonColumn } from '@core/services/app-db/node/versioned-column';
import { myConfig } from '@core/primitives/my-domain/api/my-config';
export const myTable = sqliteTable('my_table', {
// Before:
// col: text('col'),
// After:
col: versionedJsonColumn(myConfig)('col'),
});Drizzle infers the TypeScript type as `MyConfig | null` for both reads and writes. No `JSON.parse` or `JSON.stringify` is needed at any call site.
After wiring `versionedJsonColumn`, remove any manual serialization in write paths and any manual parsing in read paths:
// Before
await db.update(myTable).set({ col: JSON.stringify(value) });
const parsed = JSON.parse(row.col) as MyConfig;
// After
await db.update(myTable).set({ col: value });
const parsed = row.col; // already MyConfig | nullUse `.asNested()` to embed one versioned schema as a field of another Zod object. This allows parent upgrade functions to call child upgrade logic automatically:
import { childConfig } from '@core/primitives/my-domain/api/child-config';
const parentV1Schema = z.object({
version: z.literal('1'),
child: childConfig.asNested().optional(),
});
export const parentConfig = defineVersionedSchema()
.initial('1', parentV1Schema)
.build();> **Note**: `asNested()` uses Zod's `.transform()` internally. Parent schemas that > use it cannot be validated with `z.encode()`.
When you need fine-grained control (e.g. snapshot columns that bypass Drizzle `customType`), call `parseJson()` or `safeParse()` directly:
// parseJson: convenience wrapper for JSON string columns
const data = myConfig.parseJson(row.rawJsonString); // MyConfig | null
// safeParse: discriminated union with full detail
const result = myConfig.safeParse(parsed);
if (result.status === 'ok') { /* result.data: MyConfig */ }
if (result.status === 'needs-context') { /* result.version, result.raw */ }
if (resultEmdash is the Open-Source Agentic Development Environment (🧡 YC W26). Run multiple coding agents in parallel. Use any provider.
Repo: generalaction/emdash
The ACP runtime is the domain service that serves the ACP API contract. It owns the host-scoped dependencies needed to run provider ACP sessions, but it should…
This page defines the target organization of `packages/core/src/`. Core is organized by module type so that shared domain APIs and their platform…
Git is split into a transport contract and a host-scoped runtime. Renderer, desktop, and workspace-server code share the Wire vocabulary without importing Git…
The main process is organized into domain modules under `src/main/core/`. Each domain typically has a `controller.ts` (RPC handlers) and service/implementation…
`@emdash/core/primitives/path/api` is the source of truth for portable file identity and lexical path operations. The detailed package docs live in…