This repository has been archived on 2026-06-01. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
nerve/docs/rfc-004-package-architecture.md
T

7.5 KiB

RFC-004: Package Architecture — Shareable Workflows, Roles & Senses

Author: 小橘 🍊(NEKO Team) Status: Draft Created: 2026-04-29

Summary

Make workflows, roles, and senses publishable as lightweight npm packages. Workspaces become pure configuration — selecting packages, wiring adapters, and providing credentials. No builtin workflows in the nerve core.

Motivation

Currently, workflows like develop-sense and develop-workflow live inside the workspace (~/.uncaged-nerve/workflows/). This creates problems:

  1. No sharing — every workspace duplicates the same workflow code
  2. No versioning — upgrading a workflow means manual file edits
  3. Builtin is a trap — if we bake workflows into nerve core, they require adapters and LLM providers that may not be installed. A fresh nerve install on a bare machine would fail to load builtins.
  4. Roles are already shared_shared/workspace-committer.ts proves the pattern works; we just need to formalize it as packages

The adapter pattern (@uncaged/nerve-adapter-hermes, @uncaged/nerve-adapter-cursor) already established the precedent: infrastructure as packages, workspace as wiring.

Design

Package Taxonomy

@uncaged/nerve-core              # types, engine
@uncaged/nerve-daemon            # runtime
@uncaged/nerve-workflow-utils    # createRole, decorateRole, withDryRun, onFail, etc.

# Adapters (existing)
@uncaged/nerve-adapter-hermes
@uncaged/nerve-adapter-cursor

# Workflows (new)
@uncaged/nerve-workflow-solve-issue
@uncaged/nerve-workflow-develop-sense
@uncaged/nerve-workflow-develop-workflow

# Shared Roles (new)
@uncaged/nerve-role-committer    # workspace committer (branch, commit, push)
@uncaged/nerve-role-reviewer     # code review role
@uncaged/nerve-role-publisher    # PR creation role

# Senses (existing pattern, formalized)
@uncaged/nerve-sense-cpu-usage
@uncaged/nerve-sense-disk-usage

Package Contract

Each package type exports a factory function:

Workflow Package

// @uncaged/nerve-workflow-develop-sense
import type { AgentFn, WorkflowDefinition } from "@uncaged/nerve-core";
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";

export type SenseMeta = { /* ... */ };

export type CreateDevelopSenseDeps = {
  defaultAdapter: AgentFn;
  adapters?: Partial<Record<keyof SenseMeta, AgentFn>>;
  extract: LlmExtractorConfig;
  cwd: string;
};

export function createDevelopSenseWorkflow(deps: CreateDevelopSenseDeps): WorkflowDefinition<SenseMeta>;

Key design decisions:

  • defaultAdapter + optional adapters override per role — via Partial<Record<keyof Meta, AgentFn>>
  • Adapter keys are derived from Meta type — adding/removing a role automatically updates the adapter map
  • Roles that don't need an agent simply don't appear in adapters (the Partial allows this)

Role Package

// @uncaged/nerve-role-committer
import type { AgentFn, Role } from "@uncaged/nerve-core";
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
import { createRole, decorateRole, withDryRun, onFail } from "@uncaged/nerve-workflow-utils";

export type CommitterMeta = { committed: boolean };

export function createCommitterRole(adapter: AgentFn, extract: LlmExtractorConfig): Role<CommitterMeta> {
  const inner = createRole(adapter, prompt, committerMetaSchema, extract);
  return decorateRole(inner, [
    withDryRun({ label: "committer", meta: { committed: true } }),
    onFail({ label: "committer", meta: { committed: false } }),
  ]);
}

Roles compose with the decorator chain from workflow-utils:

  • withDryRun — skip execution in dry-run mode
  • onFail — catch errors into structured failure results
  • decorateRole(role, [...]) — apply decorators left-to-right
  • Custom RoleDecorator<M> can be created for project-specific needs

Sense Package

// @uncaged/nerve-sense-cpu-usage
export const senseName = "cpu-usage";
export const schema = { /* drizzle schema */ };
export async function compute(ctx: SenseContext): Promise<SenseResult>;

Workspace as Configuration

The workspace becomes a thin wiring layer:

~/.uncaged-nerve/
  nerve.yaml           # senses, extract config
  package.json         # depends on workflow/role/adapter packages
  workflows/
    develop-sense/
      index.ts         # ~10 lines: import package, wire adapters, export
    solve-issue/
      index.ts         # same pattern

A typical index.ts:

import { createDevelopSenseWorkflow } from "@uncaged/nerve-workflow-develop-sense";
import { hermesAdapter } from "@uncaged/nerve-adapter-hermes";
import { cursorAdapter } from "@uncaged/nerve-adapter-cursor";

export default createDevelopSenseWorkflow({
  defaultAdapter: hermesAdapter,
  adapters: { planner: cursorAdapter, coder: cursorAdapter },
  extract: { provider: { apiKey, baseUrl, model } },
  cwd: nerveRoot,
});

What Stays in Workspace

  • Custom workflows — project-specific workflows that aren't general enough to share
  • Custom senses — project-specific metrics
  • Configuration — adapter selection, credentials, nerve.yaml
  • Overrides — a workspace can always write its own role/workflow instead of using a package

Dependency Rules

nerve-core           ← no deps on other nerve packages
nerve-workflow-utils ← depends on nerve-core
nerve-adapter-*      ← depends on nerve-core
nerve-role-*         ← depends on nerve-core, nerve-workflow-utils
nerve-workflow-*     ← depends on nerve-core, nerve-workflow-utils, may depend on nerve-role-*
nerve-sense-*        ← depends on nerve-core
nerve-daemon         ← depends on nerve-core, nerve-store

Workflow packages depend on role packages (not adapters). Adapters are injected at the workspace level.

Migration Path

  1. Phase 1: Extract role packages — Start with @uncaged/nerve-role-committer (already _shared/workspace-committer.ts). Publish, update workspace to import from package.
  2. Phase 2: Extract workflow packages — Move develop-sense and develop-workflow to packages. Workspace index.ts becomes pure wiring.
  3. Phase 3: Sense packages — Formalize sense packaging (lower priority, senses are already self-contained directories).
  4. Phase 4: Community — Document the package contract so others can publish workflows/roles/senses.

Not in Scope

  • No builtin workflows — nerve core ships zero workflows. All workflows are packages installed by the workspace.
  • No workflow marketplace/registry — just npm packages. pnpm add @uncaged/nerve-workflow-solve-issue.
  • No nerve.yaml workflow declaration — workflows are still TypeScript entry points. The daemon discovers them the same way it does today.

Open Questions

  1. Monorepo vs separate repos? — Should workflow/role packages live in the nerve monorepo or separate repos? Monorepo is easier for coordinated releases; separate repos allow independent versioning.
  2. Sense package format — Senses currently bundle with esbuild. Should sense packages ship pre-bundled or as TypeScript source?
  3. Version coupling — How tightly should workflow packages pin nerve-core? Peer deps with semver range?

Prior Art

  • Adapter packages (@uncaged/nerve-adapter-*) — established the factory + injection pattern
  • _shared/workspace-committer.ts — proved roles can be shared across workflows
  • createRole / decorateRole / withDryRun / onFail in workflow-utils — building blocks that role packages compose
  • defaultAdapter + Partial<Record<keyof Meta, AgentFn>> pattern — adapter injection without coupling