Skip to content
Agent Workspaces
Agent

main-patterns

Each domain exposes a Wire contract in `src/core/features/<domain>/api/` and a controller in `src/core/features/<domain>/node/`:

From plugin
emdash
6.2k33 skills33 agents

Agent definition

main-patterns.md

Main Process Patterns

Controller Pattern

Each domain exposes a Wire contract in `src/core/features/<domain>/api/` and a controller in `src/core/features/<domain>/node/`:

// src/core/features/tasks/node/wire-controller.ts
export function createTasksWireController(): Controller {
  return createController(tasksWireContract, {
    createTask: (input) => taskOperations.createTask(input),
    events: taskEvents,
  });
}

Contracts are assembled in `src/core/manifests/shared/desktop-wire-contract.ts`; controllers are served by `src/main/gateway/desktop-wire.ts`.

**Rules:**

  • Controller handlers delegate to imported operations or services.
  • Keep portable contracts in `api/` and Node implementation in `node/`.
  • Register new domains in the desktop Wire manifest and gateway.

Service Pattern

Construct stateful services explicitly at the bootstrap composition root:

export type AppServiceDeps = {
  db: AppDb;
};

export class AppService {
  constructor(private readonly deps: AppServiceDeps) {}

  private cache = new Map();

  async initialize() { /* ... */ }
  async doSomething(id: string) { /* ... */ }
}

export function createAppService(deps: AppServiceDeps): AppService {
  return new AppService(deps);
}

**Rules:**

  • Factories receive explicit dependencies; lifecycle-owning services also receive a parent `Scope`.
  • Construct services in `src/main/bootstrap/boot/phases/` and pass them through controller context.
  • Do not export constructed service instances from Core modules.
  • Main-only free-function code may use a throwing instance holder as a temporary migration bridge.
  • Initialization is called after construction in the owning boot phase; disposal belongs to the

service scope or shutdown coordinator.

  • Services hold long-lived state (caches, subscriptions, connections)

Database Access

Portable schema and database types live in `src/core/services/app-db/node/`. Main constructs the native Drizzle client during the database boot phase.

  • Core Node services receive `AppDb` through their factory or controller options.
  • Main free-function code resolves the initialized database through `@main/db/instance` at call

time; never call `getAppDb()` at module scope.

  • Tests inject a fixture database directly or install it through the test instance helper.

Provider Pattern

For domain logic with multiple backends (local vs SSH):

src/main/core/projects/
├── project-provider.ts          # Interface
├── impl/
│   ├── local-project-provider.ts
│   └── _ssh-project-provider.ts  # Prefixed with _ = not yet implemented
└── project-manager.ts           # Orchestrates providers

Used in: projects, filesystem (`local-fs.ts` / `ssh-fs.ts`), terminals (`local-terminal-provider.ts` / `ssh-terminal-provider.ts`)

Result Type (`src/main/lib/result.ts`)

Explicit error handling via discriminated union:

import { ok, err, type Result } from '../lib/result';

async function doSomething(): Promise<Result<Data, SomeError>> {
  if (problem) return err({ type: 'not_found' as const });
  return ok(data);
}

**Rules:**

  • Prefer `Result<T, E>` over thrown exceptions for expected failure modes
  • Controllers expose Result types through Wire contracts

Event Streams

Use Wire event-stream hosts for main-to-renderer notifications:

export const taskEvents = createEventStreamHost(tasksWireContract.events);
taskEvents.emit(undefined, { type: 'created', task });

Event definitions belong to the owning slice's portable API. Use live models for replicated state and live jobs for cancellable long-running work.

Child Processes

Run background commands through the shared launchers: `createBoundExec` / `NodeExecutionContext` (`packages/core/src/services/exec/api/`) or a direct `child_process` call that plans the command with `planExecutableLaunch` (`packages/core/src/primitives/exec/node/`).

**Rules:**

  • Spawn every background child with `windowsHide: true`. Emdash has no console on Windows, so a

console program (git, `where`, cmd.exe running a `.cmd` shim) spawned without it gets its own console window, which Windows 11 shows as a Windows Terminal tab.

  • Only launches of user-facing apps (open-in editors, external terminals) omit the flag, because

hiding also applies to the launched app's first window.

  • Interactive terminals and agent TUIs use the PTY plane instead (see `agents/risky-areas/pty.md`).
Read more
@generalaction
Ships withemdash

Emdash is the Open-Source Agentic Development Environment (🧡 YC W26). Run multiple coding agents in parallel. Use any provider.

Stars on GitHub

Momentum

stars in total
6.2k
Star history of generalaction/emdash on GitHub, a chart by star-history.com

Other agents on emdash.

acp-runtime

acp-runtime

Agent

The ACP runtime is the domain service that serves the ACP API contract. It owns the host-scoped dependencies needed to run…

core-modules

core-modules

Agent

This page defines the target organization of `packages/core/src/`. Core is organized by module type so that shared domain APIs…

git-runtime

git-runtime

Agent

Git is split into a transport contract and a host-scoped runtime. Renderer, desktop, and workspace-server code share the Wire…

main-process

main-process

Agent

The main process is organized into domain modules under `src/main/core/`. Each domain typically has a `controller.ts` (RPC…

path-system

path-system

Agent

`@emdash/core/primitives/path/api` is the source of truth for portable file identity and lexical path operations. The detailed…

More agents from other plugins.

api-builder

api-builder

Agent

Deprecated placeholder. Do not use; follow root AGENTS.md and package instructions instead.

happier1d2.2k
@happier-dev@happier-devInstall Agent

Questions about main-patterns.

Which coding agents can use main-patterns?
main-patterns installs in Claude Code and Cursor: each loads subagents from .claude/agents, so copy the file there. Flowy's auto-invocation runs in Claude Code only.