Harness spec: one path form, the file tools' texts, the list tree, skills

filesystem.md, spec 3.0.0-draft, and the CLI made to match:

- Paths: absolute in every answer except a result's bracket (context.md),
  which keeps the call's path. glob and grep now give full paths.
- A missing file is `No such file or directory: {path}` for every tool
  that needs it, with no hint after it: in a chat a hidden file must read
  as a missing one. The CLI's "does not exist. Use write to create it." and
  "Write it first." are gone; an edit of a directory is `Is a directory`,
  glob over a file `Not a directory`.
- write/edit refusals, exactly: "{path} exists and has not been read in
  this session. Read it first." and "{path} changed since you last read
  it. Read it again before changing it." (no "on disk"), absolute path.
- A listing sorts names in code-point order; `list` with a depth is a
  tree indented two spaces a level, marks as in a single listing, cut at
  400 lines with a line saying so.
- Skills: where they are found and in what order on each side (the
  session's own place first, the person's next, what is shared last), and
  the name rule ^[a-z0-9][a-z0-9-]{0,63}$ that skill_manage checks. The
  CLI no longer makes names with _ or .; such skills still load.

README's 3.0.0 notes say so; CHANGELOG [Unreleased].

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
HomerandClaude Opus 5.5 committed 2026-10-28 01:20:42 +00:00
1 parent 4ca6af12b1
commit b1dcaded72
15 files changed
+167 -41

No files matched your search

+62 -5
View File
@@ -24,7 +24,12 @@ one access level for the whole working directory, decided by the CLI's permissio
links: a path is what it says. On a device, a link is followed, and where the CLI's rules look at
a path they look at where it really is (an `AGENTS.md` committed as a link to a file outside the
project is not read).
- Every answer names a path in its **absolute, resolved** form, whatever the call gave.
- Every answer names a path in its **absolute, resolved** form, whatever the call gave — an
error, a refusal, a search's results, a tool's own message. **The one exception is a result's
bracket** (`context.md`): a read's header, an edit's bracket and the compaction table name the
path as the call gave it, so the model recognises what it wrote. What identifies a file, there
too, is its absolute path. A listing names entries by their names under the directory listed
(*A listing*).
- Names are case-sensitive, `/`-separated, and never empty, `.` or `..`.
## Node kinds
@@ -120,6 +125,22 @@ resolved absolute path:
| `mv`'s target, or a file `mkdir` was asked to make | `Already exists: {path}` |
| a tool whose `requires` is not met at the time of the call | the tool's `unavailable` text |
A missing file is `No such file or directory` for **every** tool that needs it to exist — `read`,
`edit`, `show`, `list`, `glob`, `grep`, `python`'s `files` — with no hint after it: in a chat a
hidden file must read exactly as a missing one, and a hint ("use write to create it") would say
which. The tool's description says what it needs.
`write` and `edit` refuse to change an existing file the session has not seen, or one that changed
since it last saw it (by reading it, or by its own change). The texts, exactly:
| Case | Text |
|---|---|
| the file exists and this session has not read it | `{path} exists and has not been read in this session. Read it first.` |
| it changed since this session last read or changed it | `{path} changed since you last read it. Read it again before changing it.` |
How a change is noticed is each side's: the content hash on the server, the modification time on a
device.
A device's own errors (a file too large, a binary file, a protected path) are the CLI's and stay as
they are; the ones above it says the same way.
@@ -127,7 +148,26 @@ they are; the ones above it says the same way.
`list` and a directory given to `read`: one entry per line, directories (and mounts) with a trailing
`/`, then — in a chat — a mount marked ` [mount]` and an entry the session can read but not change,
in a directory it can, marked ` [read-only]`. Directories first, then by name.
in a directory it can, marked ` [read-only]`. Directories first, then by name in code-point order
(`B` before `a`). An empty directory is `(empty directory)` from `read`, `(empty)` from `list`.
`list` with a `depth` (default 2, at most 6) is a tree: the entries of the directory listed, and
under each directory the entries of that directory, each level indented by two more spaces, down to
`depth` levels in all (depth 1 is the directory's own entries). The order and the marks are a single
listing's, at every level; a directory's entries come right after it. At most 400 lines, then
`(more entries — list a directory further down)` on its own line. A device leaves out ignored and
build directories (`.git`, `node_modules`, …, and what `.gitignore` says) and shows only
directories that hold a file it would list.
```
docs/
guide/
index.md
repo/ [mount]
src/
README.md [read-only]
notes.md
```
## A search
@@ -194,9 +234,26 @@ section's, then a repository's. (Before 3.0.0 the CLI took only the nearest one.
A skill is a directory with a `SKILL.md` (frontmatter `name`, `description`), and `skills_list`,
`skill_view` and `skill_manage` are its meaning over files on both sides: the index in the prompt
(`prompts/blocks/skills.md`), `skill_view` to load one, `skill_manage` to make or change one, which
checks the frontmatter, the name and the size; `edit` works on the file too. In a chat, skills live
in the tree (`/skills/<name>/SKILL.md`, and a skills repository mounted there); on a device they are
local files, as before. Neither side syncs skills on its own.
checks the frontmatter, the name and the size; `edit` works on the file too. Neither side syncs
skills on its own.
**Where they are found**, in order of precedence — a name found in an earlier place wins, and the
later one is not offered:
| Side | Places, the most specific first |
|---|---|
| chat | the `skills/` directory of every directory from the start directory up to the boundary, the nearest first, **the root's excluded**; then the person's own `/home/<user>/skills/`; then the organisation's `/skills/` (a skills repository mounted there, or a plain directory) |
| device | the project's `.agent/skills/` (a trusted project); then the user's own skills directory; then the directories the configuration adds, in its order |
So on both sides the session's own place comes first, the person's next, what is shared last. Only
what the session can read is a skill there; `skill_manage` makes a new one in the person's own
place.
**A skill's name**, which `skill_manage` checks when it makes or renames one: lowercase letters,
digits and `-`, starting with a letter or a digit, at most 64 characters —
`^[a-z0-9][a-z0-9-]{0,63}$`, the Agent Skills format's rule. A `SKILL.md` made elsewhere whose name
does not fit is still offered when its name is one line of at most 64 characters; a name is looked
up ignoring case, with `_` and spaces read as `-`.
## Memory