Merge remote-tracking branch 'origin/master' into worktree-dynamic-workflows
Beyond the mechanical conflicts (provider capability lines vs master's new inheritsParentContext field; generated catalogs regenerated rather than hand-merged; knip/lockfile), three master-side reworks required semantic adaptation of this branch: - The persona rework removed AgentOptions.systemPrompt, which was the structured-output instruction's channel. The instruction now rides the SAME final-request enforcement listener that injects the schema'd tool: appended per request to final.system (per-request wire state, not agent prompt state). Tests assert the wire request (adapter.requests) instead of child.options; the bare-direct-dispatch test pins the no-system arm. - Tool guidance moved out of deployment prompts into per-tool prompt sections; the examples' workflow paragraph became a tool:<toolName> section contributed by dsh-tool-workflow (explicit-ask-only policy), and both example personas resolve to master's minimal identity+behavior form. tool-workflow gains inject: systemPrompt (+ peer dep, tsconfig ref); the export-shape guard updated. - The uniform-RFC-format gate: the dynamic-workflows RFC restructured to the implemented/ skeleton (bare Status line; Proposal -> Decision; What-was-rejected -> Alternatives considered; new Consequences), and the overall-run-timeout deferral is now recorded in the RFC's Deferred list. The doc-graphs atlas classification gains the workflows seam (workflow-vm implementation, tool-workflow consumer). Master's harness-identity section made "empty assembled prompt" states unreachable through the loop, so the instruction-append is a plain undefined-ternary and the structured tests assert append-not-replace. All snapshot goldens (including workflow-run) replay unchanged. Full local CI-equivalent gate sequence green on the merged tree.
This commit is contained in:
244 files changed
+6025
-1629
No files matched your search
@@ -0,0 +1,73 @@
|
||||
<!-- Generated by scripts/gen-doc-graphs.ts - do not edit by hand.
|
||||
Run `pnpm run gen-doc-graphs` to regenerate. -->
|
||||
|
||||
# Coding Agent App Composition
|
||||
|
||||
The coding REPL demo adds the real DeepSeek adapter, filesystem tools, todo_write, compaction, and both subagent transports on top of the stdio app package.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
cfg["examples/coding-agent<br/>cordis.yml"]
|
||||
plugin_coding_hmr["hmr<br/>@cordisjs/plugin-hmr"]
|
||||
cfg --> plugin_coding_hmr
|
||||
plugin_coding_llm_deepseek["llm-deepseek<br/>@deepseek-ai/dsh-llm-deepseek"]
|
||||
cfg --> plugin_coding_llm_deepseek
|
||||
plugin_coding_bash["bash<br/>@deepseek-ai/dsh-bash-local"]
|
||||
cfg --> plugin_coding_bash
|
||||
plugin_coding_stdio_agent["stdio-agent<br/>@deepseek-ai/dsh-stdio-agent"]
|
||||
cfg --> plugin_coding_stdio_agent
|
||||
plugin_coding_stdio_agent --> bundle_agent_core["@deepseek-ai/dsh-agent-core"]
|
||||
plugin_coding_stdio_agent --> bundle_jsonl["@deepseek-ai/dsh-session-persistence-jsonl"]
|
||||
plugin_coding_stdio_agent --> frontdoor_stdio["readline UI<br/>console logger<br/>pre-created main agent"]
|
||||
bundle_agent_core --> spine_llm["ctx.llm"]
|
||||
bundle_agent_core --> spine_sessions["ctx.sessions"]
|
||||
bundle_agent_core --> spine_tools["ctx.tools + tool-bash"]
|
||||
bundle_agent_core --> spine_loop["ctx.agents + ctx.agentLoop"]
|
||||
plugin_coding_compact_basic["compact-basic<br/>@deepseek-ai/dsh-compact-basic"]
|
||||
cfg --> plugin_coding_compact_basic
|
||||
plugin_coding_subagent["subagent<br/>@deepseek-ai/dsh-subagent"]
|
||||
cfg --> plugin_coding_subagent
|
||||
plugin_coding_subagent_spawn["subagent-spawn<br/>@deepseek-ai/dsh-subagent-spawn"]
|
||||
cfg --> plugin_coding_subagent_spawn
|
||||
plugin_coding_subagent_fork["subagent-fork<br/>@deepseek-ai/dsh-subagent-fork"]
|
||||
cfg --> plugin_coding_subagent_fork
|
||||
plugin_coding_tool_subagent["tool-subagent<br/>@deepseek-ai/dsh-tool-subagent"]
|
||||
cfg --> plugin_coding_tool_subagent
|
||||
plugin_coding_tool_subagent_fork["tool-subagent-fork<br/>@deepseek-ai/dsh-tool-subagent"]
|
||||
cfg --> plugin_coding_tool_subagent_fork
|
||||
plugin_coding_workflow_vm["workflow-vm<br/>@deepseek-ai/dsh-workflow-vm"]
|
||||
cfg --> plugin_coding_workflow_vm
|
||||
plugin_coding_tool_workflow["tool-workflow<br/>@deepseek-ai/dsh-tool-workflow"]
|
||||
cfg --> plugin_coding_tool_workflow
|
||||
plugin_coding_tool_todo["tool-todo<br/>@deepseek-ai/dsh-tool-todo"]
|
||||
cfg --> plugin_coding_tool_todo
|
||||
plugin_coding_fs_local["fs-local<br/>@deepseek-ai/dsh-fs-local"]
|
||||
cfg --> plugin_coding_fs_local
|
||||
plugin_coding_fs_policy["fs-policy<br/>@deepseek-ai/dsh-fs-policy"]
|
||||
cfg --> plugin_coding_fs_policy
|
||||
plugin_coding_tool_fs["tool-fs<br/>@deepseek-ai/dsh-tool-fs"]
|
||||
cfg --> plugin_coding_tool_fs
|
||||
```
|
||||
|
||||
| Plugin id | Package / module |
|
||||
| --- | --- |
|
||||
| `hmr` | `@cordisjs/plugin-hmr` |
|
||||
| `llm-deepseek` | `@deepseek-ai/dsh-llm-deepseek` |
|
||||
| `bash` | `@deepseek-ai/dsh-bash-local` |
|
||||
| `stdio-agent` | `@deepseek-ai/dsh-stdio-agent` |
|
||||
| `compact-basic` | `@deepseek-ai/dsh-compact-basic` |
|
||||
| `subagent` | `@deepseek-ai/dsh-subagent` |
|
||||
| `subagent-spawn` | `@deepseek-ai/dsh-subagent-spawn` |
|
||||
| `subagent-fork` | `@deepseek-ai/dsh-subagent-fork` |
|
||||
| `tool-subagent` | `@deepseek-ai/dsh-tool-subagent` |
|
||||
| `tool-subagent-fork` | `@deepseek-ai/dsh-tool-subagent` |
|
||||
| `workflow-vm` | `@deepseek-ai/dsh-workflow-vm` |
|
||||
| `tool-workflow` | `@deepseek-ai/dsh-tool-workflow` |
|
||||
| `tool-todo` | `@deepseek-ai/dsh-tool-todo` |
|
||||
| `fs-local` | `@deepseek-ai/dsh-fs-local` |
|
||||
| `fs-policy` | `@deepseek-ai/dsh-fs-policy` |
|
||||
| `tool-fs` | `@deepseek-ai/dsh-tool-fs` |
|
||||
|
||||
Source config: [`examples/coding-agent/cordis.yml`](cordis.yml).
|
||||
|
||||
Maintenance mode: hybrid: the leaf plugin list is parsed from its `cordis.yml`; app package expansion is curated from package source.
|
||||
@@ -28,9 +28,8 @@
|
||||
- deepseek-v4-pro
|
||||
- deepseek-v4-flash
|
||||
|
||||
# Local bash executor for agent-core's tool-bash schema.
|
||||
# FIXME(config-comments): keep this executor note from implying bash is the
|
||||
# whole tool set; filesystem, subagent, and todo_write are loaded below.
|
||||
# Local bash executor for agent-core's tool-bash schema (one of several tool
|
||||
# stacks in this tree: filesystem, subagent, and todo_write load below).
|
||||
- id: bash
|
||||
name: '@deepseek-ai/dsh-bash-local'
|
||||
config:
|
||||
@@ -46,39 +45,16 @@
|
||||
# under ./.sessions); unset starts a fresh session each run.
|
||||
resumeSessionId: !!js process.env.RESUME_SESSION_ID
|
||||
persistenceRoot: './.sessions'
|
||||
welcome: 'agent REPL ready. Give it a coding task (its tools are read, write, edit, bash, subagent, workflow, and todo_write).'
|
||||
systemPrompt: |
|
||||
You are coding-agent, a CLI coding assistant.
|
||||
welcome: 'agent REPL ready. Give it a coding task.'
|
||||
# The persona: identity + behavior only, nothing about transports or
|
||||
# tooling — tool guidance lives with each tool plugin (descriptions +
|
||||
# prompt sections). {{model}} is the prompt variable the agent loop
|
||||
# resolves from this agent's configured model.
|
||||
persona: |
|
||||
You are coding-agent, a coding assistant powered by the {{model}} model.
|
||||
|
||||
Your tools are read/write/edit for file operations, bash (plus
|
||||
bash_output/bash_kill for background tasks), and subagent. Use read to
|
||||
inspect UTF-8 text files, write to create or replace files, and edit for
|
||||
targeted literal replacements. Use bash for shell commands, tests,
|
||||
searches, and operations that are not ordinary file reads or edits. Each
|
||||
bash call runs in a fresh shell — pass workdir instead of cd, and never
|
||||
rely on shell state between calls.
|
||||
|
||||
Use the subagent tool to delegate a focused, self-contained subtask
|
||||
to a fresh child agent (it works in its own context and returns only
|
||||
its final result) — give it a complete, standalone instruction. Use
|
||||
subagent_fork instead when the subtask needs THIS conversation's
|
||||
context: the child inherits the log so far.
|
||||
|
||||
Use the workflow tool ONLY when the user explicitly asks for a
|
||||
workflow or for large multi-agent orchestration: you write a
|
||||
JavaScript script (its description documents the exact format) that
|
||||
fans work out across many subagents with phases and structured
|
||||
results. For one or two delegations, prefer plain subagent calls.
|
||||
|
||||
Check the [exit code: N] marker on every command; investigate
|
||||
failures before moving on. Verify your work by running the code or
|
||||
tests. Keep answers brief and factual.
|
||||
|
||||
For multi-step work, use the todo_write tool to track a task list:
|
||||
send the WHOLE list each call (it replaces the previous one), keep at
|
||||
most one task in_progress (exactly one while work remains), and mark a
|
||||
task completed as soon as it is done. Skip it for trivial single-step
|
||||
tasks.
|
||||
Verify your work by running the code or tests. Keep answers brief and
|
||||
factual.
|
||||
|
||||
# Automatic context compaction: when the derived history approaches the model's
|
||||
# context window, summarize an older range into a checkpoint so a long-running
|
||||
|
||||
@@ -53,11 +53,8 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('coding task: fix a failing test
|
||||
const before = spawnSync('node', ['add.test.js'], { cwd: workdir })
|
||||
expect(before.status).not.toBe(0)
|
||||
|
||||
ctx = await codingHarness(workdir)
|
||||
const agent = ctx.agentLoop.create(AgentId('e2e-task'), {
|
||||
model: 'deepseek-v4-flash',
|
||||
systemPrompt: SYSTEM_PROMPT,
|
||||
})
|
||||
ctx = await codingHarness(workdir, { persona: SYSTEM_PROMPT })
|
||||
const agent = ctx.agentLoop.create(AgentId('e2e-task'), { model: 'deepseek-v4-flash' })
|
||||
|
||||
agent.send([{
|
||||
type: 'text',
|
||||
|
||||
@@ -53,6 +53,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('compaction: a long session compa
|
||||
// budget even though those blocks are stripped before the checkpoint is
|
||||
// stored.
|
||||
ctx = await codingHarness(workdir, {
|
||||
persona: SYSTEM_PROMPT,
|
||||
compact: {
|
||||
contextWindow: 2400,
|
||||
thresholdRatio: 0.5,
|
||||
@@ -63,10 +64,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('compaction: a long session compa
|
||||
},
|
||||
persistenceRoot: './.sessions',
|
||||
})
|
||||
const agent = ctx.agentLoop.create(AgentId('e2e-compaction'), {
|
||||
model: 'deepseek-v4-flash',
|
||||
systemPrompt: SYSTEM_PROMPT,
|
||||
})
|
||||
const agent = ctx.agentLoop.create(AgentId('e2e-compaction'), { model: 'deepseek-v4-flash' })
|
||||
|
||||
agent.send([{
|
||||
type: 'text',
|
||||
|
||||
@@ -20,11 +20,8 @@ afterEach(async () => {
|
||||
|
||||
describe.skipIf(!process.env.DEEPSEEK_API_KEY)('full loop: real model + real bash tool', () => {
|
||||
it('runs a bash command on request and reports its output', async () => {
|
||||
ctx = await codingHarness(process.cwd())
|
||||
const agent = ctx.agentLoop.create(AgentId('e2e-loop'), {
|
||||
model: 'deepseek-v4-flash',
|
||||
systemPrompt: SYSTEM_PROMPT,
|
||||
})
|
||||
ctx = await codingHarness(process.cwd(), { persona: SYSTEM_PROMPT })
|
||||
const agent = ctx.agentLoop.create(AgentId('e2e-loop'), { model: 'deepseek-v4-flash' })
|
||||
|
||||
agent.send([{ type: 'text', text: 'Run `echo e2e-ok` with the bash tool and tell me its exact output.' }])
|
||||
await waitForIdle(ctx, agent)
|
||||
|
||||
@@ -33,6 +33,11 @@ export const TODO_SYSTEM_PROMPT = 'You are a coding agent. For multi-step work,
|
||||
|
||||
/** Options for {@link codingHarness}. */
|
||||
export interface CodingHarnessOptions {
|
||||
/**
|
||||
* Deployment persona for the tree (the system-prompt plugin's `persona`
|
||||
* config — per-context, not per-agent). Omitted ⇒ no persona section.
|
||||
*/
|
||||
persona?: string
|
||||
/** Durable JSONL persistence root (the resume suite needs it; others stay file-free). */
|
||||
persistenceRoot?: string
|
||||
/**
|
||||
@@ -47,7 +52,7 @@ export async function codingHarness(workdir: string, options: CodingHarnessOptio
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(LlmService)
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(SystemPrompt, { persona: options.persona ?? '' })
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
|
||||
@@ -38,11 +38,11 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('resume: continue a persisted ses
|
||||
// Run 1: a fresh agent on a KNOWN session id learns a secret, then we
|
||||
// dispose the whole context (simulating process exit) so only the JSONL
|
||||
// log on disk survives.
|
||||
ctx = await codingHarness(process.cwd(), { persistenceRoot: root })
|
||||
ctx = await codingHarness(process.cwd(), { persona: SYSTEM_PROMPT, persistenceRoot: root })
|
||||
const first = ctx.agents.create({
|
||||
agentId: AgentId('resume-1'),
|
||||
sessionId: SESSION_ID,
|
||||
agentOptions: { model: 'deepseek-v4-flash', systemPrompt: SYSTEM_PROMPT },
|
||||
agentOptions: { model: 'deepseek-v4-flash' },
|
||||
}).agent as ReactLoopAgent
|
||||
first.send([{ type: 'text', text: `Remember this code for later: ${SECRET}. Just acknowledge it.` }])
|
||||
await waitForIdle(ctx, first)
|
||||
@@ -52,11 +52,11 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('resume: continue a persisted ses
|
||||
// Run 2: a brand-new context over the SAME root resumes the persisted
|
||||
// session. The loaded event log seeds the live session, so the model sees
|
||||
// run 1's exchange as conversation history.
|
||||
ctx = await codingHarness(process.cwd(), { persistenceRoot: root })
|
||||
ctx = await codingHarness(process.cwd(), { persona: SYSTEM_PROMPT, persistenceRoot: root })
|
||||
const resumed = (await ctx.agents.resume({
|
||||
agentId: AgentId('resume-2'),
|
||||
resumeSessionId: SESSION_ID,
|
||||
agentOptions: { model: 'deepseek-v4-flash', systemPrompt: SYSTEM_PROMPT },
|
||||
agentOptions: { model: 'deepseek-v4-flash' },
|
||||
})).agent as ReactLoopAgent
|
||||
expect(resumed.session.id).toBe(SESSION_ID)
|
||||
// The prior user turn is in the rehydrated log before the model is asked.
|
||||
|
||||
@@ -18,11 +18,8 @@ afterEach(async () => {
|
||||
|
||||
describe.skipIf(!process.env.DEEPSEEK_API_KEY)('todo_write: real model records a plan', () => {
|
||||
it('appends a todo/write event with the model-produced task list', async () => {
|
||||
ctx = await codingHarness(process.cwd())
|
||||
const agent = ctx.agentLoop.create(AgentId('e2e-todo'), {
|
||||
model: 'deepseek-v4-flash',
|
||||
systemPrompt: TODO_SYSTEM_PROMPT,
|
||||
})
|
||||
ctx = await codingHarness(process.cwd(), { persona: TODO_SYSTEM_PROMPT })
|
||||
const agent = ctx.agentLoop.create(AgentId('e2e-todo'), { model: 'deepseek-v4-flash' })
|
||||
|
||||
agent.send([{ type: 'text', text:
|
||||
'Use the todo_write tool to record a plan of exactly two steps: first '
|
||||
|
||||
Reference in New Issue
Block a user