diff --git a/docs/rfc-003-agent-config-layer.md b/docs/rfc-003-agent-config-layer.md index d86a1eb..dc2fc83 100644 --- a/docs/rfc-003-agent-config-layer.md +++ b/docs/rfc-003-agent-config-layer.md @@ -103,6 +103,73 @@ nerve.yaml#extract → ExtractFn(string, schema) → T (typed meta) `AgentRegistry` reads config, instantiates adapters, and returns `AgentFn` by name. Role assembly is handled by the runtime — users never call Role factories directly. +### Adapter Packages + +Each agent adapter lives in its own package to avoid pulling unnecessary dependencies: + +``` +packages/ + adapter-cursor/ # @nerve/adapter-cursor — cursor-agent CLI + adapter-hermes/ # @nerve/adapter-hermes — hermes CLI subagent + adapter-claude/ # @nerve/adapter-claude — claude-code CLI (future) + adapter-codex/ # @nerve/adapter-codex — codex CLI (future) +``` + +Each adapter exports a single factory function: + +```ts +// @nerve/adapter-cursor +import type { AgentConfig, AgentFn } from "@nerve/core"; + +export function createCursorAdapter(config: AgentConfig): AgentFn; +``` + +The factory receives the full `AgentConfig` (type, model, timeout) and returns an `AgentFn` that spawns the CLI tool, passes the prompt, and returns raw output. + +**Registration** — `AgentRegistry` accepts adapter factories at construction: + +```ts +import { createCursorAdapter } from "@nerve/adapter-cursor"; +import { createHermesAdapter } from "@nerve/adapter-hermes"; + +const registry = createAgentRegistry(config.agents, { + cursor: createCursorAdapter, + hermes: createHermesAdapter, +}); +``` + +The daemon's entry point wires installed adapters; adapters not installed are not imported. `nerve validate` checks that referenced adapter types have a registered factory. + +**Workspace `package.json`** only lists the adapters it actually uses: + +```json +{ + "dependencies": { + "@nerve/adapter-cursor": "workspace:*", + "@nerve/adapter-hermes": "workspace:*" + } +} +``` + +**Migration from `workflow-utils`** — the existing `role-cursor.ts` / `shared/cursor-agent.ts` spawn logic moves to `@nerve/adapter-cursor`. `role-hermes.ts` / `shared/hermes-agent.ts` moves to `@nerve/adapter-hermes`. `workflow-utils` retains only extract, prompt utilities, and shared spawn infrastructure. + +### Dynamic Prompts + +`RoleSpec.prompt` supports both static strings and async functions: + +```ts +type PromptInput = string | ((start: StartStep, messages: WorkflowMessage[]) => Promise); + +type RoleSpec = { + agent: string; + prompt: PromptInput; + meta: Schema; + timeout: string | null; +}; +``` + +Static prompts cover simple cases. Dynamic prompts (functions) are needed when the prompt depends on thread context — e.g. reading issue content, injecting prior step results, or resolving repo paths at runtime. + ### Timeout Resolution Two-layer with role override: