diff --git a/AGENTS.md b/AGENTS.md index e08dd02e5f..bfaf5a8e6f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -64,7 +64,10 @@ packages/ Harness packages, grouped by role at packages///. agent-loop/ THE concrete plugin: ReactLoopAgent + the loop driver agent-core/ bundle plugin: the providerless/executor-less/UI-less spine (timer+llm+sessions+system-prompt+tools+agents+invariants+ - tool-bash+agent-loop) as code; forwards agent-loop's `agents` + tool-bash+project-instructions+agent-loop) as code; forwards + agent-loop's `agents` + prompt/ prompt and request-context extensions + project-instructions/ AGENTS.md/CLAUDE.md workspace context loader llm/ LLM capability family llm/ abstract LLM service + content-block vocabulary llm-deepseek/ DeepSeek API adapter (hand-rolled fetch/SSE) diff --git a/docs/architecture.md b/docs/architecture.md index e6d8dffa9d..85b69cb1f4 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -97,6 +97,8 @@ Replay/fork = `ctx.sessions.create(id, { seed: seedEvents })`. Trace/telemetry = Plugins contribute `PromptSection`s (named, ordered, static or computed) and tool-schema providers. `assemble()` returns a `PromptAssembly { sections, tools }` through the `system-prompt/assemble` waterfall. +Prompt/context extension plugins that shape model inputs without owning a core service live under `packages/prompt/`. `dsh-project-instructions` is the reference case: it is semantically prompt/context assembly, but it uses the per-agent `agent/request` seam instead of a global `ctx.systemPrompt.section()` so concurrent sessions with different cwd values stay isolated. + Tool schemas are deliberately **part of the assembly**: "what the model is told it can do" is one coherent thing managed here, even though adapters transmit schemas as the wire-level `tools` field rather than prompt text. ## Tool pipeline (dsh-tools) diff --git a/docs/rfc/implemented/feature/2026-06-24-project-instruction-files.md b/docs/rfc/implemented/feature/2026-06-24-project-instruction-files.md index f328feb4f8..d0367d28b0 100644 --- a/docs/rfc/implemented/feature/2026-06-24-project-instruction-files.md +++ b/docs/rfc/implemented/feature/2026-06-24-project-instruction-files.md @@ -12,7 +12,7 @@ The non-obvious constraint is multi-session cwd. `dsh-system-prompt` sections ar ## Proposal -Add a new plugin package `packages/core/project-instructions` (`@deepseek-ai/dsh-project-instructions`). It is a single-purpose extension plugin, not an interface/implementation/consumer capability seam: there is no swappable backend, only filesystem discovery plus per-request context injection. It depends on interface packages only (`dsh-agent` and `dsh-llm`) and consumes the existing `agent/request` waterfall. +Add a new plugin package `packages/prompt/project-instructions` (`@deepseek-ai/dsh-project-instructions`). It is a single-purpose prompt/context extension plugin, not an interface/implementation/consumer capability seam: there is no swappable backend, only filesystem discovery plus per-request context injection. It depends on interface packages only (`dsh-agent` and `dsh-llm`) and consumes the existing `agent/request` waterfall. The plugin is loaded by `@deepseek-ai/dsh-agent-core` so both product front doors (`dsh-stdio-agent` and `dsh-acp-agent`) get instruction-file behavior by default. The bundle and both app packages expose `projectInstructions` config, so apps may set `projectInstructions: false` or `baselineMaxBytes: 0` when they need a hermetic prompt. The default product behavior matches user expectations for coding agents. @@ -102,7 +102,7 @@ Summarize instruction files before injection. This saves tokens but makes the in ## Plan -1. Add `packages/core/project-instructions` with config for `dshHome`, `projectRootMarkers` (default `['.git']`), `baselineMaxBytes` (default `65536`), and `enableClaudeFallback` (default `true`). Include pure discovery/rendering helpers so the filesystem rules can be tested without Cordis. +1. Add `packages/prompt/project-instructions` with config for `dshHome`, `projectRootMarkers` (default `['.git']`), `baselineMaxBytes` (default `65536`), and `enableClaudeFallback` (default `true`). Include pure discovery/rendering helpers so the filesystem rules can be tested without Cordis. 2. Implement baseline `agent/request` injection in `dsh-project-instructions`. The listener computes the instruction block for `agent.session.header.cwd` or the stdio-only `process.cwd()` fallback, prepends one synthetic workspace-context message to the request messages, and returns the request through `next()`. It must never mutate shared global prompt sections or the provider system field. diff --git a/knip.json b/knip.json index 7f696b2bbf..a2cfd91654 100644 --- a/knip.json +++ b/knip.json @@ -29,7 +29,7 @@ "entry": ["tests/**/*.spec.ts", "tests/**/*.e2e.ts"], "project": ["src/**/*.ts", "tests/**/*.ts"] }, - "packages/core/project-instructions": { + "packages/prompt/project-instructions": { "entry": ["tests/**/*.spec.ts", "tests/**/*.e2e.ts"], "project": ["src/**/*.ts", "tests/**/*.ts"] }, diff --git a/packages/README.md b/packages/README.md index 40cf3d7274..ad8b344460 100644 --- a/packages/README.md +++ b/packages/README.md @@ -9,6 +9,7 @@ Packages are grouped by modular role at `packages///`. The group dir | Group | Role | Release expectation | |---|---|---| | [`core/`](core/README.md) | Product API spine: session, system-prompt, tools, agent, and the concrete loop | Product — stable surface | +| [`prompt/`](prompt/README.md) | Prompt and request-context extensions that shape model inputs | Product — stable surface | | [`llm/`](llm/README.md) | LLM capability family: the abstract service + provider adapters | Product — stable surface | | [`bash/`](bash/README.md) | Bash capability family: the executor seam, a local impl, and the model-facing tool | Product — stable surface | | [`compact/`](compact/README.md) | Compaction capability family: the abstract seam (backend + tool deferred) | Product — stable surface | @@ -63,9 +64,9 @@ The rule: **extension** plugins depend on interfaces, never on the concrete loop | `system-prompt/` | `core` | Prompt-section + tool-schema assembly registry | `ctx.systemPrompt` | | `tools/` | `core` | Tool registry + `tools/execute` waterfall | `ctx.tools` | | `agent/` | `core` | Agent interface, registry, `agent/*` event vocabulary | `ctx.agents` | -| `project-instructions/` | `core` | `AGENTS.md`/`CLAUDE.md` workspace context loader | (listens on `agent/request`) | | `agent-loop/` | `core` | THE concrete loop plugin: `ReactLoopAgent` + the loop driver | `ctx.agentLoop` | | `agent-core/` | `core` | Bundle plugin: the providerless/executor-less/UI-less spine as code (forwards `agent-loop`'s `agents`) | (loads the spine) | +| `project-instructions/` | `prompt` | `AGENTS.md`/`CLAUDE.md` workspace context loader | (listens on `agent/request`) | | `bash/` | `bash` | Abstract bash executor seam (interface + vocabulary) | `ctx.bash` | | `bash-local/` | `bash` | Local-subprocess `BashExecutor` implementation | (registers `ctx.bash`) | | `tool-bash/` | `bash` | Model-facing `bash`/`bash_output`/`bash_kill` tool schemas | (registers on `ctx.tools`) | diff --git a/packages/core/README.md b/packages/core/README.md index 0a533b2270..ee32c86166 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -8,10 +8,9 @@ The packages every harness build is assembled from: the session log, the system- | `system-prompt/` | Prompt-section + tool-schema assembly registry | `ctx.systemPrompt` | | `tools/` | Tool registry + `tools/execute` waterfall | `ctx.tools` | | `agent/` | Agent interface, registry, `agent/*` event vocabulary | `ctx.agents` | -| `project-instructions/` | `AGENTS.md`/`CLAUDE.md` workspace context loader | (listens on `agent/request`) | | `agent-loop/` | The concrete loop plugin: `ReactLoopAgent` + the loop driver | `ctx.agentLoop` | | `agent-core/` | Bundle plugin: the providerless/executor-less/UI-less spine as code | (loads the spine) | `agent-loop` is the one concrete implementation of the `agent` seam and lives here because it is the harness's default product loop; everything else in `core/` is interface/vocabulary. Plugins depend on the `agent` vocabulary, never on `agent-loop` directly, so the loop stays swappable. -`agent-core` is the composition counterpart: one bundle plugin that loads the whole providerless spine (`timer` + `llm` + sessions + system-prompt + tools + agents + invariants + `tool-bash` + project-instructions + `agent-loop`) and forwards `agent-loop`'s `agents` list as its own config. App packages (`ui/stdio-agent`, `ui/acp-agent`) consume it and add only a front door; a leaf adds only the swappable backends. It lives in `core/` because it composes exclusively `core/` + interface packages and ships no provider, executor, or UI of its own. +`agent-core` is the composition counterpart: one bundle plugin that loads the whole providerless spine (`timer` + `llm` + sessions + system-prompt + tools + agents + invariants + `tool-bash` + project-instructions + `agent-loop`) and forwards `agent-loop`'s `agents` list as its own config. App packages (`ui/stdio-agent`, `ui/acp-agent`) consume it and add only a front door; a leaf adds only the swappable backends. It lives in `core/` because it is the default spine bundle and ships no provider, executor, or UI of its own; it may include product prompt/tool extensions that are common to every front door. diff --git a/packages/core/agent-core/tsconfig.json b/packages/core/agent-core/tsconfig.json index 26061457ce..e7972615c6 100644 --- a/packages/core/agent-core/tsconfig.json +++ b/packages/core/agent-core/tsconfig.json @@ -30,7 +30,7 @@ "path": "../../core/agent" }, { - "path": "../../core/project-instructions" + "path": "../../prompt/project-instructions" }, { "path": "../../core/agent-loop" diff --git a/packages/prompt/README.md b/packages/prompt/README.md new file mode 100644 index 0000000000..d702385e11 --- /dev/null +++ b/packages/prompt/README.md @@ -0,0 +1,9 @@ +# prompt/ — prompt and request-context extensions + +Product packages that contribute model-facing prompt or request-context behavior without being core agent/session/tool primitives. These packages usually consume existing seams such as `agent/request` or `system-prompt/assemble`; they do not own the loop and do not provide LLM adapters, execution backends, or UI front doors. + +| Package | Role | ctx key | +|---|---|---| +| `project-instructions/` | `AGENTS.md`/`CLAUDE.md` workspace context loader | (listens on `agent/request`) | + +`project-instructions` lives here because it is semantically a prompt/context extension: it adds workspace guidance to the model request. It deliberately uses the per-agent `agent/request` seam instead of a global `ctx.systemPrompt.section()` so multiple live sessions with different `cwd` values do not leak instruction files into one another. diff --git a/packages/core/project-instructions/README.md b/packages/prompt/project-instructions/README.md similarity index 100% rename from packages/core/project-instructions/README.md rename to packages/prompt/project-instructions/README.md diff --git a/packages/core/project-instructions/package.json b/packages/prompt/project-instructions/package.json similarity index 100% rename from packages/core/project-instructions/package.json rename to packages/prompt/project-instructions/package.json diff --git a/packages/core/project-instructions/src/index.ts b/packages/prompt/project-instructions/src/index.ts similarity index 100% rename from packages/core/project-instructions/src/index.ts rename to packages/prompt/project-instructions/src/index.ts diff --git a/packages/core/project-instructions/tests/project-instructions.e2e.ts b/packages/prompt/project-instructions/tests/project-instructions.e2e.ts similarity index 100% rename from packages/core/project-instructions/tests/project-instructions.e2e.ts rename to packages/prompt/project-instructions/tests/project-instructions.e2e.ts diff --git a/packages/core/project-instructions/tests/project-instructions.spec.ts b/packages/prompt/project-instructions/tests/project-instructions.spec.ts similarity index 98% rename from packages/core/project-instructions/tests/project-instructions.spec.ts rename to packages/prompt/project-instructions/tests/project-instructions.spec.ts index 77f46032b9..2ed5630bb2 100644 --- a/packages/core/project-instructions/tests/project-instructions.spec.ts +++ b/packages/prompt/project-instructions/tests/project-instructions.spec.ts @@ -1,7 +1,7 @@ import { chmod, mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises' import { join } from 'node:path' import { tmpdir } from 'node:os' -import { describe, expect, it } from 'vitest' +import { describe, expect, it, vi } from 'vitest' import { Context } from 'cordis' import type { GenerateOptions } from '@deepseek-ai/dsh-llm' import { Session, SessionId, SESSION_FORMAT_VERSION } from '@deepseek-ai/dsh-session' @@ -198,16 +198,18 @@ describe('project instruction discovery', () => { it('labels the default DSH home as ~/.dsh when HOME points at the configured default', async () => { const root = await tempRepo() const home = await tempRepo() - const previousHome = process.env.HOME try { - process.env.HOME = home await write(join(home, '.dsh/AGENTS.md'), 'global default rule') - const files = await discoverBaselineInstructionFiles({ cwd: root }) + vi.resetModules() + vi.doMock('node:os', () => ({ homedir: () => home })) + const isolated = await import('@deepseek-ai/dsh-project-instructions') + const files = await isolated.discoverBaselineInstructionFiles({ cwd: root }) expect(files.map(file => file.displayPath)).toEqual(['~/.dsh/AGENTS.md']) } finally { - process.env.HOME = previousHome + vi.doUnmock('node:os') + vi.resetModules() await rm(root, { recursive: true, force: true }) await rm(home, { recursive: true, force: true }) } diff --git a/packages/core/project-instructions/tsconfig.json b/packages/prompt/project-instructions/tsconfig.json similarity index 100% rename from packages/core/project-instructions/tsconfig.json rename to packages/prompt/project-instructions/tsconfig.json diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 17d832eb55..19b6cb77f0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -164,7 +164,7 @@ importers: version: link:../../llm/llm '@deepseek-ai/dsh-project-instructions': specifier: workspace:^ - version: link:../project-instructions + version: link:../../prompt/project-instructions '@deepseek-ai/dsh-session': specifier: workspace:^ version: link:../session @@ -215,37 +215,6 @@ importers: specifier: ^4.0.0-rc.6 version: 4.0.0-rc.6(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.4) - packages/core/project-instructions: - dependencies: - schemastery: - specifier: ^3.18.0 - version: 3.18.0 - devDependencies: - '@deepseek-ai/dsh-agent': - specifier: workspace:^ - version: link:../agent - '@deepseek-ai/dsh-agent-loop': - specifier: workspace:^ - version: link:../agent-loop - '@deepseek-ai/dsh-llm': - specifier: workspace:^ - version: link:../../llm/llm - '@deepseek-ai/dsh-llm-deepseek': - specifier: workspace:^ - version: link:../../llm/llm-deepseek - '@deepseek-ai/dsh-session': - specifier: workspace:^ - version: link:../session - '@deepseek-ai/dsh-system-prompt': - specifier: workspace:^ - version: link:../system-prompt - '@deepseek-ai/dsh-tools': - specifier: workspace:^ - version: link:../tools - cordis: - specifier: ^4.0.0-rc.6 - version: 4.0.0-rc.6(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.4) - packages/core/session: devDependencies: '@deepseek-ai/dsh-brand': @@ -323,6 +292,37 @@ importers: specifier: ^4.0.0-rc.6 version: 4.0.0-rc.6(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.4) + packages/prompt/project-instructions: + dependencies: + schemastery: + specifier: ^3.18.0 + version: 3.18.0 + devDependencies: + '@deepseek-ai/dsh-agent': + specifier: workspace:^ + version: link:../../core/agent + '@deepseek-ai/dsh-agent-loop': + specifier: workspace:^ + version: link:../../core/agent-loop + '@deepseek-ai/dsh-llm': + specifier: workspace:^ + version: link:../../llm/llm + '@deepseek-ai/dsh-llm-deepseek': + specifier: workspace:^ + version: link:../../llm/llm-deepseek + '@deepseek-ai/dsh-session': + specifier: workspace:^ + version: link:../../core/session + '@deepseek-ai/dsh-system-prompt': + specifier: workspace:^ + version: link:../../core/system-prompt + '@deepseek-ai/dsh-tools': + specifier: workspace:^ + version: link:../../core/tools + cordis: + specifier: ^4.0.0-rc.6 + version: 4.0.0-rc.6(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.4) + packages/session-persistence/session-persistence: devDependencies: '@deepseek-ai/dsh-session': @@ -690,7 +690,7 @@ importers: version: link:../../core/agent-core '@deepseek-ai/dsh-project-instructions': specifier: workspace:^ - version: link:../../core/project-instructions + version: link:../../prompt/project-instructions '@deepseek-ai/dsh-session-persistence-jsonl': specifier: workspace:^ version: link:../../session-persistence/session-persistence-jsonl @@ -720,7 +720,7 @@ importers: version: link:../../core/agent-core '@deepseek-ai/dsh-project-instructions': specifier: workspace:^ - version: link:../../core/project-instructions + version: link:../../prompt/project-instructions '@deepseek-ai/dsh-session': specifier: workspace:^ version: link:../../core/session diff --git a/tsconfig.base.json b/tsconfig.base.json index 7f46a9105a..59fa233622 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -41,6 +41,7 @@ // explicit — TS project references have no wildcard form. "@deepseek-ai/dsh-*": [ "./packages/core/*/src", + "./packages/prompt/*/src", "./packages/llm/*/src", "./packages/bash/*/src", "./packages/compact/*/src", diff --git a/tsconfig.build.json b/tsconfig.build.json index d7e323c135..4538eefc45 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -19,7 +19,7 @@ { "path": "./packages/core/system-prompt" }, { "path": "./packages/core/agent" }, { "path": "./packages/core/tools" }, - { "path": "./packages/core/project-instructions" }, + { "path": "./packages/prompt/project-instructions" }, { "path": "./packages/core/agent-loop" }, { "path": "./packages/core/agent-core" }, { "path": "./packages/bash/bash" }, diff --git a/tsconfig.json b/tsconfig.json index 1261223512..d8d91b690d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -30,7 +30,7 @@ { "path": "./packages/core/system-prompt" }, { "path": "./packages/core/agent" }, { "path": "./packages/core/tools" }, - { "path": "./packages/core/project-instructions" }, + { "path": "./packages/prompt/project-instructions" }, { "path": "./packages/core/agent-loop" }, { "path": "./packages/core/agent-core" }, { "path": "./packages/bash/bash" },