RFC-003: Agent Configuration Layer #233
@@ -0,0 +1,170 @@
|
||||
# RFC-003: Agent Configuration Layer
|
||||
|
||||
**Author:** 小橘 🍊(NEKO Team)
|
||||
**Status:** Draft
|
||||
**Created:** 2026-04-29
|
||||
|
||||
## Summary
|
||||
|
||||
Introduce a top-level `agents` and `extract` configuration in `nerve.yaml`, separating agent infrastructure from workflow business logic. Workflows define Roles (prompt + schema) that reference named Agents by domain expertise, not by implementation detail.
|
||||
|
||||
## Motivation
|
||||
|
||||
Currently, Role definitions in workflows are tightly coupled with agent implementation details (type, model, timeout). This leads to:
|
||||
|
||||
- **Duplication** — multiple workflows using the same agent config repeat it everywhere
|
||||
- **Fragility** — switching from `cursor` to `codex` requires touching every workflow
|
||||
- **Leaky abstraction** — workflow authors need to know agent internals
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### Agent vs Role
|
||||
|
||||
| | Agent | Role |
|
||||
|---|---|---|
|
||||
| **What** | Professional domain capability | Scenario-specific persona |
|
||||
| **Granularity** | Few (2–4) | Many (per workflow step) |
|
||||
| **Examples** | `developer`, `ops`, `writer` | `architect`, `coder`, `reviewer`, `deployer` |
|
||||
| **Defines** | Tool, model, timeout defaults | Prompt, meta schema, timeout override |
|
||||
| **Layer** | Infrastructure (`nerve.yaml`) | Business logic (TypeScript) |
|
||||
|
||||
A `developer` agent becomes an architect, coder, or reviewer depending on the prompt it receives. The agent defines *what it's good at*; the role defines *what it does right now*.
|
||||
|
||||
### Agent Protocol
|
||||
|
||||
All agent types implement a single unified interface:
|
||||
|
||||
```ts
|
||||
type AgentFn = (prompt: string, context: WorkflowContext) => Promise<string>
|
||||
```
|
||||
|
||||
- **Input**: prompt (assembled by Role) + context (start frame + prior messages)
|
||||
- **Output**: raw string — structured data is extracted separately
|
||||
- **Internals**: adapter handles tool-specific details (cursor CLI, hermes subagent, codex API, etc.)
|
||||
|
||||
Workflow runtime never interacts with agent internals.
|
||||
|
||||
### Extract Layer
|
||||
|
||||
A separate concern that parses agent output (raw string) into typed meta:
|
||||
|
||||
```ts
|
||||
type ExtractFn<T> = (raw: string, schema: Schema<T>) => Promise<T>
|
||||
```
|
||||
|
||||
Configured globally in `nerve.yaml`, overridable per role.
|
||||
|
||||
## Design
|
||||
|
||||
### Configuration (`nerve.yaml`)
|
||||
|
||||
```yaml
|
||||
agents:
|
||||
developer:
|
||||
type: cursor # adapter: cursor | hermes | codex | ...
|
||||
model: auto
|
||||
timeout: 300s
|
||||
ops:
|
||||
type: hermes
|
||||
model: auto
|
||||
timeout: 600s
|
||||
|
||||
extract:
|
||||
provider: dashscope
|
||||
model: qwen-plus
|
||||
```
|
||||
|
||||
### Workflow Definition (TypeScript)
|
||||
|
||||
```ts
|
||||
const workflow: WorkflowSpec<MyMeta> = {
|
||||
name: "develop-workflow",
|
||||
roles: {
|
||||
architect: { agent: "developer", prompt: architectPrompt, meta: architectSchema },
|
||||
coder: { agent: "developer", prompt: coderPrompt, meta: coderSchema },
|
||||
reviewer: { agent: "developer", prompt: reviewPrompt, meta: reviewSchema, timeout: "60s" },
|
||||
deployer: { agent: "ops", prompt: deployPrompt, meta: deploySchema },
|
||||
},
|
||||
moderator,
|
||||
};
|
||||
```
|
||||
|
||||
### Runtime Assembly
|
||||
|
||||
```
|
||||
nerve.yaml → AgentRegistry → adapter(cursor/hermes/codex/...)
|
||||
↓
|
||||
WorkflowSpec → Role(agent + prompt) → AgentFn(prompt, ctx) → string
|
||||
↓
|
||||
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.
|
||||
|
||||
### Timeout Resolution
|
||||
|
||||
Two-layer with role override:
|
||||
|
||||
1. Agent config provides the default timeout
|
||||
2. Role definition can override for specific scenarios
|
||||
|
||||
```yaml
|
||||
# Agent default
|
||||
agents:
|
||||
developer:
|
||||
timeout: 300s
|
||||
```
|
||||
|
||||
```ts
|
||||
// Role override — review is faster
|
||||
reviewer: { agent: "developer", ..., timeout: "60s" }
|
||||
// coder uses agent default (300s)
|
||||
coder: { agent: "developer", ... }
|
||||
```
|
||||
|
||||
### No Runtime Fallback
|
||||
|
||||
- **`nerve init`** — detects agent availability (CLI exists? service reachable?), reports errors immediately
|
||||
- **Runtime** — if an agent is unavailable, the workflow fails with a clear error. No silent degradation.
|
||||
|
||||
Rationale: silent fallback hides quality differences (cursor → hermes subagent produces very different output) and makes debugging harder.
|
||||
|
||||
## Compatibility with Current Types
|
||||
|
||||
The existing `Role<Meta>` signature:
|
||||
|
||||
```ts
|
||||
type Role<Meta> = (start: StartStep, messages: WorkflowMessage[]) => Promise<RoleResult<Meta>>
|
||||
```
|
||||
|
||||
remains the runtime interface. The new config layer is syntactic sugar — the runtime assembles `Role<Meta>` functions from `(agent config + prompt + schema)` instead of users writing them by hand. `WorkflowDefinition` stays the same at the engine level; `WorkflowSpec` is the new user-facing authoring format that compiles down to it.
|
||||
|
||||
## Knowledge Layer
|
||||
|
||||
Project knowledge is **not a nerve feature**. It is managed by [Alysaril](https://git.shazhou.work/uncaged/alysaril) — an independent project knowledge base tool (Zettelkasten cards + semantic search).
|
||||
|
||||
Nerve's relationship to project knowledge:
|
||||
|
||||
- **Nerve does not hardcode knowledge paths** — no `.nerve/knowledge/` convention in runtime code
|
||||
- **Knowledge loading is a prompt concern** — role prompts tell agents to read relevant cards
|
||||
- **Agent long-term memory** — domain expertise accumulated across runs (e.g. "this repo uses pnpm"), stored per agent, separate from project knowledge
|
||||
- **Workflow context** (`start` + `messages`) serves as the only in-run state — no separate "short-term memory" layer needed
|
||||
|
||||
```
|
||||
Project knowledge (Alysaril) Shared, git managed, any agent reads via prompt
|
||||
Agent long-term memory Per agent, domain expertise, cross-run
|
||||
Workflow context (start + msgs) Per run, moderator-controlled history
|
||||
```
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. **Agent naming convention** — should we enforce a fixed set (`developer`, `ops`, `writer`) or allow arbitrary names?
|
||||
2. **Extract override granularity** — global only, or also per-agent and per-role?
|
||||
3. **Context threading** — should `WorkflowContext` expose `workdir` and `signal` alongside the existing `start` + `messages`?
|
||||
4. **Agent long-term memory** — storage format and mechanism for persisting domain expertise across runs
|
||||
|
||||
## References
|
||||
|
||||
- [RFC-002: Workflow Engine](./rfc-002-workflow-engine.md)
|
||||
- Current `Role` / `Moderator` types: `packages/core/src/workflow.ts`
|
||||
- [Alysaril](https://git.shazhou.work/uncaged/alysaril) — project knowledge base (independent tool)
|
||||
Reference in New Issue
Block a user