diff --git a/home/base/tui/agents/README.md b/home/base/tui/agents/README.md index c14bf353..a82c635d 100644 --- a/home/base/tui/agents/README.md +++ b/home/base/tui/agents/README.md @@ -13,8 +13,8 @@ Nix dependency; this module only wires it into place. - `packages.nix`: agent CLIs (`codex`, `opencode2`, `pi`) from the `llm-agents` flake input, plus `pkgs.mcp-grafana` for the Grafana MCP server. - `env.nix`: telemetry and auto-update opt-outs for the agents. -- `dsh/`: the dsh `web` profile that lives in `~/.dsh`. See [dsh/README.md](./dsh/README.md) for the - link granularity, the tracked members and which hosts import it. +- `dsh/`: the shared dsh home-layer patch linked to `~/.dsh/cordis.patch.yml`. See + [dsh/README.md](./dsh/README.md) for why the shared layer is linked and the profile patch is not. ## Deployed rule targets diff --git a/home/base/tui/agents/dsh/README.md b/home/base/tui/agents/dsh/README.md index 514c5951..b668b0c7 100644 --- a/home/base/tui/agents/dsh/README.md +++ b/home/base/tui/agents/dsh/README.md @@ -1,26 +1,21 @@ # dsh -The dsh (DeepSeek Harness) `web` profile, linked into `~/.dsh/profiles/web` from the checked-in -files under [`web/`](./web). +Shared dsh (DeepSeek Harness) policy, linked into `~/.dsh/cordis.patch.yml`. -## Why the link targets the directory +## Layers -dsh persists Settings through `cordis.patch.yml` with an atomic write (a random-suffix sibling, then -a rename), and that rename replaces a symlinked target instead of writing through to its referent. A -file-level link — store or out-of-store — is destroyed by the first Settings save, while a directory -link keeps the write inside the checkout. Same reasoning as `home/base/tui/tuios`, one level up. +dsh composes a profile from bundle patches, the profile's own `cordis.patch.yml`, then +`$DSH_HOME/cordis.patch.yml` (the "home" layer) and `--patch` overlays. The home layer is the only +one shared by every profile and surface, so the stable policy lives here. -| Profile member | Tracked | Role | -| --------------------- | ------- | ------------------------------------------ | -| `cordis.patch.yml` | yes | the user patch layer the Settings UI edits | -| `package.json` | yes | the profile's `dsh.profile.bundles` list | -| `pnpm-workspace.yaml` | yes | the profile's pnpm settings | -| `cordis.yml` | no | rewritten on every boot | -| `node_modules/` | no | written by `dsh plugin --profile web ...` | +| Layer | Path | Writer | Tracked | +| ------- | ----------------------------------------- | ----------------------------- | ------- | +| home | `~/.dsh/cordis.patch.yml` | nix-config; dsh only reads it | yes | +| profile | `~/.dsh/profiles//cordis.patch.yml` | dsh, at runtime | no | -`web/.gitignore` owns the untracked rows. As with the rules links in the parent module, the -trade-off is a config that stays writable in the checkout: a Settings change and a commit are the -same edit, and the configuration is shared by every host that deploys the profile. +dsh writes the profile patch but only reads the home layer, so linking the home layer is safe while +linking the profile patch would put runtime state in the checkout. Declared settings are edited +here, not in the Settings UI. ## Which hosts deploy it @@ -31,11 +26,34 @@ host that ships dsh needs one of those two wirings. ## Default model route -`web/cordis.patch.yml` pins `agent-default-model` to the API-key route (`deepseek-official`). The +`cordis.patch.yml` pins `agent-default-model` to the API-key route (`deepseek-official`). The account route (`deepseek-account`) never falls back to an API key, so a host holding only `DEEPSEEK_API_KEY` fails every freshly created session with `ACCOUNT_SIGN_IN_REQUIRED`. A host that -signs in instead can select an account model per session, or override the field in its own -`$DSH_HOME/cordis.patch.yml`, which the home layer applies last and which therefore wins. +signs in instead can select an account model per session. + +## Compaction + +The web and desktop surfaces give each agent preset its own compaction service, so a top-level +`compaction-basic` row here never reaches a session. The effective knobs are the model +`contextWindow` and `maxTokens`: the `400000`/`32000` values set here place the first compaction +near 300k tokens. + +## Private and local model routes + +Endpoints that must not enter this repository — LAN/self-hosted servers and staging gateways — do +not belong in the home layer. Declare them locally as a separate pi-ai instance in the profile +patch, which dsh does not deploy from here: + +```yaml +# ~/.dsh/profiles//cordis.patch.yml +- insert: + - id: llm-pi-ai-private + name: "@deepseek-ai/dsh-llm-pi-ai" + config: + providers: { ... } +``` + +The private instance coexists with the shared one under its own id. ## Running it diff --git a/home/base/tui/agents/dsh/cordis.patch.yml b/home/base/tui/agents/dsh/cordis.patch.yml new file mode 100644 index 00000000..f9112073 --- /dev/null +++ b/home/base/tui/agents/dsh/cordis.patch.yml @@ -0,0 +1,70 @@ +# Shared dsh policy. Linked to ~/.dsh/cordis.patch.yml and applied as the +# $DSH_HOME ("home") layer, after each profile's own patch, so it covers every +# profile and surface. dsh only reads it — keep runtime preferences and local +# model routes in the profile patch (see README.md). +- id: agent-default-model + name: "@deepseek-ai/dsh-agent-default-model" + config: + provider: deepseek-official + model: deepseek-flash + reasoningEffort: high +- id: locale + name: "@deepseek-ai/dsh-client-locale" + config: + preference: zh +- id: agent-preset-registry + name: "@deepseek-ai/dsh-agent-preset-registry" + config: + default: standard + selectedDefault: ptc +- id: llm-deepseek + name: "@deepseek-ai/dsh-llm-deepseek-api-key" + config: + models: + - id: deepseek-flash + name: DeepSeek-V41-Flash + inputModalities: + - text + - image + systemPromptUpdate: in-history + toolUpdate: addition-only + contextWindow: 400000 + maxTokens: 32000 + - id: deepseek-v4-pro + name: DeepSeek-V4-Pro + description: + Stronger agentic coding, knowledge, and difficult reasoning; suited to complex or + quality-critical tasks at higher cost. + contextWindow: 500000 + inputModalities: + - text +- id: llm-deepseek-account + name: "@deepseek-ai/dsh-llm-deepseek-account" + config: + models: + - id: deepseek-flash + name: DeepSeek-V41-Flash + inputModalities: + - text + - image + systemPromptUpdate: in-history + toolUpdate: addition-only + contextWindow: 400000 + maxTokens: 32000 + - id: deepseek-v4-pro + name: DeepSeek-V4-Pro + description: + Stronger agentic coding, knowledge, and difficult reasoning; suited to complex or + quality-critical tasks at higher cost. + contextWindow: 500000 + inputModalities: + - text +- id: agent-loop + name: "@deepseek-ai/dsh-agent-loop" + config: + agents: [] + maxParallelToolCalls: 8 +- id: subagent + name: "@deepseek-ai/dsh-subagent" + config: + maxActiveSubagents: 3 diff --git a/home/base/tui/agents/dsh/default.nix b/home/base/tui/agents/dsh/default.nix index 1fe83d4e..68c915a1 100644 --- a/home/base/tui/agents/dsh/default.nix +++ b/home/base/tui/agents/dsh/default.nix @@ -3,19 +3,10 @@ ... }: let - # dsh persists Settings through the profile's cordis.patch.yml with an atomic - # write (a random-suffix sibling, then a rename), and that rename replaces a - # symlinked target instead of writing through to its referent. A file-level - # link, store or out-of-store, therefore survives only until the first save. - # Linking the whole profile directory keeps the write inside the checkout and - # leaves the directory symlink intact. Same reasoning as - # `home/base/tui/tuios`, one level up; the checkout path is hardcoded like the - # repo's other out-of-store links. - profileDir = "${config.home.homeDirectory}/nix-config/home/base/tui/agents/dsh/web"; + # Out-of-store so repo edits apply without a rebuild (see README.md). + sharedPatch = "${config.home.homeDirectory}/nix-config/home/base/tui/agents/dsh/cordis.patch.yml"; in { - # The `web` profile's configuration, owned by hand and by the Settings UI. - # Generated members of the profile (cordis.yml, node_modules, lock files) are - # gitignored inside that directory rather than tracked. - home.file.".dsh/profiles/web".source = config.lib.file.mkOutOfStoreSymlink profileDir; + # The shared dsh policy, read as $DSH_HOME/cordis.patch.yml. + home.file.".dsh/cordis.patch.yml".source = config.lib.file.mkOutOfStoreSymlink sharedPatch; } diff --git a/home/base/tui/agents/dsh/web/.gitignore b/home/base/tui/agents/dsh/web/.gitignore deleted file mode 100644 index f50e4f3d..00000000 --- a/home/base/tui/agents/dsh/web/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -# Regenerated by dsh on every boot (always the empty root entry list). -cordis.yml - -# Written by `dsh plugin --profile web ...`. -node_modules/ -pnpm-lock.yaml - -# Transient siblings of the atomic-write and file-lock protocols. -*.tmp -*.lock diff --git a/home/base/tui/agents/dsh/web/cordis.patch.yml b/home/base/tui/agents/dsh/web/cordis.patch.yml deleted file mode 100644 index 37e3a301..00000000 --- a/home/base/tui/agents/dsh/web/cordis.patch.yml +++ /dev/null @@ -1,77 +0,0 @@ -# Your patch layer for this dsh profile, applied after every bundle layer: -# a top-level YAML array of loader patch entries (id-targeted config -# overrides, disables, and insert lists; `!!js` expressions allowed). -- id: ui-settings-general - name: "@deepseek-ai/dsh-client-ui-settings-general" - config: - welcomeNoticeVersion: 2026-09-28.1 -- id: agent-default-model - name: "@deepseek-ai/dsh-agent-default-model" - config: - provider: deepseek-official - model: deepseek-flash - reasoningEffort: high -- id: ui-settings-account - name: "@deepseek-ai/dsh-client-ui-settings-account" - config: - version: 1 - step: done - purpose: development - process: standard - completion: completed - usage: detailed - developerTools: true -- id: ui-chat - name: "@deepseek-ai/dsh-client-ui-chat" - config: - transcriptView: standard - performanceUsage: detailed -- id: ui-settings - name: "@deepseek-ai/dsh-client-ui-settings" - config: - enabled: true -- id: locale - name: "@deepseek-ai/dsh-client-locale" - config: - preference: zh -- id: agent-preset-registry - name: "@deepseek-ai/dsh-agent-preset-registry" - config: - default: standard - selectedDefault: ptc -- id: llm-deepseek-account - name: "@deepseek-ai/dsh-llm-deepseek-account" - config: - models: - - id: deepseek-flash - name: DeepSeek-V41-Flash - inputModalities: - - text - - image - systemPromptUpdate: in-history - toolUpdate: addition-only - contextWindow: 1000000 - - id: deepseek-v4-pro - name: DeepSeek-V4-Pro - description: Stronger agentic coding, knowledge, and difficult reasoning; suited to complex or quality-critical tasks at higher cost. - contextWindow: 500000 - inputModalities: - - text -- id: agent-loop - name: "@deepseek-ai/dsh-agent-loop" - config: - agents: [] - maxParallelToolCalls: 8 -- id: subagent - name: "@deepseek-ai/dsh-subagent" - config: - maxActiveSubagents: 3 -- id: compaction-basic - name: "@deepseek-ai/dsh-compaction-basic" - config: - thresholdRatio: 0.35 - retainRatio: 0.16 - headroomTokens: 65536 - compactionRetries: 1 - maxOverflowRetries: 1 - auto: true diff --git a/home/base/tui/agents/dsh/web/package.json b/home/base/tui/agents/dsh/web/package.json deleted file mode 100644 index d2f8e1e8..00000000 --- a/home/base/tui/agents/dsh/web/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "dsh-profile-web", - "private": true, - "dependencies": {}, - "dsh": { - "profile": { - "bundles": [ - "@deepseek-ai/dsh-base", - "@deepseek-ai/dsh-web-app", - "@deepseek-ai/dsh-experimental-auto-review" - ] - } - } -} diff --git a/home/base/tui/agents/dsh/web/pnpm-workspace.yaml b/home/base/tui/agents/dsh/web/pnpm-workspace.yaml deleted file mode 100644 index f24c22e1..00000000 --- a/home/base/tui/agents/dsh/web/pnpm-workspace.yaml +++ /dev/null @@ -1,5 +0,0 @@ -packages: - - . - -nodeLinker: hoisted -autoInstallPeers: false diff --git a/outputs/x86_64-linux/tests/home-manager-dsh/expected.nix b/outputs/x86_64-linux/tests/home-manager-dsh/expected.nix index b87854c3..4fed1320 100644 --- a/outputs/x86_64-linux/tests/home-manager-dsh/expected.nix +++ b/outputs/x86_64-linux/tests/home-manager-dsh/expected.nix @@ -1,7 +1,5 @@ { - # The link targets the whole profile directory, not a file inside it: dsh's - # atomic Settings write replaces a symlinked file on the first save, while a - # directory link keeps the write inside the checkout. - keys = [ ".dsh/profiles/web" ]; - source = "/home/tester/nix-config/home/base/tui/agents/dsh/web"; + # The linked home-layer patch (see ../README.md). + keys = [ ".dsh/cordis.patch.yml" ]; + source = "/home/tester/nix-config/home/base/tui/agents/dsh/cordis.patch.yml"; } diff --git a/outputs/x86_64-linux/tests/home-manager-dsh/expr.nix b/outputs/x86_64-linux/tests/home-manager-dsh/expr.nix index 9411e034..1d8b65ec 100644 --- a/outputs/x86_64-linux/tests/home-manager-dsh/expr.nix +++ b/outputs/x86_64-linux/tests/home-manager-dsh/expr.nix @@ -1,7 +1,5 @@ -# The dsh module is evaluated on its own here. Reading `home.file` out of a real -# host would force the catppuccin theme derivations, which turns an eval test -# into a build, and the profile link is system-independent anyway. The stub -# supplies the only two config values the module reads. +# Evaluate the dsh module on its own: reading `home.file` from a real host would +# force the catppuccin theme derivations, turning an eval test into a build. let module = import ../../../../home/base/tui/agents/dsh/default.nix; evaluated = module { @@ -13,5 +11,5 @@ let in { keys = builtins.attrNames evaluated.home.file; - source = toString evaluated.home.file.".dsh/profiles/web".source; + source = toString evaluated.home.file.".dsh/cordis.patch.yml".source; }