The LLM service exposed three call surfaces (stream/streamBlocks/generate) but the only production consumer — the agent loop — uses stream() exclusively, feeding raw chunks through its own BlockAssembler for replay fidelity. Drop the speculative convenience surfaces and the registry-change event that no listener consumed, leaving stream() as the single model-call contract for both production and tests. - Remove LlmService.streamBlocks() and generate(), the llm/generate waterfall, and GenerateResult. - Remove the llm/adapter-change event (declaration + emits) and the listener-throw rollback ordering that existed only to protect it; keep the HMR rollback disposer. - Remove BlockAssembler.flushReady()/flushRemaining()/result() and the flushed cursor — the streaming-flush slice existed only for streamBlocks(). - Adapter tests drive a stream()+BlockAssembler helper (tests/assemble.ts) instead of generate(), exercising the same path production uses. - Land the AGENTS.md "RFCs are proposals, not golden truth" principle and move both RFCs proposed -> implemented. Implements: - docs/rfc/implemented/simplification/2026-06-20-drop-unconsumed-llm-adapter-change-event.md - docs/rfc/implemented/simplification/2026-06-20-drop-unconsumed-llm-assembled-surfaces.md
1.9 KiB
RFC: Microkernel — extension via Cordis event taxonomy, one concrete loop
Status: implemented (accepted 2026-06-11)
Context
The product principle (see the 微内核Harness实现思路 design doc) is "everything is a plugin": hooks, /goal, /loop, dynamic workflows, compaction, sandboxing, permissions, UI, persistence, MCP, skills must all be writable as plugins without modifying the core. Candidate mechanisms considered: a purpose-built middleware stack (koa-compose style), an explicit phase state machine plugins can insert into, or Cordis's native event system.
Decision
Pure Cordis event taxonomy. The loop's extension seams are typed events with deliberate dispatch modes:
- waterfall (around-middleware) where plugins mutate or veto:
agent/request,agent/step-result,agent/turn-continuation,tools/execute,llm/stream,system-prompt/assemble. - emit (sync fire-and-forget) for notifications: turn/step boundaries, stream chunks, lifecycle, errors.
- parallel (awaited) for the one durability checkpoint:
session/flush.
The event vocabulary lives in interface packages (dsh-agent declares the agent/* events); @deepseek-ai/dsh-agent-loop is the only concrete loop plugin and is itself swappable — nothing outside it may depend on it.
Consequences
- Every MVP feature maps to a listener (the "plugin sanity checklist" in docs/architecture.md is the proof obligation, kept current).
- HMR and disposal come free: listeners and registrations are Cordis effects.
- Waterfall semantics (call
next()or short-circuit) are non-obvious and must be taught — documented in AGENTS.md and covered by composition tests. - The loop must be defensive: plugin exceptions are contained at turn level, steering from any seam is never stranded (regression-tested).