// docs / working on itmirrored from DEV_SETUP.md

Orphus — Development Setup

This document covers setup, the local dev loop, testing patterns, and project layout.

Orphus is a fork of Atomic (itself a fork of pi), so most of this tree is vendored upstream code. The part this project authors is packages/roundtable — rooms, the budgeted digest, the role launcher, and the memory tool. The coding-agent fork that builds the orphus binary lives at packages/coding-agent, and the workflow extension at packages/workflows.

If you only want to see it work, the two demos need no model and no API key:

npm ci --ignore-scripts
npm run demo        # three scripted agents; a late joiner catches up for ~33% of the transcript
npm run demo:loop   # the same, through export -> memory ingest -> recall by a fresh session
npm run roles       # turn orphus.roles.yaml into launch commands

Prerequisites

  • Node.js ≥ 22.13 — runs installs, checks, and the vitest suites (node:sqlite is unflagged from 22.13)
  • Bun ≥ 1.3.14 — compiles release binaries, runs scripts/*.ts, and hosts the Bun-based test fixtures
  • Rust (stable, with cargo) — builds the @orphus/natives N-API module
  • uv — Python package/environment manager for the evals/ harness
  • Docker — required for local Pier/DeepSWE sandbox runs

This repo runs a hybrid toolchain matching upstream earendil-works/pi: npm installs, builds, checks, and runs the vitest suites; Bun compiles the release binaries, runs scripts/*.ts, and hosts the test fixtures that need it. AGENTS.md carries the full table. The @orphus/workflows workspace package ships raw .ts files with no build step; Atomic bundles it into @orphus/coding-agent during the coding-agent build.


Setup

git clone git@github.com:kelvincushman/orphus.git
cd orphus
npm ci --ignore-scripts
npm run build --workspace=@orphus/natives

The natives build is a required one-time step (and again after pulling changes to crates/ or packages/natives/). npm ci --ignore-scripts deliberately skips lifecycle scripts, and the workspace natives package has no install hook anyway — only published releases ship prebuilt binaries. Without the compiled packages/natives/native/*.node, the CLI still runs but silently degrades: pty:true bash falls back to pipes, native grep/find/tree-sitter block resolution fall back to slower JS paths, and several packages/coding-agent tests fail (bash-pty-native, search-tool-*, hashline-tools). CI builds the module explicitly for the same reason (see .github/workflows/test.yml).

The committed .npmrc applies a two-day minimum release age to anything you add with npm install, and pins exact versions. package-lock.json is the only lockfile.

If you cloned without submodules, initialize them before running evals or touching vendored benchmark harnesses:

git submodule update --init --recursive

Current submodules include evals/deep-swe and evals/vendor/pier; the evals package points datacurve-pier at the local editable evals/vendor/pier checkout.

Both are optional and are left uninitialized in a default clone. Nothing outside evals/ depends on them — no workflow, script, hook, or test — so skip this step entirely unless you are running Python evals. If you do run them, evals/vendor/pier is required rather than convenient: evals/pyproject.toml declares datacurve-pier as an editable path dependency, so uv sync fails outright when the directory is empty. evals/deep-swe is dataset content only and is never imported.

Do not substitute the published datacurve-pier from PyPI for the submodule. It shares a version number with the vendored fork but is not the same tree, so swapping it would silently change behaviour while reporting no version change.

The eval harness is Python-based and uses uv from the evals/ directory:

cd evals
uv sync
uv run python -c 'import pier, pathlib; print(pathlib.Path(pier.__file__).resolve())'
uv run pier --help

The pier import should resolve to evals/vendor/pier/src/pier/__init__.py. After pulling submodule pointer changes or local Pier edits, refresh the editable install with:

cd evals
uv sync --reinstall-package datacurve-pier

Example single-task DeepSWE run with Atomic and the local Pier checkout:

cd evals
export OPENROUTER_API_KEY=...
uv run pier run \
  -p deep-swe/tasks \
  --agent-import-path atomic_pier:Atomic \
  --model openrouter/openai/gpt-5.5 \
  --agent-kwarg thinking=xhigh \
  --agent-kwarg version=next \
  --agent-env 'OPENROUTER_API_KEY=${OPENROUTER_API_KEY}' \
  --n-tasks 1 \
  --sample-seed 0 \
  --n-concurrent 1 \
  --force-build

npm install runs the root prepare script, which installs Git hooks with prek from prek.toml. The hook shims installed by default come from default_install_hook_types; currently that is pre-commit. To reinstall hooks manually, run npm run hooks:install. Set PREK_DISABLE_INSTALL=1 to skip hook installation for a local install; CI skips it automatically.

The root package.json is a private workspace package named atomic-monorepo. The only publishable package is packages/coding-agent (@orphus/coding-agent); other packages/* workspaces are bundled or internal.


Running the coding agent from source

The packages/coding-agent package is the coding-agent CLI that builds the orphus binary. Its CLI name is orphus, its config directory is ~/.orphus/agent, and its environment variable prefix is ORPHUS_.

The Atomic and pi names are retained only as legacy fallbacks, so an existing install keeps working: the binary is also linked as atomic, and .atomic is still read as a config directory if .orphus is absent. A fresh checkout uses the Orphus names, and new code and docs should assume them. Getting this backwards matters more than it looks — pointing a fleet at ~/.atomic/agent sends each worktree to a different broker, silently, because each one finds a perfectly healthy broker of its own.

For most local development, run the TypeScript entrypoint directly with Bun from the workspace root:

bun packages/coding-agent/src/cli.ts --help
bun packages/coding-agent/src/cli.ts

For a one-shot non-interactive prompt:

bun packages/coding-agent/src/cli.ts -p "List files in this repo"

The direct source command is the recommended dev loop because it avoids generating dist/ and resolves package assets from src/.

If you need to exercise the compiled package layout, use the coding-agent watch script in one terminal:

bun run --cwd packages/coding-agent dev

After the first emit, run the compiled CLI from another terminal. The published atomic bin runs under #!/usr/bin/env node, so node is the faithful way to exercise the compiled layout; bun works too:

node packages/coding-agent/dist/cli.js --help
node packages/coding-agent/dist/cli.js

To run the development CLI against a different working directory while keeping source in this checkout:

cd /path/to/target/project
bun /path/to/atomic/packages/coding-agent/src/cli.ts

For a production-style build, run:

bun run --cwd packages/coding-agent build
node packages/coding-agent/dist/cli.js --version

Both the source and dist entrypoints pick up the native module from packages/natives/native/; if you skipped the natives build in Setup, PTY and native search quietly fall back to slower JS paths.


Local dev loop with atomic

The extension entrypoint is now:

packages/workflows/src/extension/index.ts

Three options, from heaviest to lightest:

A. atomic plugin install against the local package path (persisted)

atomic plugin install -l "$PWD/packages/workflows"   # project-local
# or
atomic plugin install    "$PWD/packages/workflows"   # global

atomic adds the absolute package path to its settings file and resolves the package’s atomic manifest. From inside atomic, /reload re-imports the extension after you edit source — no restart needed.

B. One-off load with -e (no settings write)

atomic -e "$PWD/packages/workflows/src/extension/index.ts"

The fastest iteration loop. Combine with --no-extensions to isolate the extension under test:

atomic --no-extensions \
   -e "$PWD/packages/workflows/src/extension/index.ts" \
   "/workflow list"
mkdir -p ~/.atomic/agent/extensions
ln -s "$PWD/packages/workflows" ~/.atomic/agent/extensions/workflows

Useful when you want the extension persisted globally but don’t want atomic to track it in settings.


Commands

Run these from the workspace root:

Command Description
npm ci --ignore-scripts Install from package-lock.json
npm run build --workspace=@orphus/natives Build the native N-API module (requires cargo)
npm run check Typecheck plus the published-shrinkwrap check
npm run typecheck Type-check the workspace
npm run test:unit Run unit tests
npm run test:integration Run integration tests
npm run test:ci-contracts Run the CI and release contract suite
npm run test:all Run both unit + integration
npm run test:scripts node --test scripts/*.test.mjs
npm run test --workspace=@orphus/coding-agent The coding-agent suite, under Node
npm run test:bun --workspace=@orphus/coding-agent Its Bun-hosted half; both are required
npm run hooks:install Install prek.toml Git hooks using default_install_hook_types
npm run hooks:run Run all prek.toml hooks across the repository

check runs biome check --error-on-warnings, then tsc --noEmit, then verifies packages/coding-agent/npm-shrinkwrap.json is still derivable from package-lock.json; lint is an alias for check, and npm run format applies Biome’s formatter. Biome is configured in biome.json with upstream pi’s rule set. Git hook configuration lives in prek.toml, not .pre-commit-config.yaml.


Testing patterns

All suites run under vitest with node:assert/strict assertions.

Because the suites run under Node, Bun.* and import.meta.dir are unavailable in tests. test/helpers/runtime.ts provides the replacements (sleep, readText, readJson, fileExists, writeFileEnsuringDir, spawnSyncCollect, spawnProcess, moduleDir, bunExecutable); several close traps a direct port would not, so use them rather than reaching for node:fs or node:child_process. See AGENTS.md for the table.

One exception: four files in packages/coding-agent/test are collected by a Bun-hosted vitest project (agent-bun) and run by npm run test:bun --workspace=@orphus/coding-agent. They test src/core/tools/resource-selectors.ts, which loads bun:sqlite and throws without it, so under Node they do not fail — they stop asserting. Do not add a runtime guard that returns early; add the file to BUN_HOSTED_TESTS in packages/coding-agent/vitest.config.ts instead. test/ci/ci-workflow-contracts.test.ts enforces both halves.

Unit tests (test/unit/*.test.ts)

Pure-TS tests against modules in packages/workflows/src/. They mock pi’s ExtensionAPI surface with hand-built fakes — fast, deterministic, no pi runtime in the loop.

Run: npm run test:unit.

Integration tests (test/integration/*.test.ts)

Higher-fidelity tests that compose multiple modules (runtime, wiring, overlay) and exercise the extension factory against a structural mock of ExtensionAPI. Still no real pi process — but they cover end-to-end registration, lifecycle, and overlay paths.

Run: npm run test:integration.

Improved coverage with pi’s SDK

pi exposes DefaultResourceLoader.extensionFactories for in-process extension injection:

import {
    createAgentSession,
    DefaultResourceLoader,
    SessionManager,
    getAgentDir,
} from "@orphus/coding-agent";
import factory from "./packages/workflows/src/extension/index.ts";

const resourceLoader = new DefaultResourceLoader({
    cwd: process.cwd(),
    agentDir: getAgentDir(),
    extensionFactories: [factory],
});
await resourceLoader.reload();

const { session } = await createAgentSession({
    resourceLoader,
    sessionManager: SessionManager.inMemory(),
});

Running examples

bun examples/hello-world.ts
bun examples/parallel-fan-out.ts

Examples import the workspace package @orphus/workflows.


Project layout

.
├── package.json                         # private workspace root
├── packages/
│   ├── roundtable/                      # THE ORPHUS CONTRIBUTION
│   │   ├── digest.ts                    # the budgeted digest algorithm
│   │   ├── broker/                      # local-socket room server, client, framing
│   │   ├── roles/                       # role manifest -> launch plan
│   │   ├── memory/                      # HMLR-Wiki / Dossier adapter
│   │   ├── demo/                        # the no-model demos
│   │   └── skills/                      # discussion etiquette, as an agent skill
│   ├── coding-agent/                    # @orphus/coding-agent CLI fork; builds the orphus binary
│   └── workflows/
│       ├── package.json                 # private bundled @orphus/workflows metadata
│       ├── src/
│       │   ├── extension/               # atomic extension entry point, commands, tools, hooks
│       │   ├── intercom/                # intercom adapter
│       │   ├── runs/                    # foreground/background workflow execution
│       │   ├── shared/                  # store, store-types, types, persistence helpers
│       │   ├── tui/                     # widget and DAG overlay renderers
│       │   ├── workflows/               # registry and identity helpers
│       │   └── index.ts                 # public entry point
│       ├── workflows/                   # bundled workflow definitions
│       ├── skills/                      # bundled atomic skills
│       ├── agents/                      # bundled agent definitions
│       ├── themes/                      # bundled themes
│       └── README.md
├── test/
│   ├── unit/
│   ├── integration/
│   └── support/
├── examples/
├── docs/
├── scripts/
├── vitest.config.ts
├── vitest.base.ts
├── .npmrc
├── bunfig.toml
└── tsconfig.json

Best practices

  • Source files use .js import extensions (TypeScript ESM convention). The repo ships as .ts files; Bun resolves .js specifiers to .ts sources directly.
  • Avoid any and unknown. Use specific types. The codebase compiles with strict, noUnusedLocals, and noUnusedParameters.
  • Keep the root package private. The only publishable workspace package is packages/coding-agent (@orphus/coding-agent).
  • Keep packages/workflows private. It is bundled into @orphus/coding-agent; do not publish it independently.
  • Do not add a build step for @orphus/workflows; it ships raw TypeScript/resources into the Atomic bundle.
  • Track in-progress fixes in issues.md. Delete the file once issues are resolved.

Releasing

Atomic uses a versionless release-base flow: main and supported workstreams stay at the 0.0.0 placeholder, while the real version is materialized only on a throwaway Release <version> commit whose parent is the selected exact remote branch SHA. Pushing the <version> tag (no leading v, for example 0.8.24 or 0.8.24-alpha.1) directly starts .github/workflows/publish.yml. Its lightweight integrity job checks the tag/package version and Release <version> subject before same-run native and archive builds, draft GitHub Release staging, OIDC npm publication, and final undrafting. See Direct release trigger and recovery.

Workflow

  1. Land the CHANGELOG move on the selected versionless base like any other change: move the [Unreleased] section in packages/coding-agent/CHANGELOG.md into a new ## [<version>] - <YYYY-MM-DD> section (CI extracts release notes from it). Do not bump any package.json version.
  2. From a clean selected base, cut the release. This resolves the exact remote branch, stamps the version onto a detached Release <version> commit, records Release-base-ref and Release-base-sha, tags it, and pushes only the tag:
    bun run scripts/cut-release.ts <version> --base main --push
    The selected branch is never advanced; the script does the stamp in a detached git worktree and abandons it (the tag keeps the commit alive). Omit --push to inspect the tag locally first, then git push origin <version>. A non-main base must be protected with the repository’s required CI checks before it is used.
  3. The tag push starts .github/workflows/publish.yml directly. It validates the tag identity, rebuilds all native bindings and release archives, stages a verified draft GitHub Release, publishes npm packages through OIDC, and undrafts the GitHub Release only after npm succeeds. Configure npm trusted publishers with workflow filename publish.yml and environment npm-publish.

To run the full guarded automation (release-notes PR + cut-release + publish monitoring), use the publish-release Atomic workflow instead of the manual steps above.

Bun is the development/test/runtime path. npm is still the registry publication tool because npm’s provenance flow signs the published tarball via OIDC. CI uses trusted publishing without a static npm credential.


CI

CI runs static checks, the root unit/integration suites, the coding-agent suite, and release-archive smoke tests as concurrent jobs behind a fail-closed result gate, on Linux and Windows. It builds @orphus/natives explicitly before the suites. See docs/ci.md and .github/workflows/test.yml.

edit this page on GitHub →