chore: add .knowledge/ curated cards + knowledge.yaml #251
@@ -0,0 +1,47 @@
|
||||
# Agent Adapters (RFC-003)
|
||||
|
||||
Adapter = capability. Role = scenario. Workflows declare adapters directly via import.
|
||||
|
||||
## AgentFn Protocol
|
||||
|
||||
```ts
|
||||
type AgentFn = (prompt: string, context: WorkflowContext) => Promise<string>
|
||||
```
|
||||
|
||||
- Input: prompt + context (start frame, messages, workdir, AbortSignal)
|
||||
- Output: raw string — structured extraction is separate
|
||||
- Adapter handles tool-specific details internally
|
||||
|
||||
## Available Adapters
|
||||
|
||||
| Package | Adapter | Tool |
|
||||
|---------|---------|------|
|
||||
| `@uncaged/nerve-adapter-cursor` | `cursorAdapter` / `createCursorAdapter()` | cursor-agent CLI |
|
||||
| `@uncaged/nerve-adapter-hermes` | `hermesAdapter` / `createHermesAdapter()` | hermes chat CLI |
|
||||
|
||||
Each exports a **default instance** (sensible defaults) and a **factory** for custom config.
|
||||
|
||||
## Usage in Workflows
|
||||
|
||||
Adapters are passed directly to `createRole`:
|
||||
|
||||
```ts
|
||||
import { createRole } from "@uncaged/nerve-workflow-utils";
|
||||
import { cursorAdapter } from "@uncaged/nerve-adapter-cursor";
|
||||
|
||||
const coder = createRole(cursorAdapter, prompt, schema, extractConfig);
|
||||
```
|
||||
|
||||
No registry, no config indirection. TypeScript catches missing adapters at compile time.
|
||||
|
||||
## Extract Layer
|
||||
|
||||
Parses agent raw string → typed meta. Configured in `nerve.yaml`:
|
||||
|
||||
```yaml
|
||||
extract:
|
||||
provider: dashscope
|
||||
model: qwen-plus
|
||||
```
|
||||
|
||||
Two-level merge: global → role override. Retry once on parse failure (feeds error back to LLM), then throw `ExtractError`.
|
||||
@@ -0,0 +1,33 @@
|
||||
# Nerve Architecture
|
||||
|
||||
Observation engine for autonomous agents — sense the world, react to changes, run workflows.
|
||||
|
||||
## Core Pipeline
|
||||
|
||||
```
|
||||
External World → Sense → Signal → Reflex → Workflow → Log
|
||||
```
|
||||
|
||||
Causality is **one-directional**. Logs are the end of the chain — they cannot trigger Reflexes (prevents feedback loops).
|
||||
|
||||
## Three Orthogonal Extension Points
|
||||
|
||||
| Extension | Question | Nature |
|
||||
|-----------|----------|--------|
|
||||
| **Sense** | What to compute | `compute()` function |
|
||||
| **Reflex** | When to compute | Declarative YAML (interval / on) |
|
||||
| **Workflow** | What to do | Roles + Moderator |
|
||||
|
||||
Each is independent. Reflex doesn't know compute internals, Sense doesn't know when it's triggered, Workflow doesn't know why it was started.
|
||||
|
||||
## Two Event Types
|
||||
|
||||
- **Signal** — from Sense compute (non-null return). Pure fact, no intent. Drives the front half (perception).
|
||||
- **Command Event** — inside Workflow Threads. Has causal chain, must be responded to. Drives the back half (action).
|
||||
|
||||
## Process Isolation
|
||||
|
||||
- One worker per Sense group (long-lived)
|
||||
- One worker per Workflow type (on-demand)
|
||||
- Workers never talk to each other
|
||||
- All user code runs in isolated Workers; kernel never loads user code directly
|
||||
@@ -0,0 +1,78 @@
|
||||
# Nerve CLI
|
||||
|
||||
`nerve` — CLI entry point for nerve workspace management.
|
||||
|
||||
## Workspace Lifecycle
|
||||
|
||||
```bash
|
||||
nerve init # scaffold a new workspace (nerve.yaml, senses/, workflows/)
|
||||
nerve validate # validate nerve.yaml config
|
||||
nerve dev # run kernel foreground (development, Ctrl+C to stop)
|
||||
nerve start # start daemon (background)
|
||||
nerve stop # stop daemon
|
||||
nerve status # check daemon health (uptime, senses, workflows)
|
||||
nerve daemon # restart daemon (stop + start)
|
||||
```
|
||||
|
||||
## Sense Management
|
||||
|
||||
```bash
|
||||
nerve create sense <name> # scaffold a new sense (compute.ts + schema.ts)
|
||||
nerve sense list # list configured senses
|
||||
nerve sense trigger <name> # manually trigger a sense compute
|
||||
nerve sense schema <name> # show sense Drizzle schema
|
||||
nerve sense query <name> # inspect sense SQLite database
|
||||
nerve sense query <name> --sql "SELECT * FROM samples LIMIT 5"
|
||||
```
|
||||
|
||||
## Workflow Management
|
||||
|
||||
```bash
|
||||
nerve create workflow <name> # scaffold a new workflow
|
||||
nerve workflow trigger <name> --prompt "..." [--max-rounds N] [--dry-run]
|
||||
nerve workflow list # list configured workflows
|
||||
nerve thread # list active (queued/started) workflow threads
|
||||
```
|
||||
|
||||
## Knowledge
|
||||
|
||||
```bash
|
||||
nerve knowledge sync # chunk files per knowledge.yaml, compute embeddings → knowledge.db
|
||||
nerve knowledge query "text" # search indexed knowledge (cosine similarity)
|
||||
nerve knowledge query -g "text" # global search across all indexed repos
|
||||
nerve knowledge query --repo /path "text" # search specific repo
|
||||
```
|
||||
|
||||
## Logs & Store
|
||||
|
||||
```bash
|
||||
nerve logs # view daemon logs (last 50 lines)
|
||||
nerve logs -f # follow logs (tail -f style)
|
||||
nerve logs -n 200 # last N lines
|
||||
nerve store archive # archive old log entries to JSONL
|
||||
```
|
||||
|
||||
## Remote
|
||||
|
||||
```bash
|
||||
nerve remote add <name> <url> # add a remote daemon endpoint
|
||||
nerve status --remote <name> # check remote daemon health
|
||||
```
|
||||
|
||||
## Workspace Layout
|
||||
|
||||
```
|
||||
my-agent/
|
||||
nerve.yaml # senses, workflows, extract config
|
||||
knowledge.yaml # knowledge index config (optional)
|
||||
senses/
|
||||
cpu-usage/
|
||||
compute.ts # sense implementation
|
||||
schema.ts # Drizzle schema
|
||||
migrations/ # auto-generated
|
||||
workflows/
|
||||
cleanup/
|
||||
src/index.ts # workflow definition
|
||||
knowledge.db # generated by nerve knowledge sync
|
||||
.knowledge/ # curated knowledge cards
|
||||
```
|
||||
@@ -0,0 +1,57 @@
|
||||
# Nerve Coding Conventions
|
||||
|
||||
## Functional-First
|
||||
|
||||
- `type` over `interface`, `function` over `class`
|
||||
- No `this`, no inheritance, composition over inheritance
|
||||
- Immutability first: `Readonly<T>`, `as const`
|
||||
|
||||
## No Optional Properties
|
||||
|
||||
Never use `?:`. Use `T | null` for nullable fields. Use discriminated unions for mutually exclusive fields.
|
||||
|
||||
```ts
|
||||
// ✅ Good
|
||||
type Config = { throttle: string | null }
|
||||
|
||||
// ❌ Bad
|
||||
type Config = { throttle?: string }
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
- `Result<T, E>` for expected failures
|
||||
- `throw` only for programmer errors (bugs)
|
||||
- No try-catch for flow control
|
||||
|
||||
## Naming
|
||||
|
||||
| Type | Style |
|
||||
|------|-------|
|
||||
| Files | `kebab-case.ts` |
|
||||
| Types | `PascalCase` |
|
||||
| Functions/vars | `camelCase` |
|
||||
| Constants | `UPPER_SNAKE` |
|
||||
|
||||
## Exports
|
||||
|
||||
- Always named exports, never default
|
||||
- One module = one responsibility
|
||||
|
||||
## Async
|
||||
|
||||
- Always `async/await`, never `.then()` chains
|
||||
|
||||
## No Dynamic Import
|
||||
|
||||
Static `import` only. Exceptions: `sense-runtime.ts`, `workflow-worker.ts` (runtime module paths).
|
||||
|
||||
## Toolchain
|
||||
|
||||
pnpm + TypeScript (strict) + Biome (lint/format) + Vitest (test)
|
||||
|
||||
```bash
|
||||
pnpm run check # biome check
|
||||
pnpm test # vitest
|
||||
pnpm run build # full build
|
||||
```
|
||||
@@ -0,0 +1,38 @@
|
||||
# Knowledge Layer (RFC-003 Phase 6)
|
||||
|
||||
Local-first, repo-scoped knowledge base for project context.
|
||||
|
||||
## Files
|
||||
|
||||
- `knowledge.yaml` — repo root, defines include/exclude globs
|
||||
- `knowledge.db` — SQLite, stores chunks + embeddings
|
||||
- `.knowledge/` — curated knowledge cards (indexed by sync)
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
nerve knowledge sync # chunk files, compute embeddings, write to knowledge.db
|
||||
nerve knowledge query "query" # search by cosine similarity (or word overlap fallback)
|
||||
nerve knowledge query -g "query" # global search across all indexed repos
|
||||
nerve knowledge query --repo /path "query" # search specific repo
|
||||
```
|
||||
|
||||
## Embedding
|
||||
|
||||
- Remote service: configured via `EMBED_SERVICE_URL` env var (self-hosted Cloudflare Worker + KV cache)
|
||||
- Model: Dashscope text-embedding-v3 (1024 dims)
|
||||
|
|
||||
- Cache: content-addressable (sha256 of model+text), never expires
|
||||
- Fallback: word-overlap scoring when embed service not configured
|
||||
|
||||
## Chunking
|
||||
|
||||
- Markdown: split by headings, large sections split further by paragraphs (max 24)
|
||||
- TypeScript/JS: split by function declarations, fallback to paragraphs
|
||||
- Other files: single chunk
|
||||
|
||||
## Env Config
|
||||
|
||||
```
|
||||
EMBED_SERVICE_URL=https://embed.shazhou.workers.dev
|
||||
EMBED_AUTH_TOKEN=<token>
|
||||
```
|
||||
@@ -0,0 +1,24 @@
|
||||
# Nerve Monorepo Structure
|
||||
|
||||
```
|
||||
nerve/
|
||||
packages/
|
||||
core/ # @uncaged/nerve-core — shared types, config parser, Result, spawn-safe
|
||||
cli/ # @uncaged/nerve-cli — CLI (init, validate, dev, daemon, knowledge)
|
||||
daemon/ # @uncaged/nerve-daemon — kernel, workers, signal bus, scheduler
|
||||
store/ # @uncaged/nerve-store — append-only log, SQLite, CAS blob store
|
||||
workflow-utils/ # @uncaged/nerve-workflow-utils — role factories, extract, LLM helpers
|
||||
adapter-cursor/ # @uncaged/nerve-adapter-cursor — cursor-agent CLI adapter
|
||||
adapter-hermes/ # @uncaged/nerve-adapter-hermes — hermes chat CLI adapter
|
||||
khala/ # Khala — Sense marketplace (future)
|
||||
skills/ # nerve-managed skills
|
||||
docs/ # RFCs, conventions
|
||||
.knowledge/ # curated knowledge cards (this directory)
|
||||
```
|
||||
|
||||
## Dependency Rules
|
||||
|
||||
- `core` is the shared layer — everyone depends on it
|
||||
- `cli` and `daemon` must NOT depend on each other
|
||||
- Adapter packages depend only on `core`
|
||||
|
xiaoju
commented
PR #249 后 spawn-safe 移到了 core,workflow-utils 不再依赖 adapter packages。应改为 PR #249 后 spawn-safe 移到了 core,workflow-utils 不再依赖 adapter packages。应改为 `workflow-utils depends on core`
|
||||
- `workflow-utils` depends on `core`
|
||||
@@ -0,0 +1,29 @@
|
||||
# Sense
|
||||
|
||||
A `compute()` function that samples or derives external data. The only first-class citizen in nerve.
|
||||
|
||||
## Behavior
|
||||
|
||||
- Returns `T | null` — non-null emits a Signal, null is silent (no storage write, no signal, no downstream trigger)
|
||||
- Each Sense has its own **independent SQLite database**
|
||||
- Cross-sense reads are read-only via `peers` parameter
|
||||
- Schema defined with Drizzle ORM (`schema.ts` is single source of truth)
|
||||
|
||||
## Sense → Workflow
|
||||
|
||||
If `compute()` returns an object with `workflow: "name|maxRounds|prompt"`, the engine starts that workflow and does **not** emit a Signal. `workflow: null` or `""` means emit signal normally.
|
||||
|
||||
See `routeSenseComputeOutput` / `parseSenseWorkflowDirective` in `@uncaged/nerve-core`.
|
||||
|
||||
## Config (nerve.yaml)
|
||||
|
||||
```yaml
|
||||
senses:
|
||||
cpu-usage:
|
||||
group: system # senses in same group share a worker
|
||||
throttle: 10s # min interval between computes
|
||||
timeout: 30s # max compute duration
|
||||
grace_period: 5s # wait before first compute
|
||||
interval: 30s # periodic trigger (optional)
|
||||
on: [disk-pressure] # trigger on signals from other senses (optional)
|
||||
```
|
||||
@@ -0,0 +1,59 @@
|
||||
# Workflow Engine
|
||||
|
||||
Stateful multi-step execution driven by Roles and a Moderator.
|
||||
|
||||
## Core Concepts
|
||||
|
||||
- **Workflow** — definition with concurrency strategy
|
||||
- **Thread** — one execution instance, unique `runId`
|
||||
- **Role** — executes actions (has side effects). `(start, messages) → { content, meta }`
|
||||
- **Moderator** — pure routing function. `(context) → next role | END`
|
||||
|
||||
## Thread Lifecycle
|
||||
|
||||
```
|
||||
trigger → queued → started → step_complete ↺ → completed
|
||||
↓
|
||||
failed / crashed
|
||||
```
|
||||
|
||||
## Concurrency Config (nerve.yaml)
|
||||
|
||||
```yaml
|
||||
workflows:
|
||||
cleanup:
|
||||
concurrency: 1
|
||||
overflow: drop # discard if already running
|
||||
code-review:
|
||||
concurrency: 3
|
||||
overflow: queue
|
||||
max_queue: 20 # queue limit, oldest discarded
|
||||
```
|
||||
|
||||
## createRole Helper
|
||||
|
||||
`createRole` builds a `Role<M>` from an adapter, prompt, Zod schema, and extract config:
|
||||
|
||||
```ts
|
||||
import { createRole } from "@uncaged/nerve-workflow-utils";
|
||||
import { cursorAdapter } from "@uncaged/nerve-adapter-cursor";
|
||||
import { z } from "zod";
|
||||
|
||||
const coderSchema = z.object({ plan: z.string(), files: z.array(z.string()) });
|
||||
|
||||
const coder = createRole(cursorAdapter, coderPrompt, coderSchema, {
|
||||
provider: { baseUrl: "...", apiKey: "...", model: "qwen-plus" },
|
||||
});
|
||||
|
||||
// Use in WorkflowDefinition
|
||||
const workflow: WorkflowDefinition<MyMeta> = {
|
||||
name: "develop",
|
||||
roles: { coder, reviewer },
|
||||
moderator,
|
||||
};
|
||||
```
|
||||
|
||||
- `adapter: AgentFn` — direct function reference
|
||||
- `prompt: string | ((start, messages) => Promise<string>)` — static or dynamic
|
||||
- `meta: z.ZodType<M>` — Zod schema, directly (no wrapper needed)
|
||||
- `extract: LlmExtractorConfig` — provider for structured extraction
|
||||
@@ -0,0 +1,4 @@
|
||||
include:
|
||||
- ".knowledge/**/*.md"
|
||||
|
||||
exclude: []
|
||||
Reference in New Issue
Block a user
服务地址写死了。实际从
cfg get EMBED_SERVICE_URL获取,不是固定的 workers.dev。建议改为configured via EMBED_SERVICE_URL env var (self-hosted)