review: the persona becomes the system-prompt plugin's deployment config

Review round 2 (tianyicui inline comments):

- dsh-system-prompt itself registers the harness:identity (-100) and
  deployment:persona (0) sections — they must survive a swapped loop
  plugin, so they leave dsh-agent-loop; the persona text is the plugin's
  own validated 'persona' config. The model/cwd variables STAY on the
  loop: runtime facts of the agents it drives.
- AgentOptions.systemPrompt is deleted with all its forwarding plumbing:
  the app configs' systemPrompt keys become 'persona' routed through
  dsh-agent-core (schema = z.intersect of the owners'), the ACP bridge
  and tool-subagent stop carrying persona configuration, and subagent
  children now render the deployment persona like every other agent.
- Example personas drop transport/interface trivia (ACP, CLI) — facts
  irrelevant to the model.
- Root CONTEXT.md removed (not idiomatic); its persona definition was
  wrong under the new ownership anyway.
- Docs, READMEs, the prompt-variables RFC, and generated catalogs
  updated; new loop test pins the assemble-waterfall escape valve
  (an emptied assembly sends NO system field).
This commit is contained in:
Tianyi Cui
2026-07-05 23:23:46 +08:00
parent 2304f7a245
commit 3f83a4ee96
55 changed files with 389 additions and 284 deletions
+29 -16
View File
@@ -44,9 +44,10 @@
import type { Context } from 'cordis'
import Timer from '@cordisjs/plugin-timer'
import z from 'schemastery'
import LlmService from '@deepseek-ai/dsh-llm'
import SessionStore from '@deepseek-ai/dsh-session'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
import SystemPrompt, { type Config as SystemPromptConfig } from '@deepseek-ai/dsh-system-prompt'
import ToolRegistry from '@deepseek-ai/dsh-tools'
import AgentRegistry from '@deepseek-ai/dsh-agent'
import * as invariants from '@deepseek-ai/dsh-invariants'
@@ -56,33 +57,45 @@ import AgentLoop, { type Config as AgentLoopConfig } from '@deepseek-ai/dsh-agen
export const name = 'agent-core'
/**
* Bundle config: the agent-loop `agents` list, forwarded verbatim. Default `[]`
* an app that pre-creates no agents (the ACP bridge creates them on demand at
* `session/new`) simply omits it; an app that needs a pre-created `main` (the
* stdio chat) supplies one. This IS {@link AgentLoopConfig}, so the schema and
* the forwarded shape can never drift.
* Bundle config: each field forwarded verbatim to the child that owns it —
* `agents` to the agent loop (an app that pre-creates no agents, like the ACP
* bridge, simply omits it), `persona` to the system-prompt plugin (the
* deployment's persona section). Both are optional INPUT here because each
* owner's schema supplies the default (`[]` / `''`); the schema is the
* INTERSECTION of the owners' own schemas, so validation and defaulting can
* never drift from them.
*/
export type Config = AgentLoopConfig
export interface Config {
/** The agent-loop `agents` list (see dsh-agent-loop's `Config`). */
agents?: AgentLoopConfig['agents']
/** The deployment persona (see dsh-system-prompt's `Config`). */
persona?: SystemPromptConfig['persona']
}
/** Forward the loop's own schema so validation + defaulting stay identical. */
export const Config = AgentLoop.Config
/** Intersect the owners' schemas so validation + defaulting stay identical. */
export const Config = z.intersect([AgentLoop.Config, SystemPrompt.Config]) as unknown as z<Config>
/**
* Load the spine. Each `ctx.plugin(...)` mounts one child of the bundle fiber;
* `agent-loop` receives the forwarded `agents` list. Load order is irrelevant
* (cordis pends each fiber on its `inject` until the services it needs exist),
* but the listing mirrors the dependency layering for readability: the LLM
* vocabulary and core registries first, then the dev tripwire and the bash tool
* consumer, then the loop that drives them.
* `agent-loop` receives the forwarded `agents` list and `system-prompt` the
* forwarded `persona`. Load order is irrelevant (cordis pends each fiber on
* its `inject` until the services it needs exist), but the listing mirrors the
* dependency layering for readability: the LLM vocabulary and core registries
* first, then the dev tripwire and the bash tool consumer, then the loop that
* drives them.
*/
export function apply(ctx: Context, config: Config): void {
ctx.plugin(Timer)
ctx.plugin(LlmService)
ctx.plugin(SessionStore)
ctx.plugin(SystemPrompt)
// The forwarded fields are validated + defaulted by this bundle's intersected
// schema before apply runs, so the ?? fallbacks only narrow the
// optional-input TYPES — they mirror the owners' schema defaults, never
// introduce different ones.
ctx.plugin(SystemPrompt, { persona: config.persona ?? '' })
ctx.plugin(ToolRegistry)
ctx.plugin(AgentRegistry)
ctx.plugin(invariants)
ctx.plugin(toolBash)
ctx.plugin(AgentLoop, { agents: config.agents })
ctx.plugin(AgentLoop, { agents: config.agents ?? [] })
}