* fix(agent-instructions): budget the user-global and project chains separately The user-global and project instruction files shared one maxBytes and the user-global files were the broadest, so a large project chain omitted them first. Each chain now renders under maxBytes on its own and the results share one system-reminder frame: unchanged output when everything fits or one chain is empty, at most twice maxBytes otherwise. The project chain keeps its omit-broadest-first, truncate-last rule; reconciliation batches follow the same split; the maxBytes key and the baseline identity are unchanged. Tests cover the fast paths, both chains fitting only separately, each chain over its own budget, the 2 × maxBytes boundary at exact and tiny limits, multibyte content, reconciliation batches, and a resumed single-budget baseline receiving the user-global file as one transition. The agent-instructions-budget snapshot is refreshed and agent-instructions-budget-chains is added. README, Config.maxBytes JSDoc, the configuration catalog, a new Agent Note, and an upgrade guide state the per-chain bound. * fix(agent-instructions): keep loaded order in merged frames and note the unshared chain budget A merged frame now lists sections, omissions and truncations in the loaded order, so a reconciliation batch renders its items in one order whether or not it fits a single budget. The guard comment states only the invariant and points to the Agent Note; the intro selection states its rule. The README names the unshared budget between chains. --------- Co-authored-by: ZiyaZhang <[email protected]>
240 lines
17 KiB
Markdown
240 lines
17 KiB
Markdown
---
|
|
description: "Workspace-instruction context for users and maintainers enabling, sizing, or debugging AGENTS.md/CLAUDE.md loading and refresh."
|
|
kind: "package-reference"
|
|
---
|
|
|
|
# @deepseek-ai/dsh-agent-instructions
|
|
|
|
English | [中文](README.zh.md)
|
|
|
|
## Summary
|
|
|
|
`dsh-agent-instructions` gives agents workspace guidance from user-global and project-level `AGENTS.md`-compatible files. It loads the applicable chain for the first request. It does not watch external edits continuously: successful filesystem operations discover newly relevant nested files and make later changes or removals visible, while session resume reconciles the baseline. `dsh-base` enables this behavior by default, while profiles can disable it. A byte budget bounds the user-global chain and the project chain separately: within each chain, broader files are omitted before the most specific file is truncated, and an empty chain adds nothing.
|
|
|
|
## Table of Contents
|
|
|
|
- [Use this package](#use-this-package)
|
|
- [Understand the implementation](#understand-the-implementation)
|
|
- [Further Exploration](#further-exploration)
|
|
- [Model Experience](#model-experience)
|
|
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
|
|
- [Dev Note](#dev-note)
|
|
|
|
-----
|
|
|
|
<a id="use-this-package"></a>
|
|
## Use this package
|
|
|
|
Changing the Session's working directory reloads the applicable project instruction chain before the next request.
|
|
|
|
Mount this plugin when agents should work from the workspace's own instruction files. `dsh-base` already includes it with a 65,536-byte budget for each instruction chain, so base-backed profiles only need to replace the row when they want another `maxBytes`; providerless trees load nothing until a filesystem provider is present.
|
|
|
|
### What the agent gets
|
|
|
|
The first request includes one durable baseline message with the user-global chain — `$DSH_HOME/AGENTS.md`, then `<agentsHome>/AGENTS.md` — followed by the project chain: every existing candidate file from the project root down to the session working directory, in broad-to-specific order. Within one candidate group, files whose content matches after trimming render once: a `CLAUDE.md` that duplicates its `AGENTS.md` is not repeated, and a shared-root `AGENTS.md` that duplicates the harness-home file collapses behind it. After a successful `read`, `write`, or `edit` call reaches a deeper directory, the next request includes the newly applicable instruction file; a changed file replaces its content, and a file that disappears or duplicates an earlier candidate produces a removal notice.
|
|
|
|
### Configuration
|
|
|
|
The defaults suit a typical checkout: `.git` marks the project root, `AGENTS.md` and `CLAUDE.md` are the base candidates, and `AGENTS.local.md` and `CLAUDE.local.md` are additive local overlays. Only `maxBytes` is required — it caps the user-global chain and the project chain separately within each rendered message, so each deployment chooses its prompt budget explicitly; a message that holds both chains is at most twice `maxBytes`.
|
|
|
|
The harness home holding the user-global `AGENTS.md` is process policy rather than row configuration: the plugin resolves `$DSH_HOME`, or `~/.dsh`, through `@deepseek-ai/dsh-home-paths`, so instruction loading always reads the process home. The shared agents root resolves the same way, from `$DSH_AGENTS_HOME` or `~/.agents`. Stray `dshHome` and `agentsHome` row keys are ignored.
|
|
|
|
Root discovery climbs only when a marker probe confirms that the marker is absent. A permission or I/O failure stops discovery and surfaces the host or filesystem-provider error instead of selecting an ancestor project. The [historical root-marker metadata decision](../../../.agents/notes/archived/bug-fix/2026-09-03-root-marker-metadata-failures.md) records why discovery fails instead of substituting another root.
|
|
|
|
```yaml
|
|
- name: '@deepseek-ai/dsh-agent-instructions'
|
|
config:
|
|
maxBytes: 65536
|
|
```
|
|
|
|
The accepted fields, at a glance:
|
|
|
|
```ts
|
|
export interface Config {
|
|
projectRootMarkers?: string[]
|
|
maxBytes: number
|
|
maxSourceBytes?: number
|
|
instructionFileCandidates?: string[]
|
|
localInstructionFileCandidates?: string[]
|
|
}
|
|
```
|
|
|
|
| Field | Default | Meaning |
|
|
|---|---|---|
|
|
| `maxBytes` | required | Cap on each instruction chain (user-global, project) in one rendered message, in bytes; a message holding both chains is at most twice this size |
|
|
| `maxSourceBytes` | `1048576` | Cap on one source instruction file before rendering |
|
|
| `projectRootMarkers` | `['.git']` | Directory names that mark the project root |
|
|
| `instructionFileCandidates` | `['AGENTS.md', 'CLAUDE.md']` | Base file names loaded in each project directory |
|
|
| `localInstructionFileCandidates` | `['AGENTS.local.md', 'CLAUDE.local.md']` | Local overlay file names loaded after the base files |
|
|
|
|
The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-agent-instructions) is the exhaustive source for every accepted field and its JSDoc.
|
|
|
|
### Observing the budget
|
|
|
|
Rendering budgets the user-global chain and the project chain separately, each under `maxBytes`. Within a chain it keeps the most specific files first: it drops whole broader files, truncates the last remaining file, and then falls back to a compact intro, to the budget notice with that file's empty heading, and finally to the notice alone. User-global files are therefore omitted or truncated only when they exceed their own budget, with the harness-home file omitted before the shared-root file, except in the fallback below. A visible `Workspace instruction budget ...` notice names the omitted and truncated paths of both chains. An over-budget broad file is ignored; during refresh it is treated as temporarily unavailable rather than removed.
|
|
|
|
When all files fit within `maxBytes` together, the message holds them with no budget notice; when only one chain has files, that chain renders alone under `maxBytes`. Otherwise one `<system-reminder>` frame holds the merged budget notice when a file was omitted or truncated, one intro when a chain kept its frame, and the rendered sections of both chains in their loaded order, which puts user-global files first in a baseline; this message can exceed `maxBytes` and is at most twice that size. When a chain renders without a frame and the merged frame would exceed twice `maxBytes`, all files render as one chain under one `maxBytes` instead, which can omit a user-global file that fit its own budget; this needs a budget of a few hundred bytes or a budget notice that nearly fills or exceeds `maxBytes`. The [per-chain budget decision](../../../.agents/notes/implemented/architecture/2026-10-16-separate-instruction-chain-budgets.md) records why the two chains are budgeted separately.
|
|
|
|
-----
|
|
|
|
<a id="understand-the-implementation"></a>
|
|
## Understand the implementation
|
|
|
|
<details>
|
|
<summary>Implementation internals — click to expand</summary>
|
|
|
|
This section explains the design decisions behind the plugin; the observable behavior is covered in [Use this package](#use-this-package).
|
|
|
|
### Design concept
|
|
|
|
The plugin is built on one principle: workspace instructions are durable conversation content, owned per agent and per session. Baseline and refresh messages are ordinary sourced `user/message` events, so they replay, compact, and resume exactly like other history, and model-visible state is always reconstructable from the session log. The plugin owns the complete `<system-reminder>` framing and every injected message reaches the model verbatim.
|
|
|
|
### Source map
|
|
|
|
| File | Role |
|
|
|---|---|
|
|
| [`src/index.ts`](src/index.ts) | Plugin entry: pre-step listener, `tools/result` touch tracking, inbox composition |
|
|
| [`src/config.ts`](src/config.ts) | `Config` schema, budget resolution, baseline identity |
|
|
| [`src/files.ts`](src/files.ts) | Candidate discovery, project-root search, bounded streaming reads |
|
|
| [`src/render.ts`](src/render.ts) | Instruction rendering, per-chain budget truncation, change records |
|
|
| [`src/state.ts`](src/state.ts) | Durable message sources, version/digest cache, reconciliation |
|
|
| [`src/digest.ts`](src/digest.ts) | SHA-1 content identity and per-candidate-group duplicate keys |
|
|
|
|
### Main flow
|
|
|
|
At the first eligible `agent/pre-step` of a session, the plugin composes the baseline and folds it into the entering batch right after the claimed messages. Successful first-party `read`, `write`, and `edit` calls contribute touches that bubble up through parent execution tokens; once the enclosing step is durable, a projection reconciles the visible session state against the inbox and queues additions, replacements, or removals. An unchanged path with an unchanged digest is never injected again. Discovery follows structured filesystem activity rather than shell navigation, because each local shell call starts a fresh process and parsing arbitrary shell syntax is not a reliable filesystem seam.
|
|
|
|
### Invariants
|
|
|
|
Every injected message carries a typed source with its change list; a complete baseline also carries an identity derived from normalized discovery, precedence, project-root, and budget configuration, and a matching durable message confirms a queued baseline. Model-visible text contains no hidden state markers, and literal `</system-reminder>` text anywhere in instruction content or model-visible metadata is escaped so repository-controlled text cannot close the plugin-owned frame.
|
|
|
|
</details>
|
|
|
|
-----
|
|
|
|
<a id="further-exploration"></a>
|
|
## Further Exploration
|
|
|
|
Read these pages when the package-level contract is not enough. They move from the instruction-file format to the design decision and the exhaustive configuration.
|
|
|
|
- [Documentation standard](../../../docs/AGENTS.md) — what `AGENTS.md` instruction files contain and how they are maintained.
|
|
- [Workspace-context decision record](../../../.agents/notes/archived/feature/2026-06-24-workspace-context.md) — per-agent/session isolation and lifecycle rationale.
|
|
- [Context group map](../README.md) — sibling request-context packages.
|
|
- [Generated configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-agent-instructions) — every accepted config field and its source declaration.
|
|
|
|
-----
|
|
|
|
<a id="model-experience"></a> <a id="prompt-shape"></a>
|
|
## Model Experience
|
|
|
|
### Baseline context
|
|
|
|
#### What the model sees
|
|
|
|
At the first request, derived history contains one durable user-role message with the bounded user-global and project instruction chain in broad-to-specific order. Resume reuses that message when its visible baseline is compatible.
|
|
|
|
##### Baseline instruction template
|
|
|
|
```markdown
|
|
<system-reminder>
|
|
The following workspace instructions may be relevant to your work. Use them as guidance when applicable. More specific instructions take precedence over broader ones. They do not override system, developer, or direct user instructions.
|
|
|
|
Instructions from: ~/.dsh/AGENTS.md
|
|
|
|
<user-global-instructions>
|
|
|
|
Instructions from: ~/.agents/AGENTS.md
|
|
|
|
<shared-agents-instructions>
|
|
|
|
Instructions from: AGENTS.md
|
|
|
|
<project-instructions>
|
|
</system-reminder>
|
|
```
|
|
|
|
#### Token effect
|
|
|
|
The rendered baseline is appended once and remains in derived history until compaction. `maxBytes` bounds the user-global chain and the project chain separately, so a baseline that holds both is at most twice `maxBytes`; within each chain, broader files are omitted before the most-specific file is truncated, and an empty chain contributes zero tokens.
|
|
|
|
#### KV Cache effect
|
|
|
|
Append-only after the existing reusable prefix. Resume preserves reuse when the visible baseline identity is compatible; an incompatible identity appends a complete replacement, so discovery, precedence, project-root, or budget changes affect reuse only from that history position.
|
|
|
|
### Newly discovered scope context
|
|
|
|
#### What the model sees
|
|
|
|
After a successful first-party filesystem call reaches a deeper directory, the next request includes one retained sourced `user/message` with the newly applicable instruction file. A user-global file discovered after the baseline renders the same section with `These user-global instructions apply to all work.` in place of the scope sentence below, because a user-global scope directory is an internal reconciliation key rather than a project directory.
|
|
|
|
##### Additional instruction template
|
|
|
|
```markdown
|
|
<system-reminder>
|
|
Additional instructions from: packages/app/AGENTS.md
|
|
|
|
These instructions apply to work under `packages/app`. Use them as guidance when relevant; more specific instructions take precedence. They do not override system, developer, or direct user instructions.
|
|
|
|
<nested-instructions>
|
|
</system-reminder>
|
|
```
|
|
|
|
#### Token effect
|
|
|
|
Each discovered scope adds bounded history tokens until compaction. Unchanged content is suppressed by visible session state plus version/digest comparison, and PTC mode defers the same message until after the outer `run_code` result and its enclosing durable step.
|
|
|
|
#### KV Cache effect
|
|
|
|
Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV Cache entries.
|
|
|
|
### Changed or removed instruction context
|
|
|
|
#### What the model sees
|
|
|
|
A changed file produces `Updated instructions from: <path>` plus its replacement content. A candidate that disappears or becomes a per-directory duplicate of an earlier candidate produces the removal notice below.
|
|
|
|
##### Removal notice
|
|
|
|
```markdown
|
|
<system-reminder>
|
|
Instructions removed: packages/app/AGENTS.md
|
|
|
|
The previously loaded instructions from this file no longer apply.
|
|
</system-reminder>
|
|
```
|
|
|
|
#### Token effect
|
|
|
|
Each batch of confirmed changes or removals is one retained history message. `maxBytes` bounds each instruction chain in it, so a batch with user-global and project changes is at most twice `maxBytes`. Provider failures add no message, and an update omitted by the budget remains eligible for a later filesystem touch.
|
|
|
|
#### KV Cache effect
|
|
|
|
Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV Cache entries.
|
|
|
|
## Known Limitations and Deferred Work
|
|
|
|
<a id="known-limitations-and-deferred-work"></a>
|
|
|
|
|
|
These limits define when instruction loading is a poor fit or needs operational awareness. They are current package constraints, not a task backlog.
|
|
|
|
- **Discovery follows structured fs tools, not shell navigation** — a `bash` command that changes directories does not trigger nested instruction discovery because shell syntax and per-call shell state are not a reliable filesystem seam.
|
|
- **Refresh is touch-driven** — there is no watcher; external edits become visible on the next successful first-party `read`, `write`, or `edit`, when resume reconciles a visible baseline, or when an entering pre-step restores a shadowed baseline.
|
|
- **Candidate semantics stay intentionally small** — lowercase names, `.claude/rules/`, and `@path` imports are not interpreted; project scopes load `AGENTS.local.md`/`CLAUDE.local.md` overlays by default, but neither user-global root has a local overlay and other custom names require explicit candidate configuration. A project directory named like a user-global scope directory renders as `./<name>/...`, so it keeps its own scope instead of resolving to the global root.
|
|
- **Deduplication is content-based within one candidate group** — candidates collapse only when byte-identical after trimming leading and trailing whitespace. The two user-global roots share one group, so the shared-root file collapses behind a matching harness-home file; sibling project candidates collapse the same way, while a distinct real copy that has drifted from `AGENTS.md` loads in full alongside it. A duplicate of budget-retained content stays reconciled: when the retained candidate changes or disappears, the duplicate becomes visible again. Unchanged hidden duplicates, including files removed from visible state as duplicates, use cached metadata instead of content reads.
|
|
- **One candidate group shares failure fate** — when metadata or content cannot be read for any candidate, including a hidden duplicate, the group keeps its last-good state for that pass. An unreadable shared agents root therefore also postpones a harness-home change notice until a later pass observes both roots.
|
|
- **A byte-budget omission does not re-enter reconciliation** — a candidate that its chain's budget dropped from the baseline, including any content duplicate of that candidate, is not probed again while that visible baseline holds; it becomes visible only after the baseline is rebuilt.
|
|
- **Symlinked instruction files are followed across the trust boundary** — a candidate whose final component is a symlink is resolved and its target loaded, so a cloned repository can surface off-tree file content as lower-authority workspace guidance (it never overrides system, developer, or direct user instructions). Confine `ctx.fs` with the filesystem policy gate or an OS sandbox when loading untrusted repositories.
|
|
- **Instruction content is bounded, not summarized** — within each chain, over-budget broad files are omitted and the chain's most-specific file may be truncated; the plugin never asks a model to compress instruction prose.
|
|
- **Budget is not shared across chains** — bytes one chain leaves unused do not extend the other, so a 70 KiB user-global file is truncated to fit `maxBytes` even when the project chain uses 1 KiB.
|
|
|
|
<a id="dev-note"></a>
|
|
### Dev Note
|
|
|
|
<details>
|
|
<summary>Working context for maintainers — click to expand</summary>
|
|
|
|
None.
|
|
|
|
</details>
|