diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-21 22:37:06 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:14 -0300 |
| commit | 5dcfade5f102256de787b2157b01293160780411 (patch) | |
| tree | 256416f7a82eacc06233d6a543a1fdfec390daab /.agents | |
| parent | 297e14cfc36e4613a8c1cb3b995597d0a9c2873b (diff) | |
| download | pardes-5dcfade5f102256de787b2157b01293160780411.tar.gz pardes-5dcfade5f102256de787b2157b01293160780411.zip | |
Make a pane by opening /pane/new, and a Plan 9 idiom pass
The Tcreate that replaced acme's /new was a step away from the idiom dressed
up as a step toward it. A pane is named by a server-assigned serial, so the
create ignored the client's name: `mkdir /pane/foo` succeeded and left you
/pane/12. A mkdir that does not make the directory you named is worse than the
read-with-side-effect it replaced, and it broke in the shell workflow that
motivated the change. `create` is out of the declared features, so Tcreate is
EPERM again; Tremove stays, since `rm` to close a pane is unambiguously right.
/pane/new is now opened, not created: the open makes the pane, the read of
that fid answers its serial, two reads agree, and closing it leaves the pane.
That is /net/tcp/clone's mechanism (kernel/network/ip/devip.c, in ipopen),
not acme's, and the difference is deliberate. acme allocates during the walk
and lands inside the new window, so /dev/new/body works in one step, and it
can afford to list `new` because a Plan 9 directory read carries every entry's
stat and nothing walks. A kernel or FUSE mount walks and stats each name a
listing gave it, so allocate-on-walk would make a pane per `ls -l`. Allocating
on open keeps `new` listed -- a stat is not an open -- at the cost of the
one-step new/body. `new` stays unreachable from an editor path, because that
resolution serves Look hover previews.
The idiom pass behind it, read out of the Plan 9 tree at
~/05-genizah/principia-softwarica rather than recalled:
Rerror carries a string, not an errno (man 5 error: `ename[s]`), and acme
names every refusal. The five refusals pardes shares with acme now say what
they mean; the generic sites keep their bare errno rather than invent strings
acme does not have. body and tag declare DMAPPEND, which they had always
behaved as (acme(4): "always appended; the file offset is ignored"), checked
first against Linux's fs/9p, which never maps the bit. excl stays unset
everywhere, because acme sets DMEXCL on nothing. Blocking reads, per-object
addr scope and the readable pane ctl were already right. Real stat sizes and
qid versions stay: acme reports length 0 and version 0 for everything, and
Linux clients need better.
One bug fell out of it. open reset the addr range, so `echo '#0,#5' >addr;
cat addr` answered `0 0` and `cp addr dot` copied zeros. acme(4) makes the
contract explicit -- "a regular expression may be evaluated by writing it to
addr and reading it back" -- and acme gets away with resetting on the 0-to-1
open only because its clients hold the fid across both. A shell cannot: that
is two opens. The register is cleared by truncating it now.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Diffstat (limited to '.agents')
| -rw-r--r-- | .agents/skills/pardes-9p/SKILL.md | 38 |
1 files changed, 25 insertions, 13 deletions
diff --git a/.agents/skills/pardes-9p/SKILL.md b/.agents/skills/pardes-9p/SKILL.md index 2cea4d4a..91a8e864 100644 --- a/.agents/skills/pardes-9p/SKILL.md +++ b/.agents/skills/pardes-9p/SKILL.md @@ -49,7 +49,7 @@ $m/exec write a line = a middle click: an editor command word, or a shell $m/log one record per read: new|del|rename|save <serial> <name>; reads park $m/screen the rendered screen as JSON, frozen per open $m/listeners this session's dial addresses -$m/pane/ mkdir opens a pane; rmdir <serial> closes it +$m/pane/new open it to make a pane, read names it; rmdir $m/pane/<n> closes it $m/os/ the host filesystem ``` @@ -60,7 +60,7 @@ reuse or close a pane. ```sh cat "$m/index" # which panes exist -mkdir "$m/pane/x"; n=$(awk 'END{print $1}' "$m/index") # open one, take its serial +n=$(cat "$m/pane/new") # make one, take its serial printf 'text\n' > "$m/pane/$n/body" # append cat "$m/pane/$n/tag" # what its tagline offers echo notes.txt > "$m/pane/$n/name" # rename the buffer @@ -69,10 +69,20 @@ echo "/etc/hosts:3" > "$m/look"; cat "$m/look" # open a file, see where it land rmdir "$m/pane/$n" # close it, dirty or not ``` -The name `mkdir` asks for is ignored: a pane is named by the serial the editor -gives it, and because `/index` is ordered by serial its last row is the pane -just made. Nothing else in the tree can be created or removed, and no read -creates anything, so `ls`, `stat` and `find` over the whole tree are inert. +**Opening** `$m/pane/new` is what makes a pane, and reading the open file +answers its serial — `/net/tcp/clone`'s mechanism. Each open makes another one, +two reads of the same open file answer the same serial, and closing it leaves +the pane. A pane is named by the serial the editor gives it, never by a name +you choose. + +A *stat* makes nothing, which is the whole reason the allocation sits on open: +`new` is listed in `$m/pane`, so `ls` shows it, and `ls -l`, `find` and +anything else that stats every name a listing handed it stay inert. acme +allocates on the walk instead and lets it land inside the new window, so +`/dev/new/body` works in one step — it can afford that because a Plan 9 +directory read carries every entry's stat and nothing walks. Under a kernel or +FUSE mount that would be a pane per `ls -l`. Nothing else in the tree can be +created or removed, and no read creates anything. `look` and `exec` are the editor's two clicks, one per line of a write, at the active pane from the root and at that pane from `$m/pane/<n>/look` and @@ -107,11 +117,12 @@ search and reads empty until set; truncate it to lift it. buffer differs from its file, whether a write pushes an undo point, and whether a write scrolls. Truncating `tag` clears the part of the tag you may edit. -Address state belongs to the pane, not to a client: opening `addr` resets it, -so two clients addressing the same pane will interfere. `$pane/ctl` reads -acme's window status line — serial, tag length, body length, a reserved zero, -the dirty flag, the width in cells, the font and the tab width — and takes the -one verb `get`. +Address state belongs to the pane, not to a client: it keeps the last range +written until someone writes or truncates it, so writing an address and reading +it back evaluates it, and two clients addressing the same pane will interfere. +`$pane/ctl` reads acme's window status line — serial, tag length, body length, +a reserved zero, the dirty flag, the width in cells, the font and the tab +width — and takes the one verb `get`. Terminal panes have no file: writing their `body` sends child input, and truncation does not erase terminal history. @@ -209,8 +220,9 @@ with tempfile.TemporaryDirectory(prefix='pardes-9p-skill-') as directory: PY ``` -`new_pane` is `mkdir` plus a read of the index; `execute` writes one line to a -pane's `exec`. Both are in `test/fs.py`. For terminal tests, use +`new_pane` walks to `/pane/new` and reads the serial off the directory it +lands on; `execute` writes one line to a pane's `exec`. Both are in +`test/fs.py`. For terminal tests, use `session(..., tty=True)` and read [test/agent_session.py](../../../test/agent_session.py) for bounded interactive driving; its readiness text and history threshold are application-specific, and |
