docs(rfc): RFC-003 — adapter packages + dynamic prompts
- Adapter packages: each adapter in own package (@nerve/adapter-cursor, etc.) - AgentRegistry accepts adapter factories at construction (plugin model) - Migration path: move spawn logic from workflow-utils to adapter packages - Dynamic prompts: RoleSpec.prompt supports string | async function - Workspace only installs adapters it uses Ref: #234
This commit is contained in:
@@ -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<string>);
|
||||
|
||||
type RoleSpec<M> = {
|
||||
agent: string;
|
||||
prompt: PromptInput;
|
||||
meta: Schema<M>;
|
||||
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:
|
||||
|
||||
Reference in New Issue
Block a user