feat(agents): steerable turns, PTC, and a Nushell-only dsh (#473)

* feat(agents): use the shell's own dialect; allow 5 s retry backoff

Assisted-by: DeepSeek Harness (deepseek-flash)

* feat(dsh): make Nushell the only shell

Assisted-by: DeepSeek Harness (deepseek-flash)

* docs(agents): wait for CI in the background

Pushes to a PR often start multi-minute CI runs, and an agent that holds
the turn open on a foreground watch reports nothing until it ends. Require
the pushed head and running checks to be reported first, the watch to run
in the background, and the result to be collected after independent work
or after ending the turn.

Assisted-by: DeepSeek Harness (deepseek-flash)

* docs(agents): keep the shell-handoff rule platform-neutral

The global rules carry no Windows assumptions: drop the cmd, pwsh, and
wsl handoff examples and keep the POSIX forms plus the dsh refusal.

Assisted-by: DeepSeek Harness (deepseek-flash)

* docs(dsh): keep the shell-swap docs POSIX-only

This repository manages Linux and Darwin hosts, not Windows: drop the
Windows handoff examples and terminal-backend wording from dsh.md. The home
patch also disabled pwsh-sandbox redundantly because dsh-base already
disables that row on POSIX; keep only the active bash-sandbox disable and
verify the composed rows with --dump-config.

Assisted-by: DeepSeek Harness (deepseek-flash)

* docs(agents): keep the global rules runtime-neutral

Global rules are loaded by every agent CLI, so they must not describe one
runtime's behavior. Drop the dsh-specific sentence and wording while keeping
the expectation to prefer Nushell structured pipelines and Python. Runtime
behavior stays documented in home/base/tui/agents/dsh.md.

Assisted-by: DeepSeek Harness (deepseek-flash)

* docs(agents): default shell work to Nushell

Keep the Nushell preference explicit in the runtime-neutral global rules, and
keep runtime behavior in runtime docs. Bash stays a narrow fallback for tasks
Nushell cannot do.

Assisted-by: DeepSeek Harness (deepseek-flash)

* docs(agents): tighten global-rule scope and CI guidance

Keep global rules runtime- and project-neutral, and state that boundary in the
file. Remove the duplicated CI turn-steering text in favour of the Communication
rule, resolve the watch run before watching instead of relying on
gh pr checks --watch alone, name the working-directory parameter generically,
and distinguish a bounded retry backoff from a fixed completion wait.

Assisted-by: DeepSeek Harness (deepseek-flash)

* docs(agents): never estimate a CI run's duration

A CI run has no predictable length, so do not turn "it looks short" into a
reason to wait. The rule is unconditional: report the head and started checks,
start a background watch, and end the turn; collect the result on completion.
The eval scenario now uses an unknown duration.

Assisted-by: DeepSeek Harness (deepseek-flash)

* docs(agents): move maintenance guidance to the README

The runtime/project boundary is guidance for editing the rules, not a rule
agents need loaded every session. The README's Maintaining global rules
section already owns that audience; the loaded rules keep only behavior.

Assisted-by: DeepSeek Harness (deepseek-flash)

* refactor(dsh): render the home patch from a YAML template

Move the home-layer cordis patch into dsh-cordis.patch.yml, substitute the
bundle and Nushell store paths with replaceVars, and link the rendered file
through home.file.source. This module is the only writer of
~/.dsh/cordis.patch.yml, the current host has no such file, and the profile
patch is a separate layer.

Assisted-by: DeepSeek Harness (deepseek-flash)

* refactor(dsh): install the shell plugin through NUR withPlugins

Replace the local runCommand bundle, peer symlink, and home-layer YAML file
with the plugin package and dsh.withPlugins from ryan4yin/nur-packages#43. The
package wrapper adds a generated --patch overlay after the profile layer, so
dsh no longer writes or manages ~/.dsh/cordis.patch.yml. The NUR input is
temporarily pinned to the PR branch and its commit; point it back at main once
the NUR PR merges.

Assisted-by: DeepSeek Harness (deepseek-flash)

* refactor(nix): expose the NUR package set as nurPkgs

Pass one per-system NUR package set through specialArgs instead of repeating
nur-ryan4yin.packages.${pkgs.stdenv.hostPlatform.system} at each call site.
The agents, rime, and idols-ruby modules now use the short binding.

Assisted-by: DeepSeek Harness (deepseek-flash)

* refactor(nix): use nurPkgs in package overlays

The overlays receive specialArgs too, so they can use the per-system NUR
package set instead of rebuilding the long input path from prev. This leaves
only the definition in outputs/default.nix.

Assisted-by: DeepSeek Harness (deepseek-flash)

* refactor(nix): name the per-system NUR set myNurPkgs

Leave room for consuming another NUR repository later, and match the existing
mylib/myvars naming.

Assisted-by: DeepSeek Harness (deepseek-flash)

* chore(dsh): consume the merged NUR package set

nur-ryan4yin now points at main after ryan4yin/nur-packages#43 was squash
merged; the lock records the merge commit. No branch ref remains.

Assisted-by: DeepSeek Harness (deepseek-flash)

* fix(dsh): pass the web profile explicitly

The withPlugins launcher prepends --patch, which historically prevented dsh
from auto-expanding "dsh web" into "--profile web". Pass the profile
explicitly in the Just recipe and the verification commands so they work with
both plain dsh and the wrapper.

Assisted-by: DeepSeek Harness (deepseek-flash)

* chore(dsh): pick up the wrapped-profile fix

Update nur-ryan4yin to the squash merge that keeps an implicit profile name
first, so "dsh web" and "just dsh-web" work through the wrapper again.

Assisted-by: DeepSeek Harness (deepseek-flash)

* chore(dsh): pick up the plugin peer-link fix

Update nur-ryan4yin to the squash merge that links the dsh peer tree at
node_modules/@deepseek-ai instead of nesting it one level deeper. This is the
import failure that stopped nushell-executor from activating after deploy.

Assisted-by: DeepSeek Harness (deepseek-flash)
This commit is contained in:
ryan4yin | 二花 authored and GitHub committed 2026-10-18 12:28:18 +08:00
1 parent 4258538588
commit 373b233f32
14 files changed
+196 -105

No files matched your search

+1 -1
View File
@@ -295,7 +295,7 @@ dsh-web:
HTTP_PROXY: "http://127.0.0.1:7897" HTTP_PROXY: "http://127.0.0.1:7897"
no_proxy: "localhost,127.0.0.1,::1,[::1],192.168.5.100,192.168.5.101,192.168.5.178,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16" no_proxy: "localhost,127.0.0.1,::1,[::1],192.168.5.100,192.168.5.101,192.168.5.178,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
NO_PROXY: "localhost,127.0.0.1,::1,[::1],192.168.5.100,192.168.5.101,192.168.5.178,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16" NO_PROXY: "localhost,127.0.0.1,::1,[::1],192.168.5.100,192.168.5.101,192.168.5.178,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
} { ^dsh web --no-open } } { ^dsh --profile web --no-open }
# Render the mihomo config from this repo and validate it with the live core. # Render the mihomo config from this repo and validate it with the live core.
[linux] [linux]
+3 -1
View File
@@ -47,7 +47,9 @@ lives in the Home Manager module [`home/base/tui/agents/`](../home/base/tui/agen
Before adding or expanding a rule, read the current rules and identify the decision boundary that Before adding or expanding a rule, read the current rules and identify the decision boundary that
needs to change. Keep reusable boundaries in `global-rules.md`; put concrete incidents, bypass needs to change. Keep reusable boundaries in `global-rules.md`; put concrete incidents, bypass
attempts, and counterexamples in [behavioral scenarios](evals/global-rules.md). attempts, and counterexamples in [behavioral scenarios](evals/global-rules.md). Keep the file
runtime- and project-neutral: runtime behavior belongs in that runtime's documentation, project
mechanics in the project's instructions, and a rule belongs here only when it applies across both.
- If an existing rule already covers the incident, add or refine a scenario instead of another rule. - If an existing rule already covers the incident, add or refine a scenario instead of another rule.
- If the boundary is missing or ambiguous, amend the relevant rule rather than append a special - If the boundary is missing or ambiguous, amend the relevant rule rather than append a special
+87 -49
View File
@@ -342,6 +342,10 @@ to run.
branch. → For a normal push to the same authorized and confirmed PR branch, specify the branch. → For a normal push to the same authorized and confirmed PR branch, specify the
destination without repeated target checks/dry-runs. Keep appropriate checks for new changes; destination without repeated target checks/dry-runs. Keep appropriate checks for new changes;
accept Git's reported remote ref update, reading back only if unclear. accept Git's reported remote ref update, reading back only if unclear.
- **CI watch.** A push starts a CI run of unknown duration, and the agent has no independent work
left. → Report the pushed head and the running checks, start the watch as a background job, and
end the turn; collect the result when the job finishes. Do not estimate its duration, hold the
turn open, or wait the run out even when it looks short.
- **Requested PR commits.** The user requests a PR in a named repository but does not separately say - **Requested PR commits.** The user requests a PR in a named repository but does not separately say
to commit. → Make the commits needed for that PR, preserving hooks and logical commit boundaries; to commit. → Make the commits needed for that PR, preserving hooks and logical commit boundaries;
do not treat the request as authorization to merge. do not treat the request as authorization to merge.
@@ -459,11 +463,10 @@ to run.
- **Declared dependencies.** A JS or Python project needs its declared dependencies installed. → Use - **Declared dependencies.** A JS or Python project needs its declared dependencies installed. → Use
the project's toolchain (`pnpm install`, `uv sync`) without asking or reading the dependency tree the project's toolchain (`pnpm install`, `uv sync`) without asking or reading the dependency tree
first; ask before adding a new dependency source. first; ask before adding a new dependency source.
- **GitHub SSH config.** A sandboxed GitHub Git command fails with - **GitHub SSH config.** A GitHub Git command fails with
`Bad owner or permissions on ~/.ssh/config`; the Nix-managed config maps `github.com` to `Bad owner or permissions on ~/.ssh/config`; the Nix-managed config maps `github.com` to
`ssh.github.com:443`. → Use the SSH config; do not override it with `ssh -F /dev/null` or `ssh.github.com:443`. → Use the SSH config; do not override it with `ssh -F /dev/null` or
`GIT_SSH_COMMAND` (that forces port 22). Rerun the original Git command with elevated permission `GIT_SSH_COMMAND` (that forces port 22), and do not fall back to HTTPS.
so it runs outside the sandbox and the config applies; do not fall back to HTTPS.
- **Global skill collision.** An independently installed directory already occupies - **Global skill collision.** An independently installed directory already occupies
`~/.agents/skills/git-delivery`. → Report the conflict and resolve ownership with the user; do not `~/.agents/skills/git-delivery`. → Report the conflict and resolve ownership with the user; do not
enable forced replacement or delete the existing skill. enable forced replacement or delete the existing skill.
@@ -477,72 +480,94 @@ to run.
### Tool execution ### Tool execution
- **Local pipeline.** Local output needs filtering or transformation. → Prefer native tool options, - **Local pipeline.** Local output needs filtering or transformation. → Prefer native tool options,
then available code-mode for tool results; use Nushell for shell-native orchestration and then code-mode for returned tool results; default to Nushell's structured pipelines, Python, or
structured command pipelines, Python for general local processing, or TypeScript for the JS/TS TypeScript for local processing and structured data (e.g. JSONC, YAML/TOML, TSX/JSX; Node.js). Do
ecosystem (e.g. JSONC, YAML/TOML, TSX/JSX). Prefer Bun over Node.js. Do not use a Bash text not pipe or transform tool results through a Bash text pipeline.
pipeline. - **Remote pipeline.** Read-only remote diagnostics need filtering; the remote host has Python and
- **Remote pipeline.** Read-only remote diagnostics require `journalctl | grep error`; the remote Nushell available. → Run one remote command: call the remote runtime with a quoted program
host has only bash. → Use the remote host's available shell; the local shell rules do not apply to (`ssh host python3 -c '<code>'` or `ssh host nu -c '<code>'`), or copy a script with one `scp`
it. Do not assume a local-only runtime such as Nushell exists there. command and run it with one `ssh` command. Do not build a remote shell pipeline.
- **Background server.** Start a dev server, then run a request against it. → Run it in the - **Background server.** Start a dev server, then run a request against it. → Start it as a runtime
background with output to a log file, capture its PID at startup without matching `ps` output, and background job with output to a log, capture the job handle or PID at start without matching `ps`
bound the readiness wait. output, and bound the readiness wait.
- **Simple Bash.** Run formatting then tests, e.g. `just fmt && just test`. → Plain Bash is fine; no - **Nushell-only handoff.** The task needs `bash -c '...'` or `sh -c '...'` for a shell step in a
need to wrap it in Nushell or Python. Nushell-only runtime. → Write Nushell (or do the step in code-mode); never hand a command to
- **Polling.** Wait until a service reports ready. → Prefer a native wait mechanism; otherwise use another shell, which the runtime refuses. `^program` runs a real external binary when a Nushell
available code-mode, Nushell, Python, or TypeScript with a bounded timeout. Prefer Bun over builtin would shadow it.
Node.js for TypeScript. Do not write a Bash polling loop. - **Per-command environment.** A test needs `RUST_LOG=debug` for one command; the shell tool has no
environment option. → Set it in the same Nushell command (`$env.RUST_LOG = "debug"; …` or
`with-env { RUST_LOG: "debug" } { … }`); on a POSIX-shell harness prefix it with
`env RUST_LOG=debug <command>`. Do not rely on `VAR=value` before a Nushell command.
- **Polling.** Wait until a service reports ready. → Prefer a native wait; otherwise poll in
code-mode or a native runtime with a bounded timeout that exits as soon as the condition holds. Do
not poll in a shell.
- **Blocking command.** Inspect git history or follow a log. → Disable the pager (e.g. - **Blocking command.** Inspect git history or follow a log. → Disable the pager (e.g.
`git --no-pager log`) and avoid `tail -f` or other commands that never exit. `git --no-pager log`) and read the current tail instead of `tail -f`; run a reader as a runtime
background job if you need to follow.
- **Code-mode language.** The harness exposes code-mode / programmatic tool calling (PTC) and the - **Code-mode language.** The harness exposes code-mode / programmatic tool calling (PTC) and the
task needs filtering of returned tool results. → Prefer code-mode when it can handle orchestration task needs filtering of returned tool results. → Prefer code-mode when it can handle orchestration
and returned tool results directly and clearly; use the language its runtime accepts. and returned tool results directly and clearly; use the language its runtime accepts.
- **Code-mode file access.** Code-mode cannot read a local TOML file; a shell tool and Python are - **Code-mode file access.** Code-mode cannot read a local TOML file; a shell tool and Python are
available. → Invoke Python through the shell to read and validate the file; do not assume available. → Invoke Python as one command (`python -c`) to read and validate the file; do not
code-mode has filesystem access. assume code-mode has filesystem access.
- **Bash-only tool.** The agent exposes only a Bash tool; output processing is complex and Python or - **Bash-only tool.** The agent exposes only a Bash tool; output processing is complex and Python or
Bun is available. → Invoke the suitable runtime through Bash, such as `python -c`, `bun -e`, or Nushell is available. → Invoke the suitable runtime as one command with a quoted code argument,
`nu -c`; use a quoted heredoc for multiline code. Do not require code-mode or write Bash such as `python -c` or `nu -c`, or run a script written with the write tool. Do not require
processing logic. code-mode, use a heredoc, or write Bash processing logic.
- **Missing runtime.** Code-mode is unavailable and neither Nushell, Python, nor Bun is available; a - **Missing runtime.** Code-mode is unavailable and neither Nushell, Python, nor Node.js is
task needs complex local processing. → Use an approved project toolchain or a one-off Nix available; a task needs complex local processing. → Use an approved project toolchain or a one-off
environment (`nix shell nixpkgs#<pkg> -c <cmd>`); otherwise report the limitation. Do not install Nix environment (`nix shell nixpkgs#<pkg> -c <cmd>`); otherwise report the limitation. Do not
imperatively or fall back to complex Bash logic. install imperatively or fall back to complex Bash logic.
- **Bounded long command.** An authorized build or test takes several minutes. → Run with a bounded - **Bounded long command.** An authorized build or test takes several minutes. → Run with a bounded
timeout, disable interactive prompts and pagers, and report progress; ordinary waiting is allowed. timeout, disable interactive prompts and pagers, and report progress; ordinary waiting is allowed.
- **Code-mode priority.** Code-mode can orchestrate tool calls and process their results directly - **Code-mode priority.** Code-mode can orchestrate tool calls and process their results directly
and clearly; Python is also available. → Prefer code-mode rather than invoking Python merely and clearly; Python is also available. → Prefer code-mode rather than invoking Python merely
because the logic is complex. because the logic is complex.
- **PTC orchestration.** A task needs three independent remote reads, a repository search, and a
parsed diff; code-mode is available. → Run the independent calls concurrently in one program,
filter and parse their results in code, and use native read, grep, and edit tools for local work;
bound each call so one slow call does not abandon the batch. Do not send one shell call per
program or open one program per step.
- **Cross-run state.** A code-mode program writes a temp file that a later program must read. →
Write and read it under the workspace, not `/tmp`: each code-mode run has an isolated temp
directory.
- **Readiness wait.** Wait until a local service on a port answers before a smoke test. → Prefer a - **Readiness wait.** Wait until a local service on a port answers before a smoke test. → Prefer a
bounded native wait; for curl, bound each request and the retry window, including the final bounded native wait; for curl, bound each request and the retry window, including the final
attempt in the overall bound; use `--fail`, `--max-time`, `--retry`, and `--retry-max-time` (also attempt in the overall bound; use `--fail`, `--max-time`, `--retry`, and `--retry-max-time` (also
`--retry-connrefused` when startup can refuse connections). A retry limit alone is insufficient. `--retry-connrefused` when startup can refuse connections). A retry limit alone is insufficient.
- **Service never ready.** Wait for a service that never comes up. → Stop at the bound and report - **Service never ready.** Wait for a service that never comes up. → Stop at the bound and report
the exit status and the observed response; do not claim it is up. the exit status and the observed response; do not claim it is up.
- **Fixed sleep wait.** Wait for a build that takes about four minutes, then run a dependent test. → - **Retry backoff.** A transient network or API failure is worth retrying, and the service sends no
Wait on the process or the program's own completion and check its exit status; do not sleep a retry signal. → Retry with a bounded, short backoff of at most 5 seconds per attempt inside a
fixed number of seconds. total retry window, or use the client's own retry options; never sleep to wait for progress.
- **Background PID capture.** Start a test server in the background, query it, and stop that exact - **Fixed sleep wait.** A command usually finishes in seconds, but the plan is to run `sleep` or a
server afterwards. → Capture the PID at start, quote it, and stop that PID; do not locate the `setTimeout` before reading its log; another build takes about four minutes. → Await the command's
process by name afterwards. own completion (a process wait, its exit status, or the runtime's job wait), which returns as soon
as it finishes, and make any poll exit as soon as the condition holds; never run a timer.
- **Bounded backoff.** A retry loop hits a service that fails transiently; the plan is to wait
between attempts. → Wait at most 5 seconds per attempt inside a bounded retry, and stop at the
total bound; never use a longer delay or a progress sleep.
- **Background PID capture.** Start a test server, query it, and stop that exact server afterwards.
→ Start it as a runtime background job and capture the job handle or PID at start; stop that
handle; do not locate the process by name afterwards.
- **Process check.** Check whether a named process is running and report its PID. → Use the PID - **Process check.** Check whether a named process is running and report its PID. → Use the PID
captured at start, Nushell (`ps | where name == 'foo'`), or an exact-name lookup such as captured at start, an exact-name lookup such as `pgrep -x`, or one `nu -c` command; do not match
`pgrep -x`; do not match `ps` output with `grep`, which matches the grep itself. `ps` output with `grep`, which matches the grep itself.
- **Variable in one command.** Start a server on a port held in a variable and query that port in - **Variable in one command.** Start a server on a port held in a variable and query that port. →
one command. → Sequence it in code-mode, Nushell, Python, or TypeScript, and verify the variable Sequence it in code-mode or in one Nushell or Python command; shell state does not survive between
is set before using it. Checking a Bash variable does not make variable or background-job tool calls, and a POSIX shell needs the steps in one script.
sequencing simple Bash.
- **Path with spaces.** Delete every `.log` file under a directory whose name contains a space. → - **Path with spaces.** Delete every `.log` file under a directory whose name contains a space. →
Quote the path or drive it from a runtime; do not iterate over an unquoted expansion or over `ls` Drive it from Python or Nushell with quoted paths; do not rely on Bash word splitting. Confirm the
output. Confirm the deletion with the user rather than running an interactive `rm -i`. deletion with the user rather than running an interactive `rm -i`.
- **Pipeline exit status.** Run a build, keep only its last 20 log lines, and fail the task if the - **Pipeline exit status.** Run a build, keep only its last 20 log lines, and fail the task if the
build failed. → Preserve the build's own exit status (`set -o pipefail`, or capture it before build failed. → Run the build once, capture its output and exit status, and keep the lines in
piping); the pipeline's last command must not mask it. code-mode; in a shell pipeline use `| complete` (Nushell) or `set -o pipefail` so the filter does
- **Heredoc quoting.** Append a literal `$PATH` string to a config file. → Keep the value literal (a not mask the producer's failure.
single-quoted argument, or a quoted heredoc delimiter) so the shell does not expand it. - **Literal shell text.** Append a literal `$PATH` string to a config file. → Use the write or edit
tool; never a heredoc or a shell-quoted value.
- **State across calls.** A temp directory was created in an earlier command; write a file inside it - **State across calls.** A temp directory was created in an earlier command; write a file inside it
now. → Do not assume shell state survives between calls: use the absolute path from the earlier now. → Do not assume shell state or working directory survives between calls: use the absolute
output, or do both steps in one runtime. path from the earlier output, or do both steps in one code-mode program.
- **Capacity-limited fan-out** (behavioral trace, not a paper exercise). Fourteen independent units - **Capacity-limited fan-out** (behavioral trace, not a paper exercise). Fourteen independent units
each take minutes; the runtime accepts at most ten concurrent units, refuses the rest until one each take minutes; the runtime accepts at most ten concurrent units, refuses the rest until one
finishes, and reports completion as an event. → Dispatch up to the cap, keep the refused remainder finishes, and reports completion as an event. → Dispatch up to the cap, keep the refused remainder
@@ -569,7 +594,7 @@ refusal handling and event-driven continuation; a caller's fixed sleep is not vi
(`nu-check`) or report that it is unavailable, and treat a false or non-zero result as a failure. (`nu-check`) or report that it is unavailable, and treat a false or non-zero result as a failure.
- **New target script.** A project, CI job, or container needs a new script and has no existing - **New target script.** A project, CI job, or container needs a new script and has no existing
convention. → Default to Python unless the target ecosystem or data format has a clearer supported convention. → Default to Python unless the target ecosystem or data format has a clearer supported
runtime, such as TypeScript for JS/TS ecosystem tasks. Prefer Bun over Node.js when applicable. runtime, such as TypeScript for JS/TS ecosystem tasks. Use Node.js when applicable.
### Communication ### Communication
@@ -580,6 +605,19 @@ refusal handling and event-driven continuation; a caller's fixed sleep is not vi
Give each action its own paragraph or list item, keep the important facts, and omit repetition. Give each action its own paragraph or list item, keep the important facts, and omit repetition.
- **Full review.** The user asks for a review and the agent finds eight issues. → List all eight, - **Full review.** The user asks for a review and the agent finds eight issues. → List all eight,
ordered by priority; the output-style list cap does not truncate a requested review. ordered by priority; the output-style list cap does not truncate a requested review.
- **Unanswered question.** A long build is running and the user asks a direct question that earlier
results can already answer. → Answer first; do not open new tool work before the answer, and do
not reply with investigation progress alone.
- **Steerable long job.** A build or benchmark takes about ten minutes on a remote host. → State the
interpreted target, action, expected duration, and artifact in one line; run it as a background
job and end the turn with its status and next step, letting the completion event resume the work.
Never wait a fixed duration before reading its result.
- **Wrong target echo.** The task names one target — a device, host, repository, or dataset — while
the planned work uses another. → State the interpreted target in the pre-work line so the mismatch
surfaces before the work starts.
- **Contradiction correction.** A new measurement contradicts a conclusion the agent stated earlier
in the session. → State the correction, what changed, and the new evidence; do not quietly restate
the new conclusion as if it had always held.
- **Artifact language.** An existing document is written in another language and the conversation - **Artifact language.** An existing document is written in another language and the conversation
uses it, or a document is new and the repository has no convention. → Follow the existing uses it, or a document is new and the repository has no convention. → Follow the existing
document's language; write the new one in English. The focused regression and English control are document's language; write the new one in English. The focused regression and English control are
+44 -29
View File
@@ -141,6 +141,11 @@ For an impactful change, follow these steps in order, scaled to its risk:
- Agents MUST NOT skip hooks without authorization. - Agents MUST NOT skip hooks without authorization.
- Agents MAY amend, rebase, or squash their own unpushed commits; pushed commits and others' commits - Agents MAY amend, rebase, or squash their own unpushed commits; pushed commits and others' commits
need authorization. need authorization.
- Never block a turn on CI, whatever its expected duration. After a push, report the head revision
and the checks that started. Start a watch on the resolved run as a background job
(`gh run watch <id> --exit-status`, or `gh pr checks <pr> --watch` once checks exist) following
Keep every turn steerable under Communication; collect and report the result when it finishes. Do
not estimate the run's duration or wait it out because it looks short.
- Create forks with only the default branch (`gh repo fork --default-branch-only`); the task adds - Create forks with only the default branch (`gh repo fork --default-branch-only`); the task adds
its own development branch. its own development branch.
@@ -150,12 +155,11 @@ For an impactful change, follow these steps in order, scaled to its risk:
`nix profile install`, global `npm`/`pip`). Use `nix shell nixpkgs#<pkg> -c <cmd>` or `nix run` `nix profile install`, global `npm`/`pip`). Use `nix shell nixpkgs#<pkg> -c <cmd>` or `nix run`
for one-off tools, and the project's existing toolchain (e.g. its flake, `uv`, `pnpm`) for its for one-off tools, and the project's existing toolchain (e.g. its flake, `uv`, `pnpm`) for its
dependencies; ask before creating a flake or installing another way. dependencies; ask before creating a flake or installing another way.
- The user's interactive shell is **nushell**; bash is only started from within nushell, - The user's interactive shell is **nushell**, and shell work defaults to Nushell. Bash is only
occasionally, for tasks nushell can't do. Put shell env, aliases, and per-session secrets in the started from within nushell, occasionally, for tasks nushell can't do. Put shell env, aliases, and
Nushell config, not in `bashrc`/`zshrc`. The secret block is `~/.secrets/nushell-secrets.nu`: per-session secrets in the Nushell config, not in `bashrc`/`zshrc`.
hand-edited, not Nix-managed, and agents MUST NOT read or edit it.
- Use `gh` for authorized GitHub operations; keep SSH for GitHub Git remotes and make sure the - Use `gh` for authorized GitHub operations; keep SSH for GitHub Git remotes and make sure the
Nix-managed `~/.ssh/config` is used; rerun outside the sandbox if it is rejected. Nix-managed `~/.ssh/config` is used.
- Code layout: `~/codes` = personal, `~/work` = work code, `~/src/<repo>` = source checkouts. - Code layout: `~/codes` = personal, `~/work` = work code, `~/src/<repo>` = source checkouts.
- Agents MUST publish only to repositories that are already public or that the user names, and MUST - Agents MUST publish only to repositories that are already public or that the user names, and MUST
NOT publish content from private repositories or internal sources to a public repository. NOT publish content from private repositories or internal sources to a public repository.
@@ -164,32 +168,35 @@ For an impactful change, follow these steps in order, scaled to its risk:
### Tool execution ### Tool execution
- Prefer native tool options to reduce output at the source. When code-mode (programmatic tool - When code-mode (programmatic tool calling, PTC) is available, agents MUST orchestrate multi-step
calling, PTC) is available and can do the work directly and clearly, prefer it to orchestrate tool work in one program: dispatch independent calls concurrently, process and filter results in code
calls and process their results, e.g. run independent read-only calls concurrently and filter them without hiding a command's own failure, and bound each call so one slow call does not abandon the
before returning them. batch. Keep state shared across code-mode runs in the workspace, not a per-run temp directory.
- For local filtering, transformation, loops, polling, process inspection, command sequencing with - Prefer native tool options to reduce output at the source, and the native read, grep, glob, write,
variables or background jobs, or complex quoting, agents MUST use code-mode, Nushell for and edit tools over their shell equivalents. A single shell dispatch is for genuinely shell work
shell-native pipelines, Python for general local processing, or TypeScript for the JS/TS ecosystem (git, ssh, nix, remote commands).
(Bun over Node.js) rather than Bash logic. A Bash-only tool can invoke them with `nu -c`, - Use the shell the runtime provides, in its own dialect, and never hand a command string to another
`python -c`, `bun -e`, or a quoted heredoc; if no runtime is available, use the toolchain and shell (`bash -c`, `sh -c`). Default shell work to Nushell. Compose multi-step work with code-mode
one-off Nix rules above; if none can do the work, report the limitation. Simple, obviously correct (PTC) for orchestration, returned tool results, loops, and state across calls; otherwise use
commands such as running a tool or a short `&&` sequence are fine in Bash; a command that needs a Nushell's structured pipelines when available, Python for general local processing, one runtime
shell variable, a background job, or a loop is not simple. On remote hosts, use the available invocation (`python -c '<code>'`, `nu -c '<code>'`), or a script written with the write tool. Set
shell. the tool's working-directory parameter instead of `cd`. A remote command usually runs under a
POSIX shell: keep it to one runtime invocation or a copied script.
- Commands MUST NOT block: disable pagers and interactive prompts (confirm with the user instead of - Commands MUST NOT block: disable pagers and interactive prompts (confirm with the user instead of
making a command interactive, e.g. `rm -i`), avoid commands that wait on stdin or never exit, and making a command interactive, e.g. `rm -i`), avoid commands that wait on stdin or never exit, and
bound waits and retries with a total time limit the waiting command enforces itself, not an outer bound waits and retries with a total time limit the waiting command enforces itself, not an outer
timeout. Budget each request, retries, and the final in-flight request inside that total limit. A timeout. Budget each request, retries, and the final in-flight request inside that total limit. A
retry limit alone does not bound an in-flight request. Prefer the waiting program's own wait (e.g. retry limit alone does not bound an in-flight request. Never use a fixed delay as a completion
`kubectl wait --timeout`) over a fixed sleep or a sleep loop, and make a readiness check fail on wait: no sleep-then-check loop, no `setTimeout` poll, no sleep-then-tail. Await the work's own
an error response (e.g. `curl --fail`), not only on a refused connection. Report long-job completion (the runtime's job wait, `kubectl wait --timeout`, or the process exit) and continue
progress. when it fires; any poll MUST check the condition and exit as soon as it holds. A retry or protocol
- When piping output only to display or trim it, agents MUST keep the producer's own exit status backoff MAY wait at most 5 seconds per attempt, never longer and never as a progress wait. Make a
(`set -o pipefail`, `| complete`, or capture it first); the last command's status is not the readiness check fail on an error response (e.g. `curl --fail`), not only on a refused connection.
task's. - When output is filtered or trimmed, agents MUST keep the producer's own exit status: capture
- Run servers and watchers in the background with output redirected to logs, capturing their PIDs output and status together (`| complete` in Nushell, `set -o pipefail` in a POSIX shell) or in
when starting them, not by matching `ps` output. code, and never report a successful filter as a successful command.
- Start servers and watchers as runtime background jobs with their output going to logs; capture the
job handle or PID when starting them, never by matching `ps` output or a shell `&`.
### Scripts ### Scripts
@@ -204,8 +211,16 @@ For an impactful change, follow these steps in order, scaled to its risk:
comments, commit messages, PRs, issues and docs — in English, whatever the conversation uses, comments, commit messages, PRs, issues and docs — in English, whatever the conversation uses,
unless the file or repository sets its own convention. Keep literal UI strings and match patterns unless the file or repository sets its own convention. Keep literal UI strings and match patterns
as they are. as they are.
- Restate the requested scope across turns. When a change takes effect only after a restart or - Answer before acting. When the user asks, the answer or recommendation comes before new tool work;
activation someone else performs, report what is applied and what is still pending. when the answer needs investigation, say in one line what you will check, then answer rather than
reporting only the investigation.
- Keep every turn steerable. Before work that will keep the user waiting, state the interpreted
target, action, expected duration (when knowable), and artifact in one line, run it as a
background job, and end the turn with its status and next step; let the job's completion event
resume the work. Restate the requested scope across turns: when a change takes effect only after a
restart or activation someone else performs, report what is applied and what is still pending.
- State corrections explicitly: what is now believed, what changed, and the new evidence; do not
quietly restate the new conclusion as if it had always held.
- Write for understanding at a glance: keep important facts and use short paragraphs or lists to - Write for understanding at a glance: keep important facts and use short paragraphs or lists to
separate distinct points. Brevity should reduce reading effort, not remove useful formatting. separate distinct points. Brevity should reduce reading effort, not remove useful formatting.
- A review or report the user asked for lists every finding, ordered by priority; the output-style - A review or report the user asked for lists every finding, ordered by priority; the output-style
Generated
+3 -3
View File
@@ -1027,11 +1027,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1792229484, "lastModified": 1792297214,
"narHash": "sha256-6ETJhHm7tW/c5ys0kepRVC79cRze2gbHhNsfKDS+AzA=", "narHash": "sha256-zRPv4X2ywq1gcBwbI2qCS7pur7oneze2eQAUPpODlnw=",
"owner": "ryan4yin", "owner": "ryan4yin",
"repo": "nur-packages", "repo": "nur-packages",
"rev": "6c543b8850770cd34a9d4793168cdb93b3a959c5", "rev": "1a845e77e62f8a2a1653d994e60981c0684087f2",
"type": "github" "type": "github"
}, },
"original": { "original": {
-1
View File
@@ -204,7 +204,6 @@
url = "github:DietrichGebert/ponytail/v5.1.0"; url = "github:DietrichGebert/ponytail/v5.1.0";
flake = false; flake = false;
}; };
######################## My own repositories ######################################### ######################## My own repositories #########################################
# my private secrets, it's a private repository, you need to replace it with your own. # my private secrets, it's a private repository, you need to replace it with your own.
+2 -2
View File
@@ -1,13 +1,13 @@
{ {
inputs,
lib, lib,
myNurPkgs,
pkgs, pkgs,
... ...
}: }:
let let
# flypy (小鹤音形) rime data from nur-ryan4yin (`pkgs/rime-data-flypy`). The # flypy (小鹤音形) rime data from nur-ryan4yin (`pkgs/rime-data-flypy`). The
# package stays pristine; personal preferences live in flypy.custom.yaml. # package stays pristine; personal preferences live in flypy.custom.yaml.
rime-data-flypy = inputs.nur-ryan4yin.packages.${pkgs.stdenv.hostPlatform.system}.rime-data-flypy; rime-data-flypy = myNurPkgs.rime-data-flypy;
# Personal Rime customisation, layered by Rime on top of the shared rime-data. # Personal Rime customisation, layered by Rime on top of the shared rime-data.
custom = ./flypy.custom.yaml; custom = ./flypy.custom.yaml;
# Manually maintained user dictionary (custom phrases, symbols, missing # Manually maintained user dictionary (custom phrases, symbols, missing
+5 -5
View File
@@ -15,11 +15,11 @@ Markdown with no Nix dependency; this module only wires it into place.
[agents/skills/README.md](../../../../agents/skills/README.md). [agents/skills/README.md](../../../../agents/skills/README.md).
- `packages.nix`: agent CLIs (`codex`, `opencode2`, `pi`) from the `llm-agents` flake input, plus - `packages.nix`: agent CLIs (`codex`, `opencode2`, `pi`) from the `llm-agents` flake input, plus
`pkgs.mcp-grafana` for the Grafana MCP server. `pkgs.mcp-grafana` for the Grafana MCP server.
- `env.nix`: telemetry and auto-update opt-outs for the agents. - `env.nix`: telemetry and auto-update opt-outs for the agents. dsh is installed by this module and
configured at runtime: profile patches live in `~/.dsh/profiles/<name>/cordis.patch.yml`. The
dsh is installed by this module but configured at runtime, not by Nix: profile patches live in Nushell-only shell is selected when the package is built, via `dsh.withPlugins` from
`~/.dsh/profiles/<name>/cordis.patch.yml`. See [dsh.md](./dsh.md) for the layer order and the `nur-ryan4yin`, so it is a derivation choice rather than runtime state. See [dsh.md](./dsh.md) for
settings that bite. the layer order and the settings that bite.
## Deployed rule targets ## Deployed rule targets
+38 -4
View File
@@ -8,13 +8,14 @@ profile: an ordered stack of patch layers over an empty root entry list, where l
| 1 | bundles | shipped with the profile | upstream | | 1 | bundles | shipped with the profile | upstream |
| 2 | profile | `$DSH_HOME/profiles/<name>/cordis.patch.yml` | dsh, at runtime | | 2 | profile | `$DSH_HOME/profiles/<name>/cordis.patch.yml` | dsh, at runtime |
| 3 | home | `$DSH_HOME/cordis.patch.yml` | you | | 3 | home | `$DSH_HOME/cordis.patch.yml` | you |
| 4 | `--patch` | whatever the launcher passes | you | | 4 | `--patch` | whatever the launcher passes | package wrapper |
`$DSH_HOME` defaults to `~/.dsh`. Settings UI changes are written to the profile patch; a home-layer `$DSH_HOME` defaults to `~/.dsh`. Settings UI changes are written to the profile patch; a home-layer
row outranks it, so the UI rejects that setting with row outranks it, so the UI rejects that setting with
`Configuration for "..." is overridden by a home patch`. This repository therefore ships no home `Configuration for "..." is overridden by a home patch`. This repository ships no home layer:
layer — profile patches are machine-local runtime state, the place for private endpoints (LAN profile patches remain machine-local runtime state, the place for private endpoints (LAN servers,
servers, staging gateways) too, and Nix only installs the CLI. staging gateways) too. The shell swap is a package concern instead: Nix installs `dsh` from
`nur-ryan4yin` with `dsh.withPlugins`, which adds a `--patch` overlay after the profile layer.
Three behaviours that are easy to get wrong: Three behaviours that are easy to get wrong:
@@ -49,3 +50,36 @@ on, `$DSH_AGENTS_HOME/AGENTS.md` — so no patch row is needed; identical conten
- The scopes come from the environment: `0.2.1-alpha.2` removed the row's `dshHome` / `agentsHome`. - The scopes come from the environment: `0.2.1-alpha.2` removed the row's `dshHome` / `agentsHome`.
- Nested `AGENTS.md` files are discovered by `read` / `write` / `edit`, not by `cd` inside `bash`, - Nested `AGENTS.md` files are discovered by `read` / `write` / `edit`, not by `cd` inside `bash`,
and nothing is watched: an external edit lands on the next such call, or on resume. and nothing is watched: an external edit lands on the next such call, or on resume.
## Nushell-only shell
`nur-ryan4yin`'s `dsh.withPlugins [ ...dsh-nushell-only ]` replaces the two first-party shell
providers with Nushell: every `ctx.shell` consumer (model shell tools, background jobs, hook
bridges, in-process callers) runs `nu --no-config-file -c`, and a command that hands execution to
another shell is refused before it spawns. The plugin adds Nushell rules and a syntax guide, and
rewrites the shell-tool descriptions, so the model is told what actually runs. `nu` comes from the
NUR package, so it does not depend on `PATH`.
The source is the `develop` branch of ryan4yin's fork carrying the reviewed patch set (Nu-builtin
denial facts, test-harness fixes, the `find` hint, persistent-terminal scoping), pinned by
`fetchFromGitHub` and `hashes.json` in `nur-packages`; nix-config locks that package set in its
`flake.lock`. `dsh.withPlugins` wraps the binary: each invocation gets a generated `--patch <file>`
overlay after the profile layer, while `dsh plugin` stays untouched. No `$DSH_HOME/cordis.patch.yml`
is written, so there is no home-layer state to resolve or roll back. Boundaries: preset rows that
spawn a shell themselves (`custom-bash.mjs`, `persistent-shell`) bypass `ctx.shell`. So does
`@deepseek-ai/dsh-experimental-terminal-bundle`: its `terminal_*` tools use a separate
`ctx.terminals` seam and run the platform shell (bash on the POSIX hosts this repository manages),
so they coexist with this plugin but stay outside the Nushell-only guarantee — keep the bundle off
for a strict Nu-only machine (the fork's `--with-terminal-bundle` integration run covers the
coexistence). Bash hooks now run under Nushell and need rewriting; remote hosts still run their own
POSIX shell. Rollback: install plain `dsh` or pass an empty plugin list; there is no home file to
delete. Tested on Linux; the same package also builds for Darwin, where it has not been boot-tested.
Verify after a switch:
```sh
dsh --profile web --dump-config | grep -n "nushell"
# expect the first-party shell rows disabled, nushell-executor and nushell-teaching inserted
dsh --profile web 2>&1 | grep "disabling profile plugin"
# a dsh bump can disable a row whose manifest no longer matches; nothing prints when healthy
```
+4 -3
View File
@@ -1,7 +1,7 @@
{ {
pkgs, pkgs,
llm-agents, llm-agents,
nur-ryan4yin, myNurPkgs,
... ...
}: }:
let let
@@ -14,8 +14,9 @@ in
agentPackages.opencode2 agentPackages.opencode2
agentPackages.pi agentPackages.pi
# dsh from nur-packages: llm-agents.nix's dsh cannot boot on nixpkgs node # dsh from nur-packages: llm-agents.nix's dsh cannot boot on nixpkgs node
# (node-addon-require-builtin probe), this one shims it. # (node-addon-require-builtin probe), this one shims it. The Nushell-only
nur-ryan4yin.packages.${pkgs.stdenv.hostPlatform.system}.dsh # plugin rides in through the package's withPlugins wrapper.
(myNurPkgs.dsh.withPlugins [ myNurPkgs.dsh-nushell-only ])
# Docs lookup CLI behind the find-docs skill (agents/skills/find-docs). The skill's flags are # Docs lookup CLI behind the find-docs skill (agents/skills/find-docs). The skill's flags are
# written against this package, not against upstream's npx instructions. # written against this package, not against upstream's npx instructions.
+2 -2
View File
@@ -1,7 +1,7 @@
{ {
pkgs, pkgs,
llm-agents, llm-agents,
nur-ryan4yin, myNurPkgs,
... ...
}: }:
let let
@@ -87,6 +87,6 @@ in
agentPackages.pi agentPackages.pi
# dsh from nur-packages: llm-agents.nix's dsh cannot boot on nixpkgs node # dsh from nur-packages: llm-agents.nix's dsh cannot boot on nixpkgs node
# (node-addon-require-builtin probe), this one shims it. # (node-addon-require-builtin probe), this one shims it.
nur-ryan4yin.packages.${pkgs.stdenv.hostPlatform.system}.dsh myNurPkgs.dsh
]; ];
} }
+3
View File
@@ -60,6 +60,9 @@ let
cudaCapabilities = [ "8.9" ]; cudaCapabilities = [ "8.9" ];
}; };
}; };
# NUR packages for this system, so modules use `myNurPkgs.dsh` instead of
# repeating `nur-ryan4yin.packages.${pkgs.stdenv.hostPlatform.system}`.
myNurPkgs = inputs.nur-ryan4yin.packages.${system};
}; };
# This is the args for all the haumea modules in this folder. # This is the args for all the haumea modules in this folder.
+2 -3
View File
@@ -1,4 +1,3 @@
{ inputs, ... }: _final: prev: { { myNurPkgs, ... }: _final: _prev: {
computer-use-linux = computer-use-linux = myNurPkgs.computer-use-linux;
inputs.nur-ryan4yin.packages.${prev.stdenv.hostPlatform.system}.computer-use-linux;
} }
+2 -2
View File
@@ -1,3 +1,3 @@
{ inputs, ... }: _final: prev: { { myNurPkgs, ... }: _final: _prev: {
cua-driver = inputs.nur-ryan4yin.packages.${prev.stdenv.hostPlatform.system}.cua-driver; cua-driver = myNurPkgs.cua-driver;
} }