mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
refactor(examples): DRY the acp-agent configs via base-core.yml + acp-tail.yml
The snapshot replay config duplicated most of base.yml + the acp tail just to swap llm-deepseek → llm-replay. Factor the shared pieces: - examples/base-core.yml: the providerless provider/tool core (llm, sessions, system-prompt, tools, agents, invariants, bash-local, tool-bash). base.yml is now base-core + the llm-deepseek adapter; the snapshot replay config is base-core + llm-replay. The replay config no longer hand-copies the core. - examples/acp-agent/acp-tail.yml: agent-loop (no pre-created agents) + persistence + the ACP bridge/system-prompt, shared by cordis.yml and the replay config so the three acp-agent configs can't drift. Its persistence root is `$DSH_SNAPSHOT_SESSIONS_ROOT ?? ./.sessions`. - Deleted cordis.snapshot-record.yml: recording now reuses the normal cordis.yml (real adapter), with the harness redirecting the persistence root via env. start.ts maps DSH_SNAPSHOT=record → cordis.yml. Verified: snapshot replay 8/8 keyless; record path works through cordis.yml; ACP e2e no-key boot green through the doubly-nested include (cordis.yml → base.yml → base-core.yml); coding-agent boots clean; all gates pass.
This commit is contained in:
@@ -41,7 +41,9 @@ examples/ Runnable demos (not workspaces). echo-agent = mock model + echo
|
||||
(pnpm run demo:coding, needs DEEPSEEK_API_KEY).
|
||||
acp-agent = the coding agent exposed as an ACP server over
|
||||
JSON-RPC stdio (pnpm run demo:acp, needs DEEPSEEK_API_KEY).
|
||||
base.yml = shared provider/tool core both real demos include.
|
||||
base.yml = shared provider/tool core both real demos include
|
||||
(= base-core.yml, the providerless core, + the llm-deepseek adapter;
|
||||
base-core.yml is reused by the acp-agent snapshot-replay config).
|
||||
docs/ architecture.md — the design doc. module-graph.md — generated
|
||||
inter-package dependency graph (Mermaid; `pnpm run gen-module-graph`).
|
||||
rfc/ — design decisions and proposals, one kind of doc grouped by
|
||||
|
||||
@@ -46,7 +46,7 @@ Replay is positional: the Nth `stream()` call serves the Nth `ReplayEntry`. This
|
||||
|
||||
Recording runs the scenario with the real `llm-deepseek` adapter and the JSONL persistence backend, then copies the produced `.jsonl` into the scenario dir. Per-event appends are durable, but the harness shuts the subprocess down gracefully (close stdin → `await ctx.dispose()`) before harvesting so the final events are flushed. `llm-replay` itself does no recording — it is replay-only.
|
||||
|
||||
`examples/base.yml` always loads `@deepseek-ai/dsh-llm-deepseek`, whose `apply` throws when no API key is present ([packages/llm-deepseek/src/index.ts](../../../packages/llm-deepseek/src/index.ts)). So replay cannot reuse the normal config — it uses a dedicated `examples/acp-agent/cordis.snapshot.yml` that omits `llm-deepseek` and installs `llm-replay` in its place. Recording uses a config that loads the real adapter (no `llm-replay`). In replay mode `start.ts` also skips `.env` loading so a stray key cannot trigger a live call.
|
||||
`examples/base.yml` always loads `@deepseek-ai/dsh-llm-deepseek`, whose `apply` throws when no API key is present ([packages/llm-deepseek/src/index.ts](../../../packages/llm-deepseek/src/index.ts)). So replay cannot reuse the normal config — it uses a dedicated `examples/acp-agent/cordis.snapshot.yml` that installs `llm-replay` in place of the adapter. To avoid duplicating the rest of the tree, the providerless core is factored into `examples/base-core.yml` (shared by `base.yml = base-core + llm-deepseek` and the replay config = `base-core + llm-replay`), and the agent-loop/persistence/ACP-bridge tail into `examples/acp-agent/acp-tail.yml` (shared by `cordis.yml` and the replay config). Recording reuses the normal `cordis.yml` (real adapter) — its persistence root reads `$DSH_SNAPSHOT_SESSIONS_ROOT` when the harness sets it — so there is no separate record config. In replay mode `start.ts` skips `.env` loading so a stray key cannot trigger a live call.
|
||||
|
||||
### Two goldens: normalize, then snapshot
|
||||
|
||||
|
||||
33
examples/acp-agent/acp-tail.yml
Normal file
33
examples/acp-agent/acp-tail.yml
Normal file
@@ -0,0 +1,33 @@
|
||||
# The acp-agent "tail" shared by every acp-agent config (the normal demo, the
|
||||
# snapshot RECORD path which reuses cordis.yml, and the snapshot REPLAY config):
|
||||
# agent-loop (no pre-created agents — ACP session/new creates them on demand),
|
||||
# JSONL session persistence, and the ACP bridge with its system prompt. The
|
||||
# providerless core + an LLM adapter are included BEFORE this tail by each
|
||||
# config; nothing here loads an adapter, so the tail is provider-agnostic.
|
||||
#
|
||||
# Persistence root: $DSH_SNAPSHOT_SESSIONS_ROOT when the snapshot harness sets
|
||||
# it (so it can harvest / isolate the log), else ./.sessions for the demo.
|
||||
|
||||
- id: agent-loop
|
||||
name: '@deepseek-ai/dsh-agent-loop'
|
||||
config:
|
||||
agents: []
|
||||
|
||||
- id: session-persistence
|
||||
name: '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
config:
|
||||
root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
|
||||
|
||||
- id: acp
|
||||
name: '@deepseek-ai/dsh-acp'
|
||||
config:
|
||||
model: deepseek-v4-flash
|
||||
systemPrompt: |
|
||||
You are a coding assistant driven over the Agent Client Protocol.
|
||||
|
||||
Your only tools are bash (plus bash_output/bash_kill for background
|
||||
tasks). Do ALL file operations through bash: read with cat/sed/head,
|
||||
search with grep, write with heredocs (cat <<'EOF' > file), edit with
|
||||
sed or a rewrite. Each bash call runs in a fresh shell — pass workdir
|
||||
instead of cd. Check the [exit code: N] marker; verify your work. Keep
|
||||
answers brief and factual.
|
||||
@@ -1,43 +0,0 @@
|
||||
# Snapshot-test RECORD config: a real run whose persisted session JSONL is
|
||||
# harvested into a scenario fixture. Identical to cordis.yml (real llm-deepseek
|
||||
# adapter + JSONL persistence) — recording must exercise the REAL model so the
|
||||
# recorded log is a genuine product of the system. Needs DEEPSEEK_API_KEY.
|
||||
#
|
||||
# It is a separate file (rather than reusing cordis.yml) only so the snapshot
|
||||
# harness selects it explicitly via $DSH_SNAPSHOT=record and so its persistence
|
||||
# root can be pointed at the harness's harvest directory by the same env the
|
||||
# replay path uses. The graceful-shutdown path in start.ts flushes persistence
|
||||
# before exit so the harvested log is complete.
|
||||
|
||||
- id: timer
|
||||
name: '@cordisjs/plugin-timer'
|
||||
|
||||
# Shared provider/tool core, INCLUDING the real llm-deepseek adapter.
|
||||
- id: base
|
||||
name: '@cordisjs/plugin-include'
|
||||
config:
|
||||
path: '../base.yml'
|
||||
|
||||
- id: agent-loop
|
||||
name: '@deepseek-ai/dsh-agent-loop'
|
||||
config:
|
||||
agents: []
|
||||
|
||||
- id: session-persistence
|
||||
name: '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
config:
|
||||
root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT
|
||||
|
||||
- id: acp
|
||||
name: '@deepseek-ai/dsh-acp'
|
||||
config:
|
||||
model: deepseek-v4-flash
|
||||
systemPrompt: |
|
||||
You are a coding assistant driven over the Agent Client Protocol.
|
||||
|
||||
Your only tools are bash (plus bash_output/bash_kill for background
|
||||
tasks). Do ALL file operations through bash: read with cat/sed/head,
|
||||
search with grep, write with heredocs (cat <<'EOF' > file), edit with
|
||||
sed or a rewrite. Each bash call runs in a fresh shell — pass workdir
|
||||
instead of cd. Check the [exit code: N] marker; verify your work. Keep
|
||||
answers brief and factual.
|
||||
@@ -1,10 +1,11 @@
|
||||
# Snapshot-test REPLAY config: the acp-agent plugin tree with the model replaced
|
||||
# by llm-replay (serves a recorded session JSONL — no API key, no network).
|
||||
#
|
||||
# This does NOT include ../base.yml: base.yml always loads
|
||||
# @deepseek-ai/dsh-llm-deepseek, whose apply() throws without DEEPSEEK_API_KEY,
|
||||
# so a keyless replay run would die at boot. We inline the providerless core
|
||||
# instead and install llm-replay where the adapter would be.
|
||||
# It reuses ../base-core.yml (the providerless core) + ./acp-tail.yml (agent-
|
||||
# loop + persistence + the ACP bridge), the SAME pieces cordis.yml shares — only
|
||||
# the LLM adapter differs: llm-replay here, llm-deepseek there. It can't reuse
|
||||
# ../base.yml because that loads llm-deepseek, whose apply() throws without
|
||||
# DEEPSEEK_API_KEY, killing a keyless replay run at boot.
|
||||
#
|
||||
# stdout is reserved for the ACP JSON-RPC protocol — no stdout logger (see
|
||||
# cordis.yml). The replay fixture path comes from $DSH_SNAPSHOT_FILE (and an
|
||||
@@ -13,57 +14,19 @@
|
||||
- id: timer
|
||||
name: '@cordisjs/plugin-timer'
|
||||
|
||||
# Providerless core (everything base.yml has EXCEPT llm-deepseek).
|
||||
- id: llm
|
||||
name: '@deepseek-ai/dsh-llm'
|
||||
|
||||
- id: sessions
|
||||
name: '@deepseek-ai/dsh-session'
|
||||
|
||||
- id: system-prompt
|
||||
name: '@deepseek-ai/dsh-system-prompt'
|
||||
|
||||
- id: tools
|
||||
name: '@deepseek-ai/dsh-tools'
|
||||
|
||||
- id: agents
|
||||
name: '@deepseek-ai/dsh-agent'
|
||||
|
||||
- id: invariants
|
||||
name: '@deepseek-ai/dsh-invariants'
|
||||
|
||||
- id: bash
|
||||
name: '@deepseek-ai/dsh-bash-local'
|
||||
# Providerless core (everything base.yml has EXCEPT the llm-deepseek adapter).
|
||||
- id: base-core
|
||||
name: '@cordisjs/plugin-include'
|
||||
config:
|
||||
timeoutMs: 60000
|
||||
path: '../base-core.yml'
|
||||
|
||||
- id: tool-bash
|
||||
name: '@deepseek-ai/dsh-tool-bash'
|
||||
|
||||
# The replay adapter: short-circuits llm/stream with the recorded log's chunks.
|
||||
# The replay adapter: short-circuits llm/stream with the recorded log's chunks,
|
||||
# in place of llm-deepseek.
|
||||
- id: llm-replay
|
||||
name: './src/llm-replay.ts'
|
||||
|
||||
- id: agent-loop
|
||||
name: '@deepseek-ai/dsh-agent-loop'
|
||||
# agent-loop + persistence + the ACP bridge — shared with cordis.yml.
|
||||
- id: acp-tail
|
||||
name: '@cordisjs/plugin-include'
|
||||
config:
|
||||
agents: []
|
||||
|
||||
- id: session-persistence
|
||||
name: '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
config:
|
||||
root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT
|
||||
|
||||
- id: acp
|
||||
name: '@deepseek-ai/dsh-acp'
|
||||
config:
|
||||
model: deepseek-v4-flash
|
||||
systemPrompt: |
|
||||
You are a coding assistant driven over the Agent Client Protocol.
|
||||
|
||||
Your only tools are bash (plus bash_output/bash_kill for background
|
||||
tasks). Do ALL file operations through bash: read with cat/sed/head,
|
||||
search with grep, write with heredocs (cat <<'EOF' > file), edit with
|
||||
sed or a rewrite. Each bash call runs in a fresh shell — pass workdir
|
||||
instead of cd. Check the [exit code: N] marker; verify your work. Keep
|
||||
answers brief and factual.
|
||||
path: './acp-tail.yml'
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
# The acp-agent plugin tree, loaded via @cordisjs/plugin-include.
|
||||
# The acp-agent plugin tree, loaded via @cordisjs/plugin-include. Also the
|
||||
# snapshot RECORD config (start.ts selects it for DSH_SNAPSHOT=record): a real
|
||||
# llm-deepseek run whose persisted log the snapshot harness harvests.
|
||||
#
|
||||
# CRITICAL: this example loads NO stdout logger (no @cordisjs/plugin-logger-
|
||||
# console, no stdio-chat). stdout is reserved for the ACP JSON-RPC protocol —
|
||||
@@ -12,38 +14,17 @@
|
||||
- id: timer
|
||||
name: '@cordisjs/plugin-timer'
|
||||
|
||||
# Shared provider/tool core (llm, sessions, system-prompt, tools, agents,
|
||||
# invariants, llm-deepseek, bash-local, tool-bash). Nested include resolved
|
||||
# relative to THIS file's directory.
|
||||
# Shared provider/tool core, INCLUDING the real llm-deepseek adapter. Nested
|
||||
# include resolved relative to THIS file's directory.
|
||||
- id: base
|
||||
name: '@cordisjs/plugin-include'
|
||||
config:
|
||||
path: '../base.yml'
|
||||
|
||||
# agent-loop with NO pre-created agents: ACP `session/new` creates them on
|
||||
# demand (unlike coding-agent, which pre-creates `main`).
|
||||
- id: agent-loop
|
||||
name: '@deepseek-ai/dsh-agent-loop'
|
||||
# agent-loop (no pre-created agents) + JSONL persistence + the ACP bridge.
|
||||
# Shared with the snapshot REPLAY config (cordis.snapshot.yml) so the three
|
||||
# acp-agent configs don't drift.
|
||||
- id: acp-tail
|
||||
name: '@cordisjs/plugin-include'
|
||||
config:
|
||||
agents: []
|
||||
|
||||
# Durable session persistence — required by the ACP bridge for `session/load`.
|
||||
- id: session-persistence
|
||||
name: '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
config:
|
||||
root: './.sessions'
|
||||
|
||||
# The ACP bridge: wires AgentSideConnection to stdin/stdout.
|
||||
- id: acp
|
||||
name: '@deepseek-ai/dsh-acp'
|
||||
config:
|
||||
model: deepseek-v4-flash
|
||||
systemPrompt: |
|
||||
You are a coding assistant driven over the Agent Client Protocol.
|
||||
|
||||
Your only tools are bash (plus bash_output/bash_kill for background
|
||||
tasks). Do ALL file operations through bash: read with cat/sed/head,
|
||||
search with grep, write with heredocs (cat <<'EOF' > file), edit with
|
||||
sed or a rewrite. Each bash call runs in a fresh shell — pass workdir
|
||||
instead of cd. Check the [exit code: N] marker; verify your work. Keep
|
||||
answers brief and factual.
|
||||
path: './acp-tail.yml'
|
||||
|
||||
@@ -6,13 +6,12 @@ import Loader from '@cordisjs/plugin-loader'
|
||||
// DSH_SNAPSHOT=replay — load cordis.snapshot.yml (providerless; llm-replay
|
||||
// serves a recorded session log). Skip .env so a stray
|
||||
// key can never trigger a live model call.
|
||||
// DSH_SNAPSHOT=record — load cordis.snapshot-record.yml (the real adapter +
|
||||
// persistence) so a real run can be harvested.
|
||||
// DSH_SNAPSHOT=record — load the normal cordis.yml (the real llm-deepseek
|
||||
// adapter + persistence) so a real run can be harvested
|
||||
// (the persistence root is redirected by env).
|
||||
// Absent — the normal demo (cordis.yml), driven by a real editor.
|
||||
const snapshotMode = process.env.DSH_SNAPSHOT
|
||||
const configPath = snapshotMode === 'replay' ? './cordis.snapshot.yml'
|
||||
: snapshotMode === 'record' ? './cordis.snapshot-record.yml'
|
||||
: './cordis.yml'
|
||||
const configPath = snapshotMode === 'replay' ? './cordis.snapshot.yml' : './cordis.yml'
|
||||
|
||||
// Load DEEPSEEK_API_KEY / DEEPSEEK_BASE_URL from a gitignored repo-root .env
|
||||
// (Node native). Absent file is fine — the environment may already carry them.
|
||||
|
||||
37
examples/base-core.yml
Normal file
37
examples/base-core.yml
Normal file
@@ -0,0 +1,37 @@
|
||||
# Providerless provider/tool core — everything the model and tools need EXCEPT
|
||||
# an LLM adapter. Split out of base.yml so two consumers can share it:
|
||||
# - base.yml = base-core.yml + the real llm-deepseek adapter (the demos).
|
||||
# - acp-agent/cordis.snapshot.yml = base-core.yml + llm-replay (keyless
|
||||
# snapshot replay — base.yml can't be reused there because llm-deepseek's
|
||||
# apply() throws without DEEPSEEK_API_KEY).
|
||||
#
|
||||
# Plugin entries use package names (resolved from node_modules), so they are
|
||||
# insensitive to the baseUrl reset that plugin-include performs per file.
|
||||
|
||||
- id: llm
|
||||
name: '@deepseek-ai/dsh-llm'
|
||||
|
||||
- id: sessions
|
||||
name: '@deepseek-ai/dsh-session'
|
||||
|
||||
- id: system-prompt
|
||||
name: '@deepseek-ai/dsh-system-prompt'
|
||||
|
||||
- id: tools
|
||||
name: '@deepseek-ai/dsh-tools'
|
||||
|
||||
- id: agents
|
||||
name: '@deepseek-ai/dsh-agent'
|
||||
|
||||
# Dev-mode event-contract assertions + session-log freeze (off in prod).
|
||||
- id: invariants
|
||||
name: '@deepseek-ai/dsh-invariants'
|
||||
|
||||
# Bash execution: the local executor implementation + the tool schemas.
|
||||
- id: bash
|
||||
name: '@deepseek-ai/dsh-bash-local'
|
||||
config:
|
||||
timeoutMs: 60000
|
||||
|
||||
- id: tool-bash
|
||||
name: '@deepseek-ai/dsh-tool-bash'
|
||||
@@ -1,7 +1,12 @@
|
||||
# Shared provider/tool core for the example agents, loaded via a nested
|
||||
# @cordisjs/plugin-include from each example's cordis.yml. Contains everything
|
||||
# the model and tools need; each example adds its own infra (logger/timer/hmr),
|
||||
# its agent-loop config (the examples disagree — see below), and its UI plugin.
|
||||
# @cordisjs/plugin-include from each example's cordis.yml. This is
|
||||
# base-core.yml (the providerless core: llm, sessions, system-prompt, tools,
|
||||
# agents, invariants, bash-local, tool-bash) PLUS the real llm-deepseek adapter.
|
||||
#
|
||||
# The providerless core lives in base-core.yml so the keyless snapshot-replay
|
||||
# config (acp-agent/cordis.snapshot.yml) can reuse it with llm-replay in place
|
||||
# of the adapter — it can't reuse THIS file, because llm-deepseek's apply()
|
||||
# throws without DEEPSEEK_API_KEY.
|
||||
#
|
||||
# Deliberately EXCLUDES:
|
||||
# - the console logger: it writes to stdout, which the acp-agent reserves for
|
||||
@@ -12,28 +17,13 @@
|
||||
# pre-create NONE (ACP session/new creates agents on demand). So each example
|
||||
# declares agent-loop with its own `agents` list.
|
||||
#
|
||||
# Plugin entries here use package names (resolved from node_modules), so they
|
||||
# are insensitive to the baseUrl reset that plugin-include performs per file.
|
||||
# Requires DEEPSEEK_API_KEY (and optionally DEEPSEEK_BASE_URL) in the env.
|
||||
|
||||
- id: llm
|
||||
name: '@deepseek-ai/dsh-llm'
|
||||
|
||||
- id: sessions
|
||||
name: '@deepseek-ai/dsh-session'
|
||||
|
||||
- id: system-prompt
|
||||
name: '@deepseek-ai/dsh-system-prompt'
|
||||
|
||||
- id: tools
|
||||
name: '@deepseek-ai/dsh-tools'
|
||||
|
||||
- id: agents
|
||||
name: '@deepseek-ai/dsh-agent'
|
||||
|
||||
# Dev-mode event-contract assertions + session-log freeze (off in prod).
|
||||
- id: invariants
|
||||
name: '@deepseek-ai/dsh-invariants'
|
||||
# The providerless core (resolved relative to THIS file's directory).
|
||||
- id: base-core
|
||||
name: '@cordisjs/plugin-include'
|
||||
config:
|
||||
path: './base-core.yml'
|
||||
|
||||
# The DeepSeek adapter. Swap to '@deepseek-ai/dsh-llm-pi-ai' for the pi-ai-backed
|
||||
# twin (same config shape; `reasoning: high` replaces thinking/reasoningEffort).
|
||||
@@ -45,12 +35,3 @@
|
||||
models:
|
||||
- deepseek-v4-flash
|
||||
- deepseek-v4-pro
|
||||
|
||||
# Bash execution: the local executor implementation + the tool schemas.
|
||||
- id: bash
|
||||
name: '@deepseek-ai/dsh-bash-local'
|
||||
config:
|
||||
timeoutMs: 60000
|
||||
|
||||
- id: tool-bash
|
||||
name: '@deepseek-ai/dsh-tool-bash'
|
||||
|
||||
Reference in New Issue
Block a user