From 92e8a9a1fadc67a03fb79ac609591ba7968df30c Mon Sep 17 00:00:00 2001 From: Carsten Koch Date: Sat, 26 Sep 2026 13:04:37 +0200 Subject: [PATCH] The changelog feed starts with the public repository: beta keeps 0.200.0, stable starts empty The feed carried the private repository's whole history (120 beta, 17 stable entries, 2.7 MB). The public repository begins at 0.200.0, so the feed does too: - beta keeps only 0.200.0, the one version published from here; promote refuses a version without a beta entry, so it has to stay. - stable starts empty; promote.yml already treats that as the first stable (prev-stable empty => 0.0.0), and the website falls back to beta until then. Without this, promoting 0.200.x would aggregate 92 items from 0.67-0.103. - changes/stable/0.63.0 and 0.66.0 are removed: curated rollups of promotions that already happened, read only if those versions were promoted again. The earlier history stays in the private nxsflow/nexus-flow-archive. Co-Authored-By: Claude Opus 5.5 --- changes/stable/0.63.0/10-nxc.md | 7 - changes/stable/0.63.0/11-channels.md | 7 - changes/stable/0.63.0/12-status.md | 7 - changes/stable/0.63.0/13-working-tree.md | 7 - changes/stable/0.63.0/14-answers.md | 7 - changes/stable/0.63.0/15-transcripts.md | 7 - changes/stable/0.63.0/16-guides.md | 7 - changes/stable/0.63.0/20-memory.md | 7 - changes/stable/0.63.0/30-sync.md | 7 - changes/stable/0.63.0/40-embedding.md | 8 - changes/stable/0.63.0/50-declarations-move.md | 11 - changes/stable/0.63.0/51-surface.md | 7 - changes/stable/0.63.0/52-notes-contract.md | 7 - changes/stable/0.63.0/60-workflow-gone.md | 7 - changes/stable/0.63.0/61-raw-channels-gone.md | 7 - changes/stable/0.63.0/62-origin-gone.md | 7 - changes/stable/0.63.0/70-fix-correctness.md | 7 - changes/stable/0.63.0/71-fix-rounds.md | 7 - changes/stable/0.63.0/72-fix-runtime.md | 7 - changes/stable/0.63.0/73-fix-sync.md | 7 - changes/stable/0.63.0/80-facade.md | 8 - changes/stable/0.66.0/10-next-readable.md | 7 - changes/stable/0.66.0/20-nxm-guide.md | 7 - changes/stable/0.66.0/30-engine-withdraw.md | 7 - changes/stable/0.66.0/40-turn-ends.md | 7 - changes/stable/0.66.0/41-board-signals.md | 7 - .../0.66.0/70-exclusive-working-copy.md | 7 - changes/stable/0.66.0/80-facade.md | 8 - release-notes.json | 4070 +---------------- 29 files changed, 2 insertions(+), 4271 deletions(-) delete mode 100644 changes/stable/0.63.0/10-nxc.md delete mode 100644 changes/stable/0.63.0/11-channels.md delete mode 100644 changes/stable/0.63.0/12-status.md delete mode 100644 changes/stable/0.63.0/13-working-tree.md delete mode 100644 changes/stable/0.63.0/14-answers.md delete mode 100644 changes/stable/0.63.0/15-transcripts.md delete mode 100644 changes/stable/0.63.0/16-guides.md delete mode 100644 changes/stable/0.63.0/20-memory.md delete mode 100644 changes/stable/0.63.0/30-sync.md delete mode 100644 changes/stable/0.63.0/40-embedding.md delete mode 100644 changes/stable/0.63.0/50-declarations-move.md delete mode 100644 changes/stable/0.63.0/51-surface.md delete mode 100644 changes/stable/0.63.0/52-notes-contract.md delete mode 100644 changes/stable/0.63.0/60-workflow-gone.md delete mode 100644 changes/stable/0.63.0/61-raw-channels-gone.md delete mode 100644 changes/stable/0.63.0/62-origin-gone.md delete mode 100644 changes/stable/0.63.0/70-fix-correctness.md delete mode 100644 changes/stable/0.63.0/71-fix-rounds.md delete mode 100644 changes/stable/0.63.0/72-fix-runtime.md delete mode 100644 changes/stable/0.63.0/73-fix-sync.md delete mode 100644 changes/stable/0.63.0/80-facade.md delete mode 100644 changes/stable/0.66.0/10-next-readable.md delete mode 100644 changes/stable/0.66.0/20-nxm-guide.md delete mode 100644 changes/stable/0.66.0/30-engine-withdraw.md delete mode 100644 changes/stable/0.66.0/40-turn-ends.md delete mode 100644 changes/stable/0.66.0/41-board-signals.md delete mode 100644 changes/stable/0.66.0/70-exclusive-working-copy.md delete mode 100644 changes/stable/0.66.0/80-facade.md diff --git a/changes/stable/0.63.0/10-nxc.md b/changes/stable/0.63.0/10-nxc.md deleted file mode 100644 index 824028d..0000000 --- a/changes/stable/0.63.0/10-nxc.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -type: added ---- -[en] -**`nxc` — your agents talk to each other.** This is the headline of everything since 0.35.0. You declare who your agents are and where they talk, in `/.nxs-personas/`: one YAML per persona (prompt, model band, tools, who it may address) and a `channels.yaml` for the rooms they meet in. Then there are two verbs. `nxc send --to ` opens a conversation and starts whoever is on the other end; `nxc reply --thread ` answers one. Everything else — who is waiting, what was said, where an operation stands — is read, not typed. -[de] -**`nxc` — Ihre Agenten reden miteinander.** Das ist die Überschrift über allem seit 0.35.0. Sie deklarieren, wer Ihre Agenten sind und wo sie reden, in `/.nxs-personas/`: eine YAML je Persona (Prompt, Modellklasse, Werkzeuge, wen sie ansprechen darf) und eine `channels.yaml` für die Räume, in denen sie sich treffen. Dann gibt es zwei Verben. `nxc send --to ` eröffnet ein Gespräch und startet, wer am anderen Ende steht; `nxc reply --thread ` beantwortet eines. Alles andere — wer wartet, was gesagt wurde, wo eine Operation steht — wird gelesen, nicht getippt. diff --git a/changes/stable/0.63.0/11-channels.md b/changes/stable/0.63.0/11-channels.md deleted file mode 100644 index c83f266..0000000 --- a/changes/stable/0.63.0/11-channels.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -type: added ---- -[en] -**A channel is a declaration, and the declaration is the workflow.** Write down its members and it fans out to them; add `expects:` and it waits for a quorum; add `timeout:` and a silent member releases the round instead of holding it forever. `on_complete:` decides what the requester gets back — every answer as it stands, or one folded summary. `flow: sequential` turns the member list into an order, so a channel IS a workflow: same declaration, one step at a time. A channel can also be `public` — a project's front door, readable and addressable across project boundaries. -[de] -**Ein Kanal ist eine Deklaration, und die Deklaration ist der Ablauf.** Schreiben Sie seine Mitglieder hin, und er fächert zu ihnen auf; ergänzen Sie `expects:`, und er wartet auf ein Quorum; ergänzen Sie `timeout:`, und ein verstummtes Mitglied gibt die Runde frei, statt sie ewig zu halten. `on_complete:` entscheidet, was zurückkommt — jede Antwort, wie sie steht, oder eine gefaltete Zusammenfassung. `flow: sequential` macht aus der Mitgliederliste eine Reihenfolge, ein Kanal IST damit ein Ablauf: dieselbe Deklaration, ein Schritt nach dem anderen. Ein Kanal kann außerdem `public` sein — die Vordertür eines Projekts, über Projektgrenzen hinweg lesbar und ansprechbar. diff --git a/changes/stable/0.63.0/12-status.md b/changes/stable/0.63.0/12-status.md deleted file mode 100644 index f1e49bd..0000000 --- a/changes/stable/0.63.0/12-status.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -type: added ---- -[en] -**`nxc status` — where an operation stands.** An operation is a thread tree now: your question, the round it opened, the round that round opened. `nxc status` reads that tree from its root down, across every channel border, and says per thread who still owes an answer, whether a window has run out, and which session is working on it. `awaiting_human` marks the one place a person has to act. Whoever opened an operation can also read every message in it, whatever channel an agent opened along the way. -[de] -**`nxc status` — wo eine Operation steht.** Eine Operation ist jetzt ein Fadenbaum: Ihre Frage, die Runde, die sie eröffnet hat, die Runde, die jene eröffnet hat. `nxc status` liest diesen Baum von der Wurzel abwärts, über jede Kanalgrenze, und sagt je Faden, wer noch eine Antwort schuldet, ob ein Fenster abgelaufen ist und welche Sitzung daran arbeitet. `awaiting_human` markiert die eine Stelle, an der ein Mensch handeln muss. Wer eine Operation eröffnet hat, kann außerdem jede Nachricht darin lesen, in welchem Kanal ein Agent sie auch eröffnet hat. diff --git a/changes/stable/0.63.0/13-working-tree.md b/changes/stable/0.63.0/13-working-tree.md deleted file mode 100644 index 78fbeb7..0000000 --- a/changes/stable/0.63.0/13-working-tree.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -type: added ---- -[en] -**One chain per working copy.** A persona or channel that declares `working_tree: exclusive` gets sole use of the repository checkout and build directory for as long as its task runs; a second chain that would collide waits instead of running its build against the same target directory. The protection is derived, not repeated: declare it on the persona that builds, and every channel that persona is a declared member of counts as needing it too. -[de] -**Eine Kette je Arbeitskopie.** Eine Persona oder ein Kanal mit `working_tree: exclusive` bekommt die Arbeitskopie und das Build-Verzeichnis für die Dauer ihrer Aufgabe allein; eine zweite Kette, die kollidieren würde, wartet, statt ihren Build gegen dasselbe Zielverzeichnis laufen zu lassen. Der Schutz wird abgeleitet, nicht wiederholt: deklarieren Sie ihn an der Persona, die baut, und jeder Kanal, in dem diese Persona deklariertes Mitglied ist, gilt ebenfalls als schützenswert. diff --git a/changes/stable/0.63.0/14-answers.md b/changes/stable/0.63.0/14-answers.md deleted file mode 100644 index 38077ab..0000000 --- a/changes/stable/0.63.0/14-answers.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -type: added ---- -[en] -**Two things an agent may say besides an answer.** `nxc reply --escalate ""` is "I cannot carry this out" — it ends the turn, is never folded into a summary, and travels one level up instead of dying at the round it was said in. And `nxc withdraw --thread ` takes back a commission that is still waiting for the working copy: nothing has started, so nothing is lost. -[de] -**Zwei Dinge, die ein Agent außer einer Antwort sagen kann.** `nxc reply --escalate ""` heißt „das kann ich nicht ausführen" — es beendet den Zug, wird nie in eine Zusammenfassung gefaltet und reist eine Ebene nach oben, statt in der Runde zu sterben, in der es gesagt wurde. Und `nxc withdraw --thread ` nimmt eine Beauftragung zurück, die noch auf die Arbeitskopie wartet: es hat nichts begonnen, also geht nichts verloren. diff --git a/changes/stable/0.63.0/15-transcripts.md b/changes/stable/0.63.0/15-transcripts.md deleted file mode 100644 index 553768e..0000000 --- a/changes/stable/0.63.0/15-transcripts.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -type: added ---- -[en] -**Agent sessions leave a transcript, and it is bounded.** Every role session's normalized stream — assistant text, thinking, tool calls and their results, and any subagent activity — is readable with `nxc transcript show `. Transcripts are device-local and never synced, and they are kept for 30 days after a session's last write, so they stop growing forever. -[de] -**Agenten-Sitzungen hinterlassen einen Verlauf, und der ist begrenzt.** Der normalisierte Strom jeder Rollensitzung — Text, Denkschritte, Werkzeugaufrufe samt Ergebnissen und alles, was Subagenten dabei taten — ist mit `nxc transcript show ` lesbar. Verläufe sind gerätelokal und werden nie synchronisiert; sie werden 30 Tage nach dem letzten Schreibvorgang aufbewahrt und wachsen damit nicht mehr unbegrenzt. diff --git a/changes/stable/0.63.0/16-guides.md b/changes/stable/0.63.0/16-guides.md deleted file mode 100644 index 8291035..0000000 --- a/changes/stable/0.63.0/16-guides.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -type: added ---- -[en] -**Guides, offline, in the binary.** `nxc guide`, `nxm guide` and `nxf guide` each serve their block's own narrative documentation — what it is, what you type, what you declare, and what constrains it — with `nxs guide` reading the whole suite at once. No network, no browser. Every command shown in them is executed against the test corpus, so a guide cannot show an invocation that does not exist. -[de] -**Anleitungen, offline, in der Binary.** `nxc guide`, `nxm guide` und `nxf guide` liefern je die erzählende Dokumentation ihres Bausteins — was er ist, was Sie tippen, was Sie deklarieren und was das alles einhegt; `nxs guide` liest die ganze Suite auf einmal. Kein Netz, kein Browser. Jeder darin gezeigte Befehl wird gegen den Testkorpus ausgeführt, eine Anleitung kann also keinen Aufruf zeigen, den es nicht gibt. diff --git a/changes/stable/0.63.0/20-memory.md b/changes/stable/0.63.0/20-memory.md deleted file mode 100644 index 9922ced..0000000 --- a/changes/stable/0.63.0/20-memory.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -type: added ---- -[en] -**Project memory is a file now.** `NEXUS_MEMORY.md` at the workspace root is a generated projection of the `nxm` store, so the context survives for a reader without nexus-flow installed — change it with `nxm remember` / `nxm forget`, never by hand, and `nxm doc --check` reports drift. Memories carry a category, a reach (this workspace or everywhere), the board items they are about and a reading order, and `nxs prime` hands them to a session in that order instead of alphabetically. `nxm migrate` moves a workspace off a hand-maintained `CLAUDE.md`. -[de] -**Projektwissen ist jetzt eine Datei.** `NEXUS_MEMORY.md` in der Workspace-Wurzel ist eine erzeugte Projektion des `nxm`-Speichers, damit der Kontext auch für einen Leser ohne installiertes nexus-flow erhalten bleibt — ändern Sie sie mit `nxm remember` / `nxm forget`, nie von Hand; `nxm doc --check` meldet Abweichungen. Erinnerungen tragen Kategorie, Reichweite (dieser Workspace oder überall), die Board-Items, um die es geht, und eine Lesereihenfolge; `nxs prime` übergibt sie einer Sitzung in dieser Reihenfolge statt alphabetisch. `nxm migrate` löst einen Workspace von einer handgepflegten `CLAUDE.md`. diff --git a/changes/stable/0.63.0/30-sync.md b/changes/stable/0.63.0/30-sync.md deleted file mode 100644 index db8cfb8..0000000 --- a/changes/stable/0.63.0/30-sync.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -type: added ---- -[en] -**Sync runs by itself, and it can run on your own Postgres.** `nxs sync daemon` keeps every bound workspace in sync continuously instead of on demand, and `nxs sync bind` no longer needs `--create` or `--join` — run it with no flags and it derives the stream id deterministically. The `nxf-relay` in every release archive can now use a Postgres, Supabase included, so you can host the bus yourself. -[de] -**Sync läuft von selbst — und auf Ihrem eigenen Postgres.** `nxs sync daemon` hält jeden gebundenen Workspace fortlaufend synchron statt nur auf Zuruf, und `nxs sync bind` braucht kein `--create` oder `--join` mehr: ohne Flags aufgerufen leitet es die Stream-Id deterministisch ab. Der `nxf-relay` in jedem Release-Archiv kann jetzt ein Postgres benutzen, Supabase eingeschlossen — Sie können den Bus also selbst betreiben. diff --git a/changes/stable/0.63.0/40-embedding.md b/changes/stable/0.63.0/40-embedding.md deleted file mode 100644 index e5ae508..0000000 --- a/changes/stable/0.63.0/40-embedding.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -type: added -facade: changed ---- -[en] -**An application can embed all of this.** The chat engine's handle carries the role runtime, not just messaging: an app opens one handle for its lifetime and reads and writes through it, with the same derivations and the same rejections the command line has — a differential test compares the two seams byte for byte. A host can also bring its own agent runtime instead of the bundled one, which is how a remote or containerised executor docks on. -[de] -**Eine Anwendung kann das alles einbetten.** Die Handle der Chat-Engine trägt die Rollen-Laufzeit, nicht nur das Messaging: eine App öffnet eine Handle für ihre Lebensdauer und liest und schreibt darüber — mit denselben Ableitungen und denselben Ablehnungen wie die Kommandozeile; ein Differenztest vergleicht beide Nähte Byte für Byte. Ein Wirt kann außerdem seine eigene Agenten-Laufzeit mitbringen statt der mitgelieferten; so dockt ein entfernter oder containerisierter Executor an. diff --git a/changes/stable/0.63.0/50-declarations-move.md b/changes/stable/0.63.0/50-declarations-move.md deleted file mode 100644 index 691d5fb..0000000 --- a/changes/stable/0.63.0/50-declarations-move.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -type: changed ---- -[en] -**Declarations live in `.nxs-personas/` now**, not in `roles/`. Who your agents are and where they talk is one folder at the project root: one YAML per persona plus `channels.yaml`. Nothing reads the old folder any more. -[de] -**Deklarationen liegen jetzt in `.nxs-personas/`**, nicht mehr in `roles/`. Wer Ihre Agenten sind und wo sie reden, steht in einem Ordner in der Projektwurzel: eine YAML je Persona plus `channels.yaml`. Den alten Ordner liest nichts mehr. -[migration.en] -Manual, and it is a rename: move `/roles/` to `/.nxs-personas/`. Nothing converts it for you and nothing warns you — a workspace whose declarations stayed in `roles/` simply has no declared team, so `nxc list` comes up empty and `send --to` refuses every target. The file contents are unchanged. -[migration.de] -Handarbeit, und es ist eine Umbenennung: verschieben Sie `/roles/` nach `/.nxs-personas/`. Nichts wandelt das für Sie um und nichts warnt — ein Workspace, dessen Deklarationen in `roles/` liegen blieben, hat schlicht kein deklariertes Team: `nxc list` bleibt leer und `send --to` weist jedes Ziel ab. Die Dateiinhalte bleiben unverändert. diff --git a/changes/stable/0.63.0/51-surface.md b/changes/stable/0.63.0/51-surface.md deleted file mode 100644 index 1d0f1ac..0000000 --- a/changes/stable/0.63.0/51-surface.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -type: changed ---- -[en] -**`nxc` has one way to start a conversation and one way to answer it.** `send --to` opens a thread and always starts a fresh persona session; `reply --thread` posts into an existing one and continues the session it already has. The call decides, not the declaration. The per-call options that used to sit beside them — `--kind`, `--priority`, `--disposition`, `--model`, `--deadline` — are gone: what a message is, and how long a round may wait, is declared once on the channel rather than re-answered on every call. -[de] -**`nxc` hat einen Weg, ein Gespräch zu beginnen, und einen, es zu beantworten.** `send --to` eröffnet einen Faden und startet immer eine frische Persona-Sitzung; `reply --thread` postet in einen bestehenden und setzt die Sitzung fort, die er schon hat. Der Aufruf entscheidet, nicht die Deklaration. Die Optionen, die daneben standen — `--kind`, `--priority`, `--disposition`, `--model`, `--deadline` — sind weg: was eine Nachricht ist und wie lange eine Runde warten darf, wird einmal am Kanal deklariert statt bei jedem Aufruf neu beantwortet. diff --git a/changes/stable/0.63.0/52-notes-contract.md b/changes/stable/0.63.0/52-notes-contract.md deleted file mode 100644 index 7f88529..0000000 --- a/changes/stable/0.63.0/52-notes-contract.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -type: changed ---- -[en] -**The release notes answer "does this change the library contract?".** Every entry that touches a consumed surface carries an explicit `facade:` verdict — `changed` or `breaking` — and the notes render a dedicated Facade Contract section for it. An embedding consumer can decide re-review versus fast-track from the feed instead of from a diff. -[de] -**Die Release-Notizen beantworten „ändert das den Bibliothekskontrakt?".** Jeder Eintrag, der eine konsumierte Oberfläche berührt, trägt ein ausdrückliches `facade:`-Urteil — `changed` oder `breaking` — und die Notizen rendern dafür einen eigenen Abschnitt zum Facade-Kontrakt. Ein einbettender Konsument entscheidet Nachprüfung oder Durchwinken am Feed statt am Diff. diff --git a/changes/stable/0.63.0/60-workflow-gone.md b/changes/stable/0.63.0/60-workflow-gone.md deleted file mode 100644 index fe84f21..0000000 --- a/changes/stable/0.63.0/60-workflow-gone.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -type: removed ---- -[en] -**The `nxc workflow` command group is gone, and so is the run engine behind it.** `workflow start`, `step done`, `status`, `list` and `liveness` no longer exist, and neither does the stored run record. A channel declares its flow now and `send --to ` starts it; `nxc status` is where an operation's position is read. What has no successor is the step-liveness watchdog — a named loss, not an oversight. -[de] -**Die Befehlsgruppe `nxc workflow` ist weg, und mit ihr die Run-Engine dahinter.** `workflow start`, `step done`, `status`, `list` und `liveness` gibt es nicht mehr, und den gespeicherten Run-Datensatz auch nicht. Ein Kanal deklariert jetzt seinen Ablauf, `send --to ` startet ihn, und `nxc status` ist die Stelle, an der man den Stand einer Operation liest. Ohne Nachfolger bleibt allein die Schritt-Lebendüberwachung — ein benannter Verlust, kein Versehen. diff --git a/changes/stable/0.63.0/61-raw-channels-gone.md b/changes/stable/0.63.0/61-raw-channels-gone.md deleted file mode 100644 index bd1e245..0000000 --- a/changes/stable/0.63.0/61-raw-channels-gone.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -type: removed ---- -[en] -**A channel is a declaration now, or it is nothing.** The verbs that minted or addressed an undeclared channel are gone: `nxc ask` and the whole `nxc channels` group (`create`, `dm`, `join`, `leave`, `list`, `public`). Declare the channel and use `send --to `; the declaration carries who is asked and what is expected, which is exactly what `ask --expect` and `channels create` used to say per call. The channel declaration key `member_session` is gone too — the call decides when a persona is continued. -[de] -**Ein Kanal ist jetzt eine Deklaration, oder er ist nichts.** Die Verben, die einen undeklarierten Kanal anlegten oder ansprachen, sind weg: `nxc ask` und die ganze Gruppe `nxc channels` (`create`, `dm`, `join`, `leave`, `list`, `public`). Deklarieren Sie den Kanal und benutzen Sie `send --to `; die Deklaration trägt, wer gefragt wird und was erwartet wird — genau das, was `ask --expect` und `channels create` vorher je Aufruf sagten. Der Deklarationsschlüssel `member_session` ist ebenfalls entfallen: wann eine Persona fortgesetzt wird, entscheidet der Aufruf. diff --git a/changes/stable/0.63.0/62-origin-gone.md b/changes/stable/0.63.0/62-origin-gone.md deleted file mode 100644 index 1ba408b..0000000 --- a/changes/stable/0.63.0/62-origin-gone.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -type: removed ---- -[en] -**The old delivery origin `nxf.nxsflow.com` is switched off for good.** Downloads, `install.sh` and `nxs self-update` all run against `https://nxsflow.com/nxs`. An installation that still points at the old host cannot update itself and has to be re-installed once from the current install command. -[de] -**Der alte Auslieferungs-Origin `nxf.nxsflow.com` ist endgültig abgeschaltet.** Downloads, `install.sh` und `nxs self-update` laufen alle über `https://nxsflow.com/nxs`. Eine Installation, die noch auf den alten Host zeigt, kann sich nicht selbst aktualisieren und muss einmal über den aktuellen Install-Befehl neu installiert werden. diff --git a/changes/stable/0.63.0/70-fix-correctness.md b/changes/stable/0.63.0/70-fix-correctness.md deleted file mode 100644 index 44cc7f9..0000000 --- a/changes/stable/0.63.0/70-fix-correctness.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -type: fixed ---- -[en] -**Two processes on one workspace no longer lose each other's writes.** The logical clock two `nxs` invocations advance in parallel is now correct under concurrency, so a write made while another process was writing is not silently dropped. Ops can also no longer be written with a blank author: an unset actor is refused rather than recorded as nobody. -[de] -**Zwei Prozesse auf einem Workspace verlieren einander die Schreibvorgänge nicht mehr.** Die logische Uhr, die zwei parallel laufende `nxs`-Aufrufe fortschreiben, ist unter Nebenläufigkeit jetzt korrekt — ein Schreibvorgang während eines anderen geht nicht mehr stillschweigend verloren. Außerdem können Ops nicht mehr ohne Autor geschrieben werden: ein nicht gesetzter Akteur wird abgelehnt, statt als niemand festgehalten zu werden. diff --git a/changes/stable/0.63.0/71-fix-rounds.md b/changes/stable/0.63.0/71-fix-rounds.md deleted file mode 100644 index e623b3a..0000000 --- a/changes/stable/0.63.0/71-fix-rounds.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -type: fixed ---- -[en] -**A round now ends when it really ended.** The reply that completes a board is the one that actually completed it, a reply settles the turn it answers rather than the thread's whole life, and a step whose window ran out in the middle of an ordered flow no longer stalls everything behind it. A long-running agent no longer runs out of spawn depth just for holding a conversation, and a declared channel that finishes at the same moment its timer fires no longer starts two summarizers for one round. -[de] -**Eine Runde endet jetzt, wenn sie wirklich geendet hat.** Die Antwort, die eine Tafel vollständig macht, ist die, die es tatsächlich getan hat; eine Antwort erledigt den Zug, den sie beantwortet, statt das ganze Leben des Fadens; und ein Schritt, dessen Fenster mitten in einem geordneten Ablauf ablief, hält nicht mehr alles dahinter an. Ein lang laufender Agent geht nicht mehr an der Spawn-Tiefe aus, nur weil er ein Gespräch führt, und ein Kanal, der im selben Moment fertig wird, in dem sein Zeitgeber feuert, startet nicht mehr zwei Zusammenfasser für eine Runde. diff --git a/changes/stable/0.63.0/72-fix-runtime.md b/changes/stable/0.63.0/72-fix-runtime.md deleted file mode 100644 index f9815c5..0000000 --- a/changes/stable/0.63.0/72-fix-runtime.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -type: fixed ---- -[en] -**An agent session starts, stays single, and does not lose its answer.** On macOS every spawned persona session used to die at authentication; it now starts. One internal session runs exactly one process — a wake arriving while that session is still working is refused and reported instead of starting a second agent in the same working directory, where the two overwrote each other's edits. A session that dies at an error is machine-readably distinguishable from one that answered, and a failed transcript write no longer costs the turn. -[de] -**Eine Agenten-Sitzung startet, bleibt einzeln und verliert ihre Antwort nicht.** Auf macOS starb bisher jede gespawnte Persona-Sitzung an der Authentifizierung; sie startet jetzt. Eine interne Sitzung führt genau einen Prozess — ein Wecken, das eintrifft, während diese Sitzung noch arbeitet, wird abgelehnt und gemeldet, statt einen zweiten Agenten im selben Arbeitsverzeichnis zu starten, wo beide einander die Änderungen überschrieben. Eine Sitzung, die an einem Fehler stirbt, ist maschinell von einer beantworteten unterscheidbar, und ein fehlgeschlagener Verlaufsschreibvorgang kostet nicht mehr den Zug. diff --git a/changes/stable/0.63.0/73-fix-sync.md b/changes/stable/0.63.0/73-fix-sync.md deleted file mode 100644 index f033351..0000000 --- a/changes/stable/0.63.0/73-fix-sync.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -type: fixed ---- -[en] -**Sync finishes what it started.** A pull no longer ends a pass because a page came back empty or shorter than asked for — it ends when the relay says there is no more. A stream id containing `/`, `#` or `?` used to produce a 404 or a silently truncated id instead of syncing. And the relay now carries envelope fields it does not itself understand, so a newer client and an older relay keep working together. -[de] -**Sync bringt zu Ende, was es beginnt.** Ein Pull beendet einen Durchlauf nicht mehr, weil eine Seite leer oder kürzer als angefordert zurückkam — er endet, wenn das Relay sagt, dass nichts mehr da ist. Eine Stream-Id mit `/`, `#` oder `?` erzeugte bisher einen 404 oder eine still abgeschnittene Id, statt zu synchronisieren. Und das Relay trägt jetzt Umschlagfelder weiter, die es selbst nicht versteht — ein neuerer Client und ein älteres Relay arbeiten damit weiter zusammen. diff --git a/changes/stable/0.63.0/80-facade.md b/changes/stable/0.63.0/80-facade.md deleted file mode 100644 index 6d49463..0000000 --- a/changes/stable/0.63.0/80-facade.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -type: changed -facade: breaking ---- -[en] -**For anyone embedding nexus-flow as a Rust library: this is a breaking jump, and it is one migration, not twenty-eight.** The chat engine's handle settled on a small, deliberate surface — seven reading verbs, two writing ones, and the role runtime flat beside them — and the per-call context now carries only who is calling. Declarations are read from the workspace folder rather than injected. Concretely, over this whole span: the worker seam changed shape, `Engine::ask`/`channel_open`/`role_trigger`/`role_resume`/`send`/`reply` and ten reads were removed or replaced, `RoleDecl.address_book` became an `Option`, `channel::declared_visibility` folded into `declared_policy`, and `timer::AtTimer` stopped being a unit struct. Pin by git tag, migrate once, and read the per-version entries on the releases page for the exact shape of each step. -[de] -**Für alle, die nexus-flow als Rust-Bibliothek einbetten: das ist ein Bruch, aber EINE Migration, nicht achtundzwanzig.** Die Handle der Chat-Engine hat sich auf eine kleine, bewusste Oberfläche gesetzt — sieben lesende Verben, zwei schreibende, und die Rollen-Laufzeit flach daneben — und der Kontext je Aufruf trägt nur noch, wer aufruft. Deklarationen werden aus dem Workspace-Ordner gelesen statt injiziert. Konkret über die ganze Spanne: die Worker-Naht änderte ihre Form, `Engine::ask`/`channel_open`/`role_trigger`/`role_resume`/`send`/`reply` und zehn Lesezugriffe entfielen oder wurden ersetzt, `RoleDecl.address_book` wurde ein `Option`, `channel::declared_visibility` ging in `declared_policy` auf, und `timer::AtTimer` ist kein Unit-Struct mehr. Per Git-Tag pinnen, einmal migrieren, und die Einträge je Version auf der Releases-Seite lesen, wenn Sie die genaue Form eines einzelnen Schritts brauchen. diff --git a/changes/stable/0.66.0/10-next-readable.md b/changes/stable/0.66.0/10-next-readable.md deleted file mode 100644 index cdb5255..0000000 --- a/changes/stable/0.66.0/10-next-readable.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -type: added ---- -[en] -**`nxf next` reads like a list a person can scan — and can be cut without lying about it.** On a real terminal the ticket title is bold, the one field you actually look for and the one that used to weigh exactly as much as the id in front of it; the id and the `↳` epic line are muted; and the priority is coloured along a single ramp from Deep Ember through Ember and Flame down to graphite, so the weighting is visible without reading the label. The ramp maps the canonical priority ORDINAL rather than its name, so it is just as right under a plugin that calls its levels now/soon/later. `nxf next --limit ` shows only the head of the list and never cuts silently: a truncated list is headed `showing 15 of 180`, and under `--json` the flag wraps the records as `{"items": [...], "total": 180}`, so a consumer reads the true total instead of inferring it from the array's length. The limit applies last, after `--sort` and after `--label`, so a filtered list discloses how much of your filter you are seeing. Without `--limit` the command is unchanged and complete, and its `--json` payload is still the same bare array. Nothing else changes: `--json`, a pipe, CI and an agent's `prime` context still receive the same plain ASCII, byte for byte, with no escape sequences at all, and `NO_COLOR` / `TERM=dumb` keep the weight while dropping the colour. -[de] -**`nxf next` liest sich wie eine Liste, die ein Mensch überfliegen kann — und lässt sich kürzen, ohne darüber zu schweigen.** Auf einem echten Terminal steht der Ticket-Titel fett: das Einzige, wonach man wirklich sucht, und bisher genauso schwer wie die Id davor. Id und `↳`-Epic-Zeile sind gedämpft, und die Priorität ist entlang einer einzigen Rampe eingefärbt, von Deep Ember über Ember und Flame hinunter zu Graphit — die Gewichtung ist damit sichtbar, ohne das Label zu lesen. Die Rampe bildet auf den kanonischen ORDINALWERT der Priorität ab, nicht auf ihren Namen; sie stimmt deshalb genauso unter einem Plugin, das seine Stufen jetzt/bald/später nennt. `nxf next --limit ` zeigt nur den Kopf der Liste und kürzt nie stillschweigend: über einer gekürzten Liste steht `showing 15 of 180`, und unter `--json` verpackt die Option die Datensätze als `{"items": [...], "total": 180}`, damit ein Konsument die echte Gesamtzahl liest, statt sie aus der Länge des Arrays zu erschließen. Das Limit greift zuletzt, nach `--sort` und nach `--label` — eine gefilterte Liste weist also aus, wie viel Ihres Filters Sie sehen. Ohne `--limit` ist der Befehl unverändert vollständig, und seine `--json`-Ausgabe bleibt dasselbe blanke Array. Sonst ändert sich nichts: `--json`, eine Pipe, CI und der `prime`-Kontext eines Agenten bekommen weiterhin dasselbe schlichte ASCII, Byte für Byte, ohne jede Escape-Sequenz, und `NO_COLOR` / `TERM=dumb` behalten das Gewicht und lassen die Farbe weg. diff --git a/changes/stable/0.66.0/20-nxm-guide.md b/changes/stable/0.66.0/20-nxm-guide.md deleted file mode 100644 index 2c068d5..0000000 --- a/changes/stable/0.66.0/20-nxm-guide.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -type: added ---- -[en] -**`nxm guide` — memory has documentation now.** Five guides, in English and German, shipped inside the binary and published to the docs page: an introduction, the core concepts (keys and auto-keys, category/reach/references, the retrieval rule that decides where a memory is read, reading order, the reversible tombstone), the complete command reference with every `--json` shape, the `memory_*` MCP tools and what `nxs prime` contributes, and how to bring an existing Claude memory store in. Until now `nxm` had `--help` and nothing else. -[de] -**`nxm guide` — der Speicher hat jetzt eine Dokumentation.** Fünf Anleitungen, auf Deutsch und Englisch, in der Binary mitgeliefert und auf der Doku-Seite veröffentlicht: eine Einführung, die Grundbegriffe (Schlüssel und Auto-Schlüssel, Kategorie/Reichweite/Verweise, die Abrufregel, die entscheidet, wo eine Erinnerung gelesen wird, die Lesereihenfolge, der umkehrbare Grabstein), die vollständige Befehlsreferenz mit jeder `--json`-Form, die `memory_*`-MCP-Werkzeuge samt dem, was `nxs prime` beisteuert, und wie man einen bestehenden Claude-Speicher übernimmt. Bisher hatte `nxm` `--help` und sonst nichts. diff --git a/changes/stable/0.66.0/30-engine-withdraw.md b/changes/stable/0.66.0/30-engine-withdraw.md deleted file mode 100644 index c90c56b..0000000 --- a/changes/stable/0.66.0/30-engine-withdraw.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -type: added ---- -[en] -**`Engine::withdraw`** — an embedding app can take back a commission that is still parked behind the working copy, the same narrow case `nxc withdraw` covers. The seam already handed out `queue_position`/`queued_behind` on a send receipt ("not started, 3rd in line") with no call to answer them; this closes that. -[de] -**`Engine::withdraw`** — eine einbettende Anwendung kann eine Beauftragung zurücknehmen, die noch hinter der Arbeitskopie geparkt ist; derselbe enge Fall, den `nxc withdraw` abdeckt. Die Naht gab auf einer Sende-Quittung längst `queue_position`/`queued_behind` heraus („nicht gestartet, 3. in der Reihe"), ohne einen Aufruf, der darauf antwortet. Das ist damit geschlossen. diff --git a/changes/stable/0.66.0/40-turn-ends.md b/changes/stable/0.66.0/40-turn-ends.md deleted file mode 100644 index 635ae56..0000000 --- a/changes/stable/0.66.0/40-turn-ends.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -type: changed ---- -[en] -**A session can no longer end owing an answer without anybody noticing.** One that ends without answering its thread is given its turn back and reminded — once, naming the thread, both ways to end a turn, and any round it commissioned that is still open — before the sidecar posts anything in its name. Most answer then. One that is reminded and ends silent again is handed back as an escalation, so `escalated: true` on `nxc status` tells the caller that no result exists and somebody has to decide. An escalation is now recognisable in the text the receiving session reads FIRST, on all three paths it can travel (a channel's pass-through, a 1:1 resume, a quorum wake): what it is, that the working copy is held while it stands, and what is expected in return — the flag alone did not help, because an agent reads the message that woke it, not `nxc status`. And every session that owes a reply is told in its system prompt that its turn must end in exactly one of two ways, `nxc reply --thread ""` or `nxc reply --thread --escalate ""`; that obligation reaches even a persona declared `prime: false`, so a declaration cannot leave out the one rule the engine depends on. -[de] -**Eine Sitzung kann nicht mehr enden, ohne die geschuldete Antwort zu geben und ohne dass es jemandem auffällt.** Wer ohne Antwort auf seinen Faden endet, bekommt den Zug zurück und wird erinnert — einmal, mit dem konkreten Faden, beiden Arten einen Zug zu beenden, und jeder von ihm beauftragten Runde, die noch offen ist —, bevor der Sidecar in seinem Namen etwas postet. Die meisten antworten dann. Wer erinnert wurde und erneut stumm endet, wird als Eskalation zurückgegeben; `escalated: true` in `nxc status` sagt dem Aufrufer damit, dass es kein Ergebnis gibt und jemand entscheiden muss. Eine Eskalation ist jetzt in dem Text erkennbar, den die empfangende Sitzung ZUERST liest — auf allen drei Wegen, die sie nehmen kann (Kanal-Durchreichung, 1:1-Wiederaufnahme, Quorum-Weckruf): was es ist, dass die Arbeitskopie währenddessen gehalten wird, und was erwartet wird. Das Kennzeichen allein half nicht, denn ein Agent liest die Nachricht, die ihn geweckt hat, nicht `nxc status`. Und jede Sitzung, die eine Antwort schuldet, erfährt in ihrem Systemprompt, dass ihr Zug auf genau eine von zwei Arten enden muss: `nxc reply --thread ""` oder `nxc reply --thread --escalate ""`. Diese Pflicht erreicht auch eine Persona, die `prime: false` deklariert — eine Deklaration kann die eine Regel, auf die sich die Maschinerie verlässt, also nicht weglassen. diff --git a/changes/stable/0.66.0/41-board-signals.md b/changes/stable/0.66.0/41-board-signals.md deleted file mode 100644 index 393252b..0000000 --- a/changes/stable/0.66.0/41-board-signals.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -type: changed ---- -[en] -**The board says what it is waiting for.** `nxc status` gains two operation-level flags: `needs_decision` (somewhere under this root a task was handed back and nobody took it up — read it beside `awaiting_human`, which looks identical and means the opposite) and `holds_working_tree` (the working copy is held somewhere below, so you know whether to go looking at all). `nxc tick` now distinguishes a settled round whose only remaining blocker is a live session, reporting `waiting_for_a_session` instead of `not_due` — the difference matters, because nothing re-checks that one on a clock and this verb is the way out. And a round whose every commission was withdrawn no longer counts as an open operation: `nxc withdraw` discharges the thread you named as well, instead of leaving it waiting for a supervisor whose round had just been taken away. -[de] -**Das Board sagt, worauf es wartet.** `nxc status` bekommt zwei Kennzeichen am Vorgang: `needs_decision` (irgendwo unter dieser Wurzel wurde eine Aufgabe zurückgegeben und niemand hat sie aufgenommen — neben `awaiting_human` zu lesen, das genauso aussieht und das Gegenteil bedeutet) und `holds_working_tree` (irgendwo darunter wird die Arbeitskopie gehalten, Sie wissen also, ob Sie überhaupt nachsehen müssen). `nxc tick` unterscheidet jetzt eine erledigte Runde, deren einziger verbleibender Blocker eine lebende Sitzung ist, und meldet `waiting_for_a_session` statt `not_due` — der Unterschied zählt, denn genau das prüft keine Uhr nach, und dieses Verb ist der Weg heraus. Und eine Runde, deren sämtliche Beauftragungen zurückgenommen wurden, zählt nicht mehr als offener Vorgang: `nxc withdraw` entlastet jetzt auch den genannten Faden, statt ihn auf einen Betreuer warten zu lassen, dem man gerade die Runde weggenommen hat. diff --git a/changes/stable/0.66.0/70-exclusive-working-copy.md b/changes/stable/0.66.0/70-exclusive-working-copy.md deleted file mode 100644 index 1d80f52..0000000 --- a/changes/stable/0.66.0/70-exclusive-working-copy.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -type: fixed ---- -[en] -**A step that claims the working copy exclusively now ends when its session's process ends, not when its reply arrives.** A member of a `flow: sequential` channel that answered and kept working used to let the next step start into the same checkout: measured in a real run, a coder replied at 00:16:33 and went on editing until 00:34:27, while the next step had been running since 00:16:36. Sessions now report their own end — `nxc session ended`, called by the agent sidecar's teardown, and `Engine::session_ended` for a host that runs its own runtime — and where that announcement never arrives, the worker's process check answers the next time anything asks. **Nothing re-checks it on a clock:** a channel's declared `timeout:` is a deadline for an *answer*, and a member that has replied has answered, so a lost announcement (a sidecar killed before its teardown, an older `nxc`, a host runtime that never wires `Engine::session_ended`) leaves the round waiting until somebody runs `nxc tick --thread ` — which now says exactly that instead of "nothing to do". That teardown is itself bounded now, a five-minute reminder round and a thirty-second timeout on every `nxc` call it makes, because it runs inside the very process whose exit an exclusive channel is waiting for: one call that never returns holds a checkout, not merely a process. Channels without an exclusive working-copy claim are unchanged. -[de] -**Ein Schritt, der die Arbeitskopie exklusiv beansprucht, endet jetzt mit dem Prozess seiner Sitzung, nicht mit dem Eintreffen seiner Antwort.** Ein Mitglied eines `flow: sequential`-Kanals, das geantwortet hat und weiterarbeitet, ließ bisher den nächsten Schritt in dieselbe Arbeitskopie starten: in einem echten Lauf gemessen, antwortete ein Coder um 00:16:33 und editierte bis 00:34:27 weiter, während der nächste Schritt seit 00:16:36 lief. Sitzungen melden ihr Ende jetzt selbst — `nxc session ended`, aufgerufen vom Abbau des Agent-Sidecars, und `Engine::session_ended` für einen Wirt mit eigener Laufzeitumgebung —, und wo diese Meldung nie eintrifft, antwortet die Prozessprüfung des Workers beim nächsten Mal, wenn überhaupt jemand fragt. **Keine Uhr prüft das nach:** das deklarierte `timeout:` eines Kanals ist eine Frist für eine *Antwort*, und wer geantwortet hat, hat geantwortet. Geht die Meldung verloren (ein vor dem Abbau getöteter Sidecar, ein älteres `nxc`, eine Wirtslaufzeit, die `Engine::session_ended` nie verdrahtet), wartet die Runde, bis jemand `nxc tick --thread ` ausführt — was jetzt genau das sagt, statt „nichts zu tun". Dieser Abbau ist selbst zeitlich begrenzt: eine Erinnerungsrunde von fünf Minuten und ein Zeitlimit von dreißig Sekunden auf jeden `nxc`-Aufruf, den er macht. Denn er läuft in genau dem Prozess, auf dessen Ende ein exklusiver Kanal wartet — ein Aufruf, der nie zurückkommt, hält eine Arbeitskopie und nicht bloß einen Prozess. Kanäle ohne exklusiven Anspruch auf die Arbeitskopie sind unverändert. diff --git a/changes/stable/0.66.0/80-facade.md b/changes/stable/0.66.0/80-facade.md deleted file mode 100644 index 1bd0d36..0000000 --- a/changes/stable/0.66.0/80-facade.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -type: changed -facade: changed ---- -[en] -**For anyone embedding nexus-flow as a Rust library: this jump is purely additive — pin the new tag and change nothing.** Across the whole span the three consumed surfaces (`nexus-flow-facade`, `nexus-chat`, `nexus-memory`) only gained: `Engine::withdraw` takes back a commission still parked behind the working copy; `Engine::session_ended` lets a host that runs its own agent runtime announce a session's end, with `Worker::session_is_running` as the fallback answer where it never arrives; `StatusOperation` carries the two new operation-level flags; and flow's read layer gained `truncate_next` with its `NextPage` result, the one mechanism behind `nxf next --limit` and `prime`'s own truncation. Nothing was removed, renamed or re-typed — `cargo-semver-checks` confirms it against v0.63.0 for all three packages. The one thing worth wiring while you are here is `Engine::session_ended`: without it, a channel step that claims the working copy exclusively falls back to the process check and waits for someone to run `nxc tick`. Read the per-version entries on the releases page for the exact shape of each step. -[de] -**Für alle, die nexus-flow als Rust-Bibliothek einbetten: dieser Sprung ist rein additiv — neuen Tag pinnen, sonst nichts ändern.** Über die ganze Spanne haben die drei konsumierten Oberflächen (`nexus-flow-facade`, `nexus-chat`, `nexus-memory`) nur hinzugewonnen: `Engine::withdraw` nimmt eine Beauftragung zurück, die noch hinter der Arbeitskopie geparkt ist; `Engine::session_ended` lässt einen Wirt mit eigener Agent-Laufzeit das Ende einer Sitzung melden, mit `Worker::session_is_running` als Rückfallantwort, wo diese Meldung ausbleibt; `StatusOperation` trägt die zwei neuen Kennzeichen am Vorgang; und die Leseschicht von flow bekam `truncate_next` samt seinem Ergebnis `NextPage` — der eine Mechanismus hinter `nxf next --limit` und der Kürzung in `prime`. Nichts wurde entfernt, umbenannt oder umtypisiert; `cargo-semver-checks` bestätigt das gegen v0.63.0 für alle drei Pakete. Das eine, was Sie bei der Gelegenheit verdrahten sollten, ist `Engine::session_ended`: ohne es fällt ein Kanalschritt mit exklusivem Anspruch auf die Arbeitskopie auf die Prozessprüfung zurück und wartet darauf, dass jemand `nxc tick` ausführt. Die genaue Form jedes einzelnen Schritts steht in den Einträgen je Version auf der Releases-Seite. diff --git a/release-notes.json b/release-notes.json index 9788d37..ab3f0ae 100644 --- a/release-notes.json +++ b/release-notes.json @@ -16,4074 +16,8 @@ "en": "### Changed\n- **nexus-flow's source now lives in a public repository, starting from a single commit.** The code is\nthe same as in 0.103.0; what changed is where it is published. The history before this release stays\nin a private archive, so the release list on GitHub begins here. Installing and updating are\nunaffected: `install.sh` and `nxs self-update` keep fetching from `nxsflow.com/nxs`.", "de": "### Geändert\n- **Der Quellcode von nexus-flow liegt jetzt in einem öffentlichen Repository, beginnend mit einem\neinzigen Commit.** Der Code ist derselbe wie in 0.103.0; geändert hat sich, wo er veröffentlicht wird.\nDie Historie vor diesem Release bleibt in einem privaten Archiv, deshalb beginnt die Release-Liste auf\nGitHub hier. Installation und Aktualisierung sind nicht betroffen: `install.sh` und\n`nxs self-update` holen weiterhin von `nxsflow.com/nxs`." } - }, - { - "version": "0.103.0", - "date": "2026-09-23", - "items": [ - { - "type": "added", - "en": "**A chat runs on the machine it is meant for — start it on your phone, and the persona works on your\nMac.** A chat with a persona now runs on exactly one machine, and every other machine that syncs the\nworkspace shows it and starts nothing. Which machine is decided when the chat starts:\n`nxc send --to --machine ` for that chat, else a new `machine:` in the persona's\ndeclaration, else the machine that starts it. The answer is written into the chat, so a reply from\nany machine reaches the persona where it runs, and `nxc reply --thread --machine ` hands the\nchat to another machine. The designated machine's background service picks the chat up within about\nhalf a minute — it now asks the relay every 15 seconds whether anything is new, one small request\nper workspace — once per message, and only when the order and the designation come from a key on its\ntrust list. **When the machine is not online, you are asked:** nothing is sent, and the answer lists\nthe machines that are online to choose from. `nxc machine --to ` / `--thread ` shows\nwhere a chat runs and whether sending would ask, without writing anything. Declared channels still\nrun where they are started. The service now also pushes the first change of a freshly bound\nworkspace at once instead of at its next five-minute pass. **For embedders:**\n`EngineConfig::machines` takes the host's `nexus_chat::machine::Machines` (this machine, who is\nonline, the per-machine claims); `Engine::machine` and `Engine::pick_up` are new;\n`SendToRequest::machine` and `ReplyThreadRequest::machine` are new fields, and `SendToReceipt`,\n`ReplyReceipt` and `TriggerReceipt` carry `handed_to`; `Ctx`, `Adapter`, `Commission` and\n`RoleDecl` gained a field each, and `nexus_chat::run_from_with` takes the machines as a third\nargument. A host that passes no machines behaves exactly as before.", - "de": "**Ein Chat läuft auf der Maschine, für die er gedacht ist — auf dem Handy beginnen, und die Persona\narbeitet auf dem Mac.** Ein Chat mit einer Persona läuft jetzt auf genau einer Maschine, und jede\nandere Maschine, die den Arbeitsbereich synchronisiert, zeigt ihn und startet nichts. Welche\nMaschine, wird beim Beginn des Chats festgelegt: `nxc send --to --machine ` für\ndiesen Chat, sonst ein neues `machine:` in der Deklaration der Persona, sonst die Maschine, die ihn\nbeginnt. Die Antwort steht im Chat, sodass eine Antwort von jeder Maschine die Persona dort erreicht,\nwo sie läuft, und `nxc reply --thread --machine ` übergibt den Chat an eine andere Maschine.\nDer Hintergrunddienst der bestimmten Maschine nimmt den Chat binnen etwa einer halben Minute auf — er\nfragt den Relay jetzt alle 15 Sekunden, ob es etwas Neues gibt, eine kleine Anfrage je\nArbeitsbereich —, einmal je Nachricht und nur, wenn Auftrag und Festlegung von einem Schlüssel auf\nseiner Vertrauensliste stammen. **Ist die Maschine nicht online, wird nachgefragt:** Nichts wird\ngesendet, und die Antwort nennt die Maschinen, die online sind, zur Wahl.\n`nxc machine --to ` / `--thread ` zeigt, wo ein Chat läuft und ob Senden nachfragen\nwürde, ohne etwas zu schreiben. Deklarierte Kanäle laufen weiterhin dort, wo sie begonnen werden.\nDer Dienst schiebt außerdem die erste Änderung eines frisch gebundenen Arbeitsbereichs sofort hoch\nstatt beim nächsten Fünf-Minuten-Durchgang. **Für Einbettende:** `EngineConfig::machines` nimmt die\n`nexus_chat::machine::Machines` des Wirts (diese Maschine, wer online ist, die Ansprüche je\nMaschine); `Engine::machine` und `Engine::pick_up` sind neu; `SendToRequest::machine` und\n`ReplyThreadRequest::machine` sind neue Felder, und `SendToReceipt`, `ReplyReceipt` und\n`TriggerReceipt` tragen `handed_to`; `Ctx`, `Adapter`, `Commission` und `RoleDecl` haben je ein Feld\nmehr, und `nexus_chat::run_from_with` nimmt die Maschinen als drittes Argument. Ein Wirt, der keine\nMaschinen übergibt, verhält sich genau wie bisher.", - "facade": "breaking" - } - ], - "notes": { - "en": "### Added\n- **A chat runs on the machine it is meant for — start it on your phone, and the persona works on your\nMac.** A chat with a persona now runs on exactly one machine, and every other machine that syncs the\nworkspace shows it and starts nothing. Which machine is decided when the chat starts:\n`nxc send --to --machine ` for that chat, else a new `machine:` in the persona's\ndeclaration, else the machine that starts it. The answer is written into the chat, so a reply from\nany machine reaches the persona where it runs, and `nxc reply --thread --machine ` hands the\nchat to another machine. The designated machine's background service picks the chat up within about\nhalf a minute — it now asks the relay every 15 seconds whether anything is new, one small request\nper workspace — once per message, and only when the order and the designation come from a key on its\ntrust list. **When the machine is not online, you are asked:** nothing is sent, and the answer lists\nthe machines that are online to choose from. `nxc machine --to ` / `--thread ` shows\nwhere a chat runs and whether sending would ask, without writing anything. Declared channels still\nrun where they are started. The service now also pushes the first change of a freshly bound\nworkspace at once instead of at its next five-minute pass. **For embedders:**\n`EngineConfig::machines` takes the host's `nexus_chat::machine::Machines` (this machine, who is\nonline, the per-machine claims); `Engine::machine` and `Engine::pick_up` are new;\n`SendToRequest::machine` and `ReplyThreadRequest::machine` are new fields, and `SendToReceipt`,\n`ReplyReceipt` and `TriggerReceipt` carry `handed_to`; `Ctx`, `Adapter`, `Commission` and\n`RoleDecl` gained a field each, and `nexus_chat::run_from_with` takes the machines as a third\nargument. A host that passes no machines behaves exactly as before.\n\n### Facade Contract\n- `breaking` · **A chat runs on the machine it is meant for — start it on your phone, and the persona works on your\nMac.** A chat with a persona now runs on exactly one machine, and every other machine that syncs the\nworkspace shows it and starts nothing. Which machine is decided when the chat starts:\n`nxc send --to --machine ` for that chat, else a new `machine:` in the persona's\ndeclaration, else the machine that starts it. The answer is written into the chat, so a reply from\nany machine reaches the persona where it runs, and `nxc reply --thread --machine ` hands the\nchat to another machine. The designated machine's background service picks the chat up within about\nhalf a minute — it now asks the relay every 15 seconds whether anything is new, one small request\nper workspace — once per message, and only when the order and the designation come from a key on its\ntrust list. **When the machine is not online, you are asked:** nothing is sent, and the answer lists\nthe machines that are online to choose from. `nxc machine --to ` / `--thread ` shows\nwhere a chat runs and whether sending would ask, without writing anything. Declared channels still\nrun where they are started. The service now also pushes the first change of a freshly bound\nworkspace at once instead of at its next five-minute pass. **For embedders:**\n`EngineConfig::machines` takes the host's `nexus_chat::machine::Machines` (this machine, who is\nonline, the per-machine claims); `Engine::machine` and `Engine::pick_up` are new;\n`SendToRequest::machine` and `ReplyThreadRequest::machine` are new fields, and `SendToReceipt`,\n`ReplyReceipt` and `TriggerReceipt` carry `handed_to`; `Ctx`, `Adapter`, `Commission` and\n`RoleDecl` gained a field each, and `nexus_chat::run_from_with` takes the machines as a third\nargument. A host that passes no machines behaves exactly as before.", - "de": "### Neu\n- **Ein Chat läuft auf der Maschine, für die er gedacht ist — auf dem Handy beginnen, und die Persona\narbeitet auf dem Mac.** Ein Chat mit einer Persona läuft jetzt auf genau einer Maschine, und jede\nandere Maschine, die den Arbeitsbereich synchronisiert, zeigt ihn und startet nichts. Welche\nMaschine, wird beim Beginn des Chats festgelegt: `nxc send --to --machine ` für\ndiesen Chat, sonst ein neues `machine:` in der Deklaration der Persona, sonst die Maschine, die ihn\nbeginnt. Die Antwort steht im Chat, sodass eine Antwort von jeder Maschine die Persona dort erreicht,\nwo sie läuft, und `nxc reply --thread --machine ` übergibt den Chat an eine andere Maschine.\nDer Hintergrunddienst der bestimmten Maschine nimmt den Chat binnen etwa einer halben Minute auf — er\nfragt den Relay jetzt alle 15 Sekunden, ob es etwas Neues gibt, eine kleine Anfrage je\nArbeitsbereich —, einmal je Nachricht und nur, wenn Auftrag und Festlegung von einem Schlüssel auf\nseiner Vertrauensliste stammen. **Ist die Maschine nicht online, wird nachgefragt:** Nichts wird\ngesendet, und die Antwort nennt die Maschinen, die online sind, zur Wahl.\n`nxc machine --to ` / `--thread ` zeigt, wo ein Chat läuft und ob Senden nachfragen\nwürde, ohne etwas zu schreiben. Deklarierte Kanäle laufen weiterhin dort, wo sie begonnen werden.\nDer Dienst schiebt außerdem die erste Änderung eines frisch gebundenen Arbeitsbereichs sofort hoch\nstatt beim nächsten Fünf-Minuten-Durchgang. **Für Einbettende:** `EngineConfig::machines` nimmt die\n`nexus_chat::machine::Machines` des Wirts (diese Maschine, wer online ist, die Ansprüche je\nMaschine); `Engine::machine` und `Engine::pick_up` sind neu; `SendToRequest::machine` und\n`ReplyThreadRequest::machine` sind neue Felder, und `SendToReceipt`, `ReplyReceipt` und\n`TriggerReceipt` tragen `handed_to`; `Ctx`, `Adapter`, `Commission` und `RoleDecl` haben je ein Feld\nmehr, und `nexus_chat::run_from_with` nimmt die Maschinen als drittes Argument. Ein Wirt, der keine\nMaschinen übergibt, verhält sich genau wie bisher.\n\n### Facade-Kontrakt\n- `breaking` · **Ein Chat läuft auf der Maschine, für die er gedacht ist — auf dem Handy beginnen, und die Persona\narbeitet auf dem Mac.** Ein Chat mit einer Persona läuft jetzt auf genau einer Maschine, und jede\nandere Maschine, die den Arbeitsbereich synchronisiert, zeigt ihn und startet nichts. Welche\nMaschine, wird beim Beginn des Chats festgelegt: `nxc send --to --machine ` für\ndiesen Chat, sonst ein neues `machine:` in der Deklaration der Persona, sonst die Maschine, die ihn\nbeginnt. Die Antwort steht im Chat, sodass eine Antwort von jeder Maschine die Persona dort erreicht,\nwo sie läuft, und `nxc reply --thread --machine ` übergibt den Chat an eine andere Maschine.\nDer Hintergrunddienst der bestimmten Maschine nimmt den Chat binnen etwa einer halben Minute auf — er\nfragt den Relay jetzt alle 15 Sekunden, ob es etwas Neues gibt, eine kleine Anfrage je\nArbeitsbereich —, einmal je Nachricht und nur, wenn Auftrag und Festlegung von einem Schlüssel auf\nseiner Vertrauensliste stammen. **Ist die Maschine nicht online, wird nachgefragt:** Nichts wird\ngesendet, und die Antwort nennt die Maschinen, die online sind, zur Wahl.\n`nxc machine --to ` / `--thread ` zeigt, wo ein Chat läuft und ob Senden nachfragen\nwürde, ohne etwas zu schreiben. Deklarierte Kanäle laufen weiterhin dort, wo sie begonnen werden.\nDer Dienst schiebt außerdem die erste Änderung eines frisch gebundenen Arbeitsbereichs sofort hoch\nstatt beim nächsten Fünf-Minuten-Durchgang. **Für Einbettende:** `EngineConfig::machines` nimmt die\n`nexus_chat::machine::Machines` des Wirts (diese Maschine, wer online ist, die Ansprüche je\nMaschine); `Engine::machine` und `Engine::pick_up` sind neu; `SendToRequest::machine` und\n`ReplyThreadRequest::machine` sind neue Felder, und `SendToReceipt`, `ReplyReceipt` und\n`TriggerReceipt` tragen `handed_to`; `Ctx`, `Adapter`, `Commission` und `RoleDecl` haben je ein Feld\nmehr, und `nexus_chat::run_from_with` nimmt die Maschinen als drittes Argument. Ein Wirt, der keine\nMaschinen übergibt, verhält sich genau wie bisher." - } - }, - { - "version": "0.102.0", - "date": "2026-09-22", - "items": [ - { - "type": "fixed", - "en": "**A single op with an absurd Lamport number can no longer crash a machine or freeze its board.**\nAnyone who can write to a relay could plant an op numbered near the top of the clock; the next local\nchange then panicked, or in a release build wrapped around and lost every comparison from then on.\nNow an op numbered past 2^62 is kept in the log — visible in history, as every op is — but folds\nnowhere and moves no clock, and an op received from elsewhere moves this machine's clock at most to\n2^61, so local writes always have room to follow. The one thing such an op can still do is keep\nwinning its own field against later writes; which ops an agent ACTS on is the signature's question,\nnot the clock's. A snapshot of a board holding such an op now loads (it used to be refused, so one\nplanted op could make every snapshot of a board unloadable). **For embedders:**\n`nxs_foundation::model::{MAX_LAMPORT, CLOCK_CEILING}` and `Op::lamport_in_bound`;\n`nxs_foundation::image::MAX_LAMPORT` remains as a re-export.", - "de": "**Eine einzelne Op mit absurder Lamport-Nummer kann eine Maschine nicht mehr abstürzen lassen und\nihr Brett nicht mehr einfrieren.** Wer an einen Relay schreiben kann, konnte eine Op mit einer Nummer\nnahe dem oberen Ende der Uhr einschleusen; die nächste lokale Änderung stürzte dann ab oder lief in\neinem Release-Build über und verlor ab da jeden Vergleich. Jetzt wird eine Op mit einer Nummer über\n2^62 im Log behalten — in der Historie sichtbar wie jede Op —, aber nirgends gefaltet und bewegt keine\nUhr, und eine von anderswo empfangene Op bewegt die Uhr dieser Maschine höchstens bis 2^61, sodass\nlokale Schreibvorgänge immer Platz haben, zu folgen. Das Einzige, was eine solche Op noch kann: ihr\neigenes Feld gegen spätere Schreibvorgänge weiter gewinnen; auf welche Ops ein Agent HANDELT,\nentscheidet die Unterschrift, nicht die Uhr. Ein Schnappschuss eines Bretts, das eine solche Op hält,\nlässt sich jetzt laden (er wurde bisher abgewiesen, eine einzige eingeschleuste Op konnte also jeden\nSchnappschuss eines Bretts unladbar machen). **Für Einbettende:**\n`nxs_foundation::model::{MAX_LAMPORT, CLOCK_CEILING}` und `Op::lamport_in_bound`;\n`nxs_foundation::image::MAX_LAMPORT` bleibt als Re-Export.", - "facade": "changed", - "security": true - }, - { - "type": "added", - "en": "**Every op is signed, and an agent acts only on what a key you trust signed.** Each workspace now\nhas a signing key of its own (`.nxs/signing.key`, readable by you alone, created on first open), and\nevery op it writes carries its key id and signature. Every op it receives is checked, and the result\nis kept beside it in the log. The check decides one thing: whether an agent action may follow the\nop here — the board itself never asks, so every op is still stored, shown and folded and every\nmachine still converges. An action follows an op this workspace wrote, or one whose signature checks\nout against a key on the workspace's **trust list**; everything else — an untrusted key, a signature\nthat does not check out, no signature at all — is shown and acts nowhere. So a relay, or anyone who\ncan reach it, can no longer answer a round in someone's name, pick the session a reply wakes, move a\nthread's deadline or re-declare who owes an answer: such a message is marked `unvouched` in\n`nxc threads show` (`\"unvouched\": true` in `--json`), and a thread whose obligation came from such an\nop is **held** — `nxc tick` answers `held` instead of acting. New verbs: `nxs sync key` prints this\nworkspace's key id; `nxs sync trust add --name `, `nxs sync trust remove ` and\n`nxs sync trust list` keep the list, which is local, never synced and not in a snapshot (compare the\nkey over a channel you trust, never through the relay; there is no MCP tool for it on purpose);\n`nxs sync verify ` shows who signed one op and whether it may act. A sync pass\nwarns when signatures went missing on the way — a relay older than nxs 0.58 drops them: upgrade the\nrelay before relying on this. Ops written before this version on this machine count as its own;\nunsigned ops from other machines act nowhere. **For embedders:** `Engine::{key_id, trusted_keys,\ntrust_key, distrust_key, untrusted_signers, op_provenance, acts_on}` on the flow facade;\n`nxs_foundation::{signing, trust}`; `Op` carries `key_id` and `sig`; chat's `MessageView` gains\n`unvouched` and `ThreadQuorum` gains `held` (both omitted from JSON when false), `MessageRow` gains\n`acts` — a struct literal built by hand needs the new fields. The workspace schema moves to v7\n(additive; an older nxs still opens it). Design: `docs/specs/E4-auth-identity-and-signed-ops.md`.", - "de": "**Jede Op ist unterschrieben, und ein Agent handelt nur auf das, was ein Schlüssel unterschrieben\nhat, dem du vertraust.** Jeder Arbeitsbereich hat jetzt einen eigenen Signierschlüssel\n(`.nxs/signing.key`, nur für dich lesbar, beim ersten Öffnen angelegt), und jede Op, die er schreibt,\nträgt dessen Kennung und Unterschrift. Jede empfangene Op wird geprüft, das Ergebnis steht neben ihr\nim Log. Die Prüfung entscheidet nur eines: ob hier eine Agenten-Aktion auf die Op folgen darf — das\nBrett selbst fragt nie danach, jede Op wird weiter gespeichert, gezeigt und gefaltet, und alle\nMaschinen kommen weiter beim selben Stand an. Eine Aktion folgt einer Op, die dieser Arbeitsbereich\ngeschrieben hat, oder einer, deren Unterschrift zu einem Schlüssel auf seiner **Vertrauensliste**\npasst; alles andere — ein nicht vertrauter Schlüssel, eine Unterschrift, die nicht passt, gar keine\nUnterschrift — wird gezeigt und bewirkt nichts. Ein Relay oder jemand, der ihn erreicht, kann also\nkeine Runde mehr in fremdem Namen beantworten, nicht die Sitzung wählen, die eine Antwort weckt,\nkeine Frist eines Threads verschieben und nicht neu festlegen, wer eine Antwort schuldet: Eine solche\nNachricht ist in `nxc threads show` als `unvouched` markiert (`\"unvouched\": true` in `--json`), und\nein Thread, dessen Verpflichtung aus einer solchen Op stammt, ist **gehalten** — `nxc tick` antwortet\n`held`, statt zu handeln. Neue Verben: `nxs sync key` gibt die Schlüssel-Kennung dieses\nArbeitsbereichs aus; `nxs sync trust add --name `, `nxs sync trust remove ` und\n`nxs sync trust list` pflegen die Liste, die lokal ist, nie synchronisiert wird und in keinem\nSchnappschuss steckt (vergleiche den Schlüssel über einen Weg, dem du traust, nie über den Relay;\nein MCP-Werkzeug dafür gibt es absichtlich nicht); `nxs sync verify ` zeigt,\nwer eine Op unterschrieben hat und ob sie wirken darf. Ein Sync-Durchlauf warnt, wenn unterwegs\nUnterschriften verloren gingen — ein Relay älter als nxs 0.58 wirft sie weg: aktualisiere den Relay,\nbevor du dich darauf verlässt. Ops, die diese Maschine vor dieser Version geschrieben hat, gelten als\nihre eigenen; unsignierte Ops anderer Maschinen bewirken nichts. **Für Einbettende:**\n`Engine::{key_id, trusted_keys, trust_key, distrust_key, untrusted_signers, op_provenance, acts_on}`\nan der Flow-Fassade; `nxs_foundation::{signing, trust}`; `Op` trägt `key_id` und `sig`; im Chat\nbekommt `MessageView` das Feld `unvouched` und `ThreadQuorum` das Feld `held` (beide fehlen im JSON,\nwenn sie falsch sind), `MessageRow` bekommt `acts` — ein von Hand gebautes Struct-Literal braucht die\nneuen Felder. Das Schema des Arbeitsbereichs geht auf v7 (additiv; ein älteres nxs öffnet ihn\nweiterhin). Entwurf: `docs/specs/E4-auth-identity-and-signed-ops.md`.", - "facade": "breaking" - } - ], - "notes": { - "en": "### Added\n- **Every op is signed, and an agent acts only on what a key you trust signed.** Each workspace now\nhas a signing key of its own (`.nxs/signing.key`, readable by you alone, created on first open), and\nevery op it writes carries its key id and signature. Every op it receives is checked, and the result\nis kept beside it in the log. The check decides one thing: whether an agent action may follow the\nop here — the board itself never asks, so every op is still stored, shown and folded and every\nmachine still converges. An action follows an op this workspace wrote, or one whose signature checks\nout against a key on the workspace's **trust list**; everything else — an untrusted key, a signature\nthat does not check out, no signature at all — is shown and acts nowhere. So a relay, or anyone who\ncan reach it, can no longer answer a round in someone's name, pick the session a reply wakes, move a\nthread's deadline or re-declare who owes an answer: such a message is marked `unvouched` in\n`nxc threads show` (`\"unvouched\": true` in `--json`), and a thread whose obligation came from such an\nop is **held** — `nxc tick` answers `held` instead of acting. New verbs: `nxs sync key` prints this\nworkspace's key id; `nxs sync trust add --name `, `nxs sync trust remove ` and\n`nxs sync trust list` keep the list, which is local, never synced and not in a snapshot (compare the\nkey over a channel you trust, never through the relay; there is no MCP tool for it on purpose);\n`nxs sync verify ` shows who signed one op and whether it may act. A sync pass\nwarns when signatures went missing on the way — a relay older than nxs 0.58 drops them: upgrade the\nrelay before relying on this. Ops written before this version on this machine count as its own;\nunsigned ops from other machines act nowhere. **For embedders:** `Engine::{key_id, trusted_keys,\ntrust_key, distrust_key, untrusted_signers, op_provenance, acts_on}` on the flow facade;\n`nxs_foundation::{signing, trust}`; `Op` carries `key_id` and `sig`; chat's `MessageView` gains\n`unvouched` and `ThreadQuorum` gains `held` (both omitted from JSON when false), `MessageRow` gains\n`acts` — a struct literal built by hand needs the new fields. The workspace schema moves to v7\n(additive; an older nxs still opens it). Design: `docs/specs/E4-auth-identity-and-signed-ops.md`.\n\n### Fixed\n- **A single op with an absurd Lamport number can no longer crash a machine or freeze its board.**\nAnyone who can write to a relay could plant an op numbered near the top of the clock; the next local\nchange then panicked, or in a release build wrapped around and lost every comparison from then on.\nNow an op numbered past 2^62 is kept in the log — visible in history, as every op is — but folds\nnowhere and moves no clock, and an op received from elsewhere moves this machine's clock at most to\n2^61, so local writes always have room to follow. The one thing such an op can still do is keep\nwinning its own field against later writes; which ops an agent ACTS on is the signature's question,\nnot the clock's. A snapshot of a board holding such an op now loads (it used to be refused, so one\nplanted op could make every snapshot of a board unloadable). **For embedders:**\n`nxs_foundation::model::{MAX_LAMPORT, CLOCK_CEILING}` and `Op::lamport_in_bound`;\n`nxs_foundation::image::MAX_LAMPORT` remains as a re-export.\n\n### Facade Contract\n- `changed` · security · **A single op with an absurd Lamport number can no longer crash a machine or freeze its board.**\nAnyone who can write to a relay could plant an op numbered near the top of the clock; the next local\nchange then panicked, or in a release build wrapped around and lost every comparison from then on.\nNow an op numbered past 2^62 is kept in the log — visible in history, as every op is — but folds\nnowhere and moves no clock, and an op received from elsewhere moves this machine's clock at most to\n2^61, so local writes always have room to follow. The one thing such an op can still do is keep\nwinning its own field against later writes; which ops an agent ACTS on is the signature's question,\nnot the clock's. A snapshot of a board holding such an op now loads (it used to be refused, so one\nplanted op could make every snapshot of a board unloadable). **For embedders:**\n`nxs_foundation::model::{MAX_LAMPORT, CLOCK_CEILING}` and `Op::lamport_in_bound`;\n`nxs_foundation::image::MAX_LAMPORT` remains as a re-export.\n- `breaking` · **Every op is signed, and an agent acts only on what a key you trust signed.** Each workspace now\nhas a signing key of its own (`.nxs/signing.key`, readable by you alone, created on first open), and\nevery op it writes carries its key id and signature. Every op it receives is checked, and the result\nis kept beside it in the log. The check decides one thing: whether an agent action may follow the\nop here — the board itself never asks, so every op is still stored, shown and folded and every\nmachine still converges. An action follows an op this workspace wrote, or one whose signature checks\nout against a key on the workspace's **trust list**; everything else — an untrusted key, a signature\nthat does not check out, no signature at all — is shown and acts nowhere. So a relay, or anyone who\ncan reach it, can no longer answer a round in someone's name, pick the session a reply wakes, move a\nthread's deadline or re-declare who owes an answer: such a message is marked `unvouched` in\n`nxc threads show` (`\"unvouched\": true` in `--json`), and a thread whose obligation came from such an\nop is **held** — `nxc tick` answers `held` instead of acting. New verbs: `nxs sync key` prints this\nworkspace's key id; `nxs sync trust add --name `, `nxs sync trust remove ` and\n`nxs sync trust list` keep the list, which is local, never synced and not in a snapshot (compare the\nkey over a channel you trust, never through the relay; there is no MCP tool for it on purpose);\n`nxs sync verify ` shows who signed one op and whether it may act. A sync pass\nwarns when signatures went missing on the way — a relay older than nxs 0.58 drops them: upgrade the\nrelay before relying on this. Ops written before this version on this machine count as its own;\nunsigned ops from other machines act nowhere. **For embedders:** `Engine::{key_id, trusted_keys,\ntrust_key, distrust_key, untrusted_signers, op_provenance, acts_on}` on the flow facade;\n`nxs_foundation::{signing, trust}`; `Op` carries `key_id` and `sig`; chat's `MessageView` gains\n`unvouched` and `ThreadQuorum` gains `held` (both omitted from JSON when false), `MessageRow` gains\n`acts` — a struct literal built by hand needs the new fields. The workspace schema moves to v7\n(additive; an older nxs still opens it). Design: `docs/specs/E4-auth-identity-and-signed-ops.md`.", - "de": "### Neu\n- **Jede Op ist unterschrieben, und ein Agent handelt nur auf das, was ein Schlüssel unterschrieben\nhat, dem du vertraust.** Jeder Arbeitsbereich hat jetzt einen eigenen Signierschlüssel\n(`.nxs/signing.key`, nur für dich lesbar, beim ersten Öffnen angelegt), und jede Op, die er schreibt,\nträgt dessen Kennung und Unterschrift. Jede empfangene Op wird geprüft, das Ergebnis steht neben ihr\nim Log. Die Prüfung entscheidet nur eines: ob hier eine Agenten-Aktion auf die Op folgen darf — das\nBrett selbst fragt nie danach, jede Op wird weiter gespeichert, gezeigt und gefaltet, und alle\nMaschinen kommen weiter beim selben Stand an. Eine Aktion folgt einer Op, die dieser Arbeitsbereich\ngeschrieben hat, oder einer, deren Unterschrift zu einem Schlüssel auf seiner **Vertrauensliste**\npasst; alles andere — ein nicht vertrauter Schlüssel, eine Unterschrift, die nicht passt, gar keine\nUnterschrift — wird gezeigt und bewirkt nichts. Ein Relay oder jemand, der ihn erreicht, kann also\nkeine Runde mehr in fremdem Namen beantworten, nicht die Sitzung wählen, die eine Antwort weckt,\nkeine Frist eines Threads verschieben und nicht neu festlegen, wer eine Antwort schuldet: Eine solche\nNachricht ist in `nxc threads show` als `unvouched` markiert (`\"unvouched\": true` in `--json`), und\nein Thread, dessen Verpflichtung aus einer solchen Op stammt, ist **gehalten** — `nxc tick` antwortet\n`held`, statt zu handeln. Neue Verben: `nxs sync key` gibt die Schlüssel-Kennung dieses\nArbeitsbereichs aus; `nxs sync trust add --name `, `nxs sync trust remove ` und\n`nxs sync trust list` pflegen die Liste, die lokal ist, nie synchronisiert wird und in keinem\nSchnappschuss steckt (vergleiche den Schlüssel über einen Weg, dem du traust, nie über den Relay;\nein MCP-Werkzeug dafür gibt es absichtlich nicht); `nxs sync verify ` zeigt,\nwer eine Op unterschrieben hat und ob sie wirken darf. Ein Sync-Durchlauf warnt, wenn unterwegs\nUnterschriften verloren gingen — ein Relay älter als nxs 0.58 wirft sie weg: aktualisiere den Relay,\nbevor du dich darauf verlässt. Ops, die diese Maschine vor dieser Version geschrieben hat, gelten als\nihre eigenen; unsignierte Ops anderer Maschinen bewirken nichts. **Für Einbettende:**\n`Engine::{key_id, trusted_keys, trust_key, distrust_key, untrusted_signers, op_provenance, acts_on}`\nan der Flow-Fassade; `nxs_foundation::{signing, trust}`; `Op` trägt `key_id` und `sig`; im Chat\nbekommt `MessageView` das Feld `unvouched` und `ThreadQuorum` das Feld `held` (beide fehlen im JSON,\nwenn sie falsch sind), `MessageRow` bekommt `acts` — ein von Hand gebautes Struct-Literal braucht die\nneuen Felder. Das Schema des Arbeitsbereichs geht auf v7 (additiv; ein älteres nxs öffnet ihn\nweiterhin). Entwurf: `docs/specs/E4-auth-identity-and-signed-ops.md`.\n\n### Behoben\n- **Eine einzelne Op mit absurder Lamport-Nummer kann eine Maschine nicht mehr abstürzen lassen und\nihr Brett nicht mehr einfrieren.** Wer an einen Relay schreiben kann, konnte eine Op mit einer Nummer\nnahe dem oberen Ende der Uhr einschleusen; die nächste lokale Änderung stürzte dann ab oder lief in\neinem Release-Build über und verlor ab da jeden Vergleich. Jetzt wird eine Op mit einer Nummer über\n2^62 im Log behalten — in der Historie sichtbar wie jede Op —, aber nirgends gefaltet und bewegt keine\nUhr, und eine von anderswo empfangene Op bewegt die Uhr dieser Maschine höchstens bis 2^61, sodass\nlokale Schreibvorgänge immer Platz haben, zu folgen. Das Einzige, was eine solche Op noch kann: ihr\neigenes Feld gegen spätere Schreibvorgänge weiter gewinnen; auf welche Ops ein Agent HANDELT,\nentscheidet die Unterschrift, nicht die Uhr. Ein Schnappschuss eines Bretts, das eine solche Op hält,\nlässt sich jetzt laden (er wurde bisher abgewiesen, eine einzige eingeschleuste Op konnte also jeden\nSchnappschuss eines Bretts unladbar machen). **Für Einbettende:**\n`nxs_foundation::model::{MAX_LAMPORT, CLOCK_CEILING}` und `Op::lamport_in_bound`;\n`nxs_foundation::image::MAX_LAMPORT` bleibt als Re-Export.\n\n### Facade-Kontrakt\n- `changed` · Sicherheit · **Eine einzelne Op mit absurder Lamport-Nummer kann eine Maschine nicht mehr abstürzen lassen und\nihr Brett nicht mehr einfrieren.** Wer an einen Relay schreiben kann, konnte eine Op mit einer Nummer\nnahe dem oberen Ende der Uhr einschleusen; die nächste lokale Änderung stürzte dann ab oder lief in\neinem Release-Build über und verlor ab da jeden Vergleich. Jetzt wird eine Op mit einer Nummer über\n2^62 im Log behalten — in der Historie sichtbar wie jede Op —, aber nirgends gefaltet und bewegt keine\nUhr, und eine von anderswo empfangene Op bewegt die Uhr dieser Maschine höchstens bis 2^61, sodass\nlokale Schreibvorgänge immer Platz haben, zu folgen. Das Einzige, was eine solche Op noch kann: ihr\neigenes Feld gegen spätere Schreibvorgänge weiter gewinnen; auf welche Ops ein Agent HANDELT,\nentscheidet die Unterschrift, nicht die Uhr. Ein Schnappschuss eines Bretts, das eine solche Op hält,\nlässt sich jetzt laden (er wurde bisher abgewiesen, eine einzige eingeschleuste Op konnte also jeden\nSchnappschuss eines Bretts unladbar machen). **Für Einbettende:**\n`nxs_foundation::model::{MAX_LAMPORT, CLOCK_CEILING}` und `Op::lamport_in_bound`;\n`nxs_foundation::image::MAX_LAMPORT` bleibt als Re-Export.\n- `breaking` · **Jede Op ist unterschrieben, und ein Agent handelt nur auf das, was ein Schlüssel unterschrieben\nhat, dem du vertraust.** Jeder Arbeitsbereich hat jetzt einen eigenen Signierschlüssel\n(`.nxs/signing.key`, nur für dich lesbar, beim ersten Öffnen angelegt), und jede Op, die er schreibt,\nträgt dessen Kennung und Unterschrift. Jede empfangene Op wird geprüft, das Ergebnis steht neben ihr\nim Log. Die Prüfung entscheidet nur eines: ob hier eine Agenten-Aktion auf die Op folgen darf — das\nBrett selbst fragt nie danach, jede Op wird weiter gespeichert, gezeigt und gefaltet, und alle\nMaschinen kommen weiter beim selben Stand an. Eine Aktion folgt einer Op, die dieser Arbeitsbereich\ngeschrieben hat, oder einer, deren Unterschrift zu einem Schlüssel auf seiner **Vertrauensliste**\npasst; alles andere — ein nicht vertrauter Schlüssel, eine Unterschrift, die nicht passt, gar keine\nUnterschrift — wird gezeigt und bewirkt nichts. Ein Relay oder jemand, der ihn erreicht, kann also\nkeine Runde mehr in fremdem Namen beantworten, nicht die Sitzung wählen, die eine Antwort weckt,\nkeine Frist eines Threads verschieben und nicht neu festlegen, wer eine Antwort schuldet: Eine solche\nNachricht ist in `nxc threads show` als `unvouched` markiert (`\"unvouched\": true` in `--json`), und\nein Thread, dessen Verpflichtung aus einer solchen Op stammt, ist **gehalten** — `nxc tick` antwortet\n`held`, statt zu handeln. Neue Verben: `nxs sync key` gibt die Schlüssel-Kennung dieses\nArbeitsbereichs aus; `nxs sync trust add --name `, `nxs sync trust remove ` und\n`nxs sync trust list` pflegen die Liste, die lokal ist, nie synchronisiert wird und in keinem\nSchnappschuss steckt (vergleiche den Schlüssel über einen Weg, dem du traust, nie über den Relay;\nein MCP-Werkzeug dafür gibt es absichtlich nicht); `nxs sync verify ` zeigt,\nwer eine Op unterschrieben hat und ob sie wirken darf. Ein Sync-Durchlauf warnt, wenn unterwegs\nUnterschriften verloren gingen — ein Relay älter als nxs 0.58 wirft sie weg: aktualisiere den Relay,\nbevor du dich darauf verlässt. Ops, die diese Maschine vor dieser Version geschrieben hat, gelten als\nihre eigenen; unsignierte Ops anderer Maschinen bewirken nichts. **Für Einbettende:**\n`Engine::{key_id, trusted_keys, trust_key, distrust_key, untrusted_signers, op_provenance, acts_on}`\nan der Flow-Fassade; `nxs_foundation::{signing, trust}`; `Op` trägt `key_id` und `sig`; im Chat\nbekommt `MessageView` das Feld `unvouched` und `ThreadQuorum` das Feld `held` (beide fehlen im JSON,\nwenn sie falsch sind), `MessageRow` bekommt `acts` — ein von Hand gebautes Struct-Literal braucht die\nneuen Felder. Das Schema des Arbeitsbereichs geht auf v7 (additiv; ein älteres nxs öffnet ihn\nweiterhin). Entwurf: `docs/specs/E4-auth-identity-and-signed-ops.md`." - } - }, - { - "version": "0.101.0", - "date": "2026-09-21", - "items": [ - { - "type": "added", - "en": "**A new replica starts from a snapshot instead of folding the whole history.** `nxs sync snapshot\n` writes a snapshot of a workspace that syncs a board — its op log, the board folded from it,\nand the relay position the two reach. On a new machine, `nxs sync bind --snapshot ` joins\nthat stream from it, and the first pass pulls only what came after. The promise is exact: a\nsnapshot plus the rest folds to the same board as the whole history, late edits written into the\npast and ops a relay stores twice included. It is refused by name where it could not keep that\npromise: the source still holds ops it has not pushed, the new workspace is not empty, or the\nsnapshot comes from an nxs whose database this one cannot read; one another nxs version wrote is\nfolded again from the log it carries. The position is checked against the relay on both ends: the\nsource refuses a snapshot when the relay does not hold, at its position, an op it has, and records\nthat op; before anything is written, the new machine requires the relay to hold the same op there\nand pulls from the start otherwise — slower, never short. A snapshot is taken as given, log and board\nalike, so treat the file as you treat the relay; one the import could not read back is refused whole.\nThe relay does not hold snapshots — where one lives is up to you. **For embedders:**\n`nxs_sync::snapshot::{export, import, peek}` is the same mechanism at the seam, for a host that\nstarts cold and keeps its snapshots itself (`export` takes the transport, to confirm the position; a\nreplica that will write registers its prefix before importing); `Store::image`/`load_image` are the substrate half, and\n`Reducer::view_tables` is now the one list of a reducer's views (`clear_views` empties exactly\nthose by default). Measured on a real board of 6,065 ops against a local relay: a new machine that\nfolds the whole history took about 1.35 s and pulled 3.3 MB; one that started from a snapshot took\nabout 0.2 s and read a 1.5 MB file — roughly an eighth of the time, and at the embedding seam, in\nmemory, roughly a quarter.", - "de": "**Ein neues Replikat startet von einem Schnappschuss, statt die ganze Historie zu falten.**\n`nxs sync snapshot ` schreibt einen Schnappschuss eines Arbeitsbereichs, der ein Brett\nsynchronisiert — sein Op-Log, das daraus gefaltete Brett und die Relay-Position, bis zu der beide\nreichen. Auf einer neuen Maschine tritt `nxs sync bind --snapshot ` dem Stream damit bei, und\nder erste Durchlauf zieht nur noch, was danach kam. Die Zusage ist exakt: Ein Schnappschuss plus der\nRest faltet zum selben Brett wie die ganze Historie, auch mit Änderungen, die in die Vergangenheit\ngeschrieben wurden, und mit Ops, die ein Relay doppelt hält. Wo er das nicht halten könnte, wird er\nnamentlich abgewiesen: Die Quelle hält noch ungepushte Ops, der neue Arbeitsbereich ist nicht leer,\noder der Schnappschuss stammt von einem nxs, dessen Datenbank dieses nicht lesen kann; einer von\neiner anderen nxs-Version wird aus dem mitgelieferten Log neu gefaltet. Die Position wird an beiden Enden gegen den Relay geprüft: Die Quelle verweigert einen\nSchnappschuss, wenn der Relay an seiner Position keine Op hält, die sie hat, und hält diese Op fest;\nbevor etwas geschrieben wird, verlangt die neue Maschine, dass der Relay dort dieselbe Op hält, und\nzieht sonst von vorn — langsamer, aber nie lückenhaft. Ein Schnappschuss wird genommen, wie er ist,\nLog und Brett gleichermaßen; behandle die Datei also wie den Relay. Einer, den der Import nicht\nzurücklesen könnte, wird ganz abgewiesen. Der Relay hält keine Schnappschüsse — wo einer liegt,\nentscheidest du. **Für Einbetter:** `nxs_sync::snapshot::{export, import, peek}` ist derselbe\nMechanismus an der Naht, für einen Host, der kalt startet und seine Schnappschüsse selbst ablegt\n(`export` bekommt den Transport, um die Position zu bestätigen; ein Replikat, das schreiben wird,\nregistriert sein Präfix vor dem Import);\n`Store::image`/`load_image` sind die Hälfte im Substrat, und `Reducer::view_tables` ist jetzt die\neine Liste der Views eines Reducers (`clear_views` leert standardmäßig genau diese). Gemessen an einem echten Brett mit 6.065 Ops gegen einen lokalen Relay: Eine neue\nMaschine, die die ganze Historie faltete, brauchte etwa 1,35 s und zog 3,3 MB; eine, die vom\nSchnappschuss startete, etwa 0,2 s und las eine Datei von 1,5 MB — rund ein Achtel der Zeit, an der\nEinbettungs-Naht im Speicher rund ein Viertel.", - "facade": "changed" - }, - { - "type": "fixed", - "en": "**Security:** a relay URL carrying credentials (`https://:@relay.example`) no longer\nleaks them. A failed sync pass used to quote the key whole, and from there it reached `service.log`,\nthe background service's heartbeat and every reader of its attendance — the apps and agent sessions\nthat show what the service last did. Now the credential is taken off the URL before any request is\nbuilt and sent as the `Authorization: Basic` header the transport derived from it before (none for\nan empty userinfo; kept on a redirect to the same host, never to another), and every error is\nscrubbed of it in any form — as userinfo, as the header's base64, as the password on its own. Every\nmessage names the relay by host, port and path only. `nxs sync machines`, `nxs sync bind --json` and\n`nxs sync endpoint` show the endpoint masked, and a configuration file's parse error no longer quotes\nit. `.nxs/sync.toml`, `~/.nexusflow/config.toml` and a snapshot file are now written readable by\ntheir owner alone. What an existing `service.log` already holds is not rewritten: if a failed pass\nran against such a URL, rotate that key. Also fixed: an app reading the service's attendance for a\nworkspace now sees what the service last did with it — the reading never found the workspace\nbefore, because the service recorded it under its `.nxs` directory.", - "de": "**Sicherheit:** Eine Relay-URL mit Zugangsdaten (`https://:@relay.example`)\nverrät sie nicht mehr. Ein fehlgeschlagener Sync-Durchlauf zitierte den Schlüssel bisher\nvollständig, und von dort gelangte er in `service.log`, in den Herzschlag des Hintergrunddienstes und\nzu jedem, der dessen Attendance liest — also zu den Apps und Agentensitzungen, die zeigen, was der\nDienst zuletzt getan hat. Jetzt wird die Zugangsangabe von der URL genommen, bevor eine Anfrage\nentsteht, und als der `Authorization: Basic`-Header gesendet, den der Transport vorher daraus gebildet\nhat (keiner bei leerer Userinfo; bei einer Umleitung auf denselben Host behalten, nie auf einen\nanderen). Jede Fehlermeldung wird davon bereinigt, in jeder Form — als Userinfo, als Base64 des\nHeaders, als Passwort allein. Jede Meldung nennt das Relay nur mit Host, Port und Pfad.\n`nxs sync machines`, `nxs sync bind --json` und `nxs sync endpoint` zeigen den Endpunkt maskiert, und\nein Parsefehler einer Konfigurationsdatei zitiert ihn nicht mehr. `.nxs/sync.toml`,\n`~/.nexusflow/config.toml` und eine Schnappschuss-Datei werden jetzt nur für ihren Besitzer lesbar\ngeschrieben. Was ein bestehendes `service.log` schon enthält, wird nicht umgeschrieben: Lief ein\nfehlgeschlagener Durchlauf gegen eine solche URL, rotiere diesen Schlüssel. Ebenfalls behoben: Eine\nApp, die die Attendance des Dienstes für einen Arbeitsbereich liest, sieht jetzt, was der Dienst\nzuletzt damit getan hat. Bisher fand die Abfrage den Arbeitsbereich nie, weil der Dienst ihn unter\nseinem `.nxs`-Verzeichnis vermerkte.", - "security": true - }, - { - "type": "changed", - "en": "**No op can be deleted from the log any more, and what an op is cannot be changed.** The database\nnow refuses to delete an op — including through an `INSERT OR REPLACE` — and refuses to change an\nop's id, its `(lamport, site)` coordinate or its author. The log was append-only before because no\ncode deleted; now a path that tried would fail the first time it ran instead of passing every test.\nWhat stays writable on purpose: an op's target and value, which the prefix remap and the legacy\n`belongs_to` migration rewrite and still do. No public method of nxs deletes an op or changes its\nidentity, so nothing that goes through the API changes. **For embedders:** raw SQL through\n`Store::connection()` that deletes or replaces rows of `ops`, or updates `op_id`, `lamport`, `site`\nor `author`, now fails with an error naming the rule — including a test fixture that fabricates an\nolder log that way. `nxs doctor` also reports, in `--json` as `duplicate_coordinates` and as a\nwarning line, when two ops share one `(lamport, site)` coordinate: something only a log written\nbefore nxs could refuse it carries, and on which a concurrent edit is decided by arrival order\nrather than the CRDT order.", - "de": "**Aus dem Log lässt sich keine Op mehr löschen, und was eine Op ist, lässt sich nicht mehr ändern.**\nDie Datenbank weist es ab, eine Op zu löschen — auch über ein `INSERT OR REPLACE` — und ebenso, die\nID einer Op, ihre `(lamport, site)`-Koordinate oder ihren Autor zu ändern. Bisher war das Log\nappend-only, weil kein Code löschte; jetzt scheitert ein Pfad, der es versucht, beim ersten Lauf,\nstatt jeden Test zu bestehen. Bewusst schreibbar bleiben Ziel und Wert einer Op, die der\nPräfix-Remap und die alte `belongs_to`-Migration umschreiben und weiter umschreiben. Keine\nöffentliche Methode von nxs löscht eine Op oder ändert ihre Identität; wer über die API geht, merkt\nalso nichts. **Für Einbetter:** Rohes SQL über `Store::connection()`, das Zeilen von `ops` löscht\noder ersetzt oder `op_id`, `lamport`, `site` oder `author` ändert, scheitert jetzt mit einer\nMeldung, die die Regel nennt — auch eine Test-Fixture, die so ein älteres Log nachbaut.\n`nxs doctor` meldet außerdem, in `--json` als `duplicate_coordinates` und als Warnzeile, wenn zwei\nOps dieselbe `(lamport, site)`-Koordinate tragen. Das kommt nur in einem Log vor, das geschrieben\nwurde, bevor nxs es abweisen konnte, und dort entscheidet bei einer gleichzeitigen Änderung die\nAnkunftsreihenfolge statt der CRDT-Ordnung.", - "facade": "breaking" - } - ], - "notes": { - "en": "### Added\n- **A new replica starts from a snapshot instead of folding the whole history.** `nxs sync snapshot\n` writes a snapshot of a workspace that syncs a board — its op log, the board folded from it,\nand the relay position the two reach. On a new machine, `nxs sync bind --snapshot ` joins\nthat stream from it, and the first pass pulls only what came after. The promise is exact: a\nsnapshot plus the rest folds to the same board as the whole history, late edits written into the\npast and ops a relay stores twice included. It is refused by name where it could not keep that\npromise: the source still holds ops it has not pushed, the new workspace is not empty, or the\nsnapshot comes from an nxs whose database this one cannot read; one another nxs version wrote is\nfolded again from the log it carries. The position is checked against the relay on both ends: the\nsource refuses a snapshot when the relay does not hold, at its position, an op it has, and records\nthat op; before anything is written, the new machine requires the relay to hold the same op there\nand pulls from the start otherwise — slower, never short. A snapshot is taken as given, log and board\nalike, so treat the file as you treat the relay; one the import could not read back is refused whole.\nThe relay does not hold snapshots — where one lives is up to you. **For embedders:**\n`nxs_sync::snapshot::{export, import, peek}` is the same mechanism at the seam, for a host that\nstarts cold and keeps its snapshots itself (`export` takes the transport, to confirm the position; a\nreplica that will write registers its prefix before importing); `Store::image`/`load_image` are the substrate half, and\n`Reducer::view_tables` is now the one list of a reducer's views (`clear_views` empties exactly\nthose by default). Measured on a real board of 6,065 ops against a local relay: a new machine that\nfolds the whole history took about 1.35 s and pulled 3.3 MB; one that started from a snapshot took\nabout 0.2 s and read a 1.5 MB file — roughly an eighth of the time, and at the embedding seam, in\nmemory, roughly a quarter.\n\n### Changed\n- **No op can be deleted from the log any more, and what an op is cannot be changed.** The database\nnow refuses to delete an op — including through an `INSERT OR REPLACE` — and refuses to change an\nop's id, its `(lamport, site)` coordinate or its author. The log was append-only before because no\ncode deleted; now a path that tried would fail the first time it ran instead of passing every test.\nWhat stays writable on purpose: an op's target and value, which the prefix remap and the legacy\n`belongs_to` migration rewrite and still do. No public method of nxs deletes an op or changes its\nidentity, so nothing that goes through the API changes. **For embedders:** raw SQL through\n`Store::connection()` that deletes or replaces rows of `ops`, or updates `op_id`, `lamport`, `site`\nor `author`, now fails with an error naming the rule — including a test fixture that fabricates an\nolder log that way. `nxs doctor` also reports, in `--json` as `duplicate_coordinates` and as a\nwarning line, when two ops share one `(lamport, site)` coordinate: something only a log written\nbefore nxs could refuse it carries, and on which a concurrent edit is decided by arrival order\nrather than the CRDT order.\n\n### Fixed\n- **Security:** a relay URL carrying credentials (`https://:@relay.example`) no longer\nleaks them. A failed sync pass used to quote the key whole, and from there it reached `service.log`,\nthe background service's heartbeat and every reader of its attendance — the apps and agent sessions\nthat show what the service last did. Now the credential is taken off the URL before any request is\nbuilt and sent as the `Authorization: Basic` header the transport derived from it before (none for\nan empty userinfo; kept on a redirect to the same host, never to another), and every error is\nscrubbed of it in any form — as userinfo, as the header's base64, as the password on its own. Every\nmessage names the relay by host, port and path only. `nxs sync machines`, `nxs sync bind --json` and\n`nxs sync endpoint` show the endpoint masked, and a configuration file's parse error no longer quotes\nit. `.nxs/sync.toml`, `~/.nexusflow/config.toml` and a snapshot file are now written readable by\ntheir owner alone. What an existing `service.log` already holds is not rewritten: if a failed pass\nran against such a URL, rotate that key. Also fixed: an app reading the service's attendance for a\nworkspace now sees what the service last did with it — the reading never found the workspace\nbefore, because the service recorded it under its `.nxs` directory.\n\n### Facade Contract\n- `changed` · **A new replica starts from a snapshot instead of folding the whole history.** `nxs sync snapshot\n` writes a snapshot of a workspace that syncs a board — its op log, the board folded from it,\nand the relay position the two reach. On a new machine, `nxs sync bind --snapshot ` joins\nthat stream from it, and the first pass pulls only what came after. The promise is exact: a\nsnapshot plus the rest folds to the same board as the whole history, late edits written into the\npast and ops a relay stores twice included. It is refused by name where it could not keep that\npromise: the source still holds ops it has not pushed, the new workspace is not empty, or the\nsnapshot comes from an nxs whose database this one cannot read; one another nxs version wrote is\nfolded again from the log it carries. The position is checked against the relay on both ends: the\nsource refuses a snapshot when the relay does not hold, at its position, an op it has, and records\nthat op; before anything is written, the new machine requires the relay to hold the same op there\nand pulls from the start otherwise — slower, never short. A snapshot is taken as given, log and board\nalike, so treat the file as you treat the relay; one the import could not read back is refused whole.\nThe relay does not hold snapshots — where one lives is up to you. **For embedders:**\n`nxs_sync::snapshot::{export, import, peek}` is the same mechanism at the seam, for a host that\nstarts cold and keeps its snapshots itself (`export` takes the transport, to confirm the position; a\nreplica that will write registers its prefix before importing); `Store::image`/`load_image` are the substrate half, and\n`Reducer::view_tables` is now the one list of a reducer's views (`clear_views` empties exactly\nthose by default). Measured on a real board of 6,065 ops against a local relay: a new machine that\nfolds the whole history took about 1.35 s and pulled 3.3 MB; one that started from a snapshot took\nabout 0.2 s and read a 1.5 MB file — roughly an eighth of the time, and at the embedding seam, in\nmemory, roughly a quarter.\n- `breaking` · **No op can be deleted from the log any more, and what an op is cannot be changed.** The database\nnow refuses to delete an op — including through an `INSERT OR REPLACE` — and refuses to change an\nop's id, its `(lamport, site)` coordinate or its author. The log was append-only before because no\ncode deleted; now a path that tried would fail the first time it ran instead of passing every test.\nWhat stays writable on purpose: an op's target and value, which the prefix remap and the legacy\n`belongs_to` migration rewrite and still do. No public method of nxs deletes an op or changes its\nidentity, so nothing that goes through the API changes. **For embedders:** raw SQL through\n`Store::connection()` that deletes or replaces rows of `ops`, or updates `op_id`, `lamport`, `site`\nor `author`, now fails with an error naming the rule — including a test fixture that fabricates an\nolder log that way. `nxs doctor` also reports, in `--json` as `duplicate_coordinates` and as a\nwarning line, when two ops share one `(lamport, site)` coordinate: something only a log written\nbefore nxs could refuse it carries, and on which a concurrent edit is decided by arrival order\nrather than the CRDT order.", - "de": "### Neu\n- **Ein neues Replikat startet von einem Schnappschuss, statt die ganze Historie zu falten.**\n`nxs sync snapshot ` schreibt einen Schnappschuss eines Arbeitsbereichs, der ein Brett\nsynchronisiert — sein Op-Log, das daraus gefaltete Brett und die Relay-Position, bis zu der beide\nreichen. Auf einer neuen Maschine tritt `nxs sync bind --snapshot ` dem Stream damit bei, und\nder erste Durchlauf zieht nur noch, was danach kam. Die Zusage ist exakt: Ein Schnappschuss plus der\nRest faltet zum selben Brett wie die ganze Historie, auch mit Änderungen, die in die Vergangenheit\ngeschrieben wurden, und mit Ops, die ein Relay doppelt hält. Wo er das nicht halten könnte, wird er\nnamentlich abgewiesen: Die Quelle hält noch ungepushte Ops, der neue Arbeitsbereich ist nicht leer,\noder der Schnappschuss stammt von einem nxs, dessen Datenbank dieses nicht lesen kann; einer von\neiner anderen nxs-Version wird aus dem mitgelieferten Log neu gefaltet. Die Position wird an beiden Enden gegen den Relay geprüft: Die Quelle verweigert einen\nSchnappschuss, wenn der Relay an seiner Position keine Op hält, die sie hat, und hält diese Op fest;\nbevor etwas geschrieben wird, verlangt die neue Maschine, dass der Relay dort dieselbe Op hält, und\nzieht sonst von vorn — langsamer, aber nie lückenhaft. Ein Schnappschuss wird genommen, wie er ist,\nLog und Brett gleichermaßen; behandle die Datei also wie den Relay. Einer, den der Import nicht\nzurücklesen könnte, wird ganz abgewiesen. Der Relay hält keine Schnappschüsse — wo einer liegt,\nentscheidest du. **Für Einbetter:** `nxs_sync::snapshot::{export, import, peek}` ist derselbe\nMechanismus an der Naht, für einen Host, der kalt startet und seine Schnappschüsse selbst ablegt\n(`export` bekommt den Transport, um die Position zu bestätigen; ein Replikat, das schreiben wird,\nregistriert sein Präfix vor dem Import);\n`Store::image`/`load_image` sind die Hälfte im Substrat, und `Reducer::view_tables` ist jetzt die\neine Liste der Views eines Reducers (`clear_views` leert standardmäßig genau diese). Gemessen an einem echten Brett mit 6.065 Ops gegen einen lokalen Relay: Eine neue\nMaschine, die die ganze Historie faltete, brauchte etwa 1,35 s und zog 3,3 MB; eine, die vom\nSchnappschuss startete, etwa 0,2 s und las eine Datei von 1,5 MB — rund ein Achtel der Zeit, an der\nEinbettungs-Naht im Speicher rund ein Viertel.\n\n### Geändert\n- **Aus dem Log lässt sich keine Op mehr löschen, und was eine Op ist, lässt sich nicht mehr ändern.**\nDie Datenbank weist es ab, eine Op zu löschen — auch über ein `INSERT OR REPLACE` — und ebenso, die\nID einer Op, ihre `(lamport, site)`-Koordinate oder ihren Autor zu ändern. Bisher war das Log\nappend-only, weil kein Code löschte; jetzt scheitert ein Pfad, der es versucht, beim ersten Lauf,\nstatt jeden Test zu bestehen. Bewusst schreibbar bleiben Ziel und Wert einer Op, die der\nPräfix-Remap und die alte `belongs_to`-Migration umschreiben und weiter umschreiben. Keine\nöffentliche Methode von nxs löscht eine Op oder ändert ihre Identität; wer über die API geht, merkt\nalso nichts. **Für Einbetter:** Rohes SQL über `Store::connection()`, das Zeilen von `ops` löscht\noder ersetzt oder `op_id`, `lamport`, `site` oder `author` ändert, scheitert jetzt mit einer\nMeldung, die die Regel nennt — auch eine Test-Fixture, die so ein älteres Log nachbaut.\n`nxs doctor` meldet außerdem, in `--json` als `duplicate_coordinates` und als Warnzeile, wenn zwei\nOps dieselbe `(lamport, site)`-Koordinate tragen. Das kommt nur in einem Log vor, das geschrieben\nwurde, bevor nxs es abweisen konnte, und dort entscheidet bei einer gleichzeitigen Änderung die\nAnkunftsreihenfolge statt der CRDT-Ordnung.\n\n### Behoben\n- **Sicherheit:** Eine Relay-URL mit Zugangsdaten (`https://:@relay.example`)\nverrät sie nicht mehr. Ein fehlgeschlagener Sync-Durchlauf zitierte den Schlüssel bisher\nvollständig, und von dort gelangte er in `service.log`, in den Herzschlag des Hintergrunddienstes und\nzu jedem, der dessen Attendance liest — also zu den Apps und Agentensitzungen, die zeigen, was der\nDienst zuletzt getan hat. Jetzt wird die Zugangsangabe von der URL genommen, bevor eine Anfrage\nentsteht, und als der `Authorization: Basic`-Header gesendet, den der Transport vorher daraus gebildet\nhat (keiner bei leerer Userinfo; bei einer Umleitung auf denselben Host behalten, nie auf einen\nanderen). Jede Fehlermeldung wird davon bereinigt, in jeder Form — als Userinfo, als Base64 des\nHeaders, als Passwort allein. Jede Meldung nennt das Relay nur mit Host, Port und Pfad.\n`nxs sync machines`, `nxs sync bind --json` und `nxs sync endpoint` zeigen den Endpunkt maskiert, und\nein Parsefehler einer Konfigurationsdatei zitiert ihn nicht mehr. `.nxs/sync.toml`,\n`~/.nexusflow/config.toml` und eine Schnappschuss-Datei werden jetzt nur für ihren Besitzer lesbar\ngeschrieben. Was ein bestehendes `service.log` schon enthält, wird nicht umgeschrieben: Lief ein\nfehlgeschlagener Durchlauf gegen eine solche URL, rotiere diesen Schlüssel. Ebenfalls behoben: Eine\nApp, die die Attendance des Dienstes für einen Arbeitsbereich liest, sieht jetzt, was der Dienst\nzuletzt damit getan hat. Bisher fand die Abfrage den Arbeitsbereich nie, weil der Dienst ihn unter\nseinem `.nxs`-Verzeichnis vermerkte.\n\n### Facade-Kontrakt\n- `changed` · **Ein neues Replikat startet von einem Schnappschuss, statt die ganze Historie zu falten.**\n`nxs sync snapshot ` schreibt einen Schnappschuss eines Arbeitsbereichs, der ein Brett\nsynchronisiert — sein Op-Log, das daraus gefaltete Brett und die Relay-Position, bis zu der beide\nreichen. Auf einer neuen Maschine tritt `nxs sync bind --snapshot ` dem Stream damit bei, und\nder erste Durchlauf zieht nur noch, was danach kam. Die Zusage ist exakt: Ein Schnappschuss plus der\nRest faltet zum selben Brett wie die ganze Historie, auch mit Änderungen, die in die Vergangenheit\ngeschrieben wurden, und mit Ops, die ein Relay doppelt hält. Wo er das nicht halten könnte, wird er\nnamentlich abgewiesen: Die Quelle hält noch ungepushte Ops, der neue Arbeitsbereich ist nicht leer,\noder der Schnappschuss stammt von einem nxs, dessen Datenbank dieses nicht lesen kann; einer von\neiner anderen nxs-Version wird aus dem mitgelieferten Log neu gefaltet. Die Position wird an beiden Enden gegen den Relay geprüft: Die Quelle verweigert einen\nSchnappschuss, wenn der Relay an seiner Position keine Op hält, die sie hat, und hält diese Op fest;\nbevor etwas geschrieben wird, verlangt die neue Maschine, dass der Relay dort dieselbe Op hält, und\nzieht sonst von vorn — langsamer, aber nie lückenhaft. Ein Schnappschuss wird genommen, wie er ist,\nLog und Brett gleichermaßen; behandle die Datei also wie den Relay. Einer, den der Import nicht\nzurücklesen könnte, wird ganz abgewiesen. Der Relay hält keine Schnappschüsse — wo einer liegt,\nentscheidest du. **Für Einbetter:** `nxs_sync::snapshot::{export, import, peek}` ist derselbe\nMechanismus an der Naht, für einen Host, der kalt startet und seine Schnappschüsse selbst ablegt\n(`export` bekommt den Transport, um die Position zu bestätigen; ein Replikat, das schreiben wird,\nregistriert sein Präfix vor dem Import);\n`Store::image`/`load_image` sind die Hälfte im Substrat, und `Reducer::view_tables` ist jetzt die\neine Liste der Views eines Reducers (`clear_views` leert standardmäßig genau diese). Gemessen an einem echten Brett mit 6.065 Ops gegen einen lokalen Relay: Eine neue\nMaschine, die die ganze Historie faltete, brauchte etwa 1,35 s und zog 3,3 MB; eine, die vom\nSchnappschuss startete, etwa 0,2 s und las eine Datei von 1,5 MB — rund ein Achtel der Zeit, an der\nEinbettungs-Naht im Speicher rund ein Viertel.\n- `breaking` · **Aus dem Log lässt sich keine Op mehr löschen, und was eine Op ist, lässt sich nicht mehr ändern.**\nDie Datenbank weist es ab, eine Op zu löschen — auch über ein `INSERT OR REPLACE` — und ebenso, die\nID einer Op, ihre `(lamport, site)`-Koordinate oder ihren Autor zu ändern. Bisher war das Log\nappend-only, weil kein Code löschte; jetzt scheitert ein Pfad, der es versucht, beim ersten Lauf,\nstatt jeden Test zu bestehen. Bewusst schreibbar bleiben Ziel und Wert einer Op, die der\nPräfix-Remap und die alte `belongs_to`-Migration umschreiben und weiter umschreiben. Keine\nöffentliche Methode von nxs löscht eine Op oder ändert ihre Identität; wer über die API geht, merkt\nalso nichts. **Für Einbetter:** Rohes SQL über `Store::connection()`, das Zeilen von `ops` löscht\noder ersetzt oder `op_id`, `lamport`, `site` oder `author` ändert, scheitert jetzt mit einer\nMeldung, die die Regel nennt — auch eine Test-Fixture, die so ein älteres Log nachbaut.\n`nxs doctor` meldet außerdem, in `--json` als `duplicate_coordinates` und als Warnzeile, wenn zwei\nOps dieselbe `(lamport, site)`-Koordinate tragen. Das kommt nur in einem Log vor, das geschrieben\nwurde, bevor nxs es abweisen konnte, und dort entscheidet bei einer gleichzeitigen Änderung die\nAnkunftsreihenfolge statt der CRDT-Ordnung." - } - }, - { - "version": "0.100.0", - "date": "2026-09-21", - "items": [ - { - "type": "added", - "en": "**See which machines sync a workspace, and which of them are online.** Every machine now has a name — `nxs sync machine` shows it, `nxs sync machine \"Mac mini\"` renames it, and `nxs sync bind` says which name the relay will see before anything is announced — and its background service tells the relay it is here after every pass that synced. `nxs sync machines` lists the machines that sync the current workspace, when the relay last heard from each, and whether it is online: its last announcement is at most twice its service's cadence plus a minute old, 11 minutes by default. The cadence is the service's interval but never less than its 5-minute retry backoff, so one failed pass never shows a live machine as offline. Only the background service announces a machine, never a manual `nxs sync run`. Presence never enters the op log; the relay keeps one entry per machine and stream (a `machine_presence` table on SQLite and Postgres, created on boot; a `machine#` item in the existing registry table on DynamoDB, whose role now also needs `dynamodb:Query` on that table, with optional TTL on `expires_at`). A machine nobody heard from for 30 days is forgotten; one answer lists at most 100 machines and says when the relay holds more. A relay that predates this keeps syncing and simply reports no machines; a relay behind a gateway must also route `GET`/`POST /streams/{id}/machines`. The relay authenticates nobody: machine names are readable by anyone who can read the stream (rename a machine whose host name carries your own), and a listing is a claim — anyone who can reach the relay can keep a stopped machine looking online for up to about two hours. Embedding hosts read the same list with `nxs_sync::engine::machines` (judged by `nxs_sync::presence`) and this machine's identity through `nxs_service::ServiceHome::machine` and `rename_machine`; a `Transport` wrapper must forward the new `announce`/`machines` methods (their defaults report \"not forwarded\"), and a custom `PrefixRegistry` keeps compiling — the relay then answers the presence route like one that predates it.", - "de": "**Sehen, welche Maschinen einen Arbeitsbereich synchronisieren und welche davon online sind.** Jede Maschine hat jetzt einen Namen — `nxs sync machine` zeigt ihn, `nxs sync machine \"Mac mini\"` benennt sie um, und `nxs sync bind` sagt, welchen Namen der Relay sehen wird, bevor irgendetwas gemeldet wird —, und ihr Hintergrunddienst meldet dem Relay nach jedem Durchlauf, der synchronisiert hat, dass sie da ist. `nxs sync machines` listet die Maschinen, die den aktuellen Arbeitsbereich synchronisieren, wann der Relay zuletzt von jeder gehört hat und ob sie online ist: Ihre letzte Meldung ist höchstens doppelt so alt wie der Takt ihres Dienstes plus eine Minute, voreingestellt 11 Minuten. Der Takt ist das Intervall des Dienstes, aber nie kürzer als seine Wiederholungspause von 5 Minuten, damit ein gescheiterter Durchlauf eine laufende Maschine nie als offline zeigt. Nur der Hintergrunddienst meldet eine Maschine an, nie ein von Hand gestartetes `nxs sync run`. Präsenz gelangt nie ins Op-Log; der Relay hält einen Eintrag je Maschine und Stream (eine Tabelle `machine_presence` bei SQLite und Postgres, beim Start angelegt; ein Eintrag `machine#` in der vorhandenen Registry-Tabelle bei DynamoDB, deren Rolle jetzt zusätzlich `dynamodb:Query` auf dieser Tabelle braucht, mit optionaler TTL auf `expires_at`). Eine Maschine, von der 30 Tage niemand gehört hat, wird vergessen; eine Antwort nennt höchstens 100 Maschinen und sagt, wenn der Relay mehr hält. Ein älterer Relay synchronisiert weiter und meldet schlicht keine Maschinen; ein Relay hinter einem Gateway muss zusätzlich `GET`/`POST /streams/{id}/machines` durchleiten. Der Relay authentifiziert niemanden: Maschinennamen kann jeder lesen, der den Stream lesen kann (benenne eine Maschine um, deren Hostname deinen eigenen Namen trägt), und ein Eintrag ist eine Behauptung — wer den Relay erreicht, kann eine gestoppte Maschine bis zu etwa zwei Stunden online aussehen lassen. Einbettende Hosts lesen dieselbe Liste mit `nxs_sync::engine::machines` (beurteilt von `nxs_sync::presence`) und die Identität dieser Maschine über `nxs_service::ServiceHome::machine` und `rename_machine`; ein `Transport`-Wrapper muss die neuen Methoden `announce`/`machines` weiterreichen (ihre Voreinstellung meldet „nicht weitergereicht“), und eine eigene `PrefixRegistry` kompiliert weiter — der Relay beantwortet die Präsenz-Route dann wie ein älterer." - } - ], - "notes": { - "en": "### Added\n- **See which machines sync a workspace, and which of them are online.** Every machine now has a name — `nxs sync machine` shows it, `nxs sync machine \"Mac mini\"` renames it, and `nxs sync bind` says which name the relay will see before anything is announced — and its background service tells the relay it is here after every pass that synced. `nxs sync machines` lists the machines that sync the current workspace, when the relay last heard from each, and whether it is online: its last announcement is at most twice its service's cadence plus a minute old, 11 minutes by default. The cadence is the service's interval but never less than its 5-minute retry backoff, so one failed pass never shows a live machine as offline. Only the background service announces a machine, never a manual `nxs sync run`. Presence never enters the op log; the relay keeps one entry per machine and stream (a `machine_presence` table on SQLite and Postgres, created on boot; a `machine#` item in the existing registry table on DynamoDB, whose role now also needs `dynamodb:Query` on that table, with optional TTL on `expires_at`). A machine nobody heard from for 30 days is forgotten; one answer lists at most 100 machines and says when the relay holds more. A relay that predates this keeps syncing and simply reports no machines; a relay behind a gateway must also route `GET`/`POST /streams/{id}/machines`. The relay authenticates nobody: machine names are readable by anyone who can read the stream (rename a machine whose host name carries your own), and a listing is a claim — anyone who can reach the relay can keep a stopped machine looking online for up to about two hours. Embedding hosts read the same list with `nxs_sync::engine::machines` (judged by `nxs_sync::presence`) and this machine's identity through `nxs_service::ServiceHome::machine` and `rename_machine`; a `Transport` wrapper must forward the new `announce`/`machines` methods (their defaults report \"not forwarded\"), and a custom `PrefixRegistry` keeps compiling — the relay then answers the presence route like one that predates it.", - "de": "### Neu\n- **Sehen, welche Maschinen einen Arbeitsbereich synchronisieren und welche davon online sind.** Jede Maschine hat jetzt einen Namen — `nxs sync machine` zeigt ihn, `nxs sync machine \"Mac mini\"` benennt sie um, und `nxs sync bind` sagt, welchen Namen der Relay sehen wird, bevor irgendetwas gemeldet wird —, und ihr Hintergrunddienst meldet dem Relay nach jedem Durchlauf, der synchronisiert hat, dass sie da ist. `nxs sync machines` listet die Maschinen, die den aktuellen Arbeitsbereich synchronisieren, wann der Relay zuletzt von jeder gehört hat und ob sie online ist: Ihre letzte Meldung ist höchstens doppelt so alt wie der Takt ihres Dienstes plus eine Minute, voreingestellt 11 Minuten. Der Takt ist das Intervall des Dienstes, aber nie kürzer als seine Wiederholungspause von 5 Minuten, damit ein gescheiterter Durchlauf eine laufende Maschine nie als offline zeigt. Nur der Hintergrunddienst meldet eine Maschine an, nie ein von Hand gestartetes `nxs sync run`. Präsenz gelangt nie ins Op-Log; der Relay hält einen Eintrag je Maschine und Stream (eine Tabelle `machine_presence` bei SQLite und Postgres, beim Start angelegt; ein Eintrag `machine#` in der vorhandenen Registry-Tabelle bei DynamoDB, deren Rolle jetzt zusätzlich `dynamodb:Query` auf dieser Tabelle braucht, mit optionaler TTL auf `expires_at`). Eine Maschine, von der 30 Tage niemand gehört hat, wird vergessen; eine Antwort nennt höchstens 100 Maschinen und sagt, wenn der Relay mehr hält. Ein älterer Relay synchronisiert weiter und meldet schlicht keine Maschinen; ein Relay hinter einem Gateway muss zusätzlich `GET`/`POST /streams/{id}/machines` durchleiten. Der Relay authentifiziert niemanden: Maschinennamen kann jeder lesen, der den Stream lesen kann (benenne eine Maschine um, deren Hostname deinen eigenen Namen trägt), und ein Eintrag ist eine Behauptung — wer den Relay erreicht, kann eine gestoppte Maschine bis zu etwa zwei Stunden online aussehen lassen. Einbettende Hosts lesen dieselbe Liste mit `nxs_sync::engine::machines` (beurteilt von `nxs_sync::presence`) und die Identität dieser Maschine über `nxs_service::ServiceHome::machine` und `rename_machine`; ein `Transport`-Wrapper muss die neuen Methoden `announce`/`machines` weiterreichen (ihre Voreinstellung meldet „nicht weitergereicht“), und eine eigene `PrefixRegistry` kompiliert weiter — der Relay beantwortet die Präsenz-Route dann wie ein älterer." - } - }, - { - "version": "0.99.0", - "date": "2026-09-21", - "items": [ - { - "type": "fixed", - "en": "**A commission no longer waits out two hours behind an operation that is already dead.** The\nworking-tree lease has a two-hour backstop, and until now that backstop was the only thing watching:\nif the chain holding the checkout died hard — crashed, killed, machine asleep — nothing released it,\nnothing looked, and whoever was queued behind it stood still until the lease expired. Now the\nbackground tick asks, once a minute while anybody is waiting, whether the holder is *provably* gone,\nand takes the checkout as soon as it is: the dead operation's work is committed to a branch\n(untracked files included; anything your `.gitignore` excludes stays in the tree), the tree goes back\nto the branch that operation started on, and the waiting commission starts — typically within a\nminute of the death instead of up to two hours later (nxf 6j6v.xb24).\n\n**\"Provably\" is five conditions, and all five have to hold**, because taking a checkout away writes\ninto it. The chat worker must actually be able to answer the process question — a runtime that cannot\nlook never takes a checkout early, so nothing changes for a host that does not implement the liveness\nprobe. At least one thread of that operation must still owe an answer; an operation whose sessions\nall ended normally and handed the task back is not dead but waiting for a human, and it keeps the\nthirty-minute contention rule it already had. Nothing in the operation may have a live process. No\nowing thread's session may have reported its end — a reported end is a teardown that ran, which is\nnot a hard death. And nothing in the operation may be on hold at an availability boundary, which has\nits own path and its own way back. What that check costs was measured before it was put in every\ntick, and measured end to end: about **two milliseconds** for an operation of twenty threads with\ntwenty sessions, on a development machine, in the slower of the two builds this project makes.\n\nEverything else about this hand-off is the behaviour the other occasions already had: `nxc tick`\nreports where the work went, `nxc status` shows the parked branch on the operation until it is\nresumed, and the refusal rule applies unchanged — a merge, rebase, cherry-pick, revert or bisect in\nprogress, or a git command that failed, **keeps the checkout** with the operation marked\n`PARK REFUSED` and the park retried every tick, while a refusal that can never pass (no working copy,\nnot a git repository, no base branch ever recorded) hands the checkout on unparked with the named\n`work_handed_on_unparked` warning.\n\n**One timing change you may notice.** While a commission is queued behind a claim, the tick that\nre-checks it is now scheduled a minute out rather than at the holder's own bound — that re-check is\nwhat makes the above arrive in a minute instead of at the bound. A bound that falls sooner than a\nminute still wins; nothing is ever pushed later.\n\n**Facade contract.** `ParkOccasion` gains the variant `DiedInsideItsBound`\n(`died_inside_its_bound` in `--json` and on `StatusOperation::park_refused.occasion`), and\n`TickReceipt::handed_on` can now carry it; the enum is `#[non_exhaustive]`, so an exhaustive `match`\noutside the crate already had a wildcard. `ChatStore` gains\n`reclaim_a_dead_holders_working_tree_and_take_next`, the compare-and-swap twin of\n`reclaim_expired_working_tree_and_take_next` for a lease that is still inside its bound. Nothing was\nremoved and no signature changed.", - "de": "**Eine Beauftragung wartet nicht mehr zwei Stunden hinter einem Vorgang, der längst tot ist.** Die\nArbeitskopie-Lease hat eine Rückfallgrenze von zwei Stunden, und bisher war diese Grenze das Einzige,\nwas hinsah: Starb die haltende Kette hart — Absturz, Kill, schlafender Rechner —, gab niemand die\nKopie frei, niemand sah nach, und wer dahinter anstand, stand still, bis die Lease ablief. Jetzt\nfragt der Hintergrund-Tick einmal pro Minute, solange jemand wartet, ob der Halter *nachweislich*\nfort ist, und nimmt die Arbeitskopie, sobald er es ist: Die Arbeit des toten Vorgangs wird auf einen\nZweig committet (samt nicht versionierter Dateien; was `.gitignore` ausschließt, bleibt im Baum), der\nBaum kehrt auf den Zweig zurück, auf dem der Vorgang begonnen hat, und die wartende Beauftragung\nstartet — in der Regel innerhalb einer Minute nach dem Tod statt bis zu zwei Stunden später\n(nxf 6j6v.xb24).\n\n**„Nachweislich\" sind fünf Bedingungen, und alle fünf müssen gelten**, denn eine Arbeitskopie zu\nübernehmen schreibt in sie hinein. Der Chat-Worker muss die Prozessfrage tatsächlich beantworten\nkönnen — eine Laufzeitumgebung, die nicht nachsehen kann, nimmt nie vorzeitig eine Arbeitskopie, für\neinen Host ohne Liveness-Prüfung ändert sich also nichts. Mindestens ein Thread des Vorgangs muss\nnoch eine Antwort schulden; ein Vorgang, dessen Sitzungen alle normal endeten und die Aufgabe\nzurückgegeben haben, ist nicht tot, sondern wartet auf einen Menschen, und für ihn gilt weiterhin die\nDreißig-Minuten-Regel. Kein Prozess des Vorgangs darf noch leben. Keine Sitzung eines schuldenden\nThreads darf ihr Ende gemeldet haben — ein gemeldetes Ende ist ein gelaufener Abbau und damit kein\nharter Tod. Und nichts im Vorgang darf an einer Verfügbarkeitsgrenze pausieren, die ihren eigenen Weg\nzurück hat. Was diese Prüfung kostet, wurde gemessen, bevor sie in jeden Tick kam — und zwar\nvollständig, von Anfang bis Ende: rund **zwei Millisekunden** für einen Vorgang aus zwanzig Threads\nmit zwanzig Sitzungen, auf einem Entwicklungsrechner, im langsameren der beiden Builds dieses\nProjekts.\n\nAlles Übrige an dieser Übergabe ist das Verhalten, das die anderen Anlässe schon hatten: `nxc tick`\nmeldet, wohin die Arbeit ging, `nxc status` zeigt den geparkten Zweig am Vorgang, bis er wieder\naufgenommen wird, und die Ablehnungsregel gilt unverändert — ein laufender Merge, Rebase,\nCherry-Pick, Revert oder Bisect oder ein fehlgeschlagener git-Befehl **behält die Arbeitskopie**, der\nVorgang wird mit `PARK REFUSED` markiert und das Parken bei jedem Tick erneut versucht, während eine\nAblehnung, die nie vorübergehen kann (keine Arbeitskopie, kein git-Repository, nie ein Basiszweig\nvermerkt), die Arbeitskopie ungeparkt weitergibt und das mit der benannten Warnung\n`work_handed_on_unparked` sagt.\n\n**Eine Änderung am Zeitverhalten, die auffallen kann.** Solange eine Beauftragung hinter einem\nAnspruch wartet, wird der Tick, der erneut nachsieht, jetzt eine Minute im Voraus geplant statt zur\neigenen Frist des Halters — genau dieses erneute Nachsehen lässt das Obige in einer Minute statt erst\nzur Frist eintreten. Eine Frist, die früher als eine Minute fällt, gewinnt weiterhin; nichts wird je\nnach hinten verschoben.\n\n**Facade-Kontrakt.** `ParkOccasion` bekommt die Variante `DiedInsideItsBound`\n(`died_inside_its_bound` in `--json` und an `StatusOperation::park_refused.occasion`), und\n`TickReceipt::handed_on` kann sie jetzt tragen; das Enum ist `#[non_exhaustive]`, ein erschöpfendes\n`match` außerhalb des Crates hatte also schon einen Auffangzweig. `ChatStore` bekommt\n`reclaim_a_dead_holders_working_tree_and_take_next`, den Compare-and-Swap-Zwilling von\n`reclaim_expired_working_tree_and_take_next` für eine Lease, die noch innerhalb ihrer Frist steht.\nNichts wurde entfernt, keine Signatur geändert.", - "facade": "changed" - }, - { - "type": "changed", - "en": "**A refused park now follows one rule: hold and retry, or hand on unparked.** When a stranded\nescalation or an operation on hold at an availability boundary has to give up the working copy, its\nwork is parked first — and a park can be refused. Until now every refusal kept the checkout where it\nwas and named `nxc release` as the way out. Owner decision of 2026-09-17 (nxf 6j6v.8bv9,\n6j6v.b9nf): a refusal that can pass (a merge, rebase, cherry-pick, revert or bisect in progress, or a\ngit command that failed) keeps the checkout, is reported as `work_not_parked` with what to finish,\nshows on `nxc status` as `PARK REFUSED` with a line `park refused (): — retrying\nsince `, and is retried by the background service on every tick — and only for as long as\nthe trouble that wanted the checkout lasts: once the escalation is answered, the operation on hold is\ntaken up, or nobody is waiting any more, the mark goes on the next tick, because nothing is retrying\nthat park then. A refusal that cannot pass (the runtime names no working copy, the directory is not a\ngit repository, no base branch was ever recorded) hands the checkout on WITHOUT parking and says so\nas the new `work_handed_on_unparked` warning — the operation's uncommitted files stay in the tree for\nthe next holder. An operation on hold at an availability boundary is now also secured by the tick\nwhile somebody waits, not only by `nxc resume`. No refusal text names `nxc release` any more. And an\noperation with unresumed parked work or a refused park now stays on the default `nxc status` listing\neven when nothing else about it is open.\n\n**Facade contract.** `ParkRefusal` gains the variant `NoBaseRecorded` (the refusal that used to be\nan inline sentence) plus `is_permanent()` and `kind()`; the enum is not `#[non_exhaustive]`, so an\nexhaustive `match` outside the crate has to name the new arm. `TickReceipt` gains a public\n`handed_on: Option` field (with the new `ParkOccasion`), so a struct literal outside the\ncrate has to name it. `ConsequenceClass` gains `WorkHandedOnUnparked` (already `#[non_exhaustive]`).\n`StatusOperation` (from `Engine::status`) gains `park_refused: Option` (already\n`#[non_exhaustive]`), and `ChatStore` gains `note_park_refusal`, `clear_park_refusal`,\n`park_refusal` and `all_park_refusals` over a new device-local table; `ParkRefusalNote` names the\n`occasion` that refused the park (`stranded_escalation`, `availability_boundary`), in `--json` too.\nThe behavioural half no signature carries: `tick` and `Engine::resume_interrupted` now hand the\nworking copy on for a host whose worker names no working copy (the default of every worker but the\nsidecar) where they used to leave it held.", - "de": "**Ein abgelehntes Parken folgt jetzt einer Regel: halten und wiederholen, oder ungeparkt\nweitergeben.** Muss eine unbeantwortete Eskalation oder ein an einer Verfügbarkeitsgrenze\nangehaltener Vorgang die Arbeitskopie abgeben, wird seine Arbeit zuerst geparkt — und das Parken\nkann abgelehnt werden. Bisher hielt jede Ablehnung die Arbeitskopie fest und nannte `nxc release` als\nAusweg. Entscheidung des Owners vom 17.09.2026 (nxf 6j6v.8bv9, 6j6v.b9nf): Eine Ablehnung, die\nvorübergehen kann (ein laufender Merge, Rebase, Cherry-Pick, Revert oder Bisect, oder ein\nfehlgeschlagener git-Befehl), behält die Arbeitskopie, wird als `work_not_parked` mit dem, was zu\nbeenden ist, gemeldet, erscheint in `nxc status` als `PARK REFUSED` mit einer Zeile\n`park refused (): — retrying since ` und wird vom Hintergrunddienst\nbei jedem Tick erneut versucht — und nur so lange, wie der Anlass dauert, der die Arbeitskopie\nwollte: Ist die Eskalation beantwortet, der angehaltene Vorgang aufgenommen oder wartet niemand mehr,\nverschwindet die Markierung beim nächsten Tick, denn dann versucht niemand dieses Parken. Eine\nAblehnung, die nicht vorübergehen kann (die Laufzeit nennt keine Arbeitskopie, das Verzeichnis ist\nkein git-Repository, es wurde nie ein Basiszweig vermerkt), gibt die Arbeitskopie UNGEPARKT weiter\nund sagt das mit der neuen Warnung `work_handed_on_unparked` — die nicht committeten Dateien des\nVorgangs bleiben für den nächsten im Baum. Ein an einer Verfügbarkeitsgrenze angehaltener Vorgang\nwird jetzt auch vom Tick gesichert, solange jemand wartet, nicht nur von `nxc resume`. Kein\nAblehnungstext nennt mehr `nxc release`. Und ein Vorgang mit nicht zurückgeholter geparkter Arbeit\noder einem abgelehnten Parken bleibt jetzt in der Standardliste von `nxc status`, auch wenn sonst\nnichts an ihm offen ist.\n\n**Facade-Kontrakt.** `ParkRefusal` bekommt die Variante `NoBaseRecorded` (die Ablehnung, die bisher\nein eingebetteter Satz war) sowie `is_permanent()` und `kind()`; das Enum ist nicht\n`#[non_exhaustive]`, ein erschöpfendes `match` außerhalb des Crates muss den neuen Zweig also nennen.\n`TickReceipt` bekommt das öffentliche Feld `handed_on: Option` (mit dem neuen\n`ParkOccasion`), ein Struct-Literal außerhalb des Crates muss es also nennen. `ConsequenceClass`\nbekommt `WorkHandedOnUnparked` (bereits `#[non_exhaustive]`). `StatusOperation` (aus\n`Engine::status`) bekommt `park_refused: Option` (bereits `#[non_exhaustive]`),\nund `ChatStore` bekommt `note_park_refusal`, `clear_park_refusal`, `park_refusal` und\n`all_park_refusals` über eine neue gerätelokale Tabelle; `ParkRefusalNote` nennt den Anlass\n(`occasion`), der das Parken abgelehnt hat (`stranded_escalation`, `availability_boundary`), auch in\n`--json`. Die verhaltensseitige Hälfte trägt keine Signatur: `tick` und `Engine::resume_interrupted`\ngeben die Arbeitskopie für einen Host, dessen Worker keine Arbeitskopie nennt (die Vorgabe jedes\nWorkers außer dem Sidecar), jetzt weiter, wo sie sie bisher festhielten.", - "facade": "breaking" - }, - { - "type": "fixed", - "en": "**A tick no longer consolidates a withdrawn round and wakes the agent that commissioned it.** When\nevery commission of a channel round was taken back, the channel thread was discharged as withdrawn —\nand the channel's own clock could still fire on it later, read a settled set, and deliver it: for a\nround a person had commissioned that was a no-op, but for one an agent had commissioned (a `pm`\nthat sent work to a channel) it woke that agent with the taken-back round as its answer, and the\nwork went on. `nxc tick` now treats a thread discharged by a withdrawal as already handled, exactly\nlike one that was consolidated (`reason: already_handled`), and nothing is delivered (nxf\n6j6v.s2cj).", - "de": "**Ein Tick führt eine zurückgenommene Runde nicht mehr zusammen und weckt nicht mehr den Agenten,\nder sie beauftragt hat.** Wurde jede Beauftragung einer Kanalrunde zurückgenommen, wurde der\nKanalfaden als zurückgenommen entlastet — und die eigene Uhr des Kanals konnte später trotzdem auf\nihm feuern, einen abgeschlossenen Satz lesen und ihn ausliefern: Bei einer Runde, die ein Mensch\nbeauftragt hatte, lief das ins Leere, bei einer, die ein Agent beauftragt hatte (eine `pm`, die\nArbeit an einen Kanal geschickt hat), weckte es diesen Agenten mit der zurückgenommenen Runde als\nAntwort, und die Arbeit ging weiter. `nxc tick` behandelt einen durch eine Rücknahme entlasteten\nFaden jetzt als bereits erledigt, genau wie einen zusammengeführten (`reason: already_handled`), und\nnichts wird ausgeliefert (nxf 6j6v.s2cj).", - "facade": "changed" - }, - { - "type": "added", - "en": "**A chat worker can now stop a session it started.** Until now the engine could only ask a worker\nquestions about a session — is it running, where does it run, can it be resumed — and the one thing\nthat ended a session was the session itself. That is the piece a withdrawal of a *running* round\nneeds and did not have: taking the work back while its session goes on writing into the checkout\nleaves the park stale before its receipt is printed. The shipped sidecar worker answers the new\nquestion with yes and stops a session by sending its process `SIGTERM` — never `SIGKILL`, never the\nwhole process group — to the pid recorded in the same `.nxs/agent-logs/.pid` file the\nliveness check reads, so the process signalled is by construction the one reported as running. A\nmissing pid file, a file that holds no pid, or a claim that cannot prove which process it names is\nrefused by name rather than signalled into the dark; a session whose process is already gone is not\na failure at all but the state the stop was asked for, reported as such. The request is *delivered*,\nnot waited out: whoever stops a session watches the liveness check turn false before touching the\ntree (nxf 6j6v.b9nf).\n\n**A session claim now records WHICH process holds it, not only its number.** Claims are deliberately\nnever removed, so a session that crashed — or a machine that rebooted — leaves a file naming a pid\nthe operating system is free to hand to something else: an editor, a dev server, another `nxc` of\nthe same user. `kill(pid, 0)` says *yes* about that stranger, which was tolerable while the answer\nonly delayed a wake and is not tolerable now that taking a round back sends the process a signal. So\n`.nxs/agent-logs/.pid` holds the pid on its first line and the instant that process started\non its second, and every reader compares that instant against what the operating system reports for\nthe pid it finds — the same identity check the background service already makes for its own\nheartbeat, shared rather than copied. A claim whose instant disagrees names a session that **ended**:\nit reads as not running, and nothing is signalled. A claim written before this existed records no\ninstant, cannot be told apart from a recycled pid, and is therefore refused by name instead of\nsignalled — the liveness reads still believe it, because being wrong there costs a delayed turn\nwhile being wrong about a signal costs a stranger's process.\n\n**And the liveness checks now read a pid file the same way the stop does.** A `.pid` file holding\n`0` — or any number the operating system would read as a process *group* rather than a process —\nreads as *no live process* everywhere: for one session, and for the whole-workspace list the\nbackground service polls. Both used to parse a bare number and ask `kill(pid, 0)` about it, which\nfor `0` is a question about the asking process's own group and answers *yes* whenever anything of\nthis workspace is alive — a session reported as running that nothing could ever end.\n\n**The sidecar tears down on `SIGTERM` instead of dying.** It aborts the running turn through the\nSDK's own abort door, then runs its teardown in the usual order — binds the runtime session so a\nlater resume can find the conversation, flushes everything the aborted turn produced into the\ntranscript, and announces `nxc session ended`. Exactly one step is skipped on the signal: the\nsession is **not reminded** to answer, because a reminder tells a session it broke the answering\nrule and this one was told to stop, and it would spend a model call doing so.\n\n**Whether anything is posted in the session's name is decided by the BOARD, not by the signal.** A\n`SIGTERM` is not only a withdrawal: it is a system shutdown, a logout, a `docker stop`, a person's\n`kill `. A withdrawal discharges the thread before it signals, so the teardown reads a thread\nthat owes nothing and says nothing — which is right, because a substitute reply there would speak\nas the agent into a round that has just been taken back. Every other `SIGTERM` leaves the thread\nstill owing an answer, and the teardown posts the usual `nxc reply --thread --if-unanswered`\nfor it; the engine's own gate still decides whether that write lands. Without this the handler\nturned every outside `SIGTERM` from a hard death — which the dead-holder sweep frees in about a\nminute — into a tidy silent end, because the teardown now announces the session's end: the debt\nunsettled, the waiting caller unwoken, and the working copy held until the two-hour bound.\n\nA stop is neither a finished turn nor a failed one, so the log ends in `sidecar stopped:` rather\nthan `sidecar done:`, and the process exits with **143** (128 + SIGTERM, the number a shell reports\nfor a process that died of the signal, kept for one that caught it). A `SIGTERM` that arrives during\nthe reply-reminder round ends that round the same way.\n\n**Facade contract.** `Worker` gains two defaulted methods: `stops_sessions(&self) -> bool`\n(`false` by default — a host that cannot stop a session is refused by name rather than believed)\nand `stop_session(&self, internal_session: &str) -> Result` (a named refusal by\ndefault). An existing implementation keeps exactly the behaviour it had; a host whose runtime can\nstop its own sessions overrides both. `WorkerConfig::Custom`'s wrapper forwards both. The new\n`SessionStop` enum (`#[non_exhaustive]`) is what a delivered stop and an already-finished session\nare told apart by: `Requested` is the signal sent, `NothingToStop(String)` is the goal state with the\nsentence that found it — a caller puts that on its receipt rather than in a warning. `worker` also\ngains `session_claim_for(pid) -> String`, the one spelling of a claim file's contents, so anything\nstanding a process in for a session writes the shape the readers expect. Nothing was removed.", - "de": "**Ein Chat-Worker kann jetzt eine Sitzung beenden, die er gestartet hat.** Bisher konnte die Engine\neinen Worker nur zu einer Sitzung befragen — läuft sie, wo läuft sie, lässt sie sich fortsetzen —,\nund das Einzige, was eine Sitzung beendete, war die Sitzung selbst. Genau dieses Stück fehlte dem\nZurückziehen einer *laufenden* Runde: Wer die Arbeit zurücknimmt, während ihre Sitzung weiter in die\nArbeitskopie schreibt, hat das Geparkte schon überholt, bevor die Quittung gedruckt ist. Der\nmitgelieferte Sidecar-Worker beantwortet die neue Frage mit Ja und beendet eine Sitzung, indem er\nihrem Prozess `SIGTERM` schickt — nie `SIGKILL`, nie die ganze Prozessgruppe —, und zwar an die\nPID aus derselben Datei `.nxs/agent-logs/.pid`, die auch die Lebendigkeitsprüfung liest;\nder signalisierte Prozess ist damit konstruktionsbedingt derjenige, der als laufend gemeldet wird.\nEine fehlende PID-Datei, eine Datei ohne PID oder ein Anspruch, der nicht belegen kann, welchen\nProzess er meint, wird benannt abgelehnt, statt ins Leere zu signalisieren; eine Sitzung, deren\nProzess bereits weg ist, ist gar kein Fehlschlag, sondern genau der Zustand, um den es beim Stoppen\nging — und wird auch so gemeldet. Die Anforderung wird *zugestellt*, nicht abgewartet: Wer eine\nSitzung beendet, wartet, bis die Lebendigkeitsprüfung auf „nicht laufend\" wechselt, bevor er den\nBaum anfasst (nxf 6j6v.b9nf).\n\n**Ein Sitzungsanspruch hält jetzt fest, WELCHER Prozess ihn hält, nicht nur dessen Nummer.**\nAnsprüche werden bewusst nie entfernt; eine abgestürzte Sitzung — oder ein Neustart der Maschine —\nhinterlässt also eine Datei mit einer PID, die das Betriebssystem frei an etwas anderes vergeben\ndarf: einen Editor, einen Entwicklungsserver, ein weiteres `nxc` desselben Benutzers. `kill(pid, 0)`\nsagt über diesen Fremden *ja*, was hinnehmbar war, solange die Antwort nur eine Weckung verzögerte,\nund nicht mehr hinnehmbar ist, seit das Zurücknehmen einer Runde dem Prozess ein Signal schickt.\nDeshalb enthält `.nxs/agent-logs/.pid` in der ersten Zeile die PID und in der zweiten den\nZeitpunkt, zu dem dieser Prozess gestartet ist, und jeder Leser vergleicht diesen Zeitpunkt mit dem,\nwas das Betriebssystem zur gefundenen PID sagt — dieselbe Identitätsprüfung, die der\nHintergrunddienst für seinen eigenen Herzschlag längst macht, geteilt statt kopiert. Ein Anspruch,\ndessen Zeitpunkt nicht passt, benennt eine Sitzung, die **beendet** ist: Er gilt als nicht laufend,\nund es wird nichts signalisiert. Ein Anspruch aus der Zeit davor hält keinen Zeitpunkt fest, ist von\neiner wiederverwendeten PID nicht zu unterscheiden und wird deshalb benannt abgelehnt statt\nsignalisiert — die Lebendigkeitsprüfungen glauben ihm weiterhin, denn dort kostet ein Irrtum einen\nverzögerten Zug, bei einem Signal kostet er den Prozess eines Fremden.\n\n**Und die Lebendigkeitsprüfungen lesen eine PID-Datei jetzt so wie der Stopp.** Eine `.pid`-Datei\nmit `0` — oder mit einer Zahl, die das Betriebssystem als Prozess*gruppe* statt als Prozess liest —\ngilt überall als *kein laufender Prozess*: für die einzelne Sitzung wie für die Liste aller\nSitzungen, die der Hintergrunddienst abfragt. Beide lasen bisher eine bloße Zahl und fragten\n`kill(pid, 0)` danach, was bei `0` eine Frage nach der eigenen Prozessgruppe des Fragenden ist und\nmit *ja* antwortet, solange irgendetwas von diesem Arbeitsbereich lebt — eine als laufend gemeldete\nSitzung, die niemand jemals beenden konnte.\n\n**Der Sidecar baut bei `SIGTERM` geordnet ab, statt zu sterben.** Er bricht den laufenden Zug über\nden Abbruchmechanismus des SDK ab und führt dann seinen Abbau in der gewohnten Reihenfolge aus —\nbindet die Laufzeitsitzung, damit ein späteres Fortsetzen das Gespräch findet, schreibt alles, was\nder abgebrochene Zug erzeugt hat, ins Transkript und meldet `nxc session ended`. Genau ein Schritt\nentfällt auf das Signal hin: Die Sitzung wird **nicht ans Antworten erinnert** — eine Erinnerung\nsagt einer Sitzung, dass sie die Antwortregel gebrochen hat, und dieser wurde gesagt, sie solle\naufhören; der Modellaufruf dafür wäre verschwendet.\n\n**Ob in ihrem Namen etwas gepostet wird, entscheidet das REGISTER, nicht das Signal.** Ein `SIGTERM`\nist nicht nur eine Rücknahme: Er ist auch ein Systemabschalten, ein Logout, ein `docker stop`, ein\n`kill ` von Hand. Eine Rücknahme entlastet den Faden, bevor sie signalisiert — der Abbau liest\nalso einen Faden, der nichts mehr schuldet, und sagt nichts, was richtig ist: Eine Ersatzantwort\nspräche dort als der Agent in eine gerade zurückgenommene Runde hinein. Jeder andere `SIGTERM`\nlässt den Faden weiter schuldend zurück, und der Abbau postet das übliche\n`nxc reply --thread --if-unanswered` für ihn; ob dieser Schreibvorgang landet, entscheidet\nweiterhin das Tor der Engine. Ohne das machte der Handler aus jedem fremden `SIGTERM` — bisher ein\nharter Tod, den die Kehrschleife in etwa einer Minute auflöst — ein ordentliches, stummes Ende, weil\nder Abbau jetzt das Sitzungsende meldet: die Schuld ungeklärt, der wartende Aufrufer ungeweckt und\ndie Arbeitskopie bis zur Zwei-Stunden-Grenze gehalten.\n\nEin Stopp ist weder ein beendeter noch ein gescheiterter Zug, deshalb endet das Log mit\n`sidecar stopped:` statt `sidecar done:`, und der Prozess beendet sich mit **143** (128 + SIGTERM,\ndie Zahl, die eine Shell für einen am Signal gestorbenen Prozess meldet — beibehalten für einen, der\nes abgefangen hat). Ein `SIGTERM` während der Erinnerungsrunde beendet diese Runde auf dieselbe\nWeise.\n\n**Facade-Kontrakt.** `Worker` bekommt zwei Methoden mit Vorgabe: `stops_sessions(&self) -> bool`\n(vorgegeben `false` — ein Host, der keine Sitzung beenden kann, wird benannt abgelehnt statt\ngeglaubt) und `stop_session(&self, internal_session: &str) -> Result`\n(vorgegeben eine benannte Ablehnung). Eine bestehende Implementierung behält genau das Verhalten,\ndas sie hatte; ein Host, dessen Laufzeit eigene Sitzungen beenden kann, überschreibt beide. Der\nWrapper von `WorkerConfig::Custom` leitet beide weiter. Das neue Enum `SessionStop`\n(`#[non_exhaustive]`) unterscheidet einen zugestellten Stopp von einer bereits beendeten Sitzung:\n`Requested` ist das gesendete Signal, `NothingToStop(String)` der gewünschte Zustand samt dem Satz,\nder ihn gefunden hat — ein Aufrufer schreibt den auf seine Quittung, nicht in eine Warnung. `worker`\nbekommt außerdem `session_claim_for(pid) -> String`, die eine Schreibweise des Dateiinhalts eines\nAnspruchs, damit alles, was einen Prozess für eine Sitzung einsetzt, die Form schreibt, die die\nLeser erwarten. Nichts wurde entfernt.", - "facade": "changed" - }, - { - "type": "changed", - "en": "**`nxs` describes itself the way the README does.** `nxs --help` now reads \"nexus-flow — the board\n(nxf), the memory (nxm) and the channel (nxc) your agents work from, in one binary\" instead of \"nxs\nplatform umbrella CLI\", and the `nxs init` welcome box opens with the README's tagline. The welcome\nbox no longer says nexus-chat is \"coming soon\" — chat is offered in the chooser right below it.\nChat's line in the chooser, in the init summary and in the `advertisement` of `nxf init --json` now\nreads \"the channel — messages between you and your agents, on the record\"; the guides call chat the\nchannel too, and neither they nor `nxs sync --help` call `nxs` a platform any more.", - "de": "**`nxs` beschreibt sich so, wie es der README tut.** `nxs --help` nennt jetzt „nexus-flow — the\nboard (nxf), the memory (nxm) and the channel (nxc) your agents work from, in one binary\" statt „nxs\nplatform umbrella CLI\", und die Willkommensbox von `nxs init` beginnt mit der Tagline des README.\nDort steht auch nicht mehr, nexus-chat komme „bald\" — chat wird im Auswahlmenü direkt darunter\nangeboten. Die Zeile zu chat im Auswahlmenü, in der Init-Zusammenfassung und im `advertisement` von\n`nxf init --json` lautet jetzt „the channel — messages between you and your agents, on the record\";\nauch die Anleitungen nennen chat den Kanal, und weder sie noch `nxs sync --help` nennen `nxs` noch\neine Plattform." - }, - { - "type": "changed", - "en": "**A park on a clean tree no longer creates a branch.** Owner decision of 2026-09-17 (nxf\n6j6v.7hqc): a park creates a branch only when there is work on the operation's base branch, because\nthe engine never deletes park branches — an empty one would sit there forever as clutter nobody\nremoves. When HEAD is on the operation's recorded base branch and the tree is clean, the park now\nreports the operation parked on the base branch itself, with `created_branch: false` and\n`committed: false`, instead of minting `nxs/park/...`. A dirty tree on the base branch, an\noperation already on a branch of its own, and a detached HEAD (clean or not — its position has to\nstay reachable) are all unchanged.", - "de": "**Ein Park auf einem sauberen Arbeitsverzeichnis legt keinen Zweig mehr an.** Entscheidung des\nOwners vom 17.09.2026 (nxf 6j6v.7hqc): Ein Park legt nur dann einen Zweig an, wenn es Arbeit auf\ndem Basiszweig des Vorgangs gibt, denn die Engine löscht Park-Zweige nie — ein leerer würde für\nimmer als Unrat liegen bleiben, den niemand entfernt. Steht HEAD auf dem vermerkten Basiszweig des\nVorgangs und ist der Baum sauber, meldet der Park jetzt, dass der Vorgang auf dem Basiszweig selbst\ngeparkt wurde, mit `created_branch: false` und `committed: false`, statt `nxs/park/...` anzulegen.\nEin schmutziger Baum auf dem Basiszweig, ein Vorgang, der schon einen eigenen Zweig hat, und ein\nlosgelöster HEAD (sauber oder nicht — seine Position muss irgendwie erreichbar bleiben) bleiben\nunverändert." - }, - { - "type": "removed", - "en": "**`nxc release` and `Engine::release_working_tree` are gone.** The verb gave a workspace's held\nworking copy back by hand, on the strength of one refusal: it checked that no session in the holding\nchain still had a live process. Offered on the agent surface, that made it able to take a working\ncopy away from a running coding operation, and on a chat worker that never implemented the liveness\ncheck it was an unconditional release with no guard at all — exactly the blunt override this\nproject's own epic on manual overrides declined to build, reached anyway because the refusal was the\nonly thing standing between the verb and it.\n\nEvery hand-off now PARKS the holder's work first, so what the verb used to do by hand happens on its\nown. A working copy held by a chain that has died is parked and handed on by the background service\nwithout anyone asking — past its two-hour bound, or, since the background service can now tell a\ndead chain from a merely quiet one, inside it. A working copy held by a chain that is still running,\nand that you — the person who started it — want back on purpose, is taken with `nxc withdraw\n--thread ` / `Engine::withdraw`:\nit discharges the round, asks its session to stop, and parks whatever it left uncommitted once the\nprocess is gone — never rolled back — so the next commission into the same thread brings the work\nback.\n\n**Facade contract.** `Engine::release_working_tree` and `orchestration::release_working_tree` are\nremoved, along with `ReleaseReceipt`. `ChatStore::release_working_tree_and_take_next` is unaffected\nand stays: it is the transaction every hand-off — the reply path, a park, the sweep — still goes\nthrough to give the lease back and hand it to whoever is next.", - "de": "**`nxc release` und `Engine::release_working_tree` sind entfernt.** Das Verb gab die gehaltene\nArbeitskopie eines Workspace von Hand zurück, gestützt auf eine einzige Ablehnung: Es prüfte, ob\nkeine Sitzung der haltenden Kette noch einen lebenden Prozess hatte. Auf der Agentenoberfläche\nangeboten, konnte es damit einer laufenden Coding-Operation die Arbeitskopie wegnehmen, und bei\neinem Chat-Worker, der die Lebendigkeitsprüfung nie implementierte, war es ein bedingungsloses\nFreigeben ganz ohne Absicherung — genau die grobe Übersteuerung, die das Epic dieses Projekts über\nmanuelle Übersteuerungen ablehnte zu bauen, hier erreicht, weil die Ablehnung das Einzige war, was\nzwischen dem Verb und ihr stand.\n\nJede Übergabe parkt jetzt zuerst die Arbeit des Halters, sodass geschieht, was das Verb bisher von\nHand tat, von selbst. Eine Arbeitskopie, die eine bereits tote Kette hält, parkt und gibt der\nHintergrunddienst weiter, ohne dass jemand fragt — über ihrer Zwei-Stunden-Grenze, oder, da der\nHintergrunddienst jetzt eine tote Kette von einer nur stillen unterscheiden kann, schon davor. Eine\nArbeitskopie, die eine noch laufende Kette hält und die Sie — der Mensch, der sie gestartet hat —\nabsichtlich zurückwollen, nimmt man mit `nxc withdraw --thread ` / `Engine::withdraw`: Es entlastet die Runde, bittet ihre Sitzung, sich\nzu beenden, und parkt, sobald der Prozess weg ist, was sie unfertig hinterlassen hat — nie\nzurückgerollt —, sodass die nächste Beauftragung in denselben Faden die Arbeit zurückbringt.\n\n**Facade-Kontrakt.** `Engine::release_working_tree` und `orchestration::release_working_tree` sind\nentfernt, ebenso `ReleaseReceipt`. `ChatStore::release_working_tree_and_take_next` ist davon nicht\nbetroffen und bleibt: Es ist die Transaktion, über die jede Übergabe — der Antwortpfad, ein Parken,\nder Sweep — weiterhin läuft, um den Anspruch zurückzugeben und ihn dem Nächsten zu übergeben.", - "migration": { - "en": "`nxc release` and `Engine::release_working_tree` are gone. A working copy held by a dead or\nwithdrawn chain is now parked and handed on by the background service; to take back a running\nround you started, use `nxc withdraw --thread ` / `Engine::withdraw` — an agent escalates\ninstead.", - "de": "`nxc release` und `Engine::release_working_tree` sind entfernt. Eine Arbeitskopie, die eine tote\noder zurückgenommene Kette hält, parkt und gibt jetzt der Hintergrunddienst weiter; um eine\nlaufende Runde zurückzunehmen, die Sie gestartet haben, verwenden Sie `nxc withdraw --thread ` /\n`Engine::withdraw` — ein Agent eskaliert stattdessen." - }, - "facade": "breaking" - }, - { - "type": "added", - "en": "**`nxc status` now names an operation's unresumed parked work.** Since nxf 6j6v.de9s a stranded\nescalation could be parked onto a branch and the working copy handed on — but the only way to find\nthat branch again was to already know it existed. Now the operation's own line on `nxc status`\ncarries one line per unresumed park: the branch, the commit's first seven characters, and when the\ncopy was handed on (plus a note when the park found nothing to commit). `--json` carries the same\nrows as `parked` on the operation, and a workspace that has never parked stays silent about the\nfield — no `\"parked\"` key at all.\n\nFor embedding apps: `StatusOperation` (from `Engine::status`) gains a `parked` field. It was\nalready `#[non_exhaustive]`, so nothing changes for a reader.", - "de": "**`nxc status` nennt jetzt die noch nicht wieder aufgenommene geparkte Arbeit eines Vorgangs.** Seit\nnxf 6j6v.de9s konnte eine unbeantwortete Eskalation auf einen Zweig geparkt und die Arbeitskopie\nweitergegeben werden — aber der einzige Weg, diesen Zweig wiederzufinden, war, ihn schon zu kennen.\nJetzt trägt die eigene Zeile des Vorgangs auf `nxc status` eine Zeile pro noch nicht\nzurückgeholtem Park: den Zweig, die ersten sieben Zeichen des Commits und seit wann die Kopie\nweitergegeben ist (plus einen Hinweis, wenn der Park nichts zu committen fand). `--json` trägt\ndieselben Zeilen als `parked` auf dem Vorgang, und ein Arbeitsbereich, der noch nie geparkt hat,\nbleibt beim Feld still — kein `\"parked\"`-Schlüssel überhaupt.\n\nFür einbettende Anwendungen: `StatusOperation` (aus `Engine::status`) bekommt ein Feld `parked`. Es\nwar schon `#[non_exhaustive]`, also ändert sich für Lesende nichts.", - "facade": "changed" - }, - { - "type": "fixed", - "en": "**A holder that dies without saying anything no longer loses its uncommitted work when the queue\ntakes its checkout.** When a working-tree lease runs out and nothing in that operation's chain still\nhas a live process, the background sweep hands the checkout to whoever is waiting — and until now it\ndid that with the dead operation's uncommitted files still in the tree, so the next agent started\ninside somebody else's half-finished work. It was the last hand-off caused by trouble that saved\nnothing first: a chain that crashed never escalated and never announced an availability boundary, so\nneither of the two parks that already existed was ever asked about it (nxf 6j6v.8bv9). Now its work\nis committed to a branch (untracked files included; anything your `.gitignore` excludes stays in the\ntree), the checkout goes back to the branch that operation started on, and only then does the waiting\ncommission start — `nxc tick` reports the park on the same block as the reclaim, and `nxc status`\nshows the parked branch on the operation until it is resumed. The refusal rule applies here exactly\nas it does to the other two occasions: a merge, rebase, cherry-pick, revert or bisect in progress, or\na git command that failed, now **keeps the checkout** with the operation marked `PARK REFUSED` and\nthe park retried on every tick — where before the same tick said the claim was staying and then\nreclaimed it two steps later, which is the one contradiction this release removes. A refusal that\ncannot pass (no working copy, not a git repository, no base branch ever recorded) hands the checkout\non unparked with the named `work_handed_on_unparked` warning, as it already did elsewhere. A holder\nthat is past its bound but still has a live process is untouched, exactly as before.\n\n**The same is now true of the other door onto that moment.** A newcomer's own `nxc send` reclaims an\nexpired lease too — that is how whoever is already in line gets the copy before whoever happens to\nask — and it had every one of the sweep's conditions except the park, so a dead operation's work was\nstill handed on in the tree whenever a trigger got there before a tick did. It parks first now, under\nthe same occasion and the same refusal rule: if the park is refused for a reason that can pass,\n**nobody** takes the copy — the newcomer queues behind the holder exactly as it would have while the\nlease was live, and the service's retry is what eventually moves it. That holds **even when nobody\nelse is waiting at all**, which is the most ordinary shape of it: one agent working alone crashes\nwith uncommitted work, the bound passes, and the next `nxc send` is the hand-off. There is no turn to\nhand out then, but the copy still leaves an operation that came to grief, so its work goes onto a\nbranch first and the newcomer starts in a clean tree. The background sweep does not cover that one\nand is not meant to: with an empty queue it hands nothing on, so there is nothing there to save. Neither park finding makes a\nverb exit non-zero: `work_not_parked` and `work_handed_on_unparked` are reports on the receipt and in\n`--json`, and a routine reclaim in a workspace that is not a git repository (where no park can ever\nsucceed) no longer fails the call it happened during.\n\n**Facade contract.** `TriggerAdmission::Queued`'s `drain_unarmed: Option` becomes\n`findings: Vec` — one list, because that hand-off can now report a park beside the\nun-armed drain; a caller that built or destructured the variant has to name the new field, and\n`TriggerReceipt::warnings` carries the same values it always did.\n`WorkingTreeSweep` (on `TickReceipt::working_tree`) gains two public fields,\n`parked: Option` and `unparked: Option`, so a struct literal outside the crate\nhas to name them; both are omitted from `--json` when unset. `ParkOccasion` gains the variant\n`PastItsBound` (`past_its_bound` in `--json` and on `StatusOperation::park_refused.occasion`); the\nenum is `#[non_exhaustive]`, so an exhaustive `match` outside the crate already had a wildcard.", - "de": "**Ein Vorgang, der wortlos stirbt, verliert seine nicht committete Arbeit nicht mehr, wenn die\nWarteschlange seine Arbeitskopie übernimmt.** Läuft eine Arbeitskopie-Lease ab und hat kein Prozess\nin der Kette dieses Vorgangs mehr Leben, gibt der Hintergrund-Sweep die Arbeitskopie an den\nWartenden weiter — und tat das bisher mit den nicht committeten Dateien des toten Vorgangs noch im\nBaum, sodass der nächste Agent in der halbfertigen Arbeit eines anderen startete. Es war die letzte\ndurch eine Störung ausgelöste Übergabe, die nichts zuerst sicherte: Eine abgestürzte Kette hat weder\neskaliert noch eine Verfügbarkeitsgrenze angekündigt, also wurde keines der beiden bereits\nvorhandenen Parkverfahren je nach ihr gefragt (nxf 6j6v.8bv9). Jetzt wird ihre Arbeit auf einen Zweig\ncommittet (samt nicht versionierter Dateien; was `.gitignore` ausschließt, bleibt im Baum), die\nArbeitskopie kehrt auf den Zweig zurück, auf dem der Vorgang begonnen hat, und erst dann startet die\nwartende Beauftragung — `nxc tick` meldet das Parken im selben Block wie die Rückholung, und\n`nxc status` zeigt den geparkten Zweig am Vorgang, bis er wieder aufgenommen wird. Die Ablehnungsregel\ngilt hier genau wie bei den anderen beiden Anlässen: Ein laufender Merge, Rebase, Cherry-Pick, Revert\noder Bisect oder ein fehlgeschlagener git-Befehl **behält die Arbeitskopie** jetzt, der Vorgang wird\nmit `PARK REFUSED` markiert und das Parken bei jedem Tick erneut versucht — wo bisher derselbe Tick\nsagte, der Anspruch bleibe bestehen, und ihn zwei Schritte später zurückholte, was der eine\nWiderspruch ist, den diese Freigabe beseitigt. Eine Ablehnung, die nicht vorübergehen kann (keine\nArbeitskopie, kein git-Repository, nie ein Basiszweig vermerkt), gibt die Arbeitskopie ungeparkt\nweiter und sagt das mit der benannten Warnung `work_handed_on_unparked`, wie schon an anderer Stelle.\nEin Vorgang, dessen Frist abgelaufen ist, der aber noch einen lebenden Prozess hat, bleibt unberührt,\ngenau wie bisher.\n\n**Für die andere Tür zu demselben Moment gilt jetzt dasselbe.** Auch das eigene `nxc send` eines\nNeuankömmlings holt eine abgelaufene Lease zurück — so bekommt die Kopie, wer schon ansteht, und\nnicht, wer zufällig fragt — und es hatte alle Bedingungen des Sweeps außer dem Parken. Die Arbeit\neines toten Vorgangs wurde also weiterhin im Baum weitergereicht, sobald ein Anstoß vor einem Tick\ndort ankam. Jetzt wird zuerst geparkt, unter demselben Anlass und derselben Ablehnungsregel: Wird\ndas Parken aus einem Grund abgelehnt, der vorübergehen kann, bekommt **niemand** die Kopie — der\nNeuankömmling stellt sich hinter den Halter, genau wie bei noch laufendem Anspruch, und weiterbewegt\nwird sie vom erneuten Versuch des Dienstes. Das gilt **auch, wenn überhaupt niemand wartet** — der\ngewöhnlichste Fall überhaupt: Ein allein arbeitender Agent stürzt mit nicht committeter Arbeit ab,\ndie Frist läuft ab, und das nächste `nxc send` ist die Übergabe. Dann gibt es keinen Zug\nweiterzugeben, aber die Kopie verlässt trotzdem einen Vorgang, der verunglückt ist: Seine Arbeit\nkommt zuerst auf einen Zweig, und der Neuankömmling startet in einem sauberen Baum. Der\nHintergrund-Sweep deckt diesen Fall nicht ab und soll es auch nicht — bei leerer Warteschlange gibt\ner nichts weiter, also ist dort auch nichts zu sichern. Keine der beiden Park-Meldungen lässt ein Verb mit einem\nFehlercode enden: `work_not_parked` und `work_handed_on_unparked` sind Meldungen auf dem Beleg und in\n`--json`, und ein routinemäßiges Zurückholen in einem Arbeitsbereich, der kein git-Repository ist (wo\nkein Parken je gelingen kann), lässt den Aufruf, bei dem es geschieht, nicht mehr fehlschlagen.\n\n**Facade-Kontrakt.** Aus `drain_unarmed: Option` an `TriggerAdmission::Queued`\nwird `findings: Vec` — eine Liste, weil diese Übergabe jetzt neben dem nicht\ngestellten Wecker auch ein Parken melden kann; wer die Variante gebaut oder zerlegt hat, muss das\nneue Feld nennen, und `TriggerReceipt::warnings` trägt dieselben Werte wie bisher.\n`WorkingTreeSweep` (an `TickReceipt::working_tree`) bekommt zwei öffentliche\nFelder, `parked: Option` und `unparked: Option`; ein Struct-Literal außerhalb\ndes Crates muss sie also nennen, und in `--json` fehlen beide, solange sie nicht gesetzt sind.\n`ParkOccasion` bekommt die Variante `PastItsBound` (`past_its_bound` in `--json` und an\n`StatusOperation::park_refused.occasion`); das Enum ist `#[non_exhaustive]`, ein erschöpfendes\n`match` außerhalb des Crates hatte also schon einen Auffangzweig.", - "facade": "breaking" - }, - { - "type": "changed", - "en": "**`nxc withdraw` is a person's verb now: only whoever sent an operation's first commission may take\nit back, and only by naming that thread.** Until now the bar was \"may read the thread\" — on a public\nchannel any handle, an agent's own `nxc` included — and `nxc prime` taught the verb to every session\nthat started. Since the verb can stop a running round, who may call it became the question, and the\nanswer is a person, or an assistant a person started. Three callers are now refused before anything\nchanges, each by name: **a session this workspace started** (`forbidden`) — whatever it names; the\nrefusal names the session and the role it was started for, and points it at `nxc reply --thread\n --escalate -`, which goes up to whoever commissioned it; **a caller who did not\nopen the operation** (`forbidden`), told who did; and **a thread below the operation's first one** —\na step of a channel round, a sub-round an agent opened — (`validation`), told which thread to name\ninstead. The refusal of a session rests on what the engine can prove — the session map records\nevery session it started — and makes no claim beyond it: a process that hides its session is not\ncaught by that rule, and then meets the other two (nxf 6j6v.ezbr).\n\n**`nxc prime` no longer offers the verb.** The `withdraw` line is gone from *How a conversation\nmoves*, the entry is gone from `--json`'s `commands` (seven entries now), and `when_stuck` points a\nsession with a held working copy at escalation instead. The shipped example team's `pm` persona says\nthe same.\n\n**Facade contract.** `Engine::withdraw` / `orchestration::withdraw` refuse callers they used to\naccept, which is why this is marked breaking although no signature moved: a `Caller` whose `session`\nthis workspace minted (`ErrorKind::Forbidden`), a caller that is not the opener of the thread's\noperation (`Forbidden`; the read gate still answers first for a caller who cannot even read the\nthread), and a thread that is not its operation's root (`Validation`). A host acting for its\nlogged-in user passes no session and withdraws what that user sent, exactly as before; a host that\nwithdrew a member thread it read off `status` now names the operation's root. `PRIME_COMMANDS` loses\nits `withdraw` entry, and the texts of `PRIME_HOW_A_CONVERSATION_MOVES` and `PRIME_WHEN_STUCK`\nchanged accordingly.", - "de": "**`nxc withdraw` ist jetzt das Verb eines Menschen: Nur wer die erste Beauftragung eines Vorgangs\nabgeschickt hat, darf sie zurücknehmen, und nur, indem er diesen Faden nennt.** Bisher lautete die\nHürde „darf den Faden lesen\" — in einem öffentlichen Kanal jedes Handle, das eigene `nxc` eines\nAgenten eingeschlossen —, und `nxc prime` brachte das Verb jeder startenden Sitzung bei. Seit das\nVerb eine laufende Runde anhalten kann, ist die Frage, wer es aufrufen darf, und die Antwort ist ein\nMensch oder ein Assistent, den ein Mensch gestartet hat. Drei Aufrufer werden jetzt abgelehnt, bevor\nsich etwas ändert, jeder benannt: **eine Sitzung, die dieser Workspace gestartet hat**\n(`forbidden`) — gleich, was sie nennt; die Ablehnung nennt die Sitzung und die Rolle, für die sie\ngestartet wurde, und verweist sie auf `nxc reply --thread --escalate -`, das\nnach oben geht, zu dem, der sie beauftragt hat; **wer den Vorgang nicht eröffnet hat**\n(`forbidden`), mit dem Namen dessen, der es war; und **ein Faden unterhalb des ersten** — ein\nSchritt einer Kanalrunde, eine Unterrunde, die ein Agent eröffnet hat — (`validation`), mit dem\nFaden, der stattdessen zu nennen ist. Die Ablehnung einer Sitzung stützt sich auf das, was die\nEngine belegen kann — die Sitzungstabelle verzeichnet jede Sitzung, die sie gestartet hat —, und\nbehauptet nicht mehr: Ein Prozess, der seine Sitzung verbirgt, fällt nicht unter diese Regel und\ntrifft dann auf die beiden anderen (nxf 6j6v.ezbr).\n\n**`nxc prime` bietet das Verb nicht mehr an.** Die `withdraw`-Zeile ist aus *How a conversation\nmoves* verschwunden, der Eintrag aus `commands` in `--json` (jetzt sieben Einträge), und\n`when_stuck` verweist eine Sitzung mit gehaltener Arbeitskopie stattdessen auf die Eskalation. Die\n`pm`-Persona des mitgelieferten Beispielteams sagt dasselbe.\n\n**Facade-Kontrakt.** `Engine::withdraw` / `orchestration::withdraw` lehnen Aufrufer ab, die sie\nbisher angenommen haben — deshalb als breaking markiert, obwohl sich keine Signatur bewegt hat: ein\n`Caller`, dessen `session` dieser Workspace erzeugt hat (`ErrorKind::Forbidden`), ein Aufrufer, der\nnicht Eröffner des Vorgangs ist (`Forbidden`; wer den Faden nicht einmal lesen darf, bekommt weiter\nzuerst die Ablehnung des Lese-Tors), und ein Faden, der nicht die Wurzel seines Vorgangs ist\n(`Validation`). Ein Host, der für seinen angemeldeten Nutzer handelt, übergibt keine Sitzung und\nnimmt zurück, was dieser Nutzer abgeschickt hat, genau wie bisher; ein Host, der einen aus `status`\ngelesenen Mitglieds-Faden zurücknahm, nennt jetzt die Wurzel des Vorgangs. `PRIME_COMMANDS` verliert\nden Eintrag `withdraw`, und die Texte von `PRIME_HOW_A_CONVERSATION_MOVES` und `PRIME_WHEN_STUCK`\nhaben sich entsprechend geändert.", - "facade": "breaking" - }, - { - "type": "added", - "en": "**`nxc withdraw` takes back a round that is already running, and parks its work.** Until now the\nverb could only take back a commission that was still waiting for the working copy; a round whose\nsession had started was refused, and the only way to stop it was to find its process by hand — after\nwhich nothing saved what it had left in the checkout, and the next holder started inside it. Now\n`nxc withdraw --thread ` on a running round — by the person who sent it, see the entry on who\nmay withdraw — discharges the thread (the register says the round\nis over), asks the chat worker to stop the session (the shipped sidecar receives `SIGTERM`, aborts\nits turn, saves its transcript and announces its end), and — when the round holds this workspace's\nworking copy — records that its work is to be parked. The background tick then commits whatever the\nsession left uncommitted onto a branch once nothing in that operation is running any more, and hands\nthe copy on to whoever is waiting; with nobody waiting the copy simply becomes free. **Nothing is rolled back** at any point.\n`nxc status` lists the branch under the operation until somebody comes back for it, and the way\nback is the next commission into the same thread: on a direct persona thread, `nxc reply --thread\n` resumes the stopped session with its own transcript on the park branch; for a channel round, a\nfollow-up into the channel thread starts the next pass there. A fresh `send` opens a new claim and\ndoes not find it (nxf 6j6v.b9nf).\n\n**Between the stop and the park, the withdrawn holder keeps the copy.** The discharge leaves the\noperation owing nothing, which is exactly the state the ordinary end-of-operation release fires on —\nand a stopped session's own teardown or a late reply into the thread would have handed the copy on\nwith the work still in the tree. While the withdrawn marker stands, every such release declines;\nthe tick's withdrawn occasion is the one way such a holder lets go, and it acts only once nothing\nin the claim area has a live process, re-checking once a minute while one still does. The refusal\nrule applies unchanged: a park refused for a reason that can pass keeps the copy with `PARK\nREFUSED` on `nxc status` and is retried; one that cannot pass hands the copy on unparked with the\nnamed `work_handed_on_unparked` warning.\n\n**A withdrawal interrupts the chain below the thread it names** (nxf 6j6v.s2cj). Every thread\nbetween what was taken back and that thread that is still waiting — a channel thread owing its\nround's result, a persona thread waiting on the round it commissioned — is discharged too, with a\nmessage saying the chain was interrupted. So a channel commissions nothing further once one of its\nsteps is taken back: not the next step and not the taken-back step again, on a `flow: sequential`\nchannel and on one that declares `steps:` alike, before the park and after it; and nothing is\nconsolidated. The answers steps had already given stay in their threads, and the work comes back\nwhen the person who withdrew it commissions the operation's first thread again — for a round an\nagent commissioned, that thread's discharge carries the agent's session, so the follow-up resumes\nit. (A round a withdrawal did not\ndischarge — one where a queued commission started while the withdrawal ran — is still held by the\nmarker: its settled set is not advanced, routed or consolidated until the copy has gone on.)\n\n**What the receipt promises about a branch is now what the park can actually deliver.** `will_park`\nused to mean only \"this round holds the working copy\", and said `true` on hosts where a park can\nnever succeed — a worker that names no working copy (the default of every worker but the shipped\nsidecar), a workspace that is not a git repository, an operation with no recorded base. It now also\nasks those three read-only preconditions, and when the round holds the copy but cannot be parked the\nreceipt carries `cannot_park` with the reason: the copy is still handed on once nothing in the\noperation is running, unparked and with the `work_handed_on_unparked` warning saying so. `nxc\nwithdraw` prints that third case in its own words instead of \"nothing of it is in this workspace's\nworking copy\", and the message written into the withdrawn thread says the same true thing.\n\n**And a withdrawal that leaves work for the tick says when there is nothing to run one.** While the\nmarker stands every other release path declines by design, so on a machine whose background service\nis down the copy stays held by a round that is over, with nobody told. `nxc withdraw` now carries\nthe `service_not_running` warning `send`, `reply` and `tick` already carry (it does not change the\nexit code), and both park lines name `nxc tick --thread ` — the command that does the park, or\nthe unparked hand-off, by hand.\n\n**A host whose worker cannot stop a session is refused before anything changes** — the whole call,\nby name (`this host cannot stop a running session; nothing was withdrawn`), so a withdrawal never\nleaves a discharged thread beside a session that goes on writing. A stop the worker supports and\nthen could not deliver is a `session_not_stopped` warning on the receipt naming the session; the\nthread is discharged all the same and the command exits non-zero. A session that had **already\nended** by the time the stop was sent — the ordinary race for a round that was about to finish — is\nnot a failure: the receipt carries a note saying what was found, `warnings` stays empty and the\ncommand exits 0, because that is the state the call was asked for. And a host whose worker cannot\nanswer the liveness question at all is now refused **by name** — `ErrorKind::Validation`, where a\nworker unable to answer liveness with an unended session in the area used to get `not_found` — when\nthe area holds sessions that never reported an end, instead of being told there is nothing to\nwithdraw: that answer was a statement about processes nobody had looked at. Apart from who may call\nit and the chain it interrupts (both below and in the entry on who may withdraw), the queued case\nbehaves as before; the refusal for a thread with nothing under it (a worker that DOES answer\nliveness, or an area with no unended session either way) is still `not_found` and now reads\n*nothing under thread … is waiting or running*.\n\n**And a withdrawn holder that will not leave now has a visible reason.** A stopped session can\nsurvive its `SIGTERM` — a wedged sidecar, a custom worker whose stop returns `Ok` and changes\nnothing, a reused pid — and until now nothing on any surface said so: `nxc status` had no field for\nit, and \"stop it by hand\" printed only when the very first `stop_session` call had itself errored,\nnever when the request landed and was simply ignored. `nxc status` now marks the holding operation\n`WITHDRAWN` (beside `PARK REFUSED` and `holds working tree`) and names who withdrew it, when, and\nevery session still pinning the claim right now; `--json` carries the same fact as a new\n`withdrawn` field, omitted when the operation's round was never withdrawn. And after five liveness\ncadences (five minutes) with a session the withdrawal stopped still there, `nxc tick` sends it **one\nfurther `SIGTERM`** — through the worker's own stop, behind the same identity check as the first —\nand names it: the session, its pid, the pid file that names it, and when that further signal went\n(nxf 6j6v.27b9). Nothing more is ever sent, and never `SIGKILL`: the sidecar's whole teardown\nhappens on `SIGTERM`, and ending the process past that is left to a person. Against the shipped\nsidecar, which is already stopping when it arrives, that further signal changes nothing; it helps a\nhost's own worker whose first stop was lost or ignored. A session a follow-up put back in motion\nbefore the park is somebody's work again, and is neither signalled nor named.\n\n**Facade contract.** `WithdrawReceipt` gains `stopped: Vec` (`{thread, role,\nsession, already_gone?}` — the last omitted unless the session had already ended, in which case it\ncarries the sentence saying so; the array omitted from `--json` when empty), `will_park: bool`,\n`cannot_park: Option` (omitted from `--json` when absent; `will_park: false` with no\n`cannot_park` is a round that holds nothing, `cannot_park: Some(_)` is work in the checkout this\nhost can never park) and `warnings: Vec` (omitted when empty), and is now\n`#[non_exhaustive]` — both the new fields\nand the marker break a struct literal written outside the crate, which is why this is marked\nbreaking; reading the receipt is unaffected. `ParkOccasion` gains `Withdrawn` (`withdrawn` in\n`--json` and on `park_refused.occasion`); `ConsequenceClass` gains `SessionNotStopped`\n(`session_not_stopped`) and `WithdrawnHolderWedged` (`withdrawn_holder_wedged`); both enums were\nalready `#[non_exhaustive]`. `ChatStore` gains `note_withdrawn_holder`, `withdrawn_holder` and\n`all_withdrawn_holders` over the new device-local table `withdrawn_holders`, and\n`note_withdrawn_sessions`, `withdrawn_session` (a new `WithdrawnSession {stopped_at, resignalled_at,\nresignal_refused}`), `claim_withdrawn_session_resignal`, `note_withdrawn_session_resignal_refused`\nand `forget_withdrawn_session` over its sibling `withdrawn_sessions` — the sessions a withdrawal\nstopped, which are the only ones the tick's further `SIGTERM` may reach.\n`StatusOperation` — also `#[non_exhaustive]` already — gains `withdrawn: Option`\n(the new type, `#[non_exhaustive]`: `{withdrawn_at, by, pinned_by: Vec}`), a purely\nadditive field. `Engine::withdraw`'s signature is unchanged.", - "de": "**`nxc withdraw` nimmt eine bereits laufende Runde zurück und parkt ihre Arbeit.** Bisher konnte das\nVerb nur eine Beauftragung zurücknehmen, die noch auf die Arbeitskopie wartete; eine Runde, deren\nSitzung gestartet war, wurde abgelehnt, und der einzige Weg, sie anzuhalten, war, ihren Prozess von\nHand zu suchen — wonach nichts sicherte, was sie in der Arbeitskopie hinterlassen hatte, und der\nnächste Halter darin startete. Jetzt entlastet `nxc withdraw --thread ` auf einer laufenden\nRunde — durch den Menschen, der sie abgeschickt hat, siehe den Eintrag dazu, wer zurücknehmen darf —\nden Faden (das Register sagt, die Runde ist vorbei), bittet den Chat-Worker, die Sitzung\nanzuhalten (der mitgelieferte Sidecar erhält `SIGTERM`, bricht seinen Zug ab, sichert sein\nTranskript und meldet sein Ende), und vermerkt — wenn die Runde die Arbeitskopie dieses Workspace\nhält —, dass ihre Arbeit zu parken ist. Der Hintergrund-Tick committet dann, sobald in diesem\nVorgang nichts mehr läuft, alles Uncommittete auf einen Zweig und gibt die Kopie an den weiter, der\nwartet; wartet niemand, wird die Kopie einfach frei. **Nichts wird je zurückgerollt.** `nxc status` führt den Zweig\nunter dem Vorgang, bis jemand ihn wieder abholt, und der Weg zurück ist die nächste Beauftragung in\ndenselben Faden: auf einem direkten Persona-Faden setzt `nxc reply --thread ` die angehaltene\nSitzung mit ihrem eigenen Transkript auf dem Parkzweig fort; bei einer Kanalrunde startet eine\nFolgenachricht in den Kanalfaden dort den nächsten Durchgang. Ein frisches `send` eröffnet einen\nneuen Anspruch und findet sie nicht (nxf 6j6v.b9nf).\n\n**Zwischen Stopp und Parken behält der zurückgenommene Halter die Kopie.** Die Entlastung lässt den\nVorgang nichts mehr schulden — genau der Zustand, in dem die gewöhnliche Freigabe am Ende eines\nVorgangs greift —, und der Abbau der angehaltenen Sitzung oder eine späte Antwort in den Faden hätte\ndie Kopie mit der Arbeit noch im Baum weitergegeben. Solange die Markierung steht, unterbleibt jede\nsolche Freigabe; der Anlass „zurückgenommen\" des Ticks ist der eine Weg, auf dem ein solcher Halter\nloslässt, und er handelt erst, wenn nichts im Anspruchsbereich mehr einen lebenden Prozess hat —\nsolange einer da ist, sieht er einmal pro Minute nach. Die Ablehnungsregel gilt unverändert: Ein\nParken, das aus einem behebbaren Grund abgelehnt wird, behält die Kopie mit `PARK REFUSED` in\n`nxc status` und wird erneut versucht; eines, das nie durchgehen kann, gibt die Kopie ungeparkt mit\nder benannten Warnung `work_handed_on_unparked` weiter.\n\n**Eine Rücknahme unterbricht die Kette unter dem Faden, den sie nennt** (nxf 6j6v.s2cj). Jeder\nFaden zwischen dem Zurückgenommenen und diesem Faden, der noch wartet — ein Kanalfaden, der das\nErgebnis seiner Runde schuldet, ein Persona-Faden, der auf die von ihm beauftragte Runde wartet —,\nwird ebenfalls entlastet, mit einer Nachricht, die sagt, dass die Kette unterbrochen wurde. Ein\nKanal beauftragt also nichts mehr, sobald einer seiner Schritte zurückgenommen ist: weder den\nnächsten Schritt noch den zurückgenommenen erneut, an einem `flow: sequential`-Kanal genauso wie an\neinem mit `steps:`, vor dem Parken und danach; und nichts wird zusammengeführt. Die Antworten, die\nSchritte schon gegeben hatten, bleiben in ihren Fäden, und die Arbeit kommt zurück, wenn der Mensch,\nder sie zurückgenommen hat, den ersten Faden des Vorgangs erneut beauftragt — bei einer Runde, die\nein Agent beauftragt hat, trägt die Entlastung dieses Fadens die Sitzung des Agenten, sodass die\nFolgenachricht sie fortsetzt. (Eine Runde, die eine Rücknahme nicht\nentlastet hat — eine, in der eine wartende Beauftragung startete, während die Rücknahme lief —, hält\nweiter die Markierung: Ihr abgeschlossener Satz wird weder fortgeschaltet noch geroutet noch\nzusammengeführt, bis die Kopie weitergegeben ist.)\n\n**Was die Quittung über einen Zweig verspricht, ist jetzt das, was das Parken auch liefern kann.**\n`will_park` hieß bisher nur „diese Runde hält die Arbeitskopie\" und stand auf `true` auch auf Hosts,\nauf denen ein Parken nie gelingen kann — ein Worker, der keine Arbeitskopie benennt (die Vorgabe\njedes Workers außer dem mitgelieferten Sidecar), ein Workspace, der kein Git-Repository ist, ein\nVorgang ohne vermerkte Basis. Jetzt werden diese drei rein lesenden Vorbedingungen mitgefragt, und\nwenn die Runde die Kopie zwar hält, aber nicht geparkt werden kann, trägt die Quittung\n`cannot_park` mit dem Grund: Die Kopie wird trotzdem weitergegeben, sobald im Vorgang nichts mehr\nläuft — ungeparkt und mit der Warnung `work_handed_on_unparked`, die das sagt. `nxc withdraw` gibt\ndiesen dritten Fall in eigenen Worten aus statt „nothing of it is in this workspace's working copy\",\nund die in den zurückgenommenen Faden geschriebene Nachricht sagt dasselbe Wahre.\n\n**Und eine Rücknahme, die dem Tick Arbeit hinterlässt, sagt, wenn nichts einen Tick ausführt.**\nSolange die Markierung steht, unterbleibt jede andere Freigabe planmäßig — auf einer Maschine mit\ntotem Hintergrunddienst bleibt die Kopie also bei einer Runde, die vorbei ist, ohne dass es jemand\nerfährt. `nxc withdraw` trägt jetzt die Warnung `service_not_running`, die `send`, `reply` und\n`tick` schon tragen (sie ändert den Exit-Code nicht), und beide Park-Zeilen nennen\n`nxc tick --thread ` — den Befehl, der das Parken beziehungsweise die ungeparkte Weitergabe von\nHand erledigt.\n\n**Ein Host, dessen Worker keine Sitzung anhalten kann, wird abgelehnt, bevor sich etwas ändert** —\nder ganze Aufruf, benannt (`this host cannot stop a running session; nothing was withdrawn`), damit\neine Rücknahme nie einen entlasteten Faden neben einer weiterschreibenden Sitzung hinterlässt. Ein\nStopp, den der Worker beherrscht und dann nicht zustellen konnte, ist eine Warnung\n`session_not_stopped` auf der Quittung, die die Sitzung nennt; der Faden ist trotzdem entlastet, und\nder Befehl endet mit einem Fehlercode. Eine Sitzung, die zum Zeitpunkt des Stopps **bereits beendet**\nwar — das gewöhnliche Rennen bei einer Runde, die ohnehin gerade fertig wurde —, ist kein\nFehlschlag: Die Quittung trägt einen Vermerk, was vorgefunden wurde, `warnings` bleibt leer, und der\nBefehl endet mit 0, denn das ist genau der Zustand, um den es ging. Und ein Host, dessen Worker die\nLebendigkeitsfrage überhaupt nicht beantworten kann, wird jetzt **benannt** abgelehnt —\n`ErrorKind::Validation`, wo ein Worker, der die Lebendigkeit nicht beantworten kann, bei einer nicht\ngemeldeten Sitzung im Bereich bisher `not_found` bekam —, wenn im Bereich Sitzungen ohne gemeldetes\nEnde liegen, statt zu hören, es gebe nichts zurückzunehmen: Diese Antwort war eine Aussage über\nProzesse, die niemand angesehen hatte. Abgesehen davon, wer zurücknehmen darf, und von der Kette,\ndie eine Rücknahme unterbricht (beides unten und im Eintrag dazu, wer zurücknehmen darf), verhält\nsich der wartende Fall wie bisher; die Ablehnung für einen Faden, unter dem nichts liegt (ein\nWorker, der die Lebendigkeit beantworten kann, oder ein Bereich ganz ohne nicht gemeldete Sitzung),\nbleibt `not_found` und lautet jetzt *nothing under thread … is waiting or running*.\n\n**Und ein Halter, der die Rücknahme nicht loslässt, hat jetzt einen sichtbaren Grund.** Eine\nangehaltene Sitzung kann ihr `SIGTERM` überleben — ein hängender Sidecar, ein eigener Worker, dessen\nStopp `Ok` meldet und nichts ändert, eine wiederverwendete PID —, und bisher sagte das nichts auf\nkeiner Oberfläche: `nxc status` hatte dafür kein Feld, und „von Hand stoppen\" stand nur dann, wenn\nschon der erste `stop_session`-Aufruf selbst fehlgeschlagen war, nie wenn die Anforderung ankam und\neinfach ignoriert wurde. `nxc status` markiert den haltenden Vorgang jetzt mit `WITHDRAWN` (neben\n`PARK REFUSED` und `holds working tree`) und nennt, wer zurückgenommen hat, wann, und jede Sitzung,\ndie den Anspruch gerade noch hält; `--json` trägt dieselbe Tatsache als neues Feld `withdrawn`,\nweggelassen, wenn die Runde des Vorgangs nie zurückgenommen wurde. Und nach fünf\nLebendigkeits-Takten (fünf Minuten), in denen eine von der Rücknahme gestoppte Sitzung noch da ist,\nschickt `nxc tick` ihr **ein weiteres `SIGTERM`** — über den eigenen Stopp des Workers, hinter\nderselben Identitätsprüfung wie das erste — und nennt sie: die Sitzung, ihre PID, die PID-Datei, die\nsie nennt, und wann dieses weitere Signal ging (nxf 6j6v.27b9). Mehr wird nie geschickt, und nie\n`SIGKILL`: Der ganze Abbau des Sidecars geschieht auf `SIGTERM`, und den Prozess darüber hinaus zu\nbeenden, bleibt einem Menschen überlassen. Beim mitgelieferten Sidecar, der beim Eintreffen schon\nanhält, ändert dieses weitere Signal nichts; es hilft dem eigenen Worker eines Hosts, dessen erster\nStopp verloren ging oder ignoriert wurde. Eine Sitzung, die eine Folgenachricht vor dem Parken\nwieder in Gang gesetzt hat, ist wieder jemandes Arbeit und wird weder signalisiert noch genannt.\n\n**Facade-Kontrakt.** `WithdrawReceipt` bekommt `stopped: Vec` (`{thread, role,\nsession, already_gone?}` — das letzte Feld nur, wenn die Sitzung bereits beendet war, dann mit dem\nSatz, der das sagt; das Array in `--json` weggelassen, wenn leer), `will_park: bool`,\n`cannot_park: Option` (in `--json` weggelassen, wenn nicht gesetzt; `will_park: false` ohne\n`cannot_park` ist eine Runde, die nichts hält, `cannot_park: Some(_)` ist Arbeit in der\nArbeitskopie, die dieser Host nie parken kann) und `warnings: Vec` (weggelassen,\nwenn leer) und ist jetzt `#[non_exhaustive]` — sowohl die\nneuen Felder als auch die Markierung brechen ein außerhalb des Crates geschriebenes Struct-Literal,\nweshalb dies als breaking markiert ist; das Lesen der Quittung ist nicht betroffen. `ParkOccasion`\nbekommt `Withdrawn` (`withdrawn` in `--json` und an `park_refused.occasion`); `ConsequenceClass`\nbekommt `SessionNotStopped` (`session_not_stopped`) und `WithdrawnHolderWedged`\n(`withdrawn_holder_wedged`); beide Enums waren bereits `#[non_exhaustive]`.\n`ChatStore` bekommt `note_withdrawn_holder`, `withdrawn_holder` und `all_withdrawn_holders` über die\nneue gerätelokale Tabelle `withdrawn_holders`, und `note_withdrawn_sessions`, `withdrawn_session`\n(ein neues `WithdrawnSession {stopped_at, resignalled_at, resignal_refused}`),\n`claim_withdrawn_session_resignal`, `note_withdrawn_session_resignal_refused` und\n`forget_withdrawn_session` über ihre Schwester `withdrawn_sessions` — die Sitzungen, die eine\nRücknahme gestoppt hat, und damit die einzigen, die das weitere `SIGTERM` des Ticks erreichen darf. `StatusOperation` — ebenfalls schon\n`#[non_exhaustive]` — bekommt `withdrawn: Option` (der neue Typ, auch\n`#[non_exhaustive]`: `{withdrawn_at, by, pinned_by: Vec}`), ein rein additives Feld. Die\nSignatur von `Engine::withdraw` ist unverändert.", - "facade": "breaking" - } - ], - "notes": { - "en": "### Added\n- **A chat worker can now stop a session it started.** Until now the engine could only ask a worker\nquestions about a session — is it running, where does it run, can it be resumed — and the one thing\nthat ended a session was the session itself. That is the piece a withdrawal of a *running* round\nneeds and did not have: taking the work back while its session goes on writing into the checkout\nleaves the park stale before its receipt is printed. The shipped sidecar worker answers the new\nquestion with yes and stops a session by sending its process `SIGTERM` — never `SIGKILL`, never the\nwhole process group — to the pid recorded in the same `.nxs/agent-logs/.pid` file the\nliveness check reads, so the process signalled is by construction the one reported as running. A\nmissing pid file, a file that holds no pid, or a claim that cannot prove which process it names is\nrefused by name rather than signalled into the dark; a session whose process is already gone is not\na failure at all but the state the stop was asked for, reported as such. The request is *delivered*,\nnot waited out: whoever stops a session watches the liveness check turn false before touching the\ntree (nxf 6j6v.b9nf).\n\n**A session claim now records WHICH process holds it, not only its number.** Claims are deliberately\nnever removed, so a session that crashed — or a machine that rebooted — leaves a file naming a pid\nthe operating system is free to hand to something else: an editor, a dev server, another `nxc` of\nthe same user. `kill(pid, 0)` says *yes* about that stranger, which was tolerable while the answer\nonly delayed a wake and is not tolerable now that taking a round back sends the process a signal. So\n`.nxs/agent-logs/.pid` holds the pid on its first line and the instant that process started\non its second, and every reader compares that instant against what the operating system reports for\nthe pid it finds — the same identity check the background service already makes for its own\nheartbeat, shared rather than copied. A claim whose instant disagrees names a session that **ended**:\nit reads as not running, and nothing is signalled. A claim written before this existed records no\ninstant, cannot be told apart from a recycled pid, and is therefore refused by name instead of\nsignalled — the liveness reads still believe it, because being wrong there costs a delayed turn\nwhile being wrong about a signal costs a stranger's process.\n\n**And the liveness checks now read a pid file the same way the stop does.** A `.pid` file holding\n`0` — or any number the operating system would read as a process *group* rather than a process —\nreads as *no live process* everywhere: for one session, and for the whole-workspace list the\nbackground service polls. Both used to parse a bare number and ask `kill(pid, 0)` about it, which\nfor `0` is a question about the asking process's own group and answers *yes* whenever anything of\nthis workspace is alive — a session reported as running that nothing could ever end.\n\n**The sidecar tears down on `SIGTERM` instead of dying.** It aborts the running turn through the\nSDK's own abort door, then runs its teardown in the usual order — binds the runtime session so a\nlater resume can find the conversation, flushes everything the aborted turn produced into the\ntranscript, and announces `nxc session ended`. Exactly one step is skipped on the signal: the\nsession is **not reminded** to answer, because a reminder tells a session it broke the answering\nrule and this one was told to stop, and it would spend a model call doing so.\n\n**Whether anything is posted in the session's name is decided by the BOARD, not by the signal.** A\n`SIGTERM` is not only a withdrawal: it is a system shutdown, a logout, a `docker stop`, a person's\n`kill `. A withdrawal discharges the thread before it signals, so the teardown reads a thread\nthat owes nothing and says nothing — which is right, because a substitute reply there would speak\nas the agent into a round that has just been taken back. Every other `SIGTERM` leaves the thread\nstill owing an answer, and the teardown posts the usual `nxc reply --thread --if-unanswered`\nfor it; the engine's own gate still decides whether that write lands. Without this the handler\nturned every outside `SIGTERM` from a hard death — which the dead-holder sweep frees in about a\nminute — into a tidy silent end, because the teardown now announces the session's end: the debt\nunsettled, the waiting caller unwoken, and the working copy held until the two-hour bound.\n\nA stop is neither a finished turn nor a failed one, so the log ends in `sidecar stopped:` rather\nthan `sidecar done:`, and the process exits with **143** (128 + SIGTERM, the number a shell reports\nfor a process that died of the signal, kept for one that caught it). A `SIGTERM` that arrives during\nthe reply-reminder round ends that round the same way.\n\n**Facade contract.** `Worker` gains two defaulted methods: `stops_sessions(&self) -> bool`\n(`false` by default — a host that cannot stop a session is refused by name rather than believed)\nand `stop_session(&self, internal_session: &str) -> Result` (a named refusal by\ndefault). An existing implementation keeps exactly the behaviour it had; a host whose runtime can\nstop its own sessions overrides both. `WorkerConfig::Custom`'s wrapper forwards both. The new\n`SessionStop` enum (`#[non_exhaustive]`) is what a delivered stop and an already-finished session\nare told apart by: `Requested` is the signal sent, `NothingToStop(String)` is the goal state with the\nsentence that found it — a caller puts that on its receipt rather than in a warning. `worker` also\ngains `session_claim_for(pid) -> String`, the one spelling of a claim file's contents, so anything\nstanding a process in for a session writes the shape the readers expect. Nothing was removed.\n- **`nxc status` now names an operation's unresumed parked work.** Since nxf 6j6v.de9s a stranded\nescalation could be parked onto a branch and the working copy handed on — but the only way to find\nthat branch again was to already know it existed. Now the operation's own line on `nxc status`\ncarries one line per unresumed park: the branch, the commit's first seven characters, and when the\ncopy was handed on (plus a note when the park found nothing to commit). `--json` carries the same\nrows as `parked` on the operation, and a workspace that has never parked stays silent about the\nfield — no `\"parked\"` key at all.\n\nFor embedding apps: `StatusOperation` (from `Engine::status`) gains a `parked` field. It was\nalready `#[non_exhaustive]`, so nothing changes for a reader.\n- **`nxc withdraw` takes back a round that is already running, and parks its work.** Until now the\nverb could only take back a commission that was still waiting for the working copy; a round whose\nsession had started was refused, and the only way to stop it was to find its process by hand — after\nwhich nothing saved what it had left in the checkout, and the next holder started inside it. Now\n`nxc withdraw --thread ` on a running round — by the person who sent it, see the entry on who\nmay withdraw — discharges the thread (the register says the round\nis over), asks the chat worker to stop the session (the shipped sidecar receives `SIGTERM`, aborts\nits turn, saves its transcript and announces its end), and — when the round holds this workspace's\nworking copy — records that its work is to be parked. The background tick then commits whatever the\nsession left uncommitted onto a branch once nothing in that operation is running any more, and hands\nthe copy on to whoever is waiting; with nobody waiting the copy simply becomes free. **Nothing is rolled back** at any point.\n`nxc status` lists the branch under the operation until somebody comes back for it, and the way\nback is the next commission into the same thread: on a direct persona thread, `nxc reply --thread\n` resumes the stopped session with its own transcript on the park branch; for a channel round, a\nfollow-up into the channel thread starts the next pass there. A fresh `send` opens a new claim and\ndoes not find it (nxf 6j6v.b9nf).\n\n**Between the stop and the park, the withdrawn holder keeps the copy.** The discharge leaves the\noperation owing nothing, which is exactly the state the ordinary end-of-operation release fires on —\nand a stopped session's own teardown or a late reply into the thread would have handed the copy on\nwith the work still in the tree. While the withdrawn marker stands, every such release declines;\nthe tick's withdrawn occasion is the one way such a holder lets go, and it acts only once nothing\nin the claim area has a live process, re-checking once a minute while one still does. The refusal\nrule applies unchanged: a park refused for a reason that can pass keeps the copy with `PARK\nREFUSED` on `nxc status` and is retried; one that cannot pass hands the copy on unparked with the\nnamed `work_handed_on_unparked` warning.\n\n**A withdrawal interrupts the chain below the thread it names** (nxf 6j6v.s2cj). Every thread\nbetween what was taken back and that thread that is still waiting — a channel thread owing its\nround's result, a persona thread waiting on the round it commissioned — is discharged too, with a\nmessage saying the chain was interrupted. So a channel commissions nothing further once one of its\nsteps is taken back: not the next step and not the taken-back step again, on a `flow: sequential`\nchannel and on one that declares `steps:` alike, before the park and after it; and nothing is\nconsolidated. The answers steps had already given stay in their threads, and the work comes back\nwhen the person who withdrew it commissions the operation's first thread again — for a round an\nagent commissioned, that thread's discharge carries the agent's session, so the follow-up resumes\nit. (A round a withdrawal did not\ndischarge — one where a queued commission started while the withdrawal ran — is still held by the\nmarker: its settled set is not advanced, routed or consolidated until the copy has gone on.)\n\n**What the receipt promises about a branch is now what the park can actually deliver.** `will_park`\nused to mean only \"this round holds the working copy\", and said `true` on hosts where a park can\nnever succeed — a worker that names no working copy (the default of every worker but the shipped\nsidecar), a workspace that is not a git repository, an operation with no recorded base. It now also\nasks those three read-only preconditions, and when the round holds the copy but cannot be parked the\nreceipt carries `cannot_park` with the reason: the copy is still handed on once nothing in the\noperation is running, unparked and with the `work_handed_on_unparked` warning saying so. `nxc\nwithdraw` prints that third case in its own words instead of \"nothing of it is in this workspace's\nworking copy\", and the message written into the withdrawn thread says the same true thing.\n\n**And a withdrawal that leaves work for the tick says when there is nothing to run one.** While the\nmarker stands every other release path declines by design, so on a machine whose background service\nis down the copy stays held by a round that is over, with nobody told. `nxc withdraw` now carries\nthe `service_not_running` warning `send`, `reply` and `tick` already carry (it does not change the\nexit code), and both park lines name `nxc tick --thread ` — the command that does the park, or\nthe unparked hand-off, by hand.\n\n**A host whose worker cannot stop a session is refused before anything changes** — the whole call,\nby name (`this host cannot stop a running session; nothing was withdrawn`), so a withdrawal never\nleaves a discharged thread beside a session that goes on writing. A stop the worker supports and\nthen could not deliver is a `session_not_stopped` warning on the receipt naming the session; the\nthread is discharged all the same and the command exits non-zero. A session that had **already\nended** by the time the stop was sent — the ordinary race for a round that was about to finish — is\nnot a failure: the receipt carries a note saying what was found, `warnings` stays empty and the\ncommand exits 0, because that is the state the call was asked for. And a host whose worker cannot\nanswer the liveness question at all is now refused **by name** — `ErrorKind::Validation`, where a\nworker unable to answer liveness with an unended session in the area used to get `not_found` — when\nthe area holds sessions that never reported an end, instead of being told there is nothing to\nwithdraw: that answer was a statement about processes nobody had looked at. Apart from who may call\nit and the chain it interrupts (both below and in the entry on who may withdraw), the queued case\nbehaves as before; the refusal for a thread with nothing under it (a worker that DOES answer\nliveness, or an area with no unended session either way) is still `not_found` and now reads\n*nothing under thread … is waiting or running*.\n\n**And a withdrawn holder that will not leave now has a visible reason.** A stopped session can\nsurvive its `SIGTERM` — a wedged sidecar, a custom worker whose stop returns `Ok` and changes\nnothing, a reused pid — and until now nothing on any surface said so: `nxc status` had no field for\nit, and \"stop it by hand\" printed only when the very first `stop_session` call had itself errored,\nnever when the request landed and was simply ignored. `nxc status` now marks the holding operation\n`WITHDRAWN` (beside `PARK REFUSED` and `holds working tree`) and names who withdrew it, when, and\nevery session still pinning the claim right now; `--json` carries the same fact as a new\n`withdrawn` field, omitted when the operation's round was never withdrawn. And after five liveness\ncadences (five minutes) with a session the withdrawal stopped still there, `nxc tick` sends it **one\nfurther `SIGTERM`** — through the worker's own stop, behind the same identity check as the first —\nand names it: the session, its pid, the pid file that names it, and when that further signal went\n(nxf 6j6v.27b9). Nothing more is ever sent, and never `SIGKILL`: the sidecar's whole teardown\nhappens on `SIGTERM`, and ending the process past that is left to a person. Against the shipped\nsidecar, which is already stopping when it arrives, that further signal changes nothing; it helps a\nhost's own worker whose first stop was lost or ignored. A session a follow-up put back in motion\nbefore the park is somebody's work again, and is neither signalled nor named.\n\n**Facade contract.** `WithdrawReceipt` gains `stopped: Vec` (`{thread, role,\nsession, already_gone?}` — the last omitted unless the session had already ended, in which case it\ncarries the sentence saying so; the array omitted from `--json` when empty), `will_park: bool`,\n`cannot_park: Option` (omitted from `--json` when absent; `will_park: false` with no\n`cannot_park` is a round that holds nothing, `cannot_park: Some(_)` is work in the checkout this\nhost can never park) and `warnings: Vec` (omitted when empty), and is now\n`#[non_exhaustive]` — both the new fields\nand the marker break a struct literal written outside the crate, which is why this is marked\nbreaking; reading the receipt is unaffected. `ParkOccasion` gains `Withdrawn` (`withdrawn` in\n`--json` and on `park_refused.occasion`); `ConsequenceClass` gains `SessionNotStopped`\n(`session_not_stopped`) and `WithdrawnHolderWedged` (`withdrawn_holder_wedged`); both enums were\nalready `#[non_exhaustive]`. `ChatStore` gains `note_withdrawn_holder`, `withdrawn_holder` and\n`all_withdrawn_holders` over the new device-local table `withdrawn_holders`, and\n`note_withdrawn_sessions`, `withdrawn_session` (a new `WithdrawnSession {stopped_at, resignalled_at,\nresignal_refused}`), `claim_withdrawn_session_resignal`, `note_withdrawn_session_resignal_refused`\nand `forget_withdrawn_session` over its sibling `withdrawn_sessions` — the sessions a withdrawal\nstopped, which are the only ones the tick's further `SIGTERM` may reach.\n`StatusOperation` — also `#[non_exhaustive]` already — gains `withdrawn: Option`\n(the new type, `#[non_exhaustive]`: `{withdrawn_at, by, pinned_by: Vec}`), a purely\nadditive field. `Engine::withdraw`'s signature is unchanged.\n\n### Changed\n- **A refused park now follows one rule: hold and retry, or hand on unparked.** When a stranded\nescalation or an operation on hold at an availability boundary has to give up the working copy, its\nwork is parked first — and a park can be refused. Until now every refusal kept the checkout where it\nwas and named `nxc release` as the way out. Owner decision of 2026-09-17 (nxf 6j6v.8bv9,\n6j6v.b9nf): a refusal that can pass (a merge, rebase, cherry-pick, revert or bisect in progress, or a\ngit command that failed) keeps the checkout, is reported as `work_not_parked` with what to finish,\nshows on `nxc status` as `PARK REFUSED` with a line `park refused (): — retrying\nsince `, and is retried by the background service on every tick — and only for as long as\nthe trouble that wanted the checkout lasts: once the escalation is answered, the operation on hold is\ntaken up, or nobody is waiting any more, the mark goes on the next tick, because nothing is retrying\nthat park then. A refusal that cannot pass (the runtime names no working copy, the directory is not a\ngit repository, no base branch was ever recorded) hands the checkout on WITHOUT parking and says so\nas the new `work_handed_on_unparked` warning — the operation's uncommitted files stay in the tree for\nthe next holder. An operation on hold at an availability boundary is now also secured by the tick\nwhile somebody waits, not only by `nxc resume`. No refusal text names `nxc release` any more. And an\noperation with unresumed parked work or a refused park now stays on the default `nxc status` listing\neven when nothing else about it is open.\n\n**Facade contract.** `ParkRefusal` gains the variant `NoBaseRecorded` (the refusal that used to be\nan inline sentence) plus `is_permanent()` and `kind()`; the enum is not `#[non_exhaustive]`, so an\nexhaustive `match` outside the crate has to name the new arm. `TickReceipt` gains a public\n`handed_on: Option` field (with the new `ParkOccasion`), so a struct literal outside the\ncrate has to name it. `ConsequenceClass` gains `WorkHandedOnUnparked` (already `#[non_exhaustive]`).\n`StatusOperation` (from `Engine::status`) gains `park_refused: Option` (already\n`#[non_exhaustive]`), and `ChatStore` gains `note_park_refusal`, `clear_park_refusal`,\n`park_refusal` and `all_park_refusals` over a new device-local table; `ParkRefusalNote` names the\n`occasion` that refused the park (`stranded_escalation`, `availability_boundary`), in `--json` too.\nThe behavioural half no signature carries: `tick` and `Engine::resume_interrupted` now hand the\nworking copy on for a host whose worker names no working copy (the default of every worker but the\nsidecar) where they used to leave it held.\n- **`nxs` describes itself the way the README does.** `nxs --help` now reads \"nexus-flow — the board\n(nxf), the memory (nxm) and the channel (nxc) your agents work from, in one binary\" instead of \"nxs\nplatform umbrella CLI\", and the `nxs init` welcome box opens with the README's tagline. The welcome\nbox no longer says nexus-chat is \"coming soon\" — chat is offered in the chooser right below it.\nChat's line in the chooser, in the init summary and in the `advertisement` of `nxf init --json` now\nreads \"the channel — messages between you and your agents, on the record\"; the guides call chat the\nchannel too, and neither they nor `nxs sync --help` call `nxs` a platform any more.\n- **A park on a clean tree no longer creates a branch.** Owner decision of 2026-09-17 (nxf\n6j6v.7hqc): a park creates a branch only when there is work on the operation's base branch, because\nthe engine never deletes park branches — an empty one would sit there forever as clutter nobody\nremoves. When HEAD is on the operation's recorded base branch and the tree is clean, the park now\nreports the operation parked on the base branch itself, with `created_branch: false` and\n`committed: false`, instead of minting `nxs/park/...`. A dirty tree on the base branch, an\noperation already on a branch of its own, and a detached HEAD (clean or not — its position has to\nstay reachable) are all unchanged.\n- **`nxc withdraw` is a person's verb now: only whoever sent an operation's first commission may take\nit back, and only by naming that thread.** Until now the bar was \"may read the thread\" — on a public\nchannel any handle, an agent's own `nxc` included — and `nxc prime` taught the verb to every session\nthat started. Since the verb can stop a running round, who may call it became the question, and the\nanswer is a person, or an assistant a person started. Three callers are now refused before anything\nchanges, each by name: **a session this workspace started** (`forbidden`) — whatever it names; the\nrefusal names the session and the role it was started for, and points it at `nxc reply --thread\n --escalate -`, which goes up to whoever commissioned it; **a caller who did not\nopen the operation** (`forbidden`), told who did; and **a thread below the operation's first one** —\na step of a channel round, a sub-round an agent opened — (`validation`), told which thread to name\ninstead. The refusal of a session rests on what the engine can prove — the session map records\nevery session it started — and makes no claim beyond it: a process that hides its session is not\ncaught by that rule, and then meets the other two (nxf 6j6v.ezbr).\n\n**`nxc prime` no longer offers the verb.** The `withdraw` line is gone from *How a conversation\nmoves*, the entry is gone from `--json`'s `commands` (seven entries now), and `when_stuck` points a\nsession with a held working copy at escalation instead. The shipped example team's `pm` persona says\nthe same.\n\n**Facade contract.** `Engine::withdraw` / `orchestration::withdraw` refuse callers they used to\naccept, which is why this is marked breaking although no signature moved: a `Caller` whose `session`\nthis workspace minted (`ErrorKind::Forbidden`), a caller that is not the opener of the thread's\noperation (`Forbidden`; the read gate still answers first for a caller who cannot even read the\nthread), and a thread that is not its operation's root (`Validation`). A host acting for its\nlogged-in user passes no session and withdraws what that user sent, exactly as before; a host that\nwithdrew a member thread it read off `status` now names the operation's root. `PRIME_COMMANDS` loses\nits `withdraw` entry, and the texts of `PRIME_HOW_A_CONVERSATION_MOVES` and `PRIME_WHEN_STUCK`\nchanged accordingly.\n\n### Fixed\n- **A commission no longer waits out two hours behind an operation that is already dead.** The\nworking-tree lease has a two-hour backstop, and until now that backstop was the only thing watching:\nif the chain holding the checkout died hard — crashed, killed, machine asleep — nothing released it,\nnothing looked, and whoever was queued behind it stood still until the lease expired. Now the\nbackground tick asks, once a minute while anybody is waiting, whether the holder is *provably* gone,\nand takes the checkout as soon as it is: the dead operation's work is committed to a branch\n(untracked files included; anything your `.gitignore` excludes stays in the tree), the tree goes back\nto the branch that operation started on, and the waiting commission starts — typically within a\nminute of the death instead of up to two hours later (nxf 6j6v.xb24).\n\n**\"Provably\" is five conditions, and all five have to hold**, because taking a checkout away writes\ninto it. The chat worker must actually be able to answer the process question — a runtime that cannot\nlook never takes a checkout early, so nothing changes for a host that does not implement the liveness\nprobe. At least one thread of that operation must still owe an answer; an operation whose sessions\nall ended normally and handed the task back is not dead but waiting for a human, and it keeps the\nthirty-minute contention rule it already had. Nothing in the operation may have a live process. No\nowing thread's session may have reported its end — a reported end is a teardown that ran, which is\nnot a hard death. And nothing in the operation may be on hold at an availability boundary, which has\nits own path and its own way back. What that check costs was measured before it was put in every\ntick, and measured end to end: about **two milliseconds** for an operation of twenty threads with\ntwenty sessions, on a development machine, in the slower of the two builds this project makes.\n\nEverything else about this hand-off is the behaviour the other occasions already had: `nxc tick`\nreports where the work went, `nxc status` shows the parked branch on the operation until it is\nresumed, and the refusal rule applies unchanged — a merge, rebase, cherry-pick, revert or bisect in\nprogress, or a git command that failed, **keeps the checkout** with the operation marked\n`PARK REFUSED` and the park retried every tick, while a refusal that can never pass (no working copy,\nnot a git repository, no base branch ever recorded) hands the checkout on unparked with the named\n`work_handed_on_unparked` warning.\n\n**One timing change you may notice.** While a commission is queued behind a claim, the tick that\nre-checks it is now scheduled a minute out rather than at the holder's own bound — that re-check is\nwhat makes the above arrive in a minute instead of at the bound. A bound that falls sooner than a\nminute still wins; nothing is ever pushed later.\n\n**Facade contract.** `ParkOccasion` gains the variant `DiedInsideItsBound`\n(`died_inside_its_bound` in `--json` and on `StatusOperation::park_refused.occasion`), and\n`TickReceipt::handed_on` can now carry it; the enum is `#[non_exhaustive]`, so an exhaustive `match`\noutside the crate already had a wildcard. `ChatStore` gains\n`reclaim_a_dead_holders_working_tree_and_take_next`, the compare-and-swap twin of\n`reclaim_expired_working_tree_and_take_next` for a lease that is still inside its bound. Nothing was\nremoved and no signature changed.\n- **A tick no longer consolidates a withdrawn round and wakes the agent that commissioned it.** When\nevery commission of a channel round was taken back, the channel thread was discharged as withdrawn —\nand the channel's own clock could still fire on it later, read a settled set, and deliver it: for a\nround a person had commissioned that was a no-op, but for one an agent had commissioned (a `pm`\nthat sent work to a channel) it woke that agent with the taken-back round as its answer, and the\nwork went on. `nxc tick` now treats a thread discharged by a withdrawal as already handled, exactly\nlike one that was consolidated (`reason: already_handled`), and nothing is delivered (nxf\n6j6v.s2cj).\n- **A holder that dies without saying anything no longer loses its uncommitted work when the queue\ntakes its checkout.** When a working-tree lease runs out and nothing in that operation's chain still\nhas a live process, the background sweep hands the checkout to whoever is waiting — and until now it\ndid that with the dead operation's uncommitted files still in the tree, so the next agent started\ninside somebody else's half-finished work. It was the last hand-off caused by trouble that saved\nnothing first: a chain that crashed never escalated and never announced an availability boundary, so\nneither of the two parks that already existed was ever asked about it (nxf 6j6v.8bv9). Now its work\nis committed to a branch (untracked files included; anything your `.gitignore` excludes stays in the\ntree), the checkout goes back to the branch that operation started on, and only then does the waiting\ncommission start — `nxc tick` reports the park on the same block as the reclaim, and `nxc status`\nshows the parked branch on the operation until it is resumed. The refusal rule applies here exactly\nas it does to the other two occasions: a merge, rebase, cherry-pick, revert or bisect in progress, or\na git command that failed, now **keeps the checkout** with the operation marked `PARK REFUSED` and\nthe park retried on every tick — where before the same tick said the claim was staying and then\nreclaimed it two steps later, which is the one contradiction this release removes. A refusal that\ncannot pass (no working copy, not a git repository, no base branch ever recorded) hands the checkout\non unparked with the named `work_handed_on_unparked` warning, as it already did elsewhere. A holder\nthat is past its bound but still has a live process is untouched, exactly as before.\n\n**The same is now true of the other door onto that moment.** A newcomer's own `nxc send` reclaims an\nexpired lease too — that is how whoever is already in line gets the copy before whoever happens to\nask — and it had every one of the sweep's conditions except the park, so a dead operation's work was\nstill handed on in the tree whenever a trigger got there before a tick did. It parks first now, under\nthe same occasion and the same refusal rule: if the park is refused for a reason that can pass,\n**nobody** takes the copy — the newcomer queues behind the holder exactly as it would have while the\nlease was live, and the service's retry is what eventually moves it. That holds **even when nobody\nelse is waiting at all**, which is the most ordinary shape of it: one agent working alone crashes\nwith uncommitted work, the bound passes, and the next `nxc send` is the hand-off. There is no turn to\nhand out then, but the copy still leaves an operation that came to grief, so its work goes onto a\nbranch first and the newcomer starts in a clean tree. The background sweep does not cover that one\nand is not meant to: with an empty queue it hands nothing on, so there is nothing there to save. Neither park finding makes a\nverb exit non-zero: `work_not_parked` and `work_handed_on_unparked` are reports on the receipt and in\n`--json`, and a routine reclaim in a workspace that is not a git repository (where no park can ever\nsucceed) no longer fails the call it happened during.\n\n**Facade contract.** `TriggerAdmission::Queued`'s `drain_unarmed: Option` becomes\n`findings: Vec` — one list, because that hand-off can now report a park beside the\nun-armed drain; a caller that built or destructured the variant has to name the new field, and\n`TriggerReceipt::warnings` carries the same values it always did.\n`WorkingTreeSweep` (on `TickReceipt::working_tree`) gains two public fields,\n`parked: Option` and `unparked: Option`, so a struct literal outside the crate\nhas to name them; both are omitted from `--json` when unset. `ParkOccasion` gains the variant\n`PastItsBound` (`past_its_bound` in `--json` and on `StatusOperation::park_refused.occasion`); the\nenum is `#[non_exhaustive]`, so an exhaustive `match` outside the crate already had a wildcard.\n\n### Removed\n- **`nxc release` and `Engine::release_working_tree` are gone.** The verb gave a workspace's held\nworking copy back by hand, on the strength of one refusal: it checked that no session in the holding\nchain still had a live process. Offered on the agent surface, that made it able to take a working\ncopy away from a running coding operation, and on a chat worker that never implemented the liveness\ncheck it was an unconditional release with no guard at all — exactly the blunt override this\nproject's own epic on manual overrides declined to build, reached anyway because the refusal was the\nonly thing standing between the verb and it.\n\nEvery hand-off now PARKS the holder's work first, so what the verb used to do by hand happens on its\nown. A working copy held by a chain that has died is parked and handed on by the background service\nwithout anyone asking — past its two-hour bound, or, since the background service can now tell a\ndead chain from a merely quiet one, inside it. A working copy held by a chain that is still running,\nand that you — the person who started it — want back on purpose, is taken with `nxc withdraw\n--thread ` / `Engine::withdraw`:\nit discharges the round, asks its session to stop, and parks whatever it left uncommitted once the\nprocess is gone — never rolled back — so the next commission into the same thread brings the work\nback.\n\n**Facade contract.** `Engine::release_working_tree` and `orchestration::release_working_tree` are\nremoved, along with `ReleaseReceipt`. `ChatStore::release_working_tree_and_take_next` is unaffected\nand stays: it is the transaction every hand-off — the reply path, a park, the sweep — still goes\nthrough to give the lease back and hand it to whoever is next.\n\n### Facade Contract\n- `changed` · **A commission no longer waits out two hours behind an operation that is already dead.** The\nworking-tree lease has a two-hour backstop, and until now that backstop was the only thing watching:\nif the chain holding the checkout died hard — crashed, killed, machine asleep — nothing released it,\nnothing looked, and whoever was queued behind it stood still until the lease expired. Now the\nbackground tick asks, once a minute while anybody is waiting, whether the holder is *provably* gone,\nand takes the checkout as soon as it is: the dead operation's work is committed to a branch\n(untracked files included; anything your `.gitignore` excludes stays in the tree), the tree goes back\nto the branch that operation started on, and the waiting commission starts — typically within a\nminute of the death instead of up to two hours later (nxf 6j6v.xb24).\n\n**\"Provably\" is five conditions, and all five have to hold**, because taking a checkout away writes\ninto it. The chat worker must actually be able to answer the process question — a runtime that cannot\nlook never takes a checkout early, so nothing changes for a host that does not implement the liveness\nprobe. At least one thread of that operation must still owe an answer; an operation whose sessions\nall ended normally and handed the task back is not dead but waiting for a human, and it keeps the\nthirty-minute contention rule it already had. Nothing in the operation may have a live process. No\nowing thread's session may have reported its end — a reported end is a teardown that ran, which is\nnot a hard death. And nothing in the operation may be on hold at an availability boundary, which has\nits own path and its own way back. What that check costs was measured before it was put in every\ntick, and measured end to end: about **two milliseconds** for an operation of twenty threads with\ntwenty sessions, on a development machine, in the slower of the two builds this project makes.\n\nEverything else about this hand-off is the behaviour the other occasions already had: `nxc tick`\nreports where the work went, `nxc status` shows the parked branch on the operation until it is\nresumed, and the refusal rule applies unchanged — a merge, rebase, cherry-pick, revert or bisect in\nprogress, or a git command that failed, **keeps the checkout** with the operation marked\n`PARK REFUSED` and the park retried every tick, while a refusal that can never pass (no working copy,\nnot a git repository, no base branch ever recorded) hands the checkout on unparked with the named\n`work_handed_on_unparked` warning.\n\n**One timing change you may notice.** While a commission is queued behind a claim, the tick that\nre-checks it is now scheduled a minute out rather than at the holder's own bound — that re-check is\nwhat makes the above arrive in a minute instead of at the bound. A bound that falls sooner than a\nminute still wins; nothing is ever pushed later.\n\n**Facade contract.** `ParkOccasion` gains the variant `DiedInsideItsBound`\n(`died_inside_its_bound` in `--json` and on `StatusOperation::park_refused.occasion`), and\n`TickReceipt::handed_on` can now carry it; the enum is `#[non_exhaustive]`, so an exhaustive `match`\noutside the crate already had a wildcard. `ChatStore` gains\n`reclaim_a_dead_holders_working_tree_and_take_next`, the compare-and-swap twin of\n`reclaim_expired_working_tree_and_take_next` for a lease that is still inside its bound. Nothing was\nremoved and no signature changed.\n- `breaking` · **A refused park now follows one rule: hold and retry, or hand on unparked.** When a stranded\nescalation or an operation on hold at an availability boundary has to give up the working copy, its\nwork is parked first — and a park can be refused. Until now every refusal kept the checkout where it\nwas and named `nxc release` as the way out. Owner decision of 2026-09-17 (nxf 6j6v.8bv9,\n6j6v.b9nf): a refusal that can pass (a merge, rebase, cherry-pick, revert or bisect in progress, or a\ngit command that failed) keeps the checkout, is reported as `work_not_parked` with what to finish,\nshows on `nxc status` as `PARK REFUSED` with a line `park refused (): — retrying\nsince `, and is retried by the background service on every tick — and only for as long as\nthe trouble that wanted the checkout lasts: once the escalation is answered, the operation on hold is\ntaken up, or nobody is waiting any more, the mark goes on the next tick, because nothing is retrying\nthat park then. A refusal that cannot pass (the runtime names no working copy, the directory is not a\ngit repository, no base branch was ever recorded) hands the checkout on WITHOUT parking and says so\nas the new `work_handed_on_unparked` warning — the operation's uncommitted files stay in the tree for\nthe next holder. An operation on hold at an availability boundary is now also secured by the tick\nwhile somebody waits, not only by `nxc resume`. No refusal text names `nxc release` any more. And an\noperation with unresumed parked work or a refused park now stays on the default `nxc status` listing\neven when nothing else about it is open.\n\n**Facade contract.** `ParkRefusal` gains the variant `NoBaseRecorded` (the refusal that used to be\nan inline sentence) plus `is_permanent()` and `kind()`; the enum is not `#[non_exhaustive]`, so an\nexhaustive `match` outside the crate has to name the new arm. `TickReceipt` gains a public\n`handed_on: Option` field (with the new `ParkOccasion`), so a struct literal outside the\ncrate has to name it. `ConsequenceClass` gains `WorkHandedOnUnparked` (already `#[non_exhaustive]`).\n`StatusOperation` (from `Engine::status`) gains `park_refused: Option` (already\n`#[non_exhaustive]`), and `ChatStore` gains `note_park_refusal`, `clear_park_refusal`,\n`park_refusal` and `all_park_refusals` over a new device-local table; `ParkRefusalNote` names the\n`occasion` that refused the park (`stranded_escalation`, `availability_boundary`), in `--json` too.\nThe behavioural half no signature carries: `tick` and `Engine::resume_interrupted` now hand the\nworking copy on for a host whose worker names no working copy (the default of every worker but the\nsidecar) where they used to leave it held.\n- `changed` · **A tick no longer consolidates a withdrawn round and wakes the agent that commissioned it.** When\nevery commission of a channel round was taken back, the channel thread was discharged as withdrawn —\nand the channel's own clock could still fire on it later, read a settled set, and deliver it: for a\nround a person had commissioned that was a no-op, but for one an agent had commissioned (a `pm`\nthat sent work to a channel) it woke that agent with the taken-back round as its answer, and the\nwork went on. `nxc tick` now treats a thread discharged by a withdrawal as already handled, exactly\nlike one that was consolidated (`reason: already_handled`), and nothing is delivered (nxf\n6j6v.s2cj).\n- `changed` · **A chat worker can now stop a session it started.** Until now the engine could only ask a worker\nquestions about a session — is it running, where does it run, can it be resumed — and the one thing\nthat ended a session was the session itself. That is the piece a withdrawal of a *running* round\nneeds and did not have: taking the work back while its session goes on writing into the checkout\nleaves the park stale before its receipt is printed. The shipped sidecar worker answers the new\nquestion with yes and stops a session by sending its process `SIGTERM` — never `SIGKILL`, never the\nwhole process group — to the pid recorded in the same `.nxs/agent-logs/.pid` file the\nliveness check reads, so the process signalled is by construction the one reported as running. A\nmissing pid file, a file that holds no pid, or a claim that cannot prove which process it names is\nrefused by name rather than signalled into the dark; a session whose process is already gone is not\na failure at all but the state the stop was asked for, reported as such. The request is *delivered*,\nnot waited out: whoever stops a session watches the liveness check turn false before touching the\ntree (nxf 6j6v.b9nf).\n\n**A session claim now records WHICH process holds it, not only its number.** Claims are deliberately\nnever removed, so a session that crashed — or a machine that rebooted — leaves a file naming a pid\nthe operating system is free to hand to something else: an editor, a dev server, another `nxc` of\nthe same user. `kill(pid, 0)` says *yes* about that stranger, which was tolerable while the answer\nonly delayed a wake and is not tolerable now that taking a round back sends the process a signal. So\n`.nxs/agent-logs/.pid` holds the pid on its first line and the instant that process started\non its second, and every reader compares that instant against what the operating system reports for\nthe pid it finds — the same identity check the background service already makes for its own\nheartbeat, shared rather than copied. A claim whose instant disagrees names a session that **ended**:\nit reads as not running, and nothing is signalled. A claim written before this existed records no\ninstant, cannot be told apart from a recycled pid, and is therefore refused by name instead of\nsignalled — the liveness reads still believe it, because being wrong there costs a delayed turn\nwhile being wrong about a signal costs a stranger's process.\n\n**And the liveness checks now read a pid file the same way the stop does.** A `.pid` file holding\n`0` — or any number the operating system would read as a process *group* rather than a process —\nreads as *no live process* everywhere: for one session, and for the whole-workspace list the\nbackground service polls. Both used to parse a bare number and ask `kill(pid, 0)` about it, which\nfor `0` is a question about the asking process's own group and answers *yes* whenever anything of\nthis workspace is alive — a session reported as running that nothing could ever end.\n\n**The sidecar tears down on `SIGTERM` instead of dying.** It aborts the running turn through the\nSDK's own abort door, then runs its teardown in the usual order — binds the runtime session so a\nlater resume can find the conversation, flushes everything the aborted turn produced into the\ntranscript, and announces `nxc session ended`. Exactly one step is skipped on the signal: the\nsession is **not reminded** to answer, because a reminder tells a session it broke the answering\nrule and this one was told to stop, and it would spend a model call doing so.\n\n**Whether anything is posted in the session's name is decided by the BOARD, not by the signal.** A\n`SIGTERM` is not only a withdrawal: it is a system shutdown, a logout, a `docker stop`, a person's\n`kill `. A withdrawal discharges the thread before it signals, so the teardown reads a thread\nthat owes nothing and says nothing — which is right, because a substitute reply there would speak\nas the agent into a round that has just been taken back. Every other `SIGTERM` leaves the thread\nstill owing an answer, and the teardown posts the usual `nxc reply --thread --if-unanswered`\nfor it; the engine's own gate still decides whether that write lands. Without this the handler\nturned every outside `SIGTERM` from a hard death — which the dead-holder sweep frees in about a\nminute — into a tidy silent end, because the teardown now announces the session's end: the debt\nunsettled, the waiting caller unwoken, and the working copy held until the two-hour bound.\n\nA stop is neither a finished turn nor a failed one, so the log ends in `sidecar stopped:` rather\nthan `sidecar done:`, and the process exits with **143** (128 + SIGTERM, the number a shell reports\nfor a process that died of the signal, kept for one that caught it). A `SIGTERM` that arrives during\nthe reply-reminder round ends that round the same way.\n\n**Facade contract.** `Worker` gains two defaulted methods: `stops_sessions(&self) -> bool`\n(`false` by default — a host that cannot stop a session is refused by name rather than believed)\nand `stop_session(&self, internal_session: &str) -> Result` (a named refusal by\ndefault). An existing implementation keeps exactly the behaviour it had; a host whose runtime can\nstop its own sessions overrides both. `WorkerConfig::Custom`'s wrapper forwards both. The new\n`SessionStop` enum (`#[non_exhaustive]`) is what a delivered stop and an already-finished session\nare told apart by: `Requested` is the signal sent, `NothingToStop(String)` is the goal state with the\nsentence that found it — a caller puts that on its receipt rather than in a warning. `worker` also\ngains `session_claim_for(pid) -> String`, the one spelling of a claim file's contents, so anything\nstanding a process in for a session writes the shape the readers expect. Nothing was removed.\n- `breaking` · **`nxc release` and `Engine::release_working_tree` are gone.** The verb gave a workspace's held\nworking copy back by hand, on the strength of one refusal: it checked that no session in the holding\nchain still had a live process. Offered on the agent surface, that made it able to take a working\ncopy away from a running coding operation, and on a chat worker that never implemented the liveness\ncheck it was an unconditional release with no guard at all — exactly the blunt override this\nproject's own epic on manual overrides declined to build, reached anyway because the refusal was the\nonly thing standing between the verb and it.\n\nEvery hand-off now PARKS the holder's work first, so what the verb used to do by hand happens on its\nown. A working copy held by a chain that has died is parked and handed on by the background service\nwithout anyone asking — past its two-hour bound, or, since the background service can now tell a\ndead chain from a merely quiet one, inside it. A working copy held by a chain that is still running,\nand that you — the person who started it — want back on purpose, is taken with `nxc withdraw\n--thread ` / `Engine::withdraw`:\nit discharges the round, asks its session to stop, and parks whatever it left uncommitted once the\nprocess is gone — never rolled back — so the next commission into the same thread brings the work\nback.\n\n**Facade contract.** `Engine::release_working_tree` and `orchestration::release_working_tree` are\nremoved, along with `ReleaseReceipt`. `ChatStore::release_working_tree_and_take_next` is unaffected\nand stays: it is the transaction every hand-off — the reply path, a park, the sweep — still goes\nthrough to give the lease back and hand it to whoever is next.\n- `changed` · **`nxc status` now names an operation's unresumed parked work.** Since nxf 6j6v.de9s a stranded\nescalation could be parked onto a branch and the working copy handed on — but the only way to find\nthat branch again was to already know it existed. Now the operation's own line on `nxc status`\ncarries one line per unresumed park: the branch, the commit's first seven characters, and when the\ncopy was handed on (plus a note when the park found nothing to commit). `--json` carries the same\nrows as `parked` on the operation, and a workspace that has never parked stays silent about the\nfield — no `\"parked\"` key at all.\n\nFor embedding apps: `StatusOperation` (from `Engine::status`) gains a `parked` field. It was\nalready `#[non_exhaustive]`, so nothing changes for a reader.\n- `breaking` · **A holder that dies without saying anything no longer loses its uncommitted work when the queue\ntakes its checkout.** When a working-tree lease runs out and nothing in that operation's chain still\nhas a live process, the background sweep hands the checkout to whoever is waiting — and until now it\ndid that with the dead operation's uncommitted files still in the tree, so the next agent started\ninside somebody else's half-finished work. It was the last hand-off caused by trouble that saved\nnothing first: a chain that crashed never escalated and never announced an availability boundary, so\nneither of the two parks that already existed was ever asked about it (nxf 6j6v.8bv9). Now its work\nis committed to a branch (untracked files included; anything your `.gitignore` excludes stays in the\ntree), the checkout goes back to the branch that operation started on, and only then does the waiting\ncommission start — `nxc tick` reports the park on the same block as the reclaim, and `nxc status`\nshows the parked branch on the operation until it is resumed. The refusal rule applies here exactly\nas it does to the other two occasions: a merge, rebase, cherry-pick, revert or bisect in progress, or\na git command that failed, now **keeps the checkout** with the operation marked `PARK REFUSED` and\nthe park retried on every tick — where before the same tick said the claim was staying and then\nreclaimed it two steps later, which is the one contradiction this release removes. A refusal that\ncannot pass (no working copy, not a git repository, no base branch ever recorded) hands the checkout\non unparked with the named `work_handed_on_unparked` warning, as it already did elsewhere. A holder\nthat is past its bound but still has a live process is untouched, exactly as before.\n\n**The same is now true of the other door onto that moment.** A newcomer's own `nxc send` reclaims an\nexpired lease too — that is how whoever is already in line gets the copy before whoever happens to\nask — and it had every one of the sweep's conditions except the park, so a dead operation's work was\nstill handed on in the tree whenever a trigger got there before a tick did. It parks first now, under\nthe same occasion and the same refusal rule: if the park is refused for a reason that can pass,\n**nobody** takes the copy — the newcomer queues behind the holder exactly as it would have while the\nlease was live, and the service's retry is what eventually moves it. That holds **even when nobody\nelse is waiting at all**, which is the most ordinary shape of it: one agent working alone crashes\nwith uncommitted work, the bound passes, and the next `nxc send` is the hand-off. There is no turn to\nhand out then, but the copy still leaves an operation that came to grief, so its work goes onto a\nbranch first and the newcomer starts in a clean tree. The background sweep does not cover that one\nand is not meant to: with an empty queue it hands nothing on, so there is nothing there to save. Neither park finding makes a\nverb exit non-zero: `work_not_parked` and `work_handed_on_unparked` are reports on the receipt and in\n`--json`, and a routine reclaim in a workspace that is not a git repository (where no park can ever\nsucceed) no longer fails the call it happened during.\n\n**Facade contract.** `TriggerAdmission::Queued`'s `drain_unarmed: Option` becomes\n`findings: Vec` — one list, because that hand-off can now report a park beside the\nun-armed drain; a caller that built or destructured the variant has to name the new field, and\n`TriggerReceipt::warnings` carries the same values it always did.\n`WorkingTreeSweep` (on `TickReceipt::working_tree`) gains two public fields,\n`parked: Option` and `unparked: Option`, so a struct literal outside the crate\nhas to name them; both are omitted from `--json` when unset. `ParkOccasion` gains the variant\n`PastItsBound` (`past_its_bound` in `--json` and on `StatusOperation::park_refused.occasion`); the\nenum is `#[non_exhaustive]`, so an exhaustive `match` outside the crate already had a wildcard.\n- `breaking` · **`nxc withdraw` is a person's verb now: only whoever sent an operation's first commission may take\nit back, and only by naming that thread.** Until now the bar was \"may read the thread\" — on a public\nchannel any handle, an agent's own `nxc` included — and `nxc prime` taught the verb to every session\nthat started. Since the verb can stop a running round, who may call it became the question, and the\nanswer is a person, or an assistant a person started. Three callers are now refused before anything\nchanges, each by name: **a session this workspace started** (`forbidden`) — whatever it names; the\nrefusal names the session and the role it was started for, and points it at `nxc reply --thread\n --escalate -`, which goes up to whoever commissioned it; **a caller who did not\nopen the operation** (`forbidden`), told who did; and **a thread below the operation's first one** —\na step of a channel round, a sub-round an agent opened — (`validation`), told which thread to name\ninstead. The refusal of a session rests on what the engine can prove — the session map records\nevery session it started — and makes no claim beyond it: a process that hides its session is not\ncaught by that rule, and then meets the other two (nxf 6j6v.ezbr).\n\n**`nxc prime` no longer offers the verb.** The `withdraw` line is gone from *How a conversation\nmoves*, the entry is gone from `--json`'s `commands` (seven entries now), and `when_stuck` points a\nsession with a held working copy at escalation instead. The shipped example team's `pm` persona says\nthe same.\n\n**Facade contract.** `Engine::withdraw` / `orchestration::withdraw` refuse callers they used to\naccept, which is why this is marked breaking although no signature moved: a `Caller` whose `session`\nthis workspace minted (`ErrorKind::Forbidden`), a caller that is not the opener of the thread's\noperation (`Forbidden`; the read gate still answers first for a caller who cannot even read the\nthread), and a thread that is not its operation's root (`Validation`). A host acting for its\nlogged-in user passes no session and withdraws what that user sent, exactly as before; a host that\nwithdrew a member thread it read off `status` now names the operation's root. `PRIME_COMMANDS` loses\nits `withdraw` entry, and the texts of `PRIME_HOW_A_CONVERSATION_MOVES` and `PRIME_WHEN_STUCK`\nchanged accordingly.\n- `breaking` · **`nxc withdraw` takes back a round that is already running, and parks its work.** Until now the\nverb could only take back a commission that was still waiting for the working copy; a round whose\nsession had started was refused, and the only way to stop it was to find its process by hand — after\nwhich nothing saved what it had left in the checkout, and the next holder started inside it. Now\n`nxc withdraw --thread ` on a running round — by the person who sent it, see the entry on who\nmay withdraw — discharges the thread (the register says the round\nis over), asks the chat worker to stop the session (the shipped sidecar receives `SIGTERM`, aborts\nits turn, saves its transcript and announces its end), and — when the round holds this workspace's\nworking copy — records that its work is to be parked. The background tick then commits whatever the\nsession left uncommitted onto a branch once nothing in that operation is running any more, and hands\nthe copy on to whoever is waiting; with nobody waiting the copy simply becomes free. **Nothing is rolled back** at any point.\n`nxc status` lists the branch under the operation until somebody comes back for it, and the way\nback is the next commission into the same thread: on a direct persona thread, `nxc reply --thread\n` resumes the stopped session with its own transcript on the park branch; for a channel round, a\nfollow-up into the channel thread starts the next pass there. A fresh `send` opens a new claim and\ndoes not find it (nxf 6j6v.b9nf).\n\n**Between the stop and the park, the withdrawn holder keeps the copy.** The discharge leaves the\noperation owing nothing, which is exactly the state the ordinary end-of-operation release fires on —\nand a stopped session's own teardown or a late reply into the thread would have handed the copy on\nwith the work still in the tree. While the withdrawn marker stands, every such release declines;\nthe tick's withdrawn occasion is the one way such a holder lets go, and it acts only once nothing\nin the claim area has a live process, re-checking once a minute while one still does. The refusal\nrule applies unchanged: a park refused for a reason that can pass keeps the copy with `PARK\nREFUSED` on `nxc status` and is retried; one that cannot pass hands the copy on unparked with the\nnamed `work_handed_on_unparked` warning.\n\n**A withdrawal interrupts the chain below the thread it names** (nxf 6j6v.s2cj). Every thread\nbetween what was taken back and that thread that is still waiting — a channel thread owing its\nround's result, a persona thread waiting on the round it commissioned — is discharged too, with a\nmessage saying the chain was interrupted. So a channel commissions nothing further once one of its\nsteps is taken back: not the next step and not the taken-back step again, on a `flow: sequential`\nchannel and on one that declares `steps:` alike, before the park and after it; and nothing is\nconsolidated. The answers steps had already given stay in their threads, and the work comes back\nwhen the person who withdrew it commissions the operation's first thread again — for a round an\nagent commissioned, that thread's discharge carries the agent's session, so the follow-up resumes\nit. (A round a withdrawal did not\ndischarge — one where a queued commission started while the withdrawal ran — is still held by the\nmarker: its settled set is not advanced, routed or consolidated until the copy has gone on.)\n\n**What the receipt promises about a branch is now what the park can actually deliver.** `will_park`\nused to mean only \"this round holds the working copy\", and said `true` on hosts where a park can\nnever succeed — a worker that names no working copy (the default of every worker but the shipped\nsidecar), a workspace that is not a git repository, an operation with no recorded base. It now also\nasks those three read-only preconditions, and when the round holds the copy but cannot be parked the\nreceipt carries `cannot_park` with the reason: the copy is still handed on once nothing in the\noperation is running, unparked and with the `work_handed_on_unparked` warning saying so. `nxc\nwithdraw` prints that third case in its own words instead of \"nothing of it is in this workspace's\nworking copy\", and the message written into the withdrawn thread says the same true thing.\n\n**And a withdrawal that leaves work for the tick says when there is nothing to run one.** While the\nmarker stands every other release path declines by design, so on a machine whose background service\nis down the copy stays held by a round that is over, with nobody told. `nxc withdraw` now carries\nthe `service_not_running` warning `send`, `reply` and `tick` already carry (it does not change the\nexit code), and both park lines name `nxc tick --thread ` — the command that does the park, or\nthe unparked hand-off, by hand.\n\n**A host whose worker cannot stop a session is refused before anything changes** — the whole call,\nby name (`this host cannot stop a running session; nothing was withdrawn`), so a withdrawal never\nleaves a discharged thread beside a session that goes on writing. A stop the worker supports and\nthen could not deliver is a `session_not_stopped` warning on the receipt naming the session; the\nthread is discharged all the same and the command exits non-zero. A session that had **already\nended** by the time the stop was sent — the ordinary race for a round that was about to finish — is\nnot a failure: the receipt carries a note saying what was found, `warnings` stays empty and the\ncommand exits 0, because that is the state the call was asked for. And a host whose worker cannot\nanswer the liveness question at all is now refused **by name** — `ErrorKind::Validation`, where a\nworker unable to answer liveness with an unended session in the area used to get `not_found` — when\nthe area holds sessions that never reported an end, instead of being told there is nothing to\nwithdraw: that answer was a statement about processes nobody had looked at. Apart from who may call\nit and the chain it interrupts (both below and in the entry on who may withdraw), the queued case\nbehaves as before; the refusal for a thread with nothing under it (a worker that DOES answer\nliveness, or an area with no unended session either way) is still `not_found` and now reads\n*nothing under thread … is waiting or running*.\n\n**And a withdrawn holder that will not leave now has a visible reason.** A stopped session can\nsurvive its `SIGTERM` — a wedged sidecar, a custom worker whose stop returns `Ok` and changes\nnothing, a reused pid — and until now nothing on any surface said so: `nxc status` had no field for\nit, and \"stop it by hand\" printed only when the very first `stop_session` call had itself errored,\nnever when the request landed and was simply ignored. `nxc status` now marks the holding operation\n`WITHDRAWN` (beside `PARK REFUSED` and `holds working tree`) and names who withdrew it, when, and\nevery session still pinning the claim right now; `--json` carries the same fact as a new\n`withdrawn` field, omitted when the operation's round was never withdrawn. And after five liveness\ncadences (five minutes) with a session the withdrawal stopped still there, `nxc tick` sends it **one\nfurther `SIGTERM`** — through the worker's own stop, behind the same identity check as the first —\nand names it: the session, its pid, the pid file that names it, and when that further signal went\n(nxf 6j6v.27b9). Nothing more is ever sent, and never `SIGKILL`: the sidecar's whole teardown\nhappens on `SIGTERM`, and ending the process past that is left to a person. Against the shipped\nsidecar, which is already stopping when it arrives, that further signal changes nothing; it helps a\nhost's own worker whose first stop was lost or ignored. A session a follow-up put back in motion\nbefore the park is somebody's work again, and is neither signalled nor named.\n\n**Facade contract.** `WithdrawReceipt` gains `stopped: Vec` (`{thread, role,\nsession, already_gone?}` — the last omitted unless the session had already ended, in which case it\ncarries the sentence saying so; the array omitted from `--json` when empty), `will_park: bool`,\n`cannot_park: Option` (omitted from `--json` when absent; `will_park: false` with no\n`cannot_park` is a round that holds nothing, `cannot_park: Some(_)` is work in the checkout this\nhost can never park) and `warnings: Vec` (omitted when empty), and is now\n`#[non_exhaustive]` — both the new fields\nand the marker break a struct literal written outside the crate, which is why this is marked\nbreaking; reading the receipt is unaffected. `ParkOccasion` gains `Withdrawn` (`withdrawn` in\n`--json` and on `park_refused.occasion`); `ConsequenceClass` gains `SessionNotStopped`\n(`session_not_stopped`) and `WithdrawnHolderWedged` (`withdrawn_holder_wedged`); both enums were\nalready `#[non_exhaustive]`. `ChatStore` gains `note_withdrawn_holder`, `withdrawn_holder` and\n`all_withdrawn_holders` over the new device-local table `withdrawn_holders`, and\n`note_withdrawn_sessions`, `withdrawn_session` (a new `WithdrawnSession {stopped_at, resignalled_at,\nresignal_refused}`), `claim_withdrawn_session_resignal`, `note_withdrawn_session_resignal_refused`\nand `forget_withdrawn_session` over its sibling `withdrawn_sessions` — the sessions a withdrawal\nstopped, which are the only ones the tick's further `SIGTERM` may reach.\n`StatusOperation` — also `#[non_exhaustive]` already — gains `withdrawn: Option`\n(the new type, `#[non_exhaustive]`: `{withdrawn_at, by, pinned_by: Vec}`), a purely\nadditive field. `Engine::withdraw`'s signature is unchanged.", - "de": "### Neu\n- **Ein Chat-Worker kann jetzt eine Sitzung beenden, die er gestartet hat.** Bisher konnte die Engine\neinen Worker nur zu einer Sitzung befragen — läuft sie, wo läuft sie, lässt sie sich fortsetzen —,\nund das Einzige, was eine Sitzung beendete, war die Sitzung selbst. Genau dieses Stück fehlte dem\nZurückziehen einer *laufenden* Runde: Wer die Arbeit zurücknimmt, während ihre Sitzung weiter in die\nArbeitskopie schreibt, hat das Geparkte schon überholt, bevor die Quittung gedruckt ist. Der\nmitgelieferte Sidecar-Worker beantwortet die neue Frage mit Ja und beendet eine Sitzung, indem er\nihrem Prozess `SIGTERM` schickt — nie `SIGKILL`, nie die ganze Prozessgruppe —, und zwar an die\nPID aus derselben Datei `.nxs/agent-logs/.pid`, die auch die Lebendigkeitsprüfung liest;\nder signalisierte Prozess ist damit konstruktionsbedingt derjenige, der als laufend gemeldet wird.\nEine fehlende PID-Datei, eine Datei ohne PID oder ein Anspruch, der nicht belegen kann, welchen\nProzess er meint, wird benannt abgelehnt, statt ins Leere zu signalisieren; eine Sitzung, deren\nProzess bereits weg ist, ist gar kein Fehlschlag, sondern genau der Zustand, um den es beim Stoppen\nging — und wird auch so gemeldet. Die Anforderung wird *zugestellt*, nicht abgewartet: Wer eine\nSitzung beendet, wartet, bis die Lebendigkeitsprüfung auf „nicht laufend\" wechselt, bevor er den\nBaum anfasst (nxf 6j6v.b9nf).\n\n**Ein Sitzungsanspruch hält jetzt fest, WELCHER Prozess ihn hält, nicht nur dessen Nummer.**\nAnsprüche werden bewusst nie entfernt; eine abgestürzte Sitzung — oder ein Neustart der Maschine —\nhinterlässt also eine Datei mit einer PID, die das Betriebssystem frei an etwas anderes vergeben\ndarf: einen Editor, einen Entwicklungsserver, ein weiteres `nxc` desselben Benutzers. `kill(pid, 0)`\nsagt über diesen Fremden *ja*, was hinnehmbar war, solange die Antwort nur eine Weckung verzögerte,\nund nicht mehr hinnehmbar ist, seit das Zurücknehmen einer Runde dem Prozess ein Signal schickt.\nDeshalb enthält `.nxs/agent-logs/.pid` in der ersten Zeile die PID und in der zweiten den\nZeitpunkt, zu dem dieser Prozess gestartet ist, und jeder Leser vergleicht diesen Zeitpunkt mit dem,\nwas das Betriebssystem zur gefundenen PID sagt — dieselbe Identitätsprüfung, die der\nHintergrunddienst für seinen eigenen Herzschlag längst macht, geteilt statt kopiert. Ein Anspruch,\ndessen Zeitpunkt nicht passt, benennt eine Sitzung, die **beendet** ist: Er gilt als nicht laufend,\nund es wird nichts signalisiert. Ein Anspruch aus der Zeit davor hält keinen Zeitpunkt fest, ist von\neiner wiederverwendeten PID nicht zu unterscheiden und wird deshalb benannt abgelehnt statt\nsignalisiert — die Lebendigkeitsprüfungen glauben ihm weiterhin, denn dort kostet ein Irrtum einen\nverzögerten Zug, bei einem Signal kostet er den Prozess eines Fremden.\n\n**Und die Lebendigkeitsprüfungen lesen eine PID-Datei jetzt so wie der Stopp.** Eine `.pid`-Datei\nmit `0` — oder mit einer Zahl, die das Betriebssystem als Prozess*gruppe* statt als Prozess liest —\ngilt überall als *kein laufender Prozess*: für die einzelne Sitzung wie für die Liste aller\nSitzungen, die der Hintergrunddienst abfragt. Beide lasen bisher eine bloße Zahl und fragten\n`kill(pid, 0)` danach, was bei `0` eine Frage nach der eigenen Prozessgruppe des Fragenden ist und\nmit *ja* antwortet, solange irgendetwas von diesem Arbeitsbereich lebt — eine als laufend gemeldete\nSitzung, die niemand jemals beenden konnte.\n\n**Der Sidecar baut bei `SIGTERM` geordnet ab, statt zu sterben.** Er bricht den laufenden Zug über\nden Abbruchmechanismus des SDK ab und führt dann seinen Abbau in der gewohnten Reihenfolge aus —\nbindet die Laufzeitsitzung, damit ein späteres Fortsetzen das Gespräch findet, schreibt alles, was\nder abgebrochene Zug erzeugt hat, ins Transkript und meldet `nxc session ended`. Genau ein Schritt\nentfällt auf das Signal hin: Die Sitzung wird **nicht ans Antworten erinnert** — eine Erinnerung\nsagt einer Sitzung, dass sie die Antwortregel gebrochen hat, und dieser wurde gesagt, sie solle\naufhören; der Modellaufruf dafür wäre verschwendet.\n\n**Ob in ihrem Namen etwas gepostet wird, entscheidet das REGISTER, nicht das Signal.** Ein `SIGTERM`\nist nicht nur eine Rücknahme: Er ist auch ein Systemabschalten, ein Logout, ein `docker stop`, ein\n`kill ` von Hand. Eine Rücknahme entlastet den Faden, bevor sie signalisiert — der Abbau liest\nalso einen Faden, der nichts mehr schuldet, und sagt nichts, was richtig ist: Eine Ersatzantwort\nspräche dort als der Agent in eine gerade zurückgenommene Runde hinein. Jeder andere `SIGTERM`\nlässt den Faden weiter schuldend zurück, und der Abbau postet das übliche\n`nxc reply --thread --if-unanswered` für ihn; ob dieser Schreibvorgang landet, entscheidet\nweiterhin das Tor der Engine. Ohne das machte der Handler aus jedem fremden `SIGTERM` — bisher ein\nharter Tod, den die Kehrschleife in etwa einer Minute auflöst — ein ordentliches, stummes Ende, weil\nder Abbau jetzt das Sitzungsende meldet: die Schuld ungeklärt, der wartende Aufrufer ungeweckt und\ndie Arbeitskopie bis zur Zwei-Stunden-Grenze gehalten.\n\nEin Stopp ist weder ein beendeter noch ein gescheiterter Zug, deshalb endet das Log mit\n`sidecar stopped:` statt `sidecar done:`, und der Prozess beendet sich mit **143** (128 + SIGTERM,\ndie Zahl, die eine Shell für einen am Signal gestorbenen Prozess meldet — beibehalten für einen, der\nes abgefangen hat). Ein `SIGTERM` während der Erinnerungsrunde beendet diese Runde auf dieselbe\nWeise.\n\n**Facade-Kontrakt.** `Worker` bekommt zwei Methoden mit Vorgabe: `stops_sessions(&self) -> bool`\n(vorgegeben `false` — ein Host, der keine Sitzung beenden kann, wird benannt abgelehnt statt\ngeglaubt) und `stop_session(&self, internal_session: &str) -> Result`\n(vorgegeben eine benannte Ablehnung). Eine bestehende Implementierung behält genau das Verhalten,\ndas sie hatte; ein Host, dessen Laufzeit eigene Sitzungen beenden kann, überschreibt beide. Der\nWrapper von `WorkerConfig::Custom` leitet beide weiter. Das neue Enum `SessionStop`\n(`#[non_exhaustive]`) unterscheidet einen zugestellten Stopp von einer bereits beendeten Sitzung:\n`Requested` ist das gesendete Signal, `NothingToStop(String)` der gewünschte Zustand samt dem Satz,\nder ihn gefunden hat — ein Aufrufer schreibt den auf seine Quittung, nicht in eine Warnung. `worker`\nbekommt außerdem `session_claim_for(pid) -> String`, die eine Schreibweise des Dateiinhalts eines\nAnspruchs, damit alles, was einen Prozess für eine Sitzung einsetzt, die Form schreibt, die die\nLeser erwarten. Nichts wurde entfernt.\n- **`nxc status` nennt jetzt die noch nicht wieder aufgenommene geparkte Arbeit eines Vorgangs.** Seit\nnxf 6j6v.de9s konnte eine unbeantwortete Eskalation auf einen Zweig geparkt und die Arbeitskopie\nweitergegeben werden — aber der einzige Weg, diesen Zweig wiederzufinden, war, ihn schon zu kennen.\nJetzt trägt die eigene Zeile des Vorgangs auf `nxc status` eine Zeile pro noch nicht\nzurückgeholtem Park: den Zweig, die ersten sieben Zeichen des Commits und seit wann die Kopie\nweitergegeben ist (plus einen Hinweis, wenn der Park nichts zu committen fand). `--json` trägt\ndieselben Zeilen als `parked` auf dem Vorgang, und ein Arbeitsbereich, der noch nie geparkt hat,\nbleibt beim Feld still — kein `\"parked\"`-Schlüssel überhaupt.\n\nFür einbettende Anwendungen: `StatusOperation` (aus `Engine::status`) bekommt ein Feld `parked`. Es\nwar schon `#[non_exhaustive]`, also ändert sich für Lesende nichts.\n- **`nxc withdraw` nimmt eine bereits laufende Runde zurück und parkt ihre Arbeit.** Bisher konnte das\nVerb nur eine Beauftragung zurücknehmen, die noch auf die Arbeitskopie wartete; eine Runde, deren\nSitzung gestartet war, wurde abgelehnt, und der einzige Weg, sie anzuhalten, war, ihren Prozess von\nHand zu suchen — wonach nichts sicherte, was sie in der Arbeitskopie hinterlassen hatte, und der\nnächste Halter darin startete. Jetzt entlastet `nxc withdraw --thread ` auf einer laufenden\nRunde — durch den Menschen, der sie abgeschickt hat, siehe den Eintrag dazu, wer zurücknehmen darf —\nden Faden (das Register sagt, die Runde ist vorbei), bittet den Chat-Worker, die Sitzung\nanzuhalten (der mitgelieferte Sidecar erhält `SIGTERM`, bricht seinen Zug ab, sichert sein\nTranskript und meldet sein Ende), und vermerkt — wenn die Runde die Arbeitskopie dieses Workspace\nhält —, dass ihre Arbeit zu parken ist. Der Hintergrund-Tick committet dann, sobald in diesem\nVorgang nichts mehr läuft, alles Uncommittete auf einen Zweig und gibt die Kopie an den weiter, der\nwartet; wartet niemand, wird die Kopie einfach frei. **Nichts wird je zurückgerollt.** `nxc status` führt den Zweig\nunter dem Vorgang, bis jemand ihn wieder abholt, und der Weg zurück ist die nächste Beauftragung in\ndenselben Faden: auf einem direkten Persona-Faden setzt `nxc reply --thread ` die angehaltene\nSitzung mit ihrem eigenen Transkript auf dem Parkzweig fort; bei einer Kanalrunde startet eine\nFolgenachricht in den Kanalfaden dort den nächsten Durchgang. Ein frisches `send` eröffnet einen\nneuen Anspruch und findet sie nicht (nxf 6j6v.b9nf).\n\n**Zwischen Stopp und Parken behält der zurückgenommene Halter die Kopie.** Die Entlastung lässt den\nVorgang nichts mehr schulden — genau der Zustand, in dem die gewöhnliche Freigabe am Ende eines\nVorgangs greift —, und der Abbau der angehaltenen Sitzung oder eine späte Antwort in den Faden hätte\ndie Kopie mit der Arbeit noch im Baum weitergegeben. Solange die Markierung steht, unterbleibt jede\nsolche Freigabe; der Anlass „zurückgenommen\" des Ticks ist der eine Weg, auf dem ein solcher Halter\nloslässt, und er handelt erst, wenn nichts im Anspruchsbereich mehr einen lebenden Prozess hat —\nsolange einer da ist, sieht er einmal pro Minute nach. Die Ablehnungsregel gilt unverändert: Ein\nParken, das aus einem behebbaren Grund abgelehnt wird, behält die Kopie mit `PARK REFUSED` in\n`nxc status` und wird erneut versucht; eines, das nie durchgehen kann, gibt die Kopie ungeparkt mit\nder benannten Warnung `work_handed_on_unparked` weiter.\n\n**Eine Rücknahme unterbricht die Kette unter dem Faden, den sie nennt** (nxf 6j6v.s2cj). Jeder\nFaden zwischen dem Zurückgenommenen und diesem Faden, der noch wartet — ein Kanalfaden, der das\nErgebnis seiner Runde schuldet, ein Persona-Faden, der auf die von ihm beauftragte Runde wartet —,\nwird ebenfalls entlastet, mit einer Nachricht, die sagt, dass die Kette unterbrochen wurde. Ein\nKanal beauftragt also nichts mehr, sobald einer seiner Schritte zurückgenommen ist: weder den\nnächsten Schritt noch den zurückgenommenen erneut, an einem `flow: sequential`-Kanal genauso wie an\neinem mit `steps:`, vor dem Parken und danach; und nichts wird zusammengeführt. Die Antworten, die\nSchritte schon gegeben hatten, bleiben in ihren Fäden, und die Arbeit kommt zurück, wenn der Mensch,\nder sie zurückgenommen hat, den ersten Faden des Vorgangs erneut beauftragt — bei einer Runde, die\nein Agent beauftragt hat, trägt die Entlastung dieses Fadens die Sitzung des Agenten, sodass die\nFolgenachricht sie fortsetzt. (Eine Runde, die eine Rücknahme nicht\nentlastet hat — eine, in der eine wartende Beauftragung startete, während die Rücknahme lief —, hält\nweiter die Markierung: Ihr abgeschlossener Satz wird weder fortgeschaltet noch geroutet noch\nzusammengeführt, bis die Kopie weitergegeben ist.)\n\n**Was die Quittung über einen Zweig verspricht, ist jetzt das, was das Parken auch liefern kann.**\n`will_park` hieß bisher nur „diese Runde hält die Arbeitskopie\" und stand auf `true` auch auf Hosts,\nauf denen ein Parken nie gelingen kann — ein Worker, der keine Arbeitskopie benennt (die Vorgabe\njedes Workers außer dem mitgelieferten Sidecar), ein Workspace, der kein Git-Repository ist, ein\nVorgang ohne vermerkte Basis. Jetzt werden diese drei rein lesenden Vorbedingungen mitgefragt, und\nwenn die Runde die Kopie zwar hält, aber nicht geparkt werden kann, trägt die Quittung\n`cannot_park` mit dem Grund: Die Kopie wird trotzdem weitergegeben, sobald im Vorgang nichts mehr\nläuft — ungeparkt und mit der Warnung `work_handed_on_unparked`, die das sagt. `nxc withdraw` gibt\ndiesen dritten Fall in eigenen Worten aus statt „nothing of it is in this workspace's working copy\",\nund die in den zurückgenommenen Faden geschriebene Nachricht sagt dasselbe Wahre.\n\n**Und eine Rücknahme, die dem Tick Arbeit hinterlässt, sagt, wenn nichts einen Tick ausführt.**\nSolange die Markierung steht, unterbleibt jede andere Freigabe planmäßig — auf einer Maschine mit\ntotem Hintergrunddienst bleibt die Kopie also bei einer Runde, die vorbei ist, ohne dass es jemand\nerfährt. `nxc withdraw` trägt jetzt die Warnung `service_not_running`, die `send`, `reply` und\n`tick` schon tragen (sie ändert den Exit-Code nicht), und beide Park-Zeilen nennen\n`nxc tick --thread ` — den Befehl, der das Parken beziehungsweise die ungeparkte Weitergabe von\nHand erledigt.\n\n**Ein Host, dessen Worker keine Sitzung anhalten kann, wird abgelehnt, bevor sich etwas ändert** —\nder ganze Aufruf, benannt (`this host cannot stop a running session; nothing was withdrawn`), damit\neine Rücknahme nie einen entlasteten Faden neben einer weiterschreibenden Sitzung hinterlässt. Ein\nStopp, den der Worker beherrscht und dann nicht zustellen konnte, ist eine Warnung\n`session_not_stopped` auf der Quittung, die die Sitzung nennt; der Faden ist trotzdem entlastet, und\nder Befehl endet mit einem Fehlercode. Eine Sitzung, die zum Zeitpunkt des Stopps **bereits beendet**\nwar — das gewöhnliche Rennen bei einer Runde, die ohnehin gerade fertig wurde —, ist kein\nFehlschlag: Die Quittung trägt einen Vermerk, was vorgefunden wurde, `warnings` bleibt leer, und der\nBefehl endet mit 0, denn das ist genau der Zustand, um den es ging. Und ein Host, dessen Worker die\nLebendigkeitsfrage überhaupt nicht beantworten kann, wird jetzt **benannt** abgelehnt —\n`ErrorKind::Validation`, wo ein Worker, der die Lebendigkeit nicht beantworten kann, bei einer nicht\ngemeldeten Sitzung im Bereich bisher `not_found` bekam —, wenn im Bereich Sitzungen ohne gemeldetes\nEnde liegen, statt zu hören, es gebe nichts zurückzunehmen: Diese Antwort war eine Aussage über\nProzesse, die niemand angesehen hatte. Abgesehen davon, wer zurücknehmen darf, und von der Kette,\ndie eine Rücknahme unterbricht (beides unten und im Eintrag dazu, wer zurücknehmen darf), verhält\nsich der wartende Fall wie bisher; die Ablehnung für einen Faden, unter dem nichts liegt (ein\nWorker, der die Lebendigkeit beantworten kann, oder ein Bereich ganz ohne nicht gemeldete Sitzung),\nbleibt `not_found` und lautet jetzt *nothing under thread … is waiting or running*.\n\n**Und ein Halter, der die Rücknahme nicht loslässt, hat jetzt einen sichtbaren Grund.** Eine\nangehaltene Sitzung kann ihr `SIGTERM` überleben — ein hängender Sidecar, ein eigener Worker, dessen\nStopp `Ok` meldet und nichts ändert, eine wiederverwendete PID —, und bisher sagte das nichts auf\nkeiner Oberfläche: `nxc status` hatte dafür kein Feld, und „von Hand stoppen\" stand nur dann, wenn\nschon der erste `stop_session`-Aufruf selbst fehlgeschlagen war, nie wenn die Anforderung ankam und\neinfach ignoriert wurde. `nxc status` markiert den haltenden Vorgang jetzt mit `WITHDRAWN` (neben\n`PARK REFUSED` und `holds working tree`) und nennt, wer zurückgenommen hat, wann, und jede Sitzung,\ndie den Anspruch gerade noch hält; `--json` trägt dieselbe Tatsache als neues Feld `withdrawn`,\nweggelassen, wenn die Runde des Vorgangs nie zurückgenommen wurde. Und nach fünf\nLebendigkeits-Takten (fünf Minuten), in denen eine von der Rücknahme gestoppte Sitzung noch da ist,\nschickt `nxc tick` ihr **ein weiteres `SIGTERM`** — über den eigenen Stopp des Workers, hinter\nderselben Identitätsprüfung wie das erste — und nennt sie: die Sitzung, ihre PID, die PID-Datei, die\nsie nennt, und wann dieses weitere Signal ging (nxf 6j6v.27b9). Mehr wird nie geschickt, und nie\n`SIGKILL`: Der ganze Abbau des Sidecars geschieht auf `SIGTERM`, und den Prozess darüber hinaus zu\nbeenden, bleibt einem Menschen überlassen. Beim mitgelieferten Sidecar, der beim Eintreffen schon\nanhält, ändert dieses weitere Signal nichts; es hilft dem eigenen Worker eines Hosts, dessen erster\nStopp verloren ging oder ignoriert wurde. Eine Sitzung, die eine Folgenachricht vor dem Parken\nwieder in Gang gesetzt hat, ist wieder jemandes Arbeit und wird weder signalisiert noch genannt.\n\n**Facade-Kontrakt.** `WithdrawReceipt` bekommt `stopped: Vec` (`{thread, role,\nsession, already_gone?}` — das letzte Feld nur, wenn die Sitzung bereits beendet war, dann mit dem\nSatz, der das sagt; das Array in `--json` weggelassen, wenn leer), `will_park: bool`,\n`cannot_park: Option` (in `--json` weggelassen, wenn nicht gesetzt; `will_park: false` ohne\n`cannot_park` ist eine Runde, die nichts hält, `cannot_park: Some(_)` ist Arbeit in der\nArbeitskopie, die dieser Host nie parken kann) und `warnings: Vec` (weggelassen,\nwenn leer) und ist jetzt `#[non_exhaustive]` — sowohl die\nneuen Felder als auch die Markierung brechen ein außerhalb des Crates geschriebenes Struct-Literal,\nweshalb dies als breaking markiert ist; das Lesen der Quittung ist nicht betroffen. `ParkOccasion`\nbekommt `Withdrawn` (`withdrawn` in `--json` und an `park_refused.occasion`); `ConsequenceClass`\nbekommt `SessionNotStopped` (`session_not_stopped`) und `WithdrawnHolderWedged`\n(`withdrawn_holder_wedged`); beide Enums waren bereits `#[non_exhaustive]`.\n`ChatStore` bekommt `note_withdrawn_holder`, `withdrawn_holder` und `all_withdrawn_holders` über die\nneue gerätelokale Tabelle `withdrawn_holders`, und `note_withdrawn_sessions`, `withdrawn_session`\n(ein neues `WithdrawnSession {stopped_at, resignalled_at, resignal_refused}`),\n`claim_withdrawn_session_resignal`, `note_withdrawn_session_resignal_refused` und\n`forget_withdrawn_session` über ihre Schwester `withdrawn_sessions` — die Sitzungen, die eine\nRücknahme gestoppt hat, und damit die einzigen, die das weitere `SIGTERM` des Ticks erreichen darf. `StatusOperation` — ebenfalls schon\n`#[non_exhaustive]` — bekommt `withdrawn: Option` (der neue Typ, auch\n`#[non_exhaustive]`: `{withdrawn_at, by, pinned_by: Vec}`), ein rein additives Feld. Die\nSignatur von `Engine::withdraw` ist unverändert.\n\n### Geändert\n- **Ein abgelehntes Parken folgt jetzt einer Regel: halten und wiederholen, oder ungeparkt\nweitergeben.** Muss eine unbeantwortete Eskalation oder ein an einer Verfügbarkeitsgrenze\nangehaltener Vorgang die Arbeitskopie abgeben, wird seine Arbeit zuerst geparkt — und das Parken\nkann abgelehnt werden. Bisher hielt jede Ablehnung die Arbeitskopie fest und nannte `nxc release` als\nAusweg. Entscheidung des Owners vom 17.09.2026 (nxf 6j6v.8bv9, 6j6v.b9nf): Eine Ablehnung, die\nvorübergehen kann (ein laufender Merge, Rebase, Cherry-Pick, Revert oder Bisect, oder ein\nfehlgeschlagener git-Befehl), behält die Arbeitskopie, wird als `work_not_parked` mit dem, was zu\nbeenden ist, gemeldet, erscheint in `nxc status` als `PARK REFUSED` mit einer Zeile\n`park refused (): — retrying since ` und wird vom Hintergrunddienst\nbei jedem Tick erneut versucht — und nur so lange, wie der Anlass dauert, der die Arbeitskopie\nwollte: Ist die Eskalation beantwortet, der angehaltene Vorgang aufgenommen oder wartet niemand mehr,\nverschwindet die Markierung beim nächsten Tick, denn dann versucht niemand dieses Parken. Eine\nAblehnung, die nicht vorübergehen kann (die Laufzeit nennt keine Arbeitskopie, das Verzeichnis ist\nkein git-Repository, es wurde nie ein Basiszweig vermerkt), gibt die Arbeitskopie UNGEPARKT weiter\nund sagt das mit der neuen Warnung `work_handed_on_unparked` — die nicht committeten Dateien des\nVorgangs bleiben für den nächsten im Baum. Ein an einer Verfügbarkeitsgrenze angehaltener Vorgang\nwird jetzt auch vom Tick gesichert, solange jemand wartet, nicht nur von `nxc resume`. Kein\nAblehnungstext nennt mehr `nxc release`. Und ein Vorgang mit nicht zurückgeholter geparkter Arbeit\noder einem abgelehnten Parken bleibt jetzt in der Standardliste von `nxc status`, auch wenn sonst\nnichts an ihm offen ist.\n\n**Facade-Kontrakt.** `ParkRefusal` bekommt die Variante `NoBaseRecorded` (die Ablehnung, die bisher\nein eingebetteter Satz war) sowie `is_permanent()` und `kind()`; das Enum ist nicht\n`#[non_exhaustive]`, ein erschöpfendes `match` außerhalb des Crates muss den neuen Zweig also nennen.\n`TickReceipt` bekommt das öffentliche Feld `handed_on: Option` (mit dem neuen\n`ParkOccasion`), ein Struct-Literal außerhalb des Crates muss es also nennen. `ConsequenceClass`\nbekommt `WorkHandedOnUnparked` (bereits `#[non_exhaustive]`). `StatusOperation` (aus\n`Engine::status`) bekommt `park_refused: Option` (bereits `#[non_exhaustive]`),\nund `ChatStore` bekommt `note_park_refusal`, `clear_park_refusal`, `park_refusal` und\n`all_park_refusals` über eine neue gerätelokale Tabelle; `ParkRefusalNote` nennt den Anlass\n(`occasion`), der das Parken abgelehnt hat (`stranded_escalation`, `availability_boundary`), auch in\n`--json`. Die verhaltensseitige Hälfte trägt keine Signatur: `tick` und `Engine::resume_interrupted`\ngeben die Arbeitskopie für einen Host, dessen Worker keine Arbeitskopie nennt (die Vorgabe jedes\nWorkers außer dem Sidecar), jetzt weiter, wo sie sie bisher festhielten.\n- **`nxs` beschreibt sich so, wie es der README tut.** `nxs --help` nennt jetzt „nexus-flow — the\nboard (nxf), the memory (nxm) and the channel (nxc) your agents work from, in one binary\" statt „nxs\nplatform umbrella CLI\", und die Willkommensbox von `nxs init` beginnt mit der Tagline des README.\nDort steht auch nicht mehr, nexus-chat komme „bald\" — chat wird im Auswahlmenü direkt darunter\nangeboten. Die Zeile zu chat im Auswahlmenü, in der Init-Zusammenfassung und im `advertisement` von\n`nxf init --json` lautet jetzt „the channel — messages between you and your agents, on the record\";\nauch die Anleitungen nennen chat den Kanal, und weder sie noch `nxs sync --help` nennen `nxs` noch\neine Plattform.\n- **Ein Park auf einem sauberen Arbeitsverzeichnis legt keinen Zweig mehr an.** Entscheidung des\nOwners vom 17.09.2026 (nxf 6j6v.7hqc): Ein Park legt nur dann einen Zweig an, wenn es Arbeit auf\ndem Basiszweig des Vorgangs gibt, denn die Engine löscht Park-Zweige nie — ein leerer würde für\nimmer als Unrat liegen bleiben, den niemand entfernt. Steht HEAD auf dem vermerkten Basiszweig des\nVorgangs und ist der Baum sauber, meldet der Park jetzt, dass der Vorgang auf dem Basiszweig selbst\ngeparkt wurde, mit `created_branch: false` und `committed: false`, statt `nxs/park/...` anzulegen.\nEin schmutziger Baum auf dem Basiszweig, ein Vorgang, der schon einen eigenen Zweig hat, und ein\nlosgelöster HEAD (sauber oder nicht — seine Position muss irgendwie erreichbar bleiben) bleiben\nunverändert.\n- **`nxc withdraw` ist jetzt das Verb eines Menschen: Nur wer die erste Beauftragung eines Vorgangs\nabgeschickt hat, darf sie zurücknehmen, und nur, indem er diesen Faden nennt.** Bisher lautete die\nHürde „darf den Faden lesen\" — in einem öffentlichen Kanal jedes Handle, das eigene `nxc` eines\nAgenten eingeschlossen —, und `nxc prime` brachte das Verb jeder startenden Sitzung bei. Seit das\nVerb eine laufende Runde anhalten kann, ist die Frage, wer es aufrufen darf, und die Antwort ist ein\nMensch oder ein Assistent, den ein Mensch gestartet hat. Drei Aufrufer werden jetzt abgelehnt, bevor\nsich etwas ändert, jeder benannt: **eine Sitzung, die dieser Workspace gestartet hat**\n(`forbidden`) — gleich, was sie nennt; die Ablehnung nennt die Sitzung und die Rolle, für die sie\ngestartet wurde, und verweist sie auf `nxc reply --thread --escalate -`, das\nnach oben geht, zu dem, der sie beauftragt hat; **wer den Vorgang nicht eröffnet hat**\n(`forbidden`), mit dem Namen dessen, der es war; und **ein Faden unterhalb des ersten** — ein\nSchritt einer Kanalrunde, eine Unterrunde, die ein Agent eröffnet hat — (`validation`), mit dem\nFaden, der stattdessen zu nennen ist. Die Ablehnung einer Sitzung stützt sich auf das, was die\nEngine belegen kann — die Sitzungstabelle verzeichnet jede Sitzung, die sie gestartet hat —, und\nbehauptet nicht mehr: Ein Prozess, der seine Sitzung verbirgt, fällt nicht unter diese Regel und\ntrifft dann auf die beiden anderen (nxf 6j6v.ezbr).\n\n**`nxc prime` bietet das Verb nicht mehr an.** Die `withdraw`-Zeile ist aus *How a conversation\nmoves* verschwunden, der Eintrag aus `commands` in `--json` (jetzt sieben Einträge), und\n`when_stuck` verweist eine Sitzung mit gehaltener Arbeitskopie stattdessen auf die Eskalation. Die\n`pm`-Persona des mitgelieferten Beispielteams sagt dasselbe.\n\n**Facade-Kontrakt.** `Engine::withdraw` / `orchestration::withdraw` lehnen Aufrufer ab, die sie\nbisher angenommen haben — deshalb als breaking markiert, obwohl sich keine Signatur bewegt hat: ein\n`Caller`, dessen `session` dieser Workspace erzeugt hat (`ErrorKind::Forbidden`), ein Aufrufer, der\nnicht Eröffner des Vorgangs ist (`Forbidden`; wer den Faden nicht einmal lesen darf, bekommt weiter\nzuerst die Ablehnung des Lese-Tors), und ein Faden, der nicht die Wurzel seines Vorgangs ist\n(`Validation`). Ein Host, der für seinen angemeldeten Nutzer handelt, übergibt keine Sitzung und\nnimmt zurück, was dieser Nutzer abgeschickt hat, genau wie bisher; ein Host, der einen aus `status`\ngelesenen Mitglieds-Faden zurücknahm, nennt jetzt die Wurzel des Vorgangs. `PRIME_COMMANDS` verliert\nden Eintrag `withdraw`, und die Texte von `PRIME_HOW_A_CONVERSATION_MOVES` und `PRIME_WHEN_STUCK`\nhaben sich entsprechend geändert.\n\n### Behoben\n- **Eine Beauftragung wartet nicht mehr zwei Stunden hinter einem Vorgang, der längst tot ist.** Die\nArbeitskopie-Lease hat eine Rückfallgrenze von zwei Stunden, und bisher war diese Grenze das Einzige,\nwas hinsah: Starb die haltende Kette hart — Absturz, Kill, schlafender Rechner —, gab niemand die\nKopie frei, niemand sah nach, und wer dahinter anstand, stand still, bis die Lease ablief. Jetzt\nfragt der Hintergrund-Tick einmal pro Minute, solange jemand wartet, ob der Halter *nachweislich*\nfort ist, und nimmt die Arbeitskopie, sobald er es ist: Die Arbeit des toten Vorgangs wird auf einen\nZweig committet (samt nicht versionierter Dateien; was `.gitignore` ausschließt, bleibt im Baum), der\nBaum kehrt auf den Zweig zurück, auf dem der Vorgang begonnen hat, und die wartende Beauftragung\nstartet — in der Regel innerhalb einer Minute nach dem Tod statt bis zu zwei Stunden später\n(nxf 6j6v.xb24).\n\n**„Nachweislich\" sind fünf Bedingungen, und alle fünf müssen gelten**, denn eine Arbeitskopie zu\nübernehmen schreibt in sie hinein. Der Chat-Worker muss die Prozessfrage tatsächlich beantworten\nkönnen — eine Laufzeitumgebung, die nicht nachsehen kann, nimmt nie vorzeitig eine Arbeitskopie, für\neinen Host ohne Liveness-Prüfung ändert sich also nichts. Mindestens ein Thread des Vorgangs muss\nnoch eine Antwort schulden; ein Vorgang, dessen Sitzungen alle normal endeten und die Aufgabe\nzurückgegeben haben, ist nicht tot, sondern wartet auf einen Menschen, und für ihn gilt weiterhin die\nDreißig-Minuten-Regel. Kein Prozess des Vorgangs darf noch leben. Keine Sitzung eines schuldenden\nThreads darf ihr Ende gemeldet haben — ein gemeldetes Ende ist ein gelaufener Abbau und damit kein\nharter Tod. Und nichts im Vorgang darf an einer Verfügbarkeitsgrenze pausieren, die ihren eigenen Weg\nzurück hat. Was diese Prüfung kostet, wurde gemessen, bevor sie in jeden Tick kam — und zwar\nvollständig, von Anfang bis Ende: rund **zwei Millisekunden** für einen Vorgang aus zwanzig Threads\nmit zwanzig Sitzungen, auf einem Entwicklungsrechner, im langsameren der beiden Builds dieses\nProjekts.\n\nAlles Übrige an dieser Übergabe ist das Verhalten, das die anderen Anlässe schon hatten: `nxc tick`\nmeldet, wohin die Arbeit ging, `nxc status` zeigt den geparkten Zweig am Vorgang, bis er wieder\naufgenommen wird, und die Ablehnungsregel gilt unverändert — ein laufender Merge, Rebase,\nCherry-Pick, Revert oder Bisect oder ein fehlgeschlagener git-Befehl **behält die Arbeitskopie**, der\nVorgang wird mit `PARK REFUSED` markiert und das Parken bei jedem Tick erneut versucht, während eine\nAblehnung, die nie vorübergehen kann (keine Arbeitskopie, kein git-Repository, nie ein Basiszweig\nvermerkt), die Arbeitskopie ungeparkt weitergibt und das mit der benannten Warnung\n`work_handed_on_unparked` sagt.\n\n**Eine Änderung am Zeitverhalten, die auffallen kann.** Solange eine Beauftragung hinter einem\nAnspruch wartet, wird der Tick, der erneut nachsieht, jetzt eine Minute im Voraus geplant statt zur\neigenen Frist des Halters — genau dieses erneute Nachsehen lässt das Obige in einer Minute statt erst\nzur Frist eintreten. Eine Frist, die früher als eine Minute fällt, gewinnt weiterhin; nichts wird je\nnach hinten verschoben.\n\n**Facade-Kontrakt.** `ParkOccasion` bekommt die Variante `DiedInsideItsBound`\n(`died_inside_its_bound` in `--json` und an `StatusOperation::park_refused.occasion`), und\n`TickReceipt::handed_on` kann sie jetzt tragen; das Enum ist `#[non_exhaustive]`, ein erschöpfendes\n`match` außerhalb des Crates hatte also schon einen Auffangzweig. `ChatStore` bekommt\n`reclaim_a_dead_holders_working_tree_and_take_next`, den Compare-and-Swap-Zwilling von\n`reclaim_expired_working_tree_and_take_next` für eine Lease, die noch innerhalb ihrer Frist steht.\nNichts wurde entfernt, keine Signatur geändert.\n- **Ein Tick führt eine zurückgenommene Runde nicht mehr zusammen und weckt nicht mehr den Agenten,\nder sie beauftragt hat.** Wurde jede Beauftragung einer Kanalrunde zurückgenommen, wurde der\nKanalfaden als zurückgenommen entlastet — und die eigene Uhr des Kanals konnte später trotzdem auf\nihm feuern, einen abgeschlossenen Satz lesen und ihn ausliefern: Bei einer Runde, die ein Mensch\nbeauftragt hatte, lief das ins Leere, bei einer, die ein Agent beauftragt hatte (eine `pm`, die\nArbeit an einen Kanal geschickt hat), weckte es diesen Agenten mit der zurückgenommenen Runde als\nAntwort, und die Arbeit ging weiter. `nxc tick` behandelt einen durch eine Rücknahme entlasteten\nFaden jetzt als bereits erledigt, genau wie einen zusammengeführten (`reason: already_handled`), und\nnichts wird ausgeliefert (nxf 6j6v.s2cj).\n- **Ein Vorgang, der wortlos stirbt, verliert seine nicht committete Arbeit nicht mehr, wenn die\nWarteschlange seine Arbeitskopie übernimmt.** Läuft eine Arbeitskopie-Lease ab und hat kein Prozess\nin der Kette dieses Vorgangs mehr Leben, gibt der Hintergrund-Sweep die Arbeitskopie an den\nWartenden weiter — und tat das bisher mit den nicht committeten Dateien des toten Vorgangs noch im\nBaum, sodass der nächste Agent in der halbfertigen Arbeit eines anderen startete. Es war die letzte\ndurch eine Störung ausgelöste Übergabe, die nichts zuerst sicherte: Eine abgestürzte Kette hat weder\neskaliert noch eine Verfügbarkeitsgrenze angekündigt, also wurde keines der beiden bereits\nvorhandenen Parkverfahren je nach ihr gefragt (nxf 6j6v.8bv9). Jetzt wird ihre Arbeit auf einen Zweig\ncommittet (samt nicht versionierter Dateien; was `.gitignore` ausschließt, bleibt im Baum), die\nArbeitskopie kehrt auf den Zweig zurück, auf dem der Vorgang begonnen hat, und erst dann startet die\nwartende Beauftragung — `nxc tick` meldet das Parken im selben Block wie die Rückholung, und\n`nxc status` zeigt den geparkten Zweig am Vorgang, bis er wieder aufgenommen wird. Die Ablehnungsregel\ngilt hier genau wie bei den anderen beiden Anlässen: Ein laufender Merge, Rebase, Cherry-Pick, Revert\noder Bisect oder ein fehlgeschlagener git-Befehl **behält die Arbeitskopie** jetzt, der Vorgang wird\nmit `PARK REFUSED` markiert und das Parken bei jedem Tick erneut versucht — wo bisher derselbe Tick\nsagte, der Anspruch bleibe bestehen, und ihn zwei Schritte später zurückholte, was der eine\nWiderspruch ist, den diese Freigabe beseitigt. Eine Ablehnung, die nicht vorübergehen kann (keine\nArbeitskopie, kein git-Repository, nie ein Basiszweig vermerkt), gibt die Arbeitskopie ungeparkt\nweiter und sagt das mit der benannten Warnung `work_handed_on_unparked`, wie schon an anderer Stelle.\nEin Vorgang, dessen Frist abgelaufen ist, der aber noch einen lebenden Prozess hat, bleibt unberührt,\ngenau wie bisher.\n\n**Für die andere Tür zu demselben Moment gilt jetzt dasselbe.** Auch das eigene `nxc send` eines\nNeuankömmlings holt eine abgelaufene Lease zurück — so bekommt die Kopie, wer schon ansteht, und\nnicht, wer zufällig fragt — und es hatte alle Bedingungen des Sweeps außer dem Parken. Die Arbeit\neines toten Vorgangs wurde also weiterhin im Baum weitergereicht, sobald ein Anstoß vor einem Tick\ndort ankam. Jetzt wird zuerst geparkt, unter demselben Anlass und derselben Ablehnungsregel: Wird\ndas Parken aus einem Grund abgelehnt, der vorübergehen kann, bekommt **niemand** die Kopie — der\nNeuankömmling stellt sich hinter den Halter, genau wie bei noch laufendem Anspruch, und weiterbewegt\nwird sie vom erneuten Versuch des Dienstes. Das gilt **auch, wenn überhaupt niemand wartet** — der\ngewöhnlichste Fall überhaupt: Ein allein arbeitender Agent stürzt mit nicht committeter Arbeit ab,\ndie Frist läuft ab, und das nächste `nxc send` ist die Übergabe. Dann gibt es keinen Zug\nweiterzugeben, aber die Kopie verlässt trotzdem einen Vorgang, der verunglückt ist: Seine Arbeit\nkommt zuerst auf einen Zweig, und der Neuankömmling startet in einem sauberen Baum. Der\nHintergrund-Sweep deckt diesen Fall nicht ab und soll es auch nicht — bei leerer Warteschlange gibt\ner nichts weiter, also ist dort auch nichts zu sichern. Keine der beiden Park-Meldungen lässt ein Verb mit einem\nFehlercode enden: `work_not_parked` und `work_handed_on_unparked` sind Meldungen auf dem Beleg und in\n`--json`, und ein routinemäßiges Zurückholen in einem Arbeitsbereich, der kein git-Repository ist (wo\nkein Parken je gelingen kann), lässt den Aufruf, bei dem es geschieht, nicht mehr fehlschlagen.\n\n**Facade-Kontrakt.** Aus `drain_unarmed: Option` an `TriggerAdmission::Queued`\nwird `findings: Vec` — eine Liste, weil diese Übergabe jetzt neben dem nicht\ngestellten Wecker auch ein Parken melden kann; wer die Variante gebaut oder zerlegt hat, muss das\nneue Feld nennen, und `TriggerReceipt::warnings` trägt dieselben Werte wie bisher.\n`WorkingTreeSweep` (an `TickReceipt::working_tree`) bekommt zwei öffentliche\nFelder, `parked: Option` und `unparked: Option`; ein Struct-Literal außerhalb\ndes Crates muss sie also nennen, und in `--json` fehlen beide, solange sie nicht gesetzt sind.\n`ParkOccasion` bekommt die Variante `PastItsBound` (`past_its_bound` in `--json` und an\n`StatusOperation::park_refused.occasion`); das Enum ist `#[non_exhaustive]`, ein erschöpfendes\n`match` außerhalb des Crates hatte also schon einen Auffangzweig.\n\n### Entfernt\n- **`nxc release` und `Engine::release_working_tree` sind entfernt.** Das Verb gab die gehaltene\nArbeitskopie eines Workspace von Hand zurück, gestützt auf eine einzige Ablehnung: Es prüfte, ob\nkeine Sitzung der haltenden Kette noch einen lebenden Prozess hatte. Auf der Agentenoberfläche\nangeboten, konnte es damit einer laufenden Coding-Operation die Arbeitskopie wegnehmen, und bei\neinem Chat-Worker, der die Lebendigkeitsprüfung nie implementierte, war es ein bedingungsloses\nFreigeben ganz ohne Absicherung — genau die grobe Übersteuerung, die das Epic dieses Projekts über\nmanuelle Übersteuerungen ablehnte zu bauen, hier erreicht, weil die Ablehnung das Einzige war, was\nzwischen dem Verb und ihr stand.\n\nJede Übergabe parkt jetzt zuerst die Arbeit des Halters, sodass geschieht, was das Verb bisher von\nHand tat, von selbst. Eine Arbeitskopie, die eine bereits tote Kette hält, parkt und gibt der\nHintergrunddienst weiter, ohne dass jemand fragt — über ihrer Zwei-Stunden-Grenze, oder, da der\nHintergrunddienst jetzt eine tote Kette von einer nur stillen unterscheiden kann, schon davor. Eine\nArbeitskopie, die eine noch laufende Kette hält und die Sie — der Mensch, der sie gestartet hat —\nabsichtlich zurückwollen, nimmt man mit `nxc withdraw --thread ` / `Engine::withdraw`: Es entlastet die Runde, bittet ihre Sitzung, sich\nzu beenden, und parkt, sobald der Prozess weg ist, was sie unfertig hinterlassen hat — nie\nzurückgerollt —, sodass die nächste Beauftragung in denselben Faden die Arbeit zurückbringt.\n\n**Facade-Kontrakt.** `Engine::release_working_tree` und `orchestration::release_working_tree` sind\nentfernt, ebenso `ReleaseReceipt`. `ChatStore::release_working_tree_and_take_next` ist davon nicht\nbetroffen und bleibt: Es ist die Transaktion, über die jede Übergabe — der Antwortpfad, ein Parken,\nder Sweep — weiterhin läuft, um den Anspruch zurückzugeben und ihn dem Nächsten zu übergeben.\n\n### Facade-Kontrakt\n- `changed` · **Eine Beauftragung wartet nicht mehr zwei Stunden hinter einem Vorgang, der längst tot ist.** Die\nArbeitskopie-Lease hat eine Rückfallgrenze von zwei Stunden, und bisher war diese Grenze das Einzige,\nwas hinsah: Starb die haltende Kette hart — Absturz, Kill, schlafender Rechner —, gab niemand die\nKopie frei, niemand sah nach, und wer dahinter anstand, stand still, bis die Lease ablief. Jetzt\nfragt der Hintergrund-Tick einmal pro Minute, solange jemand wartet, ob der Halter *nachweislich*\nfort ist, und nimmt die Arbeitskopie, sobald er es ist: Die Arbeit des toten Vorgangs wird auf einen\nZweig committet (samt nicht versionierter Dateien; was `.gitignore` ausschließt, bleibt im Baum), der\nBaum kehrt auf den Zweig zurück, auf dem der Vorgang begonnen hat, und die wartende Beauftragung\nstartet — in der Regel innerhalb einer Minute nach dem Tod statt bis zu zwei Stunden später\n(nxf 6j6v.xb24).\n\n**„Nachweislich\" sind fünf Bedingungen, und alle fünf müssen gelten**, denn eine Arbeitskopie zu\nübernehmen schreibt in sie hinein. Der Chat-Worker muss die Prozessfrage tatsächlich beantworten\nkönnen — eine Laufzeitumgebung, die nicht nachsehen kann, nimmt nie vorzeitig eine Arbeitskopie, für\neinen Host ohne Liveness-Prüfung ändert sich also nichts. Mindestens ein Thread des Vorgangs muss\nnoch eine Antwort schulden; ein Vorgang, dessen Sitzungen alle normal endeten und die Aufgabe\nzurückgegeben haben, ist nicht tot, sondern wartet auf einen Menschen, und für ihn gilt weiterhin die\nDreißig-Minuten-Regel. Kein Prozess des Vorgangs darf noch leben. Keine Sitzung eines schuldenden\nThreads darf ihr Ende gemeldet haben — ein gemeldetes Ende ist ein gelaufener Abbau und damit kein\nharter Tod. Und nichts im Vorgang darf an einer Verfügbarkeitsgrenze pausieren, die ihren eigenen Weg\nzurück hat. Was diese Prüfung kostet, wurde gemessen, bevor sie in jeden Tick kam — und zwar\nvollständig, von Anfang bis Ende: rund **zwei Millisekunden** für einen Vorgang aus zwanzig Threads\nmit zwanzig Sitzungen, auf einem Entwicklungsrechner, im langsameren der beiden Builds dieses\nProjekts.\n\nAlles Übrige an dieser Übergabe ist das Verhalten, das die anderen Anlässe schon hatten: `nxc tick`\nmeldet, wohin die Arbeit ging, `nxc status` zeigt den geparkten Zweig am Vorgang, bis er wieder\naufgenommen wird, und die Ablehnungsregel gilt unverändert — ein laufender Merge, Rebase,\nCherry-Pick, Revert oder Bisect oder ein fehlgeschlagener git-Befehl **behält die Arbeitskopie**, der\nVorgang wird mit `PARK REFUSED` markiert und das Parken bei jedem Tick erneut versucht, während eine\nAblehnung, die nie vorübergehen kann (keine Arbeitskopie, kein git-Repository, nie ein Basiszweig\nvermerkt), die Arbeitskopie ungeparkt weitergibt und das mit der benannten Warnung\n`work_handed_on_unparked` sagt.\n\n**Eine Änderung am Zeitverhalten, die auffallen kann.** Solange eine Beauftragung hinter einem\nAnspruch wartet, wird der Tick, der erneut nachsieht, jetzt eine Minute im Voraus geplant statt zur\neigenen Frist des Halters — genau dieses erneute Nachsehen lässt das Obige in einer Minute statt erst\nzur Frist eintreten. Eine Frist, die früher als eine Minute fällt, gewinnt weiterhin; nichts wird je\nnach hinten verschoben.\n\n**Facade-Kontrakt.** `ParkOccasion` bekommt die Variante `DiedInsideItsBound`\n(`died_inside_its_bound` in `--json` und an `StatusOperation::park_refused.occasion`), und\n`TickReceipt::handed_on` kann sie jetzt tragen; das Enum ist `#[non_exhaustive]`, ein erschöpfendes\n`match` außerhalb des Crates hatte also schon einen Auffangzweig. `ChatStore` bekommt\n`reclaim_a_dead_holders_working_tree_and_take_next`, den Compare-and-Swap-Zwilling von\n`reclaim_expired_working_tree_and_take_next` für eine Lease, die noch innerhalb ihrer Frist steht.\nNichts wurde entfernt, keine Signatur geändert.\n- `breaking` · **Ein abgelehntes Parken folgt jetzt einer Regel: halten und wiederholen, oder ungeparkt\nweitergeben.** Muss eine unbeantwortete Eskalation oder ein an einer Verfügbarkeitsgrenze\nangehaltener Vorgang die Arbeitskopie abgeben, wird seine Arbeit zuerst geparkt — und das Parken\nkann abgelehnt werden. Bisher hielt jede Ablehnung die Arbeitskopie fest und nannte `nxc release` als\nAusweg. Entscheidung des Owners vom 17.09.2026 (nxf 6j6v.8bv9, 6j6v.b9nf): Eine Ablehnung, die\nvorübergehen kann (ein laufender Merge, Rebase, Cherry-Pick, Revert oder Bisect, oder ein\nfehlgeschlagener git-Befehl), behält die Arbeitskopie, wird als `work_not_parked` mit dem, was zu\nbeenden ist, gemeldet, erscheint in `nxc status` als `PARK REFUSED` mit einer Zeile\n`park refused (): — retrying since ` und wird vom Hintergrunddienst\nbei jedem Tick erneut versucht — und nur so lange, wie der Anlass dauert, der die Arbeitskopie\nwollte: Ist die Eskalation beantwortet, der angehaltene Vorgang aufgenommen oder wartet niemand mehr,\nverschwindet die Markierung beim nächsten Tick, denn dann versucht niemand dieses Parken. Eine\nAblehnung, die nicht vorübergehen kann (die Laufzeit nennt keine Arbeitskopie, das Verzeichnis ist\nkein git-Repository, es wurde nie ein Basiszweig vermerkt), gibt die Arbeitskopie UNGEPARKT weiter\nund sagt das mit der neuen Warnung `work_handed_on_unparked` — die nicht committeten Dateien des\nVorgangs bleiben für den nächsten im Baum. Ein an einer Verfügbarkeitsgrenze angehaltener Vorgang\nwird jetzt auch vom Tick gesichert, solange jemand wartet, nicht nur von `nxc resume`. Kein\nAblehnungstext nennt mehr `nxc release`. Und ein Vorgang mit nicht zurückgeholter geparkter Arbeit\noder einem abgelehnten Parken bleibt jetzt in der Standardliste von `nxc status`, auch wenn sonst\nnichts an ihm offen ist.\n\n**Facade-Kontrakt.** `ParkRefusal` bekommt die Variante `NoBaseRecorded` (die Ablehnung, die bisher\nein eingebetteter Satz war) sowie `is_permanent()` und `kind()`; das Enum ist nicht\n`#[non_exhaustive]`, ein erschöpfendes `match` außerhalb des Crates muss den neuen Zweig also nennen.\n`TickReceipt` bekommt das öffentliche Feld `handed_on: Option` (mit dem neuen\n`ParkOccasion`), ein Struct-Literal außerhalb des Crates muss es also nennen. `ConsequenceClass`\nbekommt `WorkHandedOnUnparked` (bereits `#[non_exhaustive]`). `StatusOperation` (aus\n`Engine::status`) bekommt `park_refused: Option` (bereits `#[non_exhaustive]`),\nund `ChatStore` bekommt `note_park_refusal`, `clear_park_refusal`, `park_refusal` und\n`all_park_refusals` über eine neue gerätelokale Tabelle; `ParkRefusalNote` nennt den Anlass\n(`occasion`), der das Parken abgelehnt hat (`stranded_escalation`, `availability_boundary`), auch in\n`--json`. Die verhaltensseitige Hälfte trägt keine Signatur: `tick` und `Engine::resume_interrupted`\ngeben die Arbeitskopie für einen Host, dessen Worker keine Arbeitskopie nennt (die Vorgabe jedes\nWorkers außer dem Sidecar), jetzt weiter, wo sie sie bisher festhielten.\n- `changed` · **Ein Tick führt eine zurückgenommene Runde nicht mehr zusammen und weckt nicht mehr den Agenten,\nder sie beauftragt hat.** Wurde jede Beauftragung einer Kanalrunde zurückgenommen, wurde der\nKanalfaden als zurückgenommen entlastet — und die eigene Uhr des Kanals konnte später trotzdem auf\nihm feuern, einen abgeschlossenen Satz lesen und ihn ausliefern: Bei einer Runde, die ein Mensch\nbeauftragt hatte, lief das ins Leere, bei einer, die ein Agent beauftragt hatte (eine `pm`, die\nArbeit an einen Kanal geschickt hat), weckte es diesen Agenten mit der zurückgenommenen Runde als\nAntwort, und die Arbeit ging weiter. `nxc tick` behandelt einen durch eine Rücknahme entlasteten\nFaden jetzt als bereits erledigt, genau wie einen zusammengeführten (`reason: already_handled`), und\nnichts wird ausgeliefert (nxf 6j6v.s2cj).\n- `changed` · **Ein Chat-Worker kann jetzt eine Sitzung beenden, die er gestartet hat.** Bisher konnte die Engine\neinen Worker nur zu einer Sitzung befragen — läuft sie, wo läuft sie, lässt sie sich fortsetzen —,\nund das Einzige, was eine Sitzung beendete, war die Sitzung selbst. Genau dieses Stück fehlte dem\nZurückziehen einer *laufenden* Runde: Wer die Arbeit zurücknimmt, während ihre Sitzung weiter in die\nArbeitskopie schreibt, hat das Geparkte schon überholt, bevor die Quittung gedruckt ist. Der\nmitgelieferte Sidecar-Worker beantwortet die neue Frage mit Ja und beendet eine Sitzung, indem er\nihrem Prozess `SIGTERM` schickt — nie `SIGKILL`, nie die ganze Prozessgruppe —, und zwar an die\nPID aus derselben Datei `.nxs/agent-logs/.pid`, die auch die Lebendigkeitsprüfung liest;\nder signalisierte Prozess ist damit konstruktionsbedingt derjenige, der als laufend gemeldet wird.\nEine fehlende PID-Datei, eine Datei ohne PID oder ein Anspruch, der nicht belegen kann, welchen\nProzess er meint, wird benannt abgelehnt, statt ins Leere zu signalisieren; eine Sitzung, deren\nProzess bereits weg ist, ist gar kein Fehlschlag, sondern genau der Zustand, um den es beim Stoppen\nging — und wird auch so gemeldet. Die Anforderung wird *zugestellt*, nicht abgewartet: Wer eine\nSitzung beendet, wartet, bis die Lebendigkeitsprüfung auf „nicht laufend\" wechselt, bevor er den\nBaum anfasst (nxf 6j6v.b9nf).\n\n**Ein Sitzungsanspruch hält jetzt fest, WELCHER Prozess ihn hält, nicht nur dessen Nummer.**\nAnsprüche werden bewusst nie entfernt; eine abgestürzte Sitzung — oder ein Neustart der Maschine —\nhinterlässt also eine Datei mit einer PID, die das Betriebssystem frei an etwas anderes vergeben\ndarf: einen Editor, einen Entwicklungsserver, ein weiteres `nxc` desselben Benutzers. `kill(pid, 0)`\nsagt über diesen Fremden *ja*, was hinnehmbar war, solange die Antwort nur eine Weckung verzögerte,\nund nicht mehr hinnehmbar ist, seit das Zurücknehmen einer Runde dem Prozess ein Signal schickt.\nDeshalb enthält `.nxs/agent-logs/.pid` in der ersten Zeile die PID und in der zweiten den\nZeitpunkt, zu dem dieser Prozess gestartet ist, und jeder Leser vergleicht diesen Zeitpunkt mit dem,\nwas das Betriebssystem zur gefundenen PID sagt — dieselbe Identitätsprüfung, die der\nHintergrunddienst für seinen eigenen Herzschlag längst macht, geteilt statt kopiert. Ein Anspruch,\ndessen Zeitpunkt nicht passt, benennt eine Sitzung, die **beendet** ist: Er gilt als nicht laufend,\nund es wird nichts signalisiert. Ein Anspruch aus der Zeit davor hält keinen Zeitpunkt fest, ist von\neiner wiederverwendeten PID nicht zu unterscheiden und wird deshalb benannt abgelehnt statt\nsignalisiert — die Lebendigkeitsprüfungen glauben ihm weiterhin, denn dort kostet ein Irrtum einen\nverzögerten Zug, bei einem Signal kostet er den Prozess eines Fremden.\n\n**Und die Lebendigkeitsprüfungen lesen eine PID-Datei jetzt so wie der Stopp.** Eine `.pid`-Datei\nmit `0` — oder mit einer Zahl, die das Betriebssystem als Prozess*gruppe* statt als Prozess liest —\ngilt überall als *kein laufender Prozess*: für die einzelne Sitzung wie für die Liste aller\nSitzungen, die der Hintergrunddienst abfragt. Beide lasen bisher eine bloße Zahl und fragten\n`kill(pid, 0)` danach, was bei `0` eine Frage nach der eigenen Prozessgruppe des Fragenden ist und\nmit *ja* antwortet, solange irgendetwas von diesem Arbeitsbereich lebt — eine als laufend gemeldete\nSitzung, die niemand jemals beenden konnte.\n\n**Der Sidecar baut bei `SIGTERM` geordnet ab, statt zu sterben.** Er bricht den laufenden Zug über\nden Abbruchmechanismus des SDK ab und führt dann seinen Abbau in der gewohnten Reihenfolge aus —\nbindet die Laufzeitsitzung, damit ein späteres Fortsetzen das Gespräch findet, schreibt alles, was\nder abgebrochene Zug erzeugt hat, ins Transkript und meldet `nxc session ended`. Genau ein Schritt\nentfällt auf das Signal hin: Die Sitzung wird **nicht ans Antworten erinnert** — eine Erinnerung\nsagt einer Sitzung, dass sie die Antwortregel gebrochen hat, und dieser wurde gesagt, sie solle\naufhören; der Modellaufruf dafür wäre verschwendet.\n\n**Ob in ihrem Namen etwas gepostet wird, entscheidet das REGISTER, nicht das Signal.** Ein `SIGTERM`\nist nicht nur eine Rücknahme: Er ist auch ein Systemabschalten, ein Logout, ein `docker stop`, ein\n`kill ` von Hand. Eine Rücknahme entlastet den Faden, bevor sie signalisiert — der Abbau liest\nalso einen Faden, der nichts mehr schuldet, und sagt nichts, was richtig ist: Eine Ersatzantwort\nspräche dort als der Agent in eine gerade zurückgenommene Runde hinein. Jeder andere `SIGTERM`\nlässt den Faden weiter schuldend zurück, und der Abbau postet das übliche\n`nxc reply --thread --if-unanswered` für ihn; ob dieser Schreibvorgang landet, entscheidet\nweiterhin das Tor der Engine. Ohne das machte der Handler aus jedem fremden `SIGTERM` — bisher ein\nharter Tod, den die Kehrschleife in etwa einer Minute auflöst — ein ordentliches, stummes Ende, weil\nder Abbau jetzt das Sitzungsende meldet: die Schuld ungeklärt, der wartende Aufrufer ungeweckt und\ndie Arbeitskopie bis zur Zwei-Stunden-Grenze gehalten.\n\nEin Stopp ist weder ein beendeter noch ein gescheiterter Zug, deshalb endet das Log mit\n`sidecar stopped:` statt `sidecar done:`, und der Prozess beendet sich mit **143** (128 + SIGTERM,\ndie Zahl, die eine Shell für einen am Signal gestorbenen Prozess meldet — beibehalten für einen, der\nes abgefangen hat). Ein `SIGTERM` während der Erinnerungsrunde beendet diese Runde auf dieselbe\nWeise.\n\n**Facade-Kontrakt.** `Worker` bekommt zwei Methoden mit Vorgabe: `stops_sessions(&self) -> bool`\n(vorgegeben `false` — ein Host, der keine Sitzung beenden kann, wird benannt abgelehnt statt\ngeglaubt) und `stop_session(&self, internal_session: &str) -> Result`\n(vorgegeben eine benannte Ablehnung). Eine bestehende Implementierung behält genau das Verhalten,\ndas sie hatte; ein Host, dessen Laufzeit eigene Sitzungen beenden kann, überschreibt beide. Der\nWrapper von `WorkerConfig::Custom` leitet beide weiter. Das neue Enum `SessionStop`\n(`#[non_exhaustive]`) unterscheidet einen zugestellten Stopp von einer bereits beendeten Sitzung:\n`Requested` ist das gesendete Signal, `NothingToStop(String)` der gewünschte Zustand samt dem Satz,\nder ihn gefunden hat — ein Aufrufer schreibt den auf seine Quittung, nicht in eine Warnung. `worker`\nbekommt außerdem `session_claim_for(pid) -> String`, die eine Schreibweise des Dateiinhalts eines\nAnspruchs, damit alles, was einen Prozess für eine Sitzung einsetzt, die Form schreibt, die die\nLeser erwarten. Nichts wurde entfernt.\n- `breaking` · **`nxc release` und `Engine::release_working_tree` sind entfernt.** Das Verb gab die gehaltene\nArbeitskopie eines Workspace von Hand zurück, gestützt auf eine einzige Ablehnung: Es prüfte, ob\nkeine Sitzung der haltenden Kette noch einen lebenden Prozess hatte. Auf der Agentenoberfläche\nangeboten, konnte es damit einer laufenden Coding-Operation die Arbeitskopie wegnehmen, und bei\neinem Chat-Worker, der die Lebendigkeitsprüfung nie implementierte, war es ein bedingungsloses\nFreigeben ganz ohne Absicherung — genau die grobe Übersteuerung, die das Epic dieses Projekts über\nmanuelle Übersteuerungen ablehnte zu bauen, hier erreicht, weil die Ablehnung das Einzige war, was\nzwischen dem Verb und ihr stand.\n\nJede Übergabe parkt jetzt zuerst die Arbeit des Halters, sodass geschieht, was das Verb bisher von\nHand tat, von selbst. Eine Arbeitskopie, die eine bereits tote Kette hält, parkt und gibt der\nHintergrunddienst weiter, ohne dass jemand fragt — über ihrer Zwei-Stunden-Grenze, oder, da der\nHintergrunddienst jetzt eine tote Kette von einer nur stillen unterscheiden kann, schon davor. Eine\nArbeitskopie, die eine noch laufende Kette hält und die Sie — der Mensch, der sie gestartet hat —\nabsichtlich zurückwollen, nimmt man mit `nxc withdraw --thread ` / `Engine::withdraw`: Es entlastet die Runde, bittet ihre Sitzung, sich\nzu beenden, und parkt, sobald der Prozess weg ist, was sie unfertig hinterlassen hat — nie\nzurückgerollt —, sodass die nächste Beauftragung in denselben Faden die Arbeit zurückbringt.\n\n**Facade-Kontrakt.** `Engine::release_working_tree` und `orchestration::release_working_tree` sind\nentfernt, ebenso `ReleaseReceipt`. `ChatStore::release_working_tree_and_take_next` ist davon nicht\nbetroffen und bleibt: Es ist die Transaktion, über die jede Übergabe — der Antwortpfad, ein Parken,\nder Sweep — weiterhin läuft, um den Anspruch zurückzugeben und ihn dem Nächsten zu übergeben.\n- `changed` · **`nxc status` nennt jetzt die noch nicht wieder aufgenommene geparkte Arbeit eines Vorgangs.** Seit\nnxf 6j6v.de9s konnte eine unbeantwortete Eskalation auf einen Zweig geparkt und die Arbeitskopie\nweitergegeben werden — aber der einzige Weg, diesen Zweig wiederzufinden, war, ihn schon zu kennen.\nJetzt trägt die eigene Zeile des Vorgangs auf `nxc status` eine Zeile pro noch nicht\nzurückgeholtem Park: den Zweig, die ersten sieben Zeichen des Commits und seit wann die Kopie\nweitergegeben ist (plus einen Hinweis, wenn der Park nichts zu committen fand). `--json` trägt\ndieselben Zeilen als `parked` auf dem Vorgang, und ein Arbeitsbereich, der noch nie geparkt hat,\nbleibt beim Feld still — kein `\"parked\"`-Schlüssel überhaupt.\n\nFür einbettende Anwendungen: `StatusOperation` (aus `Engine::status`) bekommt ein Feld `parked`. Es\nwar schon `#[non_exhaustive]`, also ändert sich für Lesende nichts.\n- `breaking` · **Ein Vorgang, der wortlos stirbt, verliert seine nicht committete Arbeit nicht mehr, wenn die\nWarteschlange seine Arbeitskopie übernimmt.** Läuft eine Arbeitskopie-Lease ab und hat kein Prozess\nin der Kette dieses Vorgangs mehr Leben, gibt der Hintergrund-Sweep die Arbeitskopie an den\nWartenden weiter — und tat das bisher mit den nicht committeten Dateien des toten Vorgangs noch im\nBaum, sodass der nächste Agent in der halbfertigen Arbeit eines anderen startete. Es war die letzte\ndurch eine Störung ausgelöste Übergabe, die nichts zuerst sicherte: Eine abgestürzte Kette hat weder\neskaliert noch eine Verfügbarkeitsgrenze angekündigt, also wurde keines der beiden bereits\nvorhandenen Parkverfahren je nach ihr gefragt (nxf 6j6v.8bv9). Jetzt wird ihre Arbeit auf einen Zweig\ncommittet (samt nicht versionierter Dateien; was `.gitignore` ausschließt, bleibt im Baum), die\nArbeitskopie kehrt auf den Zweig zurück, auf dem der Vorgang begonnen hat, und erst dann startet die\nwartende Beauftragung — `nxc tick` meldet das Parken im selben Block wie die Rückholung, und\n`nxc status` zeigt den geparkten Zweig am Vorgang, bis er wieder aufgenommen wird. Die Ablehnungsregel\ngilt hier genau wie bei den anderen beiden Anlässen: Ein laufender Merge, Rebase, Cherry-Pick, Revert\noder Bisect oder ein fehlgeschlagener git-Befehl **behält die Arbeitskopie** jetzt, der Vorgang wird\nmit `PARK REFUSED` markiert und das Parken bei jedem Tick erneut versucht — wo bisher derselbe Tick\nsagte, der Anspruch bleibe bestehen, und ihn zwei Schritte später zurückholte, was der eine\nWiderspruch ist, den diese Freigabe beseitigt. Eine Ablehnung, die nicht vorübergehen kann (keine\nArbeitskopie, kein git-Repository, nie ein Basiszweig vermerkt), gibt die Arbeitskopie ungeparkt\nweiter und sagt das mit der benannten Warnung `work_handed_on_unparked`, wie schon an anderer Stelle.\nEin Vorgang, dessen Frist abgelaufen ist, der aber noch einen lebenden Prozess hat, bleibt unberührt,\ngenau wie bisher.\n\n**Für die andere Tür zu demselben Moment gilt jetzt dasselbe.** Auch das eigene `nxc send` eines\nNeuankömmlings holt eine abgelaufene Lease zurück — so bekommt die Kopie, wer schon ansteht, und\nnicht, wer zufällig fragt — und es hatte alle Bedingungen des Sweeps außer dem Parken. Die Arbeit\neines toten Vorgangs wurde also weiterhin im Baum weitergereicht, sobald ein Anstoß vor einem Tick\ndort ankam. Jetzt wird zuerst geparkt, unter demselben Anlass und derselben Ablehnungsregel: Wird\ndas Parken aus einem Grund abgelehnt, der vorübergehen kann, bekommt **niemand** die Kopie — der\nNeuankömmling stellt sich hinter den Halter, genau wie bei noch laufendem Anspruch, und weiterbewegt\nwird sie vom erneuten Versuch des Dienstes. Das gilt **auch, wenn überhaupt niemand wartet** — der\ngewöhnlichste Fall überhaupt: Ein allein arbeitender Agent stürzt mit nicht committeter Arbeit ab,\ndie Frist läuft ab, und das nächste `nxc send` ist die Übergabe. Dann gibt es keinen Zug\nweiterzugeben, aber die Kopie verlässt trotzdem einen Vorgang, der verunglückt ist: Seine Arbeit\nkommt zuerst auf einen Zweig, und der Neuankömmling startet in einem sauberen Baum. Der\nHintergrund-Sweep deckt diesen Fall nicht ab und soll es auch nicht — bei leerer Warteschlange gibt\ner nichts weiter, also ist dort auch nichts zu sichern. Keine der beiden Park-Meldungen lässt ein Verb mit einem\nFehlercode enden: `work_not_parked` und `work_handed_on_unparked` sind Meldungen auf dem Beleg und in\n`--json`, und ein routinemäßiges Zurückholen in einem Arbeitsbereich, der kein git-Repository ist (wo\nkein Parken je gelingen kann), lässt den Aufruf, bei dem es geschieht, nicht mehr fehlschlagen.\n\n**Facade-Kontrakt.** Aus `drain_unarmed: Option` an `TriggerAdmission::Queued`\nwird `findings: Vec` — eine Liste, weil diese Übergabe jetzt neben dem nicht\ngestellten Wecker auch ein Parken melden kann; wer die Variante gebaut oder zerlegt hat, muss das\nneue Feld nennen, und `TriggerReceipt::warnings` trägt dieselben Werte wie bisher.\n`WorkingTreeSweep` (an `TickReceipt::working_tree`) bekommt zwei öffentliche\nFelder, `parked: Option` und `unparked: Option`; ein Struct-Literal außerhalb\ndes Crates muss sie also nennen, und in `--json` fehlen beide, solange sie nicht gesetzt sind.\n`ParkOccasion` bekommt die Variante `PastItsBound` (`past_its_bound` in `--json` und an\n`StatusOperation::park_refused.occasion`); das Enum ist `#[non_exhaustive]`, ein erschöpfendes\n`match` außerhalb des Crates hatte also schon einen Auffangzweig.\n- `breaking` · **`nxc withdraw` ist jetzt das Verb eines Menschen: Nur wer die erste Beauftragung eines Vorgangs\nabgeschickt hat, darf sie zurücknehmen, und nur, indem er diesen Faden nennt.** Bisher lautete die\nHürde „darf den Faden lesen\" — in einem öffentlichen Kanal jedes Handle, das eigene `nxc` eines\nAgenten eingeschlossen —, und `nxc prime` brachte das Verb jeder startenden Sitzung bei. Seit das\nVerb eine laufende Runde anhalten kann, ist die Frage, wer es aufrufen darf, und die Antwort ist ein\nMensch oder ein Assistent, den ein Mensch gestartet hat. Drei Aufrufer werden jetzt abgelehnt, bevor\nsich etwas ändert, jeder benannt: **eine Sitzung, die dieser Workspace gestartet hat**\n(`forbidden`) — gleich, was sie nennt; die Ablehnung nennt die Sitzung und die Rolle, für die sie\ngestartet wurde, und verweist sie auf `nxc reply --thread --escalate -`, das\nnach oben geht, zu dem, der sie beauftragt hat; **wer den Vorgang nicht eröffnet hat**\n(`forbidden`), mit dem Namen dessen, der es war; und **ein Faden unterhalb des ersten** — ein\nSchritt einer Kanalrunde, eine Unterrunde, die ein Agent eröffnet hat — (`validation`), mit dem\nFaden, der stattdessen zu nennen ist. Die Ablehnung einer Sitzung stützt sich auf das, was die\nEngine belegen kann — die Sitzungstabelle verzeichnet jede Sitzung, die sie gestartet hat —, und\nbehauptet nicht mehr: Ein Prozess, der seine Sitzung verbirgt, fällt nicht unter diese Regel und\ntrifft dann auf die beiden anderen (nxf 6j6v.ezbr).\n\n**`nxc prime` bietet das Verb nicht mehr an.** Die `withdraw`-Zeile ist aus *How a conversation\nmoves* verschwunden, der Eintrag aus `commands` in `--json` (jetzt sieben Einträge), und\n`when_stuck` verweist eine Sitzung mit gehaltener Arbeitskopie stattdessen auf die Eskalation. Die\n`pm`-Persona des mitgelieferten Beispielteams sagt dasselbe.\n\n**Facade-Kontrakt.** `Engine::withdraw` / `orchestration::withdraw` lehnen Aufrufer ab, die sie\nbisher angenommen haben — deshalb als breaking markiert, obwohl sich keine Signatur bewegt hat: ein\n`Caller`, dessen `session` dieser Workspace erzeugt hat (`ErrorKind::Forbidden`), ein Aufrufer, der\nnicht Eröffner des Vorgangs ist (`Forbidden`; wer den Faden nicht einmal lesen darf, bekommt weiter\nzuerst die Ablehnung des Lese-Tors), und ein Faden, der nicht die Wurzel seines Vorgangs ist\n(`Validation`). Ein Host, der für seinen angemeldeten Nutzer handelt, übergibt keine Sitzung und\nnimmt zurück, was dieser Nutzer abgeschickt hat, genau wie bisher; ein Host, der einen aus `status`\ngelesenen Mitglieds-Faden zurücknahm, nennt jetzt die Wurzel des Vorgangs. `PRIME_COMMANDS` verliert\nden Eintrag `withdraw`, und die Texte von `PRIME_HOW_A_CONVERSATION_MOVES` und `PRIME_WHEN_STUCK`\nhaben sich entsprechend geändert.\n- `breaking` · **`nxc withdraw` nimmt eine bereits laufende Runde zurück und parkt ihre Arbeit.** Bisher konnte das\nVerb nur eine Beauftragung zurücknehmen, die noch auf die Arbeitskopie wartete; eine Runde, deren\nSitzung gestartet war, wurde abgelehnt, und der einzige Weg, sie anzuhalten, war, ihren Prozess von\nHand zu suchen — wonach nichts sicherte, was sie in der Arbeitskopie hinterlassen hatte, und der\nnächste Halter darin startete. Jetzt entlastet `nxc withdraw --thread ` auf einer laufenden\nRunde — durch den Menschen, der sie abgeschickt hat, siehe den Eintrag dazu, wer zurücknehmen darf —\nden Faden (das Register sagt, die Runde ist vorbei), bittet den Chat-Worker, die Sitzung\nanzuhalten (der mitgelieferte Sidecar erhält `SIGTERM`, bricht seinen Zug ab, sichert sein\nTranskript und meldet sein Ende), und vermerkt — wenn die Runde die Arbeitskopie dieses Workspace\nhält —, dass ihre Arbeit zu parken ist. Der Hintergrund-Tick committet dann, sobald in diesem\nVorgang nichts mehr läuft, alles Uncommittete auf einen Zweig und gibt die Kopie an den weiter, der\nwartet; wartet niemand, wird die Kopie einfach frei. **Nichts wird je zurückgerollt.** `nxc status` führt den Zweig\nunter dem Vorgang, bis jemand ihn wieder abholt, und der Weg zurück ist die nächste Beauftragung in\ndenselben Faden: auf einem direkten Persona-Faden setzt `nxc reply --thread ` die angehaltene\nSitzung mit ihrem eigenen Transkript auf dem Parkzweig fort; bei einer Kanalrunde startet eine\nFolgenachricht in den Kanalfaden dort den nächsten Durchgang. Ein frisches `send` eröffnet einen\nneuen Anspruch und findet sie nicht (nxf 6j6v.b9nf).\n\n**Zwischen Stopp und Parken behält der zurückgenommene Halter die Kopie.** Die Entlastung lässt den\nVorgang nichts mehr schulden — genau der Zustand, in dem die gewöhnliche Freigabe am Ende eines\nVorgangs greift —, und der Abbau der angehaltenen Sitzung oder eine späte Antwort in den Faden hätte\ndie Kopie mit der Arbeit noch im Baum weitergegeben. Solange die Markierung steht, unterbleibt jede\nsolche Freigabe; der Anlass „zurückgenommen\" des Ticks ist der eine Weg, auf dem ein solcher Halter\nloslässt, und er handelt erst, wenn nichts im Anspruchsbereich mehr einen lebenden Prozess hat —\nsolange einer da ist, sieht er einmal pro Minute nach. Die Ablehnungsregel gilt unverändert: Ein\nParken, das aus einem behebbaren Grund abgelehnt wird, behält die Kopie mit `PARK REFUSED` in\n`nxc status` und wird erneut versucht; eines, das nie durchgehen kann, gibt die Kopie ungeparkt mit\nder benannten Warnung `work_handed_on_unparked` weiter.\n\n**Eine Rücknahme unterbricht die Kette unter dem Faden, den sie nennt** (nxf 6j6v.s2cj). Jeder\nFaden zwischen dem Zurückgenommenen und diesem Faden, der noch wartet — ein Kanalfaden, der das\nErgebnis seiner Runde schuldet, ein Persona-Faden, der auf die von ihm beauftragte Runde wartet —,\nwird ebenfalls entlastet, mit einer Nachricht, die sagt, dass die Kette unterbrochen wurde. Ein\nKanal beauftragt also nichts mehr, sobald einer seiner Schritte zurückgenommen ist: weder den\nnächsten Schritt noch den zurückgenommenen erneut, an einem `flow: sequential`-Kanal genauso wie an\neinem mit `steps:`, vor dem Parken und danach; und nichts wird zusammengeführt. Die Antworten, die\nSchritte schon gegeben hatten, bleiben in ihren Fäden, und die Arbeit kommt zurück, wenn der Mensch,\nder sie zurückgenommen hat, den ersten Faden des Vorgangs erneut beauftragt — bei einer Runde, die\nein Agent beauftragt hat, trägt die Entlastung dieses Fadens die Sitzung des Agenten, sodass die\nFolgenachricht sie fortsetzt. (Eine Runde, die eine Rücknahme nicht\nentlastet hat — eine, in der eine wartende Beauftragung startete, während die Rücknahme lief —, hält\nweiter die Markierung: Ihr abgeschlossener Satz wird weder fortgeschaltet noch geroutet noch\nzusammengeführt, bis die Kopie weitergegeben ist.)\n\n**Was die Quittung über einen Zweig verspricht, ist jetzt das, was das Parken auch liefern kann.**\n`will_park` hieß bisher nur „diese Runde hält die Arbeitskopie\" und stand auf `true` auch auf Hosts,\nauf denen ein Parken nie gelingen kann — ein Worker, der keine Arbeitskopie benennt (die Vorgabe\njedes Workers außer dem mitgelieferten Sidecar), ein Workspace, der kein Git-Repository ist, ein\nVorgang ohne vermerkte Basis. Jetzt werden diese drei rein lesenden Vorbedingungen mitgefragt, und\nwenn die Runde die Kopie zwar hält, aber nicht geparkt werden kann, trägt die Quittung\n`cannot_park` mit dem Grund: Die Kopie wird trotzdem weitergegeben, sobald im Vorgang nichts mehr\nläuft — ungeparkt und mit der Warnung `work_handed_on_unparked`, die das sagt. `nxc withdraw` gibt\ndiesen dritten Fall in eigenen Worten aus statt „nothing of it is in this workspace's working copy\",\nund die in den zurückgenommenen Faden geschriebene Nachricht sagt dasselbe Wahre.\n\n**Und eine Rücknahme, die dem Tick Arbeit hinterlässt, sagt, wenn nichts einen Tick ausführt.**\nSolange die Markierung steht, unterbleibt jede andere Freigabe planmäßig — auf einer Maschine mit\ntotem Hintergrunddienst bleibt die Kopie also bei einer Runde, die vorbei ist, ohne dass es jemand\nerfährt. `nxc withdraw` trägt jetzt die Warnung `service_not_running`, die `send`, `reply` und\n`tick` schon tragen (sie ändert den Exit-Code nicht), und beide Park-Zeilen nennen\n`nxc tick --thread ` — den Befehl, der das Parken beziehungsweise die ungeparkte Weitergabe von\nHand erledigt.\n\n**Ein Host, dessen Worker keine Sitzung anhalten kann, wird abgelehnt, bevor sich etwas ändert** —\nder ganze Aufruf, benannt (`this host cannot stop a running session; nothing was withdrawn`), damit\neine Rücknahme nie einen entlasteten Faden neben einer weiterschreibenden Sitzung hinterlässt. Ein\nStopp, den der Worker beherrscht und dann nicht zustellen konnte, ist eine Warnung\n`session_not_stopped` auf der Quittung, die die Sitzung nennt; der Faden ist trotzdem entlastet, und\nder Befehl endet mit einem Fehlercode. Eine Sitzung, die zum Zeitpunkt des Stopps **bereits beendet**\nwar — das gewöhnliche Rennen bei einer Runde, die ohnehin gerade fertig wurde —, ist kein\nFehlschlag: Die Quittung trägt einen Vermerk, was vorgefunden wurde, `warnings` bleibt leer, und der\nBefehl endet mit 0, denn das ist genau der Zustand, um den es ging. Und ein Host, dessen Worker die\nLebendigkeitsfrage überhaupt nicht beantworten kann, wird jetzt **benannt** abgelehnt —\n`ErrorKind::Validation`, wo ein Worker, der die Lebendigkeit nicht beantworten kann, bei einer nicht\ngemeldeten Sitzung im Bereich bisher `not_found` bekam —, wenn im Bereich Sitzungen ohne gemeldetes\nEnde liegen, statt zu hören, es gebe nichts zurückzunehmen: Diese Antwort war eine Aussage über\nProzesse, die niemand angesehen hatte. Abgesehen davon, wer zurücknehmen darf, und von der Kette,\ndie eine Rücknahme unterbricht (beides unten und im Eintrag dazu, wer zurücknehmen darf), verhält\nsich der wartende Fall wie bisher; die Ablehnung für einen Faden, unter dem nichts liegt (ein\nWorker, der die Lebendigkeit beantworten kann, oder ein Bereich ganz ohne nicht gemeldete Sitzung),\nbleibt `not_found` und lautet jetzt *nothing under thread … is waiting or running*.\n\n**Und ein Halter, der die Rücknahme nicht loslässt, hat jetzt einen sichtbaren Grund.** Eine\nangehaltene Sitzung kann ihr `SIGTERM` überleben — ein hängender Sidecar, ein eigener Worker, dessen\nStopp `Ok` meldet und nichts ändert, eine wiederverwendete PID —, und bisher sagte das nichts auf\nkeiner Oberfläche: `nxc status` hatte dafür kein Feld, und „von Hand stoppen\" stand nur dann, wenn\nschon der erste `stop_session`-Aufruf selbst fehlgeschlagen war, nie wenn die Anforderung ankam und\neinfach ignoriert wurde. `nxc status` markiert den haltenden Vorgang jetzt mit `WITHDRAWN` (neben\n`PARK REFUSED` und `holds working tree`) und nennt, wer zurückgenommen hat, wann, und jede Sitzung,\ndie den Anspruch gerade noch hält; `--json` trägt dieselbe Tatsache als neues Feld `withdrawn`,\nweggelassen, wenn die Runde des Vorgangs nie zurückgenommen wurde. Und nach fünf\nLebendigkeits-Takten (fünf Minuten), in denen eine von der Rücknahme gestoppte Sitzung noch da ist,\nschickt `nxc tick` ihr **ein weiteres `SIGTERM`** — über den eigenen Stopp des Workers, hinter\nderselben Identitätsprüfung wie das erste — und nennt sie: die Sitzung, ihre PID, die PID-Datei, die\nsie nennt, und wann dieses weitere Signal ging (nxf 6j6v.27b9). Mehr wird nie geschickt, und nie\n`SIGKILL`: Der ganze Abbau des Sidecars geschieht auf `SIGTERM`, und den Prozess darüber hinaus zu\nbeenden, bleibt einem Menschen überlassen. Beim mitgelieferten Sidecar, der beim Eintreffen schon\nanhält, ändert dieses weitere Signal nichts; es hilft dem eigenen Worker eines Hosts, dessen erster\nStopp verloren ging oder ignoriert wurde. Eine Sitzung, die eine Folgenachricht vor dem Parken\nwieder in Gang gesetzt hat, ist wieder jemandes Arbeit und wird weder signalisiert noch genannt.\n\n**Facade-Kontrakt.** `WithdrawReceipt` bekommt `stopped: Vec` (`{thread, role,\nsession, already_gone?}` — das letzte Feld nur, wenn die Sitzung bereits beendet war, dann mit dem\nSatz, der das sagt; das Array in `--json` weggelassen, wenn leer), `will_park: bool`,\n`cannot_park: Option` (in `--json` weggelassen, wenn nicht gesetzt; `will_park: false` ohne\n`cannot_park` ist eine Runde, die nichts hält, `cannot_park: Some(_)` ist Arbeit in der\nArbeitskopie, die dieser Host nie parken kann) und `warnings: Vec` (weggelassen,\nwenn leer) und ist jetzt `#[non_exhaustive]` — sowohl die\nneuen Felder als auch die Markierung brechen ein außerhalb des Crates geschriebenes Struct-Literal,\nweshalb dies als breaking markiert ist; das Lesen der Quittung ist nicht betroffen. `ParkOccasion`\nbekommt `Withdrawn` (`withdrawn` in `--json` und an `park_refused.occasion`); `ConsequenceClass`\nbekommt `SessionNotStopped` (`session_not_stopped`) und `WithdrawnHolderWedged`\n(`withdrawn_holder_wedged`); beide Enums waren bereits `#[non_exhaustive]`.\n`ChatStore` bekommt `note_withdrawn_holder`, `withdrawn_holder` und `all_withdrawn_holders` über die\nneue gerätelokale Tabelle `withdrawn_holders`, und `note_withdrawn_sessions`, `withdrawn_session`\n(ein neues `WithdrawnSession {stopped_at, resignalled_at, resignal_refused}`),\n`claim_withdrawn_session_resignal`, `note_withdrawn_session_resignal_refused` und\n`forget_withdrawn_session` über ihre Schwester `withdrawn_sessions` — die Sitzungen, die eine\nRücknahme gestoppt hat, und damit die einzigen, die das weitere `SIGTERM` des Ticks erreichen darf. `StatusOperation` — ebenfalls schon\n`#[non_exhaustive]` — bekommt `withdrawn: Option` (der neue Typ, auch\n`#[non_exhaustive]`: `{withdrawn_at, by, pinned_by: Vec}`), ein rein additives Feld. Die\nSignatur von `Engine::withdraw` ist unverändert." - } - }, - { - "version": "0.98.0", - "date": "2026-09-14", - "items": [ - { - "type": "added", - "en": "**A session that stops because the model went away now has a way back.** An exhausted quota, a\nprovider that cannot be reached, a broken connection: three causes, one state, and it is the state\nin which nothing is broken and nobody did anything wrong. Until now the runtime could not tell that\napart from a crash, so it posted an escalation in the agent's own name — *\"I cannot carry this\nout\"*, which was false — the round was discharged, and the operation stopped on `NEEDS DECISION`\nwith nothing able to restart it.\n\n**First, it can be read.** `nxc status` marks such an operation `INTERRUPTED` and the thread's row\nsays which window was hit and when it lifts; `nxc session state` says the same about the session;\n`--json` carries it as `interrupted` on both. That is the larger half: a session that ran into a\nweekly window used to report a clean `ended` with its thread reading `answered`, so the one thing\nnobody could establish was that anything had happened at all. Nothing is escalated any more, and the\nthread keeps owing its answer — because the session that owes it is coming back.\n\n**Then, `nxc resume --thread `** takes the operation up, and the background service does it by\nitself once the boundary falls. The session **continues with its own transcript** rather than\nstarting over, which is the safety argument and not a convenience: the transcript carries every\ncommand the session ran *and what it returned*, so a session that already created a ticket sees the\nid it got back instead of creating it twice. No tool call is ever replayed.\n\n**It checks the world before it starts anything**, and most of what it prints is a decision not to.\nA round that was **already answered** before the interruption is not run again — the common case,\nbecause a quota strikes the expensive turns and an expensive turn has usually just finished. A\nsession with a **live process** is left alone, which is what keeps a hand-run resume from colliding\nwith the service arriving at the reset time. A **working copy that moved** while the operation\nwaited sends a question back to the thread that started the operation instead of continuing blind.\nAnd a runtime that **cannot continue a conversation it began** is refused by name rather than\nquietly handed a fresh session with none of the history.\n\nWhile the window is closed, the operation's uncommitted work is **committed onto a park branch** and\nthe working copy handed on — a weekly window is up to a week, and holding this workspace's only\ncheckout that long would stop everything else. Resuming brings it back and reports whether the base\nmoved underneath it.\n\nTwo limits stated up front. **Ignored files** — `.env`, local databases, `node_modules` — are seen\nby neither the anchor nor the park. And **the repository rewinds; the record does not**: tickets that\nwere created stay created, and messages that were posted stay posted.\n\nFor hosts: continuation is a **provider capability** (`Worker::resumes_sessions`, default `false`).\nA runtime that does not continue its own conversations gets a named refusal instead of a silent\nfresh start — because the silent fresh start is exactly what the transcript protects against. If it\ngenuinely cannot, the hold is closed and the question goes to the thread that started the\noperation, rather than standing on a refusal that will never change.\n\n**What breaks for an embedding app:** `SessionStanding` (from `Engine::session_state`) gains an\n`interrupted` field and becomes `#[non_exhaustive]`, so a struct literal or an exhaustive\ndestructuring of it no longer compiles — reading its fields is unaffected. `StatusThread` and\n`StatusOperation` gain `interrupted` too and were already `#[non_exhaustive]`, so nothing there\nchanges for a reader.", - "de": "**Eine Sitzung, die stehenbleibt, weil das Modell weg ist, hat jetzt einen Weg zurück.** Ein\nerschöpftes Kontingent, ein nicht erreichbarer Anbieter, ein unterbrochenes Netz: drei Ursachen, ein\nZustand — und es ist der Zustand, in dem nichts kaputt ist und niemand etwas falsch gemacht hat.\nBisher konnte die Laufzeit das nicht von einem Absturz unterscheiden und stellte eine Eskalation im\nNamen des Agenten ein — *„ich kann diese Aufgabe nicht ausführen\"*, was falsch war. Die Runde galt\nals erledigt, der Vorgang stand auf `NEEDS DECISION`, und nichts konnte ihn wieder anstoßen.\n\n**Zuerst ist es nachlesbar.** `nxc status` markiert so einen Vorgang mit `INTERRUPTED`, und die\nZeile des Fadens nennt die getroffene Grenze und wann sie fällt; `nxc session state` sagt dasselbe\nüber die Sitzung, `--json` trägt es auf beiden als `interrupted`. Das ist die größere Hälfte: eine\nSitzung, die ins Wochenkontingent lief, meldete vorher ein sauberes `ended` und ihr Faden\n`answered` — das Einzige, was niemand feststellen konnte, war, dass überhaupt etwas passiert war.\nNichts wird mehr eskaliert, und der Faden schuldet weiterhin seine Antwort, weil die Sitzung, die\nsie schuldet, zurückkommt.\n\n**Dann nimmt `nxc resume --thread `** den Vorgang wieder auf — und der Hintergrunddienst tut es\nvon allein, sobald die Grenze fällt. Die Sitzung **macht mit ihrer eigenen Mitschrift weiter**,\nstatt von vorn anzufangen; das ist die Sicherheitsbegründung und keine Bequemlichkeit: die\nMitschrift trägt jeden Befehl *und dessen Ergebnis*, also sieht eine Sitzung, die ein Ticket schon\nangelegt hat, die Kennung, die sie zurückbekam — statt es ein zweites Mal anzulegen. Kein\nWerkzeugaufruf wird je wiederabgespielt.\n\n**Vorher wird die Welt geprüft**, und das meiste, was ausgegeben wird, ist eine Entscheidung, gerade\nnicht zu starten. Eine Runde, die vor der Unterbrechung **schon beantwortet** war, läuft nicht noch\neinmal — der Normalfall, denn ein Kontingent trifft die teuren Züge, und ein teurer Zug ist meistens\ngerade fertig geworden. Eine Sitzung mit **lebendem Prozess** bleibt in Ruhe; das hält ein\nFortsetzen von Hand und den Dienst zur Rücksetzzeit auseinander. Eine **gewanderte Arbeitskopie**\nschickt eine Frage zurück an den Faden, der den Vorgang gestartet hat, statt blind weiterzumachen.\nUnd eine Laufzeit, die **ein begonnenes Gespräch nicht fortsetzen kann**, wird mit Namen abgelehnt,\nstatt still eine frische Sitzung ohne die Vorgeschichte zu bekommen.\n\nSolange das Fenster zu ist, wird die unfertige Arbeit des Vorgangs **auf einen Park-Zweig\ncommittet** und die Arbeitskopie weitergegeben — ein Wochenfenster dauert bis zu einer Woche, und\ndie einzige Arbeitskopie so lange zu halten, brächte alles andere zum Stehen. Das Fortsetzen holt\nsie zurück und meldet, ob die Basis sich darunter bewegt hat.\n\nZwei Grenzen, vorab gesagt. **Ignorierte Dateien** — `.env`, lokale Datenbanken, `node_modules` —\nsieht weder der Anker noch der Park. Und **zurück spult das Repository, nicht die Aufzeichnung**:\nangelegte Tickets bleiben angelegt, gesendete Nachrichten bleiben gesendet.\n\nFür einbettende Anwendungen: Fortsetzen ist eine **Fähigkeit des Providers**\n(`Worker::resumes_sessions`, Vorgabe `false`). Eine Laufzeit, die ihre eigenen Gespräche nicht\nfortsetzt, bekommt eine benannte Ablehnung statt eines stillen Neuanfangs — denn genau gegen den\nstillen Neuanfang schützt die Mitschrift. Kann sie es wirklich nicht, wird der Haltezustand\ngeschlossen und die Frage geht an den Faden, der den Vorgang gestartet hat, statt auf einer\nAblehnung stehenzubleiben, die sich nie ändert.\n\n**Was sich für einbettende Anwendungen ändert:** `SessionStanding` (aus `Engine::session_state`)\nbekommt ein Feld `interrupted` und wird `#[non_exhaustive]` — ein Struct-Literal oder eine\nvollständige Destrukturierung übersetzt also nicht mehr; das Lesen der Felder bleibt unberührt.\n`StatusThread` und `StatusOperation` bekommen `interrupted` ebenfalls, waren aber schon\n`#[non_exhaustive]`, dort ändert sich für Lesende nichts.", - "facade": "breaking" - } - ], - "notes": { - "en": "### Added\n- **A session that stops because the model went away now has a way back.** An exhausted quota, a\nprovider that cannot be reached, a broken connection: three causes, one state, and it is the state\nin which nothing is broken and nobody did anything wrong. Until now the runtime could not tell that\napart from a crash, so it posted an escalation in the agent's own name — *\"I cannot carry this\nout\"*, which was false — the round was discharged, and the operation stopped on `NEEDS DECISION`\nwith nothing able to restart it.\n\n**First, it can be read.** `nxc status` marks such an operation `INTERRUPTED` and the thread's row\nsays which window was hit and when it lifts; `nxc session state` says the same about the session;\n`--json` carries it as `interrupted` on both. That is the larger half: a session that ran into a\nweekly window used to report a clean `ended` with its thread reading `answered`, so the one thing\nnobody could establish was that anything had happened at all. Nothing is escalated any more, and the\nthread keeps owing its answer — because the session that owes it is coming back.\n\n**Then, `nxc resume --thread `** takes the operation up, and the background service does it by\nitself once the boundary falls. The session **continues with its own transcript** rather than\nstarting over, which is the safety argument and not a convenience: the transcript carries every\ncommand the session ran *and what it returned*, so a session that already created a ticket sees the\nid it got back instead of creating it twice. No tool call is ever replayed.\n\n**It checks the world before it starts anything**, and most of what it prints is a decision not to.\nA round that was **already answered** before the interruption is not run again — the common case,\nbecause a quota strikes the expensive turns and an expensive turn has usually just finished. A\nsession with a **live process** is left alone, which is what keeps a hand-run resume from colliding\nwith the service arriving at the reset time. A **working copy that moved** while the operation\nwaited sends a question back to the thread that started the operation instead of continuing blind.\nAnd a runtime that **cannot continue a conversation it began** is refused by name rather than\nquietly handed a fresh session with none of the history.\n\nWhile the window is closed, the operation's uncommitted work is **committed onto a park branch** and\nthe working copy handed on — a weekly window is up to a week, and holding this workspace's only\ncheckout that long would stop everything else. Resuming brings it back and reports whether the base\nmoved underneath it.\n\nTwo limits stated up front. **Ignored files** — `.env`, local databases, `node_modules` — are seen\nby neither the anchor nor the park. And **the repository rewinds; the record does not**: tickets that\nwere created stay created, and messages that were posted stay posted.\n\nFor hosts: continuation is a **provider capability** (`Worker::resumes_sessions`, default `false`).\nA runtime that does not continue its own conversations gets a named refusal instead of a silent\nfresh start — because the silent fresh start is exactly what the transcript protects against. If it\ngenuinely cannot, the hold is closed and the question goes to the thread that started the\noperation, rather than standing on a refusal that will never change.\n\n**What breaks for an embedding app:** `SessionStanding` (from `Engine::session_state`) gains an\n`interrupted` field and becomes `#[non_exhaustive]`, so a struct literal or an exhaustive\ndestructuring of it no longer compiles — reading its fields is unaffected. `StatusThread` and\n`StatusOperation` gain `interrupted` too and were already `#[non_exhaustive]`, so nothing there\nchanges for a reader.\n\n### Facade Contract\n- `breaking` · **A session that stops because the model went away now has a way back.** An exhausted quota, a\nprovider that cannot be reached, a broken connection: three causes, one state, and it is the state\nin which nothing is broken and nobody did anything wrong. Until now the runtime could not tell that\napart from a crash, so it posted an escalation in the agent's own name — *\"I cannot carry this\nout\"*, which was false — the round was discharged, and the operation stopped on `NEEDS DECISION`\nwith nothing able to restart it.\n\n**First, it can be read.** `nxc status` marks such an operation `INTERRUPTED` and the thread's row\nsays which window was hit and when it lifts; `nxc session state` says the same about the session;\n`--json` carries it as `interrupted` on both. That is the larger half: a session that ran into a\nweekly window used to report a clean `ended` with its thread reading `answered`, so the one thing\nnobody could establish was that anything had happened at all. Nothing is escalated any more, and the\nthread keeps owing its answer — because the session that owes it is coming back.\n\n**Then, `nxc resume --thread `** takes the operation up, and the background service does it by\nitself once the boundary falls. The session **continues with its own transcript** rather than\nstarting over, which is the safety argument and not a convenience: the transcript carries every\ncommand the session ran *and what it returned*, so a session that already created a ticket sees the\nid it got back instead of creating it twice. No tool call is ever replayed.\n\n**It checks the world before it starts anything**, and most of what it prints is a decision not to.\nA round that was **already answered** before the interruption is not run again — the common case,\nbecause a quota strikes the expensive turns and an expensive turn has usually just finished. A\nsession with a **live process** is left alone, which is what keeps a hand-run resume from colliding\nwith the service arriving at the reset time. A **working copy that moved** while the operation\nwaited sends a question back to the thread that started the operation instead of continuing blind.\nAnd a runtime that **cannot continue a conversation it began** is refused by name rather than\nquietly handed a fresh session with none of the history.\n\nWhile the window is closed, the operation's uncommitted work is **committed onto a park branch** and\nthe working copy handed on — a weekly window is up to a week, and holding this workspace's only\ncheckout that long would stop everything else. Resuming brings it back and reports whether the base\nmoved underneath it.\n\nTwo limits stated up front. **Ignored files** — `.env`, local databases, `node_modules` — are seen\nby neither the anchor nor the park. And **the repository rewinds; the record does not**: tickets that\nwere created stay created, and messages that were posted stay posted.\n\nFor hosts: continuation is a **provider capability** (`Worker::resumes_sessions`, default `false`).\nA runtime that does not continue its own conversations gets a named refusal instead of a silent\nfresh start — because the silent fresh start is exactly what the transcript protects against. If it\ngenuinely cannot, the hold is closed and the question goes to the thread that started the\noperation, rather than standing on a refusal that will never change.\n\n**What breaks for an embedding app:** `SessionStanding` (from `Engine::session_state`) gains an\n`interrupted` field and becomes `#[non_exhaustive]`, so a struct literal or an exhaustive\ndestructuring of it no longer compiles — reading its fields is unaffected. `StatusThread` and\n`StatusOperation` gain `interrupted` too and were already `#[non_exhaustive]`, so nothing there\nchanges for a reader.", - "de": "### Neu\n- **Eine Sitzung, die stehenbleibt, weil das Modell weg ist, hat jetzt einen Weg zurück.** Ein\nerschöpftes Kontingent, ein nicht erreichbarer Anbieter, ein unterbrochenes Netz: drei Ursachen, ein\nZustand — und es ist der Zustand, in dem nichts kaputt ist und niemand etwas falsch gemacht hat.\nBisher konnte die Laufzeit das nicht von einem Absturz unterscheiden und stellte eine Eskalation im\nNamen des Agenten ein — *„ich kann diese Aufgabe nicht ausführen\"*, was falsch war. Die Runde galt\nals erledigt, der Vorgang stand auf `NEEDS DECISION`, und nichts konnte ihn wieder anstoßen.\n\n**Zuerst ist es nachlesbar.** `nxc status` markiert so einen Vorgang mit `INTERRUPTED`, und die\nZeile des Fadens nennt die getroffene Grenze und wann sie fällt; `nxc session state` sagt dasselbe\nüber die Sitzung, `--json` trägt es auf beiden als `interrupted`. Das ist die größere Hälfte: eine\nSitzung, die ins Wochenkontingent lief, meldete vorher ein sauberes `ended` und ihr Faden\n`answered` — das Einzige, was niemand feststellen konnte, war, dass überhaupt etwas passiert war.\nNichts wird mehr eskaliert, und der Faden schuldet weiterhin seine Antwort, weil die Sitzung, die\nsie schuldet, zurückkommt.\n\n**Dann nimmt `nxc resume --thread `** den Vorgang wieder auf — und der Hintergrunddienst tut es\nvon allein, sobald die Grenze fällt. Die Sitzung **macht mit ihrer eigenen Mitschrift weiter**,\nstatt von vorn anzufangen; das ist die Sicherheitsbegründung und keine Bequemlichkeit: die\nMitschrift trägt jeden Befehl *und dessen Ergebnis*, also sieht eine Sitzung, die ein Ticket schon\nangelegt hat, die Kennung, die sie zurückbekam — statt es ein zweites Mal anzulegen. Kein\nWerkzeugaufruf wird je wiederabgespielt.\n\n**Vorher wird die Welt geprüft**, und das meiste, was ausgegeben wird, ist eine Entscheidung, gerade\nnicht zu starten. Eine Runde, die vor der Unterbrechung **schon beantwortet** war, läuft nicht noch\neinmal — der Normalfall, denn ein Kontingent trifft die teuren Züge, und ein teurer Zug ist meistens\ngerade fertig geworden. Eine Sitzung mit **lebendem Prozess** bleibt in Ruhe; das hält ein\nFortsetzen von Hand und den Dienst zur Rücksetzzeit auseinander. Eine **gewanderte Arbeitskopie**\nschickt eine Frage zurück an den Faden, der den Vorgang gestartet hat, statt blind weiterzumachen.\nUnd eine Laufzeit, die **ein begonnenes Gespräch nicht fortsetzen kann**, wird mit Namen abgelehnt,\nstatt still eine frische Sitzung ohne die Vorgeschichte zu bekommen.\n\nSolange das Fenster zu ist, wird die unfertige Arbeit des Vorgangs **auf einen Park-Zweig\ncommittet** und die Arbeitskopie weitergegeben — ein Wochenfenster dauert bis zu einer Woche, und\ndie einzige Arbeitskopie so lange zu halten, brächte alles andere zum Stehen. Das Fortsetzen holt\nsie zurück und meldet, ob die Basis sich darunter bewegt hat.\n\nZwei Grenzen, vorab gesagt. **Ignorierte Dateien** — `.env`, lokale Datenbanken, `node_modules` —\nsieht weder der Anker noch der Park. Und **zurück spult das Repository, nicht die Aufzeichnung**:\nangelegte Tickets bleiben angelegt, gesendete Nachrichten bleiben gesendet.\n\nFür einbettende Anwendungen: Fortsetzen ist eine **Fähigkeit des Providers**\n(`Worker::resumes_sessions`, Vorgabe `false`). Eine Laufzeit, die ihre eigenen Gespräche nicht\nfortsetzt, bekommt eine benannte Ablehnung statt eines stillen Neuanfangs — denn genau gegen den\nstillen Neuanfang schützt die Mitschrift. Kann sie es wirklich nicht, wird der Haltezustand\ngeschlossen und die Frage geht an den Faden, der den Vorgang gestartet hat, statt auf einer\nAblehnung stehenzubleiben, die sich nie ändert.\n\n**Was sich für einbettende Anwendungen ändert:** `SessionStanding` (aus `Engine::session_state`)\nbekommt ein Feld `interrupted` und wird `#[non_exhaustive]` — ein Struct-Literal oder eine\nvollständige Destrukturierung übersetzt also nicht mehr; das Lesen der Felder bleibt unberührt.\n`StatusThread` und `StatusOperation` bekommen `interrupted` ebenfalls, waren aber schon\n`#[non_exhaustive]`, dort ändert sich für Lesende nichts.\n\n### Facade-Kontrakt\n- `breaking` · **Eine Sitzung, die stehenbleibt, weil das Modell weg ist, hat jetzt einen Weg zurück.** Ein\nerschöpftes Kontingent, ein nicht erreichbarer Anbieter, ein unterbrochenes Netz: drei Ursachen, ein\nZustand — und es ist der Zustand, in dem nichts kaputt ist und niemand etwas falsch gemacht hat.\nBisher konnte die Laufzeit das nicht von einem Absturz unterscheiden und stellte eine Eskalation im\nNamen des Agenten ein — *„ich kann diese Aufgabe nicht ausführen\"*, was falsch war. Die Runde galt\nals erledigt, der Vorgang stand auf `NEEDS DECISION`, und nichts konnte ihn wieder anstoßen.\n\n**Zuerst ist es nachlesbar.** `nxc status` markiert so einen Vorgang mit `INTERRUPTED`, und die\nZeile des Fadens nennt die getroffene Grenze und wann sie fällt; `nxc session state` sagt dasselbe\nüber die Sitzung, `--json` trägt es auf beiden als `interrupted`. Das ist die größere Hälfte: eine\nSitzung, die ins Wochenkontingent lief, meldete vorher ein sauberes `ended` und ihr Faden\n`answered` — das Einzige, was niemand feststellen konnte, war, dass überhaupt etwas passiert war.\nNichts wird mehr eskaliert, und der Faden schuldet weiterhin seine Antwort, weil die Sitzung, die\nsie schuldet, zurückkommt.\n\n**Dann nimmt `nxc resume --thread `** den Vorgang wieder auf — und der Hintergrunddienst tut es\nvon allein, sobald die Grenze fällt. Die Sitzung **macht mit ihrer eigenen Mitschrift weiter**,\nstatt von vorn anzufangen; das ist die Sicherheitsbegründung und keine Bequemlichkeit: die\nMitschrift trägt jeden Befehl *und dessen Ergebnis*, also sieht eine Sitzung, die ein Ticket schon\nangelegt hat, die Kennung, die sie zurückbekam — statt es ein zweites Mal anzulegen. Kein\nWerkzeugaufruf wird je wiederabgespielt.\n\n**Vorher wird die Welt geprüft**, und das meiste, was ausgegeben wird, ist eine Entscheidung, gerade\nnicht zu starten. Eine Runde, die vor der Unterbrechung **schon beantwortet** war, läuft nicht noch\neinmal — der Normalfall, denn ein Kontingent trifft die teuren Züge, und ein teurer Zug ist meistens\ngerade fertig geworden. Eine Sitzung mit **lebendem Prozess** bleibt in Ruhe; das hält ein\nFortsetzen von Hand und den Dienst zur Rücksetzzeit auseinander. Eine **gewanderte Arbeitskopie**\nschickt eine Frage zurück an den Faden, der den Vorgang gestartet hat, statt blind weiterzumachen.\nUnd eine Laufzeit, die **ein begonnenes Gespräch nicht fortsetzen kann**, wird mit Namen abgelehnt,\nstatt still eine frische Sitzung ohne die Vorgeschichte zu bekommen.\n\nSolange das Fenster zu ist, wird die unfertige Arbeit des Vorgangs **auf einen Park-Zweig\ncommittet** und die Arbeitskopie weitergegeben — ein Wochenfenster dauert bis zu einer Woche, und\ndie einzige Arbeitskopie so lange zu halten, brächte alles andere zum Stehen. Das Fortsetzen holt\nsie zurück und meldet, ob die Basis sich darunter bewegt hat.\n\nZwei Grenzen, vorab gesagt. **Ignorierte Dateien** — `.env`, lokale Datenbanken, `node_modules` —\nsieht weder der Anker noch der Park. Und **zurück spult das Repository, nicht die Aufzeichnung**:\nangelegte Tickets bleiben angelegt, gesendete Nachrichten bleiben gesendet.\n\nFür einbettende Anwendungen: Fortsetzen ist eine **Fähigkeit des Providers**\n(`Worker::resumes_sessions`, Vorgabe `false`). Eine Laufzeit, die ihre eigenen Gespräche nicht\nfortsetzt, bekommt eine benannte Ablehnung statt eines stillen Neuanfangs — denn genau gegen den\nstillen Neuanfang schützt die Mitschrift. Kann sie es wirklich nicht, wird der Haltezustand\ngeschlossen und die Frage geht an den Faden, der den Vorgang gestartet hat, statt auf einer\nAblehnung stehenzubleiben, die sich nie ändert.\n\n**Was sich für einbettende Anwendungen ändert:** `SessionStanding` (aus `Engine::session_state`)\nbekommt ein Feld `interrupted` und wird `#[non_exhaustive]` — ein Struct-Literal oder eine\nvollständige Destrukturierung übersetzt also nicht mehr; das Lesen der Felder bleibt unberührt.\n`StatusThread` und `StatusOperation` bekommen `interrupted` ebenfalls, waren aber schon\n`#[non_exhaustive]`, dort ändert sich für Lesende nichts." - } - }, - { - "version": "0.97.0", - "date": "2026-09-14", - "items": [ - { - "type": "added", - "en": "**Every `nxc send` and `nxc reply` now records where the working copy stood.** The commit `HEAD`\npointed at, the branch it was on, whether anything was uncommitted, and a fingerprint over\n`HEAD` + diff + `git status` that makes the whole state comparable later — written onto the message\nitself, so it comes back wherever the message does. `nxc threads show` prints it under the message\n(`working copy: 9f2c1ab4e7d0 on feat/parser · uncommitted work NOT saved anywhere`), `nxc status` as\na suffix on the thread's row, and `--json` as `refs.working_copy` / `working_copy`.\n\nIt answers three questions the channel could not. **What did this reviewer actually have in front of\nit?** — until now you could only guess from the git log. **Where do I continue an operation that was\ninterrupted?** — a fresh session can compare the tree it finds against the state the handover\nrecorded and see whether it has drifted. And **which commit does this round rewind to?**\n\nThree limits, stated up front rather than met later. A dirty tree is **recorded, not saved**: an\nagent hands over mid-work, so the commit names where the work started from and the record says\n`uncommitted work NOT saved anywhere` — nothing about a handover commits anything, and your branch,\n`HEAD` and unstaged changes are exactly as you left them. What **git does not see, this does not\nsee** — ignored files above all (`.env`, a local database, `node_modules`). And **the repository\nrewinds, the record does not**: an anchor names a commit for the working copy, never for the board,\nthe channel or anything that left the machine, and `.nxs/` is explicitly outside it.\n\nTwo robustness notes about how it reads git, because both decide whether the record can be\nbelieved. **The reads are byte-exact**: a tracked file in Windows-1252 or Shift-JIS makes `git diff`\nemit something that is not text, and a text-only reader would have handed back an empty answer for a\ncommand that succeeded — a dirty tree reported as clean. And **a command whose output cannot be\ncollected is an error, not an empty answer**, for the same reason. The diff is taken with\n`--no-textconv`, so a repository's own converter program is never run on this path and the\nfingerprint stays a function of the tree rather than of that program.\n\n**Fixed along the way: `nxc tick` could never park.** The command-line worker wrapper did not forward\n\"which directory do sessions run in\", so on the shipped path the engine believed this host runs its\nsessions nowhere it can see — and an unanswered escalation holding the working copy under contention\nwas never committed to a branch and the copy never handed on, although `nxc status` reported the\nhold. A workspace whose runtime genuinely names no directory records no anchors and says so once, in\na closing `note:` line on `nxc status` (`worker_names_a_working_copy` in `--json`).\n\nLibrary consumers: `Refs` and `StatusThread` gain a field, `StatusReport` gains\n`worker_names_a_working_copy`, and the new `nexus_chat::anchor` module carries `Anchor`,\n`Anchor::drift_from` and `Drift`.", - "de": "**Jedes `nxc send` und `nxc reply` hält jetzt fest, wo die Arbeitskopie stand.** Den Commit, auf den\n`HEAD` zeigte, den Zweig, ob etwas nicht eingecheckt war, und einen Fingerabdruck über\n`HEAD` + Diff + `git status`, der den ganzen Zustand später vergleichbar macht — an der Nachricht\nselbst, damit es überall zurückkommt, wo die Nachricht zurückkommt. `nxc threads show` zeigt es unter\nder Nachricht (`working copy: 9f2c1ab4e7d0 on feat/parser · uncommitted work NOT saved anywhere`),\n`nxc status` als Zusatz an der Zeile des Fadens, `--json` als `refs.working_copy` / `working_copy`.\n\nEs beantwortet drei Fragen, die der Kanal nicht beantworten konnte. **Welchen Stand hatte dieser\nPrüfer eigentlich vor sich?** — bisher nur aus dem git-Log zu erraten. **Wo setze ich eine\nunterbrochene Operation fort?** — eine frische Sitzung kann den Baum, den sie findet, mit dem\nfestgehaltenen Zustand vergleichen und sehen, ob er gewandert ist. Und **auf welchen Commit spult\ndiese Runde zurück?**\n\nDrei Grenzen, vorher gesagt statt hinterher erlebt. Ein schmutziger Baum wird **notiert, nicht\ngesichert**: ein Agent übergibt mitten in der Arbeit, der Commit benennt also, wovon die Arbeit\nausging, und der Eintrag sagt `uncommitted work NOT saved anywhere` — eine Übergabe committet nichts,\nZweig, `HEAD` und ungestagte Änderungen sind danach genau, wie Sie sie verlassen haben. Was **git\nnicht sieht, sieht auch das nicht** — vor allem ignorierte Dateien (`.env`, lokale Datenbanken,\n`node_modules`). Und **das Repo spult zurück, die Aufzeichnung nicht**: ein Anker benennt einen\nCommit für die Arbeitskopie, nie für das Brett, den Kanal oder etwas, das die Maschine verlassen hat\n— und `.nxs/` liegt ausdrücklich außerhalb.\n\nZwei Robustheits-Punkte dazu, wie git gelesen wird — beide entscheiden, ob der Eintrag zu glauben\nist. **Die Lesezugriffe sind byte-genau**: eine Datei in Windows-1252 oder Shift-JIS lässt\n`git diff` etwas ausgeben, das kein Text ist, und ein reiner Text-Leser hätte für einen\nerfolgreichen Befehl eine leere Antwort zurückgegeben — ein schmutziger Baum, gemeldet als sauber.\nUnd **ein Befehl, dessen Ausgabe nicht eingesammelt werden kann, ist ein Fehler und keine leere\nAntwort**, aus demselben Grund. Der Diff wird mit `--no-textconv` genommen, damit nie ein\nKonverter-Programm des Repos auf diesem Pfad läuft und der Fingerabdruck eine Funktion des Baums\nbleibt statt dieses Programms.\n\n**Nebenbei behoben: `nxc tick` konnte nie parken.** Die Worker-Hülle der Kommandozeile gab „in\nwelchem Verzeichnis laufen die Sitzungen\" nicht weiter, auf dem ausgelieferten Pfad glaubte die\nEngine also, dieser Host starte seine Sitzungen nirgends, wo sie hinsehen kann — eine unbeantwortete\nEskalation, die die Arbeitskopie unter Andrang hielt, wurde nie auf einen Zweig committet und die\nKopie nie weitergegeben, obwohl `nxc status` den Halt meldete. Ein Workspace, dessen Laufzeit\nwirklich kein Verzeichnis benennt, hält keine Anker fest und sagt das einmal in einer abschließenden\n`note:`-Zeile von `nxc status` (`worker_names_a_working_copy` im `--json`).\n\nFür Library-Konsumenten: `Refs` und `StatusThread` bekommen je ein Feld, `StatusReport` bekommt\n`worker_names_a_working_copy`, und das neue Modul `nexus_chat::anchor` trägt `Anchor`,\n`Anchor::drift_from` und `Drift`.", - "facade": "breaking" - } - ], - "notes": { - "en": "### Added\n- **Every `nxc send` and `nxc reply` now records where the working copy stood.** The commit `HEAD`\npointed at, the branch it was on, whether anything was uncommitted, and a fingerprint over\n`HEAD` + diff + `git status` that makes the whole state comparable later — written onto the message\nitself, so it comes back wherever the message does. `nxc threads show` prints it under the message\n(`working copy: 9f2c1ab4e7d0 on feat/parser · uncommitted work NOT saved anywhere`), `nxc status` as\na suffix on the thread's row, and `--json` as `refs.working_copy` / `working_copy`.\n\nIt answers three questions the channel could not. **What did this reviewer actually have in front of\nit?** — until now you could only guess from the git log. **Where do I continue an operation that was\ninterrupted?** — a fresh session can compare the tree it finds against the state the handover\nrecorded and see whether it has drifted. And **which commit does this round rewind to?**\n\nThree limits, stated up front rather than met later. A dirty tree is **recorded, not saved**: an\nagent hands over mid-work, so the commit names where the work started from and the record says\n`uncommitted work NOT saved anywhere` — nothing about a handover commits anything, and your branch,\n`HEAD` and unstaged changes are exactly as you left them. What **git does not see, this does not\nsee** — ignored files above all (`.env`, a local database, `node_modules`). And **the repository\nrewinds, the record does not**: an anchor names a commit for the working copy, never for the board,\nthe channel or anything that left the machine, and `.nxs/` is explicitly outside it.\n\nTwo robustness notes about how it reads git, because both decide whether the record can be\nbelieved. **The reads are byte-exact**: a tracked file in Windows-1252 or Shift-JIS makes `git diff`\nemit something that is not text, and a text-only reader would have handed back an empty answer for a\ncommand that succeeded — a dirty tree reported as clean. And **a command whose output cannot be\ncollected is an error, not an empty answer**, for the same reason. The diff is taken with\n`--no-textconv`, so a repository's own converter program is never run on this path and the\nfingerprint stays a function of the tree rather than of that program.\n\n**Fixed along the way: `nxc tick` could never park.** The command-line worker wrapper did not forward\n\"which directory do sessions run in\", so on the shipped path the engine believed this host runs its\nsessions nowhere it can see — and an unanswered escalation holding the working copy under contention\nwas never committed to a branch and the copy never handed on, although `nxc status` reported the\nhold. A workspace whose runtime genuinely names no directory records no anchors and says so once, in\na closing `note:` line on `nxc status` (`worker_names_a_working_copy` in `--json`).\n\nLibrary consumers: `Refs` and `StatusThread` gain a field, `StatusReport` gains\n`worker_names_a_working_copy`, and the new `nexus_chat::anchor` module carries `Anchor`,\n`Anchor::drift_from` and `Drift`.\n\n### Facade Contract\n- `breaking` · **Every `nxc send` and `nxc reply` now records where the working copy stood.** The commit `HEAD`\npointed at, the branch it was on, whether anything was uncommitted, and a fingerprint over\n`HEAD` + diff + `git status` that makes the whole state comparable later — written onto the message\nitself, so it comes back wherever the message does. `nxc threads show` prints it under the message\n(`working copy: 9f2c1ab4e7d0 on feat/parser · uncommitted work NOT saved anywhere`), `nxc status` as\na suffix on the thread's row, and `--json` as `refs.working_copy` / `working_copy`.\n\nIt answers three questions the channel could not. **What did this reviewer actually have in front of\nit?** — until now you could only guess from the git log. **Where do I continue an operation that was\ninterrupted?** — a fresh session can compare the tree it finds against the state the handover\nrecorded and see whether it has drifted. And **which commit does this round rewind to?**\n\nThree limits, stated up front rather than met later. A dirty tree is **recorded, not saved**: an\nagent hands over mid-work, so the commit names where the work started from and the record says\n`uncommitted work NOT saved anywhere` — nothing about a handover commits anything, and your branch,\n`HEAD` and unstaged changes are exactly as you left them. What **git does not see, this does not\nsee** — ignored files above all (`.env`, a local database, `node_modules`). And **the repository\nrewinds, the record does not**: an anchor names a commit for the working copy, never for the board,\nthe channel or anything that left the machine, and `.nxs/` is explicitly outside it.\n\nTwo robustness notes about how it reads git, because both decide whether the record can be\nbelieved. **The reads are byte-exact**: a tracked file in Windows-1252 or Shift-JIS makes `git diff`\nemit something that is not text, and a text-only reader would have handed back an empty answer for a\ncommand that succeeded — a dirty tree reported as clean. And **a command whose output cannot be\ncollected is an error, not an empty answer**, for the same reason. The diff is taken with\n`--no-textconv`, so a repository's own converter program is never run on this path and the\nfingerprint stays a function of the tree rather than of that program.\n\n**Fixed along the way: `nxc tick` could never park.** The command-line worker wrapper did not forward\n\"which directory do sessions run in\", so on the shipped path the engine believed this host runs its\nsessions nowhere it can see — and an unanswered escalation holding the working copy under contention\nwas never committed to a branch and the copy never handed on, although `nxc status` reported the\nhold. A workspace whose runtime genuinely names no directory records no anchors and says so once, in\na closing `note:` line on `nxc status` (`worker_names_a_working_copy` in `--json`).\n\nLibrary consumers: `Refs` and `StatusThread` gain a field, `StatusReport` gains\n`worker_names_a_working_copy`, and the new `nexus_chat::anchor` module carries `Anchor`,\n`Anchor::drift_from` and `Drift`.", - "de": "### Neu\n- **Jedes `nxc send` und `nxc reply` hält jetzt fest, wo die Arbeitskopie stand.** Den Commit, auf den\n`HEAD` zeigte, den Zweig, ob etwas nicht eingecheckt war, und einen Fingerabdruck über\n`HEAD` + Diff + `git status`, der den ganzen Zustand später vergleichbar macht — an der Nachricht\nselbst, damit es überall zurückkommt, wo die Nachricht zurückkommt. `nxc threads show` zeigt es unter\nder Nachricht (`working copy: 9f2c1ab4e7d0 on feat/parser · uncommitted work NOT saved anywhere`),\n`nxc status` als Zusatz an der Zeile des Fadens, `--json` als `refs.working_copy` / `working_copy`.\n\nEs beantwortet drei Fragen, die der Kanal nicht beantworten konnte. **Welchen Stand hatte dieser\nPrüfer eigentlich vor sich?** — bisher nur aus dem git-Log zu erraten. **Wo setze ich eine\nunterbrochene Operation fort?** — eine frische Sitzung kann den Baum, den sie findet, mit dem\nfestgehaltenen Zustand vergleichen und sehen, ob er gewandert ist. Und **auf welchen Commit spult\ndiese Runde zurück?**\n\nDrei Grenzen, vorher gesagt statt hinterher erlebt. Ein schmutziger Baum wird **notiert, nicht\ngesichert**: ein Agent übergibt mitten in der Arbeit, der Commit benennt also, wovon die Arbeit\nausging, und der Eintrag sagt `uncommitted work NOT saved anywhere` — eine Übergabe committet nichts,\nZweig, `HEAD` und ungestagte Änderungen sind danach genau, wie Sie sie verlassen haben. Was **git\nnicht sieht, sieht auch das nicht** — vor allem ignorierte Dateien (`.env`, lokale Datenbanken,\n`node_modules`). Und **das Repo spult zurück, die Aufzeichnung nicht**: ein Anker benennt einen\nCommit für die Arbeitskopie, nie für das Brett, den Kanal oder etwas, das die Maschine verlassen hat\n— und `.nxs/` liegt ausdrücklich außerhalb.\n\nZwei Robustheits-Punkte dazu, wie git gelesen wird — beide entscheiden, ob der Eintrag zu glauben\nist. **Die Lesezugriffe sind byte-genau**: eine Datei in Windows-1252 oder Shift-JIS lässt\n`git diff` etwas ausgeben, das kein Text ist, und ein reiner Text-Leser hätte für einen\nerfolgreichen Befehl eine leere Antwort zurückgegeben — ein schmutziger Baum, gemeldet als sauber.\nUnd **ein Befehl, dessen Ausgabe nicht eingesammelt werden kann, ist ein Fehler und keine leere\nAntwort**, aus demselben Grund. Der Diff wird mit `--no-textconv` genommen, damit nie ein\nKonverter-Programm des Repos auf diesem Pfad läuft und der Fingerabdruck eine Funktion des Baums\nbleibt statt dieses Programms.\n\n**Nebenbei behoben: `nxc tick` konnte nie parken.** Die Worker-Hülle der Kommandozeile gab „in\nwelchem Verzeichnis laufen die Sitzungen\" nicht weiter, auf dem ausgelieferten Pfad glaubte die\nEngine also, dieser Host starte seine Sitzungen nirgends, wo sie hinsehen kann — eine unbeantwortete\nEskalation, die die Arbeitskopie unter Andrang hielt, wurde nie auf einen Zweig committet und die\nKopie nie weitergegeben, obwohl `nxc status` den Halt meldete. Ein Workspace, dessen Laufzeit\nwirklich kein Verzeichnis benennt, hält keine Anker fest und sagt das einmal in einer abschließenden\n`note:`-Zeile von `nxc status` (`worker_names_a_working_copy` im `--json`).\n\nFür Library-Konsumenten: `Refs` und `StatusThread` bekommen je ein Feld, `StatusReport` bekommt\n`worker_names_a_working_copy`, und das neue Modul `nexus_chat::anchor` trägt `Anchor`,\n`Anchor::drift_from` und `Drift`.\n\n### Facade-Kontrakt\n- `breaking` · **Jedes `nxc send` und `nxc reply` hält jetzt fest, wo die Arbeitskopie stand.** Den Commit, auf den\n`HEAD` zeigte, den Zweig, ob etwas nicht eingecheckt war, und einen Fingerabdruck über\n`HEAD` + Diff + `git status`, der den ganzen Zustand später vergleichbar macht — an der Nachricht\nselbst, damit es überall zurückkommt, wo die Nachricht zurückkommt. `nxc threads show` zeigt es unter\nder Nachricht (`working copy: 9f2c1ab4e7d0 on feat/parser · uncommitted work NOT saved anywhere`),\n`nxc status` als Zusatz an der Zeile des Fadens, `--json` als `refs.working_copy` / `working_copy`.\n\nEs beantwortet drei Fragen, die der Kanal nicht beantworten konnte. **Welchen Stand hatte dieser\nPrüfer eigentlich vor sich?** — bisher nur aus dem git-Log zu erraten. **Wo setze ich eine\nunterbrochene Operation fort?** — eine frische Sitzung kann den Baum, den sie findet, mit dem\nfestgehaltenen Zustand vergleichen und sehen, ob er gewandert ist. Und **auf welchen Commit spult\ndiese Runde zurück?**\n\nDrei Grenzen, vorher gesagt statt hinterher erlebt. Ein schmutziger Baum wird **notiert, nicht\ngesichert**: ein Agent übergibt mitten in der Arbeit, der Commit benennt also, wovon die Arbeit\nausging, und der Eintrag sagt `uncommitted work NOT saved anywhere` — eine Übergabe committet nichts,\nZweig, `HEAD` und ungestagte Änderungen sind danach genau, wie Sie sie verlassen haben. Was **git\nnicht sieht, sieht auch das nicht** — vor allem ignorierte Dateien (`.env`, lokale Datenbanken,\n`node_modules`). Und **das Repo spult zurück, die Aufzeichnung nicht**: ein Anker benennt einen\nCommit für die Arbeitskopie, nie für das Brett, den Kanal oder etwas, das die Maschine verlassen hat\n— und `.nxs/` liegt ausdrücklich außerhalb.\n\nZwei Robustheits-Punkte dazu, wie git gelesen wird — beide entscheiden, ob der Eintrag zu glauben\nist. **Die Lesezugriffe sind byte-genau**: eine Datei in Windows-1252 oder Shift-JIS lässt\n`git diff` etwas ausgeben, das kein Text ist, und ein reiner Text-Leser hätte für einen\nerfolgreichen Befehl eine leere Antwort zurückgegeben — ein schmutziger Baum, gemeldet als sauber.\nUnd **ein Befehl, dessen Ausgabe nicht eingesammelt werden kann, ist ein Fehler und keine leere\nAntwort**, aus demselben Grund. Der Diff wird mit `--no-textconv` genommen, damit nie ein\nKonverter-Programm des Repos auf diesem Pfad läuft und der Fingerabdruck eine Funktion des Baums\nbleibt statt dieses Programms.\n\n**Nebenbei behoben: `nxc tick` konnte nie parken.** Die Worker-Hülle der Kommandozeile gab „in\nwelchem Verzeichnis laufen die Sitzungen\" nicht weiter, auf dem ausgelieferten Pfad glaubte die\nEngine also, dieser Host starte seine Sitzungen nirgends, wo sie hinsehen kann — eine unbeantwortete\nEskalation, die die Arbeitskopie unter Andrang hielt, wurde nie auf einen Zweig committet und die\nKopie nie weitergegeben, obwohl `nxc status` den Halt meldete. Ein Workspace, dessen Laufzeit\nwirklich kein Verzeichnis benennt, hält keine Anker fest und sagt das einmal in einer abschließenden\n`note:`-Zeile von `nxc status` (`worker_names_a_working_copy` im `--json`).\n\nFür Library-Konsumenten: `Refs` und `StatusThread` bekommen je ein Feld, `StatusReport` bekommt\n`worker_names_a_working_copy`, und das neue Modul `nexus_chat::anchor` trägt `Anchor`,\n`Anchor::drift_from` und `Drift`." - } - }, - { - "version": "0.96.0", - "date": "2026-09-13", - "items": [ - { - "type": "added", - "en": "**A persona can now say WHO may address it, and a channel declares its cast once.** `addressable:`\ngained two forms beside `general`: `none` (nobody directly — come through a channel), and a\nwhitelist that names callers, `{personas: [pm], humans: true}`. An omitted half names nobody of that\nclass, so `{humans: true}` is \"the person at the terminal may message me, every agent goes through\nthe round\" and `{personas: [head]}` is the reverse. The old channel-list form still parses and still\nmeans \"nobody directly\", but it is deprecated — `nxs prime` now says what to write instead.\nAlongside it, **naming a persona in a channel's step is what puts it in the channel**: the cast is\nthe step targets plus whatever `members:` adds, so a step target no longer has to be repeated under\n`members:` and the channel no longer has to be repeated at the persona. That closes a real hole — a\nstep target absent from `members:` used to be commissioned and able to reply, but was refused when\nit tried to *read* the thread it had been put on. Nothing breaks: every declaration written before\nthis means exactly what it meant. Finally, persona files are now checked referentially when the\ncatalogue is read: an `addressable:` entry naming no declared channel, or a whitelist naming no\ndeclared persona, is reported by `nxs prime` instead of surfacing as a refused send much later.", - "de": "**Eine Persona kann jetzt sagen, WER sie ansprechen darf, und ein Kanal erklärt seine Besetzung\neinmal.** `addressable:` hat neben `general` zwei Formen dazubekommen: `none` (niemand direkt —\nkomm über einen Kanal) und eine Erlaubnisliste, die Aufrufer benennt: `{personas: [pm], humans:\ntrue}`. Eine weggelassene Hälfte nennt niemanden dieser Klasse, `{humans: true}` heißt also „der\nMensch am Terminal darf mir schreiben, jeder Agent geht durch die Runde\" und `{personas: [head]}`\ngenau umgekehrt. Die alte Kanalliste wird weiterhin gelesen und heißt weiterhin „niemand direkt\",\nist aber veraltet — `nxs prime` sagt jetzt, was stattdessen dasteht. Dazu gilt: **wer in einem\nSchritt eines Kanals genannt ist, gehört damit zum Kanal.** Die Besetzung sind die Schrittziele plus\ndas, was `members:` hinzufügt; ein Schrittziel muss also nicht mehr unter `members:` wiederholt und\nder Kanal nicht mehr bei der Persona genannt werden. Das schließt eine echte Lücke: ein Schrittziel,\ndas in `members:` fehlte, wurde beauftragt und durfte antworten, wurde aber abgewiesen, als es den\nFaden *lesen* wollte, auf den man es gesetzt hatte. Nichts bricht — jede zuvor geschriebene\nDeklaration bedeutet genau das, was sie bedeutet hat. Schließlich werden Persona-Dateien beim Lesen\ndes Katalogs referenziell geprüft: ein `addressable:`-Eintrag, der keinen deklarierten Kanal nennt,\noder eine Erlaubnisliste, die keine deklarierte Persona nennt, wird von `nxs prime` gemeldet statt\nerst viel später als abgelehnte Sendung aufzufallen.", - "facade": "breaking" - } - ], - "notes": { - "en": "### Added\n- **A persona can now say WHO may address it, and a channel declares its cast once.** `addressable:`\ngained two forms beside `general`: `none` (nobody directly — come through a channel), and a\nwhitelist that names callers, `{personas: [pm], humans: true}`. An omitted half names nobody of that\nclass, so `{humans: true}` is \"the person at the terminal may message me, every agent goes through\nthe round\" and `{personas: [head]}` is the reverse. The old channel-list form still parses and still\nmeans \"nobody directly\", but it is deprecated — `nxs prime` now says what to write instead.\nAlongside it, **naming a persona in a channel's step is what puts it in the channel**: the cast is\nthe step targets plus whatever `members:` adds, so a step target no longer has to be repeated under\n`members:` and the channel no longer has to be repeated at the persona. That closes a real hole — a\nstep target absent from `members:` used to be commissioned and able to reply, but was refused when\nit tried to *read* the thread it had been put on. Nothing breaks: every declaration written before\nthis means exactly what it meant. Finally, persona files are now checked referentially when the\ncatalogue is read: an `addressable:` entry naming no declared channel, or a whitelist naming no\ndeclared persona, is reported by `nxs prime` instead of surfacing as a refused send much later.\n\n### Facade Contract\n- `breaking` · **A persona can now say WHO may address it, and a channel declares its cast once.** `addressable:`\ngained two forms beside `general`: `none` (nobody directly — come through a channel), and a\nwhitelist that names callers, `{personas: [pm], humans: true}`. An omitted half names nobody of that\nclass, so `{humans: true}` is \"the person at the terminal may message me, every agent goes through\nthe round\" and `{personas: [head]}` is the reverse. The old channel-list form still parses and still\nmeans \"nobody directly\", but it is deprecated — `nxs prime` now says what to write instead.\nAlongside it, **naming a persona in a channel's step is what puts it in the channel**: the cast is\nthe step targets plus whatever `members:` adds, so a step target no longer has to be repeated under\n`members:` and the channel no longer has to be repeated at the persona. That closes a real hole — a\nstep target absent from `members:` used to be commissioned and able to reply, but was refused when\nit tried to *read* the thread it had been put on. Nothing breaks: every declaration written before\nthis means exactly what it meant. Finally, persona files are now checked referentially when the\ncatalogue is read: an `addressable:` entry naming no declared channel, or a whitelist naming no\ndeclared persona, is reported by `nxs prime` instead of surfacing as a refused send much later.", - "de": "### Neu\n- **Eine Persona kann jetzt sagen, WER sie ansprechen darf, und ein Kanal erklärt seine Besetzung\neinmal.** `addressable:` hat neben `general` zwei Formen dazubekommen: `none` (niemand direkt —\nkomm über einen Kanal) und eine Erlaubnisliste, die Aufrufer benennt: `{personas: [pm], humans:\ntrue}`. Eine weggelassene Hälfte nennt niemanden dieser Klasse, `{humans: true}` heißt also „der\nMensch am Terminal darf mir schreiben, jeder Agent geht durch die Runde\" und `{personas: [head]}`\ngenau umgekehrt. Die alte Kanalliste wird weiterhin gelesen und heißt weiterhin „niemand direkt\",\nist aber veraltet — `nxs prime` sagt jetzt, was stattdessen dasteht. Dazu gilt: **wer in einem\nSchritt eines Kanals genannt ist, gehört damit zum Kanal.** Die Besetzung sind die Schrittziele plus\ndas, was `members:` hinzufügt; ein Schrittziel muss also nicht mehr unter `members:` wiederholt und\nder Kanal nicht mehr bei der Persona genannt werden. Das schließt eine echte Lücke: ein Schrittziel,\ndas in `members:` fehlte, wurde beauftragt und durfte antworten, wurde aber abgewiesen, als es den\nFaden *lesen* wollte, auf den man es gesetzt hatte. Nichts bricht — jede zuvor geschriebene\nDeklaration bedeutet genau das, was sie bedeutet hat. Schließlich werden Persona-Dateien beim Lesen\ndes Katalogs referenziell geprüft: ein `addressable:`-Eintrag, der keinen deklarierten Kanal nennt,\noder eine Erlaubnisliste, die keine deklarierte Persona nennt, wird von `nxs prime` gemeldet statt\nerst viel später als abgelehnte Sendung aufzufallen.\n\n### Facade-Kontrakt\n- `breaking` · **Eine Persona kann jetzt sagen, WER sie ansprechen darf, und ein Kanal erklärt seine Besetzung\neinmal.** `addressable:` hat neben `general` zwei Formen dazubekommen: `none` (niemand direkt —\nkomm über einen Kanal) und eine Erlaubnisliste, die Aufrufer benennt: `{personas: [pm], humans:\ntrue}`. Eine weggelassene Hälfte nennt niemanden dieser Klasse, `{humans: true}` heißt also „der\nMensch am Terminal darf mir schreiben, jeder Agent geht durch die Runde\" und `{personas: [head]}`\ngenau umgekehrt. Die alte Kanalliste wird weiterhin gelesen und heißt weiterhin „niemand direkt\",\nist aber veraltet — `nxs prime` sagt jetzt, was stattdessen dasteht. Dazu gilt: **wer in einem\nSchritt eines Kanals genannt ist, gehört damit zum Kanal.** Die Besetzung sind die Schrittziele plus\ndas, was `members:` hinzufügt; ein Schrittziel muss also nicht mehr unter `members:` wiederholt und\nder Kanal nicht mehr bei der Persona genannt werden. Das schließt eine echte Lücke: ein Schrittziel,\ndas in `members:` fehlte, wurde beauftragt und durfte antworten, wurde aber abgewiesen, als es den\nFaden *lesen* wollte, auf den man es gesetzt hatte. Nichts bricht — jede zuvor geschriebene\nDeklaration bedeutet genau das, was sie bedeutet hat. Schließlich werden Persona-Dateien beim Lesen\ndes Katalogs referenziell geprüft: ein `addressable:`-Eintrag, der keinen deklarierten Kanal nennt,\noder eine Erlaubnisliste, die keine deklarierte Persona nennt, wird von `nxs prime` gemeldet statt\nerst viel später als abgelehnte Sendung aufzufallen." - } - }, - { - "version": "0.95.0", - "date": "2026-09-13", - "items": [ - { - "type": "added", - "en": "**`nxc reply --accept` — the way out of a review cycle that never becomes satisfied.** A channel that declares `steps:` with an `on_needs_rework:` edge had exactly one transition to its `next:` step: the reviewing step saying so itself. If it never did, nobody could finish the round — the party that produced the work could only rework or escalate, and the party that commissioned it could only ask for another attempt (a NEW run, with `max_passes` set back to full) or throw the round away. Measured on the first real planning run: thirty-one threads, six draft→review passes, six verdicts of `needs_rework` out of six, an epic cut and usable, and not one ticket handed to the coding chain. The human above the loop was its rewinder rather than its exit. `nxc reply --thread --accept -` now overrules the verdict that stopped the round: the step that sent the work back counts as satisfied and the SAME run continues over its `next:`. The next step is handed your words together with the verdict you set aside, under a notice that says plainly this is not the review passing — so it cannot report downstream that the work was reviewed clean. Two rules keep it a decision rather than a shortcut: only the party that commissioned a round may accept a verdict on it (a party serving a step is refused by name and sent to `--escalate`, which is how an agent asks for this decision), and nothing about it reads the pass counter — a round that stopped short of its ceiling can be accepted just the same, while a round with a step still working is refused rather than opening a second session into one working copy. The ceiling's own escalation now names both moves instead of only \"commission the round again\", and an acceptance is recorded as its own message kind, so an override stays findable afterwards as an override.", - "de": "**`nxc reply --accept` — der Ausgang aus einem Prüfzyklus, der nie zufrieden wird.** Ein Kanal mit `steps:` und einer `on_needs_rework:`-Kante hatte genau einen Übergang zu seinem `next:`-Schritt: den prüfenden Schritt selbst. Wurde der nie zufrieden, konnte niemand die Runde beenden — wer die Arbeit erzeugt hatte, konnte nur nacharbeiten oder eskalieren, und wer sie beauftragt hatte, nur einen weiteren Anlauf verlangen (ein NEUER Lauf, mit wieder voll aufgezogenem `max_passes`) oder die Runde wegwerfen. Gemessen im ersten echten Planungslauf: einunddreißig Fäden, sechs Durchläufe Entwurf→Prüfung, sechs Urteile `needs_rework` von sechs, ein geschnittenes und brauchbares Epic — und kein einziges Ticket an die Coding-Kette. Der Mensch über der Schleife war ihr Aufzieher statt ihr Ausgang. `nxc reply --thread --accept -` hebt jetzt das Urteil auf, an dem die Runde stehengeblieben ist: der Schritt, der zurückgeschickt hat, gilt als erfüllt, und DERSELBE Lauf geht über dessen `next:` weiter. Der Folgeschritt bekommt Ihre Worte zusammen mit dem aufgehobenen Urteil gereicht, unter einem Hinweis, der klar sagt, dass dies keine bestandene Prüfung ist — er kann also nicht weiter unten melden, die Arbeit sei sauber geprüft worden. Zwei Regeln machen daraus eine Entscheidung statt einer Abkürzung: nur wer eine Runde beauftragt hat, darf ein Urteil darin annehmen (wer einen Schritt bedient, wird namentlich abgelehnt und auf `--escalate` verwiesen — so bittet ein Agent um genau diese Entscheidung), und nichts daran liest den Durchgangszähler — eine Runde, die vor ihrer Decke stehengeblieben ist, lässt sich genauso annehmen, während eine Runde mit einem noch arbeitenden Schritt abgelehnt wird, statt eine zweite Sitzung in dieselbe Arbeitskopie zu öffnen. Die Eskalation an der Decke nennt jetzt beide Züge statt nur „die Runde erneut beauftragen\", und eine Annahme wird als eigene Nachrichtenart aufgezeichnet, damit eine Übersteuerung später als Übersteuerung wiederzufinden ist.", - "facade": "breaking" - } - ], - "notes": { - "en": "### Added\n- **`nxc reply --accept` — the way out of a review cycle that never becomes satisfied.** A channel that declares `steps:` with an `on_needs_rework:` edge had exactly one transition to its `next:` step: the reviewing step saying so itself. If it never did, nobody could finish the round — the party that produced the work could only rework or escalate, and the party that commissioned it could only ask for another attempt (a NEW run, with `max_passes` set back to full) or throw the round away. Measured on the first real planning run: thirty-one threads, six draft→review passes, six verdicts of `needs_rework` out of six, an epic cut and usable, and not one ticket handed to the coding chain. The human above the loop was its rewinder rather than its exit. `nxc reply --thread --accept -` now overrules the verdict that stopped the round: the step that sent the work back counts as satisfied and the SAME run continues over its `next:`. The next step is handed your words together with the verdict you set aside, under a notice that says plainly this is not the review passing — so it cannot report downstream that the work was reviewed clean. Two rules keep it a decision rather than a shortcut: only the party that commissioned a round may accept a verdict on it (a party serving a step is refused by name and sent to `--escalate`, which is how an agent asks for this decision), and nothing about it reads the pass counter — a round that stopped short of its ceiling can be accepted just the same, while a round with a step still working is refused rather than opening a second session into one working copy. The ceiling's own escalation now names both moves instead of only \"commission the round again\", and an acceptance is recorded as its own message kind, so an override stays findable afterwards as an override.\n\n### Facade Contract\n- `breaking` · **`nxc reply --accept` — the way out of a review cycle that never becomes satisfied.** A channel that declares `steps:` with an `on_needs_rework:` edge had exactly one transition to its `next:` step: the reviewing step saying so itself. If it never did, nobody could finish the round — the party that produced the work could only rework or escalate, and the party that commissioned it could only ask for another attempt (a NEW run, with `max_passes` set back to full) or throw the round away. Measured on the first real planning run: thirty-one threads, six draft→review passes, six verdicts of `needs_rework` out of six, an epic cut and usable, and not one ticket handed to the coding chain. The human above the loop was its rewinder rather than its exit. `nxc reply --thread --accept -` now overrules the verdict that stopped the round: the step that sent the work back counts as satisfied and the SAME run continues over its `next:`. The next step is handed your words together with the verdict you set aside, under a notice that says plainly this is not the review passing — so it cannot report downstream that the work was reviewed clean. Two rules keep it a decision rather than a shortcut: only the party that commissioned a round may accept a verdict on it (a party serving a step is refused by name and sent to `--escalate`, which is how an agent asks for this decision), and nothing about it reads the pass counter — a round that stopped short of its ceiling can be accepted just the same, while a round with a step still working is refused rather than opening a second session into one working copy. The ceiling's own escalation now names both moves instead of only \"commission the round again\", and an acceptance is recorded as its own message kind, so an override stays findable afterwards as an override.", - "de": "### Neu\n- **`nxc reply --accept` — der Ausgang aus einem Prüfzyklus, der nie zufrieden wird.** Ein Kanal mit `steps:` und einer `on_needs_rework:`-Kante hatte genau einen Übergang zu seinem `next:`-Schritt: den prüfenden Schritt selbst. Wurde der nie zufrieden, konnte niemand die Runde beenden — wer die Arbeit erzeugt hatte, konnte nur nacharbeiten oder eskalieren, und wer sie beauftragt hatte, nur einen weiteren Anlauf verlangen (ein NEUER Lauf, mit wieder voll aufgezogenem `max_passes`) oder die Runde wegwerfen. Gemessen im ersten echten Planungslauf: einunddreißig Fäden, sechs Durchläufe Entwurf→Prüfung, sechs Urteile `needs_rework` von sechs, ein geschnittenes und brauchbares Epic — und kein einziges Ticket an die Coding-Kette. Der Mensch über der Schleife war ihr Aufzieher statt ihr Ausgang. `nxc reply --thread --accept -` hebt jetzt das Urteil auf, an dem die Runde stehengeblieben ist: der Schritt, der zurückgeschickt hat, gilt als erfüllt, und DERSELBE Lauf geht über dessen `next:` weiter. Der Folgeschritt bekommt Ihre Worte zusammen mit dem aufgehobenen Urteil gereicht, unter einem Hinweis, der klar sagt, dass dies keine bestandene Prüfung ist — er kann also nicht weiter unten melden, die Arbeit sei sauber geprüft worden. Zwei Regeln machen daraus eine Entscheidung statt einer Abkürzung: nur wer eine Runde beauftragt hat, darf ein Urteil darin annehmen (wer einen Schritt bedient, wird namentlich abgelehnt und auf `--escalate` verwiesen — so bittet ein Agent um genau diese Entscheidung), und nichts daran liest den Durchgangszähler — eine Runde, die vor ihrer Decke stehengeblieben ist, lässt sich genauso annehmen, während eine Runde mit einem noch arbeitenden Schritt abgelehnt wird, statt eine zweite Sitzung in dieselbe Arbeitskopie zu öffnen. Die Eskalation an der Decke nennt jetzt beide Züge statt nur „die Runde erneut beauftragen\", und eine Annahme wird als eigene Nachrichtenart aufgezeichnet, damit eine Übersteuerung später als Übersteuerung wiederzufinden ist.\n\n### Facade-Kontrakt\n- `breaking` · **`nxc reply --accept` — der Ausgang aus einem Prüfzyklus, der nie zufrieden wird.** Ein Kanal mit `steps:` und einer `on_needs_rework:`-Kante hatte genau einen Übergang zu seinem `next:`-Schritt: den prüfenden Schritt selbst. Wurde der nie zufrieden, konnte niemand die Runde beenden — wer die Arbeit erzeugt hatte, konnte nur nacharbeiten oder eskalieren, und wer sie beauftragt hatte, nur einen weiteren Anlauf verlangen (ein NEUER Lauf, mit wieder voll aufgezogenem `max_passes`) oder die Runde wegwerfen. Gemessen im ersten echten Planungslauf: einunddreißig Fäden, sechs Durchläufe Entwurf→Prüfung, sechs Urteile `needs_rework` von sechs, ein geschnittenes und brauchbares Epic — und kein einziges Ticket an die Coding-Kette. Der Mensch über der Schleife war ihr Aufzieher statt ihr Ausgang. `nxc reply --thread --accept -` hebt jetzt das Urteil auf, an dem die Runde stehengeblieben ist: der Schritt, der zurückgeschickt hat, gilt als erfüllt, und DERSELBE Lauf geht über dessen `next:` weiter. Der Folgeschritt bekommt Ihre Worte zusammen mit dem aufgehobenen Urteil gereicht, unter einem Hinweis, der klar sagt, dass dies keine bestandene Prüfung ist — er kann also nicht weiter unten melden, die Arbeit sei sauber geprüft worden. Zwei Regeln machen daraus eine Entscheidung statt einer Abkürzung: nur wer eine Runde beauftragt hat, darf ein Urteil darin annehmen (wer einen Schritt bedient, wird namentlich abgelehnt und auf `--escalate` verwiesen — so bittet ein Agent um genau diese Entscheidung), und nichts daran liest den Durchgangszähler — eine Runde, die vor ihrer Decke stehengeblieben ist, lässt sich genauso annehmen, während eine Runde mit einem noch arbeitenden Schritt abgelehnt wird, statt eine zweite Sitzung in dieselbe Arbeitskopie zu öffnen. Die Eskalation an der Decke nennt jetzt beide Züge statt nur „die Runde erneut beauftragen\", und eine Annahme wird als eigene Nachrichtenart aufgezeichnet, damit eine Übersteuerung später als Übersteuerung wiederzufinden ist." - } - }, - { - "version": "0.94.0", - "date": "2026-09-12", - "items": [ - { - "type": "changed", - "en": "**`--escalate` is the way to ask for a DECISION, and the text a session reads first now says so.** The `nxc prime` block a session meets at its start described the flag in four words — `--escalate` says \"I cannot\" — which is the half that does not apply to a session that can do its job perfectly well but has hit a question that is not its to settle. Measured: a PM ended its turn with a plain reply and two open product questions in the text, and the chain did what a chain does and handed them DOWNWARD — three review rounds judged a draft whose central decision was still open, and the questions reached the owner only when the pass limit ran out. `--escalate` now has a line of its own in that block, the block says which way an escalation travels (UP, to whoever commissioned you), and `nxc reply --help`, `nxc guide` and the shipped example teams say the same thing as the forced ending that was right all along.", - "de": "**`--escalate` ist der Weg, um eine ENTSCHEIDUNG zu erbitten, und der Text, den eine Sitzung zuerst liest, sagt das jetzt.** Der `nxc prime`-Block, den eine Sitzung an ihrem Anfang bekommt, beschrieb die Marke in vier Worten — `--escalate` heißt „ich kann nicht\" —, und das ist die Hälfte, die auf eine Sitzung nicht zutrifft, die ihre Aufgabe sehr wohl ausführen kann, aber auf eine Frage gestoßen ist, die nicht ihr gehört. Gemessen: ein PM beendete seinen Zug mit einer blanken Antwort und zwei offenen Produktfragen im Text, und die Kette reichte sie nach unten weiter — drei Prüfrunden beurteilten einen Entwurf, dessen zentrale Entscheidung noch offen war, und die Fragen erreichten den Eigentümer erst, als die Durchlaufgrenze ablief. `--escalate` hat in diesem Block jetzt eine eigene Zeile, der Block sagt, wohin eine Eskalation läuft (nach OBEN, zu dem, der beauftragt hat), und `nxc reply --help`, `nxc guide` und die mitgelieferten Beispielteams sagen dasselbe wie das erzwungene Ende, das von Anfang an recht hatte.", - "facade": "changed" - }, - { - "type": "fixed", - "en": "**The shipped example declarations no longer teach the mistake `nxc prime` warns about.** Running v0.93.0's new declaration warning over this repository's own examples reported twelve of them: every persona in `role-runtime-v2` and `role-runtime-v3`, the golden fixture, and the worked example in `nxc guide personas` — which invited the reader to copy it verbatim. All of them spelled out `nxc reply` (and `nxc send`) with a placeholder where the engine puts the real thread id. That text is gone from every one of them; what is left is the part that is actually the author's — what counts as finished for that role, and what to do instead of answering when a decision is missing. The guide's worked example now shows a ROLE rather than a position (\"you are the first step of …\", which a persona cannot see and got wrong by one), and the paragraph under it says what is worth copying today instead of arguing from a risk the forced ending abolished. `nxc prime` over any of the three sets now reports zero declaration warnings.", - "de": "**Die mitgelieferten Beispiel-Deklarationen lehren nicht länger den Fehler, vor dem `nxc prime` warnt.** Lässt man die neue Deklarationswarnung aus v0.93.0 über die Beispiele dieses Repositorys laufen, meldete sie zwölf davon: jede Persona in `role-runtime-v2` und `role-runtime-v3`, die Golden-Fixture und das ausgearbeitete Beispiel in `nxc guide personas` — das obendrein wörtlich zum Abschreiben einlud. Alle buchstabierten `nxc reply` (und `nxc send`) aus, mit einem Platzhalter dort, wo die Engine die echte Faden-ID einsetzt. Dieser Text ist überall entfernt; geblieben ist der Teil, der wirklich dem Autor gehört — was für diese Rolle als fertig zählt, und was statt einer Antwort zu tun ist, wenn eine Entscheidung fehlt. Das ausgearbeitete Beispiel im Handbuch zeigt jetzt eine ROLLE statt einer Position („du bist der erste Schritt von …\", was eine Persona nicht sehen kann und um eins verfehlte), und der Absatz darunter sagt, was heute abschreibenswert ist, statt mit einer Gefahr zu argumentieren, die das erzwungene Ende abgeschafft hat. `nxc prime` meldet über jedem der drei Sätze jetzt null Deklarationswarnungen." - } - ], - "notes": { - "en": "### Changed\n- **`--escalate` is the way to ask for a DECISION, and the text a session reads first now says so.** The `nxc prime` block a session meets at its start described the flag in four words — `--escalate` says \"I cannot\" — which is the half that does not apply to a session that can do its job perfectly well but has hit a question that is not its to settle. Measured: a PM ended its turn with a plain reply and two open product questions in the text, and the chain did what a chain does and handed them DOWNWARD — three review rounds judged a draft whose central decision was still open, and the questions reached the owner only when the pass limit ran out. `--escalate` now has a line of its own in that block, the block says which way an escalation travels (UP, to whoever commissioned you), and `nxc reply --help`, `nxc guide` and the shipped example teams say the same thing as the forced ending that was right all along.\n\n### Fixed\n- **The shipped example declarations no longer teach the mistake `nxc prime` warns about.** Running v0.93.0's new declaration warning over this repository's own examples reported twelve of them: every persona in `role-runtime-v2` and `role-runtime-v3`, the golden fixture, and the worked example in `nxc guide personas` — which invited the reader to copy it verbatim. All of them spelled out `nxc reply` (and `nxc send`) with a placeholder where the engine puts the real thread id. That text is gone from every one of them; what is left is the part that is actually the author's — what counts as finished for that role, and what to do instead of answering when a decision is missing. The guide's worked example now shows a ROLE rather than a position (\"you are the first step of …\", which a persona cannot see and got wrong by one), and the paragraph under it says what is worth copying today instead of arguing from a risk the forced ending abolished. `nxc prime` over any of the three sets now reports zero declaration warnings.\n\n### Facade Contract\n- `changed` · **`--escalate` is the way to ask for a DECISION, and the text a session reads first now says so.** The `nxc prime` block a session meets at its start described the flag in four words — `--escalate` says \"I cannot\" — which is the half that does not apply to a session that can do its job perfectly well but has hit a question that is not its to settle. Measured: a PM ended its turn with a plain reply and two open product questions in the text, and the chain did what a chain does and handed them DOWNWARD — three review rounds judged a draft whose central decision was still open, and the questions reached the owner only when the pass limit ran out. `--escalate` now has a line of its own in that block, the block says which way an escalation travels (UP, to whoever commissioned you), and `nxc reply --help`, `nxc guide` and the shipped example teams say the same thing as the forced ending that was right all along.", - "de": "### Geändert\n- **`--escalate` ist der Weg, um eine ENTSCHEIDUNG zu erbitten, und der Text, den eine Sitzung zuerst liest, sagt das jetzt.** Der `nxc prime`-Block, den eine Sitzung an ihrem Anfang bekommt, beschrieb die Marke in vier Worten — `--escalate` heißt „ich kann nicht\" —, und das ist die Hälfte, die auf eine Sitzung nicht zutrifft, die ihre Aufgabe sehr wohl ausführen kann, aber auf eine Frage gestoßen ist, die nicht ihr gehört. Gemessen: ein PM beendete seinen Zug mit einer blanken Antwort und zwei offenen Produktfragen im Text, und die Kette reichte sie nach unten weiter — drei Prüfrunden beurteilten einen Entwurf, dessen zentrale Entscheidung noch offen war, und die Fragen erreichten den Eigentümer erst, als die Durchlaufgrenze ablief. `--escalate` hat in diesem Block jetzt eine eigene Zeile, der Block sagt, wohin eine Eskalation läuft (nach OBEN, zu dem, der beauftragt hat), und `nxc reply --help`, `nxc guide` und die mitgelieferten Beispielteams sagen dasselbe wie das erzwungene Ende, das von Anfang an recht hatte.\n\n### Behoben\n- **Die mitgelieferten Beispiel-Deklarationen lehren nicht länger den Fehler, vor dem `nxc prime` warnt.** Lässt man die neue Deklarationswarnung aus v0.93.0 über die Beispiele dieses Repositorys laufen, meldete sie zwölf davon: jede Persona in `role-runtime-v2` und `role-runtime-v3`, die Golden-Fixture und das ausgearbeitete Beispiel in `nxc guide personas` — das obendrein wörtlich zum Abschreiben einlud. Alle buchstabierten `nxc reply` (und `nxc send`) aus, mit einem Platzhalter dort, wo die Engine die echte Faden-ID einsetzt. Dieser Text ist überall entfernt; geblieben ist der Teil, der wirklich dem Autor gehört — was für diese Rolle als fertig zählt, und was statt einer Antwort zu tun ist, wenn eine Entscheidung fehlt. Das ausgearbeitete Beispiel im Handbuch zeigt jetzt eine ROLLE statt einer Position („du bist der erste Schritt von …\", was eine Persona nicht sehen kann und um eins verfehlte), und der Absatz darunter sagt, was heute abschreibenswert ist, statt mit einer Gefahr zu argumentieren, die das erzwungene Ende abgeschafft hat. `nxc prime` meldet über jedem der drei Sätze jetzt null Deklarationswarnungen.\n\n### Facade-Kontrakt\n- `changed` · **`--escalate` ist der Weg, um eine ENTSCHEIDUNG zu erbitten, und der Text, den eine Sitzung zuerst liest, sagt das jetzt.** Der `nxc prime`-Block, den eine Sitzung an ihrem Anfang bekommt, beschrieb die Marke in vier Worten — `--escalate` heißt „ich kann nicht\" —, und das ist die Hälfte, die auf eine Sitzung nicht zutrifft, die ihre Aufgabe sehr wohl ausführen kann, aber auf eine Frage gestoßen ist, die nicht ihr gehört. Gemessen: ein PM beendete seinen Zug mit einer blanken Antwort und zwei offenen Produktfragen im Text, und die Kette reichte sie nach unten weiter — drei Prüfrunden beurteilten einen Entwurf, dessen zentrale Entscheidung noch offen war, und die Fragen erreichten den Eigentümer erst, als die Durchlaufgrenze ablief. `--escalate` hat in diesem Block jetzt eine eigene Zeile, der Block sagt, wohin eine Eskalation läuft (nach OBEN, zu dem, der beauftragt hat), und `nxc reply --help`, `nxc guide` und die mitgelieferten Beispielteams sagen dasselbe wie das erzwungene Ende, das von Anfang an recht hatte." - } - }, - { - "version": "0.93.0", - "date": "2026-09-12", - "items": [ - { - "type": "added", - "en": "`nxc guide writing-declarations` — a new chat guide topic on how to get from an intention to a persona or channel that produces usable answers. Seven measured error classes, each with a rule, the incident it was learned from and a counter-example/example pair; plus the question no guide answered before it — when a persona is enough and when it has to be a channel — and a checklist to run before handing a declaration over.\n`nxs prime` now reports **declaration warnings** beside the referential errors: engine text copied into a declaration (the engine supplies `nxc send`/`reply`/`list` and the `nxm` verbs itself, and a copy drifts), a persona with no usable `job_description`, and a channel with no `description`. They are warnings, not errors — the declaration loads and runs unchanged — and, like the errors, they reach a human at the keyboard and never a spawned persona's prompt. `prime --json` carries them as `declaration_warnings`.\n**Facade contract:** `facade::PrimeRoster` carries the findings as a new public field, `warnings`. Reading a roster is unaffected — the field is additive and every existing one is untouched — but the struct is exhaustively constructible, so code that builds a `PrimeRoster` with a struct literal (a test fixture, a stub) has to name it. `cargo-semver-checks` reports it as `constructible_struct_adds_field`; nothing else in `nexus-chat`, `nexus-memory` or `nexus-flow-facade` moved.", - "de": "`nxc guide writing-declarations` — ein neues chat-Guide-Thema darüber, wie aus einer Absicht eine Persona oder ein Kanal wird, die brauchbare Antworten liefern. Sieben gemessene Fehlerklassen, jede mit Regel, dem Vorfall, an dem sie gelernt wurde, und einem Gegenbeispiel/Beispiel-Paar; dazu die Frage, die bis jetzt kein Guide beantwortet hat — wann eine Persona genügt und wann es ein Kanal sein muss — und eine Checkliste für den Moment, bevor Sie eine Deklaration aus der Hand geben.\n`nxs prime` meldet jetzt **Deklarationswarnungen** neben den Referenzfehlern: in eine Deklaration kopierter Engine-Text (die Engine spielt `nxc send`/`reply`/`list` und die `nxm`-Verben selbst ein, und eine Kopie driftet), eine Persona ohne brauchbare `job_description` und ein Kanal ohne `description`. Es sind Warnungen, keine Fehler — die Deklaration lädt und läuft unverändert — und sie erreichen wie die Fehler einen Menschen an der Tastatur und nie den Prompt einer gestarteten Persona. `prime --json` führt sie als `declaration_warnings`.\n**Fassaden-Vertrag:** `facade::PrimeRoster` trägt die Befunde in einem neuen öffentlichen Feld, `warnings`. Das Lesen eines Rosters ist davon unberührt — das Feld ist additiv und kein bestehendes ändert sich —, aber die Struktur ist vollständig konstruierbar, also muss Code, der ein `PrimeRoster` als Struktur-Literal baut (eine Test-Vorrichtung, ein Stub), es benennen. `cargo-semver-checks` meldet das als `constructible_struct_adds_field`; sonst hat sich in `nexus-chat`, `nexus-memory` und `nexus-flow-facade` nichts bewegt.", - "facade": "breaking" - } - ], - "notes": { - "en": "### Added\n- `nxc guide writing-declarations` — a new chat guide topic on how to get from an intention to a persona or channel that produces usable answers. Seven measured error classes, each with a rule, the incident it was learned from and a counter-example/example pair; plus the question no guide answered before it — when a persona is enough and when it has to be a channel — and a checklist to run before handing a declaration over.\n`nxs prime` now reports **declaration warnings** beside the referential errors: engine text copied into a declaration (the engine supplies `nxc send`/`reply`/`list` and the `nxm` verbs itself, and a copy drifts), a persona with no usable `job_description`, and a channel with no `description`. They are warnings, not errors — the declaration loads and runs unchanged — and, like the errors, they reach a human at the keyboard and never a spawned persona's prompt. `prime --json` carries them as `declaration_warnings`.\n**Facade contract:** `facade::PrimeRoster` carries the findings as a new public field, `warnings`. Reading a roster is unaffected — the field is additive and every existing one is untouched — but the struct is exhaustively constructible, so code that builds a `PrimeRoster` with a struct literal (a test fixture, a stub) has to name it. `cargo-semver-checks` reports it as `constructible_struct_adds_field`; nothing else in `nexus-chat`, `nexus-memory` or `nexus-flow-facade` moved.\n\n### Facade Contract\n- `breaking` · `nxc guide writing-declarations` — a new chat guide topic on how to get from an intention to a persona or channel that produces usable answers. Seven measured error classes, each with a rule, the incident it was learned from and a counter-example/example pair; plus the question no guide answered before it — when a persona is enough and when it has to be a channel — and a checklist to run before handing a declaration over.\n`nxs prime` now reports **declaration warnings** beside the referential errors: engine text copied into a declaration (the engine supplies `nxc send`/`reply`/`list` and the `nxm` verbs itself, and a copy drifts), a persona with no usable `job_description`, and a channel with no `description`. They are warnings, not errors — the declaration loads and runs unchanged — and, like the errors, they reach a human at the keyboard and never a spawned persona's prompt. `prime --json` carries them as `declaration_warnings`.\n**Facade contract:** `facade::PrimeRoster` carries the findings as a new public field, `warnings`. Reading a roster is unaffected — the field is additive and every existing one is untouched — but the struct is exhaustively constructible, so code that builds a `PrimeRoster` with a struct literal (a test fixture, a stub) has to name it. `cargo-semver-checks` reports it as `constructible_struct_adds_field`; nothing else in `nexus-chat`, `nexus-memory` or `nexus-flow-facade` moved.", - "de": "### Neu\n- `nxc guide writing-declarations` — ein neues chat-Guide-Thema darüber, wie aus einer Absicht eine Persona oder ein Kanal wird, die brauchbare Antworten liefern. Sieben gemessene Fehlerklassen, jede mit Regel, dem Vorfall, an dem sie gelernt wurde, und einem Gegenbeispiel/Beispiel-Paar; dazu die Frage, die bis jetzt kein Guide beantwortet hat — wann eine Persona genügt und wann es ein Kanal sein muss — und eine Checkliste für den Moment, bevor Sie eine Deklaration aus der Hand geben.\n`nxs prime` meldet jetzt **Deklarationswarnungen** neben den Referenzfehlern: in eine Deklaration kopierter Engine-Text (die Engine spielt `nxc send`/`reply`/`list` und die `nxm`-Verben selbst ein, und eine Kopie driftet), eine Persona ohne brauchbare `job_description` und ein Kanal ohne `description`. Es sind Warnungen, keine Fehler — die Deklaration lädt und läuft unverändert — und sie erreichen wie die Fehler einen Menschen an der Tastatur und nie den Prompt einer gestarteten Persona. `prime --json` führt sie als `declaration_warnings`.\n**Fassaden-Vertrag:** `facade::PrimeRoster` trägt die Befunde in einem neuen öffentlichen Feld, `warnings`. Das Lesen eines Rosters ist davon unberührt — das Feld ist additiv und kein bestehendes ändert sich —, aber die Struktur ist vollständig konstruierbar, also muss Code, der ein `PrimeRoster` als Struktur-Literal baut (eine Test-Vorrichtung, ein Stub), es benennen. `cargo-semver-checks` meldet das als `constructible_struct_adds_field`; sonst hat sich in `nexus-chat`, `nexus-memory` und `nexus-flow-facade` nichts bewegt.\n\n### Facade-Kontrakt\n- `breaking` · `nxc guide writing-declarations` — ein neues chat-Guide-Thema darüber, wie aus einer Absicht eine Persona oder ein Kanal wird, die brauchbare Antworten liefern. Sieben gemessene Fehlerklassen, jede mit Regel, dem Vorfall, an dem sie gelernt wurde, und einem Gegenbeispiel/Beispiel-Paar; dazu die Frage, die bis jetzt kein Guide beantwortet hat — wann eine Persona genügt und wann es ein Kanal sein muss — und eine Checkliste für den Moment, bevor Sie eine Deklaration aus der Hand geben.\n`nxs prime` meldet jetzt **Deklarationswarnungen** neben den Referenzfehlern: in eine Deklaration kopierter Engine-Text (die Engine spielt `nxc send`/`reply`/`list` und die `nxm`-Verben selbst ein, und eine Kopie driftet), eine Persona ohne brauchbare `job_description` und ein Kanal ohne `description`. Es sind Warnungen, keine Fehler — die Deklaration lädt und läuft unverändert — und sie erreichen wie die Fehler einen Menschen an der Tastatur und nie den Prompt einer gestarteten Persona. `prime --json` führt sie als `declaration_warnings`.\n**Fassaden-Vertrag:** `facade::PrimeRoster` trägt die Befunde in einem neuen öffentlichen Feld, `warnings`. Das Lesen eines Rosters ist davon unberührt — das Feld ist additiv und kein bestehendes ändert sich —, aber die Struktur ist vollständig konstruierbar, also muss Code, der ein `PrimeRoster` als Struktur-Literal baut (eine Test-Vorrichtung, ein Stub), es benennen. `cargo-semver-checks` meldet das als `constructible_struct_adds_field`; sonst hat sich in `nexus-chat`, `nexus-memory` und `nexus-flow-facade` nichts bewegt." - } - }, - { - "version": "0.92.0", - "date": "2026-09-11", - "items": [ - { - "type": "added", - "en": "`nxc send` and `nxc reply` now take the message body from STDIN or from a file, in the same\nnotation `nxf` uses: a body of `-` reads STDIN (`nxc reply --thread - <<'EOF' … EOF`), and\n`--body-file ` reads a file. Neither path passes through the shell, so backticks, `$(…)` and\nquotes in a review verdict, a session report or a quoted command arrive exactly as written. The\nargument form is unchanged and still right for a one-line answer. An empty read is refused rather\nthan posted as an empty reply.\n\nEvery text that teaches the form now shows the safe one: the forced ending in a role's system\nprompt, the wake message that names each open conversation, the sidecar's end-of-turn reminder,\nthe `.nxs-personas/README.md` that `nxc init` writes, the worked-example persona prompts in the\nguides, and the shipped `role-runtime-v2`/`v3` example teams — whose review roles were telling a\nreviewer to deliver a full verdict through `nxc reply \"…\"`, a form the CLI has refused\noutright since the positional thread was removed.", - "de": "`nxc send` und `nxc reply` nehmen den Nachrichtentext jetzt von STDIN oder aus einer Datei\nentgegen, in derselben Schreibweise wie `nxf`: ein Rumpf `-` liest STDIN\n(`nxc reply --thread - <<'EOF' … EOF`), `--body-file ` liest eine Datei. Keiner der\nbeiden Wege führt durch die Shell — Backticks, `$(…)` und Anführungszeichen in einem Prüfurteil,\neinem Sitzungsbericht oder einem zitierten Befehl kommen genau so an, wie sie geschrieben wurden.\nDie Argumentform bleibt unverändert und ist für eine einzeilige Antwort weiterhin richtig. Ein\nleerer Lesevorgang wird abgelehnt, statt als leere Antwort gepostet zu werden.\n\nJeder Text, der die Form beibringt, zeigt jetzt die sichere: das erzwungene Ende im System-Prompt\neiner Rolle, die Weckmeldung, die jedes offene Gespräch benennt, die Erinnerung des Sidecars am\nZugende, die `.nxs-personas/README.md`, die `nxc init` schreibt, die Beispiel-Prompts in den\nGuides und die mitgelieferten Beispiel-Teams `role-runtime-v2`/`v3` — deren Review-Rollen einem\nPrüfer bisher sagten, er solle sein vollständiges Urteil über `nxc reply \"…\"` abliefern, eine\nForm, die die CLI seit dem Wegfall des positionalen Fadens rundheraus ablehnt.", - "facade": "changed" - } - ], - "notes": { - "en": "### Added\n- `nxc send` and `nxc reply` now take the message body from STDIN or from a file, in the same\nnotation `nxf` uses: a body of `-` reads STDIN (`nxc reply --thread - <<'EOF' … EOF`), and\n`--body-file ` reads a file. Neither path passes through the shell, so backticks, `$(…)` and\nquotes in a review verdict, a session report or a quoted command arrive exactly as written. The\nargument form is unchanged and still right for a one-line answer. An empty read is refused rather\nthan posted as an empty reply.\n\nEvery text that teaches the form now shows the safe one: the forced ending in a role's system\nprompt, the wake message that names each open conversation, the sidecar's end-of-turn reminder,\nthe `.nxs-personas/README.md` that `nxc init` writes, the worked-example persona prompts in the\nguides, and the shipped `role-runtime-v2`/`v3` example teams — whose review roles were telling a\nreviewer to deliver a full verdict through `nxc reply \"…\"`, a form the CLI has refused\noutright since the positional thread was removed.\n\n### Facade Contract\n- `changed` · `nxc send` and `nxc reply` now take the message body from STDIN or from a file, in the same\nnotation `nxf` uses: a body of `-` reads STDIN (`nxc reply --thread - <<'EOF' … EOF`), and\n`--body-file ` reads a file. Neither path passes through the shell, so backticks, `$(…)` and\nquotes in a review verdict, a session report or a quoted command arrive exactly as written. The\nargument form is unchanged and still right for a one-line answer. An empty read is refused rather\nthan posted as an empty reply.\n\nEvery text that teaches the form now shows the safe one: the forced ending in a role's system\nprompt, the wake message that names each open conversation, the sidecar's end-of-turn reminder,\nthe `.nxs-personas/README.md` that `nxc init` writes, the worked-example persona prompts in the\nguides, and the shipped `role-runtime-v2`/`v3` example teams — whose review roles were telling a\nreviewer to deliver a full verdict through `nxc reply \"…\"`, a form the CLI has refused\noutright since the positional thread was removed.", - "de": "### Neu\n- `nxc send` und `nxc reply` nehmen den Nachrichtentext jetzt von STDIN oder aus einer Datei\nentgegen, in derselben Schreibweise wie `nxf`: ein Rumpf `-` liest STDIN\n(`nxc reply --thread - <<'EOF' … EOF`), `--body-file ` liest eine Datei. Keiner der\nbeiden Wege führt durch die Shell — Backticks, `$(…)` und Anführungszeichen in einem Prüfurteil,\neinem Sitzungsbericht oder einem zitierten Befehl kommen genau so an, wie sie geschrieben wurden.\nDie Argumentform bleibt unverändert und ist für eine einzeilige Antwort weiterhin richtig. Ein\nleerer Lesevorgang wird abgelehnt, statt als leere Antwort gepostet zu werden.\n\nJeder Text, der die Form beibringt, zeigt jetzt die sichere: das erzwungene Ende im System-Prompt\neiner Rolle, die Weckmeldung, die jedes offene Gespräch benennt, die Erinnerung des Sidecars am\nZugende, die `.nxs-personas/README.md`, die `nxc init` schreibt, die Beispiel-Prompts in den\nGuides und die mitgelieferten Beispiel-Teams `role-runtime-v2`/`v3` — deren Review-Rollen einem\nPrüfer bisher sagten, er solle sein vollständiges Urteil über `nxc reply \"…\"` abliefern, eine\nForm, die die CLI seit dem Wegfall des positionalen Fadens rundheraus ablehnt.\n\n### Facade-Kontrakt\n- `changed` · `nxc send` und `nxc reply` nehmen den Nachrichtentext jetzt von STDIN oder aus einer Datei\nentgegen, in derselben Schreibweise wie `nxf`: ein Rumpf `-` liest STDIN\n(`nxc reply --thread - <<'EOF' … EOF`), `--body-file ` liest eine Datei. Keiner der\nbeiden Wege führt durch die Shell — Backticks, `$(…)` und Anführungszeichen in einem Prüfurteil,\neinem Sitzungsbericht oder einem zitierten Befehl kommen genau so an, wie sie geschrieben wurden.\nDie Argumentform bleibt unverändert und ist für eine einzeilige Antwort weiterhin richtig. Ein\nleerer Lesevorgang wird abgelehnt, statt als leere Antwort gepostet zu werden.\n\nJeder Text, der die Form beibringt, zeigt jetzt die sichere: das erzwungene Ende im System-Prompt\neiner Rolle, die Weckmeldung, die jedes offene Gespräch benennt, die Erinnerung des Sidecars am\nZugende, die `.nxs-personas/README.md`, die `nxc init` schreibt, die Beispiel-Prompts in den\nGuides und die mitgelieferten Beispiel-Teams `role-runtime-v2`/`v3` — deren Review-Rollen einem\nPrüfer bisher sagten, er solle sein vollständiges Urteil über `nxc reply \"…\"` abliefern, eine\nForm, die die CLI seit dem Wegfall des positionalen Fadens rundheraus ablehnt." - } - }, - { - "version": "0.91.0", - "date": "2026-09-11", - "items": [ - { - "type": "changed", - "en": "**A role that is waiting on a round it commissioned itself can now say so — by saying nothing.** It\nhad three answers before this and all three were untrue: `reply` claims a result it does not have,\nsilence buys a reminder and then an escalation posted in its own name, and `--escalate` says \"I\ncannot carry this out\" while stopping a chain nothing is wrong with. Measured on 2026-09-08 in a\nlive workspace: a marketing role consulted four specialists ad hoc, reached for the third, and the\noperation view read\n\n```text\n5 thread(s), 4 open · NEEDS DECISION\n awaiting you — HANDED BACK by 47jy/head-of-marketing, not answered\n```\n\nNothing needed deciding. Four sessions were working.\n\nSuch a session now simply **ends its turn**. Nothing is declared and no new verb exists: the engine\nopened those sub-threads on the caller's behalf, so \"ended its turn owing an answer, with a round of\nits own still open\" is a fact about the record. It gets no reminder, nothing is posted in its name,\nand the one reminder attempt a genuinely forgetful session has is not spent on it. It is woken when\nthe sub-round returns — with whatever that round produced, an escalation included — and the ceiling\nstays the commissioned round's own deadline. There is no second clock.\n\n`nxc status` says it on the thread's own row, and `NEEDS DECISION` no longer fires:\n\n```text\nm-01M0… #positioning waiting on 47jy/head-of-marketing — waiting on its own sub-round (4 open)\n```\n\nThe `--json` field is `waiting_on_sub_round`, omitted where there is nothing to say.\n\nThree things change for anyone already running roles:\n\n- **A plain `nxc reply` while your own round is still open is now refused**, at the write point, with\n a reason that names what is being waited on. It means \"I am finished\", and that is not true yet.\n `nxc reply --escalate` and `--needs-rework` are untouched — \"I cannot\" can be true while a round of\n yours runs.\n- **The forced ending in every commissioned session's prompt says the opposite of what it said.** It\n used to read \"do not wait for work you commissioned yourself: if you cannot finish without it, end\n with `--escalate`\" — which is the instruction to raise the false alarm above. It now says to end\n the turn, and adds the thing that is genuinely dangerous once waiting is allowed: do not keep\n working while you wait, because the wake starts a second process under the same session in the same\n working copy and overwrites what the first one was still editing.\n- **An escalation gets its meaning back.** After this, one in an operation means a chain has stopped\n and somebody has to decide what happens to it.", - "de": "**Eine Rolle, die auf eine selbst beauftragte Runde wartet, kann das jetzt sagen — indem sie nichts\nsagt.** Vorher hatte sie drei Antworten, und alle drei waren unwahr: `reply` behauptet ein Ergebnis,\ndas es nicht gibt, Schweigen bringt eine Erinnerung und danach eine Eskalation in ihrem eigenen\nNamen, und `--escalate` sagt „das kann ich nicht ausführen\" und hält dabei eine Kette an, an der\nnichts falsch ist. Am 08.09.2026 in einem laufenden Arbeitsbereich gemessen: Eine Marketing-Rolle\nkonsultierte ad hoc vier Fachleute, griff zur dritten Antwort, und die Vorgangsübersicht las sich so:\n\n```text\n5 thread(s), 4 open · NEEDS DECISION\n awaiting you — HANDED BACK by 47jy/head-of-marketing, not answered\n```\n\nEs war nichts zu entscheiden. Vier Sitzungen arbeiteten.\n\nEine solche Sitzung **beendet jetzt einfach ihren Zug**. Nichts wird deklariert, und es gibt kein\nneues Verb: Die Engine hat diese Unterfäden im Auftrag des Aufrufers geöffnet, „Zug beendet, Antwort\ngeschuldet, eigene Runde noch offen\" ist also eine Tatsache über den Datensatz. Sie bekommt keine\nErinnerung, nichts wird in ihrem Namen gepostet, und der eine Erinnerungsversuch, den eine wirklich\nvergessliche Sitzung hat, wird dafür nicht verbraucht. Geweckt wird sie, wenn die Unterrunde\nzurückkommt — mit deren Ausgang, Eskalation eingeschlossen —, und die Decke bleibt die Frist jener\nRunde. Es gibt keine zweite Uhr.\n\n`nxc status` sagt es in der Zeile des Fadens, und `NEEDS DECISION` feuert nicht mehr:\n\n```text\nm-01M0… #positioning waiting on 47jy/head-of-marketing — waiting on its own sub-round (4 open)\n```\n\nDas `--json`-Feld heißt `waiting_on_sub_round` und fehlt, wo es nichts zu sagen gibt.\n\nDrei Dinge ändern sich für alle, die bereits Rollen betreiben:\n\n- **Ein blankes `nxc reply` bei offener eigener Runde wird jetzt abgelehnt**, an der Schreibstelle,\n mit einem Grund, der nennt, worauf gewartet wird. Es hieße „ich bin fertig\", und das stimmt noch\n nicht. `nxc reply --escalate` und `--needs-rework` bleiben unangetastet — „ich kann nicht\" kann\n wahr sein, während eine eigene Runde läuft.\n- **Das erzwungene Ende im Prompt jeder beauftragten Sitzung sagt das Gegenteil von vorher.** Es\n lautete „warte nicht auf Arbeit, die du selbst beauftragt hast: wenn du ohne sie nicht fertig\n wirst, beende mit `--escalate`\" — die Anweisung zum obigen Fehlalarm. Jetzt sagt es, den Zug zu\n beenden, und ergänzt das, was wirklich gefährlich ist, sobald Warten erlaubt ist: nicht\n weiterarbeiten, während man wartet, denn das Wecken startet einen zweiten Prozess unter derselben\n Sitzung in derselben Arbeitskopie und überschreibt, was der erste noch bearbeitet hat.\n- **Eine Eskalation bekommt ihre Bedeutung zurück.** Danach heißt eine in einem Vorgang: Eine Kette\n steht, und jemand muss entscheiden, wie es weitergeht.", - "facade": "breaking" - } - ], - "notes": { - "en": "### Changed\n- **A role that is waiting on a round it commissioned itself can now say so — by saying nothing.** It\nhad three answers before this and all three were untrue: `reply` claims a result it does not have,\nsilence buys a reminder and then an escalation posted in its own name, and `--escalate` says \"I\ncannot carry this out\" while stopping a chain nothing is wrong with. Measured on 2026-09-08 in a\nlive workspace: a marketing role consulted four specialists ad hoc, reached for the third, and the\noperation view read\n\n```text\n5 thread(s), 4 open · NEEDS DECISION\n awaiting you — HANDED BACK by 47jy/head-of-marketing, not answered\n```\n\nNothing needed deciding. Four sessions were working.\n\nSuch a session now simply **ends its turn**. Nothing is declared and no new verb exists: the engine\nopened those sub-threads on the caller's behalf, so \"ended its turn owing an answer, with a round of\nits own still open\" is a fact about the record. It gets no reminder, nothing is posted in its name,\nand the one reminder attempt a genuinely forgetful session has is not spent on it. It is woken when\nthe sub-round returns — with whatever that round produced, an escalation included — and the ceiling\nstays the commissioned round's own deadline. There is no second clock.\n\n`nxc status` says it on the thread's own row, and `NEEDS DECISION` no longer fires:\n\n```text\nm-01M0… #positioning waiting on 47jy/head-of-marketing — waiting on its own sub-round (4 open)\n```\n\nThe `--json` field is `waiting_on_sub_round`, omitted where there is nothing to say.\n\nThree things change for anyone already running roles:\n\n- **A plain `nxc reply` while your own round is still open is now refused**, at the write point, with\n a reason that names what is being waited on. It means \"I am finished\", and that is not true yet.\n `nxc reply --escalate` and `--needs-rework` are untouched — \"I cannot\" can be true while a round of\n yours runs.\n- **The forced ending in every commissioned session's prompt says the opposite of what it said.** It\n used to read \"do not wait for work you commissioned yourself: if you cannot finish without it, end\n with `--escalate`\" — which is the instruction to raise the false alarm above. It now says to end\n the turn, and adds the thing that is genuinely dangerous once waiting is allowed: do not keep\n working while you wait, because the wake starts a second process under the same session in the same\n working copy and overwrites what the first one was still editing.\n- **An escalation gets its meaning back.** After this, one in an operation means a chain has stopped\n and somebody has to decide what happens to it.\n\n### Facade Contract\n- `breaking` · **A role that is waiting on a round it commissioned itself can now say so — by saying nothing.** It\nhad three answers before this and all three were untrue: `reply` claims a result it does not have,\nsilence buys a reminder and then an escalation posted in its own name, and `--escalate` says \"I\ncannot carry this out\" while stopping a chain nothing is wrong with. Measured on 2026-09-08 in a\nlive workspace: a marketing role consulted four specialists ad hoc, reached for the third, and the\noperation view read\n\n```text\n5 thread(s), 4 open · NEEDS DECISION\n awaiting you — HANDED BACK by 47jy/head-of-marketing, not answered\n```\n\nNothing needed deciding. Four sessions were working.\n\nSuch a session now simply **ends its turn**. Nothing is declared and no new verb exists: the engine\nopened those sub-threads on the caller's behalf, so \"ended its turn owing an answer, with a round of\nits own still open\" is a fact about the record. It gets no reminder, nothing is posted in its name,\nand the one reminder attempt a genuinely forgetful session has is not spent on it. It is woken when\nthe sub-round returns — with whatever that round produced, an escalation included — and the ceiling\nstays the commissioned round's own deadline. There is no second clock.\n\n`nxc status` says it on the thread's own row, and `NEEDS DECISION` no longer fires:\n\n```text\nm-01M0… #positioning waiting on 47jy/head-of-marketing — waiting on its own sub-round (4 open)\n```\n\nThe `--json` field is `waiting_on_sub_round`, omitted where there is nothing to say.\n\nThree things change for anyone already running roles:\n\n- **A plain `nxc reply` while your own round is still open is now refused**, at the write point, with\n a reason that names what is being waited on. It means \"I am finished\", and that is not true yet.\n `nxc reply --escalate` and `--needs-rework` are untouched — \"I cannot\" can be true while a round of\n yours runs.\n- **The forced ending in every commissioned session's prompt says the opposite of what it said.** It\n used to read \"do not wait for work you commissioned yourself: if you cannot finish without it, end\n with `--escalate`\" — which is the instruction to raise the false alarm above. It now says to end\n the turn, and adds the thing that is genuinely dangerous once waiting is allowed: do not keep\n working while you wait, because the wake starts a second process under the same session in the same\n working copy and overwrites what the first one was still editing.\n- **An escalation gets its meaning back.** After this, one in an operation means a chain has stopped\n and somebody has to decide what happens to it.", - "de": "### Geändert\n- **Eine Rolle, die auf eine selbst beauftragte Runde wartet, kann das jetzt sagen — indem sie nichts\nsagt.** Vorher hatte sie drei Antworten, und alle drei waren unwahr: `reply` behauptet ein Ergebnis,\ndas es nicht gibt, Schweigen bringt eine Erinnerung und danach eine Eskalation in ihrem eigenen\nNamen, und `--escalate` sagt „das kann ich nicht ausführen\" und hält dabei eine Kette an, an der\nnichts falsch ist. Am 08.09.2026 in einem laufenden Arbeitsbereich gemessen: Eine Marketing-Rolle\nkonsultierte ad hoc vier Fachleute, griff zur dritten Antwort, und die Vorgangsübersicht las sich so:\n\n```text\n5 thread(s), 4 open · NEEDS DECISION\n awaiting you — HANDED BACK by 47jy/head-of-marketing, not answered\n```\n\nEs war nichts zu entscheiden. Vier Sitzungen arbeiteten.\n\nEine solche Sitzung **beendet jetzt einfach ihren Zug**. Nichts wird deklariert, und es gibt kein\nneues Verb: Die Engine hat diese Unterfäden im Auftrag des Aufrufers geöffnet, „Zug beendet, Antwort\ngeschuldet, eigene Runde noch offen\" ist also eine Tatsache über den Datensatz. Sie bekommt keine\nErinnerung, nichts wird in ihrem Namen gepostet, und der eine Erinnerungsversuch, den eine wirklich\nvergessliche Sitzung hat, wird dafür nicht verbraucht. Geweckt wird sie, wenn die Unterrunde\nzurückkommt — mit deren Ausgang, Eskalation eingeschlossen —, und die Decke bleibt die Frist jener\nRunde. Es gibt keine zweite Uhr.\n\n`nxc status` sagt es in der Zeile des Fadens, und `NEEDS DECISION` feuert nicht mehr:\n\n```text\nm-01M0… #positioning waiting on 47jy/head-of-marketing — waiting on its own sub-round (4 open)\n```\n\nDas `--json`-Feld heißt `waiting_on_sub_round` und fehlt, wo es nichts zu sagen gibt.\n\nDrei Dinge ändern sich für alle, die bereits Rollen betreiben:\n\n- **Ein blankes `nxc reply` bei offener eigener Runde wird jetzt abgelehnt**, an der Schreibstelle,\n mit einem Grund, der nennt, worauf gewartet wird. Es hieße „ich bin fertig\", und das stimmt noch\n nicht. `nxc reply --escalate` und `--needs-rework` bleiben unangetastet — „ich kann nicht\" kann\n wahr sein, während eine eigene Runde läuft.\n- **Das erzwungene Ende im Prompt jeder beauftragten Sitzung sagt das Gegenteil von vorher.** Es\n lautete „warte nicht auf Arbeit, die du selbst beauftragt hast: wenn du ohne sie nicht fertig\n wirst, beende mit `--escalate`\" — die Anweisung zum obigen Fehlalarm. Jetzt sagt es, den Zug zu\n beenden, und ergänzt das, was wirklich gefährlich ist, sobald Warten erlaubt ist: nicht\n weiterarbeiten, während man wartet, denn das Wecken startet einen zweiten Prozess unter derselben\n Sitzung in derselben Arbeitskopie und überschreibt, was der erste noch bearbeitet hat.\n- **Eine Eskalation bekommt ihre Bedeutung zurück.** Danach heißt eine in einem Vorgang: Eine Kette\n steht, und jemand muss entscheiden, wie es weitergeht.\n\n### Facade-Kontrakt\n- `breaking` · **Eine Rolle, die auf eine selbst beauftragte Runde wartet, kann das jetzt sagen — indem sie nichts\nsagt.** Vorher hatte sie drei Antworten, und alle drei waren unwahr: `reply` behauptet ein Ergebnis,\ndas es nicht gibt, Schweigen bringt eine Erinnerung und danach eine Eskalation in ihrem eigenen\nNamen, und `--escalate` sagt „das kann ich nicht ausführen\" und hält dabei eine Kette an, an der\nnichts falsch ist. Am 08.09.2026 in einem laufenden Arbeitsbereich gemessen: Eine Marketing-Rolle\nkonsultierte ad hoc vier Fachleute, griff zur dritten Antwort, und die Vorgangsübersicht las sich so:\n\n```text\n5 thread(s), 4 open · NEEDS DECISION\n awaiting you — HANDED BACK by 47jy/head-of-marketing, not answered\n```\n\nEs war nichts zu entscheiden. Vier Sitzungen arbeiteten.\n\nEine solche Sitzung **beendet jetzt einfach ihren Zug**. Nichts wird deklariert, und es gibt kein\nneues Verb: Die Engine hat diese Unterfäden im Auftrag des Aufrufers geöffnet, „Zug beendet, Antwort\ngeschuldet, eigene Runde noch offen\" ist also eine Tatsache über den Datensatz. Sie bekommt keine\nErinnerung, nichts wird in ihrem Namen gepostet, und der eine Erinnerungsversuch, den eine wirklich\nvergessliche Sitzung hat, wird dafür nicht verbraucht. Geweckt wird sie, wenn die Unterrunde\nzurückkommt — mit deren Ausgang, Eskalation eingeschlossen —, und die Decke bleibt die Frist jener\nRunde. Es gibt keine zweite Uhr.\n\n`nxc status` sagt es in der Zeile des Fadens, und `NEEDS DECISION` feuert nicht mehr:\n\n```text\nm-01M0… #positioning waiting on 47jy/head-of-marketing — waiting on its own sub-round (4 open)\n```\n\nDas `--json`-Feld heißt `waiting_on_sub_round` und fehlt, wo es nichts zu sagen gibt.\n\nDrei Dinge ändern sich für alle, die bereits Rollen betreiben:\n\n- **Ein blankes `nxc reply` bei offener eigener Runde wird jetzt abgelehnt**, an der Schreibstelle,\n mit einem Grund, der nennt, worauf gewartet wird. Es hieße „ich bin fertig\", und das stimmt noch\n nicht. `nxc reply --escalate` und `--needs-rework` bleiben unangetastet — „ich kann nicht\" kann\n wahr sein, während eine eigene Runde läuft.\n- **Das erzwungene Ende im Prompt jeder beauftragten Sitzung sagt das Gegenteil von vorher.** Es\n lautete „warte nicht auf Arbeit, die du selbst beauftragt hast: wenn du ohne sie nicht fertig\n wirst, beende mit `--escalate`\" — die Anweisung zum obigen Fehlalarm. Jetzt sagt es, den Zug zu\n beenden, und ergänzt das, was wirklich gefährlich ist, sobald Warten erlaubt ist: nicht\n weiterarbeiten, während man wartet, denn das Wecken startet einen zweiten Prozess unter derselben\n Sitzung in derselben Arbeitskopie und überschreibt, was der erste noch bearbeitet hat.\n- **Eine Eskalation bekommt ihre Bedeutung zurück.** Danach heißt eine in einem Vorgang: Eine Kette\n steht, und jemand muss entscheiden, wie es weitergeht." - } - }, - { - "version": "0.90.0", - "date": "2026-09-10", - "items": [ - { - "type": "added", - "en": "**A step of a channel's flow now says what it is given out of the round.** Until now every step was\ncommissioned with the request that opened the round, so an answer reached the next step only if\nsomebody wrote it down elsewhere. It travels now, and for the ordinary channel nothing has to be\ndeclared for it to:\n\n- the **first** step of a round is given the request;\n- every **later** step is given the answer of the step it was reached from.\n\nWith a back edge from `check` to `build`, the step before `ship` is always the check — so a finisher\nreads the review's verdict with nothing declared at all. That is the measured case: a finisher opened\na pull request and reported itself that no verdict had been put in front of it, while two finished\ngrade-A judgements expired unread.\n\nWhere the chain is written out flat instead — `build → check → fix → ship` — the step before `ship`\nis the coder, and the new optional `input:` key says otherwise:\n\n```yaml\n - id: ship\n target: finisher\n input: [check] # the verdict, not the coder's account of its own work\n```\n\n`input: [request]` is the words the round was opened with, `input: [request, check]` is both,\n`input: []` is nothing but this step's own instructions — the off switch, for a verifier that must\njudge the tree and not the coder's account of it. A named step that ran more than once contributes\nits last answer. `request` is the one name that is not a step; a step declared under that id is\nrefused when the team is loaded, and so is an `input:` naming a step the channel does not declare.\n\nAn answer from another party arrives framed as data, in the same delimited, attributed block a\nchannel's own consolidation uses. No model runs between two steps: the coordinator puts the answer\ntogether, it does not summarize it. A step's own `task:` and its persona are untouched by `input:`,\nand the way *back* is unchanged — it already carries the reviewer's findings word for word.\n\n**Two things a channel author should re-read after upgrading.** A later step of a stepped channel is\nnow given its predecessor's answer where it used to be given the request; a step that genuinely wants\nthe request declares `input: [request]`. And `visibility:` and `input:` are now two different\nquestions — the first decides who may read a thread, the second what a step is given, so a finisher\ncan be handed the verdict of a review round whose thread it may not read.", - "de": "**Ein Schritt im Ablauf eines Kanals sagt jetzt, was er aus dem Durchgang mitbekommt.** Bisher wurde\njeder Schritt mit dem Auftrag beauftragt, der den Durchgang eröffnet hatte — eine Antwort erreichte\nden nächsten Schritt also nur, wenn jemand sie anderswo notierte. Sie reist jetzt mit, und für den\ngewöhnlichen Kanal muss dafür nichts deklariert werden:\n\n- der **erste** Schritt eines Durchgangs bekommt den Auftrag;\n- jeder **weitere** bekommt die Antwort des Schritts, von dem aus er erreicht wurde.\n\nMit einer Rückkante von `check` nach `build` ist der Schritt vor `ship` immer die Prüfung — ein\nFinisher liest das Urteil der Prüfrunde also ohne jede Angabe. Das ist der gemessene Fall: ein\nFinisher stellte einen Pull Request ein und meldete selbst, dass ihm kein Prüfurteil vorlag, während\nzwei fertige Urteile mit Note A ungelesen verfielen.\n\nWo die Kette stattdessen flach ausgeschrieben ist — `build → check → fix → ship` — ist der Schritt\nvor `ship` der Coder, und das neue optionale Feld `input:` sagt es anders:\n\n```yaml\n - id: ship\n target: finisher\n input: [check] # das Urteil, nicht der Bericht des Coders über die eigene Arbeit\n```\n\n`input: [request]` sind die Worte, mit denen der Durchgang eröffnet wurde, `input: [request, check]`\nist beides, `input: []` ist nichts außer der eigenen Aufgabenbeschreibung des Schritts — der\nAbschalter, für einen Verifier, der den Baum prüfen soll und nicht den Bericht des Coders darüber.\nLief ein benannter Schritt mehrfach, kommt seine letzte Antwort. `request` ist der einzige Name, der\nkein Schritt ist; ein Schritt unter dieser id wird beim Laden zurückgewiesen, ebenso ein `input:`,\ndas einen Schritt nennt, den der Kanal nicht deklariert.\n\nEine fremde Antwort kommt als Daten gekennzeichnet an, im selben abgegrenzten, zugeordneten Block,\nden auch die Zusammenführung eines Kanals verwendet. Zwischen zwei Schritten läuft kein Modell: der\nKoordinator setzt zusammen, er fasst nicht zusammen. Das `task:` eines Schritts und seine Persona\nbleiben von `input:` unberührt, und der Weg *zurück* ist unverändert — er trägt die Befunde des\nPrüfers ohnehin schon wörtlich.\n\n**Zwei Dinge, die ein Kanal-Designer nach dem Update noch einmal lesen sollte.** Ein späterer Schritt\neines Kanals mit `steps:` bekommt jetzt die Antwort seines Vorgängers, wo er bisher den Auftrag\nbekam; wer wirklich den Auftrag will, deklariert `input: [request]`. Und `visibility:` und `input:`\nsind nun zwei verschiedene Fragen — die erste entscheidet, wer einen Faden lesen darf, die zweite,\nwas ein Schritt zu sehen bekommt; ein Finisher kann also das Urteil einer Prüfrunde bekommen, deren\nFaden er nicht lesen darf.", - "facade": "breaking" - } - ], - "notes": { - "en": "### Added\n- **A step of a channel's flow now says what it is given out of the round.** Until now every step was\ncommissioned with the request that opened the round, so an answer reached the next step only if\nsomebody wrote it down elsewhere. It travels now, and for the ordinary channel nothing has to be\ndeclared for it to:\n\n- the **first** step of a round is given the request;\n- every **later** step is given the answer of the step it was reached from.\n\nWith a back edge from `check` to `build`, the step before `ship` is always the check — so a finisher\nreads the review's verdict with nothing declared at all. That is the measured case: a finisher opened\na pull request and reported itself that no verdict had been put in front of it, while two finished\ngrade-A judgements expired unread.\n\nWhere the chain is written out flat instead — `build → check → fix → ship` — the step before `ship`\nis the coder, and the new optional `input:` key says otherwise:\n\n```yaml\n - id: ship\n target: finisher\n input: [check] # the verdict, not the coder's account of its own work\n```\n\n`input: [request]` is the words the round was opened with, `input: [request, check]` is both,\n`input: []` is nothing but this step's own instructions — the off switch, for a verifier that must\njudge the tree and not the coder's account of it. A named step that ran more than once contributes\nits last answer. `request` is the one name that is not a step; a step declared under that id is\nrefused when the team is loaded, and so is an `input:` naming a step the channel does not declare.\n\nAn answer from another party arrives framed as data, in the same delimited, attributed block a\nchannel's own consolidation uses. No model runs between two steps: the coordinator puts the answer\ntogether, it does not summarize it. A step's own `task:` and its persona are untouched by `input:`,\nand the way *back* is unchanged — it already carries the reviewer's findings word for word.\n\n**Two things a channel author should re-read after upgrading.** A later step of a stepped channel is\nnow given its predecessor's answer where it used to be given the request; a step that genuinely wants\nthe request declares `input: [request]`. And `visibility:` and `input:` are now two different\nquestions — the first decides who may read a thread, the second what a step is given, so a finisher\ncan be handed the verdict of a review round whose thread it may not read.\n\n### Facade Contract\n- `breaking` · **A step of a channel's flow now says what it is given out of the round.** Until now every step was\ncommissioned with the request that opened the round, so an answer reached the next step only if\nsomebody wrote it down elsewhere. It travels now, and for the ordinary channel nothing has to be\ndeclared for it to:\n\n- the **first** step of a round is given the request;\n- every **later** step is given the answer of the step it was reached from.\n\nWith a back edge from `check` to `build`, the step before `ship` is always the check — so a finisher\nreads the review's verdict with nothing declared at all. That is the measured case: a finisher opened\na pull request and reported itself that no verdict had been put in front of it, while two finished\ngrade-A judgements expired unread.\n\nWhere the chain is written out flat instead — `build → check → fix → ship` — the step before `ship`\nis the coder, and the new optional `input:` key says otherwise:\n\n```yaml\n - id: ship\n target: finisher\n input: [check] # the verdict, not the coder's account of its own work\n```\n\n`input: [request]` is the words the round was opened with, `input: [request, check]` is both,\n`input: []` is nothing but this step's own instructions — the off switch, for a verifier that must\njudge the tree and not the coder's account of it. A named step that ran more than once contributes\nits last answer. `request` is the one name that is not a step; a step declared under that id is\nrefused when the team is loaded, and so is an `input:` naming a step the channel does not declare.\n\nAn answer from another party arrives framed as data, in the same delimited, attributed block a\nchannel's own consolidation uses. No model runs between two steps: the coordinator puts the answer\ntogether, it does not summarize it. A step's own `task:` and its persona are untouched by `input:`,\nand the way *back* is unchanged — it already carries the reviewer's findings word for word.\n\n**Two things a channel author should re-read after upgrading.** A later step of a stepped channel is\nnow given its predecessor's answer where it used to be given the request; a step that genuinely wants\nthe request declares `input: [request]`. And `visibility:` and `input:` are now two different\nquestions — the first decides who may read a thread, the second what a step is given, so a finisher\ncan be handed the verdict of a review round whose thread it may not read.", - "de": "### Neu\n- **Ein Schritt im Ablauf eines Kanals sagt jetzt, was er aus dem Durchgang mitbekommt.** Bisher wurde\njeder Schritt mit dem Auftrag beauftragt, der den Durchgang eröffnet hatte — eine Antwort erreichte\nden nächsten Schritt also nur, wenn jemand sie anderswo notierte. Sie reist jetzt mit, und für den\ngewöhnlichen Kanal muss dafür nichts deklariert werden:\n\n- der **erste** Schritt eines Durchgangs bekommt den Auftrag;\n- jeder **weitere** bekommt die Antwort des Schritts, von dem aus er erreicht wurde.\n\nMit einer Rückkante von `check` nach `build` ist der Schritt vor `ship` immer die Prüfung — ein\nFinisher liest das Urteil der Prüfrunde also ohne jede Angabe. Das ist der gemessene Fall: ein\nFinisher stellte einen Pull Request ein und meldete selbst, dass ihm kein Prüfurteil vorlag, während\nzwei fertige Urteile mit Note A ungelesen verfielen.\n\nWo die Kette stattdessen flach ausgeschrieben ist — `build → check → fix → ship` — ist der Schritt\nvor `ship` der Coder, und das neue optionale Feld `input:` sagt es anders:\n\n```yaml\n - id: ship\n target: finisher\n input: [check] # das Urteil, nicht der Bericht des Coders über die eigene Arbeit\n```\n\n`input: [request]` sind die Worte, mit denen der Durchgang eröffnet wurde, `input: [request, check]`\nist beides, `input: []` ist nichts außer der eigenen Aufgabenbeschreibung des Schritts — der\nAbschalter, für einen Verifier, der den Baum prüfen soll und nicht den Bericht des Coders darüber.\nLief ein benannter Schritt mehrfach, kommt seine letzte Antwort. `request` ist der einzige Name, der\nkein Schritt ist; ein Schritt unter dieser id wird beim Laden zurückgewiesen, ebenso ein `input:`,\ndas einen Schritt nennt, den der Kanal nicht deklariert.\n\nEine fremde Antwort kommt als Daten gekennzeichnet an, im selben abgegrenzten, zugeordneten Block,\nden auch die Zusammenführung eines Kanals verwendet. Zwischen zwei Schritten läuft kein Modell: der\nKoordinator setzt zusammen, er fasst nicht zusammen. Das `task:` eines Schritts und seine Persona\nbleiben von `input:` unberührt, und der Weg *zurück* ist unverändert — er trägt die Befunde des\nPrüfers ohnehin schon wörtlich.\n\n**Zwei Dinge, die ein Kanal-Designer nach dem Update noch einmal lesen sollte.** Ein späterer Schritt\neines Kanals mit `steps:` bekommt jetzt die Antwort seines Vorgängers, wo er bisher den Auftrag\nbekam; wer wirklich den Auftrag will, deklariert `input: [request]`. Und `visibility:` und `input:`\nsind nun zwei verschiedene Fragen — die erste entscheidet, wer einen Faden lesen darf, die zweite,\nwas ein Schritt zu sehen bekommt; ein Finisher kann also das Urteil einer Prüfrunde bekommen, deren\nFaden er nicht lesen darf.\n\n### Facade-Kontrakt\n- `breaking` · **Ein Schritt im Ablauf eines Kanals sagt jetzt, was er aus dem Durchgang mitbekommt.** Bisher wurde\njeder Schritt mit dem Auftrag beauftragt, der den Durchgang eröffnet hatte — eine Antwort erreichte\nden nächsten Schritt also nur, wenn jemand sie anderswo notierte. Sie reist jetzt mit, und für den\ngewöhnlichen Kanal muss dafür nichts deklariert werden:\n\n- der **erste** Schritt eines Durchgangs bekommt den Auftrag;\n- jeder **weitere** bekommt die Antwort des Schritts, von dem aus er erreicht wurde.\n\nMit einer Rückkante von `check` nach `build` ist der Schritt vor `ship` immer die Prüfung — ein\nFinisher liest das Urteil der Prüfrunde also ohne jede Angabe. Das ist der gemessene Fall: ein\nFinisher stellte einen Pull Request ein und meldete selbst, dass ihm kein Prüfurteil vorlag, während\nzwei fertige Urteile mit Note A ungelesen verfielen.\n\nWo die Kette stattdessen flach ausgeschrieben ist — `build → check → fix → ship` — ist der Schritt\nvor `ship` der Coder, und das neue optionale Feld `input:` sagt es anders:\n\n```yaml\n - id: ship\n target: finisher\n input: [check] # das Urteil, nicht der Bericht des Coders über die eigene Arbeit\n```\n\n`input: [request]` sind die Worte, mit denen der Durchgang eröffnet wurde, `input: [request, check]`\nist beides, `input: []` ist nichts außer der eigenen Aufgabenbeschreibung des Schritts — der\nAbschalter, für einen Verifier, der den Baum prüfen soll und nicht den Bericht des Coders darüber.\nLief ein benannter Schritt mehrfach, kommt seine letzte Antwort. `request` ist der einzige Name, der\nkein Schritt ist; ein Schritt unter dieser id wird beim Laden zurückgewiesen, ebenso ein `input:`,\ndas einen Schritt nennt, den der Kanal nicht deklariert.\n\nEine fremde Antwort kommt als Daten gekennzeichnet an, im selben abgegrenzten, zugeordneten Block,\nden auch die Zusammenführung eines Kanals verwendet. Zwischen zwei Schritten läuft kein Modell: der\nKoordinator setzt zusammen, er fasst nicht zusammen. Das `task:` eines Schritts und seine Persona\nbleiben von `input:` unberührt, und der Weg *zurück* ist unverändert — er trägt die Befunde des\nPrüfers ohnehin schon wörtlich.\n\n**Zwei Dinge, die ein Kanal-Designer nach dem Update noch einmal lesen sollte.** Ein späterer Schritt\neines Kanals mit `steps:` bekommt jetzt die Antwort seines Vorgängers, wo er bisher den Auftrag\nbekam; wer wirklich den Auftrag will, deklariert `input: [request]`. Und `visibility:` und `input:`\nsind nun zwei verschiedene Fragen — die erste entscheidet, wer einen Faden lesen darf, die zweite,\nwas ein Schritt zu sehen bekommt; ein Finisher kann also das Urteil einer Prüfrunde bekommen, deren\nFaden er nicht lesen darf." - } - }, - { - "version": "0.89.1", - "date": "2026-09-09", - "items": [ - { - "type": "fixed", - "en": "**A reviewer inside a review round is now told about `--needs-rework`.** A step whose target is a\nwhole channel may declare `on_needs_rework:`, and one member's verdict has always been enough to\nsend the round back. But the offer stopped one level short of them: the step stands on the channel's\nthread and the members stand below it, so each member's forced ending read *\"there are exactly two\nways to end it\"* — while the very same persona, addressed directly as a step's target, was offered\nthree.\n\n```yaml\n - id: judge\n target: review # a whole channel: three reviewers, one thread each\n on_needs_rework: design # …and until now none of them was told this existed\n max_passes: 2\n```\n\nThe consequence was silent and one-directional: a round could hand out grades but never send the\ndraft back, because the engine's own obligation text told the parties expected to pull the edge that\nit was not there. Every member of a channel a step commissioned now reads the same three endings a\nrole target reads.\n\nUnchanged: the bit is offered exactly where it has somewhere to go. A member of a channel whose\ncommissioning step declares no back edge is still told about two endings, and so is anyone such a\nmember commissions itself — that session is running the member's errand, not the step's.", - "de": "**Ein Prüfer innerhalb einer Prüfrunde erfährt jetzt von `--needs-rework`.** Ein Schritt, dessen\nZiel ein ganzer Kanal ist, darf `on_needs_rework:` deklarieren, und die Wertung eines einzigen\nMitglieds hat die Runde immer schon zurückgeschickt. Nur das Angebot blieb eine Ebene darüber\nstehen: Der Schritt steht auf dem Faden des Kanals, die Mitglieder stehen darunter — und so las das\nerzwungene Ende jedes Mitglieds *„es gibt genau zwei Wege, ihn zu beenden\"*, während dieselbe Persona\nals direktes Ziel eines Schritts drei angeboten bekam.\n\n```yaml\n - id: judge\n target: review # ein ganzer Kanal: drei Prüfer, je ein Faden\n on_needs_rework: design # … und bis jetzt erfuhr keiner von ihnen davon\n max_passes: 2\n```\n\nDie Folge war lautlos und einseitig: Eine Runde konnte Noten vergeben, den Entwurf aber nie\nzurückschicken, weil der Pflichttext der Engine ausgerechnet denen, die die Rückkante ziehen sollen,\nsagte, es gebe sie nicht. Jedes Mitglied eines von einem Schritt beauftragten Kanals liest jetzt\ndieselben drei Ausgänge wie ein Rollen-Ziel.\n\nUnverändert: Angeboten wird das Bit genau dort, wo es irgendwohin führt. Ein Mitglied eines Kanals,\ndessen beauftragender Schritt keine Rückkante deklariert, erfährt weiterhin von zwei Ausgängen — und\nebenso, wen ein solches Mitglied selbst beauftragt: Diese Sitzung erledigt die Besorgung des\nMitglieds, nicht den Schritt.", - "facade": "changed" - } - ], - "notes": { - "en": "### Fixed\n- **A reviewer inside a review round is now told about `--needs-rework`.** A step whose target is a\nwhole channel may declare `on_needs_rework:`, and one member's verdict has always been enough to\nsend the round back. But the offer stopped one level short of them: the step stands on the channel's\nthread and the members stand below it, so each member's forced ending read *\"there are exactly two\nways to end it\"* — while the very same persona, addressed directly as a step's target, was offered\nthree.\n\n```yaml\n - id: judge\n target: review # a whole channel: three reviewers, one thread each\n on_needs_rework: design # …and until now none of them was told this existed\n max_passes: 2\n```\n\nThe consequence was silent and one-directional: a round could hand out grades but never send the\ndraft back, because the engine's own obligation text told the parties expected to pull the edge that\nit was not there. Every member of a channel a step commissioned now reads the same three endings a\nrole target reads.\n\nUnchanged: the bit is offered exactly where it has somewhere to go. A member of a channel whose\ncommissioning step declares no back edge is still told about two endings, and so is anyone such a\nmember commissions itself — that session is running the member's errand, not the step's.\n\n### Facade Contract\n- `changed` · **A reviewer inside a review round is now told about `--needs-rework`.** A step whose target is a\nwhole channel may declare `on_needs_rework:`, and one member's verdict has always been enough to\nsend the round back. But the offer stopped one level short of them: the step stands on the channel's\nthread and the members stand below it, so each member's forced ending read *\"there are exactly two\nways to end it\"* — while the very same persona, addressed directly as a step's target, was offered\nthree.\n\n```yaml\n - id: judge\n target: review # a whole channel: three reviewers, one thread each\n on_needs_rework: design # …and until now none of them was told this existed\n max_passes: 2\n```\n\nThe consequence was silent and one-directional: a round could hand out grades but never send the\ndraft back, because the engine's own obligation text told the parties expected to pull the edge that\nit was not there. Every member of a channel a step commissioned now reads the same three endings a\nrole target reads.\n\nUnchanged: the bit is offered exactly where it has somewhere to go. A member of a channel whose\ncommissioning step declares no back edge is still told about two endings, and so is anyone such a\nmember commissions itself — that session is running the member's errand, not the step's.", - "de": "### Behoben\n- **Ein Prüfer innerhalb einer Prüfrunde erfährt jetzt von `--needs-rework`.** Ein Schritt, dessen\nZiel ein ganzer Kanal ist, darf `on_needs_rework:` deklarieren, und die Wertung eines einzigen\nMitglieds hat die Runde immer schon zurückgeschickt. Nur das Angebot blieb eine Ebene darüber\nstehen: Der Schritt steht auf dem Faden des Kanals, die Mitglieder stehen darunter — und so las das\nerzwungene Ende jedes Mitglieds *„es gibt genau zwei Wege, ihn zu beenden\"*, während dieselbe Persona\nals direktes Ziel eines Schritts drei angeboten bekam.\n\n```yaml\n - id: judge\n target: review # ein ganzer Kanal: drei Prüfer, je ein Faden\n on_needs_rework: design # … und bis jetzt erfuhr keiner von ihnen davon\n max_passes: 2\n```\n\nDie Folge war lautlos und einseitig: Eine Runde konnte Noten vergeben, den Entwurf aber nie\nzurückschicken, weil der Pflichttext der Engine ausgerechnet denen, die die Rückkante ziehen sollen,\nsagte, es gebe sie nicht. Jedes Mitglied eines von einem Schritt beauftragten Kanals liest jetzt\ndieselben drei Ausgänge wie ein Rollen-Ziel.\n\nUnverändert: Angeboten wird das Bit genau dort, wo es irgendwohin führt. Ein Mitglied eines Kanals,\ndessen beauftragender Schritt keine Rückkante deklariert, erfährt weiterhin von zwei Ausgängen — und\nebenso, wen ein solches Mitglied selbst beauftragt: Diese Sitzung erledigt die Besorgung des\nMitglieds, nicht den Schritt.\n\n### Facade-Kontrakt\n- `changed` · **Ein Prüfer innerhalb einer Prüfrunde erfährt jetzt von `--needs-rework`.** Ein Schritt, dessen\nZiel ein ganzer Kanal ist, darf `on_needs_rework:` deklarieren, und die Wertung eines einzigen\nMitglieds hat die Runde immer schon zurückgeschickt. Nur das Angebot blieb eine Ebene darüber\nstehen: Der Schritt steht auf dem Faden des Kanals, die Mitglieder stehen darunter — und so las das\nerzwungene Ende jedes Mitglieds *„es gibt genau zwei Wege, ihn zu beenden\"*, während dieselbe Persona\nals direktes Ziel eines Schritts drei angeboten bekam.\n\n```yaml\n - id: judge\n target: review # ein ganzer Kanal: drei Prüfer, je ein Faden\n on_needs_rework: design # … und bis jetzt erfuhr keiner von ihnen davon\n max_passes: 2\n```\n\nDie Folge war lautlos und einseitig: Eine Runde konnte Noten vergeben, den Entwurf aber nie\nzurückschicken, weil der Pflichttext der Engine ausgerechnet denen, die die Rückkante ziehen sollen,\nsagte, es gebe sie nicht. Jedes Mitglied eines von einem Schritt beauftragten Kanals liest jetzt\ndieselben drei Ausgänge wie ein Rollen-Ziel.\n\nUnverändert: Angeboten wird das Bit genau dort, wo es irgendwohin führt. Ein Mitglied eines Kanals,\ndessen beauftragender Schritt keine Rückkante deklariert, erfährt weiterhin von zwei Ausgängen — und\nebenso, wen ein solches Mitglied selbst beauftragt: Diese Sitzung erledigt die Besorgung des\nMitglieds, nicht den Schritt." - } - }, - { - "version": "0.89.0", - "date": "2026-09-09", - "items": [ - { - "type": "fixed", - "en": "**A flow step whose target is a whole channel no longer counts as answered before that channel has\ndelivered.** Such a step is opened by the channel supervisor, which is also the party expected to\nanswer it — and the step's opening message was therefore counted as its own answer, so the step read\nas finished from the instant it was created. Three consequences, all measured in one live run: the\nstep *after* it started beside the review round instead of after it and worked without the verdict;\n`nxc status` showed the review branch as `answered` while its members were still working; and a\nmember that had already answered showed as `orphaned`, because that state asks whether the parent\nwas asked and answered.\n\nAll three came from one cause and are fixed at it: a thread's opening message is now written before\nthe obligation it declares, so the question is never counted as the answer to itself. Nothing\nchanges for any other thread — the count only ever looked at messages from an expected party, and\nelsewhere the opener is not one.\n\n**What this does not do**, stated because the case it removes is the one people will ask about: a\nstep that has already started still cannot be reached — an escalation or a send-back arrives after\nit is running and stops nothing, because the engine has no way to interrupt a step at all. This\nchange removes the bug that used to start a step too early; it does not add that missing ability,\nwhich is tracked on its own.", - "de": "**Ein Ablaufschritt, dessen Ziel ein ganzer Kanal ist, gilt nicht mehr als beantwortet, bevor dieser\nKanal geliefert hat.** So einen Schritt eröffnet der Kanal-Supervisor, der zugleich die Instanz ist,\nvon der die Antwort erwartet wird — die Eröffnungsnachricht des Schritts zählte damit als seine\neigene Antwort, und der Schritt las sich vom ersten Moment an als fertig. Drei Folgen, alle in einem\nechten Lauf gemessen: der *nachfolgende* Schritt lief neben der Reviewrunde statt nach ihr und\narbeitete ohne deren Spruch; `nxc status` zeigte den Review-Zweig als `answered`, während seine\nMitglieder noch arbeiteten; und ein Mitglied, das schon geantwortet hatte, erschien als `orphaned` —\ndenn dieser Zustand fragt danach, ob der Elternfaden gefragt und beantwortet wurde.\n\nAlle drei hatten eine Ursache und sind dort behoben: die Eröffnungsnachricht eines Fadens wird jetzt\nvor der Verpflichtung geschrieben, die sie erklärt — die Frage zählt nie mehr als ihre eigene\nAntwort. Für jeden anderen Faden ändert sich nichts: gezählt wurden immer nur Nachrichten einer\nerwarteten Instanz, und sonst ist der Eröffner keine.\n\n**Was das nicht tut** — gesagt, weil genau danach gefragt werden wird: Ein Schritt, der bereits\nläuft, ist weiterhin nicht erreichbar. Eine Eskalation oder eine Rückkante, die eintrifft, während\ner läuft, hält ihn nicht auf, denn die Engine kann einen laufenden Schritt überhaupt nicht\nunterbrechen. Diese Änderung nimmt den Fehler weg, der einen Schritt zu früh startete; die fehlende\nFähigkeit selbst kommt separat und wird eigens verfolgt.", - "facade": "changed" - }, - { - "type": "added", - "en": "**A conversation has a name, not just a hash.** `nxc send` now gives the thread it opens a short\ndisplay name — at most seven words, derived from the message you sent — so a surface can show what\na conversation is about instead of `dm:d9ab4f601a08…`. A direct conversation's id is a hash over the\ntwo handles and by construction has no name; this is the name.\n\nThe name rides on every thread read (`nxc threads list`/`show`, `nxc status`, and the app seam's\nquorum) and is written once, when the thread is opened: no later message renames it. It is a display\nname and nothing keys on it — the id is still the id.\n\nDeriving it never holds up or fails a `send`: it happens out of band, in a run the coordinator\ncommissions, and a thread nobody could name stays a perfectly ordinary thread that every surface\nrenders as before. `nxc threads name []` states or derives one by hand, and reads\nthe thread it names under the same membership rule `nxc threads show` does.\n\nAn embedding app names threads itself with `Engine::name_thread`, or asks the engine to by setting\n`EngineConfig::namer`; the default names nothing, so no app starts paying for model calls because it\nopened a workspace.", - "de": "**Eine Unterhaltung hat einen Namen, nicht nur einen Hash.** `nxc send` gibt dem Faden, den es\nöffnet, jetzt einen kurzen Anzeigenamen — höchstens sieben Wörter, abgeleitet aus der gesendeten\nNachricht —, damit eine Oberfläche zeigen kann, worum es geht, statt `dm:d9ab4f601a08…`. Die Id\neiner Direktunterhaltung ist ein Hash über die beiden Handles und hat bauartbedingt keinen Namen;\ndas hier ist der Name.\n\nDer Name reist auf jeder Faden-Lesenaht mit (`nxc threads list`/`show`, `nxc status` und das Quorum\nder App-Naht) und wird einmal geschrieben, beim Öffnen des Fadens: keine spätere Nachricht benennt\nihn um. Er ist ein Anzeigename, und nichts keyt darauf — die Id bleibt die Id.\n\nDie Ableitung hält ein `send` nie auf und lässt es nie scheitern: sie läuft außerhalb, in einem Lauf,\nden der Koordinator beauftragt, und ein Faden ohne Namen bleibt ein ganz gewöhnlicher Faden, den jede\nOberfläche wie bisher darstellt. `nxc threads name []` setzt oder leitet einen von\nHand ab und liest den Faden, den es benennt, unter derselben Mitgliedschaftsregel wie\n`nxc threads show`.\n\nEine einbettende App benennt Fäden selbst über `Engine::name_thread` oder lässt die Engine es tun\n(`EngineConfig::namer`); die Voreinstellung benennt nichts, damit keine App Modellaufrufe bezahlt,\nnur weil sie einen Arbeitsbereich geöffnet hat.", - "facade": "breaking" - }, - { - "type": "fixed", - "en": "**A reply into a round that is over is refused instead of accepted in silence.** `nxc reply\n--thread ` used to take a message into a step of an operation that had already delivered, post\nit, and answer with a receipt that named a due date — while nothing had been started and nothing\ncould be: the round's answers had been consolidated and handed on long before. Measured in a live\nrun: twenty minutes of waiting for work nobody had been asked to do. Such a reply now comes back\nwith a refusal that says the round is over, names the round to read, and names the command that\ndoes start the next piece of work.\n\n**And no receipt promises an answer nobody owes.** The window a channel declares is reported only\nwhile somebody actually still owes a reply on that thread. A round that has been answered no longer\nhands its caller a date to wait for.\n\nThe sentence every persona is handed at session start says the same thing now: replying into your\nown thread resumes the conversation *while the round is open*, and once it has been answered and\nhanded on you start the next thing with `nxc send`.", - "de": "**Eine Antwort in eine beendete Runde wird abgelehnt statt stillschweigend angenommen.** `nxc reply\n--thread ` hat eine Nachricht bisher auch in einen Schritt eines längst ausgelieferten Vorgangs\naufgenommen, sie abgelegt und eine Quittung mit einer Frist zurückgegeben — obwohl nichts gestartet\nwurde und nichts gestartet werden konnte: die Antworten dieser Runde waren lange vorher\nzusammengeführt und weitergereicht. In einem echten Lauf gemessen: zwanzig Minuten Warten auf eine\nArbeit, um die niemand gebeten worden war. Eine solche Antwort wird jetzt abgelehnt, mit einem\nGrund, der sagt, dass die Runde beendet ist, welche Runde nachzulesen ist und welcher Befehl die\nnächste Arbeit tatsächlich startet.\n\n**Und keine Quittung verspricht eine Antwort, die niemand schuldet.** Das von einem Kanal\ndeklarierte Zeitfenster wird nur noch genannt, solange auf diesem Faden wirklich noch jemand eine\nAntwort schuldet. Eine beantwortete Runde gibt ihrem Aufrufer keine Frist mehr mit.\n\nDer Satz, den jede Persona beim Sitzungsstart bekommt, sagt jetzt dasselbe: eine Antwort auf den\neigenen Faden nimmt die Unterhaltung wieder auf, *solange die Runde offen ist* — ist sie beantwortet\nund weitergereicht, beginnt das Nächste mit `nxc send`.", - "facade": "breaking" - }, - { - "type": "added", - "en": "**A step of a channel's flow can now say what its target is to do there, and at what experience.**\nTwo new optional keys on a `steps:` entry:\n\n```yaml\n - id: check\n target: review # the same three reviewers the coding chain uses\n stage: senior # …but a design judgement is dearer thinking than a diff\n task: |\n You are judging a SPECIFICATION, not a diff. Ask what would happen if what is written\n here were built.\n```\n\n`task:` becomes a fourth layer of the target's prompt, under the persona's own `system_prompt` and\nmarked as the step's, so a model can tell the two apart. It **adds**: a step can neither replace a\npersona nor switch a part of it off, and the engine says so in a sentence of its own above the\ntask — where the two seem to disagree, the role's rubric, its answering rules and its threshold\nstand. `stage:` runs the target at another band than the one it declared — `junior | senior |\nprincipal`, the same vocabulary a persona uses. A step may **not** name a model: the key is refused\nby name when the team is loaded and points at `stage:` instead, and a persona that declared its own\n`model:` keeps it whatever a step asks for.\n\nThis is what lets one declared set of reviewers judge a diff at one station and a specification at\nanother. Until now the job was welded into the role — the shipped reviewer personas say \"read the\ndiff\" in as many words — so a second kind of review meant a second set of personas to keep in step\nwith the first.\n\nBoth keys are optional and a step that declares neither composes exactly the prompt it composed\nbefore, byte for byte. A step whose target is a whole **channel** passes both to every member: a\nchannel thread runs no session, its members do. It reaches the step's target and no further —\nsomebody the step's own session goes on to commission is doing that session's errand, not the\nstep's.\n\n**For embedding consumers:** `nexus_chat::role::compose_system_prompt` takes a fifth argument,\n`Option` — pass `None` for the previous behaviour. `RoleDecl::effective_model` is\nunchanged and now delegates to the new `RoleDecl::effective_model_at`.", - "de": "**Ein Schritt eines Kanalablaufs kann jetzt sagen, was sein Ziel dort tun soll und mit welcher\nErfahrung.** Zwei neue, freiwillige Schlüssel an einem `steps:`-Eintrag:\n\n```yaml\n - id: check\n target: review # dieselben drei Prüfer wie in der Codekette\n stage: senior # … aber ein Entwurfsurteil ist teurere Denkarbeit als ein Diff\n task: |\n Du beurteilst eine SPEZIFIKATION, keinen Diff. Frage, was passieren WÜRDE, wenn das hier\n Geschriebene so gebaut wird.\n```\n\n`task:` wird zu einer vierten Ebene im Prompt des Ziels, unter dem eigenen `system_prompt` der\nPersona und als die des Schritts gekennzeichnet, damit ein Modell beides unterscheiden kann. Sie\n**ergänzt**: Ein Schritt kann eine Persona weder ersetzen noch Teile von ihr abschalten, und die\nEngine sagt das in einem eigenen Satz über der Aufgabe — wo beide sich zu widersprechen scheinen,\ngelten die Rubrik der Rolle, ihre Antwortregeln und ihre Schwelle. `stage:` lässt das Ziel auf einer\nanderen Stufe laufen als der selbst deklarierten — `junior | senior | principal`, dasselbe\nVokabular wie bei einer Persona. Ein Schritt darf **kein** Modell benennen: Der Schlüssel wird beim\nLaden des Teams namentlich abgelehnt und verweist auf `stage:`, und eine Persona mit eigenem\n`model:` behält es, was ein Schritt auch verlangt.\n\nDamit kann ein einziger deklarierter Satz Prüfer an der einen Station einen Diff und an der anderen\neine Spezifikation beurteilen. Bisher steckte die Aufgabe in der Rolle — die ausgelieferten\nPrüfer-Personas sagen wörtlich „lies den Diff\" —, sodass eine zweite Art von Prüfung einen zweiten\nSatz Personas mit doppelter Pflege bedeutete.\n\nBeide Schlüssel sind freiwillig, und ein Schritt, der keinen von beiden deklariert, setzt seinen\nPrompt genau so zusammen wie vorher, Byte für Byte. Ein Schritt, dessen Ziel ein ganzer **Kanal**\nist, reicht beides an jedes Mitglied weiter: Ein Kanalfaden führt keine Sitzung, seine Mitglieder\ntun es. Weiter reicht es nicht — wen die Sitzung des Schritts danach selbst beauftragt, erledigt\neine Besorgung dieser Sitzung und nicht den Schritt.\n\n**Für einbettende Konsumenten:** `nexus_chat::role::compose_system_prompt` nimmt ein fünftes\nArgument, `Option` — `None` ergibt das bisherige Verhalten. `RoleDecl::effective_model`\nist unverändert und ruft jetzt das neue `RoleDecl::effective_model_at` auf.", - "facade": "breaking" - }, - { - "type": "changed", - "en": "**`nxc reply` no longer needs a thread id while only one conversation is open for you.** A role is\nsupposed to know what it has to do and whom it may answer — not who commissioned it — so the id it\nwas handed minutes ago is no longer part of answering. `nxc reply \"\"` answers the one\nconversation waiting for you.\n\nAmbiguity does not come back through the back door: with more than one open — you owe your\ncommissioner an answer AND the role you consulted has come back to you — `--thread` is required\nagain, and the refusal names every open conversation with the command it takes. The id-bearing form\nis always allowed.\n\n**And the coordinator names the addressees at the moment it wakes you.** Every wake — a fresh\ncommission, an answer coming back, a batched delivery — now ends with which conversations are open,\nwhom each one reaches and which verb each one takes, instead of a single fixed\n`Reply via nxc reply --thread ` that named one thread whatever the situation was.", - "de": "**`nxc reply` braucht keine Faden-Id mehr, solange genau eine Unterhaltung für Sie offen ist.** Eine\nRolle soll wissen, was sie zu tun hat und wem sie antworten darf — nicht, wer sie beauftragt hat —,\nalso gehört die vor Minuten übergebene Id nicht mehr zum Antworten. `nxc reply \"\"`\nbeantwortet die eine Unterhaltung, die auf Sie wartet.\n\nMehrdeutigkeit kommt nicht durch die Hintertür zurück: sind mehrere offen — Sie schulden Ihrem\nAuftraggeber eine Antwort UND die konsultierte Rolle hat sich gemeldet —, verlangt `--thread` wieder\neine Id, und die Ablehnung nennt jede offene Unterhaltung mit dem Befehl, der sie beantwortet. Die\nForm mit Id ist immer erlaubt.\n\n**Und der Koordinator nennt die Adressaten in dem Moment, in dem er weckt.** Jede Weckmeldung — eine\nfrische Beauftragung, eine zurückkommende Antwort, eine gesammelte Zustellung — endet jetzt damit,\nwelche Unterhaltungen offen sind, wen jede erreicht und welches Verb jede nimmt, statt mit einem\nfesten `Reply via nxc reply --thread `, das immer nur einen Faden nannte.", - "facade": "breaking" - } - ], - "notes": { - "en": "### Added\n- **A conversation has a name, not just a hash.** `nxc send` now gives the thread it opens a short\ndisplay name — at most seven words, derived from the message you sent — so a surface can show what\na conversation is about instead of `dm:d9ab4f601a08…`. A direct conversation's id is a hash over the\ntwo handles and by construction has no name; this is the name.\n\nThe name rides on every thread read (`nxc threads list`/`show`, `nxc status`, and the app seam's\nquorum) and is written once, when the thread is opened: no later message renames it. It is a display\nname and nothing keys on it — the id is still the id.\n\nDeriving it never holds up or fails a `send`: it happens out of band, in a run the coordinator\ncommissions, and a thread nobody could name stays a perfectly ordinary thread that every surface\nrenders as before. `nxc threads name []` states or derives one by hand, and reads\nthe thread it names under the same membership rule `nxc threads show` does.\n\nAn embedding app names threads itself with `Engine::name_thread`, or asks the engine to by setting\n`EngineConfig::namer`; the default names nothing, so no app starts paying for model calls because it\nopened a workspace.\n- **A step of a channel's flow can now say what its target is to do there, and at what experience.**\nTwo new optional keys on a `steps:` entry:\n\n```yaml\n - id: check\n target: review # the same three reviewers the coding chain uses\n stage: senior # …but a design judgement is dearer thinking than a diff\n task: |\n You are judging a SPECIFICATION, not a diff. Ask what would happen if what is written\n here were built.\n```\n\n`task:` becomes a fourth layer of the target's prompt, under the persona's own `system_prompt` and\nmarked as the step's, so a model can tell the two apart. It **adds**: a step can neither replace a\npersona nor switch a part of it off, and the engine says so in a sentence of its own above the\ntask — where the two seem to disagree, the role's rubric, its answering rules and its threshold\nstand. `stage:` runs the target at another band than the one it declared — `junior | senior |\nprincipal`, the same vocabulary a persona uses. A step may **not** name a model: the key is refused\nby name when the team is loaded and points at `stage:` instead, and a persona that declared its own\n`model:` keeps it whatever a step asks for.\n\nThis is what lets one declared set of reviewers judge a diff at one station and a specification at\nanother. Until now the job was welded into the role — the shipped reviewer personas say \"read the\ndiff\" in as many words — so a second kind of review meant a second set of personas to keep in step\nwith the first.\n\nBoth keys are optional and a step that declares neither composes exactly the prompt it composed\nbefore, byte for byte. A step whose target is a whole **channel** passes both to every member: a\nchannel thread runs no session, its members do. It reaches the step's target and no further —\nsomebody the step's own session goes on to commission is doing that session's errand, not the\nstep's.\n\n**For embedding consumers:** `nexus_chat::role::compose_system_prompt` takes a fifth argument,\n`Option` — pass `None` for the previous behaviour. `RoleDecl::effective_model` is\nunchanged and now delegates to the new `RoleDecl::effective_model_at`.\n\n### Changed\n- **`nxc reply` no longer needs a thread id while only one conversation is open for you.** A role is\nsupposed to know what it has to do and whom it may answer — not who commissioned it — so the id it\nwas handed minutes ago is no longer part of answering. `nxc reply \"\"` answers the one\nconversation waiting for you.\n\nAmbiguity does not come back through the back door: with more than one open — you owe your\ncommissioner an answer AND the role you consulted has come back to you — `--thread` is required\nagain, and the refusal names every open conversation with the command it takes. The id-bearing form\nis always allowed.\n\n**And the coordinator names the addressees at the moment it wakes you.** Every wake — a fresh\ncommission, an answer coming back, a batched delivery — now ends with which conversations are open,\nwhom each one reaches and which verb each one takes, instead of a single fixed\n`Reply via nxc reply --thread ` that named one thread whatever the situation was.\n\n### Fixed\n- **A flow step whose target is a whole channel no longer counts as answered before that channel has\ndelivered.** Such a step is opened by the channel supervisor, which is also the party expected to\nanswer it — and the step's opening message was therefore counted as its own answer, so the step read\nas finished from the instant it was created. Three consequences, all measured in one live run: the\nstep *after* it started beside the review round instead of after it and worked without the verdict;\n`nxc status` showed the review branch as `answered` while its members were still working; and a\nmember that had already answered showed as `orphaned`, because that state asks whether the parent\nwas asked and answered.\n\nAll three came from one cause and are fixed at it: a thread's opening message is now written before\nthe obligation it declares, so the question is never counted as the answer to itself. Nothing\nchanges for any other thread — the count only ever looked at messages from an expected party, and\nelsewhere the opener is not one.\n\n**What this does not do**, stated because the case it removes is the one people will ask about: a\nstep that has already started still cannot be reached — an escalation or a send-back arrives after\nit is running and stops nothing, because the engine has no way to interrupt a step at all. This\nchange removes the bug that used to start a step too early; it does not add that missing ability,\nwhich is tracked on its own.\n- **A reply into a round that is over is refused instead of accepted in silence.** `nxc reply\n--thread ` used to take a message into a step of an operation that had already delivered, post\nit, and answer with a receipt that named a due date — while nothing had been started and nothing\ncould be: the round's answers had been consolidated and handed on long before. Measured in a live\nrun: twenty minutes of waiting for work nobody had been asked to do. Such a reply now comes back\nwith a refusal that says the round is over, names the round to read, and names the command that\ndoes start the next piece of work.\n\n**And no receipt promises an answer nobody owes.** The window a channel declares is reported only\nwhile somebody actually still owes a reply on that thread. A round that has been answered no longer\nhands its caller a date to wait for.\n\nThe sentence every persona is handed at session start says the same thing now: replying into your\nown thread resumes the conversation *while the round is open*, and once it has been answered and\nhanded on you start the next thing with `nxc send`.\n\n### Facade Contract\n- `changed` · **A flow step whose target is a whole channel no longer counts as answered before that channel has\ndelivered.** Such a step is opened by the channel supervisor, which is also the party expected to\nanswer it — and the step's opening message was therefore counted as its own answer, so the step read\nas finished from the instant it was created. Three consequences, all measured in one live run: the\nstep *after* it started beside the review round instead of after it and worked without the verdict;\n`nxc status` showed the review branch as `answered` while its members were still working; and a\nmember that had already answered showed as `orphaned`, because that state asks whether the parent\nwas asked and answered.\n\nAll three came from one cause and are fixed at it: a thread's opening message is now written before\nthe obligation it declares, so the question is never counted as the answer to itself. Nothing\nchanges for any other thread — the count only ever looked at messages from an expected party, and\nelsewhere the opener is not one.\n\n**What this does not do**, stated because the case it removes is the one people will ask about: a\nstep that has already started still cannot be reached — an escalation or a send-back arrives after\nit is running and stops nothing, because the engine has no way to interrupt a step at all. This\nchange removes the bug that used to start a step too early; it does not add that missing ability,\nwhich is tracked on its own.\n- `breaking` · **A conversation has a name, not just a hash.** `nxc send` now gives the thread it opens a short\ndisplay name — at most seven words, derived from the message you sent — so a surface can show what\na conversation is about instead of `dm:d9ab4f601a08…`. A direct conversation's id is a hash over the\ntwo handles and by construction has no name; this is the name.\n\nThe name rides on every thread read (`nxc threads list`/`show`, `nxc status`, and the app seam's\nquorum) and is written once, when the thread is opened: no later message renames it. It is a display\nname and nothing keys on it — the id is still the id.\n\nDeriving it never holds up or fails a `send`: it happens out of band, in a run the coordinator\ncommissions, and a thread nobody could name stays a perfectly ordinary thread that every surface\nrenders as before. `nxc threads name []` states or derives one by hand, and reads\nthe thread it names under the same membership rule `nxc threads show` does.\n\nAn embedding app names threads itself with `Engine::name_thread`, or asks the engine to by setting\n`EngineConfig::namer`; the default names nothing, so no app starts paying for model calls because it\nopened a workspace.\n- `breaking` · **A reply into a round that is over is refused instead of accepted in silence.** `nxc reply\n--thread ` used to take a message into a step of an operation that had already delivered, post\nit, and answer with a receipt that named a due date — while nothing had been started and nothing\ncould be: the round's answers had been consolidated and handed on long before. Measured in a live\nrun: twenty minutes of waiting for work nobody had been asked to do. Such a reply now comes back\nwith a refusal that says the round is over, names the round to read, and names the command that\ndoes start the next piece of work.\n\n**And no receipt promises an answer nobody owes.** The window a channel declares is reported only\nwhile somebody actually still owes a reply on that thread. A round that has been answered no longer\nhands its caller a date to wait for.\n\nThe sentence every persona is handed at session start says the same thing now: replying into your\nown thread resumes the conversation *while the round is open*, and once it has been answered and\nhanded on you start the next thing with `nxc send`.\n- `breaking` · **A step of a channel's flow can now say what its target is to do there, and at what experience.**\nTwo new optional keys on a `steps:` entry:\n\n```yaml\n - id: check\n target: review # the same three reviewers the coding chain uses\n stage: senior # …but a design judgement is dearer thinking than a diff\n task: |\n You are judging a SPECIFICATION, not a diff. Ask what would happen if what is written\n here were built.\n```\n\n`task:` becomes a fourth layer of the target's prompt, under the persona's own `system_prompt` and\nmarked as the step's, so a model can tell the two apart. It **adds**: a step can neither replace a\npersona nor switch a part of it off, and the engine says so in a sentence of its own above the\ntask — where the two seem to disagree, the role's rubric, its answering rules and its threshold\nstand. `stage:` runs the target at another band than the one it declared — `junior | senior |\nprincipal`, the same vocabulary a persona uses. A step may **not** name a model: the key is refused\nby name when the team is loaded and points at `stage:` instead, and a persona that declared its own\n`model:` keeps it whatever a step asks for.\n\nThis is what lets one declared set of reviewers judge a diff at one station and a specification at\nanother. Until now the job was welded into the role — the shipped reviewer personas say \"read the\ndiff\" in as many words — so a second kind of review meant a second set of personas to keep in step\nwith the first.\n\nBoth keys are optional and a step that declares neither composes exactly the prompt it composed\nbefore, byte for byte. A step whose target is a whole **channel** passes both to every member: a\nchannel thread runs no session, its members do. It reaches the step's target and no further —\nsomebody the step's own session goes on to commission is doing that session's errand, not the\nstep's.\n\n**For embedding consumers:** `nexus_chat::role::compose_system_prompt` takes a fifth argument,\n`Option` — pass `None` for the previous behaviour. `RoleDecl::effective_model` is\nunchanged and now delegates to the new `RoleDecl::effective_model_at`.\n- `breaking` · **`nxc reply` no longer needs a thread id while only one conversation is open for you.** A role is\nsupposed to know what it has to do and whom it may answer — not who commissioned it — so the id it\nwas handed minutes ago is no longer part of answering. `nxc reply \"\"` answers the one\nconversation waiting for you.\n\nAmbiguity does not come back through the back door: with more than one open — you owe your\ncommissioner an answer AND the role you consulted has come back to you — `--thread` is required\nagain, and the refusal names every open conversation with the command it takes. The id-bearing form\nis always allowed.\n\n**And the coordinator names the addressees at the moment it wakes you.** Every wake — a fresh\ncommission, an answer coming back, a batched delivery — now ends with which conversations are open,\nwhom each one reaches and which verb each one takes, instead of a single fixed\n`Reply via nxc reply --thread ` that named one thread whatever the situation was.", - "de": "### Neu\n- **Eine Unterhaltung hat einen Namen, nicht nur einen Hash.** `nxc send` gibt dem Faden, den es\nöffnet, jetzt einen kurzen Anzeigenamen — höchstens sieben Wörter, abgeleitet aus der gesendeten\nNachricht —, damit eine Oberfläche zeigen kann, worum es geht, statt `dm:d9ab4f601a08…`. Die Id\neiner Direktunterhaltung ist ein Hash über die beiden Handles und hat bauartbedingt keinen Namen;\ndas hier ist der Name.\n\nDer Name reist auf jeder Faden-Lesenaht mit (`nxc threads list`/`show`, `nxc status` und das Quorum\nder App-Naht) und wird einmal geschrieben, beim Öffnen des Fadens: keine spätere Nachricht benennt\nihn um. Er ist ein Anzeigename, und nichts keyt darauf — die Id bleibt die Id.\n\nDie Ableitung hält ein `send` nie auf und lässt es nie scheitern: sie läuft außerhalb, in einem Lauf,\nden der Koordinator beauftragt, und ein Faden ohne Namen bleibt ein ganz gewöhnlicher Faden, den jede\nOberfläche wie bisher darstellt. `nxc threads name []` setzt oder leitet einen von\nHand ab und liest den Faden, den es benennt, unter derselben Mitgliedschaftsregel wie\n`nxc threads show`.\n\nEine einbettende App benennt Fäden selbst über `Engine::name_thread` oder lässt die Engine es tun\n(`EngineConfig::namer`); die Voreinstellung benennt nichts, damit keine App Modellaufrufe bezahlt,\nnur weil sie einen Arbeitsbereich geöffnet hat.\n- **Ein Schritt eines Kanalablaufs kann jetzt sagen, was sein Ziel dort tun soll und mit welcher\nErfahrung.** Zwei neue, freiwillige Schlüssel an einem `steps:`-Eintrag:\n\n```yaml\n - id: check\n target: review # dieselben drei Prüfer wie in der Codekette\n stage: senior # … aber ein Entwurfsurteil ist teurere Denkarbeit als ein Diff\n task: |\n Du beurteilst eine SPEZIFIKATION, keinen Diff. Frage, was passieren WÜRDE, wenn das hier\n Geschriebene so gebaut wird.\n```\n\n`task:` wird zu einer vierten Ebene im Prompt des Ziels, unter dem eigenen `system_prompt` der\nPersona und als die des Schritts gekennzeichnet, damit ein Modell beides unterscheiden kann. Sie\n**ergänzt**: Ein Schritt kann eine Persona weder ersetzen noch Teile von ihr abschalten, und die\nEngine sagt das in einem eigenen Satz über der Aufgabe — wo beide sich zu widersprechen scheinen,\ngelten die Rubrik der Rolle, ihre Antwortregeln und ihre Schwelle. `stage:` lässt das Ziel auf einer\nanderen Stufe laufen als der selbst deklarierten — `junior | senior | principal`, dasselbe\nVokabular wie bei einer Persona. Ein Schritt darf **kein** Modell benennen: Der Schlüssel wird beim\nLaden des Teams namentlich abgelehnt und verweist auf `stage:`, und eine Persona mit eigenem\n`model:` behält es, was ein Schritt auch verlangt.\n\nDamit kann ein einziger deklarierter Satz Prüfer an der einen Station einen Diff und an der anderen\neine Spezifikation beurteilen. Bisher steckte die Aufgabe in der Rolle — die ausgelieferten\nPrüfer-Personas sagen wörtlich „lies den Diff\" —, sodass eine zweite Art von Prüfung einen zweiten\nSatz Personas mit doppelter Pflege bedeutete.\n\nBeide Schlüssel sind freiwillig, und ein Schritt, der keinen von beiden deklariert, setzt seinen\nPrompt genau so zusammen wie vorher, Byte für Byte. Ein Schritt, dessen Ziel ein ganzer **Kanal**\nist, reicht beides an jedes Mitglied weiter: Ein Kanalfaden führt keine Sitzung, seine Mitglieder\ntun es. Weiter reicht es nicht — wen die Sitzung des Schritts danach selbst beauftragt, erledigt\neine Besorgung dieser Sitzung und nicht den Schritt.\n\n**Für einbettende Konsumenten:** `nexus_chat::role::compose_system_prompt` nimmt ein fünftes\nArgument, `Option` — `None` ergibt das bisherige Verhalten. `RoleDecl::effective_model`\nist unverändert und ruft jetzt das neue `RoleDecl::effective_model_at` auf.\n\n### Geändert\n- **`nxc reply` braucht keine Faden-Id mehr, solange genau eine Unterhaltung für Sie offen ist.** Eine\nRolle soll wissen, was sie zu tun hat und wem sie antworten darf — nicht, wer sie beauftragt hat —,\nalso gehört die vor Minuten übergebene Id nicht mehr zum Antworten. `nxc reply \"\"`\nbeantwortet die eine Unterhaltung, die auf Sie wartet.\n\nMehrdeutigkeit kommt nicht durch die Hintertür zurück: sind mehrere offen — Sie schulden Ihrem\nAuftraggeber eine Antwort UND die konsultierte Rolle hat sich gemeldet —, verlangt `--thread` wieder\neine Id, und die Ablehnung nennt jede offene Unterhaltung mit dem Befehl, der sie beantwortet. Die\nForm mit Id ist immer erlaubt.\n\n**Und der Koordinator nennt die Adressaten in dem Moment, in dem er weckt.** Jede Weckmeldung — eine\nfrische Beauftragung, eine zurückkommende Antwort, eine gesammelte Zustellung — endet jetzt damit,\nwelche Unterhaltungen offen sind, wen jede erreicht und welches Verb jede nimmt, statt mit einem\nfesten `Reply via nxc reply --thread `, das immer nur einen Faden nannte.\n\n### Behoben\n- **Ein Ablaufschritt, dessen Ziel ein ganzer Kanal ist, gilt nicht mehr als beantwortet, bevor dieser\nKanal geliefert hat.** So einen Schritt eröffnet der Kanal-Supervisor, der zugleich die Instanz ist,\nvon der die Antwort erwartet wird — die Eröffnungsnachricht des Schritts zählte damit als seine\neigene Antwort, und der Schritt las sich vom ersten Moment an als fertig. Drei Folgen, alle in einem\nechten Lauf gemessen: der *nachfolgende* Schritt lief neben der Reviewrunde statt nach ihr und\narbeitete ohne deren Spruch; `nxc status` zeigte den Review-Zweig als `answered`, während seine\nMitglieder noch arbeiteten; und ein Mitglied, das schon geantwortet hatte, erschien als `orphaned` —\ndenn dieser Zustand fragt danach, ob der Elternfaden gefragt und beantwortet wurde.\n\nAlle drei hatten eine Ursache und sind dort behoben: die Eröffnungsnachricht eines Fadens wird jetzt\nvor der Verpflichtung geschrieben, die sie erklärt — die Frage zählt nie mehr als ihre eigene\nAntwort. Für jeden anderen Faden ändert sich nichts: gezählt wurden immer nur Nachrichten einer\nerwarteten Instanz, und sonst ist der Eröffner keine.\n\n**Was das nicht tut** — gesagt, weil genau danach gefragt werden wird: Ein Schritt, der bereits\nläuft, ist weiterhin nicht erreichbar. Eine Eskalation oder eine Rückkante, die eintrifft, während\ner läuft, hält ihn nicht auf, denn die Engine kann einen laufenden Schritt überhaupt nicht\nunterbrechen. Diese Änderung nimmt den Fehler weg, der einen Schritt zu früh startete; die fehlende\nFähigkeit selbst kommt separat und wird eigens verfolgt.\n- **Eine Antwort in eine beendete Runde wird abgelehnt statt stillschweigend angenommen.** `nxc reply\n--thread ` hat eine Nachricht bisher auch in einen Schritt eines längst ausgelieferten Vorgangs\naufgenommen, sie abgelegt und eine Quittung mit einer Frist zurückgegeben — obwohl nichts gestartet\nwurde und nichts gestartet werden konnte: die Antworten dieser Runde waren lange vorher\nzusammengeführt und weitergereicht. In einem echten Lauf gemessen: zwanzig Minuten Warten auf eine\nArbeit, um die niemand gebeten worden war. Eine solche Antwort wird jetzt abgelehnt, mit einem\nGrund, der sagt, dass die Runde beendet ist, welche Runde nachzulesen ist und welcher Befehl die\nnächste Arbeit tatsächlich startet.\n\n**Und keine Quittung verspricht eine Antwort, die niemand schuldet.** Das von einem Kanal\ndeklarierte Zeitfenster wird nur noch genannt, solange auf diesem Faden wirklich noch jemand eine\nAntwort schuldet. Eine beantwortete Runde gibt ihrem Aufrufer keine Frist mehr mit.\n\nDer Satz, den jede Persona beim Sitzungsstart bekommt, sagt jetzt dasselbe: eine Antwort auf den\neigenen Faden nimmt die Unterhaltung wieder auf, *solange die Runde offen ist* — ist sie beantwortet\nund weitergereicht, beginnt das Nächste mit `nxc send`.\n\n### Facade-Kontrakt\n- `changed` · **Ein Ablaufschritt, dessen Ziel ein ganzer Kanal ist, gilt nicht mehr als beantwortet, bevor dieser\nKanal geliefert hat.** So einen Schritt eröffnet der Kanal-Supervisor, der zugleich die Instanz ist,\nvon der die Antwort erwartet wird — die Eröffnungsnachricht des Schritts zählte damit als seine\neigene Antwort, und der Schritt las sich vom ersten Moment an als fertig. Drei Folgen, alle in einem\nechten Lauf gemessen: der *nachfolgende* Schritt lief neben der Reviewrunde statt nach ihr und\narbeitete ohne deren Spruch; `nxc status` zeigte den Review-Zweig als `answered`, während seine\nMitglieder noch arbeiteten; und ein Mitglied, das schon geantwortet hatte, erschien als `orphaned` —\ndenn dieser Zustand fragt danach, ob der Elternfaden gefragt und beantwortet wurde.\n\nAlle drei hatten eine Ursache und sind dort behoben: die Eröffnungsnachricht eines Fadens wird jetzt\nvor der Verpflichtung geschrieben, die sie erklärt — die Frage zählt nie mehr als ihre eigene\nAntwort. Für jeden anderen Faden ändert sich nichts: gezählt wurden immer nur Nachrichten einer\nerwarteten Instanz, und sonst ist der Eröffner keine.\n\n**Was das nicht tut** — gesagt, weil genau danach gefragt werden wird: Ein Schritt, der bereits\nläuft, ist weiterhin nicht erreichbar. Eine Eskalation oder eine Rückkante, die eintrifft, während\ner läuft, hält ihn nicht auf, denn die Engine kann einen laufenden Schritt überhaupt nicht\nunterbrechen. Diese Änderung nimmt den Fehler weg, der einen Schritt zu früh startete; die fehlende\nFähigkeit selbst kommt separat und wird eigens verfolgt.\n- `breaking` · **Eine Unterhaltung hat einen Namen, nicht nur einen Hash.** `nxc send` gibt dem Faden, den es\nöffnet, jetzt einen kurzen Anzeigenamen — höchstens sieben Wörter, abgeleitet aus der gesendeten\nNachricht —, damit eine Oberfläche zeigen kann, worum es geht, statt `dm:d9ab4f601a08…`. Die Id\neiner Direktunterhaltung ist ein Hash über die beiden Handles und hat bauartbedingt keinen Namen;\ndas hier ist der Name.\n\nDer Name reist auf jeder Faden-Lesenaht mit (`nxc threads list`/`show`, `nxc status` und das Quorum\nder App-Naht) und wird einmal geschrieben, beim Öffnen des Fadens: keine spätere Nachricht benennt\nihn um. Er ist ein Anzeigename, und nichts keyt darauf — die Id bleibt die Id.\n\nDie Ableitung hält ein `send` nie auf und lässt es nie scheitern: sie läuft außerhalb, in einem Lauf,\nden der Koordinator beauftragt, und ein Faden ohne Namen bleibt ein ganz gewöhnlicher Faden, den jede\nOberfläche wie bisher darstellt. `nxc threads name []` setzt oder leitet einen von\nHand ab und liest den Faden, den es benennt, unter derselben Mitgliedschaftsregel wie\n`nxc threads show`.\n\nEine einbettende App benennt Fäden selbst über `Engine::name_thread` oder lässt die Engine es tun\n(`EngineConfig::namer`); die Voreinstellung benennt nichts, damit keine App Modellaufrufe bezahlt,\nnur weil sie einen Arbeitsbereich geöffnet hat.\n- `breaking` · **Eine Antwort in eine beendete Runde wird abgelehnt statt stillschweigend angenommen.** `nxc reply\n--thread ` hat eine Nachricht bisher auch in einen Schritt eines längst ausgelieferten Vorgangs\naufgenommen, sie abgelegt und eine Quittung mit einer Frist zurückgegeben — obwohl nichts gestartet\nwurde und nichts gestartet werden konnte: die Antworten dieser Runde waren lange vorher\nzusammengeführt und weitergereicht. In einem echten Lauf gemessen: zwanzig Minuten Warten auf eine\nArbeit, um die niemand gebeten worden war. Eine solche Antwort wird jetzt abgelehnt, mit einem\nGrund, der sagt, dass die Runde beendet ist, welche Runde nachzulesen ist und welcher Befehl die\nnächste Arbeit tatsächlich startet.\n\n**Und keine Quittung verspricht eine Antwort, die niemand schuldet.** Das von einem Kanal\ndeklarierte Zeitfenster wird nur noch genannt, solange auf diesem Faden wirklich noch jemand eine\nAntwort schuldet. Eine beantwortete Runde gibt ihrem Aufrufer keine Frist mehr mit.\n\nDer Satz, den jede Persona beim Sitzungsstart bekommt, sagt jetzt dasselbe: eine Antwort auf den\neigenen Faden nimmt die Unterhaltung wieder auf, *solange die Runde offen ist* — ist sie beantwortet\nund weitergereicht, beginnt das Nächste mit `nxc send`.\n- `breaking` · **Ein Schritt eines Kanalablaufs kann jetzt sagen, was sein Ziel dort tun soll und mit welcher\nErfahrung.** Zwei neue, freiwillige Schlüssel an einem `steps:`-Eintrag:\n\n```yaml\n - id: check\n target: review # dieselben drei Prüfer wie in der Codekette\n stage: senior # … aber ein Entwurfsurteil ist teurere Denkarbeit als ein Diff\n task: |\n Du beurteilst eine SPEZIFIKATION, keinen Diff. Frage, was passieren WÜRDE, wenn das hier\n Geschriebene so gebaut wird.\n```\n\n`task:` wird zu einer vierten Ebene im Prompt des Ziels, unter dem eigenen `system_prompt` der\nPersona und als die des Schritts gekennzeichnet, damit ein Modell beides unterscheiden kann. Sie\n**ergänzt**: Ein Schritt kann eine Persona weder ersetzen noch Teile von ihr abschalten, und die\nEngine sagt das in einem eigenen Satz über der Aufgabe — wo beide sich zu widersprechen scheinen,\ngelten die Rubrik der Rolle, ihre Antwortregeln und ihre Schwelle. `stage:` lässt das Ziel auf einer\nanderen Stufe laufen als der selbst deklarierten — `junior | senior | principal`, dasselbe\nVokabular wie bei einer Persona. Ein Schritt darf **kein** Modell benennen: Der Schlüssel wird beim\nLaden des Teams namentlich abgelehnt und verweist auf `stage:`, und eine Persona mit eigenem\n`model:` behält es, was ein Schritt auch verlangt.\n\nDamit kann ein einziger deklarierter Satz Prüfer an der einen Station einen Diff und an der anderen\neine Spezifikation beurteilen. Bisher steckte die Aufgabe in der Rolle — die ausgelieferten\nPrüfer-Personas sagen wörtlich „lies den Diff\" —, sodass eine zweite Art von Prüfung einen zweiten\nSatz Personas mit doppelter Pflege bedeutete.\n\nBeide Schlüssel sind freiwillig, und ein Schritt, der keinen von beiden deklariert, setzt seinen\nPrompt genau so zusammen wie vorher, Byte für Byte. Ein Schritt, dessen Ziel ein ganzer **Kanal**\nist, reicht beides an jedes Mitglied weiter: Ein Kanalfaden führt keine Sitzung, seine Mitglieder\ntun es. Weiter reicht es nicht — wen die Sitzung des Schritts danach selbst beauftragt, erledigt\neine Besorgung dieser Sitzung und nicht den Schritt.\n\n**Für einbettende Konsumenten:** `nexus_chat::role::compose_system_prompt` nimmt ein fünftes\nArgument, `Option` — `None` ergibt das bisherige Verhalten. `RoleDecl::effective_model`\nist unverändert und ruft jetzt das neue `RoleDecl::effective_model_at` auf.\n- `breaking` · **`nxc reply` braucht keine Faden-Id mehr, solange genau eine Unterhaltung für Sie offen ist.** Eine\nRolle soll wissen, was sie zu tun hat und wem sie antworten darf — nicht, wer sie beauftragt hat —,\nalso gehört die vor Minuten übergebene Id nicht mehr zum Antworten. `nxc reply \"\"`\nbeantwortet die eine Unterhaltung, die auf Sie wartet.\n\nMehrdeutigkeit kommt nicht durch die Hintertür zurück: sind mehrere offen — Sie schulden Ihrem\nAuftraggeber eine Antwort UND die konsultierte Rolle hat sich gemeldet —, verlangt `--thread` wieder\neine Id, und die Ablehnung nennt jede offene Unterhaltung mit dem Befehl, der sie beantwortet. Die\nForm mit Id ist immer erlaubt.\n\n**Und der Koordinator nennt die Adressaten in dem Moment, in dem er weckt.** Jede Weckmeldung — eine\nfrische Beauftragung, eine zurückkommende Antwort, eine gesammelte Zustellung — endet jetzt damit,\nwelche Unterhaltungen offen sind, wen jede erreicht und welches Verb jede nimmt, statt mit einem\nfesten `Reply via nxc reply --thread `, das immer nur einen Faden nannte." - } - }, - { - "version": "0.88.0", - "date": "2026-09-08", - "items": [ - { - "type": "removed", - "en": "**The unread apparatus is gone: the read cursor, the ack that moved it, and every count derived from\nit.** Nothing measured it any more. `nxc inbox` and `nxc read` left the command line a fortnight ago\n(0 uses across 66 measured agent sessions each), and what stayed behind them was a synced read\ncursor written by an acknowledgement no application ever called — five layers of pipe carrying no\nwater. The one thing that still consumed the cursor, the session-start notice of finished work, was\nreplaced rather than deleted: it is derived from the operation now (v0.87.0), so removing the cursor\ntakes nothing with it.\n**What is gone, by name:** the `read_cursors` table and its `read_cursor` operation kind;\n`Engine::mark_read` and `facade::mark_read` with their `ReadReceipt`; `facade::inbox` and the store\nread under it; `PrimeReport`'s `in_turn`, `next_session` and `count`, and so those three fields of\n`nxc prime --json`; `ChannelView`'s `unread` and `unread_next_session`, and so those two fields of\n`Engine::channels` and `Engine::public_channels`; and the `facade::render_unread_blocks` renderer.\n**Nothing is invented in their place, because all three delivery paths already PUSH.** `nxc send\n--to` starts a session with the message in its prompt, `nxc reply --thread` resumes the session on\nthe other side with the reply's body, and a completed quorum wakes whoever opened the board. An\nunread list was a second, pull-shaped copy of what had already arrived. A person reads the\nconversation — `nxc threads show ` for one board in full, `nxc status` for where an\noperation stands — and both are untouched.\n**Upgrading is automatic and nothing is lost.** A workspace sheds the retired table on its next\nopen; every message stays exactly where it was, because messages were never in it. A `read_cursor`\noperation already in a synced log stays in the log and is simply no longer folded, which is how this\nengine has always treated an operation kind it does not know — so an older and a newer peer still\nconverge.\n**For an embedding application this is a compile break, and it is meant to be one.** If you call\n`markRead` or read `unread` / `in_turn` / `next_session` / `count`, those calls stop compiling\nrather than quietly returning zero. There is no replacement for the counts: since the surface became\nthread-shaped, an unread badge on a channel marks the one thing an application can no longer\naddress. What answers \"where does this stand\" is `Engine::status`, per operation, on demand.", - "de": "**Der Ungelesen-Apparat ist entfallen: der Lesezeiger, die Bestätigung, die ihn bewegte, und jede\ndaraus abgeleitete Zählung.** Nichts hat ihn mehr ausgewertet. `nxc inbox` und `nxc read` haben die\nKommandozeile vor zwei Wochen verlassen (je 0 Verwendungen in 66 gemessenen Agentensitzungen), und\nwas dahinter stehen blieb, war ein synchronisierter Lesezeiger, den eine Bestätigung bewegte, die\nkeine Anwendung je aufrief — fünf Schichten Rohr ohne Wasser. Das Einzige, was den Zeiger noch\nverbrauchte, die Sitzungsstart-Meldung über fertige Arbeit, wurde ERSETZT statt gelöscht: sie wird\njetzt aus dem Vorgang abgeleitet (v0.87.0), also nimmt das Entfernen des Zeigers nichts mit.\n**Was entfällt, beim Namen:** die Tabelle `read_cursors` und ihre Operationsart `read_cursor`;\n`Engine::mark_read` und `facade::mark_read` samt `ReadReceipt`; `facade::inbox` und die Lesung im\nStore darunter; `in_turn`, `next_session` und `count` auf `PrimeReport` und damit diese drei Felder\nvon `nxc prime --json`; `unread` und `unread_next_session` auf `ChannelView` und damit diese beiden\nFelder von `Engine::channels` und `Engine::public_channels`; und der Zeichner\n`facade::render_unread_blocks`.\n**An ihre Stelle tritt nichts, weil alle drei Zustellwege ohnehin SCHIEBEN.** `nxc send --to`\nstartet eine Sitzung mit der Nachricht im Prompt, `nxc reply --thread` nimmt die Sitzung auf der\nanderen Seite mit dem Rumpf der Antwort wieder auf, und ein vollständiges Quorum weckt den, der die\nTafel geöffnet hat. Eine Ungelesen-Liste war die zweite, ziehende Kopie dessen, was schon angekommen\nwar. Ein Mensch liest das Gespräch — `nxc threads show ` für eine Tafel in voller Länge, `nxc\nstatus` für den Stand eines Vorgangs — und beides bleibt unangetastet.\n**Das Hochziehen läuft automatisch, und es geht nichts verloren.** Ein Arbeitsbereich legt die\nstillgelegte Tabelle beim nächsten Öffnen ab; jede Nachricht bleibt genau dort, wo sie war, denn\nNachrichten standen nie darin. Eine `read_cursor`-Operation, die bereits in einem synchronisierten\nLog liegt, bleibt im Log und wird nur nicht mehr eingefaltet — so behandelt diese Engine seit jeher\neine Operationsart, die sie nicht kennt, und ein älterer und ein neuerer Knoten konvergieren\nweiterhin.\n**Für eine einbettende Anwendung ist das ein Übersetzungsfehler, und das ist Absicht.** Wer\n`markRead` aufruft oder `unread` / `in_turn` / `next_session` / `count` liest, dessen Code\nkompiliert nicht mehr, statt still eine Null zu liefern. Für die Zähler gibt es keinen Ersatz: seit\ndie Oberfläche fadenförmig ist, markiert ein Ungelesen-Abzeichen am Kanal genau das Eine, was eine\nAnwendung nicht mehr adressieren kann. Die Frage „wo steht das\" beantwortet `Engine::status`, je\nVorgang, auf Abruf.", - "migration": { - "en": "Automatic: a workspace drops the retired `read_cursors` table on its next open, and no message,\nthread or operation is touched. Cursor operations already in a synced log stay there and stop being\nfolded — old and new peers still converge.\nManual, only for an application embedding `nexus-chat`: remove your calls to `Engine::mark_read`\n(and any `markRead` wrapper over it), and stop reading `unread` / `unread_next_session` off\n`Engine::channels` / `Engine::public_channels` and `in_turn` / `next_session` / `count` off\n`Engine::prime_as` and `nxc prime --json`. There is no drop-in replacement for an unread COUNT; ask\n`Engine::status` where an operation stands, or `Engine::thread` for a board in full.", - "de": "Automatisch: ein Arbeitsbereich legt die stillgelegte Tabelle `read_cursors` beim nächsten Öffnen\nab; keine Nachricht, kein Faden und kein Vorgang wird angefasst. Cursor-Operationen, die schon in\neinem synchronisierten Log liegen, bleiben dort und werden nur nicht mehr eingefaltet — alte und\nneue Knoten konvergieren weiterhin.\nVon Hand, nur für eine Anwendung, die `nexus-chat` einbettet: entfernen Sie Ihre Aufrufe von\n`Engine::mark_read` (und jede `markRead`-Hülle darüber) und lesen Sie `unread` /\n`unread_next_session` nicht mehr von `Engine::channels` / `Engine::public_channels` und `in_turn` /\n`next_session` / `count` nicht mehr von `Engine::prime_as` und `nxc prime --json`. Für einen\nUngelesen-ZÄHLER gibt es keinen direkten Ersatz; fragen Sie `Engine::status`, wo ein Vorgang steht,\noder `Engine::thread` für eine Tafel in voller Länge." - }, - "facade": "breaking" - } - ], - "notes": { - "en": "### Removed\n- **The unread apparatus is gone: the read cursor, the ack that moved it, and every count derived from\nit.** Nothing measured it any more. `nxc inbox` and `nxc read` left the command line a fortnight ago\n(0 uses across 66 measured agent sessions each), and what stayed behind them was a synced read\ncursor written by an acknowledgement no application ever called — five layers of pipe carrying no\nwater. The one thing that still consumed the cursor, the session-start notice of finished work, was\nreplaced rather than deleted: it is derived from the operation now (v0.87.0), so removing the cursor\ntakes nothing with it.\n**What is gone, by name:** the `read_cursors` table and its `read_cursor` operation kind;\n`Engine::mark_read` and `facade::mark_read` with their `ReadReceipt`; `facade::inbox` and the store\nread under it; `PrimeReport`'s `in_turn`, `next_session` and `count`, and so those three fields of\n`nxc prime --json`; `ChannelView`'s `unread` and `unread_next_session`, and so those two fields of\n`Engine::channels` and `Engine::public_channels`; and the `facade::render_unread_blocks` renderer.\n**Nothing is invented in their place, because all three delivery paths already PUSH.** `nxc send\n--to` starts a session with the message in its prompt, `nxc reply --thread` resumes the session on\nthe other side with the reply's body, and a completed quorum wakes whoever opened the board. An\nunread list was a second, pull-shaped copy of what had already arrived. A person reads the\nconversation — `nxc threads show ` for one board in full, `nxc status` for where an\noperation stands — and both are untouched.\n**Upgrading is automatic and nothing is lost.** A workspace sheds the retired table on its next\nopen; every message stays exactly where it was, because messages were never in it. A `read_cursor`\noperation already in a synced log stays in the log and is simply no longer folded, which is how this\nengine has always treated an operation kind it does not know — so an older and a newer peer still\nconverge.\n**For an embedding application this is a compile break, and it is meant to be one.** If you call\n`markRead` or read `unread` / `in_turn` / `next_session` / `count`, those calls stop compiling\nrather than quietly returning zero. There is no replacement for the counts: since the surface became\nthread-shaped, an unread badge on a channel marks the one thing an application can no longer\naddress. What answers \"where does this stand\" is `Engine::status`, per operation, on demand.\n\n### Facade Contract\n- `breaking` · **The unread apparatus is gone: the read cursor, the ack that moved it, and every count derived from\nit.** Nothing measured it any more. `nxc inbox` and `nxc read` left the command line a fortnight ago\n(0 uses across 66 measured agent sessions each), and what stayed behind them was a synced read\ncursor written by an acknowledgement no application ever called — five layers of pipe carrying no\nwater. The one thing that still consumed the cursor, the session-start notice of finished work, was\nreplaced rather than deleted: it is derived from the operation now (v0.87.0), so removing the cursor\ntakes nothing with it.\n**What is gone, by name:** the `read_cursors` table and its `read_cursor` operation kind;\n`Engine::mark_read` and `facade::mark_read` with their `ReadReceipt`; `facade::inbox` and the store\nread under it; `PrimeReport`'s `in_turn`, `next_session` and `count`, and so those three fields of\n`nxc prime --json`; `ChannelView`'s `unread` and `unread_next_session`, and so those two fields of\n`Engine::channels` and `Engine::public_channels`; and the `facade::render_unread_blocks` renderer.\n**Nothing is invented in their place, because all three delivery paths already PUSH.** `nxc send\n--to` starts a session with the message in its prompt, `nxc reply --thread` resumes the session on\nthe other side with the reply's body, and a completed quorum wakes whoever opened the board. An\nunread list was a second, pull-shaped copy of what had already arrived. A person reads the\nconversation — `nxc threads show ` for one board in full, `nxc status` for where an\noperation stands — and both are untouched.\n**Upgrading is automatic and nothing is lost.** A workspace sheds the retired table on its next\nopen; every message stays exactly where it was, because messages were never in it. A `read_cursor`\noperation already in a synced log stays in the log and is simply no longer folded, which is how this\nengine has always treated an operation kind it does not know — so an older and a newer peer still\nconverge.\n**For an embedding application this is a compile break, and it is meant to be one.** If you call\n`markRead` or read `unread` / `in_turn` / `next_session` / `count`, those calls stop compiling\nrather than quietly returning zero. There is no replacement for the counts: since the surface became\nthread-shaped, an unread badge on a channel marks the one thing an application can no longer\naddress. What answers \"where does this stand\" is `Engine::status`, per operation, on demand.", - "de": "### Entfernt\n- **Der Ungelesen-Apparat ist entfallen: der Lesezeiger, die Bestätigung, die ihn bewegte, und jede\ndaraus abgeleitete Zählung.** Nichts hat ihn mehr ausgewertet. `nxc inbox` und `nxc read` haben die\nKommandozeile vor zwei Wochen verlassen (je 0 Verwendungen in 66 gemessenen Agentensitzungen), und\nwas dahinter stehen blieb, war ein synchronisierter Lesezeiger, den eine Bestätigung bewegte, die\nkeine Anwendung je aufrief — fünf Schichten Rohr ohne Wasser. Das Einzige, was den Zeiger noch\nverbrauchte, die Sitzungsstart-Meldung über fertige Arbeit, wurde ERSETZT statt gelöscht: sie wird\njetzt aus dem Vorgang abgeleitet (v0.87.0), also nimmt das Entfernen des Zeigers nichts mit.\n**Was entfällt, beim Namen:** die Tabelle `read_cursors` und ihre Operationsart `read_cursor`;\n`Engine::mark_read` und `facade::mark_read` samt `ReadReceipt`; `facade::inbox` und die Lesung im\nStore darunter; `in_turn`, `next_session` und `count` auf `PrimeReport` und damit diese drei Felder\nvon `nxc prime --json`; `unread` und `unread_next_session` auf `ChannelView` und damit diese beiden\nFelder von `Engine::channels` und `Engine::public_channels`; und der Zeichner\n`facade::render_unread_blocks`.\n**An ihre Stelle tritt nichts, weil alle drei Zustellwege ohnehin SCHIEBEN.** `nxc send --to`\nstartet eine Sitzung mit der Nachricht im Prompt, `nxc reply --thread` nimmt die Sitzung auf der\nanderen Seite mit dem Rumpf der Antwort wieder auf, und ein vollständiges Quorum weckt den, der die\nTafel geöffnet hat. Eine Ungelesen-Liste war die zweite, ziehende Kopie dessen, was schon angekommen\nwar. Ein Mensch liest das Gespräch — `nxc threads show ` für eine Tafel in voller Länge, `nxc\nstatus` für den Stand eines Vorgangs — und beides bleibt unangetastet.\n**Das Hochziehen läuft automatisch, und es geht nichts verloren.** Ein Arbeitsbereich legt die\nstillgelegte Tabelle beim nächsten Öffnen ab; jede Nachricht bleibt genau dort, wo sie war, denn\nNachrichten standen nie darin. Eine `read_cursor`-Operation, die bereits in einem synchronisierten\nLog liegt, bleibt im Log und wird nur nicht mehr eingefaltet — so behandelt diese Engine seit jeher\neine Operationsart, die sie nicht kennt, und ein älterer und ein neuerer Knoten konvergieren\nweiterhin.\n**Für eine einbettende Anwendung ist das ein Übersetzungsfehler, und das ist Absicht.** Wer\n`markRead` aufruft oder `unread` / `in_turn` / `next_session` / `count` liest, dessen Code\nkompiliert nicht mehr, statt still eine Null zu liefern. Für die Zähler gibt es keinen Ersatz: seit\ndie Oberfläche fadenförmig ist, markiert ein Ungelesen-Abzeichen am Kanal genau das Eine, was eine\nAnwendung nicht mehr adressieren kann. Die Frage „wo steht das\" beantwortet `Engine::status`, je\nVorgang, auf Abruf.\n\n### Facade-Kontrakt\n- `breaking` · **Der Ungelesen-Apparat ist entfallen: der Lesezeiger, die Bestätigung, die ihn bewegte, und jede\ndaraus abgeleitete Zählung.** Nichts hat ihn mehr ausgewertet. `nxc inbox` und `nxc read` haben die\nKommandozeile vor zwei Wochen verlassen (je 0 Verwendungen in 66 gemessenen Agentensitzungen), und\nwas dahinter stehen blieb, war ein synchronisierter Lesezeiger, den eine Bestätigung bewegte, die\nkeine Anwendung je aufrief — fünf Schichten Rohr ohne Wasser. Das Einzige, was den Zeiger noch\nverbrauchte, die Sitzungsstart-Meldung über fertige Arbeit, wurde ERSETZT statt gelöscht: sie wird\njetzt aus dem Vorgang abgeleitet (v0.87.0), also nimmt das Entfernen des Zeigers nichts mit.\n**Was entfällt, beim Namen:** die Tabelle `read_cursors` und ihre Operationsart `read_cursor`;\n`Engine::mark_read` und `facade::mark_read` samt `ReadReceipt`; `facade::inbox` und die Lesung im\nStore darunter; `in_turn`, `next_session` und `count` auf `PrimeReport` und damit diese drei Felder\nvon `nxc prime --json`; `unread` und `unread_next_session` auf `ChannelView` und damit diese beiden\nFelder von `Engine::channels` und `Engine::public_channels`; und der Zeichner\n`facade::render_unread_blocks`.\n**An ihre Stelle tritt nichts, weil alle drei Zustellwege ohnehin SCHIEBEN.** `nxc send --to`\nstartet eine Sitzung mit der Nachricht im Prompt, `nxc reply --thread` nimmt die Sitzung auf der\nanderen Seite mit dem Rumpf der Antwort wieder auf, und ein vollständiges Quorum weckt den, der die\nTafel geöffnet hat. Eine Ungelesen-Liste war die zweite, ziehende Kopie dessen, was schon angekommen\nwar. Ein Mensch liest das Gespräch — `nxc threads show ` für eine Tafel in voller Länge, `nxc\nstatus` für den Stand eines Vorgangs — und beides bleibt unangetastet.\n**Das Hochziehen läuft automatisch, und es geht nichts verloren.** Ein Arbeitsbereich legt die\nstillgelegte Tabelle beim nächsten Öffnen ab; jede Nachricht bleibt genau dort, wo sie war, denn\nNachrichten standen nie darin. Eine `read_cursor`-Operation, die bereits in einem synchronisierten\nLog liegt, bleibt im Log und wird nur nicht mehr eingefaltet — so behandelt diese Engine seit jeher\neine Operationsart, die sie nicht kennt, und ein älterer und ein neuerer Knoten konvergieren\nweiterhin.\n**Für eine einbettende Anwendung ist das ein Übersetzungsfehler, und das ist Absicht.** Wer\n`markRead` aufruft oder `unread` / `in_turn` / `next_session` / `count` liest, dessen Code\nkompiliert nicht mehr, statt still eine Null zu liefern. Für die Zähler gibt es keinen Ersatz: seit\ndie Oberfläche fadenförmig ist, markiert ein Ungelesen-Abzeichen am Kanal genau das Eine, was eine\nAnwendung nicht mehr adressieren kann. Die Frage „wo steht das\" beantwortet `Engine::status`, je\nVorgang, auf Abruf." - } - }, - { - "version": "0.87.0", - "date": "2026-09-08", - "items": [ - { - "type": "changed", - "en": "**The three products are called the board, the memory and the channel** — everywhere, in that\nwording. `nxf`, `nxm` and `nxc` were introduced on the website as \"the record, the knowledge, the\nconversation\", while the site's own headline had been saying \"the board, the memory and the\nchannel\" for weeks. Two names for one thing is a reader's problem, not a stylistic one: the suite\npage and the page that links to it were describing the same three tools in vocabulary that did not\nmeet. The website's wording wins, because it is the one that was decided.\nAnd `chat`'s description no longer says \"so a team of agents coordinates\". That phrasing is the\ncompetitor framing this positioning is defined against, and it sat at the one point on the page\nclosest to it. It now describes the mechanism instead of the goal: one agent hands work to\nanother and gets the answer back.", - "de": "**Die drei Produkte heißen Board, Gedächtnis und Kanal** — überall, und in genau diesen Worten.\n`nxf`, `nxm` und `nxc` wurden auf der Website als „das Verzeichnis, das Wissen, das Gespräch\"\neingeführt, während die Überschrift derselben Seite seit Wochen „Board, Gedächtnis und Kanal\"\nsagte. Zwei Namen für eine Sache sind ein Problem des Lesers, keine Stilfrage. Die Fassung der\nWebsite gewinnt, weil sie die beschlossene ist. „Verzeichnis\" fällt zusätzlich aus einem eigenen\nGrund: im Entwicklerohr ist das ein Ordner.\nUnd die Beschreibung von `chat` sagt nicht mehr „ein Team von Agenten stimmt sich ab\". Das ist die\nSprache des Wettbewerbs, gegen die diese Positionierung definiert ist — an der Stelle der Seite,\ndie ihr am nächsten liegt. Sie beschreibt jetzt den Mechanismus statt des Ziels: Ein Agent\nübergibt Arbeit an einen anderen und bekommt die Antwort zurück." - }, - { - "type": "fixed", - "en": "Two lines that told readers something untrue, and neither was a link, so nothing checked them.\nThe **architecture guide** pointed at `nxsflow.com/open-source/docs#develop-architecture` for its\nrendered diagram — an address that had already stopped existing twice over: the anchor went when\nthe documentation became one page per document, and the `/open-source` prefix goes with the\nwebsite restructure. It now reads `nxsflow.com/docs/develop/architecture`, in both languages. A\nreader in the terminal would have typed the old one out and landed on nothing.\nAnd **`chat` no longer says its guides are still to come**. \"In the binary today — its guides\nfollow with 1.0\" was true and stopped being: the guides exist. A note that defers them tells a\nreader not to go and look — which now happens on the website's front page, where that section\nmoved.", - "de": "Zwei Zeilen, die den Lesern etwas Falsches sagten, und keine davon war ein Link — geprüft hat sie\ndeshalb nichts. Die **Architektur-Anleitung** verwies für ihr gerendertes Diagramm auf\n`nxsflow.com/open-source/docs#develop-architecture`, eine Adresse, die es gleich zweifach nicht\nmehr gab: der Anker fiel weg, als die Dokumentation eine Seite je Dokument wurde, der Präfix\n`/open-source` fällt mit dem Umbau der Website. Sie lautet jetzt\n`nxsflow.com/docs/develop/architecture`, in beiden Sprachen. Wer im Terminal liest, hätte die\nalte abgetippt und wäre auf nichts gestoßen.\nUnd **`chat` behauptet nicht mehr, seine Anleitungen kämen erst noch**. „Heute schon in der\nBinary — die Anleitungen folgen mit 1.0\" war wahr und ist es nicht mehr: die Anleitungen sind da.\nEin Hinweis, der sie vertagt, sagt einem Leser, er brauche gar nicht erst nachzusehen — und das\nsteht jetzt auf der Startseite der Website, wohin diese Sektion umgezogen ist." - }, - { - "type": "added", - "en": "**A receipt now says how you learn the answer.** `nxc send --to` and `nxc reply --thread` used to\nanswer with \"opened thread … in dm: — reply with `nxc reply --thread …`\", which tells the\nrequester how to keep TALKING at the moment nobody has answered yet, and names a hash derived from\nthe two handles that the reader can do nothing with. The line now names who was asked, which thread\nthe answer lands in, and the two reads that say where it stands — and `--json` carries the same as\nfields under `await`: the exact command to poll, the state that means finished, the two markers that\neach mean it has stopped, where the answer sits, and the declared deadline. A registered persona\ngets the opposite advice in the same field: it is resumed with the answer, so it must not poll.\n**A caller is now resumed ONCE with everything that arrived while it was working.** An answer that\ncame back while its requester was mid-turn used to be refused and reported only on the replier's own\nreceipt — the requester was never told. It is now held durably and delivered in one wake when that\nsession settles, in the same shape a channel round uses, naming what is still outstanding. A\nhand-back (\"I cannot carry this out\") is never batched away: it is delivered set apart, ahead of the\nordinary answers.\n**A session start names the commissions that finished while it was away.** That notice used to be\nderived from a read cursor nothing ever advanced, so it never cleared and grew with the age of the\nworkspace. It is derived from the operation now — what finished since this caller's previous session\nended — so it is a window rather than a debt. The cost, stated plainly: you see it once, not until\nyou acknowledge it.\n**FOR AN EMBEDDING APP, one behaviour change worth reading twice**: `prime --json`'s\n`threads_you_opened` is now ABSENT far more often than it used to be. It appeared for as long as\nanything the caller opened had completed and not been acknowledged; it now appears only inside that\none window, and not at all for a caller with no recorded session end — a human, or a persona whose\nsessions this device has never seen end. If your app relies on that block to notice finished work,\nrecord session ends through `Engine::session_ended`, or ask `Engine::status`, which answers the same\nquestion on demand and is unaffected.", - "de": "**Eine Quittung sagt jetzt, wie Sie die Antwort erfahren.** `nxc send --to` und `nxc reply --thread`\nantworteten mit „opened thread … in dm: — reply with `nxc reply --thread …`\" — das sagt dem\nAuftraggeber, wie er WEITERREDET, in dem Moment, in dem noch niemand geantwortet hat, und nennt\neinen aus den beiden Handles abgeleiteten Hash, mit dem der Leser nichts anfangen kann. Die Zeile\nnennt jetzt, wen Sie gefragt haben, in welchem Faden die Antwort landet und die beiden Lesebefehle,\ndie sagen, wo es steht — und `--json` trägt denselben Inhalt als Felder unter `await`: das exakte\nKommando zum Pollen, den Zustand, der „fertig\" heißt, die beiden Marker, von denen jeder „gestoppt\"\nheißt, wo die Antwort steht und die deklarierte Frist. Eine registrierte Persona bekommt im selben\nFeld den umgekehrten Rat: sie wird mit der Antwort geweckt und darf nicht pollen.\n**Ein Auftraggeber wird jetzt EINMAL mit allem geweckt, was eintraf, während er gearbeitet hat.**\nEine Antwort, die kam, während ihr Auftraggeber mitten im Zug war, wurde abgewiesen und nur auf der\nQuittung des Antwortenden vermerkt — der Auftraggeber erfuhr nie davon. Sie wird jetzt dauerhaft\ngehalten und in einem einzigen Wecken zugestellt, sobald die Sitzung zur Ruhe kommt, in derselben\nForm wie bei einem Kanal, und benennt, was noch aussteht. Eine Rückgabe („ich kann das nicht\nausführen\") wird nie mit weggebündelt: sie steht abgesetzt und vor den gewöhnlichen Antworten.\n**Ein Sitzungsstart benennt die Aufträge, die fertig wurden, während er weg war.** Diese Meldung kam\nbisher aus einem Lese-Cursor, den nichts je weiterbewegte — sie wurde also nie leer und wuchs mit dem\nAlter des Arbeitsbereichs. Sie wird jetzt aus dem Vorgang abgeleitet: was fertig wurde, seit die\nvorige Sitzung dieses Auftraggebers endete. Damit ist sie ein Fenster und keine Schuld. Der Preis,\nklar gesagt: Sie sehen sie einmal, nicht bis Sie sie quittieren.\n**Für eine einbettende App eine Verhaltensänderung, die zweimal gelesen gehört**:\n`threads_you_opened` in `prime --json` FEHLT jetzt viel häufiger als bisher. Der Block erschien,\nsolange irgendetwas Eröffnetes fertig und unquittiert war; er erscheint jetzt nur noch innerhalb\ndieses einen Fensters — und gar nicht für einen Auftraggeber ohne aufgezeichnetes Sitzungsende, also\nfür einen Menschen oder eine Persona, deren Sitzungen dieses Gerät nie enden sah. Wer sich auf den\nBlock verlässt, um fertige Arbeit zu bemerken, sollte Sitzungsenden über `Engine::session_ended`\naufzeichnen — oder `Engine::status` fragen, das dieselbe Frage jederzeit beantwortet und unberührt\nbleibt.", - "facade": "breaking" - }, - { - "type": "fixed", - "en": "The background service now **hands its single-instance lock back itself** when it stops, instead\nof leaving that to the kernel. A `flock` belongs to an *open file description*, and the kernel\ndrops it only when the **last** descriptor referring to that description closes — while the child\nprocesses the service starts (the `nxs` it re-executes for a due timer) briefly hold a copy of\nevery descriptor the service had. So a service that had just stopped could be refused by its own\nleftover lock: *\"a nexus-flow service is already running\"* with nothing running. Measured on\nmacOS: 165 of 100 000 stop-then-start cycles refused with one thread spawning children beside\nthem, 0 of 100 000 with nothing spawning — and 0 of 100 000 in both cases now. The workspace\nregistry's own lock had the same defect, where the cost was quieter: a registrant that could not\ntake it writes without it, so two `nxf init` runs at the same moment could lose one another's\nentry.", - "de": "Der Hintergrunddienst **gibt seine Einzelinstanz-Sperre jetzt selbst zurück**, wenn er sich\nbeendet, statt das dem Kernel zu überlassen. Ein `flock` hängt an einer *offenen\nDateibeschreibung*, und der Kernel gibt es erst frei, wenn der **letzte** Deskriptor auf diese\nBeschreibung geschlossen wird — die Kindprozesse, die der Dienst startet (das `nxs`, das er für\neinen fälligen Timer neu ausführt), halten aber kurzzeitig eine Kopie sämtlicher Deskriptoren des\nDienstes. Ein gerade gestoppter Dienst konnte deshalb von seiner eigenen zurückgelassenen Sperre\nabgewiesen werden: *„a nexus-flow service is already running\"*, obwohl keiner lief. Auf macOS\ngemessen: 165 von 100 000 Stopp-Start-Zyklen abgewiesen, während nebenher ein Thread Kindprozesse\nstartete, 0 von 100 000 ohne — und jetzt 0 von 100 000 in beiden Fällen. Die Sperre der\nWorkspace-Registry hatte denselben Defekt, dort mit leiserer Wirkung: Wer sie nicht bekommt,\nschreibt ohne sie, sodass zwei gleichzeitige `nxf init` den Eintrag des jeweils anderen verlieren\nkonnten." - } - ], - "notes": { - "en": "### Added\n- **A receipt now says how you learn the answer.** `nxc send --to` and `nxc reply --thread` used to\nanswer with \"opened thread … in dm: — reply with `nxc reply --thread …`\", which tells the\nrequester how to keep TALKING at the moment nobody has answered yet, and names a hash derived from\nthe two handles that the reader can do nothing with. The line now names who was asked, which thread\nthe answer lands in, and the two reads that say where it stands — and `--json` carries the same as\nfields under `await`: the exact command to poll, the state that means finished, the two markers that\neach mean it has stopped, where the answer sits, and the declared deadline. A registered persona\ngets the opposite advice in the same field: it is resumed with the answer, so it must not poll.\n**A caller is now resumed ONCE with everything that arrived while it was working.** An answer that\ncame back while its requester was mid-turn used to be refused and reported only on the replier's own\nreceipt — the requester was never told. It is now held durably and delivered in one wake when that\nsession settles, in the same shape a channel round uses, naming what is still outstanding. A\nhand-back (\"I cannot carry this out\") is never batched away: it is delivered set apart, ahead of the\nordinary answers.\n**A session start names the commissions that finished while it was away.** That notice used to be\nderived from a read cursor nothing ever advanced, so it never cleared and grew with the age of the\nworkspace. It is derived from the operation now — what finished since this caller's previous session\nended — so it is a window rather than a debt. The cost, stated plainly: you see it once, not until\nyou acknowledge it.\n**FOR AN EMBEDDING APP, one behaviour change worth reading twice**: `prime --json`'s\n`threads_you_opened` is now ABSENT far more often than it used to be. It appeared for as long as\nanything the caller opened had completed and not been acknowledged; it now appears only inside that\none window, and not at all for a caller with no recorded session end — a human, or a persona whose\nsessions this device has never seen end. If your app relies on that block to notice finished work,\nrecord session ends through `Engine::session_ended`, or ask `Engine::status`, which answers the same\nquestion on demand and is unaffected.\n\n### Changed\n- **The three products are called the board, the memory and the channel** — everywhere, in that\nwording. `nxf`, `nxm` and `nxc` were introduced on the website as \"the record, the knowledge, the\nconversation\", while the site's own headline had been saying \"the board, the memory and the\nchannel\" for weeks. Two names for one thing is a reader's problem, not a stylistic one: the suite\npage and the page that links to it were describing the same three tools in vocabulary that did not\nmeet. The website's wording wins, because it is the one that was decided.\nAnd `chat`'s description no longer says \"so a team of agents coordinates\". That phrasing is the\ncompetitor framing this positioning is defined against, and it sat at the one point on the page\nclosest to it. It now describes the mechanism instead of the goal: one agent hands work to\nanother and gets the answer back.\n\n### Fixed\n- Two lines that told readers something untrue, and neither was a link, so nothing checked them.\nThe **architecture guide** pointed at `nxsflow.com/open-source/docs#develop-architecture` for its\nrendered diagram — an address that had already stopped existing twice over: the anchor went when\nthe documentation became one page per document, and the `/open-source` prefix goes with the\nwebsite restructure. It now reads `nxsflow.com/docs/develop/architecture`, in both languages. A\nreader in the terminal would have typed the old one out and landed on nothing.\nAnd **`chat` no longer says its guides are still to come**. \"In the binary today — its guides\nfollow with 1.0\" was true and stopped being: the guides exist. A note that defers them tells a\nreader not to go and look — which now happens on the website's front page, where that section\nmoved.\n- The background service now **hands its single-instance lock back itself** when it stops, instead\nof leaving that to the kernel. A `flock` belongs to an *open file description*, and the kernel\ndrops it only when the **last** descriptor referring to that description closes — while the child\nprocesses the service starts (the `nxs` it re-executes for a due timer) briefly hold a copy of\nevery descriptor the service had. So a service that had just stopped could be refused by its own\nleftover lock: *\"a nexus-flow service is already running\"* with nothing running. Measured on\nmacOS: 165 of 100 000 stop-then-start cycles refused with one thread spawning children beside\nthem, 0 of 100 000 with nothing spawning — and 0 of 100 000 in both cases now. The workspace\nregistry's own lock had the same defect, where the cost was quieter: a registrant that could not\ntake it writes without it, so two `nxf init` runs at the same moment could lose one another's\nentry.\n\n### Facade Contract\n- `breaking` · **A receipt now says how you learn the answer.** `nxc send --to` and `nxc reply --thread` used to\nanswer with \"opened thread … in dm: — reply with `nxc reply --thread …`\", which tells the\nrequester how to keep TALKING at the moment nobody has answered yet, and names a hash derived from\nthe two handles that the reader can do nothing with. The line now names who was asked, which thread\nthe answer lands in, and the two reads that say where it stands — and `--json` carries the same as\nfields under `await`: the exact command to poll, the state that means finished, the two markers that\neach mean it has stopped, where the answer sits, and the declared deadline. A registered persona\ngets the opposite advice in the same field: it is resumed with the answer, so it must not poll.\n**A caller is now resumed ONCE with everything that arrived while it was working.** An answer that\ncame back while its requester was mid-turn used to be refused and reported only on the replier's own\nreceipt — the requester was never told. It is now held durably and delivered in one wake when that\nsession settles, in the same shape a channel round uses, naming what is still outstanding. A\nhand-back (\"I cannot carry this out\") is never batched away: it is delivered set apart, ahead of the\nordinary answers.\n**A session start names the commissions that finished while it was away.** That notice used to be\nderived from a read cursor nothing ever advanced, so it never cleared and grew with the age of the\nworkspace. It is derived from the operation now — what finished since this caller's previous session\nended — so it is a window rather than a debt. The cost, stated plainly: you see it once, not until\nyou acknowledge it.\n**FOR AN EMBEDDING APP, one behaviour change worth reading twice**: `prime --json`'s\n`threads_you_opened` is now ABSENT far more often than it used to be. It appeared for as long as\nanything the caller opened had completed and not been acknowledged; it now appears only inside that\none window, and not at all for a caller with no recorded session end — a human, or a persona whose\nsessions this device has never seen end. If your app relies on that block to notice finished work,\nrecord session ends through `Engine::session_ended`, or ask `Engine::status`, which answers the same\nquestion on demand and is unaffected.", - "de": "### Neu\n- **Eine Quittung sagt jetzt, wie Sie die Antwort erfahren.** `nxc send --to` und `nxc reply --thread`\nantworteten mit „opened thread … in dm: — reply with `nxc reply --thread …`\" — das sagt dem\nAuftraggeber, wie er WEITERREDET, in dem Moment, in dem noch niemand geantwortet hat, und nennt\neinen aus den beiden Handles abgeleiteten Hash, mit dem der Leser nichts anfangen kann. Die Zeile\nnennt jetzt, wen Sie gefragt haben, in welchem Faden die Antwort landet und die beiden Lesebefehle,\ndie sagen, wo es steht — und `--json` trägt denselben Inhalt als Felder unter `await`: das exakte\nKommando zum Pollen, den Zustand, der „fertig\" heißt, die beiden Marker, von denen jeder „gestoppt\"\nheißt, wo die Antwort steht und die deklarierte Frist. Eine registrierte Persona bekommt im selben\nFeld den umgekehrten Rat: sie wird mit der Antwort geweckt und darf nicht pollen.\n**Ein Auftraggeber wird jetzt EINMAL mit allem geweckt, was eintraf, während er gearbeitet hat.**\nEine Antwort, die kam, während ihr Auftraggeber mitten im Zug war, wurde abgewiesen und nur auf der\nQuittung des Antwortenden vermerkt — der Auftraggeber erfuhr nie davon. Sie wird jetzt dauerhaft\ngehalten und in einem einzigen Wecken zugestellt, sobald die Sitzung zur Ruhe kommt, in derselben\nForm wie bei einem Kanal, und benennt, was noch aussteht. Eine Rückgabe („ich kann das nicht\nausführen\") wird nie mit weggebündelt: sie steht abgesetzt und vor den gewöhnlichen Antworten.\n**Ein Sitzungsstart benennt die Aufträge, die fertig wurden, während er weg war.** Diese Meldung kam\nbisher aus einem Lese-Cursor, den nichts je weiterbewegte — sie wurde also nie leer und wuchs mit dem\nAlter des Arbeitsbereichs. Sie wird jetzt aus dem Vorgang abgeleitet: was fertig wurde, seit die\nvorige Sitzung dieses Auftraggebers endete. Damit ist sie ein Fenster und keine Schuld. Der Preis,\nklar gesagt: Sie sehen sie einmal, nicht bis Sie sie quittieren.\n**Für eine einbettende App eine Verhaltensänderung, die zweimal gelesen gehört**:\n`threads_you_opened` in `prime --json` FEHLT jetzt viel häufiger als bisher. Der Block erschien,\nsolange irgendetwas Eröffnetes fertig und unquittiert war; er erscheint jetzt nur noch innerhalb\ndieses einen Fensters — und gar nicht für einen Auftraggeber ohne aufgezeichnetes Sitzungsende, also\nfür einen Menschen oder eine Persona, deren Sitzungen dieses Gerät nie enden sah. Wer sich auf den\nBlock verlässt, um fertige Arbeit zu bemerken, sollte Sitzungsenden über `Engine::session_ended`\naufzeichnen — oder `Engine::status` fragen, das dieselbe Frage jederzeit beantwortet und unberührt\nbleibt.\n\n### Geändert\n- **Die drei Produkte heißen Board, Gedächtnis und Kanal** — überall, und in genau diesen Worten.\n`nxf`, `nxm` und `nxc` wurden auf der Website als „das Verzeichnis, das Wissen, das Gespräch\"\neingeführt, während die Überschrift derselben Seite seit Wochen „Board, Gedächtnis und Kanal\"\nsagte. Zwei Namen für eine Sache sind ein Problem des Lesers, keine Stilfrage. Die Fassung der\nWebsite gewinnt, weil sie die beschlossene ist. „Verzeichnis\" fällt zusätzlich aus einem eigenen\nGrund: im Entwicklerohr ist das ein Ordner.\nUnd die Beschreibung von `chat` sagt nicht mehr „ein Team von Agenten stimmt sich ab\". Das ist die\nSprache des Wettbewerbs, gegen die diese Positionierung definiert ist — an der Stelle der Seite,\ndie ihr am nächsten liegt. Sie beschreibt jetzt den Mechanismus statt des Ziels: Ein Agent\nübergibt Arbeit an einen anderen und bekommt die Antwort zurück.\n\n### Behoben\n- Zwei Zeilen, die den Lesern etwas Falsches sagten, und keine davon war ein Link — geprüft hat sie\ndeshalb nichts. Die **Architektur-Anleitung** verwies für ihr gerendertes Diagramm auf\n`nxsflow.com/open-source/docs#develop-architecture`, eine Adresse, die es gleich zweifach nicht\nmehr gab: der Anker fiel weg, als die Dokumentation eine Seite je Dokument wurde, der Präfix\n`/open-source` fällt mit dem Umbau der Website. Sie lautet jetzt\n`nxsflow.com/docs/develop/architecture`, in beiden Sprachen. Wer im Terminal liest, hätte die\nalte abgetippt und wäre auf nichts gestoßen.\nUnd **`chat` behauptet nicht mehr, seine Anleitungen kämen erst noch**. „Heute schon in der\nBinary — die Anleitungen folgen mit 1.0\" war wahr und ist es nicht mehr: die Anleitungen sind da.\nEin Hinweis, der sie vertagt, sagt einem Leser, er brauche gar nicht erst nachzusehen — und das\nsteht jetzt auf der Startseite der Website, wohin diese Sektion umgezogen ist.\n- Der Hintergrunddienst **gibt seine Einzelinstanz-Sperre jetzt selbst zurück**, wenn er sich\nbeendet, statt das dem Kernel zu überlassen. Ein `flock` hängt an einer *offenen\nDateibeschreibung*, und der Kernel gibt es erst frei, wenn der **letzte** Deskriptor auf diese\nBeschreibung geschlossen wird — die Kindprozesse, die der Dienst startet (das `nxs`, das er für\neinen fälligen Timer neu ausführt), halten aber kurzzeitig eine Kopie sämtlicher Deskriptoren des\nDienstes. Ein gerade gestoppter Dienst konnte deshalb von seiner eigenen zurückgelassenen Sperre\nabgewiesen werden: *„a nexus-flow service is already running\"*, obwohl keiner lief. Auf macOS\ngemessen: 165 von 100 000 Stopp-Start-Zyklen abgewiesen, während nebenher ein Thread Kindprozesse\nstartete, 0 von 100 000 ohne — und jetzt 0 von 100 000 in beiden Fällen. Die Sperre der\nWorkspace-Registry hatte denselben Defekt, dort mit leiserer Wirkung: Wer sie nicht bekommt,\nschreibt ohne sie, sodass zwei gleichzeitige `nxf init` den Eintrag des jeweils anderen verlieren\nkonnten.\n\n### Facade-Kontrakt\n- `breaking` · **Eine Quittung sagt jetzt, wie Sie die Antwort erfahren.** `nxc send --to` und `nxc reply --thread`\nantworteten mit „opened thread … in dm: — reply with `nxc reply --thread …`\" — das sagt dem\nAuftraggeber, wie er WEITERREDET, in dem Moment, in dem noch niemand geantwortet hat, und nennt\neinen aus den beiden Handles abgeleiteten Hash, mit dem der Leser nichts anfangen kann. Die Zeile\nnennt jetzt, wen Sie gefragt haben, in welchem Faden die Antwort landet und die beiden Lesebefehle,\ndie sagen, wo es steht — und `--json` trägt denselben Inhalt als Felder unter `await`: das exakte\nKommando zum Pollen, den Zustand, der „fertig\" heißt, die beiden Marker, von denen jeder „gestoppt\"\nheißt, wo die Antwort steht und die deklarierte Frist. Eine registrierte Persona bekommt im selben\nFeld den umgekehrten Rat: sie wird mit der Antwort geweckt und darf nicht pollen.\n**Ein Auftraggeber wird jetzt EINMAL mit allem geweckt, was eintraf, während er gearbeitet hat.**\nEine Antwort, die kam, während ihr Auftraggeber mitten im Zug war, wurde abgewiesen und nur auf der\nQuittung des Antwortenden vermerkt — der Auftraggeber erfuhr nie davon. Sie wird jetzt dauerhaft\ngehalten und in einem einzigen Wecken zugestellt, sobald die Sitzung zur Ruhe kommt, in derselben\nForm wie bei einem Kanal, und benennt, was noch aussteht. Eine Rückgabe („ich kann das nicht\nausführen\") wird nie mit weggebündelt: sie steht abgesetzt und vor den gewöhnlichen Antworten.\n**Ein Sitzungsstart benennt die Aufträge, die fertig wurden, während er weg war.** Diese Meldung kam\nbisher aus einem Lese-Cursor, den nichts je weiterbewegte — sie wurde also nie leer und wuchs mit dem\nAlter des Arbeitsbereichs. Sie wird jetzt aus dem Vorgang abgeleitet: was fertig wurde, seit die\nvorige Sitzung dieses Auftraggebers endete. Damit ist sie ein Fenster und keine Schuld. Der Preis,\nklar gesagt: Sie sehen sie einmal, nicht bis Sie sie quittieren.\n**Für eine einbettende App eine Verhaltensänderung, die zweimal gelesen gehört**:\n`threads_you_opened` in `prime --json` FEHLT jetzt viel häufiger als bisher. Der Block erschien,\nsolange irgendetwas Eröffnetes fertig und unquittiert war; er erscheint jetzt nur noch innerhalb\ndieses einen Fensters — und gar nicht für einen Auftraggeber ohne aufgezeichnetes Sitzungsende, also\nfür einen Menschen oder eine Persona, deren Sitzungen dieses Gerät nie enden sah. Wer sich auf den\nBlock verlässt, um fertige Arbeit zu bemerken, sollte Sitzungsenden über `Engine::session_ended`\naufzeichnen — oder `Engine::status` fragen, das dieselbe Frage jederzeit beantwortet und unberührt\nbleibt." - } - }, - { - "version": "0.86.0", - "date": "2026-09-07", - "items": [ - { - "type": "fixed", - "en": "`nxc search` and `Engine::search` now answer \"am I in this channel at all?\" the way the thread reader\nbeside them does: for a **declared** channel the `members:` list decides, in both directions and with\nno `send` in between. Three things change. A member that had only ever *replied* in a declared channel\nused to get \"no matches\" for everything, including for its own words — a caller arrives under its\nqualified `origin/handle` identity while a declaration materialises its members bare, and the old\nsearch compared the two for equality; it now finds its channels. A handle written into `members:`\nfinds that channel's boards at once instead of only after somebody sends again. And a handle struck\nfrom `members:` stops finding message bodies there, where it used to keep finding them after `nxc\nthreads show` had already stopped serving them. Two deliberate limits are unchanged and now written\nin `nxc guide commands`: a `public` channel you never joined is still not searched, and the search\nstill does not follow an operation across channel borders the way `nxc status` does. Library callers:\n`ChatStore::search_messages` takes the channel scope (`&[String]`) instead of a handle — membership is\ndecided one layer up, in `nexus_chat::facade::search`, whose signature is unchanged along with\n`Engine::search`'s and the `--json` shape.", - "de": "`nxc search` und `Engine::search` beantworten die Frage „bin ich in diesem Kanal überhaupt drin?\" nun\nso wie der Faden-Leser daneben: Bei einem **deklarierten** Kanal entscheidet die `members:`-Liste, in\nbeiden Richtungen und ohne ein `send` dazwischen. Drei Dinge ändern sich. Ein Mitglied, das in einem\ndeklarierten Kanal nur *geantwortet* hatte, bekam bisher auf alles „keine Treffer\" — auch auf seine\neigenen Worte: Ein Aufrufer kommt unter seiner qualifizierten `origin/handle`-Identität an, während\neine Deklaration ihre Mitglieder bar materialisiert, und die alte Suche verglich beides auf\nGleichheit; jetzt findet es seine Kanäle. Ein in `members:` eingetragenes Handle findet die Runden des\nKanals sofort und nicht erst, wenn wieder jemand sendet. Und ein aus `members:` gestrichenes Handle\nfindet dort keine Nachrichtentexte mehr, wo es sie bisher weiterfand, nachdem `nxc threads show` sie\ndemselben Aufrufer längst verweigerte. Zwei bewusste Grenzen bleiben und stehen jetzt in `nxc guide\ncommands`: Ein `public`-Kanal, dem Sie nie beigetreten sind, wird weiterhin nicht durchsucht, und die\nSuche folgt einem Vorgang weiterhin nicht über Kanalgrenzen, wie `nxc status` es tut. Für\nBibliotheks-Aufrufer: `ChatStore::search_messages` nimmt den Kanal-Suchraum (`&[String]`) statt eines\nHandles — die Mitgliedschaft entscheidet eine Schicht höher, in `nexus_chat::facade::search`, deren\nSignatur unverändert bleibt, ebenso die von `Engine::search` und die `--json`-Form.", - "facade": "breaking", - "security": true - }, - { - "type": "fixed", - "en": "Which background service instance a command belongs to is now decided by the **binary that is\nrunning**, not by the directory it is standing in. `NXS_SERVICE_INSTANCE` is exported by `direnv`\nfor a whole working copy, so until now the installed `nxs`, `nxf`, `nxm` and `nxc` on your `PATH`\nbecame calls of a development instance merely by being run there — and the development instance they\ninstalled ran the *installed* binary, so it never carried a development build at all. Measured on one\nmachine: three registered services, all three pointing at the same `~/.local/bin/nxs`. Now an\ninstalled binary is always the machine's own `nexus-flow` service and the variable has no vote over\nit; only a binary out of `target/debug` or `target/release` is a development instance, and the\nvariable says *which* one. Two consequences worth knowing: install a checkout's own service by\nrunning the build (`./target/debug/nxs sync daemon install`) — which is what finally points that\ninstance's alias at the binary you built — and `nxs sync bind` from an installed binary no longer\nrecreates a development service behind your back. `nxs sync daemon install`/`uninstall` say so, in\nthe receipt and on stderr, when the variable named an instance they could not give a vote to; an\ninstance that is already installed can still be addressed by its own alias\n(`~/.nexusflow-dev/bin/nexus-flow-dev uninstall` — the alias stands for `nxs sync daemon`), which is\nthe same name the running service reads itself from.\n\n*For embedders:* `nxs-service` gains `Origin` and `Instance::resolve_from` takes it as a third\nargument. `Instance::ambient()`, `ServiceHome::resolve()` and everything built on them keep their\nsignatures and now answer by this rule.", - "de": "Welcher Instanz des Hintergrunddienstes ein Befehl angehört, entscheidet jetzt die **laufende\nBinärdatei** — nicht das Verzeichnis, in dem er steht. `NXS_SERVICE_INSTANCE` wird von `direnv` für\neine ganze Arbeitskopie exportiert; bis jetzt wurden damit die installierten `nxs`, `nxf`, `nxm` und\n`nxc` aus Ihrem `PATH` allein dadurch zu Aufrufen einer Entwicklungsinstanz, dass sie dort liefen —\nund die so installierte Entwicklungsinstanz fuhr die *installierte* Binärdatei, hat also nie einen\nEntwicklungsstand getragen. Auf einer Maschine gemessen: drei angemeldete Dienste, alle drei auf\ndieselbe `~/.local/bin/nxs` zeigend. Jetzt ist eine installierte Binärdatei immer der `nexus-flow`\n-Dienst des Rechners selbst, und die Variable hat über sie keine Stimme; nur eine Binärdatei aus\n`target/debug` oder `target/release` ist eine Entwicklungsinstanz, und die Variable sagt, *welche*.\nZwei Folgen, die man kennen sollte: Installieren Sie den eigenen Dienst einer Arbeitskopie aus dem\nBau heraus (`./target/debug/nxs sync daemon install`) — das ist es, was die Verknüpfung dieser\nInstanz endlich auf die von Ihnen gebaute Datei richtet —, und ein `nxs sync bind` aus der\ninstallierten Binärdatei legt hinter Ihrem Rücken keinen Entwicklungsdienst mehr an. `nxs sync daemon\ninstall`/`uninstall` sagen es, in der Quittung und auf stderr, wenn die Variable eine Instanz genannt\nhat, der sie keine Stimme geben konnten; eine bereits installierte Instanz erreichen Sie weiterhin\nüber ihre eigene Verknüpfung (`~/.nexusflow-dev/bin/nexus-flow-dev uninstall` — die Verknüpfung\nsteht für `nxs sync daemon`) — derselbe Name, aus dem der laufende Dienst sich selbst liest.\n\n*Für Einbettende:* `nxs-service` bekommt `Origin`, und `Instance::resolve_from` nimmt es als drittes\nArgument entgegen. `Instance::ambient()`, `ServiceHome::resolve()` und alles darauf Gebaute behalten\nihre Signatur und antworten jetzt nach dieser Regel." - }, - { - "type": "changed", - "en": "`nxc status` answers its own question again: **is anything still going on here?** A finished\noperation is no longer in the default view. It used to stay for ever — the root's `awaiting_human`\nflag counted as \"live\", and that flag is derived from two facts that never stop being true (the root\nasked, the root was answered), so nothing ever left. After one working day in a real workspace the\nview was thirteen operations and 424 lines, nine of them with nothing open at all, and the person\nusing it had built a watchdog around `--json` because the view no longer answered its own question.\nWhat keeps an operation listed now is what a reader can act on: an open thread, a hand-back nobody\ntook up, this device's working copy, or a dead end that failed — an answer nothing was ever done\nwith, now marked `· dead end` on the operation line. **`nxc status --all`** shows the finished ones too, marked\n`· finished`, and takes `--channel` with it (`--all --channel review` is that channel's history);\n`--thread ` is unchanged and still shows one operation whether it runs or not. And each thread\nnow says **what became of the session working on it** — `session_state` of `running` / `ended` /\n`unknown` beside the `session` id it is about, so telling \"waiting for a human\" from \"hung\" no\nlonger costs a second `nxc session state` call. In the terminal an open thread whose session is over\nsays so outright. Where a runtime cannot answer the process question at all, the report says that\nonce, as `worker_answers_liveness`, rather than leaving a column of `unknown` to be misread.\nLibrary callers: `Engine::status` is unchanged in signature but its two listing scopes now return\nfewer operations, `nexus_chat::facade::status` takes the handle's `&dyn Worker` as its second\nargument, `StatusScope` has gained `All(Option<&str>)`, and `StatusThread`/`StatusReport` have\ngained the two fields above.", - "de": "`nxc status` beantwortet wieder seine eigene Frage: **läuft hier noch etwas?** Ein abgeschlossener\nVorgang steht nicht mehr in der Standardansicht. Bisher blieb er dort für immer — das Kennzeichen\n`awaiting_human` an der Wurzel zählte als „lebt\", und dieses Kennzeichen leitet sich aus zwei\nTatsachen ab, die nie aufhören zu gelten (die Wurzel hat gefragt, die Wurzel wurde beantwortet), also\nverließ nie etwas die Ansicht. Nach einem Arbeitstag in einem echten Arbeitsbereich waren es dreizehn\nVorgänge und 424 Zeilen, neun davon ohne einen einzigen offenen Faden — und wer die Ansicht benutzte,\nhatte sich um `--json` herum einen eigenen Wächter gebaut, weil sie ihre eigene Frage nicht mehr\nbeantwortete. In der Liste hält einen Vorgang jetzt das, woran ein Leser etwas tun kann: ein offener\nFaden, eine Rückgabe, die niemand aufgenommen hat, die Arbeitskopie dieses Geräts oder eine\ngescheiterte Sackgasse — eine Antwort, mit der nie etwas geschah, an der Vorgangszeile jetzt mit\n`· dead end` markiert. **`nxc status\n--all`** zeigt auch die abgeschlossenen, mit `· finished` markiert, und nimmt `--channel` mit\n(`--all --channel review` ist die Geschichte dieses Kanals); `--thread ` bleibt unverändert und\nzeigt weiterhin einen Vorgang, ob er läuft oder nicht. Und jeder Faden sagt jetzt, **was aus der\nSitzung wurde, die daran arbeitet** — `session_state` mit `running` / `ended` / `unknown`, direkt\nneben der `session`-ID, um die es geht: „wartet auf einen Menschen\" von „hängt\" zu unterscheiden\nkostet keinen zweiten Aufruf von `nxc session state` mehr. Im Terminal sagt ein offener Faden, dessen\nSitzung vorbei ist, das unmissverständlich. Wo eine Laufzeitumgebung die Prozessfrage überhaupt nicht\nbeantworten kann, sagt der Bericht das einmal als `worker_answers_liveness`, statt eine Spalte\n`unknown` stehen zu lassen, die falsch gelesen wird. Für Bibliotheks-Aufrufer: Die Signatur von\n`Engine::status` bleibt, seine beiden Listenformen geben aber weniger Vorgänge zurück;\n`nexus_chat::facade::status` nimmt als zweites Argument den `&dyn Worker` des Handles; `StatusScope`\nhat `All(Option<&str>)` bekommen und `StatusThread`/`StatusReport` die beiden Felder von oben.", - "facade": "breaking" - } - ], - "notes": { - "en": "### Changed\n- `nxc status` answers its own question again: **is anything still going on here?** A finished\noperation is no longer in the default view. It used to stay for ever — the root's `awaiting_human`\nflag counted as \"live\", and that flag is derived from two facts that never stop being true (the root\nasked, the root was answered), so nothing ever left. After one working day in a real workspace the\nview was thirteen operations and 424 lines, nine of them with nothing open at all, and the person\nusing it had built a watchdog around `--json` because the view no longer answered its own question.\nWhat keeps an operation listed now is what a reader can act on: an open thread, a hand-back nobody\ntook up, this device's working copy, or a dead end that failed — an answer nothing was ever done\nwith, now marked `· dead end` on the operation line. **`nxc status --all`** shows the finished ones too, marked\n`· finished`, and takes `--channel` with it (`--all --channel review` is that channel's history);\n`--thread ` is unchanged and still shows one operation whether it runs or not. And each thread\nnow says **what became of the session working on it** — `session_state` of `running` / `ended` /\n`unknown` beside the `session` id it is about, so telling \"waiting for a human\" from \"hung\" no\nlonger costs a second `nxc session state` call. In the terminal an open thread whose session is over\nsays so outright. Where a runtime cannot answer the process question at all, the report says that\nonce, as `worker_answers_liveness`, rather than leaving a column of `unknown` to be misread.\nLibrary callers: `Engine::status` is unchanged in signature but its two listing scopes now return\nfewer operations, `nexus_chat::facade::status` takes the handle's `&dyn Worker` as its second\nargument, `StatusScope` has gained `All(Option<&str>)`, and `StatusThread`/`StatusReport` have\ngained the two fields above.\n\n### Fixed\n- `nxc search` and `Engine::search` now answer \"am I in this channel at all?\" the way the thread reader\nbeside them does: for a **declared** channel the `members:` list decides, in both directions and with\nno `send` in between. Three things change. A member that had only ever *replied* in a declared channel\nused to get \"no matches\" for everything, including for its own words — a caller arrives under its\nqualified `origin/handle` identity while a declaration materialises its members bare, and the old\nsearch compared the two for equality; it now finds its channels. A handle written into `members:`\nfinds that channel's boards at once instead of only after somebody sends again. And a handle struck\nfrom `members:` stops finding message bodies there, where it used to keep finding them after `nxc\nthreads show` had already stopped serving them. Two deliberate limits are unchanged and now written\nin `nxc guide commands`: a `public` channel you never joined is still not searched, and the search\nstill does not follow an operation across channel borders the way `nxc status` does. Library callers:\n`ChatStore::search_messages` takes the channel scope (`&[String]`) instead of a handle — membership is\ndecided one layer up, in `nexus_chat::facade::search`, whose signature is unchanged along with\n`Engine::search`'s and the `--json` shape.\n- Which background service instance a command belongs to is now decided by the **binary that is\nrunning**, not by the directory it is standing in. `NXS_SERVICE_INSTANCE` is exported by `direnv`\nfor a whole working copy, so until now the installed `nxs`, `nxf`, `nxm` and `nxc` on your `PATH`\nbecame calls of a development instance merely by being run there — and the development instance they\ninstalled ran the *installed* binary, so it never carried a development build at all. Measured on one\nmachine: three registered services, all three pointing at the same `~/.local/bin/nxs`. Now an\ninstalled binary is always the machine's own `nexus-flow` service and the variable has no vote over\nit; only a binary out of `target/debug` or `target/release` is a development instance, and the\nvariable says *which* one. Two consequences worth knowing: install a checkout's own service by\nrunning the build (`./target/debug/nxs sync daemon install`) — which is what finally points that\ninstance's alias at the binary you built — and `nxs sync bind` from an installed binary no longer\nrecreates a development service behind your back. `nxs sync daemon install`/`uninstall` say so, in\nthe receipt and on stderr, when the variable named an instance they could not give a vote to; an\ninstance that is already installed can still be addressed by its own alias\n(`~/.nexusflow-dev/bin/nexus-flow-dev uninstall` — the alias stands for `nxs sync daemon`), which is\nthe same name the running service reads itself from.\n\n*For embedders:* `nxs-service` gains `Origin` and `Instance::resolve_from` takes it as a third\nargument. `Instance::ambient()`, `ServiceHome::resolve()` and everything built on them keep their\nsignatures and now answer by this rule.\n\n### Facade Contract\n- `breaking` · security · `nxc search` and `Engine::search` now answer \"am I in this channel at all?\" the way the thread reader\nbeside them does: for a **declared** channel the `members:` list decides, in both directions and with\nno `send` in between. Three things change. A member that had only ever *replied* in a declared channel\nused to get \"no matches\" for everything, including for its own words — a caller arrives under its\nqualified `origin/handle` identity while a declaration materialises its members bare, and the old\nsearch compared the two for equality; it now finds its channels. A handle written into `members:`\nfinds that channel's boards at once instead of only after somebody sends again. And a handle struck\nfrom `members:` stops finding message bodies there, where it used to keep finding them after `nxc\nthreads show` had already stopped serving them. Two deliberate limits are unchanged and now written\nin `nxc guide commands`: a `public` channel you never joined is still not searched, and the search\nstill does not follow an operation across channel borders the way `nxc status` does. Library callers:\n`ChatStore::search_messages` takes the channel scope (`&[String]`) instead of a handle — membership is\ndecided one layer up, in `nexus_chat::facade::search`, whose signature is unchanged along with\n`Engine::search`'s and the `--json` shape.\n- `breaking` · `nxc status` answers its own question again: **is anything still going on here?** A finished\noperation is no longer in the default view. It used to stay for ever — the root's `awaiting_human`\nflag counted as \"live\", and that flag is derived from two facts that never stop being true (the root\nasked, the root was answered), so nothing ever left. After one working day in a real workspace the\nview was thirteen operations and 424 lines, nine of them with nothing open at all, and the person\nusing it had built a watchdog around `--json` because the view no longer answered its own question.\nWhat keeps an operation listed now is what a reader can act on: an open thread, a hand-back nobody\ntook up, this device's working copy, or a dead end that failed — an answer nothing was ever done\nwith, now marked `· dead end` on the operation line. **`nxc status --all`** shows the finished ones too, marked\n`· finished`, and takes `--channel` with it (`--all --channel review` is that channel's history);\n`--thread ` is unchanged and still shows one operation whether it runs or not. And each thread\nnow says **what became of the session working on it** — `session_state` of `running` / `ended` /\n`unknown` beside the `session` id it is about, so telling \"waiting for a human\" from \"hung\" no\nlonger costs a second `nxc session state` call. In the terminal an open thread whose session is over\nsays so outright. Where a runtime cannot answer the process question at all, the report says that\nonce, as `worker_answers_liveness`, rather than leaving a column of `unknown` to be misread.\nLibrary callers: `Engine::status` is unchanged in signature but its two listing scopes now return\nfewer operations, `nexus_chat::facade::status` takes the handle's `&dyn Worker` as its second\nargument, `StatusScope` has gained `All(Option<&str>)`, and `StatusThread`/`StatusReport` have\ngained the two fields above.", - "de": "### Geändert\n- `nxc status` beantwortet wieder seine eigene Frage: **läuft hier noch etwas?** Ein abgeschlossener\nVorgang steht nicht mehr in der Standardansicht. Bisher blieb er dort für immer — das Kennzeichen\n`awaiting_human` an der Wurzel zählte als „lebt\", und dieses Kennzeichen leitet sich aus zwei\nTatsachen ab, die nie aufhören zu gelten (die Wurzel hat gefragt, die Wurzel wurde beantwortet), also\nverließ nie etwas die Ansicht. Nach einem Arbeitstag in einem echten Arbeitsbereich waren es dreizehn\nVorgänge und 424 Zeilen, neun davon ohne einen einzigen offenen Faden — und wer die Ansicht benutzte,\nhatte sich um `--json` herum einen eigenen Wächter gebaut, weil sie ihre eigene Frage nicht mehr\nbeantwortete. In der Liste hält einen Vorgang jetzt das, woran ein Leser etwas tun kann: ein offener\nFaden, eine Rückgabe, die niemand aufgenommen hat, die Arbeitskopie dieses Geräts oder eine\ngescheiterte Sackgasse — eine Antwort, mit der nie etwas geschah, an der Vorgangszeile jetzt mit\n`· dead end` markiert. **`nxc status\n--all`** zeigt auch die abgeschlossenen, mit `· finished` markiert, und nimmt `--channel` mit\n(`--all --channel review` ist die Geschichte dieses Kanals); `--thread ` bleibt unverändert und\nzeigt weiterhin einen Vorgang, ob er läuft oder nicht. Und jeder Faden sagt jetzt, **was aus der\nSitzung wurde, die daran arbeitet** — `session_state` mit `running` / `ended` / `unknown`, direkt\nneben der `session`-ID, um die es geht: „wartet auf einen Menschen\" von „hängt\" zu unterscheiden\nkostet keinen zweiten Aufruf von `nxc session state` mehr. Im Terminal sagt ein offener Faden, dessen\nSitzung vorbei ist, das unmissverständlich. Wo eine Laufzeitumgebung die Prozessfrage überhaupt nicht\nbeantworten kann, sagt der Bericht das einmal als `worker_answers_liveness`, statt eine Spalte\n`unknown` stehen zu lassen, die falsch gelesen wird. Für Bibliotheks-Aufrufer: Die Signatur von\n`Engine::status` bleibt, seine beiden Listenformen geben aber weniger Vorgänge zurück;\n`nexus_chat::facade::status` nimmt als zweites Argument den `&dyn Worker` des Handles; `StatusScope`\nhat `All(Option<&str>)` bekommen und `StatusThread`/`StatusReport` die beiden Felder von oben.\n\n### Behoben\n- `nxc search` und `Engine::search` beantworten die Frage „bin ich in diesem Kanal überhaupt drin?\" nun\nso wie der Faden-Leser daneben: Bei einem **deklarierten** Kanal entscheidet die `members:`-Liste, in\nbeiden Richtungen und ohne ein `send` dazwischen. Drei Dinge ändern sich. Ein Mitglied, das in einem\ndeklarierten Kanal nur *geantwortet* hatte, bekam bisher auf alles „keine Treffer\" — auch auf seine\neigenen Worte: Ein Aufrufer kommt unter seiner qualifizierten `origin/handle`-Identität an, während\neine Deklaration ihre Mitglieder bar materialisiert, und die alte Suche verglich beides auf\nGleichheit; jetzt findet es seine Kanäle. Ein in `members:` eingetragenes Handle findet die Runden des\nKanals sofort und nicht erst, wenn wieder jemand sendet. Und ein aus `members:` gestrichenes Handle\nfindet dort keine Nachrichtentexte mehr, wo es sie bisher weiterfand, nachdem `nxc threads show` sie\ndemselben Aufrufer längst verweigerte. Zwei bewusste Grenzen bleiben und stehen jetzt in `nxc guide\ncommands`: Ein `public`-Kanal, dem Sie nie beigetreten sind, wird weiterhin nicht durchsucht, und die\nSuche folgt einem Vorgang weiterhin nicht über Kanalgrenzen, wie `nxc status` es tut. Für\nBibliotheks-Aufrufer: `ChatStore::search_messages` nimmt den Kanal-Suchraum (`&[String]`) statt eines\nHandles — die Mitgliedschaft entscheidet eine Schicht höher, in `nexus_chat::facade::search`, deren\nSignatur unverändert bleibt, ebenso die von `Engine::search` und die `--json`-Form.\n- Welcher Instanz des Hintergrunddienstes ein Befehl angehört, entscheidet jetzt die **laufende\nBinärdatei** — nicht das Verzeichnis, in dem er steht. `NXS_SERVICE_INSTANCE` wird von `direnv` für\neine ganze Arbeitskopie exportiert; bis jetzt wurden damit die installierten `nxs`, `nxf`, `nxm` und\n`nxc` aus Ihrem `PATH` allein dadurch zu Aufrufen einer Entwicklungsinstanz, dass sie dort liefen —\nund die so installierte Entwicklungsinstanz fuhr die *installierte* Binärdatei, hat also nie einen\nEntwicklungsstand getragen. Auf einer Maschine gemessen: drei angemeldete Dienste, alle drei auf\ndieselbe `~/.local/bin/nxs` zeigend. Jetzt ist eine installierte Binärdatei immer der `nexus-flow`\n-Dienst des Rechners selbst, und die Variable hat über sie keine Stimme; nur eine Binärdatei aus\n`target/debug` oder `target/release` ist eine Entwicklungsinstanz, und die Variable sagt, *welche*.\nZwei Folgen, die man kennen sollte: Installieren Sie den eigenen Dienst einer Arbeitskopie aus dem\nBau heraus (`./target/debug/nxs sync daemon install`) — das ist es, was die Verknüpfung dieser\nInstanz endlich auf die von Ihnen gebaute Datei richtet —, und ein `nxs sync bind` aus der\ninstallierten Binärdatei legt hinter Ihrem Rücken keinen Entwicklungsdienst mehr an. `nxs sync daemon\ninstall`/`uninstall` sagen es, in der Quittung und auf stderr, wenn die Variable eine Instanz genannt\nhat, der sie keine Stimme geben konnten; eine bereits installierte Instanz erreichen Sie weiterhin\nüber ihre eigene Verknüpfung (`~/.nexusflow-dev/bin/nexus-flow-dev uninstall` — die Verknüpfung\nsteht für `nxs sync daemon`) — derselbe Name, aus dem der laufende Dienst sich selbst liest.\n\n*Für Einbettende:* `nxs-service` bekommt `Origin`, und `Instance::resolve_from` nimmt es als drittes\nArgument entgegen. `Instance::ambient()`, `ServiceHome::resolve()` und alles darauf Gebaute behalten\nihre Signatur und antworten jetzt nach dieser Regel.\n\n### Facade-Kontrakt\n- `breaking` · Sicherheit · `nxc search` und `Engine::search` beantworten die Frage „bin ich in diesem Kanal überhaupt drin?\" nun\nso wie der Faden-Leser daneben: Bei einem **deklarierten** Kanal entscheidet die `members:`-Liste, in\nbeiden Richtungen und ohne ein `send` dazwischen. Drei Dinge ändern sich. Ein Mitglied, das in einem\ndeklarierten Kanal nur *geantwortet* hatte, bekam bisher auf alles „keine Treffer\" — auch auf seine\neigenen Worte: Ein Aufrufer kommt unter seiner qualifizierten `origin/handle`-Identität an, während\neine Deklaration ihre Mitglieder bar materialisiert, und die alte Suche verglich beides auf\nGleichheit; jetzt findet es seine Kanäle. Ein in `members:` eingetragenes Handle findet die Runden des\nKanals sofort und nicht erst, wenn wieder jemand sendet. Und ein aus `members:` gestrichenes Handle\nfindet dort keine Nachrichtentexte mehr, wo es sie bisher weiterfand, nachdem `nxc threads show` sie\ndemselben Aufrufer längst verweigerte. Zwei bewusste Grenzen bleiben und stehen jetzt in `nxc guide\ncommands`: Ein `public`-Kanal, dem Sie nie beigetreten sind, wird weiterhin nicht durchsucht, und die\nSuche folgt einem Vorgang weiterhin nicht über Kanalgrenzen, wie `nxc status` es tut. Für\nBibliotheks-Aufrufer: `ChatStore::search_messages` nimmt den Kanal-Suchraum (`&[String]`) statt eines\nHandles — die Mitgliedschaft entscheidet eine Schicht höher, in `nexus_chat::facade::search`, deren\nSignatur unverändert bleibt, ebenso die von `Engine::search` und die `--json`-Form.\n- `breaking` · `nxc status` beantwortet wieder seine eigene Frage: **läuft hier noch etwas?** Ein abgeschlossener\nVorgang steht nicht mehr in der Standardansicht. Bisher blieb er dort für immer — das Kennzeichen\n`awaiting_human` an der Wurzel zählte als „lebt\", und dieses Kennzeichen leitet sich aus zwei\nTatsachen ab, die nie aufhören zu gelten (die Wurzel hat gefragt, die Wurzel wurde beantwortet), also\nverließ nie etwas die Ansicht. Nach einem Arbeitstag in einem echten Arbeitsbereich waren es dreizehn\nVorgänge und 424 Zeilen, neun davon ohne einen einzigen offenen Faden — und wer die Ansicht benutzte,\nhatte sich um `--json` herum einen eigenen Wächter gebaut, weil sie ihre eigene Frage nicht mehr\nbeantwortete. In der Liste hält einen Vorgang jetzt das, woran ein Leser etwas tun kann: ein offener\nFaden, eine Rückgabe, die niemand aufgenommen hat, die Arbeitskopie dieses Geräts oder eine\ngescheiterte Sackgasse — eine Antwort, mit der nie etwas geschah, an der Vorgangszeile jetzt mit\n`· dead end` markiert. **`nxc status\n--all`** zeigt auch die abgeschlossenen, mit `· finished` markiert, und nimmt `--channel` mit\n(`--all --channel review` ist die Geschichte dieses Kanals); `--thread ` bleibt unverändert und\nzeigt weiterhin einen Vorgang, ob er läuft oder nicht. Und jeder Faden sagt jetzt, **was aus der\nSitzung wurde, die daran arbeitet** — `session_state` mit `running` / `ended` / `unknown`, direkt\nneben der `session`-ID, um die es geht: „wartet auf einen Menschen\" von „hängt\" zu unterscheiden\nkostet keinen zweiten Aufruf von `nxc session state` mehr. Im Terminal sagt ein offener Faden, dessen\nSitzung vorbei ist, das unmissverständlich. Wo eine Laufzeitumgebung die Prozessfrage überhaupt nicht\nbeantworten kann, sagt der Bericht das einmal als `worker_answers_liveness`, statt eine Spalte\n`unknown` stehen zu lassen, die falsch gelesen wird. Für Bibliotheks-Aufrufer: Die Signatur von\n`Engine::status` bleibt, seine beiden Listenformen geben aber weniger Vorgänge zurück;\n`nexus_chat::facade::status` nimmt als zweites Argument den `&dyn Worker` des Handles; `StatusScope`\nhat `All(Option<&str>)` bekommen und `StatusThread`/`StatusReport` die beiden Felder von oben." - } - }, - { - "version": "0.85.0", - "date": "2026-09-05", - "items": [ - { - "type": "fixed", - "en": "`nxc search` and `Engine::search` now apply a channel's declared `visibility`, exactly as the thread\nreader beside them does. On a channel declared `visibility: requester_only` the search used to hand\na member a message body that `nxc threads show` withheld from that same caller — two readers, two\nanswers to \"may I see this?\". `visibility` is an access rule, so the wider answer was a broken\nconfidentiality promise. You now find the request and your own words, never another member's answer;\nthe requester still sees everything, and a channel that declares `all_members` — or that no\ndeclaration names at all — is unaffected. Library callers: `nexus_chat::facade::search` takes the\ndeclared-channel catalogue as a fourth argument, and `ChatStore::MessageHit` carries the hit's\n`thread_id`; `Engine::search`'s signature and the `--json` shape are unchanged.", - "de": "`nxc search` und `Engine::search` wenden jetzt die deklarierte `visibility` eines Kanals an — genau\nwie der Faden-Leser daneben. In einem Kanal mit `visibility: requester_only` lieferte die Suche einem\nMitglied bisher einen Nachrichtentext, den `nxc threads show` demselben Aufrufer vorenthielt: zwei\nLeser, zwei Antworten auf „darf ich das sehen?\". `visibility` ist eine Zugriffsregel, die weitere\nAntwort war also ein gebrochenes Vertraulichkeitsversprechen. Sie finden nun die Anfrage und Ihre\neigenen Worte, nie die Antwort eines anderen Mitglieds; der Anfragende sieht weiterhin alles, und ein\nKanal mit `all_members` — oder einer, den keine Deklaration nennt — bleibt unverändert. Für\nBibliotheks-Aufrufer: `nexus_chat::facade::search` nimmt den Katalog der deklarierten Kanäle als\nviertes Argument, und `ChatStore::MessageHit` trägt die `thread_id` des Treffers; Signatur von\n`Engine::search` und die `--json`-Form bleiben unverändert.", - "facade": "breaking", - "security": true - } - ], - "notes": { - "en": "### Fixed\n- `nxc search` and `Engine::search` now apply a channel's declared `visibility`, exactly as the thread\nreader beside them does. On a channel declared `visibility: requester_only` the search used to hand\na member a message body that `nxc threads show` withheld from that same caller — two readers, two\nanswers to \"may I see this?\". `visibility` is an access rule, so the wider answer was a broken\nconfidentiality promise. You now find the request and your own words, never another member's answer;\nthe requester still sees everything, and a channel that declares `all_members` — or that no\ndeclaration names at all — is unaffected. Library callers: `nexus_chat::facade::search` takes the\ndeclared-channel catalogue as a fourth argument, and `ChatStore::MessageHit` carries the hit's\n`thread_id`; `Engine::search`'s signature and the `--json` shape are unchanged.\n\n### Facade Contract\n- `breaking` · security · `nxc search` and `Engine::search` now apply a channel's declared `visibility`, exactly as the thread\nreader beside them does. On a channel declared `visibility: requester_only` the search used to hand\na member a message body that `nxc threads show` withheld from that same caller — two readers, two\nanswers to \"may I see this?\". `visibility` is an access rule, so the wider answer was a broken\nconfidentiality promise. You now find the request and your own words, never another member's answer;\nthe requester still sees everything, and a channel that declares `all_members` — or that no\ndeclaration names at all — is unaffected. Library callers: `nexus_chat::facade::search` takes the\ndeclared-channel catalogue as a fourth argument, and `ChatStore::MessageHit` carries the hit's\n`thread_id`; `Engine::search`'s signature and the `--json` shape are unchanged.", - "de": "### Behoben\n- `nxc search` und `Engine::search` wenden jetzt die deklarierte `visibility` eines Kanals an — genau\nwie der Faden-Leser daneben. In einem Kanal mit `visibility: requester_only` lieferte die Suche einem\nMitglied bisher einen Nachrichtentext, den `nxc threads show` demselben Aufrufer vorenthielt: zwei\nLeser, zwei Antworten auf „darf ich das sehen?\". `visibility` ist eine Zugriffsregel, die weitere\nAntwort war also ein gebrochenes Vertraulichkeitsversprechen. Sie finden nun die Anfrage und Ihre\neigenen Worte, nie die Antwort eines anderen Mitglieds; der Anfragende sieht weiterhin alles, und ein\nKanal mit `all_members` — oder einer, den keine Deklaration nennt — bleibt unverändert. Für\nBibliotheks-Aufrufer: `nexus_chat::facade::search` nimmt den Katalog der deklarierten Kanäle als\nviertes Argument, und `ChatStore::MessageHit` trägt die `thread_id` des Treffers; Signatur von\n`Engine::search` und die `--json`-Form bleiben unverändert.\n\n### Facade-Kontrakt\n- `breaking` · Sicherheit · `nxc search` und `Engine::search` wenden jetzt die deklarierte `visibility` eines Kanals an — genau\nwie der Faden-Leser daneben. In einem Kanal mit `visibility: requester_only` lieferte die Suche einem\nMitglied bisher einen Nachrichtentext, den `nxc threads show` demselben Aufrufer vorenthielt: zwei\nLeser, zwei Antworten auf „darf ich das sehen?\". `visibility` ist eine Zugriffsregel, die weitere\nAntwort war also ein gebrochenes Vertraulichkeitsversprechen. Sie finden nun die Anfrage und Ihre\neigenen Worte, nie die Antwort eines anderen Mitglieds; der Anfragende sieht weiterhin alles, und ein\nKanal mit `all_members` — oder einer, den keine Deklaration nennt — bleibt unverändert. Für\nBibliotheks-Aufrufer: `nexus_chat::facade::search` nimmt den Katalog der deklarierten Kanäle als\nviertes Argument, und `ChatStore::MessageHit` trägt die `thread_id` des Treffers; Signatur von\n`Engine::search` und die `--json`-Form bleiben unverändert." - } - }, - { - "version": "0.84.0", - "date": "2026-09-05", - "items": [ - { - "type": "added", - "en": "An unanswered escalation no longer blocks the queue. When a role hands its task back and waits for a\nhuman while **another operation is standing in line**, the checkout is no longer held indefinitely:\nafter 30 minutes the operation's work — tracked changes and untracked files alike — is committed to a\nbranch, the tree goes back to the branch that operation started on, and the waiting operation starts.\nWhen the answer finally arrives, the operation is resumed on its own branch and told, in a fixed text,\nwhich branch and which commit its work is on and whether the branch or its base has moved on since.\nThe clock starts at **contention**, never at the escalation: with nobody waiting, nothing is ever\nparked. A tree mid-rebase or mid-merge is never parked either, and says so instead. `nxc tick`\nreports what it parked; a park that could not happen is reported as a `work_not_parked` warning.\n\n**Facade contract.** `TickReceipt` gains a public `parked` field, so a struct literal outside the\ncrate has to name it; `Worker` gains `working_copy()` with a default (nothing to implement);\n`ConsequenceClass` and `WakeSkipReason` each gain a variant, both already `#[non_exhaustive]`. The\nbehavioural half is the one no signature carries: `tick` can now commit and move a branch in the\nworking copy, and an answer to an escalation whose work was parked reports `woke: null` with\n`wake_skipped.reason = \"queued\"` where it always started a session before.", - "de": "Eine unbeantwortete Eskalation blockiert die Warteschlange nicht mehr. Gibt eine Rolle ihre Aufgabe\nzurück und wartet auf einen Menschen, **während ein anderer Vorgang ansteht**, wird der Arbeitsbaum\nnicht mehr unbegrenzt gehalten: nach 30 Minuten wird die Arbeit des Vorgangs — versionierte Änderungen\nwie unversionierte Dateien — auf einen Zweig committet, der Baum kehrt auf den Ursprungszweig dieses\nVorgangs zurück, und der wartende Vorgang startet. Kommt die Antwort später, wird der Vorgang auf\nseinem Zweig fortgesetzt und erfährt in einem festen Text, auf welchem Zweig und welchem Commit seine\nArbeit liegt und ob sich Zweig oder Basis inzwischen weiterbewegt haben. Die Frist beginnt bei\n**Kontention**, nie beim Eskalieren: steht niemand an, wird nie geparkt. Ein Baum mitten in Rebase\noder Merge wird ebenfalls nie geparkt, sondern sagt es. `nxc tick` berichtet, was geparkt wurde; ein\nParken, das nicht möglich war, erscheint als `work_not_parked`-Warnung.\n\n**Facade-Kontrakt.** `TickReceipt` bekommt das öffentliche Feld `parked`, ein Struct-Literal\naußerhalb des Crates muss es also nennen; `Worker` bekommt `working_copy()` mit Vorgabe (nichts zu\nimplementieren); `ConsequenceClass` und `WakeSkipReason` je eine Variante, beide bereits\n`#[non_exhaustive]`. Die verhaltensseitige Hälfte trägt keine Signatur: `tick` kann jetzt im\nArbeitsbaum committen und einen Zweig wechseln, und eine Antwort auf eine Eskalation mit geparkter\nArbeit meldet `woke: null` mit `wake_skipped.reason = \"queued\"`, wo vorher immer eine Sitzung\nstartete.", - "facade": "breaking" - } - ], - "notes": { - "en": "### Added\n- An unanswered escalation no longer blocks the queue. When a role hands its task back and waits for a\nhuman while **another operation is standing in line**, the checkout is no longer held indefinitely:\nafter 30 minutes the operation's work — tracked changes and untracked files alike — is committed to a\nbranch, the tree goes back to the branch that operation started on, and the waiting operation starts.\nWhen the answer finally arrives, the operation is resumed on its own branch and told, in a fixed text,\nwhich branch and which commit its work is on and whether the branch or its base has moved on since.\nThe clock starts at **contention**, never at the escalation: with nobody waiting, nothing is ever\nparked. A tree mid-rebase or mid-merge is never parked either, and says so instead. `nxc tick`\nreports what it parked; a park that could not happen is reported as a `work_not_parked` warning.\n\n**Facade contract.** `TickReceipt` gains a public `parked` field, so a struct literal outside the\ncrate has to name it; `Worker` gains `working_copy()` with a default (nothing to implement);\n`ConsequenceClass` and `WakeSkipReason` each gain a variant, both already `#[non_exhaustive]`. The\nbehavioural half is the one no signature carries: `tick` can now commit and move a branch in the\nworking copy, and an answer to an escalation whose work was parked reports `woke: null` with\n`wake_skipped.reason = \"queued\"` where it always started a session before.\n\n### Facade Contract\n- `breaking` · An unanswered escalation no longer blocks the queue. When a role hands its task back and waits for a\nhuman while **another operation is standing in line**, the checkout is no longer held indefinitely:\nafter 30 minutes the operation's work — tracked changes and untracked files alike — is committed to a\nbranch, the tree goes back to the branch that operation started on, and the waiting operation starts.\nWhen the answer finally arrives, the operation is resumed on its own branch and told, in a fixed text,\nwhich branch and which commit its work is on and whether the branch or its base has moved on since.\nThe clock starts at **contention**, never at the escalation: with nobody waiting, nothing is ever\nparked. A tree mid-rebase or mid-merge is never parked either, and says so instead. `nxc tick`\nreports what it parked; a park that could not happen is reported as a `work_not_parked` warning.\n\n**Facade contract.** `TickReceipt` gains a public `parked` field, so a struct literal outside the\ncrate has to name it; `Worker` gains `working_copy()` with a default (nothing to implement);\n`ConsequenceClass` and `WakeSkipReason` each gain a variant, both already `#[non_exhaustive]`. The\nbehavioural half is the one no signature carries: `tick` can now commit and move a branch in the\nworking copy, and an answer to an escalation whose work was parked reports `woke: null` with\n`wake_skipped.reason = \"queued\"` where it always started a session before.", - "de": "### Neu\n- Eine unbeantwortete Eskalation blockiert die Warteschlange nicht mehr. Gibt eine Rolle ihre Aufgabe\nzurück und wartet auf einen Menschen, **während ein anderer Vorgang ansteht**, wird der Arbeitsbaum\nnicht mehr unbegrenzt gehalten: nach 30 Minuten wird die Arbeit des Vorgangs — versionierte Änderungen\nwie unversionierte Dateien — auf einen Zweig committet, der Baum kehrt auf den Ursprungszweig dieses\nVorgangs zurück, und der wartende Vorgang startet. Kommt die Antwort später, wird der Vorgang auf\nseinem Zweig fortgesetzt und erfährt in einem festen Text, auf welchem Zweig und welchem Commit seine\nArbeit liegt und ob sich Zweig oder Basis inzwischen weiterbewegt haben. Die Frist beginnt bei\n**Kontention**, nie beim Eskalieren: steht niemand an, wird nie geparkt. Ein Baum mitten in Rebase\noder Merge wird ebenfalls nie geparkt, sondern sagt es. `nxc tick` berichtet, was geparkt wurde; ein\nParken, das nicht möglich war, erscheint als `work_not_parked`-Warnung.\n\n**Facade-Kontrakt.** `TickReceipt` bekommt das öffentliche Feld `parked`, ein Struct-Literal\naußerhalb des Crates muss es also nennen; `Worker` bekommt `working_copy()` mit Vorgabe (nichts zu\nimplementieren); `ConsequenceClass` und `WakeSkipReason` je eine Variante, beide bereits\n`#[non_exhaustive]`. Die verhaltensseitige Hälfte trägt keine Signatur: `tick` kann jetzt im\nArbeitsbaum committen und einen Zweig wechseln, und eine Antwort auf eine Eskalation mit geparkter\nArbeit meldet `woke: null` mit `wake_skipped.reason = \"queued\"`, wo vorher immer eine Sitzung\nstartete.\n\n### Facade-Kontrakt\n- `breaking` · Eine unbeantwortete Eskalation blockiert die Warteschlange nicht mehr. Gibt eine Rolle ihre Aufgabe\nzurück und wartet auf einen Menschen, **während ein anderer Vorgang ansteht**, wird der Arbeitsbaum\nnicht mehr unbegrenzt gehalten: nach 30 Minuten wird die Arbeit des Vorgangs — versionierte Änderungen\nwie unversionierte Dateien — auf einen Zweig committet, der Baum kehrt auf den Ursprungszweig dieses\nVorgangs zurück, und der wartende Vorgang startet. Kommt die Antwort später, wird der Vorgang auf\nseinem Zweig fortgesetzt und erfährt in einem festen Text, auf welchem Zweig und welchem Commit seine\nArbeit liegt und ob sich Zweig oder Basis inzwischen weiterbewegt haben. Die Frist beginnt bei\n**Kontention**, nie beim Eskalieren: steht niemand an, wird nie geparkt. Ein Baum mitten in Rebase\noder Merge wird ebenfalls nie geparkt, sondern sagt es. `nxc tick` berichtet, was geparkt wurde; ein\nParken, das nicht möglich war, erscheint als `work_not_parked`-Warnung.\n\n**Facade-Kontrakt.** `TickReceipt` bekommt das öffentliche Feld `parked`, ein Struct-Literal\naußerhalb des Crates muss es also nennen; `Worker` bekommt `working_copy()` mit Vorgabe (nichts zu\nimplementieren); `ConsequenceClass` und `WakeSkipReason` je eine Variante, beide bereits\n`#[non_exhaustive]`. Die verhaltensseitige Hälfte trägt keine Signatur: `tick` kann jetzt im\nArbeitsbaum committen und einen Zweig wechseln, und eine Antwort auf eine Eskalation mit geparkter\nArbeit meldet `woke: null` mit `wake_skipped.reason = \"queued\"`, wo vorher immer eine Sitzung\nstartete." - } - }, - { - "version": "0.83.0", - "date": "2026-09-04", - "items": [ - { - "type": "fixed", - "en": "`nxs sync daemon install` no longer fails with a bare `Bootstrap failed: 5: Input/output error` when it is run right after `nxs self-update`. The bootstrap is retried a bounded number of times (the old job is still being torn down at that moment), the result is read back from launchd instead of assumed, and a failure now names the precondition that was wrong — the plist, the program alias, the log directory, the free space, a disabled label, or another registration already holding the label — with launchctl's own words kept as the last line.\n`nxs sync daemon status` now reports what launchd actually holds under this instance's label, and says so by name when that registration was bootstrapped from a different plist or from one that no longer exists. That is the state that used to show only as \"stale heartbeat — the process is gone\": true, and silent about the cause. It also reports when no registration is loaded at all, which is what a half-finished install leaves behind.\nThe installer and the uninstaller now refuse to talk to launchd from a process whose `$HOME` is not the login session's, because such a registration outlives the directory it points into and blocks the real service from ever loading. If your machine redirects `$HOME` on purpose — a managed Mac, a roaming profile, security tooling — that refusal is wrong about you and `--allow-redirected-home` on `nxs sync daemon install`, `uninstall` and `status` proceeds anyway.", - "de": "`nxs sync daemon install` scheitert nicht mehr mit einem nackten `Bootstrap failed: 5: Input/output error`, wenn es direkt nach `nxs self-update` läuft. Der Bootstrap wird begrenzt wiederholt (in diesem Moment baut launchd den alten Job noch ab), das Ergebnis wird bei launchd zurückgelesen statt angenommen, und ein Fehlschlag benennt jetzt die Vorbedingung, die nicht stimmte — plist, Programm-Alias, Log-Verzeichnis, freier Platz, deaktiviertes Label oder eine fremde Registrierung, die das Label schon hält — mit launchctls eigenen Worten als letzter Zeile.\n`nxs sync daemon status` meldet jetzt, was launchd unter dem Label dieser Instanz tatsächlich hält, und benennt es, wenn diese Registrierung aus einer anderen plist stammt oder aus einer, die es nicht mehr gibt. Genau dieser Zustand erschien bisher nur als „stale heartbeat — the process is gone\": wahr, und stumm über die Ursache. Ebenso wird gemeldet, wenn gar keine Registrierung geladen ist — der Halbzustand, den ein abgebrochener Install hinterlässt.\nInstaller und Uninstaller verweigern jetzt die Arbeit, wenn das `$HOME` des Prozesses nicht das der Anmelde-Sitzung ist: eine so entstandene Registrierung überlebt das Verzeichnis, auf das sie zeigt, und hindert den echten Dienst dauerhaft am Laden. Wenn Ihre Maschine `$HOME` absichtlich umleitet — verwalteter Mac, Roaming-Profil, Sicherheits-Software —, liegt die Verweigerung bei Ihnen falsch: `--allow-redirected-home` an `nxs sync daemon install`, `uninstall` und `status` macht trotzdem weiter." - } - ], - "notes": { - "en": "### Fixed\n- `nxs sync daemon install` no longer fails with a bare `Bootstrap failed: 5: Input/output error` when it is run right after `nxs self-update`. The bootstrap is retried a bounded number of times (the old job is still being torn down at that moment), the result is read back from launchd instead of assumed, and a failure now names the precondition that was wrong — the plist, the program alias, the log directory, the free space, a disabled label, or another registration already holding the label — with launchctl's own words kept as the last line.\n`nxs sync daemon status` now reports what launchd actually holds under this instance's label, and says so by name when that registration was bootstrapped from a different plist or from one that no longer exists. That is the state that used to show only as \"stale heartbeat — the process is gone\": true, and silent about the cause. It also reports when no registration is loaded at all, which is what a half-finished install leaves behind.\nThe installer and the uninstaller now refuse to talk to launchd from a process whose `$HOME` is not the login session's, because such a registration outlives the directory it points into and blocks the real service from ever loading. If your machine redirects `$HOME` on purpose — a managed Mac, a roaming profile, security tooling — that refusal is wrong about you and `--allow-redirected-home` on `nxs sync daemon install`, `uninstall` and `status` proceeds anyway.", - "de": "### Behoben\n- `nxs sync daemon install` scheitert nicht mehr mit einem nackten `Bootstrap failed: 5: Input/output error`, wenn es direkt nach `nxs self-update` läuft. Der Bootstrap wird begrenzt wiederholt (in diesem Moment baut launchd den alten Job noch ab), das Ergebnis wird bei launchd zurückgelesen statt angenommen, und ein Fehlschlag benennt jetzt die Vorbedingung, die nicht stimmte — plist, Programm-Alias, Log-Verzeichnis, freier Platz, deaktiviertes Label oder eine fremde Registrierung, die das Label schon hält — mit launchctls eigenen Worten als letzter Zeile.\n`nxs sync daemon status` meldet jetzt, was launchd unter dem Label dieser Instanz tatsächlich hält, und benennt es, wenn diese Registrierung aus einer anderen plist stammt oder aus einer, die es nicht mehr gibt. Genau dieser Zustand erschien bisher nur als „stale heartbeat — the process is gone\": wahr, und stumm über die Ursache. Ebenso wird gemeldet, wenn gar keine Registrierung geladen ist — der Halbzustand, den ein abgebrochener Install hinterlässt.\nInstaller und Uninstaller verweigern jetzt die Arbeit, wenn das `$HOME` des Prozesses nicht das der Anmelde-Sitzung ist: eine so entstandene Registrierung überlebt das Verzeichnis, auf das sie zeigt, und hindert den echten Dienst dauerhaft am Laden. Wenn Ihre Maschine `$HOME` absichtlich umleitet — verwalteter Mac, Roaming-Profil, Sicherheits-Software —, liegt die Verweigerung bei Ihnen falsch: `--allow-redirected-home` an `nxs sync daemon install`, `uninstall` und `status` macht trotzdem weiter." - } - }, - { - "version": "0.82.0", - "date": "2026-09-04", - "items": [ - { - "type": "added", - "en": "**A persona that starts under changed rules says so.** `.nxs-personas/` is runtime configuration\nliving in the working copy the declared agents are told to work in, which is a feedback loop no\nother configuration in this system has: one persona's `git switch`, `git stash` or\n`git checkout -- .` changes how the next persona thinks — and until now nothing anywhere reported\nit. Measured, not suspected: three declarations were rolled back that way in the proving ground,\none of them the rule telling a coordinator to end every turn with an answer, after which 4 of 251\nthreads ended with the runtime answering in its place. It was found by holding a stored session\nspec against the file on disk, by hand.\n\nTwo things now make that visible, and neither of them is a lock — the files stay editable and\nversionable at every moment, because what has to be stable is a running operation, not the folder:\n\n- Every spawned session records the version of the declaration its prompt was composed from, as\n `declarationHash` in `.nxs/agent-logs/.spec.json`. \"Did this session run under the rule\n I wrote?\" is now a comparison instead of a text search through a prompt.\n- When a persona is started under a different declaration than the last time it ran, the receipt of\n the call that started it carries a `declaration_changed` warning naming the role and both\n versions — in `--json` and on stderr. That holds for every way a session gets started: a direct\n `send --to`, a channel handing a member its next turn, a chain hop resumed by a reply, and a\n commission the working-copy queue starts later — including the unattended sweep the background\n service runs, where the finding rides `tick`'s own receipt.\n\nIt watches the **persona's own file**. A `channels.yaml` rolled back the same way is not reported by\nit, and `nxc guide personas` now says so rather than letting \"the folder\" imply otherwise.\n\nIt is a **warning and not a refusal**, and it does not change the exit code: editing a persona and\nthen addressing it is the ordinary way to work. A change is usually intended; an unnoticed one\nnever is. `nxc guide personas` carries the whole account in a section of its own: why the folder\nbelongs in version control, and what happened here when it was not.\n\nFor consumers of the `nexus-chat` library surface this is a source break in two places:\n`worker::RoleSpec` gains a required `declaration_hash: Option` field, and\n`orchestration::TriggerAdmission::Spawned` becomes a struct variant carrying the finding\n(`declaration_changed`). `orchestration::FailedConsequence` gains an additive\n`declaration: Option` and `orchestration::Promotions` an additive\n`warnings: Vec`; both types are already unconstructible outside the crate, so\nonly an exhaustive struct pattern breaks.", - "de": "**Eine Persona, die unter geänderten Regeln startet, sagt es.** `.nxs-personas/` ist\nLaufzeitkonfiguration in genau der Arbeitskopie, in der die deklarierten Agenten arbeiten sollen —\neine Rückkopplung, die keine andere Konfiguration in diesem System hat: ein `git switch`, ein\n`git stash` oder ein `git checkout -- .` der einen Persona ändert, wie die nächste denkt, und bisher\nmeldete das nirgends jemand. Gemessen, nicht vermutet: im Prüfstand wurden drei Deklarationen auf\ndiesem Weg zurückgedreht, darunter die Regel, die einem Koordinator sagte, jeden Zug mit einer\nAntwort zu beenden — danach endeten 4 von 251 Fäden damit, dass die Laufzeit an seiner Stelle\nantwortete. Gefunden wurde es von Hand, durch einen Vergleich der gespeicherten Sitzungs-Spec mit\nder Datei auf der Platte.\n\nZwei Dinge machen das jetzt sichtbar, und keines davon ist eine Sperre — die Dateien bleiben\njederzeit editierbar und versionierbar, denn stabil sein muss ein laufender Vorgang, nicht der\nOrdner:\n\n- Jede gestartete Sitzung hält fest, aus welcher Fassung der Deklaration ihr Prompt gebaut wurde:\n als `declarationHash` in `.nxs/agent-logs/.spec.json`. „Lief diese Sitzung unter der\n Regel, die ich geschrieben habe?\" ist damit ein Vergleich statt einer Textsuche in einem Prompt.\n- Wird eine Persona unter einer anderen Deklaration gestartet als beim letzten Mal, trägt der\n Empfangsschein des Aufrufs eine `declaration_changed`-Warnung, die die Rolle und beide Fassungen\n nennt — im `--json` und auf stderr. Das gilt für jeden Weg, auf dem eine Sitzung startet: ein\n direktes `send --to`, ein Kanal, der einem Mitglied seinen nächsten Zug gibt, ein von einer Antwort\n fortgesetzter Kettenschritt und ein Auftrag, den die Arbeitskopie-Warteschlange später startet —\n auch beim unbeaufsichtigten Aufräumen des Hintergrunddienstes, wo der Befund auf dem\n `tick`-Empfangsschein mitfährt.\n\nBeobachtet wird **die Datei der Persona selbst**. Eine auf demselben Weg zurückgedrehte\n`channels.yaml` meldet das nicht, und `nxc guide personas` sagt das jetzt, statt es mit „dem Ordner\"\noffenzulassen.\n\nEs ist eine **Warnung und keine Verweigerung** und ändert den Exit-Code nicht: eine Persona zu\nändern und sie dann anzusprechen ist die gewöhnliche Arbeitsweise. Eine Änderung ist meistens\ngewollt, eine unbemerkte nie. `nxc guide personas` trägt den ganzen Vorgang in einem eigenen\nAbschnitt: warum der Ordner in die Versionsverwaltung gehört, und was hier passiert ist, als er es\nnicht war.\n\nFür Konsumenten der Bibliotheksoberfläche `nexus-chat` ist das an zwei Stellen ein Quellcode-Bruch:\n`worker::RoleSpec` bekommt ein pflichtiges Feld `declaration_hash: Option`, und\n`orchestration::TriggerAdmission::Spawned` wird zur Struct-Variante, die den Befund trägt\n(`declaration_changed`). `orchestration::FailedConsequence` bekommt additiv\n`declaration: Option`, `orchestration::Promotions` additiv\n`warnings: Vec` — beide Typen sind außerhalb der Crate ohnehin nicht\nkonstruierbar, es bricht also nur ein erschöpfendes Struct-Muster.", - "facade": "breaking" - }, - { - "type": "added", - "en": "`nxs guide a-reply-across-two-machines` is the second trip across the architecture, and it adds the\naxis the first one leaves out: two replicas. An agent ran on one machine and is owed an answer; you\nare at the other machine, and you answer there.\n\nIt follows what actually crosses. `nxc reply` appends message ops to the machine you are sitting at;\nthe relay moves them by anti-entropy — each side asking what the other holds that it lacks — and the\nreceiving machine folds them into its own `messages` view, which bumps `PRAGMA data_version` and\nticks every `subscribe()` on it. An app that is already running re-reads its inbox and carries on,\nwith no network call between you and it.\n\nAnd it is just as clear about what does **not** cross. The reply's receipt says `woke: null`, and\nthat is not a failed attempt: a thread's return address names a session, sessions live in a table\nsync deliberately never carries, and so the machine you typed on cannot even tell that somebody is\nwaiting. If you build on this, that is the line to keep — sync delivers your data, not your control\nflow.", - "de": "`nxs guide a-reply-across-two-machines` ist die zweite Fahrt über die Architektur und ergänzt die\nAchse, die der ersten fehlt: zwei Repliken. Auf einer Maschine lief ein Agent, dem eine Antwort\nzusteht; du sitzt an der anderen und antwortest dort.\n\nDie Seite verfolgt, was wirklich übergeht. `nxc reply` hängt Nachrichten-Ops an das Log der Maschine\nan, an der du sitzt; das Relay bewegt sie per Anti-Entropie — jede Seite fragt, was die andere hat\nund ihr fehlt — und die empfangende Maschine faltet sie in ihre eigene `messages`-Sicht, was\n`PRAGMA data_version` erhöht und jedes `subscribe()` darauf antickt. Eine bereits laufende App liest\nihren Posteingang neu und macht weiter, ohne einen Netzwerkaufruf zwischen dir und ihr.\n\nUnd sie ist ebenso deutlich darin, was **nicht** übergeht. Die Quittung der Antwort sagt\n`woke: null`, und das ist kein fehlgeschlagener Versuch: die Rückadresse eines Fadens benennt eine\nSession, Sessions liegen in einer Tabelle, die der Sync mit Absicht nie mitnimmt — die Maschine, an\nder du getippt hast, kann also gar nicht wissen, dass jemand wartet. Wer darauf aufbaut, sollte\ndiesen Satz behalten: der Sync liefert deine Daten, nicht deinen Kontrollfluss." - }, - { - "type": "added", - "en": "Each building block now introduces itself. The published documentation carries five new\ndocuments per language: an index for flow, memory, chat and develop — what the block is for, what\nthe first step inside it is, and where to go from there — plus a first step for developers, which\nis the one page `nxs guide` cannot serve because `nxs` already answers to `getting-started`.\n\nNone of them repeats the umbrella's setup. Installation, `nxs init` and what lands on disk are said\nonce, in `nxs getting-started`, and a test now holds every index to that: the five documents are\nread one after another on the docs start page, so a second installation section is the same\nparagraph twice on one page.\n\nThe feed they travel in learned two things at the same time. A block may now carry web-only\ndocuments beside the guides its CLI serves, from `content/docs-blocks/`, and every navigation entry\ncarries a one-line summary in both languages — the description each page will show in a search\nresult. The English half of every guide summary is held against the exact output of\n`nxs guide --json`, so the website and the command line cannot say two different things about the\nsame topic.", - "de": "Jeder Baustein stellt sich jetzt selbst vor. Die veröffentlichte Dokumentation trägt fünf neue\nDokumente je Sprache: eine Einleitung für flow, memory, chat und develop — wofür der Baustein da\nist, was der erste Schritt darin ist und wohin es von dort geht — dazu einen ersten Schritt für\nEntwickler, die eine Seite, die `nxs guide` nicht ausliefern kann, weil `nxs` auf\n`getting-started` bereits antwortet.\n\nKeine davon wiederholt die Einrichtung der Klammer. Installation, `nxs init` und was auf der Platte\nlandet, stehen einmal in `nxs getting-started`, und ein Test hält jede Einleitung daran fest: die\nfünf Dokumente werden auf der Doku-Startseite hintereinander gelesen, ein zweiter\nInstallationsabschnitt wäre also derselbe Absatz zweimal auf einer Seite.\n\nDer Feed, in dem sie reisen, hat zugleich zwei Dinge gelernt. Ein Baustein darf jetzt neben den\nAnleitungen, die seine CLI ausliefert, auch reine Web-Dokumente tragen — aus `content/docs-blocks/`\n—, und jeder Navigationseintrag trägt eine einzeilige Zusammenfassung in beiden Sprachen, die\nBeschreibung also, die jede Seite künftig in einem Suchergebnis zeigt. Die englische Hälfte jeder\nAnleitungs-Zusammenfassung wird gegen die exakte Ausgabe von `nxs guide --json` gehalten: Website\nund Kommandozeile können über dasselbe Thema nicht zweierlei sagen." - }, - { - "type": "fixed", - "en": "`nxf init` and `nxm init` now add the workspace they create to the list the background service\nattends, and say so — under `--json` as `\"service\": {\"registered\": true}`, and in the summary as a\n`service:` line. Only `nxs init` did this, and only `nxs init` is what a HUMAN on a terminal reaches:\n`nxf init --json` and `nxm init --json`, the way an agent sets a workspace up, ran the module's own\ninit and stopped there. Since deadlines are kept by that service, a workspace an agent created had\nno clock at all, and nothing said so. A later `nxs init` always healed it; nothing told anybody it\nwas needed. Registering is also safe to do at the same time from several places now: the\nlist is updated under a lock, so two workspaces registering at once can no longer drop one\nanother's entry.", - "de": "`nxf init` und `nxm init` tragen den Arbeitsbereich, den sie anlegen, jetzt selbst in die Liste ein,\ndie der Hintergrunddienst betreut — und sagen es: unter `--json` als\n`\"service\": {\"registered\": true}`, in der Zusammenfassung als `service:`-Zeile. Bisher tat das nur\n`nxs init`, und nur `nxs init` ist der Weg, den ein MENSCH am Terminal geht: `nxf init --json` und\n`nxm init --json` — so setzt ein Agent einen Arbeitsbereich auf — führten das init des Moduls aus\nund hörten dort auf. Da Fristen von diesem Dienst gehalten werden, hatte ein von einem Agenten\nangelegter Arbeitsbereich überhaupt keine Uhr, und niemand sagte es. Ein späteres `nxs init` heilte\nes immer; nur sagte niemand, dass es nötig war. Das Eintragen verträgt jetzt auch\nGleichzeitigkeit: Die Liste wird unter einer Sperre fortgeschrieben, zwei gleichzeitig\neingetragene Arbeitsbereiche können sich also nicht mehr gegenseitig überschreiben.", - "facade": "changed" - }, - { - "type": "added", - "en": "`nxs init` now brings a new workspace to the background service instead of leaving it to chance:\nevery `init` adds the workspace to the list the service attends (no stream binding involved), and\non a real terminal it offers to set the service up. The answer is remembered per workspace, so a\nre-run never asks twice; `--service` / `--no-service` settle it in a script, and `--json` or a pipe\nnever installs anything without one of them. Until now both only ever happened as a side effect of\n`nxs sync bind` — so a workspace that was never bound to a sync stream had no clock at all, and\nnothing said so.", - "de": "`nxs init` bringt einen neuen Arbeitsbereich jetzt selbst zum Hintergrunddienst, statt es dem Zufall\nzu überlassen: Jedes `init` trägt den Arbeitsbereich in die Liste ein, die der Dienst betreut (ohne\njede Strom-Bindung), und auf einem echten Terminal bietet es an, den Dienst einzurichten. Die\nAntwort wird je Arbeitsbereich festgehalten, ein erneutes `init` fragt also nie zweimal;\n`--service` / `--no-service` erledigen die Frage im Skript, und unter `--json` oder in einer Pipe\nwird ohne eines der beiden nichts installiert. Bisher geschah beides nur als Nebenwirkung von\n`nxs sync bind` — ein nie an einen Strom gebundener Arbeitsbereich hatte damit überhaupt keine Uhr,\nund niemand sagte es.", - "facade": "changed" - }, - { - "type": "changed", - "en": "The guides no longer print `ready` as though you could ask for it. There is no `nxf ready` — the\nlanes you can query are `next` and `blocked` — but the core-concepts page carried the heading\n\"Derivation: ready / blocked / next\" and listed all three in code font, so a reader learned a lane\nthat does not exist. Nothing caught it: the docs and the CLI were each consistent with themselves.\n\n*Ready* stays, because it is a real state an item is **in**, and the engine computes it under that\nname. What changed is where it may appear: never in code font, and never as a third item beside\n`next` and `blocked`. Backticks in these guides mean \"you can type this\".", - "de": "Die Guides drucken `ready` nicht mehr, als könnte man es abfragen. `nxf ready` gibt es nicht — die\nSpuren, die man erfragen kann, sind `next` und `blocked` —, doch die Kernkonzepte-Seite trug die\nÜberschrift „Ableitung: ready / blocked / next\" und führte alle drei in Code-Schrift auf. Ein Leser\nlernte damit eine Spur, die es nicht gibt. Aufgefallen ist das keinem Tor: Doku und CLI waren jede\nfür sich stimmig.\n\n*Ready* bleibt, denn es ist ein echter Zustand, in dem ein Item **ist**, und die Engine berechnet\nihn unter genau diesem Namen. Geändert hat sich, wo das Wort stehen darf: nie in Code-Schrift und\nnie als drittes neben `next` und `blocked`. Backticks heißen in diesen Guides „das kannst du\ntippen\"." - }, - { - "type": "fixed", - "en": "`nxs sync daemon status` no longer reports a working background service as stopped. Two things\nproduced that. A service that took a long time to start — a binary on a removable volume sits\nbehind a macOS permission dialog until somebody clicks it, measured at 69 minutes on one machine —\nrecorded its start as the moment it first wrote, not the moment its process began, and every later\nreading called it somebody else's process for as long as it ran; it now records its process's real\nstart, and a start recorded later than that is read as what it is, a service that stamped itself\nlate. And a process that merely HOLDS the recorded process id is now its own answer, `unconfirmed`\n(\"the service is gone and its id was handed on — that process is somebody else's\"), instead of\n\"NOT running (the process is gone)\", which sent one owner to reinstall a service that was syncing\nat the time. A heartbeat that could not be WRITTEN (a full disk) no longer disappears into a log\nnobody opens either: the service keeps running, counts the misses, and the next heartbeat that\nsucceeds carries when and why — so a gap in `last pass` says what caused it.\n\nFor anything compiling against `nxs-service` directly: `ServiceState` has a fourth variant,\n`Unconfirmed`, so an exhaustive match over it needs one more arm. Treat it the way this release\ndoes — like `Unknown` where refusing would block real work, and like a reportable fault where the\nquestion is only whether to warn.", - "de": "`nxs sync daemon status` meldet einen arbeitenden Hintergrunddienst nicht mehr als gestoppt. Zwei\nDinge führten dazu. Ein Dienst, der lange zum Starten brauchte — eine Binary auf einem\nWechselvolume steht hinter einem macOS-Berechtigungsdialog, bis jemand ihn wegklickt, auf einer\nMaschine mit 69 Minuten gemessen —, notierte als seinen Start den Moment seines ersten Schreibens\nstatt den Beginn seines Prozesses, und jede spätere Lesung hielt ihn für den gesamten Lauf für einen\nfremden Prozess; er notiert jetzt den echten Prozessstart, und ein später notierter Start wird als\ndas gelesen, was er ist: ein Dienst, der sich verspätet gestempelt hat. Und ein Prozess, der die\nvermerkte Prozessnummer nur HÄLT, ist jetzt eine eigene Antwort — `unconfirmed` („der Dienst ist\nfort und seine Nummer wurde weitergegeben, dieser Prozess gehört jemand anderem\") — statt „NOT\nrunning (the process is gone)\", was einen Besitzer dazu brachte, einen Dienst neu zu installieren,\nder gerade synchronisierte. Auch ein Herzschlag, der nicht GESCHRIEBEN werden konnte (volle Platte),\nverschwindet nicht mehr in einem Log, das niemand ansieht: Der Dienst läuft weiter, zählt die\nFehlschläge, und der nächste erfolgreiche Herzschlag trägt Zeitpunkt und Grund mit — eine Lücke in\n`last pass` sagt jetzt, woher sie kam.\n\nFür alles, was direkt gegen `nxs-service` kompiliert: `ServiceState` hat eine vierte Variante,\n`Unconfirmed`; ein erschöpfendes `match` darüber braucht einen Arm mehr. Behandeln Sie sie wie diese\nVersion es tut — wie `Unknown`, wo ein Verweigern echte Arbeit blockieren würde, und wie einen\nmeldbaren Fehler, wo es nur um eine Warnung geht.", - "facade": "changed" - }, - { - "type": "fixed", - "en": "The in-repo example app builds again, and it no longer says something the engine stopped backing.\n`Engine::next` had lost an argument since the example was last touched — so the example had not\ncompiled for seven weeks and eight releases. Worse than the build error: `next` also changed\nmeaning in that window (it is `ready ∪ in_progress` now, unconditionally), so a compile-only repair\nwould have left a window listing claimed work under a heading that said *Ready*. The app's list is\ncalled **Next** now, top to bottom — command, labels, window title, README.\n\nIt is also no longer named after an internal practice: `examples/tauri-dogfood` is\n`examples/tauri-board`, because it exists for anyone who wants to see how to build their own app on\nnexus-flow. Its headless counterpart moved with it, `crates/cli/tests/embed_dogfood.rs` →\n`crates/cli/tests/embedding.rs`.\n\nAnd it can no longer rot in silence: the example sits outside the cargo workspace on purpose, so no\ngate ever compiled it. A dedicated `cargo check` job now runs whenever the example — or the facade,\ncore or foundation it links — changes.", - "de": "Die Beispiel-Applikation im Repo baut wieder, und sie behauptet nichts mehr, was die Engine nicht\nmehr deckt. `Engine::next` hatte seit der letzten Berührung ein Argument verloren — das Beispiel\nübersetzte also sieben Wochen und acht Releases lang nicht. Schlimmer als der Baufehler: `next` hat\nin diesem Fenster auch seine Bedeutung geändert (es ist jetzt unbedingt `ready ∪ in_progress`), eine\nreine Compile-Reparatur hätte also ein Fenster hinterlassen, das angefangene Arbeit unter der\nÜberschrift *Ready* auflistet. Die Liste heißt jetzt durchgängig **Next** — Kommando, Beschriftungen,\nFenstertitel, README.\n\nSie ist außerdem nicht mehr nach einer internen Praxis benannt: aus `examples/tauri-dogfood` wird\n`examples/tauri-board`, denn sie ist für alle da, die sehen wollen, wie man eigene Anwendungen auf\nnexus-flow baut. Ihr kopfloser Zwilling ist mitgewandert, `crates/cli/tests/embed_dogfood.rs` →\n`crates/cli/tests/embedding.rs`.\n\nUnd sie kann nicht mehr still verrotten: das Beispiel liegt mit Absicht außerhalb des\ncargo-Workspace, also hat es nie ein Tor gebaut. Ein eigener `cargo check`-Job läuft jetzt, sobald\nsich das Beispiel — oder facade, core oder foundation, die es einbindet — ändert." - }, - { - "type": "added", - "en": "`nxs guide the-journey-of-one-op` follows a single `nxf close` all the way through. The\narchitecture topic that shipped with 0.81.0 is the map; this one is a trip across it: the verb, the\nengine seam an app calls, the three ops it appends, the fold into the `items` view, and out the\nother side — where `nxf next` gives a different answer that nobody wrote.\n\nIt ends on the evidence rather than the claim. The page prints the whole op-log for its two example\nitems, grouped by the command that appended each block, so you can see that nothing was ever\noverwritten — the row that says `in_progress` is still there, one row above the one that says\n`closed`. And it shows the CLI output of `nxf next` before and after the close: the same command,\nrun twice, with nothing but three appended rows in between.\n\nThat is the claim the architecture topic makes in one sentence — derivation instead of stored\nstate — and this is the page where you can check it.", - "de": "`nxs guide the-journey-of-one-op` verfolgt ein einzelnes `nxf close` von Anfang bis Ende. Das\nArchitektur-Thema aus 0.81.0 ist die Landkarte; dieses hier ist eine Fahrt darüber: das Verb, die\nEngine-Naht, die eine App genauso ruft, die drei Ops, die es anhängt, der Fold in die\n`items`-Sicht — und wieder hinaus, wo `nxf next` eine andere Antwort gibt, die niemand geschrieben\nhat.\n\nEs endet beim Beleg statt bei der Behauptung. Die Seite druckt das ganze op-Log ihrer zwei\nBeispiel-Items, gruppiert nach dem Befehl, der die jeweiligen Zeilen angehängt hat — man sieht also,\ndass nie etwas überschrieben wurde: die Zeile mit `in_progress` steht weiterhin da, eine Zeile über\nder mit `closed`. Und sie zeigt die CLI-Ausgabe von `nxf next` vor und nach dem Schließen:\ndasselbe Kommando, zweimal ausgeführt, dazwischen nichts als drei angehängte Zeilen.\n\nDas ist die Behauptung, die das Architektur-Thema in einem Satz aufstellt — Ableitung statt\ngespeichertem Zustand — und dies ist die Seite, auf der man sie nachprüfen kann." - } - ], - "notes": { - "en": "### Added\n- **A persona that starts under changed rules says so.** `.nxs-personas/` is runtime configuration\nliving in the working copy the declared agents are told to work in, which is a feedback loop no\nother configuration in this system has: one persona's `git switch`, `git stash` or\n`git checkout -- .` changes how the next persona thinks — and until now nothing anywhere reported\nit. Measured, not suspected: three declarations were rolled back that way in the proving ground,\none of them the rule telling a coordinator to end every turn with an answer, after which 4 of 251\nthreads ended with the runtime answering in its place. It was found by holding a stored session\nspec against the file on disk, by hand.\n\nTwo things now make that visible, and neither of them is a lock — the files stay editable and\nversionable at every moment, because what has to be stable is a running operation, not the folder:\n\n- Every spawned session records the version of the declaration its prompt was composed from, as\n `declarationHash` in `.nxs/agent-logs/.spec.json`. \"Did this session run under the rule\n I wrote?\" is now a comparison instead of a text search through a prompt.\n- When a persona is started under a different declaration than the last time it ran, the receipt of\n the call that started it carries a `declaration_changed` warning naming the role and both\n versions — in `--json` and on stderr. That holds for every way a session gets started: a direct\n `send --to`, a channel handing a member its next turn, a chain hop resumed by a reply, and a\n commission the working-copy queue starts later — including the unattended sweep the background\n service runs, where the finding rides `tick`'s own receipt.\n\nIt watches the **persona's own file**. A `channels.yaml` rolled back the same way is not reported by\nit, and `nxc guide personas` now says so rather than letting \"the folder\" imply otherwise.\n\nIt is a **warning and not a refusal**, and it does not change the exit code: editing a persona and\nthen addressing it is the ordinary way to work. A change is usually intended; an unnoticed one\nnever is. `nxc guide personas` carries the whole account in a section of its own: why the folder\nbelongs in version control, and what happened here when it was not.\n\nFor consumers of the `nexus-chat` library surface this is a source break in two places:\n`worker::RoleSpec` gains a required `declaration_hash: Option` field, and\n`orchestration::TriggerAdmission::Spawned` becomes a struct variant carrying the finding\n(`declaration_changed`). `orchestration::FailedConsequence` gains an additive\n`declaration: Option` and `orchestration::Promotions` an additive\n`warnings: Vec`; both types are already unconstructible outside the crate, so\nonly an exhaustive struct pattern breaks.\n- `nxs guide a-reply-across-two-machines` is the second trip across the architecture, and it adds the\naxis the first one leaves out: two replicas. An agent ran on one machine and is owed an answer; you\nare at the other machine, and you answer there.\n\nIt follows what actually crosses. `nxc reply` appends message ops to the machine you are sitting at;\nthe relay moves them by anti-entropy — each side asking what the other holds that it lacks — and the\nreceiving machine folds them into its own `messages` view, which bumps `PRAGMA data_version` and\nticks every `subscribe()` on it. An app that is already running re-reads its inbox and carries on,\nwith no network call between you and it.\n\nAnd it is just as clear about what does **not** cross. The reply's receipt says `woke: null`, and\nthat is not a failed attempt: a thread's return address names a session, sessions live in a table\nsync deliberately never carries, and so the machine you typed on cannot even tell that somebody is\nwaiting. If you build on this, that is the line to keep — sync delivers your data, not your control\nflow.\n- Each building block now introduces itself. The published documentation carries five new\ndocuments per language: an index for flow, memory, chat and develop — what the block is for, what\nthe first step inside it is, and where to go from there — plus a first step for developers, which\nis the one page `nxs guide` cannot serve because `nxs` already answers to `getting-started`.\n\nNone of them repeats the umbrella's setup. Installation, `nxs init` and what lands on disk are said\nonce, in `nxs getting-started`, and a test now holds every index to that: the five documents are\nread one after another on the docs start page, so a second installation section is the same\nparagraph twice on one page.\n\nThe feed they travel in learned two things at the same time. A block may now carry web-only\ndocuments beside the guides its CLI serves, from `content/docs-blocks/`, and every navigation entry\ncarries a one-line summary in both languages — the description each page will show in a search\nresult. The English half of every guide summary is held against the exact output of\n`nxs guide --json`, so the website and the command line cannot say two different things about the\nsame topic.\n- `nxs init` now brings a new workspace to the background service instead of leaving it to chance:\nevery `init` adds the workspace to the list the service attends (no stream binding involved), and\non a real terminal it offers to set the service up. The answer is remembered per workspace, so a\nre-run never asks twice; `--service` / `--no-service` settle it in a script, and `--json` or a pipe\nnever installs anything without one of them. Until now both only ever happened as a side effect of\n`nxs sync bind` — so a workspace that was never bound to a sync stream had no clock at all, and\nnothing said so.\n- `nxs guide the-journey-of-one-op` follows a single `nxf close` all the way through. The\narchitecture topic that shipped with 0.81.0 is the map; this one is a trip across it: the verb, the\nengine seam an app calls, the three ops it appends, the fold into the `items` view, and out the\nother side — where `nxf next` gives a different answer that nobody wrote.\n\nIt ends on the evidence rather than the claim. The page prints the whole op-log for its two example\nitems, grouped by the command that appended each block, so you can see that nothing was ever\noverwritten — the row that says `in_progress` is still there, one row above the one that says\n`closed`. And it shows the CLI output of `nxf next` before and after the close: the same command,\nrun twice, with nothing but three appended rows in between.\n\nThat is the claim the architecture topic makes in one sentence — derivation instead of stored\nstate — and this is the page where you can check it.\n\n### Changed\n- The guides no longer print `ready` as though you could ask for it. There is no `nxf ready` — the\nlanes you can query are `next` and `blocked` — but the core-concepts page carried the heading\n\"Derivation: ready / blocked / next\" and listed all three in code font, so a reader learned a lane\nthat does not exist. Nothing caught it: the docs and the CLI were each consistent with themselves.\n\n*Ready* stays, because it is a real state an item is **in**, and the engine computes it under that\nname. What changed is where it may appear: never in code font, and never as a third item beside\n`next` and `blocked`. Backticks in these guides mean \"you can type this\".\n\n### Fixed\n- `nxf init` and `nxm init` now add the workspace they create to the list the background service\nattends, and say so — under `--json` as `\"service\": {\"registered\": true}`, and in the summary as a\n`service:` line. Only `nxs init` did this, and only `nxs init` is what a HUMAN on a terminal reaches:\n`nxf init --json` and `nxm init --json`, the way an agent sets a workspace up, ran the module's own\ninit and stopped there. Since deadlines are kept by that service, a workspace an agent created had\nno clock at all, and nothing said so. A later `nxs init` always healed it; nothing told anybody it\nwas needed. Registering is also safe to do at the same time from several places now: the\nlist is updated under a lock, so two workspaces registering at once can no longer drop one\nanother's entry.\n- `nxs sync daemon status` no longer reports a working background service as stopped. Two things\nproduced that. A service that took a long time to start — a binary on a removable volume sits\nbehind a macOS permission dialog until somebody clicks it, measured at 69 minutes on one machine —\nrecorded its start as the moment it first wrote, not the moment its process began, and every later\nreading called it somebody else's process for as long as it ran; it now records its process's real\nstart, and a start recorded later than that is read as what it is, a service that stamped itself\nlate. And a process that merely HOLDS the recorded process id is now its own answer, `unconfirmed`\n(\"the service is gone and its id was handed on — that process is somebody else's\"), instead of\n\"NOT running (the process is gone)\", which sent one owner to reinstall a service that was syncing\nat the time. A heartbeat that could not be WRITTEN (a full disk) no longer disappears into a log\nnobody opens either: the service keeps running, counts the misses, and the next heartbeat that\nsucceeds carries when and why — so a gap in `last pass` says what caused it.\n\nFor anything compiling against `nxs-service` directly: `ServiceState` has a fourth variant,\n`Unconfirmed`, so an exhaustive match over it needs one more arm. Treat it the way this release\ndoes — like `Unknown` where refusing would block real work, and like a reportable fault where the\nquestion is only whether to warn.\n- The in-repo example app builds again, and it no longer says something the engine stopped backing.\n`Engine::next` had lost an argument since the example was last touched — so the example had not\ncompiled for seven weeks and eight releases. Worse than the build error: `next` also changed\nmeaning in that window (it is `ready ∪ in_progress` now, unconditionally), so a compile-only repair\nwould have left a window listing claimed work under a heading that said *Ready*. The app's list is\ncalled **Next** now, top to bottom — command, labels, window title, README.\n\nIt is also no longer named after an internal practice: `examples/tauri-dogfood` is\n`examples/tauri-board`, because it exists for anyone who wants to see how to build their own app on\nnexus-flow. Its headless counterpart moved with it, `crates/cli/tests/embed_dogfood.rs` →\n`crates/cli/tests/embedding.rs`.\n\nAnd it can no longer rot in silence: the example sits outside the cargo workspace on purpose, so no\ngate ever compiled it. A dedicated `cargo check` job now runs whenever the example — or the facade,\ncore or foundation it links — changes.\n\n### Facade Contract\n- `breaking` · **A persona that starts under changed rules says so.** `.nxs-personas/` is runtime configuration\nliving in the working copy the declared agents are told to work in, which is a feedback loop no\nother configuration in this system has: one persona's `git switch`, `git stash` or\n`git checkout -- .` changes how the next persona thinks — and until now nothing anywhere reported\nit. Measured, not suspected: three declarations were rolled back that way in the proving ground,\none of them the rule telling a coordinator to end every turn with an answer, after which 4 of 251\nthreads ended with the runtime answering in its place. It was found by holding a stored session\nspec against the file on disk, by hand.\n\nTwo things now make that visible, and neither of them is a lock — the files stay editable and\nversionable at every moment, because what has to be stable is a running operation, not the folder:\n\n- Every spawned session records the version of the declaration its prompt was composed from, as\n `declarationHash` in `.nxs/agent-logs/.spec.json`. \"Did this session run under the rule\n I wrote?\" is now a comparison instead of a text search through a prompt.\n- When a persona is started under a different declaration than the last time it ran, the receipt of\n the call that started it carries a `declaration_changed` warning naming the role and both\n versions — in `--json` and on stderr. That holds for every way a session gets started: a direct\n `send --to`, a channel handing a member its next turn, a chain hop resumed by a reply, and a\n commission the working-copy queue starts later — including the unattended sweep the background\n service runs, where the finding rides `tick`'s own receipt.\n\nIt watches the **persona's own file**. A `channels.yaml` rolled back the same way is not reported by\nit, and `nxc guide personas` now says so rather than letting \"the folder\" imply otherwise.\n\nIt is a **warning and not a refusal**, and it does not change the exit code: editing a persona and\nthen addressing it is the ordinary way to work. A change is usually intended; an unnoticed one\nnever is. `nxc guide personas` carries the whole account in a section of its own: why the folder\nbelongs in version control, and what happened here when it was not.\n\nFor consumers of the `nexus-chat` library surface this is a source break in two places:\n`worker::RoleSpec` gains a required `declaration_hash: Option` field, and\n`orchestration::TriggerAdmission::Spawned` becomes a struct variant carrying the finding\n(`declaration_changed`). `orchestration::FailedConsequence` gains an additive\n`declaration: Option` and `orchestration::Promotions` an additive\n`warnings: Vec`; both types are already unconstructible outside the crate, so\nonly an exhaustive struct pattern breaks.\n- `changed` · `nxf init` and `nxm init` now add the workspace they create to the list the background service\nattends, and say so — under `--json` as `\"service\": {\"registered\": true}`, and in the summary as a\n`service:` line. Only `nxs init` did this, and only `nxs init` is what a HUMAN on a terminal reaches:\n`nxf init --json` and `nxm init --json`, the way an agent sets a workspace up, ran the module's own\ninit and stopped there. Since deadlines are kept by that service, a workspace an agent created had\nno clock at all, and nothing said so. A later `nxs init` always healed it; nothing told anybody it\nwas needed. Registering is also safe to do at the same time from several places now: the\nlist is updated under a lock, so two workspaces registering at once can no longer drop one\nanother's entry.\n- `changed` · `nxs init` now brings a new workspace to the background service instead of leaving it to chance:\nevery `init` adds the workspace to the list the service attends (no stream binding involved), and\non a real terminal it offers to set the service up. The answer is remembered per workspace, so a\nre-run never asks twice; `--service` / `--no-service` settle it in a script, and `--json` or a pipe\nnever installs anything without one of them. Until now both only ever happened as a side effect of\n`nxs sync bind` — so a workspace that was never bound to a sync stream had no clock at all, and\nnothing said so.\n- `changed` · `nxs sync daemon status` no longer reports a working background service as stopped. Two things\nproduced that. A service that took a long time to start — a binary on a removable volume sits\nbehind a macOS permission dialog until somebody clicks it, measured at 69 minutes on one machine —\nrecorded its start as the moment it first wrote, not the moment its process began, and every later\nreading called it somebody else's process for as long as it ran; it now records its process's real\nstart, and a start recorded later than that is read as what it is, a service that stamped itself\nlate. And a process that merely HOLDS the recorded process id is now its own answer, `unconfirmed`\n(\"the service is gone and its id was handed on — that process is somebody else's\"), instead of\n\"NOT running (the process is gone)\", which sent one owner to reinstall a service that was syncing\nat the time. A heartbeat that could not be WRITTEN (a full disk) no longer disappears into a log\nnobody opens either: the service keeps running, counts the misses, and the next heartbeat that\nsucceeds carries when and why — so a gap in `last pass` says what caused it.\n\nFor anything compiling against `nxs-service` directly: `ServiceState` has a fourth variant,\n`Unconfirmed`, so an exhaustive match over it needs one more arm. Treat it the way this release\ndoes — like `Unknown` where refusing would block real work, and like a reportable fault where the\nquestion is only whether to warn.", - "de": "### Neu\n- **Eine Persona, die unter geänderten Regeln startet, sagt es.** `.nxs-personas/` ist\nLaufzeitkonfiguration in genau der Arbeitskopie, in der die deklarierten Agenten arbeiten sollen —\neine Rückkopplung, die keine andere Konfiguration in diesem System hat: ein `git switch`, ein\n`git stash` oder ein `git checkout -- .` der einen Persona ändert, wie die nächste denkt, und bisher\nmeldete das nirgends jemand. Gemessen, nicht vermutet: im Prüfstand wurden drei Deklarationen auf\ndiesem Weg zurückgedreht, darunter die Regel, die einem Koordinator sagte, jeden Zug mit einer\nAntwort zu beenden — danach endeten 4 von 251 Fäden damit, dass die Laufzeit an seiner Stelle\nantwortete. Gefunden wurde es von Hand, durch einen Vergleich der gespeicherten Sitzungs-Spec mit\nder Datei auf der Platte.\n\nZwei Dinge machen das jetzt sichtbar, und keines davon ist eine Sperre — die Dateien bleiben\njederzeit editierbar und versionierbar, denn stabil sein muss ein laufender Vorgang, nicht der\nOrdner:\n\n- Jede gestartete Sitzung hält fest, aus welcher Fassung der Deklaration ihr Prompt gebaut wurde:\n als `declarationHash` in `.nxs/agent-logs/.spec.json`. „Lief diese Sitzung unter der\n Regel, die ich geschrieben habe?\" ist damit ein Vergleich statt einer Textsuche in einem Prompt.\n- Wird eine Persona unter einer anderen Deklaration gestartet als beim letzten Mal, trägt der\n Empfangsschein des Aufrufs eine `declaration_changed`-Warnung, die die Rolle und beide Fassungen\n nennt — im `--json` und auf stderr. Das gilt für jeden Weg, auf dem eine Sitzung startet: ein\n direktes `send --to`, ein Kanal, der einem Mitglied seinen nächsten Zug gibt, ein von einer Antwort\n fortgesetzter Kettenschritt und ein Auftrag, den die Arbeitskopie-Warteschlange später startet —\n auch beim unbeaufsichtigten Aufräumen des Hintergrunddienstes, wo der Befund auf dem\n `tick`-Empfangsschein mitfährt.\n\nBeobachtet wird **die Datei der Persona selbst**. Eine auf demselben Weg zurückgedrehte\n`channels.yaml` meldet das nicht, und `nxc guide personas` sagt das jetzt, statt es mit „dem Ordner\"\noffenzulassen.\n\nEs ist eine **Warnung und keine Verweigerung** und ändert den Exit-Code nicht: eine Persona zu\nändern und sie dann anzusprechen ist die gewöhnliche Arbeitsweise. Eine Änderung ist meistens\ngewollt, eine unbemerkte nie. `nxc guide personas` trägt den ganzen Vorgang in einem eigenen\nAbschnitt: warum der Ordner in die Versionsverwaltung gehört, und was hier passiert ist, als er es\nnicht war.\n\nFür Konsumenten der Bibliotheksoberfläche `nexus-chat` ist das an zwei Stellen ein Quellcode-Bruch:\n`worker::RoleSpec` bekommt ein pflichtiges Feld `declaration_hash: Option`, und\n`orchestration::TriggerAdmission::Spawned` wird zur Struct-Variante, die den Befund trägt\n(`declaration_changed`). `orchestration::FailedConsequence` bekommt additiv\n`declaration: Option`, `orchestration::Promotions` additiv\n`warnings: Vec` — beide Typen sind außerhalb der Crate ohnehin nicht\nkonstruierbar, es bricht also nur ein erschöpfendes Struct-Muster.\n- `nxs guide a-reply-across-two-machines` ist die zweite Fahrt über die Architektur und ergänzt die\nAchse, die der ersten fehlt: zwei Repliken. Auf einer Maschine lief ein Agent, dem eine Antwort\nzusteht; du sitzt an der anderen und antwortest dort.\n\nDie Seite verfolgt, was wirklich übergeht. `nxc reply` hängt Nachrichten-Ops an das Log der Maschine\nan, an der du sitzt; das Relay bewegt sie per Anti-Entropie — jede Seite fragt, was die andere hat\nund ihr fehlt — und die empfangende Maschine faltet sie in ihre eigene `messages`-Sicht, was\n`PRAGMA data_version` erhöht und jedes `subscribe()` darauf antickt. Eine bereits laufende App liest\nihren Posteingang neu und macht weiter, ohne einen Netzwerkaufruf zwischen dir und ihr.\n\nUnd sie ist ebenso deutlich darin, was **nicht** übergeht. Die Quittung der Antwort sagt\n`woke: null`, und das ist kein fehlgeschlagener Versuch: die Rückadresse eines Fadens benennt eine\nSession, Sessions liegen in einer Tabelle, die der Sync mit Absicht nie mitnimmt — die Maschine, an\nder du getippt hast, kann also gar nicht wissen, dass jemand wartet. Wer darauf aufbaut, sollte\ndiesen Satz behalten: der Sync liefert deine Daten, nicht deinen Kontrollfluss.\n- Jeder Baustein stellt sich jetzt selbst vor. Die veröffentlichte Dokumentation trägt fünf neue\nDokumente je Sprache: eine Einleitung für flow, memory, chat und develop — wofür der Baustein da\nist, was der erste Schritt darin ist und wohin es von dort geht — dazu einen ersten Schritt für\nEntwickler, die eine Seite, die `nxs guide` nicht ausliefern kann, weil `nxs` auf\n`getting-started` bereits antwortet.\n\nKeine davon wiederholt die Einrichtung der Klammer. Installation, `nxs init` und was auf der Platte\nlandet, stehen einmal in `nxs getting-started`, und ein Test hält jede Einleitung daran fest: die\nfünf Dokumente werden auf der Doku-Startseite hintereinander gelesen, ein zweiter\nInstallationsabschnitt wäre also derselbe Absatz zweimal auf einer Seite.\n\nDer Feed, in dem sie reisen, hat zugleich zwei Dinge gelernt. Ein Baustein darf jetzt neben den\nAnleitungen, die seine CLI ausliefert, auch reine Web-Dokumente tragen — aus `content/docs-blocks/`\n—, und jeder Navigationseintrag trägt eine einzeilige Zusammenfassung in beiden Sprachen, die\nBeschreibung also, die jede Seite künftig in einem Suchergebnis zeigt. Die englische Hälfte jeder\nAnleitungs-Zusammenfassung wird gegen die exakte Ausgabe von `nxs guide --json` gehalten: Website\nund Kommandozeile können über dasselbe Thema nicht zweierlei sagen.\n- `nxs init` bringt einen neuen Arbeitsbereich jetzt selbst zum Hintergrunddienst, statt es dem Zufall\nzu überlassen: Jedes `init` trägt den Arbeitsbereich in die Liste ein, die der Dienst betreut (ohne\njede Strom-Bindung), und auf einem echten Terminal bietet es an, den Dienst einzurichten. Die\nAntwort wird je Arbeitsbereich festgehalten, ein erneutes `init` fragt also nie zweimal;\n`--service` / `--no-service` erledigen die Frage im Skript, und unter `--json` oder in einer Pipe\nwird ohne eines der beiden nichts installiert. Bisher geschah beides nur als Nebenwirkung von\n`nxs sync bind` — ein nie an einen Strom gebundener Arbeitsbereich hatte damit überhaupt keine Uhr,\nund niemand sagte es.\n- `nxs guide the-journey-of-one-op` verfolgt ein einzelnes `nxf close` von Anfang bis Ende. Das\nArchitektur-Thema aus 0.81.0 ist die Landkarte; dieses hier ist eine Fahrt darüber: das Verb, die\nEngine-Naht, die eine App genauso ruft, die drei Ops, die es anhängt, der Fold in die\n`items`-Sicht — und wieder hinaus, wo `nxf next` eine andere Antwort gibt, die niemand geschrieben\nhat.\n\nEs endet beim Beleg statt bei der Behauptung. Die Seite druckt das ganze op-Log ihrer zwei\nBeispiel-Items, gruppiert nach dem Befehl, der die jeweiligen Zeilen angehängt hat — man sieht also,\ndass nie etwas überschrieben wurde: die Zeile mit `in_progress` steht weiterhin da, eine Zeile über\nder mit `closed`. Und sie zeigt die CLI-Ausgabe von `nxf next` vor und nach dem Schließen:\ndasselbe Kommando, zweimal ausgeführt, dazwischen nichts als drei angehängte Zeilen.\n\nDas ist die Behauptung, die das Architektur-Thema in einem Satz aufstellt — Ableitung statt\ngespeichertem Zustand — und dies ist die Seite, auf der man sie nachprüfen kann.\n\n### Geändert\n- Die Guides drucken `ready` nicht mehr, als könnte man es abfragen. `nxf ready` gibt es nicht — die\nSpuren, die man erfragen kann, sind `next` und `blocked` —, doch die Kernkonzepte-Seite trug die\nÜberschrift „Ableitung: ready / blocked / next\" und führte alle drei in Code-Schrift auf. Ein Leser\nlernte damit eine Spur, die es nicht gibt. Aufgefallen ist das keinem Tor: Doku und CLI waren jede\nfür sich stimmig.\n\n*Ready* bleibt, denn es ist ein echter Zustand, in dem ein Item **ist**, und die Engine berechnet\nihn unter genau diesem Namen. Geändert hat sich, wo das Wort stehen darf: nie in Code-Schrift und\nnie als drittes neben `next` und `blocked`. Backticks heißen in diesen Guides „das kannst du\ntippen\".\n\n### Behoben\n- `nxf init` und `nxm init` tragen den Arbeitsbereich, den sie anlegen, jetzt selbst in die Liste ein,\ndie der Hintergrunddienst betreut — und sagen es: unter `--json` als\n`\"service\": {\"registered\": true}`, in der Zusammenfassung als `service:`-Zeile. Bisher tat das nur\n`nxs init`, und nur `nxs init` ist der Weg, den ein MENSCH am Terminal geht: `nxf init --json` und\n`nxm init --json` — so setzt ein Agent einen Arbeitsbereich auf — führten das init des Moduls aus\nund hörten dort auf. Da Fristen von diesem Dienst gehalten werden, hatte ein von einem Agenten\nangelegter Arbeitsbereich überhaupt keine Uhr, und niemand sagte es. Ein späteres `nxs init` heilte\nes immer; nur sagte niemand, dass es nötig war. Das Eintragen verträgt jetzt auch\nGleichzeitigkeit: Die Liste wird unter einer Sperre fortgeschrieben, zwei gleichzeitig\neingetragene Arbeitsbereiche können sich also nicht mehr gegenseitig überschreiben.\n- `nxs sync daemon status` meldet einen arbeitenden Hintergrunddienst nicht mehr als gestoppt. Zwei\nDinge führten dazu. Ein Dienst, der lange zum Starten brauchte — eine Binary auf einem\nWechselvolume steht hinter einem macOS-Berechtigungsdialog, bis jemand ihn wegklickt, auf einer\nMaschine mit 69 Minuten gemessen —, notierte als seinen Start den Moment seines ersten Schreibens\nstatt den Beginn seines Prozesses, und jede spätere Lesung hielt ihn für den gesamten Lauf für einen\nfremden Prozess; er notiert jetzt den echten Prozessstart, und ein später notierter Start wird als\ndas gelesen, was er ist: ein Dienst, der sich verspätet gestempelt hat. Und ein Prozess, der die\nvermerkte Prozessnummer nur HÄLT, ist jetzt eine eigene Antwort — `unconfirmed` („der Dienst ist\nfort und seine Nummer wurde weitergegeben, dieser Prozess gehört jemand anderem\") — statt „NOT\nrunning (the process is gone)\", was einen Besitzer dazu brachte, einen Dienst neu zu installieren,\nder gerade synchronisierte. Auch ein Herzschlag, der nicht GESCHRIEBEN werden konnte (volle Platte),\nverschwindet nicht mehr in einem Log, das niemand ansieht: Der Dienst läuft weiter, zählt die\nFehlschläge, und der nächste erfolgreiche Herzschlag trägt Zeitpunkt und Grund mit — eine Lücke in\n`last pass` sagt jetzt, woher sie kam.\n\nFür alles, was direkt gegen `nxs-service` kompiliert: `ServiceState` hat eine vierte Variante,\n`Unconfirmed`; ein erschöpfendes `match` darüber braucht einen Arm mehr. Behandeln Sie sie wie diese\nVersion es tut — wie `Unknown`, wo ein Verweigern echte Arbeit blockieren würde, und wie einen\nmeldbaren Fehler, wo es nur um eine Warnung geht.\n- Die Beispiel-Applikation im Repo baut wieder, und sie behauptet nichts mehr, was die Engine nicht\nmehr deckt. `Engine::next` hatte seit der letzten Berührung ein Argument verloren — das Beispiel\nübersetzte also sieben Wochen und acht Releases lang nicht. Schlimmer als der Baufehler: `next` hat\nin diesem Fenster auch seine Bedeutung geändert (es ist jetzt unbedingt `ready ∪ in_progress`), eine\nreine Compile-Reparatur hätte also ein Fenster hinterlassen, das angefangene Arbeit unter der\nÜberschrift *Ready* auflistet. Die Liste heißt jetzt durchgängig **Next** — Kommando, Beschriftungen,\nFenstertitel, README.\n\nSie ist außerdem nicht mehr nach einer internen Praxis benannt: aus `examples/tauri-dogfood` wird\n`examples/tauri-board`, denn sie ist für alle da, die sehen wollen, wie man eigene Anwendungen auf\nnexus-flow baut. Ihr kopfloser Zwilling ist mitgewandert, `crates/cli/tests/embed_dogfood.rs` →\n`crates/cli/tests/embedding.rs`.\n\nUnd sie kann nicht mehr still verrotten: das Beispiel liegt mit Absicht außerhalb des\ncargo-Workspace, also hat es nie ein Tor gebaut. Ein eigener `cargo check`-Job läuft jetzt, sobald\nsich das Beispiel — oder facade, core oder foundation, die es einbindet — ändert.\n\n### Facade-Kontrakt\n- `breaking` · **Eine Persona, die unter geänderten Regeln startet, sagt es.** `.nxs-personas/` ist\nLaufzeitkonfiguration in genau der Arbeitskopie, in der die deklarierten Agenten arbeiten sollen —\neine Rückkopplung, die keine andere Konfiguration in diesem System hat: ein `git switch`, ein\n`git stash` oder ein `git checkout -- .` der einen Persona ändert, wie die nächste denkt, und bisher\nmeldete das nirgends jemand. Gemessen, nicht vermutet: im Prüfstand wurden drei Deklarationen auf\ndiesem Weg zurückgedreht, darunter die Regel, die einem Koordinator sagte, jeden Zug mit einer\nAntwort zu beenden — danach endeten 4 von 251 Fäden damit, dass die Laufzeit an seiner Stelle\nantwortete. Gefunden wurde es von Hand, durch einen Vergleich der gespeicherten Sitzungs-Spec mit\nder Datei auf der Platte.\n\nZwei Dinge machen das jetzt sichtbar, und keines davon ist eine Sperre — die Dateien bleiben\njederzeit editierbar und versionierbar, denn stabil sein muss ein laufender Vorgang, nicht der\nOrdner:\n\n- Jede gestartete Sitzung hält fest, aus welcher Fassung der Deklaration ihr Prompt gebaut wurde:\n als `declarationHash` in `.nxs/agent-logs/.spec.json`. „Lief diese Sitzung unter der\n Regel, die ich geschrieben habe?\" ist damit ein Vergleich statt einer Textsuche in einem Prompt.\n- Wird eine Persona unter einer anderen Deklaration gestartet als beim letzten Mal, trägt der\n Empfangsschein des Aufrufs eine `declaration_changed`-Warnung, die die Rolle und beide Fassungen\n nennt — im `--json` und auf stderr. Das gilt für jeden Weg, auf dem eine Sitzung startet: ein\n direktes `send --to`, ein Kanal, der einem Mitglied seinen nächsten Zug gibt, ein von einer Antwort\n fortgesetzter Kettenschritt und ein Auftrag, den die Arbeitskopie-Warteschlange später startet —\n auch beim unbeaufsichtigten Aufräumen des Hintergrunddienstes, wo der Befund auf dem\n `tick`-Empfangsschein mitfährt.\n\nBeobachtet wird **die Datei der Persona selbst**. Eine auf demselben Weg zurückgedrehte\n`channels.yaml` meldet das nicht, und `nxc guide personas` sagt das jetzt, statt es mit „dem Ordner\"\noffenzulassen.\n\nEs ist eine **Warnung und keine Verweigerung** und ändert den Exit-Code nicht: eine Persona zu\nändern und sie dann anzusprechen ist die gewöhnliche Arbeitsweise. Eine Änderung ist meistens\ngewollt, eine unbemerkte nie. `nxc guide personas` trägt den ganzen Vorgang in einem eigenen\nAbschnitt: warum der Ordner in die Versionsverwaltung gehört, und was hier passiert ist, als er es\nnicht war.\n\nFür Konsumenten der Bibliotheksoberfläche `nexus-chat` ist das an zwei Stellen ein Quellcode-Bruch:\n`worker::RoleSpec` bekommt ein pflichtiges Feld `declaration_hash: Option`, und\n`orchestration::TriggerAdmission::Spawned` wird zur Struct-Variante, die den Befund trägt\n(`declaration_changed`). `orchestration::FailedConsequence` bekommt additiv\n`declaration: Option`, `orchestration::Promotions` additiv\n`warnings: Vec` — beide Typen sind außerhalb der Crate ohnehin nicht\nkonstruierbar, es bricht also nur ein erschöpfendes Struct-Muster.\n- `changed` · `nxf init` und `nxm init` tragen den Arbeitsbereich, den sie anlegen, jetzt selbst in die Liste ein,\ndie der Hintergrunddienst betreut — und sagen es: unter `--json` als\n`\"service\": {\"registered\": true}`, in der Zusammenfassung als `service:`-Zeile. Bisher tat das nur\n`nxs init`, und nur `nxs init` ist der Weg, den ein MENSCH am Terminal geht: `nxf init --json` und\n`nxm init --json` — so setzt ein Agent einen Arbeitsbereich auf — führten das init des Moduls aus\nund hörten dort auf. Da Fristen von diesem Dienst gehalten werden, hatte ein von einem Agenten\nangelegter Arbeitsbereich überhaupt keine Uhr, und niemand sagte es. Ein späteres `nxs init` heilte\nes immer; nur sagte niemand, dass es nötig war. Das Eintragen verträgt jetzt auch\nGleichzeitigkeit: Die Liste wird unter einer Sperre fortgeschrieben, zwei gleichzeitig\neingetragene Arbeitsbereiche können sich also nicht mehr gegenseitig überschreiben.\n- `changed` · `nxs init` bringt einen neuen Arbeitsbereich jetzt selbst zum Hintergrunddienst, statt es dem Zufall\nzu überlassen: Jedes `init` trägt den Arbeitsbereich in die Liste ein, die der Dienst betreut (ohne\njede Strom-Bindung), und auf einem echten Terminal bietet es an, den Dienst einzurichten. Die\nAntwort wird je Arbeitsbereich festgehalten, ein erneutes `init` fragt also nie zweimal;\n`--service` / `--no-service` erledigen die Frage im Skript, und unter `--json` oder in einer Pipe\nwird ohne eines der beiden nichts installiert. Bisher geschah beides nur als Nebenwirkung von\n`nxs sync bind` — ein nie an einen Strom gebundener Arbeitsbereich hatte damit überhaupt keine Uhr,\nund niemand sagte es.\n- `changed` · `nxs sync daemon status` meldet einen arbeitenden Hintergrunddienst nicht mehr als gestoppt. Zwei\nDinge führten dazu. Ein Dienst, der lange zum Starten brauchte — eine Binary auf einem\nWechselvolume steht hinter einem macOS-Berechtigungsdialog, bis jemand ihn wegklickt, auf einer\nMaschine mit 69 Minuten gemessen —, notierte als seinen Start den Moment seines ersten Schreibens\nstatt den Beginn seines Prozesses, und jede spätere Lesung hielt ihn für den gesamten Lauf für einen\nfremden Prozess; er notiert jetzt den echten Prozessstart, und ein später notierter Start wird als\ndas gelesen, was er ist: ein Dienst, der sich verspätet gestempelt hat. Und ein Prozess, der die\nvermerkte Prozessnummer nur HÄLT, ist jetzt eine eigene Antwort — `unconfirmed` („der Dienst ist\nfort und seine Nummer wurde weitergegeben, dieser Prozess gehört jemand anderem\") — statt „NOT\nrunning (the process is gone)\", was einen Besitzer dazu brachte, einen Dienst neu zu installieren,\nder gerade synchronisierte. Auch ein Herzschlag, der nicht GESCHRIEBEN werden konnte (volle Platte),\nverschwindet nicht mehr in einem Log, das niemand ansieht: Der Dienst läuft weiter, zählt die\nFehlschläge, und der nächste erfolgreiche Herzschlag trägt Zeitpunkt und Grund mit — eine Lücke in\n`last pass` sagt jetzt, woher sie kam.\n\nFür alles, was direkt gegen `nxs-service` kompiliert: `ServiceState` hat eine vierte Variante,\n`Unconfirmed`; ein erschöpfendes `match` darüber braucht einen Arm mehr. Behandeln Sie sie wie diese\nVersion es tut — wie `Unknown`, wo ein Verweigern echte Arbeit blockieren würde, und wie einen\nmeldbaren Fehler, wo es nur um eine Warnung geht." - } - }, - { - "version": "0.81.0", - "date": "2026-09-02", - "items": [ - { - "type": "added", - "en": "The docs have a fifth section — **Develop with nexus-flow** — and the architecture has a picture.\nThe four sections above it answer \"how do I use this\"; this one answers \"how is it built, and what\nmay I build on\", which is a different reader: a contributor, or someone writing an app over the\nengine seam. `nxs guide architecture` is its first topic, and it\nnames the five layers — who calls the system, the four `argv[0]` personas over one binary, the\nthree engines and their seams, the one shared append-only op-log, and the sync edge to `nxf-relay`\n— and says which way the arrows point, including the two that are easy to get backwards (an\nembedding app links the engine seam directly and never the CLI; `ready`/`blocked`/`next` are\nderived in SQL and never stored). On the docs site the page also shows the diagram itself, exported\nfrom `docs/architecture/nexus-flow-simplified.drawio`.\n\n`nxs guide` lists the new group last and still serves its topics — the section names an audience,\nnot a new binary, so you keep typing `nxs guide architecture`. `nxs guide --json` accordingly\ncarries one more entry in `modules` (`{\"module\":\"develop\",\"binary\":\"nxs\",…}`); an entry keyed by\n`module` or `binary` is unaffected.\n\nThe terminal half is not an afterthought: the page is written to stand on its own without the\npicture, and `nxs guide` now renders a markdown image as `[image: ]` instead of leaking\nthe raw `!alt` that the renderer produced before — an image was the one inline form it had no case\nfor.", - "de": "Die Doku hat einen fünften Abschnitt — **Mit nexus-flow entwickeln** — und die Architektur hat ein\nBild. Die vier Abschnitte darüber beantworten „wie benutze ich das\"; dieser beantwortet „wie ist es\ngebaut, und worauf darf ich bauen\" — ein anderer Leser: jemand, der beiträgt, oder jemand, der eine\nApp über der Engine-Naht schreibt. `nxs guide architecture` ist sein erstes Thema. Es benennt die fünf Schichten — wer das System aufruft, die vier `argv[0]`-Personas über einer\nBinary, die drei Engines und ihre Nähte, das eine geteilte append-only-op-Log und die Sync-Kante zum\n`nxf-relay` — und sagt, wohin die Pfeile zeigen, auch die zwei, die man leicht verkehrt herum\nliest (eine einbettende App linkt die Engine-Naht direkt und nie die CLI; `ready`/`blocked`/`next`\nwerden in SQL abgeleitet und nie gespeichert). Auf der Doku-Seite zeigt die Seite zusätzlich das\nDiagramm selbst, exportiert aus `docs/architecture/nexus-flow-simplified.drawio`.\n\n`nxs guide` listet die neue Gruppe zuletzt und bedient ihre Themen weiterhin — der Abschnitt\nbenennt eine Zielgruppe, kein neues Binary, du tippst also weiter `nxs guide architecture`.\n`nxs guide --json` trägt entsprechend einen Eintrag mehr in `modules`\n(`{\"module\":\"develop\",\"binary\":\"nxs\",…}`); wer einen Eintrag über `module` oder `binary` sucht, ist\nnicht betroffen.\n\nDie Terminal-Hälfte ist dabei kein Nachgedanke: Die Seite ist so geschrieben, dass sie ohne das\nBild für sich steht, und `nxs guide` stellt ein Markdown-Bild jetzt als `[image: ]` dar,\nstatt das rohe `!alt` durchzulassen, das der Renderer vorher erzeugte — ein Bild war die eine\nInline-Form, für die er keinen Fall hatte." - }, - { - "type": "changed", - "en": "`nxs init` and `nxs setup claude` no longer write the managed nexus-flow block into `CLAUDE.md` —\nthat file belongs to the host that runs the SessionStart hook, which has already delivered the same\ntext. The block's `@NEXUS_MEMORY.md` line made the duplication expensive: Claude Code expands it\ninto an import of every memory's full body, beside the budgeted index the hook just handed the\nsession (measured at 95–120 KB per session in the larger workspaces). The block stays unchanged in\n`AGENTS.md`, which is where an agent that runs no hooks reads it. An existing workspace is carried\nalong by the next ordinary run: the retired block is taken back out of `CLAUDE.md`, and nothing else\nin the file is touched — a `CLAUDE.md` that held nothing but that block is removed rather than left\nempty.", - "de": "`nxs init` und `nxs setup claude` schreiben den verwalteten nexus-flow-Block nicht mehr in die\n`CLAUDE.md` — diese Datei gehört dem Wirt, der den SessionStart-Hook ausführt, und der hat denselben\nText bereits geliefert. Teuer wurde die Dopplung durch die Zeile `@NEXUS_MEMORY.md`: Claude Code\nexpandiert sie zu einem Import sämtlicher Erinnerungs-Volltexte, neben den gekürzten Index, den der\nHook der Sitzung gerade übergeben hat (gemessen 95–120 KB je Sitzung in den größeren\nArbeitsbereichen). In der `AGENTS.md` bleibt der Block unverändert — dort liest ihn ein Agent, der\nkeine Hooks fährt. Bestehende Arbeitsbereiche nimmt der nächste gewöhnliche Lauf mit: Der\nausgemusterte Block wird wieder aus der `CLAUDE.md` entfernt, alles Übrige bleibt unangetastet — und\neine `CLAUDE.md`, die nichts als diesen Block enthielt, wird gelöscht statt leer stehen gelassen." - }, - { - "type": "added", - "en": "`nxs` now carries guides of its own, and they come first. Until now the documentation explained\n`nxf`, `nxm` and `nxc` but never the umbrella that ties them together — so there was no page saying\nhow to install the suite, what `nxs init` does, what it leaves on disk, or what restores an agent's\ncontext at the start of a session. Three new topics answer that: `nxs guide getting-started`,\n`nxs guide modules` and `nxs guide the-workspace`, in the terminal and on the docs site, where they\nnow head the navigation above the three building blocks.\n\n`nxs guide` accordingly lists its own topics before it fans out over the modules, and\n`nxs guide getting-started` now serves the suite-wide one instead of refusing to choose between the\nblocks. Topics the umbrella does not carry are unchanged: `nxs guide commands` still names\n`nxf guide commands`, `nxm guide commands` and `nxc guide commands` and picks none of them. The\n`--json` shape is untouched — the umbrella appears as one more entry in `modules`.", - "de": "`nxs` hat jetzt eigene Anleitungen, und sie stehen an erster Stelle. Bisher erklärte die\nDokumentation `nxf`, `nxm` und `nxc`, aber nie die Klammer darüber — es gab also keine Seite, die\nsagt, wie man die Suite installiert, was `nxs init` tut, was dabei auf der Platte entsteht und was\nzu Sitzungsbeginn den Kontext eines Agenten zurückholt. Drei neue Themen beantworten das:\n`nxs guide getting-started`, `nxs guide modules` und `nxs guide the-workspace` — im Terminal und auf\nder Doku-Seite, wo sie nun über den drei Bausteinen die Navigation anführen.\n\n`nxs guide` listet entsprechend zuerst die eigenen Themen und fächert danach über die Module auf,\nund `nxs guide getting-started` liefert jetzt die Suite-weite Anleitung, statt die Wahl zwischen den\nBausteinen zu verweigern. Themen, die die Klammer nicht trägt, bleiben unverändert:\n`nxs guide commands` nennt weiterhin `nxf guide commands`, `nxm guide commands` und\n`nxc guide commands` und wählt keines davon aus. Die `--json`-Form ist unangetastet — die Klammer\nerscheint als ein weiterer Eintrag in `modules`." - }, - { - "type": "fixed", - "en": "The guides said a workspace gets a single `nxs prime` SessionStart hook. It has not worked that way\nsince the per-hook output budget was measured: `nxs init` wires **one hook per active block**\n(`nxf prime`, `nxm prime`, `nxc prime`), because the host truncates each hook's output separately at\n10 KB, so three hooks carry three budgets instead of one. Twenty-eight sentences across twelve pages\nin flow, memory and chat still described the old shape — two of them contradicting the correct\nstatement further down their own page. They now describe what actually happens, in both languages.\n\n`nxs prime` itself is unchanged: it is still the fan-out over every active block, and still what you\nrun by hand after a context compaction. It is simply no longer what the session hooks go through.", - "de": "Die Anleitungen sagten, ein Arbeitsbereich bekomme einen einzelnen `nxs prime`-SessionStart-Hook. So\nist es nicht mehr, seit das Ausgabe-Budget je Hook gemessen wurde: `nxs init` verdrahtet **einen Hook\nje aktivem Baustein** (`nxf prime`, `nxm prime`, `nxc prime`), denn der Wirt kürzt jede Hook-Ausgabe\nfür sich bei 10 KB — drei Hooks tragen also drei Budgets statt eines. Achtundzwanzig Sätze auf zwölf\nSeiten in flow, memory und chat beschrieben weiter die alte Form, zwei davon im Widerspruch zur\nrichtigen Aussage weiter unten auf derselben Seite. Sie beschreiben jetzt, was tatsächlich\ngeschieht — in beiden Sprachen.\n\n`nxs prime` selbst ist unverändert: weiterhin der Fächer über jeden aktiven Baustein und weiterhin\ndas, was man nach einer Kontext-Verdichtung von Hand ausführt. Es ist nur nicht mehr das, worüber die\nSitzungs-Hooks laufen." - } - ], - "notes": { - "en": "### Added\n- The docs have a fifth section — **Develop with nexus-flow** — and the architecture has a picture.\nThe four sections above it answer \"how do I use this\"; this one answers \"how is it built, and what\nmay I build on\", which is a different reader: a contributor, or someone writing an app over the\nengine seam. `nxs guide architecture` is its first topic, and it\nnames the five layers — who calls the system, the four `argv[0]` personas over one binary, the\nthree engines and their seams, the one shared append-only op-log, and the sync edge to `nxf-relay`\n— and says which way the arrows point, including the two that are easy to get backwards (an\nembedding app links the engine seam directly and never the CLI; `ready`/`blocked`/`next` are\nderived in SQL and never stored). On the docs site the page also shows the diagram itself, exported\nfrom `docs/architecture/nexus-flow-simplified.drawio`.\n\n`nxs guide` lists the new group last and still serves its topics — the section names an audience,\nnot a new binary, so you keep typing `nxs guide architecture`. `nxs guide --json` accordingly\ncarries one more entry in `modules` (`{\"module\":\"develop\",\"binary\":\"nxs\",…}`); an entry keyed by\n`module` or `binary` is unaffected.\n\nThe terminal half is not an afterthought: the page is written to stand on its own without the\npicture, and `nxs guide` now renders a markdown image as `[image: ]` instead of leaking\nthe raw `!alt` that the renderer produced before — an image was the one inline form it had no case\nfor.\n- `nxs` now carries guides of its own, and they come first. Until now the documentation explained\n`nxf`, `nxm` and `nxc` but never the umbrella that ties them together — so there was no page saying\nhow to install the suite, what `nxs init` does, what it leaves on disk, or what restores an agent's\ncontext at the start of a session. Three new topics answer that: `nxs guide getting-started`,\n`nxs guide modules` and `nxs guide the-workspace`, in the terminal and on the docs site, where they\nnow head the navigation above the three building blocks.\n\n`nxs guide` accordingly lists its own topics before it fans out over the modules, and\n`nxs guide getting-started` now serves the suite-wide one instead of refusing to choose between the\nblocks. Topics the umbrella does not carry are unchanged: `nxs guide commands` still names\n`nxf guide commands`, `nxm guide commands` and `nxc guide commands` and picks none of them. The\n`--json` shape is untouched — the umbrella appears as one more entry in `modules`.\n\n### Changed\n- `nxs init` and `nxs setup claude` no longer write the managed nexus-flow block into `CLAUDE.md` —\nthat file belongs to the host that runs the SessionStart hook, which has already delivered the same\ntext. The block's `@NEXUS_MEMORY.md` line made the duplication expensive: Claude Code expands it\ninto an import of every memory's full body, beside the budgeted index the hook just handed the\nsession (measured at 95–120 KB per session in the larger workspaces). The block stays unchanged in\n`AGENTS.md`, which is where an agent that runs no hooks reads it. An existing workspace is carried\nalong by the next ordinary run: the retired block is taken back out of `CLAUDE.md`, and nothing else\nin the file is touched — a `CLAUDE.md` that held nothing but that block is removed rather than left\nempty.\n\n### Fixed\n- The guides said a workspace gets a single `nxs prime` SessionStart hook. It has not worked that way\nsince the per-hook output budget was measured: `nxs init` wires **one hook per active block**\n(`nxf prime`, `nxm prime`, `nxc prime`), because the host truncates each hook's output separately at\n10 KB, so three hooks carry three budgets instead of one. Twenty-eight sentences across twelve pages\nin flow, memory and chat still described the old shape — two of them contradicting the correct\nstatement further down their own page. They now describe what actually happens, in both languages.\n\n`nxs prime` itself is unchanged: it is still the fan-out over every active block, and still what you\nrun by hand after a context compaction. It is simply no longer what the session hooks go through.", - "de": "### Neu\n- Die Doku hat einen fünften Abschnitt — **Mit nexus-flow entwickeln** — und die Architektur hat ein\nBild. Die vier Abschnitte darüber beantworten „wie benutze ich das\"; dieser beantwortet „wie ist es\ngebaut, und worauf darf ich bauen\" — ein anderer Leser: jemand, der beiträgt, oder jemand, der eine\nApp über der Engine-Naht schreibt. `nxs guide architecture` ist sein erstes Thema. Es benennt die fünf Schichten — wer das System aufruft, die vier `argv[0]`-Personas über einer\nBinary, die drei Engines und ihre Nähte, das eine geteilte append-only-op-Log und die Sync-Kante zum\n`nxf-relay` — und sagt, wohin die Pfeile zeigen, auch die zwei, die man leicht verkehrt herum\nliest (eine einbettende App linkt die Engine-Naht direkt und nie die CLI; `ready`/`blocked`/`next`\nwerden in SQL abgeleitet und nie gespeichert). Auf der Doku-Seite zeigt die Seite zusätzlich das\nDiagramm selbst, exportiert aus `docs/architecture/nexus-flow-simplified.drawio`.\n\n`nxs guide` listet die neue Gruppe zuletzt und bedient ihre Themen weiterhin — der Abschnitt\nbenennt eine Zielgruppe, kein neues Binary, du tippst also weiter `nxs guide architecture`.\n`nxs guide --json` trägt entsprechend einen Eintrag mehr in `modules`\n(`{\"module\":\"develop\",\"binary\":\"nxs\",…}`); wer einen Eintrag über `module` oder `binary` sucht, ist\nnicht betroffen.\n\nDie Terminal-Hälfte ist dabei kein Nachgedanke: Die Seite ist so geschrieben, dass sie ohne das\nBild für sich steht, und `nxs guide` stellt ein Markdown-Bild jetzt als `[image: ]` dar,\nstatt das rohe `!alt` durchzulassen, das der Renderer vorher erzeugte — ein Bild war die eine\nInline-Form, für die er keinen Fall hatte.\n- `nxs` hat jetzt eigene Anleitungen, und sie stehen an erster Stelle. Bisher erklärte die\nDokumentation `nxf`, `nxm` und `nxc`, aber nie die Klammer darüber — es gab also keine Seite, die\nsagt, wie man die Suite installiert, was `nxs init` tut, was dabei auf der Platte entsteht und was\nzu Sitzungsbeginn den Kontext eines Agenten zurückholt. Drei neue Themen beantworten das:\n`nxs guide getting-started`, `nxs guide modules` und `nxs guide the-workspace` — im Terminal und auf\nder Doku-Seite, wo sie nun über den drei Bausteinen die Navigation anführen.\n\n`nxs guide` listet entsprechend zuerst die eigenen Themen und fächert danach über die Module auf,\nund `nxs guide getting-started` liefert jetzt die Suite-weite Anleitung, statt die Wahl zwischen den\nBausteinen zu verweigern. Themen, die die Klammer nicht trägt, bleiben unverändert:\n`nxs guide commands` nennt weiterhin `nxf guide commands`, `nxm guide commands` und\n`nxc guide commands` und wählt keines davon aus. Die `--json`-Form ist unangetastet — die Klammer\nerscheint als ein weiterer Eintrag in `modules`.\n\n### Geändert\n- `nxs init` und `nxs setup claude` schreiben den verwalteten nexus-flow-Block nicht mehr in die\n`CLAUDE.md` — diese Datei gehört dem Wirt, der den SessionStart-Hook ausführt, und der hat denselben\nText bereits geliefert. Teuer wurde die Dopplung durch die Zeile `@NEXUS_MEMORY.md`: Claude Code\nexpandiert sie zu einem Import sämtlicher Erinnerungs-Volltexte, neben den gekürzten Index, den der\nHook der Sitzung gerade übergeben hat (gemessen 95–120 KB je Sitzung in den größeren\nArbeitsbereichen). In der `AGENTS.md` bleibt der Block unverändert — dort liest ihn ein Agent, der\nkeine Hooks fährt. Bestehende Arbeitsbereiche nimmt der nächste gewöhnliche Lauf mit: Der\nausgemusterte Block wird wieder aus der `CLAUDE.md` entfernt, alles Übrige bleibt unangetastet — und\neine `CLAUDE.md`, die nichts als diesen Block enthielt, wird gelöscht statt leer stehen gelassen.\n\n### Behoben\n- Die Anleitungen sagten, ein Arbeitsbereich bekomme einen einzelnen `nxs prime`-SessionStart-Hook. So\nist es nicht mehr, seit das Ausgabe-Budget je Hook gemessen wurde: `nxs init` verdrahtet **einen Hook\nje aktivem Baustein** (`nxf prime`, `nxm prime`, `nxc prime`), denn der Wirt kürzt jede Hook-Ausgabe\nfür sich bei 10 KB — drei Hooks tragen also drei Budgets statt eines. Achtundzwanzig Sätze auf zwölf\nSeiten in flow, memory und chat beschrieben weiter die alte Form, zwei davon im Widerspruch zur\nrichtigen Aussage weiter unten auf derselben Seite. Sie beschreiben jetzt, was tatsächlich\ngeschieht — in beiden Sprachen.\n\n`nxs prime` selbst ist unverändert: weiterhin der Fächer über jeden aktiven Baustein und weiterhin\ndas, was man nach einer Kontext-Verdichtung von Hand ausführt. Es ist nur nicht mehr das, worüber die\nSitzungs-Hooks laufen." - } - }, - { - "version": "0.80.0", - "date": "2026-09-01", - "items": [ - { - "type": "added", - "en": "**A worker can now say whether it is able to answer the liveness question at all.** Whether a\nsession is still running is a question only the thing that starts sessions can answer, and a worker\nthat cannot answer it has always answered `false` — the same word as \"nothing is running\". Everything\nthat acts on that answer therefore acted on a silence as if it were a fact, and the sharpest case is\n`release_working_tree`: a conditional handover of this device's working copy turns into an\nunconditional one, which is exactly the collision the working-tree lease exists to prevent.\n\n`Worker::answers_liveness` separates the question from the answer. It defaults to \"no\", so nothing\nthat already works changes, and the bundled sidecar — which reads the pid file its own session lock\nwrites — says yes. A host can ask it before any session exists, through\n`Engine::worker_answers_liveness`, and warn, refuse or proceed on its own terms; until now a host\ncould only classify its own worker configuration and guess.\n\n`nxc session state` carries the same answer as `worker_answers_liveness`, and says it in words in\nthe terminal. That is where the difference is felt: `unknown` used to mean both \"this session died\nwithout announcing it\" and \"nobody here can tell\", and now it says which.\n\n**The shape of the break, for an embedder:** `SessionStateReport` gained that field. Nothing was\nremoved or renamed, and reading the report is unaffected — but the struct has public fields and no\n`#[non_exhaustive]`, so code that *constructs* one with a struct literal has to name the new field.\n\nNo default's direction changed. A worker that cannot answer still reads as \"not running\", so one\nunknowable session can never stall a channel with nothing able to release it.", - "de": "**Ein Worker kann jetzt sagen, ob er die Lebendigkeitsfrage überhaupt beantwortet.** Ob eine Sitzung\nnoch läuft, kann nur beantworten, wer Sitzungen startet — und wer es nicht kann, hat bisher `false`\ngeantwortet, also dasselbe Wort wie „es läuft nichts\". Alles, was auf diese Antwort hin handelt, hat\ndamit ein Schweigen wie eine Tatsache behandelt, und am schärfsten `release_working_tree`: aus der\nbedingten Freigabe der Arbeitskopie dieses Geräts wird eine bedingungslose — genau die Kollision,\ngegen die die Arbeitsbaum-Leihe gebaut ist.\n\n`Worker::answers_liveness` trennt die Frage von der Antwort. Die Vorgabe ist „nein\", es ändert sich\nalso nichts an dem, was heute läuft, und der mitgelieferte Sidecar — der die pid-Datei seiner eigenen\nSitzungssperre liest — sagt ja. Ein Wirt kann die Frage stellen, bevor es überhaupt eine Sitzung\ngibt, über `Engine::worker_answers_liveness`, und dann warnen, verweigern oder weitermachen, wie es\nihm richtig scheint; bisher konnte er nur seine eigene Worker-Konfiguration einordnen und raten.\n\n`nxc session state` trägt dieselbe Antwort als `worker_answers_liveness` und sagt sie im Terminal in\nWorten. Dort wird der Unterschied spürbar: `unknown` hieß bisher beides — „diese Sitzung ist\ngestorben, ohne es zu melden\" und „hier kann es niemand wissen\" —, und jetzt sagt es, welches von\nbeiden.\n\n**Die Form des Bruchs, für einen Einbetter:** `SessionStateReport` hat dieses Feld dazubekommen.\nNichts wurde entfernt oder umbenannt, und das Lesen des Berichts ist unberührt — aber der Typ hat\nöffentliche Felder und kein `#[non_exhaustive]`, wer also selbst einen mit einem Struct-Literal\nBAUT, muss das neue Feld benennen.\n\nKeine Vorgabe hat ihre Richtung gewechselt. Ein Worker, der nicht antworten kann, liest sich\nweiterhin als „läuft nicht\", damit eine unerkennbare Sitzung niemals einen Kanal blockiert, den\nnichts mehr freigeben kann.", - "facade": "breaking" - } - ], - "notes": { - "en": "### Added\n- **A worker can now say whether it is able to answer the liveness question at all.** Whether a\nsession is still running is a question only the thing that starts sessions can answer, and a worker\nthat cannot answer it has always answered `false` — the same word as \"nothing is running\". Everything\nthat acts on that answer therefore acted on a silence as if it were a fact, and the sharpest case is\n`release_working_tree`: a conditional handover of this device's working copy turns into an\nunconditional one, which is exactly the collision the working-tree lease exists to prevent.\n\n`Worker::answers_liveness` separates the question from the answer. It defaults to \"no\", so nothing\nthat already works changes, and the bundled sidecar — which reads the pid file its own session lock\nwrites — says yes. A host can ask it before any session exists, through\n`Engine::worker_answers_liveness`, and warn, refuse or proceed on its own terms; until now a host\ncould only classify its own worker configuration and guess.\n\n`nxc session state` carries the same answer as `worker_answers_liveness`, and says it in words in\nthe terminal. That is where the difference is felt: `unknown` used to mean both \"this session died\nwithout announcing it\" and \"nobody here can tell\", and now it says which.\n\n**The shape of the break, for an embedder:** `SessionStateReport` gained that field. Nothing was\nremoved or renamed, and reading the report is unaffected — but the struct has public fields and no\n`#[non_exhaustive]`, so code that *constructs* one with a struct literal has to name the new field.\n\nNo default's direction changed. A worker that cannot answer still reads as \"not running\", so one\nunknowable session can never stall a channel with nothing able to release it.\n\n### Facade Contract\n- `breaking` · **A worker can now say whether it is able to answer the liveness question at all.** Whether a\nsession is still running is a question only the thing that starts sessions can answer, and a worker\nthat cannot answer it has always answered `false` — the same word as \"nothing is running\". Everything\nthat acts on that answer therefore acted on a silence as if it were a fact, and the sharpest case is\n`release_working_tree`: a conditional handover of this device's working copy turns into an\nunconditional one, which is exactly the collision the working-tree lease exists to prevent.\n\n`Worker::answers_liveness` separates the question from the answer. It defaults to \"no\", so nothing\nthat already works changes, and the bundled sidecar — which reads the pid file its own session lock\nwrites — says yes. A host can ask it before any session exists, through\n`Engine::worker_answers_liveness`, and warn, refuse or proceed on its own terms; until now a host\ncould only classify its own worker configuration and guess.\n\n`nxc session state` carries the same answer as `worker_answers_liveness`, and says it in words in\nthe terminal. That is where the difference is felt: `unknown` used to mean both \"this session died\nwithout announcing it\" and \"nobody here can tell\", and now it says which.\n\n**The shape of the break, for an embedder:** `SessionStateReport` gained that field. Nothing was\nremoved or renamed, and reading the report is unaffected — but the struct has public fields and no\n`#[non_exhaustive]`, so code that *constructs* one with a struct literal has to name the new field.\n\nNo default's direction changed. A worker that cannot answer still reads as \"not running\", so one\nunknowable session can never stall a channel with nothing able to release it.", - "de": "### Neu\n- **Ein Worker kann jetzt sagen, ob er die Lebendigkeitsfrage überhaupt beantwortet.** Ob eine Sitzung\nnoch läuft, kann nur beantworten, wer Sitzungen startet — und wer es nicht kann, hat bisher `false`\ngeantwortet, also dasselbe Wort wie „es läuft nichts\". Alles, was auf diese Antwort hin handelt, hat\ndamit ein Schweigen wie eine Tatsache behandelt, und am schärfsten `release_working_tree`: aus der\nbedingten Freigabe der Arbeitskopie dieses Geräts wird eine bedingungslose — genau die Kollision,\ngegen die die Arbeitsbaum-Leihe gebaut ist.\n\n`Worker::answers_liveness` trennt die Frage von der Antwort. Die Vorgabe ist „nein\", es ändert sich\nalso nichts an dem, was heute läuft, und der mitgelieferte Sidecar — der die pid-Datei seiner eigenen\nSitzungssperre liest — sagt ja. Ein Wirt kann die Frage stellen, bevor es überhaupt eine Sitzung\ngibt, über `Engine::worker_answers_liveness`, und dann warnen, verweigern oder weitermachen, wie es\nihm richtig scheint; bisher konnte er nur seine eigene Worker-Konfiguration einordnen und raten.\n\n`nxc session state` trägt dieselbe Antwort als `worker_answers_liveness` und sagt sie im Terminal in\nWorten. Dort wird der Unterschied spürbar: `unknown` hieß bisher beides — „diese Sitzung ist\ngestorben, ohne es zu melden\" und „hier kann es niemand wissen\" —, und jetzt sagt es, welches von\nbeiden.\n\n**Die Form des Bruchs, für einen Einbetter:** `SessionStateReport` hat dieses Feld dazubekommen.\nNichts wurde entfernt oder umbenannt, und das Lesen des Berichts ist unberührt — aber der Typ hat\nöffentliche Felder und kein `#[non_exhaustive]`, wer also selbst einen mit einem Struct-Literal\nBAUT, muss das neue Feld benennen.\n\nKeine Vorgabe hat ihre Richtung gewechselt. Ein Worker, der nicht antworten kann, liest sich\nweiterhin als „läuft nicht\", damit eine unerkennbare Sitzung niemals einen Kanal blockiert, den\nnichts mehr freigeben kann.\n\n### Facade-Kontrakt\n- `breaking` · **Ein Worker kann jetzt sagen, ob er die Lebendigkeitsfrage überhaupt beantwortet.** Ob eine Sitzung\nnoch läuft, kann nur beantworten, wer Sitzungen startet — und wer es nicht kann, hat bisher `false`\ngeantwortet, also dasselbe Wort wie „es läuft nichts\". Alles, was auf diese Antwort hin handelt, hat\ndamit ein Schweigen wie eine Tatsache behandelt, und am schärfsten `release_working_tree`: aus der\nbedingten Freigabe der Arbeitskopie dieses Geräts wird eine bedingungslose — genau die Kollision,\ngegen die die Arbeitsbaum-Leihe gebaut ist.\n\n`Worker::answers_liveness` trennt die Frage von der Antwort. Die Vorgabe ist „nein\", es ändert sich\nalso nichts an dem, was heute läuft, und der mitgelieferte Sidecar — der die pid-Datei seiner eigenen\nSitzungssperre liest — sagt ja. Ein Wirt kann die Frage stellen, bevor es überhaupt eine Sitzung\ngibt, über `Engine::worker_answers_liveness`, und dann warnen, verweigern oder weitermachen, wie es\nihm richtig scheint; bisher konnte er nur seine eigene Worker-Konfiguration einordnen und raten.\n\n`nxc session state` trägt dieselbe Antwort als `worker_answers_liveness` und sagt sie im Terminal in\nWorten. Dort wird der Unterschied spürbar: `unknown` hieß bisher beides — „diese Sitzung ist\ngestorben, ohne es zu melden\" und „hier kann es niemand wissen\" —, und jetzt sagt es, welches von\nbeiden.\n\n**Die Form des Bruchs, für einen Einbetter:** `SessionStateReport` hat dieses Feld dazubekommen.\nNichts wurde entfernt oder umbenannt, und das Lesen des Berichts ist unberührt — aber der Typ hat\nöffentliche Felder und kein `#[non_exhaustive]`, wer also selbst einen mit einem Struct-Literal\nBAUT, muss das neue Feld benennen.\n\nKeine Vorgabe hat ihre Richtung gewechselt. Ein Worker, der nicht antworten kann, liest sich\nweiterhin als „läuft nicht\", damit eine unerkennbare Sitzung niemals einen Kanal blockiert, den\nnichts mehr freigeben kann." - } - }, - { - "version": "0.79.0", - "date": "2026-09-01", - "items": [ - { - "type": "added", - "en": "**An application that runs its own agent sessions can record what they did.** Reading a session's\ntranscript was on the library seam and writing to it was not, so a host driving the Claude Agent SDK\nitself could fill a transcript only by starting an `nxc` process or by writing to the database\nbehind the library's back — and a host that did neither ended up with a session that was correctly\nbound and a transcript that stayed empty. `transcript_append` closes that half: the host hands over\nthe entries it already normalizes, the store assigns their order, and batches from the host and from\nthe bundled sidecar continue ONE history for a session instead of colliding over it. A batch is\nstill all-or-nothing, and retention still rides the first write of a new session, so an app that\nnever prunes still gets a bounded table.\n\nNothing changes for anyone typing `nxc transcript append`: same flag, same JSON-lines on stdin, same\n`--json` record back, same line-numbered error for a bad line. The command now goes through the same\ncall an app makes.", - "de": "**Eine Anwendung, die ihre Agenten-Sitzungen selbst fährt, kann jetzt aufzeichnen, was sie getan\nhaben.** Das Transkript einer Sitzung zu lesen lag auf der Bibliotheks-Naht, es zu schreiben nicht —\nein Wirt, der das Claude Agent SDK selbst fährt, konnte ein Transkript deshalb nur füllen, indem er\neinen `nxc`-Prozess startete oder an der Bibliothek vorbei in die Datenbank schrieb; und wer beides\nnicht tat, hatte eine korrekt gebundene Sitzung und ein Transkript, das leer blieb.\n`transcript_append` schließt diese Hälfte: der Wirt übergibt die Einträge, die er ohnehin\nnormalisiert, der Speicher vergibt ihre Reihenfolge, und Stapel vom Wirt und vom mitgelieferten\nSidecar setzen EINE Historie einer Sitzung fort, statt miteinander zu kollidieren. Ein Stapel ist\nweiterhin ganz oder gar nicht, und die Aufbewahrungsfrist reitet weiterhin auf dem ersten Schreiben\neiner neuen Sitzung mit — eine App, die nie aufräumt, bekommt also trotzdem eine begrenzte Tabelle.\n\nFür alle, die `nxc transcript append` tippen, ändert sich nichts: dasselbe Flag, dieselben\nJSON-Zeilen auf stdin, derselbe `--json`-Datensatz zurück, derselbe zeilengenaue Fehler für eine\nkaputte Zeile. Der Befehl geht jetzt durch denselben Aufruf, den auch eine App macht.", - "facade": "changed" - } - ], - "notes": { - "en": "### Added\n- **An application that runs its own agent sessions can record what they did.** Reading a session's\ntranscript was on the library seam and writing to it was not, so a host driving the Claude Agent SDK\nitself could fill a transcript only by starting an `nxc` process or by writing to the database\nbehind the library's back — and a host that did neither ended up with a session that was correctly\nbound and a transcript that stayed empty. `transcript_append` closes that half: the host hands over\nthe entries it already normalizes, the store assigns their order, and batches from the host and from\nthe bundled sidecar continue ONE history for a session instead of colliding over it. A batch is\nstill all-or-nothing, and retention still rides the first write of a new session, so an app that\nnever prunes still gets a bounded table.\n\nNothing changes for anyone typing `nxc transcript append`: same flag, same JSON-lines on stdin, same\n`--json` record back, same line-numbered error for a bad line. The command now goes through the same\ncall an app makes.\n\n### Facade Contract\n- `changed` · **An application that runs its own agent sessions can record what they did.** Reading a session's\ntranscript was on the library seam and writing to it was not, so a host driving the Claude Agent SDK\nitself could fill a transcript only by starting an `nxc` process or by writing to the database\nbehind the library's back — and a host that did neither ended up with a session that was correctly\nbound and a transcript that stayed empty. `transcript_append` closes that half: the host hands over\nthe entries it already normalizes, the store assigns their order, and batches from the host and from\nthe bundled sidecar continue ONE history for a session instead of colliding over it. A batch is\nstill all-or-nothing, and retention still rides the first write of a new session, so an app that\nnever prunes still gets a bounded table.\n\nNothing changes for anyone typing `nxc transcript append`: same flag, same JSON-lines on stdin, same\n`--json` record back, same line-numbered error for a bad line. The command now goes through the same\ncall an app makes.", - "de": "### Neu\n- **Eine Anwendung, die ihre Agenten-Sitzungen selbst fährt, kann jetzt aufzeichnen, was sie getan\nhaben.** Das Transkript einer Sitzung zu lesen lag auf der Bibliotheks-Naht, es zu schreiben nicht —\nein Wirt, der das Claude Agent SDK selbst fährt, konnte ein Transkript deshalb nur füllen, indem er\neinen `nxc`-Prozess startete oder an der Bibliothek vorbei in die Datenbank schrieb; und wer beides\nnicht tat, hatte eine korrekt gebundene Sitzung und ein Transkript, das leer blieb.\n`transcript_append` schließt diese Hälfte: der Wirt übergibt die Einträge, die er ohnehin\nnormalisiert, der Speicher vergibt ihre Reihenfolge, und Stapel vom Wirt und vom mitgelieferten\nSidecar setzen EINE Historie einer Sitzung fort, statt miteinander zu kollidieren. Ein Stapel ist\nweiterhin ganz oder gar nicht, und die Aufbewahrungsfrist reitet weiterhin auf dem ersten Schreiben\neiner neuen Sitzung mit — eine App, die nie aufräumt, bekommt also trotzdem eine begrenzte Tabelle.\n\nFür alle, die `nxc transcript append` tippen, ändert sich nichts: dasselbe Flag, dieselben\nJSON-Zeilen auf stdin, derselbe `--json`-Datensatz zurück, derselbe zeilengenaue Fehler für eine\nkaputte Zeile. Der Befehl geht jetzt durch denselben Aufruf, den auch eine App macht.\n\n### Facade-Kontrakt\n- `changed` · **Eine Anwendung, die ihre Agenten-Sitzungen selbst fährt, kann jetzt aufzeichnen, was sie getan\nhaben.** Das Transkript einer Sitzung zu lesen lag auf der Bibliotheks-Naht, es zu schreiben nicht —\nein Wirt, der das Claude Agent SDK selbst fährt, konnte ein Transkript deshalb nur füllen, indem er\neinen `nxc`-Prozess startete oder an der Bibliothek vorbei in die Datenbank schrieb; und wer beides\nnicht tat, hatte eine korrekt gebundene Sitzung und ein Transkript, das leer blieb.\n`transcript_append` schließt diese Hälfte: der Wirt übergibt die Einträge, die er ohnehin\nnormalisiert, der Speicher vergibt ihre Reihenfolge, und Stapel vom Wirt und vom mitgelieferten\nSidecar setzen EINE Historie einer Sitzung fort, statt miteinander zu kollidieren. Ein Stapel ist\nweiterhin ganz oder gar nicht, und die Aufbewahrungsfrist reitet weiterhin auf dem ersten Schreiben\neiner neuen Sitzung mit — eine App, die nie aufräumt, bekommt also trotzdem eine begrenzte Tabelle.\n\nFür alle, die `nxc transcript append` tippen, ändert sich nichts: dasselbe Flag, dieselben\nJSON-Zeilen auf stdin, derselbe `--json`-Datensatz zurück, derselbe zeilengenaue Fehler für eine\nkaputte Zeile. Der Befehl geht jetzt durch denselben Aufruf, den auch eine App macht." - } - }, - { - "version": "0.78.0", - "date": "2026-09-01", - "items": [ - { - "type": "fixed", - "en": "A channel member can no longer put words in another member's mouth. Both consolidator output forms\n— the `pass_through` delivery and the trigger the `summarize` synthesizer folds — now wrap each\ncollected answer in a delimiter chosen per round, after every answer has been read, so that no\nanswer contains it. A member whose reply writes `` therefore cannot close its own block\nand open one attributed to somebody else: the round simply moves to `` … ``\n(and the delivery names the boundary it used). Nothing is escaped, nothing is refused, and an\nanswer that contains those tags legitimately is delivered byte for byte as written.\n\n**A contract change for anything that parses this text.** A round whose answers do not spell a tag\nis byte-identical to before, which is very nearly all of them; a round where one does now carries\nthe suffixed form, and the `from=\"…\"` attribution is the engine's own record rather than a claim.\nThe `summarize` trigger changed shape outright: it used to render `sender: body` lines and now\nrenders the same blocks as the pass-through. What a member *says* is still unverified data, and the\nuntrusted-content framing around it is unchanged.", - "de": "Ein Kanalmitglied kann einem anderen keine Worte mehr in den Mund legen. Beide Ausgabeformen des\nKonsolidierers — die Durchreichung bei `pass_through` und der Anstoß, den der Synthesizer bei\n`summarize` faltet — umschließen jede gesammelte Antwort jetzt mit einem Trennzeichen, das je Runde\ngewählt wird, nachdem alle Antworten gelesen wurden, so dass keine Antwort es enthält. Ein Mitglied,\ndessen Antwort `` schreibt, kann damit nicht mehr den eigenen Block schließen und einen\nunter fremdem Namen öffnen: die Runde wechselt einfach auf `` … `` (und die\nLieferung nennt die verwendete Grenze). Nichts wird maskiert, nichts abgelehnt, und eine Antwort,\ndie diese Elemente berechtigt enthält, wird Byte für Byte so geliefert, wie sie geschrieben wurde.\n\n**Eine Vertragsänderung für alles, was diesen Text parst.** Eine Runde, deren Antworten kein Element\nbuchstabieren, ist byte-identisch zu vorher — das sind nahezu alle; eine Runde, in der eine es tut,\nträgt jetzt die Suffix-Form, und die Zuschreibung `from=\"…\"` ist die eigene Aufzeichnung der Engine\nstatt einer Behauptung. Der `summarize`-Anstoß hat seine Form ganz gewechselt: er hat bisher Zeilen\nder Art `sender: body` gerendert und rendert jetzt dieselben Blöcke wie die Durchreichung. Was ein\nMitglied *sagt*, bleibt ungeprüfte Information, und die Rahmung als nicht vertrauenswürdiger Inhalt\nbleibt unverändert.", - "facade": "breaking", - "security": true - } - ], - "notes": { - "en": "### Fixed\n- A channel member can no longer put words in another member's mouth. Both consolidator output forms\n— the `pass_through` delivery and the trigger the `summarize` synthesizer folds — now wrap each\ncollected answer in a delimiter chosen per round, after every answer has been read, so that no\nanswer contains it. A member whose reply writes `` therefore cannot close its own block\nand open one attributed to somebody else: the round simply moves to `` … ``\n(and the delivery names the boundary it used). Nothing is escaped, nothing is refused, and an\nanswer that contains those tags legitimately is delivered byte for byte as written.\n\n**A contract change for anything that parses this text.** A round whose answers do not spell a tag\nis byte-identical to before, which is very nearly all of them; a round where one does now carries\nthe suffixed form, and the `from=\"…\"` attribution is the engine's own record rather than a claim.\nThe `summarize` trigger changed shape outright: it used to render `sender: body` lines and now\nrenders the same blocks as the pass-through. What a member *says* is still unverified data, and the\nuntrusted-content framing around it is unchanged.\n\n### Facade Contract\n- `breaking` · security · A channel member can no longer put words in another member's mouth. Both consolidator output forms\n— the `pass_through` delivery and the trigger the `summarize` synthesizer folds — now wrap each\ncollected answer in a delimiter chosen per round, after every answer has been read, so that no\nanswer contains it. A member whose reply writes `` therefore cannot close its own block\nand open one attributed to somebody else: the round simply moves to `` … ``\n(and the delivery names the boundary it used). Nothing is escaped, nothing is refused, and an\nanswer that contains those tags legitimately is delivered byte for byte as written.\n\n**A contract change for anything that parses this text.** A round whose answers do not spell a tag\nis byte-identical to before, which is very nearly all of them; a round where one does now carries\nthe suffixed form, and the `from=\"…\"` attribution is the engine's own record rather than a claim.\nThe `summarize` trigger changed shape outright: it used to render `sender: body` lines and now\nrenders the same blocks as the pass-through. What a member *says* is still unverified data, and the\nuntrusted-content framing around it is unchanged.", - "de": "### Behoben\n- Ein Kanalmitglied kann einem anderen keine Worte mehr in den Mund legen. Beide Ausgabeformen des\nKonsolidierers — die Durchreichung bei `pass_through` und der Anstoß, den der Synthesizer bei\n`summarize` faltet — umschließen jede gesammelte Antwort jetzt mit einem Trennzeichen, das je Runde\ngewählt wird, nachdem alle Antworten gelesen wurden, so dass keine Antwort es enthält. Ein Mitglied,\ndessen Antwort `` schreibt, kann damit nicht mehr den eigenen Block schließen und einen\nunter fremdem Namen öffnen: die Runde wechselt einfach auf `` … `` (und die\nLieferung nennt die verwendete Grenze). Nichts wird maskiert, nichts abgelehnt, und eine Antwort,\ndie diese Elemente berechtigt enthält, wird Byte für Byte so geliefert, wie sie geschrieben wurde.\n\n**Eine Vertragsänderung für alles, was diesen Text parst.** Eine Runde, deren Antworten kein Element\nbuchstabieren, ist byte-identisch zu vorher — das sind nahezu alle; eine Runde, in der eine es tut,\nträgt jetzt die Suffix-Form, und die Zuschreibung `from=\"…\"` ist die eigene Aufzeichnung der Engine\nstatt einer Behauptung. Der `summarize`-Anstoß hat seine Form ganz gewechselt: er hat bisher Zeilen\nder Art `sender: body` gerendert und rendert jetzt dieselben Blöcke wie die Durchreichung. Was ein\nMitglied *sagt*, bleibt ungeprüfte Information, und die Rahmung als nicht vertrauenswürdiger Inhalt\nbleibt unverändert.\n\n### Facade-Kontrakt\n- `breaking` · Sicherheit · Ein Kanalmitglied kann einem anderen keine Worte mehr in den Mund legen. Beide Ausgabeformen des\nKonsolidierers — die Durchreichung bei `pass_through` und der Anstoß, den der Synthesizer bei\n`summarize` faltet — umschließen jede gesammelte Antwort jetzt mit einem Trennzeichen, das je Runde\ngewählt wird, nachdem alle Antworten gelesen wurden, so dass keine Antwort es enthält. Ein Mitglied,\ndessen Antwort `` schreibt, kann damit nicht mehr den eigenen Block schließen und einen\nunter fremdem Namen öffnen: die Runde wechselt einfach auf `` … `` (und die\nLieferung nennt die verwendete Grenze). Nichts wird maskiert, nichts abgelehnt, und eine Antwort,\ndie diese Elemente berechtigt enthält, wird Byte für Byte so geliefert, wie sie geschrieben wurde.\n\n**Eine Vertragsänderung für alles, was diesen Text parst.** Eine Runde, deren Antworten kein Element\nbuchstabieren, ist byte-identisch zu vorher — das sind nahezu alle; eine Runde, in der eine es tut,\nträgt jetzt die Suffix-Form, und die Zuschreibung `from=\"…\"` ist die eigene Aufzeichnung der Engine\nstatt einer Behauptung. Der `summarize`-Anstoß hat seine Form ganz gewechselt: er hat bisher Zeilen\nder Art `sender: body` gerendert und rendert jetzt dieselben Blöcke wie die Durchreichung. Was ein\nMitglied *sagt*, bleibt ungeprüfte Information, und die Rahmung als nicht vertrauenswürdiger Inhalt\nbleibt unverändert." - } - }, - { - "version": "0.77.0", - "date": "2026-08-31", - "items": [ - { - "type": "changed", - "en": "What a channel hands back at the end of a **stepped** round is now built out of the round rather than\npassed along flat. Every collected answer carries the step it served and which pass of that step it\nwas, the answer that carried the verdict is marked `needs_rework=\"true\"`, and the delivery says the\none thing the messages cannot say for themselves: where a step appears more than once, the later pass\nsupersedes the earlier one, together with the engine's own count of how many times each step ran, so\na mark inside the block that contradicts it can be recognised as forged. Both output forms carry it —\n`pass_through` for whoever asked, and `summarize` hands the same structure to the session that writes\nthe closing report. A channel that\ndeclares no `steps:` delivers exactly what it always did, byte for byte.", - "de": "Was ein Kanal am Ende einer Runde **mit Schritten** zurückgibt, wird jetzt aus der Runde gebaut statt\nflach durchgereicht. Jede eingesammelte Antwort trägt den Schritt, dem sie diente, und den wievielten\nDurchgang dieses Schritts sie war; die Antwort, die die Wertung trug, ist mit `needs_rework=\"true\"`\nmarkiert; und die Zustellung sagt die eine Sache, die die Nachrichten selbst nicht sagen können: taucht\nein Schritt mehrfach auf, löst der spätere Durchgang den früheren ab. Sie nennt dazu die vom Motor\ngezählte Zahl der Durchgänge je Schritt, außerhalb des Blocks — eine Marke im Block, die ihr\nwiderspricht, ist gefälscht. Beide Ausgabeformen tragen es —\n`pass_through` für den, der gefragt hat, und `summarize` übergibt dieselbe Struktur der Sitzung, die\nden Abschlussbericht schreibt. Ein Kanal ohne `steps:` liefert byteweise genau das, was er immer\ngeliefert hat.", - "migration": { - "en": "Automatic for a channel with no `steps:` — nothing about its delivery moves. For a stepped channel,\nan agent or app that PARSES the `` blocks or the fold's `sender: body` lines will see new\nattributes (`step`, `pass`, `needs_rework`) beside the existing `substituted` one, and one extra\nframing sentence above the block. Manual, for an embedding host only:\n`channel::CollectedReply` gained three public fields — build it through `CollectedReply::new` plus\nstruct-update syntax rather than a bare literal.", - "de": "Automatisch für einen Kanal ohne `steps:` — an seiner Zustellung bewegt sich nichts. Bei einem Kanal\nmit Schritten sieht ein Agent oder eine App, die die ``-Blöcke oder die\n`sender: body`-Zeilen der Faltung PARST, neue Attribute (`step`, `pass`, `needs_rework`) neben dem\nbestehenden `substituted` sowie einen zusätzlichen Rahmensatz über dem Block. Handarbeit, und nur für\neinen einbettenden Wirt: `channel::CollectedReply` hat drei neue öffentliche Felder — bau ihn über\n`CollectedReply::new` plus Struct-Update-Syntax statt über ein blankes Literal." - }, - "facade": "breaking" - }, - { - "type": "added", - "en": "A channel step can now say whether it **continues the session its target already ran in this round**\nor starts a fresh one: `resume: true` / `resume: false` on the step. Left out, the edge decides — a\nstep reached over `on_needs_rework:` continues (the party is being handed back its own work, and the\nnotice it is woken with says so), a step reached over `next:` starts fresh. The step still gets a\nfresh thread either way, so the pass counter is unaffected, and a step whose target is a whole\nchannel cannot be continued at all — that is reported as a bad declaration. `nxc guide channels`.", - "de": "Ein Kanalschritt kann jetzt sagen, ob er **die Sitzung fortsetzt, die sein Ziel in dieser Runde schon\ngeführt hat**, oder eine frische beginnt: `resume: true` / `resume: false` am Schritt. Lässt man es\nweg, entscheidet die Kante — ein über `on_needs_rework:` erreichter Schritt setzt fort (die Stelle\nbekommt ihre eigene Arbeit zurück, und der Hinweis, mit dem sie geweckt wird, sagt das auch), ein\nüber `next:` erreichter beginnt frisch. Der Schritt bekommt in beiden Fällen einen frischen Faden,\nder Durchgangszähler bleibt also unberührt, und ein Schritt, dessen Ziel ein ganzer Kanal ist, lässt\nsich gar nicht fortsetzen — das wird als fehlerhafte Deklaration gemeldet. `nxc guide channels`.", - "migration": { - "en": "Automatic: existing declarations are unchanged in meaning, and a channel that declares no `steps:` is\nunaffected. What DOES change without you asking is a stepped channel with a back edge — its rework\nstep now continues the session it sent back instead of starting a new one. Manual, for an embedding\nhost only: `orchestration::Commission` gained a required `continue_session` field (pass `None`) and\n`channel::FlowStep` gained `resume` (it derives `Default`, so `..Default::default()` covers a struct\nliteral). Pin this version in lockstep.", - "de": "Automatisch: bestehende Deklarationen bedeuten unverändert dasselbe, und ein Kanal ohne `steps:` ist\nnicht betroffen. Was sich OHNE Zutun ändert, ist ein Kanal mit Rückkante — sein\nNacharbeitungsschritt setzt jetzt die zurückgeschickte Sitzung fort, statt eine neue zu beginnen.\nHandarbeit, und nur für einen einbettenden Wirt: `orchestration::Commission` hat ein neues\nPflichtfeld `continue_session` (`None` übergeben), `channel::FlowStep` ein neues `resume` (der Typ\nhat `Default`, `..Default::default()` deckt ein Struct-Literal also ab). Diese Version im Gleichschritt\npinnen." - }, - "facade": "breaking" - } - ], - "notes": { - "en": "### Added\n- A channel step can now say whether it **continues the session its target already ran in this round**\nor starts a fresh one: `resume: true` / `resume: false` on the step. Left out, the edge decides — a\nstep reached over `on_needs_rework:` continues (the party is being handed back its own work, and the\nnotice it is woken with says so), a step reached over `next:` starts fresh. The step still gets a\nfresh thread either way, so the pass counter is unaffected, and a step whose target is a whole\nchannel cannot be continued at all — that is reported as a bad declaration. `nxc guide channels`.\n\n### Changed\n- What a channel hands back at the end of a **stepped** round is now built out of the round rather than\npassed along flat. Every collected answer carries the step it served and which pass of that step it\nwas, the answer that carried the verdict is marked `needs_rework=\"true\"`, and the delivery says the\none thing the messages cannot say for themselves: where a step appears more than once, the later pass\nsupersedes the earlier one, together with the engine's own count of how many times each step ran, so\na mark inside the block that contradicts it can be recognised as forged. Both output forms carry it —\n`pass_through` for whoever asked, and `summarize` hands the same structure to the session that writes\nthe closing report. A channel that\ndeclares no `steps:` delivers exactly what it always did, byte for byte.\n\n### Facade Contract\n- `breaking` · What a channel hands back at the end of a **stepped** round is now built out of the round rather than\npassed along flat. Every collected answer carries the step it served and which pass of that step it\nwas, the answer that carried the verdict is marked `needs_rework=\"true\"`, and the delivery says the\none thing the messages cannot say for themselves: where a step appears more than once, the later pass\nsupersedes the earlier one, together with the engine's own count of how many times each step ran, so\na mark inside the block that contradicts it can be recognised as forged. Both output forms carry it —\n`pass_through` for whoever asked, and `summarize` hands the same structure to the session that writes\nthe closing report. A channel that\ndeclares no `steps:` delivers exactly what it always did, byte for byte.\n- `breaking` · A channel step can now say whether it **continues the session its target already ran in this round**\nor starts a fresh one: `resume: true` / `resume: false` on the step. Left out, the edge decides — a\nstep reached over `on_needs_rework:` continues (the party is being handed back its own work, and the\nnotice it is woken with says so), a step reached over `next:` starts fresh. The step still gets a\nfresh thread either way, so the pass counter is unaffected, and a step whose target is a whole\nchannel cannot be continued at all — that is reported as a bad declaration. `nxc guide channels`.", - "de": "### Neu\n- Ein Kanalschritt kann jetzt sagen, ob er **die Sitzung fortsetzt, die sein Ziel in dieser Runde schon\ngeführt hat**, oder eine frische beginnt: `resume: true` / `resume: false` am Schritt. Lässt man es\nweg, entscheidet die Kante — ein über `on_needs_rework:` erreichter Schritt setzt fort (die Stelle\nbekommt ihre eigene Arbeit zurück, und der Hinweis, mit dem sie geweckt wird, sagt das auch), ein\nüber `next:` erreichter beginnt frisch. Der Schritt bekommt in beiden Fällen einen frischen Faden,\nder Durchgangszähler bleibt also unberührt, und ein Schritt, dessen Ziel ein ganzer Kanal ist, lässt\nsich gar nicht fortsetzen — das wird als fehlerhafte Deklaration gemeldet. `nxc guide channels`.\n\n### Geändert\n- Was ein Kanal am Ende einer Runde **mit Schritten** zurückgibt, wird jetzt aus der Runde gebaut statt\nflach durchgereicht. Jede eingesammelte Antwort trägt den Schritt, dem sie diente, und den wievielten\nDurchgang dieses Schritts sie war; die Antwort, die die Wertung trug, ist mit `needs_rework=\"true\"`\nmarkiert; und die Zustellung sagt die eine Sache, die die Nachrichten selbst nicht sagen können: taucht\nein Schritt mehrfach auf, löst der spätere Durchgang den früheren ab. Sie nennt dazu die vom Motor\ngezählte Zahl der Durchgänge je Schritt, außerhalb des Blocks — eine Marke im Block, die ihr\nwiderspricht, ist gefälscht. Beide Ausgabeformen tragen es —\n`pass_through` für den, der gefragt hat, und `summarize` übergibt dieselbe Struktur der Sitzung, die\nden Abschlussbericht schreibt. Ein Kanal ohne `steps:` liefert byteweise genau das, was er immer\ngeliefert hat.\n\n### Facade-Kontrakt\n- `breaking` · Was ein Kanal am Ende einer Runde **mit Schritten** zurückgibt, wird jetzt aus der Runde gebaut statt\nflach durchgereicht. Jede eingesammelte Antwort trägt den Schritt, dem sie diente, und den wievielten\nDurchgang dieses Schritts sie war; die Antwort, die die Wertung trug, ist mit `needs_rework=\"true\"`\nmarkiert; und die Zustellung sagt die eine Sache, die die Nachrichten selbst nicht sagen können: taucht\nein Schritt mehrfach auf, löst der spätere Durchgang den früheren ab. Sie nennt dazu die vom Motor\ngezählte Zahl der Durchgänge je Schritt, außerhalb des Blocks — eine Marke im Block, die ihr\nwiderspricht, ist gefälscht. Beide Ausgabeformen tragen es —\n`pass_through` für den, der gefragt hat, und `summarize` übergibt dieselbe Struktur der Sitzung, die\nden Abschlussbericht schreibt. Ein Kanal ohne `steps:` liefert byteweise genau das, was er immer\ngeliefert hat.\n- `breaking` · Ein Kanalschritt kann jetzt sagen, ob er **die Sitzung fortsetzt, die sein Ziel in dieser Runde schon\ngeführt hat**, oder eine frische beginnt: `resume: true` / `resume: false` am Schritt. Lässt man es\nweg, entscheidet die Kante — ein über `on_needs_rework:` erreichter Schritt setzt fort (die Stelle\nbekommt ihre eigene Arbeit zurück, und der Hinweis, mit dem sie geweckt wird, sagt das auch), ein\nüber `next:` erreichter beginnt frisch. Der Schritt bekommt in beiden Fällen einen frischen Faden,\nder Durchgangszähler bleibt also unberührt, und ein Schritt, dessen Ziel ein ganzer Kanal ist, lässt\nsich gar nicht fortsetzen — das wird als fehlerhafte Deklaration gemeldet. `nxc guide channels`." - } - }, - { - "version": "0.76.0", - "date": "2026-08-31", - "items": [ - { - "type": "added", - "en": "**A channel can now declare a cycle.** `steps:` beside `members:` turns a channel's flow into a small\nstate machine — `id`, `target`, `next`, and a way back — so `coder → review → coder` is finally\nsomething you write down rather than something a session has to remember to do. `members:` keeps\nmeaning who belongs to the channel; `steps:` says what happens in which order, and a step is keyed on\nits `id`, so two steps may address the same persona and the same step may run twice in one round.\n\n**The way back is one fixed bit: `nxc reply --needs-rework \"\"`.** It is a\nstatement about somebody else's work — \"this is not deliverable\" — as against `--escalate`, which is\na statement about your own — \"I cannot carry this out\". Where it goes is written in the channel\n(`on_needs_rework:`), so it is a declared signal rather than a token an agent invents. A step that\ndeclares no way back is not offered the bit at all, and one set there anyway is dropped with a\n`verdict_dropped` warning on that reply's own receipt.\n\n`max_passes:` sits on the back edge, not on the channel, and when it is reached the round is\n**escalated upward** rather than stopped quietly: no further attempt is started, every answer travels\nup to whoever commissioned the channel, and the delivery names the ceiling. Commissioning the round\nagain is a fresh round, and a fresh round counts from one. The party sent back is woken with the\nfindings themselves under a notice that says which attempt this is and that its claim on the working\ncopy is still its own — declare `rework_notice:` to say that in your own words. If a step's target is\na whole channel, one member setting the bit sends the round back, and that channel's own\n`on_complete: summarize` still folds the answers into the one judgement it was declared to produce.\n\nSee `nxc guide channels`.", - "de": "**Ein Kanal kann jetzt einen Zyklus deklarieren.** `steps:` neben `members:` macht aus dem Ablauf\neines Kanals eine kleine Zustandsmaschine — `id`, `target`, `next` und ein Weg zurück —, sodass\n`Coder → Review → Coder` endlich etwas ist, das man aufschreibt, statt etwas, an das eine Sitzung\nsich erinnern muss. `members:` bedeutet weiterhin, wer zum Kanal gehört; `steps:` sagt, was in\nwelcher Reihenfolge geschieht, und ein Schritt hängt an seiner `id` — zwei Schritte dürfen also\ndieselbe Persona ansprechen, und derselbe Schritt darf in einer Runde zweimal laufen.\n\n**Der Weg zurück ist ein festes Bit: `nxc reply --needs-rework \"\"`.**\nEs ist eine Aussage über fremde Arbeit — „so ist das nicht lieferbar\" — im Unterschied zu\n`--escalate`, das eine Aussage über die eigene ist — „das kann ich nicht ausführen\". Wohin es führt,\nsteht im Kanal (`on_needs_rework:`), es ist also ein erklärtes Signal und kein Token, das ein Agent\nerfindet. Einem Schritt ohne Rückweg wird das Bit gar nicht erst angeboten; wer es dort dennoch\nsetzt, dessen Wertung fällt weg — mit einer Warnung `verdict_dropped` auf der Quittung genau dieser\nAntwort.\n\n`max_passes:` sitzt an der Rückkante, nicht am Kanal, und ist sie erreicht, wird die Runde **nach\noben eskaliert** statt still gestoppt: kein weiterer Anlauf, jede Antwort wandert zu dem, der den\nKanal beauftragt hat, und die Zustellung nennt die erreichte Decke. Die Runde erneut zu beauftragen\nist eine neue Runde, und eine neue Runde zählt ab eins. Die zurückgeschickte Stelle wird mit den\nBefunden selbst geweckt, unter einem Hinweis, der sagt, der wievielte Anlauf das ist und dass ihr\nAnspruch auf die Arbeitskopie weiterhin ihrer ist — mit `rework_notice:` sagt man das in eigenen\nWorten. Ist das Ziel eines Schritts ein ganzer Kanal, genügt ein Mitglied, das das Bit setzt, um die\nRunde zurückzuschicken, und das `on_complete: summarize` dieses Kanals faltet die Antworten\nweiterhin zu dem einen Urteil, für das er deklariert wurde.\n\nSiehe `nxc guide channels`.", - "facade": "breaking" - } - ], - "notes": { - "en": "### Added\n- **A channel can now declare a cycle.** `steps:` beside `members:` turns a channel's flow into a small\nstate machine — `id`, `target`, `next`, and a way back — so `coder → review → coder` is finally\nsomething you write down rather than something a session has to remember to do. `members:` keeps\nmeaning who belongs to the channel; `steps:` says what happens in which order, and a step is keyed on\nits `id`, so two steps may address the same persona and the same step may run twice in one round.\n\n**The way back is one fixed bit: `nxc reply --needs-rework \"\"`.** It is a\nstatement about somebody else's work — \"this is not deliverable\" — as against `--escalate`, which is\na statement about your own — \"I cannot carry this out\". Where it goes is written in the channel\n(`on_needs_rework:`), so it is a declared signal rather than a token an agent invents. A step that\ndeclares no way back is not offered the bit at all, and one set there anyway is dropped with a\n`verdict_dropped` warning on that reply's own receipt.\n\n`max_passes:` sits on the back edge, not on the channel, and when it is reached the round is\n**escalated upward** rather than stopped quietly: no further attempt is started, every answer travels\nup to whoever commissioned the channel, and the delivery names the ceiling. Commissioning the round\nagain is a fresh round, and a fresh round counts from one. The party sent back is woken with the\nfindings themselves under a notice that says which attempt this is and that its claim on the working\ncopy is still its own — declare `rework_notice:` to say that in your own words. If a step's target is\na whole channel, one member setting the bit sends the round back, and that channel's own\n`on_complete: summarize` still folds the answers into the one judgement it was declared to produce.\n\nSee `nxc guide channels`.\n\n### Facade Contract\n- `breaking` · **A channel can now declare a cycle.** `steps:` beside `members:` turns a channel's flow into a small\nstate machine — `id`, `target`, `next`, and a way back — so `coder → review → coder` is finally\nsomething you write down rather than something a session has to remember to do. `members:` keeps\nmeaning who belongs to the channel; `steps:` says what happens in which order, and a step is keyed on\nits `id`, so two steps may address the same persona and the same step may run twice in one round.\n\n**The way back is one fixed bit: `nxc reply --needs-rework \"\"`.** It is a\nstatement about somebody else's work — \"this is not deliverable\" — as against `--escalate`, which is\na statement about your own — \"I cannot carry this out\". Where it goes is written in the channel\n(`on_needs_rework:`), so it is a declared signal rather than a token an agent invents. A step that\ndeclares no way back is not offered the bit at all, and one set there anyway is dropped with a\n`verdict_dropped` warning on that reply's own receipt.\n\n`max_passes:` sits on the back edge, not on the channel, and when it is reached the round is\n**escalated upward** rather than stopped quietly: no further attempt is started, every answer travels\nup to whoever commissioned the channel, and the delivery names the ceiling. Commissioning the round\nagain is a fresh round, and a fresh round counts from one. The party sent back is woken with the\nfindings themselves under a notice that says which attempt this is and that its claim on the working\ncopy is still its own — declare `rework_notice:` to say that in your own words. If a step's target is\na whole channel, one member setting the bit sends the round back, and that channel's own\n`on_complete: summarize` still folds the answers into the one judgement it was declared to produce.\n\nSee `nxc guide channels`.", - "de": "### Neu\n- **Ein Kanal kann jetzt einen Zyklus deklarieren.** `steps:` neben `members:` macht aus dem Ablauf\neines Kanals eine kleine Zustandsmaschine — `id`, `target`, `next` und ein Weg zurück —, sodass\n`Coder → Review → Coder` endlich etwas ist, das man aufschreibt, statt etwas, an das eine Sitzung\nsich erinnern muss. `members:` bedeutet weiterhin, wer zum Kanal gehört; `steps:` sagt, was in\nwelcher Reihenfolge geschieht, und ein Schritt hängt an seiner `id` — zwei Schritte dürfen also\ndieselbe Persona ansprechen, und derselbe Schritt darf in einer Runde zweimal laufen.\n\n**Der Weg zurück ist ein festes Bit: `nxc reply --needs-rework \"\"`.**\nEs ist eine Aussage über fremde Arbeit — „so ist das nicht lieferbar\" — im Unterschied zu\n`--escalate`, das eine Aussage über die eigene ist — „das kann ich nicht ausführen\". Wohin es führt,\nsteht im Kanal (`on_needs_rework:`), es ist also ein erklärtes Signal und kein Token, das ein Agent\nerfindet. Einem Schritt ohne Rückweg wird das Bit gar nicht erst angeboten; wer es dort dennoch\nsetzt, dessen Wertung fällt weg — mit einer Warnung `verdict_dropped` auf der Quittung genau dieser\nAntwort.\n\n`max_passes:` sitzt an der Rückkante, nicht am Kanal, und ist sie erreicht, wird die Runde **nach\noben eskaliert** statt still gestoppt: kein weiterer Anlauf, jede Antwort wandert zu dem, der den\nKanal beauftragt hat, und die Zustellung nennt die erreichte Decke. Die Runde erneut zu beauftragen\nist eine neue Runde, und eine neue Runde zählt ab eins. Die zurückgeschickte Stelle wird mit den\nBefunden selbst geweckt, unter einem Hinweis, der sagt, der wievielte Anlauf das ist und dass ihr\nAnspruch auf die Arbeitskopie weiterhin ihrer ist — mit `rework_notice:` sagt man das in eigenen\nWorten. Ist das Ziel eines Schritts ein ganzer Kanal, genügt ein Mitglied, das das Bit setzt, um die\nRunde zurückzuschicken, und das `on_complete: summarize` dieses Kanals faltet die Antworten\nweiterhin zu dem einen Urteil, für das er deklariert wurde.\n\nSiehe `nxc guide channels`.\n\n### Facade-Kontrakt\n- `breaking` · **Ein Kanal kann jetzt einen Zyklus deklarieren.** `steps:` neben `members:` macht aus dem Ablauf\neines Kanals eine kleine Zustandsmaschine — `id`, `target`, `next` und ein Weg zurück —, sodass\n`Coder → Review → Coder` endlich etwas ist, das man aufschreibt, statt etwas, an das eine Sitzung\nsich erinnern muss. `members:` bedeutet weiterhin, wer zum Kanal gehört; `steps:` sagt, was in\nwelcher Reihenfolge geschieht, und ein Schritt hängt an seiner `id` — zwei Schritte dürfen also\ndieselbe Persona ansprechen, und derselbe Schritt darf in einer Runde zweimal laufen.\n\n**Der Weg zurück ist ein festes Bit: `nxc reply --needs-rework \"\"`.**\nEs ist eine Aussage über fremde Arbeit — „so ist das nicht lieferbar\" — im Unterschied zu\n`--escalate`, das eine Aussage über die eigene ist — „das kann ich nicht ausführen\". Wohin es führt,\nsteht im Kanal (`on_needs_rework:`), es ist also ein erklärtes Signal und kein Token, das ein Agent\nerfindet. Einem Schritt ohne Rückweg wird das Bit gar nicht erst angeboten; wer es dort dennoch\nsetzt, dessen Wertung fällt weg — mit einer Warnung `verdict_dropped` auf der Quittung genau dieser\nAntwort.\n\n`max_passes:` sitzt an der Rückkante, nicht am Kanal, und ist sie erreicht, wird die Runde **nach\noben eskaliert** statt still gestoppt: kein weiterer Anlauf, jede Antwort wandert zu dem, der den\nKanal beauftragt hat, und die Zustellung nennt die erreichte Decke. Die Runde erneut zu beauftragen\nist eine neue Runde, und eine neue Runde zählt ab eins. Die zurückgeschickte Stelle wird mit den\nBefunden selbst geweckt, unter einem Hinweis, der sagt, der wievielte Anlauf das ist und dass ihr\nAnspruch auf die Arbeitskopie weiterhin ihrer ist — mit `rework_notice:` sagt man das in eigenen\nWorten. Ist das Ziel eines Schritts ein ganzer Kanal, genügt ein Mitglied, das das Bit setzt, um die\nRunde zurückzuschicken, und das `on_complete: summarize` dieses Kanals faltet die Antworten\nweiterhin zu dem einen Urteil, für das er deklariert wurde.\n\nSiehe `nxc guide channels`." - } - }, - { - "version": "0.75.0", - "date": "2026-08-31", - "items": [ - { - "type": "fixed", - "en": "A channel that fans out to several members **and** claims the working copy (`working_tree:\nexclusive` with no `flow:`) could not be opened at all: the first member started, and the hurdle\nthat stops a step while an earlier one is still writing read that live sibling as an earlier step\nand refused the rest — which fails the whole `nxc send --to ` with `Forbidden`. A quorum\nhas exactly one pass; its members are the same step, not each other's previous one, and they are no\nlonger gated on sessions their own call just started. An ordered flow is untouched, and the hurdle\nstill refuses a fresh pass opened while the previous one is writing.", - "de": "Ein Kanal, der auf mehrere Mitglieder auffächert **und** die Arbeitskopie beansprucht\n(`working_tree: exclusive` ohne `flow:`), ließ sich überhaupt nicht mehr öffnen: das erste Mitglied\nstartete, und die Hürde, die einen Schritt anhält, solange ein früherer noch schreibt, las dieses\nlebende Geschwister als früheren Schritt und lehnte die übrigen ab — was den ganzen `nxc send --to\n` mit `Forbidden` scheitern lässt. Ein Quorum hat genau einen Durchgang; seine Mitglieder\nsind derselbe Schritt und nicht der jeweils vorherige, und sie werden nicht mehr an Sitzungen\ngemessen, die ihr eigener Aufruf gerade gestartet hat. Geordnete Abläufe bleiben unverändert, und\ndie Hürde lehnt einen neuen Durchgang weiterhin ab, solange der vorige noch schreibt." - }, - { - "type": "fixed", - "en": "A persona that is commissioned is told, in its own system prompt, to end its turn with `nxc reply\n--thread ` — and until now nothing made sure it could run that command. A persona that declares\nno `tools:` (the default) reached the agent runtime with an empty auto-approval list, so its own\nreply came back \"This command requires approval\", and the sidecar's teardown then posted a failure\nin its name. The expectation was discharged, the working copy was handed on, and from the outside\nthe round looked healthy. Now the trigger that imposes the obligation grants what discharging it\ntakes, at the one seam every spawn passes through — without narrowing the toolset an omitted\n`tools:` gives a session.\n\nAnd a reply the runtime wrote is no longer distinguishable only by reading it: `nxc status` carries\n`substituted` beside `escalated`. The two are independent — a session that died is both, a session\nthat ended quietly without answering is only substituted, and an agent's own `nxc reply --escalate`\nis only escalated. Branch on the pair wherever you would otherwise read \"answered\" as \"the agent\nanswered\".\n\nThe mark also travels into the CONSOLIDATION. A channel that folds its members' answers hands them\nto a model; a member whose session ended without answering used to arrive there looking like an\nopinion, so a quorum could produce a verdict out of a member that never spoke. Such a message is now\nmarked in what the consolidator is handed, together with one sentence saying what the mark means.\nA round in which nothing was substituted reads exactly as it did before.\n\nFor embedders: `nexus_chat::worker::RoleSpec` gains `granted_tools`, `nexus_chat::model::Refs` gains\n`substituted`, `ChatStore::last_reply_kinds`/`last_reply_kinds_all` now answer with a `LastReply`\nrow instead of a `(thread, kind)` tuple, and the two consolidator composers take a `CollectedReply`\ninstead of a `(sender, body)` pair. A custom `Worker` should union `granted_tools` into whatever\nauto-approval list its runtime has.", - "de": "Eine beauftragte Persona bekommt in ihrem eigenen Systemprompt gesagt, sie solle ihren Zug mit `nxc\nreply --thread ` beenden — und bisher stellte nichts sicher, dass sie diesen Befehl ausführen\nkann. Eine Persona ohne `tools:`-Angabe (die Vorgabe) erreichte die Agenten-Laufzeit mit einer leeren\nFreigabeliste, ihre eigene Antwort wurde mit „This command requires approval\" abgelehnt, und der\nAbbau-Block des Sidecars setzte danach eine Fehlantwort in ihrem Namen. Die Erwartung galt als\nerfüllt, die Arbeitskopie wurde weitergereicht, und von außen sah die Runde gesund aus. Jetzt gibt\nder Trigger, der die Verpflichtung setzt, auch das Mittel dazu — an der einen Naht, die jeder Start\npassiert, und ohne den Werkzeugsatz zu verengen, den ein weggelassenes `tools:` gewährt.\n\nUnd eine von der Laufzeit geschriebene Antwort ist nicht mehr nur am Text zu erkennen: `nxc status`\nträgt `substituted` neben `escalated`. Die beiden sind unabhängig — eine gestorbene Sitzung ist\nbeides, eine still beendete ohne Antwort nur `substituted`, und das eigene `nxc reply --escalate`\neines Agenten nur `escalated`. Verzweigen Sie auf beide, wo Sie sonst „answered\" als „der Agent hat\ngeantwortet\" lesen würden.\n\nDie Markierung wandert auch in die KONSOLIDIERUNG. Ein Kanal, der die Antworten seiner Mitglieder\nfaltet, übergibt sie einem Modell; ein Mitglied, dessen Sitzung ohne Antwort endete, kam dort bisher\nwie eine Meinung an — ein Quorum konnte also ein Urteil aus einem Mitglied bilden, das nie gesprochen\nhat. Eine solche Nachricht ist jetzt in dem markiert, was der Konsolidierer bekommt, samt einem Satz,\nder sagt, was die Markierung bedeutet. Eine Runde ohne Einlösung liest sich genau wie zuvor.\n\nFür Einbettende: `nexus_chat::worker::RoleSpec` bekommt `granted_tools`, `nexus_chat::model::Refs`\nbekommt `substituted`, `ChatStore::last_reply_kinds`/`last_reply_kinds_all` antworten jetzt mit\neiner `LastReply`-Zeile statt mit einem `(thread, kind)`-Tupel, und die beiden\nKonsolidierer-Komponisten nehmen ein `CollectedReply` statt eines `(sender, body)`-Paars. Ein\neigener `Worker` sollte `granted_tools` in die Freigabeliste seiner Laufzeit aufnehmen.", - "facade": "breaking" - }, - { - "type": "changed", - "en": "The shipped example team's `review` quorum is now `flow: sequential`. Three of its four reviewers run\nthe project's own quality gates themselves, so a quorum that fanned out handed one working copy and\none build directory to three concurrent builds — merely slow when their shapes match, and mutually\ndestructive when they diverge. Each reviewer still runs the gates itself and forms its own verdict;\nonly one of them is in the checkout at a time. Copy the pattern for any quorum whose members BUILD;\na quorum whose members only read still wants the fan-out. The channels guide carries the trade-off,\nincluding what ordering costs.", - "de": "Das `review`-Quorum der ausgelieferten Beispielsammlung ist jetzt `flow: sequential`. Drei seiner\nvier Prüfer lassen die Qualitätstore des Projekts selbst laufen — ein auffächerndes Quorum übergab\nalso eine Arbeitskopie und ein Build-Verzeichnis an drei gleichzeitige Bauläufe: bloß langsam,\nsolange ihre Form gleich ist, und gegenseitig zerstörerisch, sobald sie auseinandergeht. Jeder\nPrüfer lässt die Tore weiterhin selbst laufen und bildet sein eigenes Urteil; nur ist immer nur einer\nin der Arbeitskopie. Übernehmen Sie das Muster für jedes Quorum, dessen Mitglieder BAUEN; eines, das\nnur liest, will weiterhin auffächern. Der Kanal-Leitfaden nennt die Abwägung samt dem, was die\nReihenfolge kostet." - } - ], - "notes": { - "en": "### Changed\n- The shipped example team's `review` quorum is now `flow: sequential`. Three of its four reviewers run\nthe project's own quality gates themselves, so a quorum that fanned out handed one working copy and\none build directory to three concurrent builds — merely slow when their shapes match, and mutually\ndestructive when they diverge. Each reviewer still runs the gates itself and forms its own verdict;\nonly one of them is in the checkout at a time. Copy the pattern for any quorum whose members BUILD;\na quorum whose members only read still wants the fan-out. The channels guide carries the trade-off,\nincluding what ordering costs.\n\n### Fixed\n- A channel that fans out to several members **and** claims the working copy (`working_tree:\nexclusive` with no `flow:`) could not be opened at all: the first member started, and the hurdle\nthat stops a step while an earlier one is still writing read that live sibling as an earlier step\nand refused the rest — which fails the whole `nxc send --to ` with `Forbidden`. A quorum\nhas exactly one pass; its members are the same step, not each other's previous one, and they are no\nlonger gated on sessions their own call just started. An ordered flow is untouched, and the hurdle\nstill refuses a fresh pass opened while the previous one is writing.\n- A persona that is commissioned is told, in its own system prompt, to end its turn with `nxc reply\n--thread ` — and until now nothing made sure it could run that command. A persona that declares\nno `tools:` (the default) reached the agent runtime with an empty auto-approval list, so its own\nreply came back \"This command requires approval\", and the sidecar's teardown then posted a failure\nin its name. The expectation was discharged, the working copy was handed on, and from the outside\nthe round looked healthy. Now the trigger that imposes the obligation grants what discharging it\ntakes, at the one seam every spawn passes through — without narrowing the toolset an omitted\n`tools:` gives a session.\n\nAnd a reply the runtime wrote is no longer distinguishable only by reading it: `nxc status` carries\n`substituted` beside `escalated`. The two are independent — a session that died is both, a session\nthat ended quietly without answering is only substituted, and an agent's own `nxc reply --escalate`\nis only escalated. Branch on the pair wherever you would otherwise read \"answered\" as \"the agent\nanswered\".\n\nThe mark also travels into the CONSOLIDATION. A channel that folds its members' answers hands them\nto a model; a member whose session ended without answering used to arrive there looking like an\nopinion, so a quorum could produce a verdict out of a member that never spoke. Such a message is now\nmarked in what the consolidator is handed, together with one sentence saying what the mark means.\nA round in which nothing was substituted reads exactly as it did before.\n\nFor embedders: `nexus_chat::worker::RoleSpec` gains `granted_tools`, `nexus_chat::model::Refs` gains\n`substituted`, `ChatStore::last_reply_kinds`/`last_reply_kinds_all` now answer with a `LastReply`\nrow instead of a `(thread, kind)` tuple, and the two consolidator composers take a `CollectedReply`\ninstead of a `(sender, body)` pair. A custom `Worker` should union `granted_tools` into whatever\nauto-approval list its runtime has.\n\n### Facade Contract\n- `breaking` · A persona that is commissioned is told, in its own system prompt, to end its turn with `nxc reply\n--thread ` — and until now nothing made sure it could run that command. A persona that declares\nno `tools:` (the default) reached the agent runtime with an empty auto-approval list, so its own\nreply came back \"This command requires approval\", and the sidecar's teardown then posted a failure\nin its name. The expectation was discharged, the working copy was handed on, and from the outside\nthe round looked healthy. Now the trigger that imposes the obligation grants what discharging it\ntakes, at the one seam every spawn passes through — without narrowing the toolset an omitted\n`tools:` gives a session.\n\nAnd a reply the runtime wrote is no longer distinguishable only by reading it: `nxc status` carries\n`substituted` beside `escalated`. The two are independent — a session that died is both, a session\nthat ended quietly without answering is only substituted, and an agent's own `nxc reply --escalate`\nis only escalated. Branch on the pair wherever you would otherwise read \"answered\" as \"the agent\nanswered\".\n\nThe mark also travels into the CONSOLIDATION. A channel that folds its members' answers hands them\nto a model; a member whose session ended without answering used to arrive there looking like an\nopinion, so a quorum could produce a verdict out of a member that never spoke. Such a message is now\nmarked in what the consolidator is handed, together with one sentence saying what the mark means.\nA round in which nothing was substituted reads exactly as it did before.\n\nFor embedders: `nexus_chat::worker::RoleSpec` gains `granted_tools`, `nexus_chat::model::Refs` gains\n`substituted`, `ChatStore::last_reply_kinds`/`last_reply_kinds_all` now answer with a `LastReply`\nrow instead of a `(thread, kind)` tuple, and the two consolidator composers take a `CollectedReply`\ninstead of a `(sender, body)` pair. A custom `Worker` should union `granted_tools` into whatever\nauto-approval list its runtime has.", - "de": "### Geändert\n- Das `review`-Quorum der ausgelieferten Beispielsammlung ist jetzt `flow: sequential`. Drei seiner\nvier Prüfer lassen die Qualitätstore des Projekts selbst laufen — ein auffächerndes Quorum übergab\nalso eine Arbeitskopie und ein Build-Verzeichnis an drei gleichzeitige Bauläufe: bloß langsam,\nsolange ihre Form gleich ist, und gegenseitig zerstörerisch, sobald sie auseinandergeht. Jeder\nPrüfer lässt die Tore weiterhin selbst laufen und bildet sein eigenes Urteil; nur ist immer nur einer\nin der Arbeitskopie. Übernehmen Sie das Muster für jedes Quorum, dessen Mitglieder BAUEN; eines, das\nnur liest, will weiterhin auffächern. Der Kanal-Leitfaden nennt die Abwägung samt dem, was die\nReihenfolge kostet.\n\n### Behoben\n- Ein Kanal, der auf mehrere Mitglieder auffächert **und** die Arbeitskopie beansprucht\n(`working_tree: exclusive` ohne `flow:`), ließ sich überhaupt nicht mehr öffnen: das erste Mitglied\nstartete, und die Hürde, die einen Schritt anhält, solange ein früherer noch schreibt, las dieses\nlebende Geschwister als früheren Schritt und lehnte die übrigen ab — was den ganzen `nxc send --to\n` mit `Forbidden` scheitern lässt. Ein Quorum hat genau einen Durchgang; seine Mitglieder\nsind derselbe Schritt und nicht der jeweils vorherige, und sie werden nicht mehr an Sitzungen\ngemessen, die ihr eigener Aufruf gerade gestartet hat. Geordnete Abläufe bleiben unverändert, und\ndie Hürde lehnt einen neuen Durchgang weiterhin ab, solange der vorige noch schreibt.\n- Eine beauftragte Persona bekommt in ihrem eigenen Systemprompt gesagt, sie solle ihren Zug mit `nxc\nreply --thread ` beenden — und bisher stellte nichts sicher, dass sie diesen Befehl ausführen\nkann. Eine Persona ohne `tools:`-Angabe (die Vorgabe) erreichte die Agenten-Laufzeit mit einer leeren\nFreigabeliste, ihre eigene Antwort wurde mit „This command requires approval\" abgelehnt, und der\nAbbau-Block des Sidecars setzte danach eine Fehlantwort in ihrem Namen. Die Erwartung galt als\nerfüllt, die Arbeitskopie wurde weitergereicht, und von außen sah die Runde gesund aus. Jetzt gibt\nder Trigger, der die Verpflichtung setzt, auch das Mittel dazu — an der einen Naht, die jeder Start\npassiert, und ohne den Werkzeugsatz zu verengen, den ein weggelassenes `tools:` gewährt.\n\nUnd eine von der Laufzeit geschriebene Antwort ist nicht mehr nur am Text zu erkennen: `nxc status`\nträgt `substituted` neben `escalated`. Die beiden sind unabhängig — eine gestorbene Sitzung ist\nbeides, eine still beendete ohne Antwort nur `substituted`, und das eigene `nxc reply --escalate`\neines Agenten nur `escalated`. Verzweigen Sie auf beide, wo Sie sonst „answered\" als „der Agent hat\ngeantwortet\" lesen würden.\n\nDie Markierung wandert auch in die KONSOLIDIERUNG. Ein Kanal, der die Antworten seiner Mitglieder\nfaltet, übergibt sie einem Modell; ein Mitglied, dessen Sitzung ohne Antwort endete, kam dort bisher\nwie eine Meinung an — ein Quorum konnte also ein Urteil aus einem Mitglied bilden, das nie gesprochen\nhat. Eine solche Nachricht ist jetzt in dem markiert, was der Konsolidierer bekommt, samt einem Satz,\nder sagt, was die Markierung bedeutet. Eine Runde ohne Einlösung liest sich genau wie zuvor.\n\nFür Einbettende: `nexus_chat::worker::RoleSpec` bekommt `granted_tools`, `nexus_chat::model::Refs`\nbekommt `substituted`, `ChatStore::last_reply_kinds`/`last_reply_kinds_all` antworten jetzt mit\neiner `LastReply`-Zeile statt mit einem `(thread, kind)`-Tupel, und die beiden\nKonsolidierer-Komponisten nehmen ein `CollectedReply` statt eines `(sender, body)`-Paars. Ein\neigener `Worker` sollte `granted_tools` in die Freigabeliste seiner Laufzeit aufnehmen.\n\n### Facade-Kontrakt\n- `breaking` · Eine beauftragte Persona bekommt in ihrem eigenen Systemprompt gesagt, sie solle ihren Zug mit `nxc\nreply --thread ` beenden — und bisher stellte nichts sicher, dass sie diesen Befehl ausführen\nkann. Eine Persona ohne `tools:`-Angabe (die Vorgabe) erreichte die Agenten-Laufzeit mit einer leeren\nFreigabeliste, ihre eigene Antwort wurde mit „This command requires approval\" abgelehnt, und der\nAbbau-Block des Sidecars setzte danach eine Fehlantwort in ihrem Namen. Die Erwartung galt als\nerfüllt, die Arbeitskopie wurde weitergereicht, und von außen sah die Runde gesund aus. Jetzt gibt\nder Trigger, der die Verpflichtung setzt, auch das Mittel dazu — an der einen Naht, die jeder Start\npassiert, und ohne den Werkzeugsatz zu verengen, den ein weggelassenes `tools:` gewährt.\n\nUnd eine von der Laufzeit geschriebene Antwort ist nicht mehr nur am Text zu erkennen: `nxc status`\nträgt `substituted` neben `escalated`. Die beiden sind unabhängig — eine gestorbene Sitzung ist\nbeides, eine still beendete ohne Antwort nur `substituted`, und das eigene `nxc reply --escalate`\neines Agenten nur `escalated`. Verzweigen Sie auf beide, wo Sie sonst „answered\" als „der Agent hat\ngeantwortet\" lesen würden.\n\nDie Markierung wandert auch in die KONSOLIDIERUNG. Ein Kanal, der die Antworten seiner Mitglieder\nfaltet, übergibt sie einem Modell; ein Mitglied, dessen Sitzung ohne Antwort endete, kam dort bisher\nwie eine Meinung an — ein Quorum konnte also ein Urteil aus einem Mitglied bilden, das nie gesprochen\nhat. Eine solche Nachricht ist jetzt in dem markiert, was der Konsolidierer bekommt, samt einem Satz,\nder sagt, was die Markierung bedeutet. Eine Runde ohne Einlösung liest sich genau wie zuvor.\n\nFür Einbettende: `nexus_chat::worker::RoleSpec` bekommt `granted_tools`, `nexus_chat::model::Refs`\nbekommt `substituted`, `ChatStore::last_reply_kinds`/`last_reply_kinds_all` antworten jetzt mit\neiner `LastReply`-Zeile statt mit einem `(thread, kind)`-Tupel, und die beiden\nKonsolidierer-Komponisten nehmen ein `CollectedReply` statt eines `(sender, body)`-Paars. Ein\neigener `Worker` sollte `granted_tools` in die Freigabeliste seiner Laufzeit aufnehmen." - } - }, - { - "version": "0.74.0", - "date": "2026-08-30", - "items": [ - { - "type": "added", - "en": "**One machine can now run several nexus-flow background services.** Set\n`NXS_SERVICE_INSTANCE=nexus-flow-dev` for a checkout (an `.envrc` is the natural place) and that\ncheckout gets its own service: its own launchd agent, its own `~/.nexusflow-dev` directory, and\ntherefore its own workspace registry, single-instance lock, heartbeat and log. Installing one never\nremoves another, and the running service reads which instance it is from the name it was started\nas, so nothing has to be configured twice. `nxs sync daemon status`, `install` and `uninstall` all\nname the instance they are talking about.\n\nTwo instances cannot contend for a lock, but they can both attend the same workspace — and then\nboth would tick its deadlines. `nxs sync bind`, `nxs sync daemon status` and the running service\nnow say so when that happens.\n\nMachines that run only the default service are unaffected: the label, the directory and the alias\nare unchanged — an existing installation keeps the service it has, under the name and in the\ndirectory it has.\n\n*For embedders:* the `nxs-service` launchd seam is now instance-driven and its shape changed with\nit. `launchd::LABEL` is gone (ask `Instance::label()`), `render_plist`, `install_with` and\n`uninstall_with` take the instance, and `Heartbeat` carries an added optional `instance`.\n`launchd::install()`/`uninstall()`/`plist_path()` keep their signatures and act on the instance the\nprocess resolves.", - "de": "**Ein Rechner kann jetzt mehrere nexus-flow-Hintergrunddienste fahren.** Setzen Sie für eine\nArbeitskopie `NXS_SERVICE_INSTANCE=nexus-flow-dev` (eine `.envrc` ist der naheliegende Ort), und\ndiese Arbeitskopie bekommt ihren eigenen Dienst: einen eigenen launchd-Agenten, ein eigenes\nVerzeichnis `~/.nexusflow-dev` und damit eine eigene Arbeitsbereichs-Registry, einen eigenen\nEinzelinstanz-Riegel, einen eigenen Herzschlag und ein eigenes Protokoll. Die Installation der einen\nentfernt nie eine andere, und der laufende Dienst erkennt an dem Namen, unter dem er gestartet\nwurde, welche Instanz er ist — es muss also nichts doppelt eingestellt werden. `nxs sync daemon\nstatus`, `install` und `uninstall` nennen jeweils die Instanz, um die es geht.\n\nZwei Instanzen können sich nicht um einen Riegel streiten, wohl aber denselben Arbeitsbereich\nbetreuen — und würden dann beide seine Fristen ticken. `nxs sync bind`, `nxs sync daemon status` und\nder laufende Dienst sagen es jetzt, wenn das so ist.\n\nFür Rechner, die nur den Standarddienst fahren, ändert sich nichts: Etikett, Verzeichnis und\nVerknüpfung bleiben, wie sie waren — eine bestehende Installation behält den Dienst, den sie hat,\nunter dem Namen und im Verzeichnis, das sie hat.\n\n*Für Einbettende:* die launchd-Naht von `nxs-service` wird jetzt von der Instanz bestimmt und hat\nsich dabei in der Form geändert. `launchd::LABEL` ist weg (`Instance::label()` beantwortet das),\n`render_plist`, `install_with` und `uninstall_with` nehmen die Instanz entgegen, und `Heartbeat`\nträgt ein zusätzliches optionales `instance`. `launchd::install()`/`uninstall()`/`plist_path()`\nbehalten ihre Signatur und wirken auf die Instanz, die der Prozess auflöst.", - "facade": "breaking" - }, - { - "type": "added", - "en": "**The background service now keeps the machine awake while a run is working.** A round that runs\nunattended for hours used to be lost to the Mac falling asleep, and nothing could hold an\nassertion for it: `nxc` is over the moment it has sent, and an embedding app is gone exactly when\nit would be needed. The service holds one while any session is alive in any workspace it attends,\nand gives it back the moment the last one ends — visible by name, with the instance that holds it,\nin `pmset -g assertions`.\n\nIdle sleep only: the display still sleeps, a closed lid still sleeps, and Sleep from the menu still\nworks. The assertion is owned by the service process, so a crash releases it rather than leaving\nthe machine awake for good.", - "de": "**Der Hintergrunddienst hält den Rechner jetzt wach, solange ein Lauf arbeitet.** Eine Runde, die\nstundenlang unbeaufsichtigt läuft, ging bisher verloren, wenn der Mac einschlief — und nichts\nkonnte eine Sperre für sie halten: `nxc` ist nach dem Absenden vorbei, und eine einbettende App ist\ngenau dann weg, wenn sie gebraucht würde. Der Dienst hält eine Sperre, solange in irgendeinem\nbetreuten Arbeitsbereich eine Sitzung lebt, und gibt sie in dem Moment zurück, in dem die letzte\nvorbei ist — sichtbar unter ihrem Namen, samt haltender Instanz, in `pmset -g assertions`.\n\nNur der Ruhezustand aus Untätigkeit: der Bildschirm schläft weiterhin ein, ein zugeklapptes Gerät\nschläft weiterhin ein, und „Ruhezustand\" aus dem Menü funktioniert weiterhin. Die Sperre gehört dem\nDienstprozess, ein Absturz gibt sie also frei, statt den Rechner dauerhaft wachzuhalten.", - "facade": "changed" - } - ], - "notes": { - "en": "### Added\n- **One machine can now run several nexus-flow background services.** Set\n`NXS_SERVICE_INSTANCE=nexus-flow-dev` for a checkout (an `.envrc` is the natural place) and that\ncheckout gets its own service: its own launchd agent, its own `~/.nexusflow-dev` directory, and\ntherefore its own workspace registry, single-instance lock, heartbeat and log. Installing one never\nremoves another, and the running service reads which instance it is from the name it was started\nas, so nothing has to be configured twice. `nxs sync daemon status`, `install` and `uninstall` all\nname the instance they are talking about.\n\nTwo instances cannot contend for a lock, but they can both attend the same workspace — and then\nboth would tick its deadlines. `nxs sync bind`, `nxs sync daemon status` and the running service\nnow say so when that happens.\n\nMachines that run only the default service are unaffected: the label, the directory and the alias\nare unchanged — an existing installation keeps the service it has, under the name and in the\ndirectory it has.\n\n*For embedders:* the `nxs-service` launchd seam is now instance-driven and its shape changed with\nit. `launchd::LABEL` is gone (ask `Instance::label()`), `render_plist`, `install_with` and\n`uninstall_with` take the instance, and `Heartbeat` carries an added optional `instance`.\n`launchd::install()`/`uninstall()`/`plist_path()` keep their signatures and act on the instance the\nprocess resolves.\n- **The background service now keeps the machine awake while a run is working.** A round that runs\nunattended for hours used to be lost to the Mac falling asleep, and nothing could hold an\nassertion for it: `nxc` is over the moment it has sent, and an embedding app is gone exactly when\nit would be needed. The service holds one while any session is alive in any workspace it attends,\nand gives it back the moment the last one ends — visible by name, with the instance that holds it,\nin `pmset -g assertions`.\n\nIdle sleep only: the display still sleeps, a closed lid still sleeps, and Sleep from the menu still\nworks. The assertion is owned by the service process, so a crash releases it rather than leaving\nthe machine awake for good.\n\n### Facade Contract\n- `breaking` · **One machine can now run several nexus-flow background services.** Set\n`NXS_SERVICE_INSTANCE=nexus-flow-dev` for a checkout (an `.envrc` is the natural place) and that\ncheckout gets its own service: its own launchd agent, its own `~/.nexusflow-dev` directory, and\ntherefore its own workspace registry, single-instance lock, heartbeat and log. Installing one never\nremoves another, and the running service reads which instance it is from the name it was started\nas, so nothing has to be configured twice. `nxs sync daemon status`, `install` and `uninstall` all\nname the instance they are talking about.\n\nTwo instances cannot contend for a lock, but they can both attend the same workspace — and then\nboth would tick its deadlines. `nxs sync bind`, `nxs sync daemon status` and the running service\nnow say so when that happens.\n\nMachines that run only the default service are unaffected: the label, the directory and the alias\nare unchanged — an existing installation keeps the service it has, under the name and in the\ndirectory it has.\n\n*For embedders:* the `nxs-service` launchd seam is now instance-driven and its shape changed with\nit. `launchd::LABEL` is gone (ask `Instance::label()`), `render_plist`, `install_with` and\n`uninstall_with` take the instance, and `Heartbeat` carries an added optional `instance`.\n`launchd::install()`/`uninstall()`/`plist_path()` keep their signatures and act on the instance the\nprocess resolves.\n- `changed` · **The background service now keeps the machine awake while a run is working.** A round that runs\nunattended for hours used to be lost to the Mac falling asleep, and nothing could hold an\nassertion for it: `nxc` is over the moment it has sent, and an embedding app is gone exactly when\nit would be needed. The service holds one while any session is alive in any workspace it attends,\nand gives it back the moment the last one ends — visible by name, with the instance that holds it,\nin `pmset -g assertions`.\n\nIdle sleep only: the display still sleeps, a closed lid still sleeps, and Sleep from the menu still\nworks. The assertion is owned by the service process, so a crash releases it rather than leaving\nthe machine awake for good.", - "de": "### Neu\n- **Ein Rechner kann jetzt mehrere nexus-flow-Hintergrunddienste fahren.** Setzen Sie für eine\nArbeitskopie `NXS_SERVICE_INSTANCE=nexus-flow-dev` (eine `.envrc` ist der naheliegende Ort), und\ndiese Arbeitskopie bekommt ihren eigenen Dienst: einen eigenen launchd-Agenten, ein eigenes\nVerzeichnis `~/.nexusflow-dev` und damit eine eigene Arbeitsbereichs-Registry, einen eigenen\nEinzelinstanz-Riegel, einen eigenen Herzschlag und ein eigenes Protokoll. Die Installation der einen\nentfernt nie eine andere, und der laufende Dienst erkennt an dem Namen, unter dem er gestartet\nwurde, welche Instanz er ist — es muss also nichts doppelt eingestellt werden. `nxs sync daemon\nstatus`, `install` und `uninstall` nennen jeweils die Instanz, um die es geht.\n\nZwei Instanzen können sich nicht um einen Riegel streiten, wohl aber denselben Arbeitsbereich\nbetreuen — und würden dann beide seine Fristen ticken. `nxs sync bind`, `nxs sync daemon status` und\nder laufende Dienst sagen es jetzt, wenn das so ist.\n\nFür Rechner, die nur den Standarddienst fahren, ändert sich nichts: Etikett, Verzeichnis und\nVerknüpfung bleiben, wie sie waren — eine bestehende Installation behält den Dienst, den sie hat,\nunter dem Namen und im Verzeichnis, das sie hat.\n\n*Für Einbettende:* die launchd-Naht von `nxs-service` wird jetzt von der Instanz bestimmt und hat\nsich dabei in der Form geändert. `launchd::LABEL` ist weg (`Instance::label()` beantwortet das),\n`render_plist`, `install_with` und `uninstall_with` nehmen die Instanz entgegen, und `Heartbeat`\nträgt ein zusätzliches optionales `instance`. `launchd::install()`/`uninstall()`/`plist_path()`\nbehalten ihre Signatur und wirken auf die Instanz, die der Prozess auflöst.\n- **Der Hintergrunddienst hält den Rechner jetzt wach, solange ein Lauf arbeitet.** Eine Runde, die\nstundenlang unbeaufsichtigt läuft, ging bisher verloren, wenn der Mac einschlief — und nichts\nkonnte eine Sperre für sie halten: `nxc` ist nach dem Absenden vorbei, und eine einbettende App ist\ngenau dann weg, wenn sie gebraucht würde. Der Dienst hält eine Sperre, solange in irgendeinem\nbetreuten Arbeitsbereich eine Sitzung lebt, und gibt sie in dem Moment zurück, in dem die letzte\nvorbei ist — sichtbar unter ihrem Namen, samt haltender Instanz, in `pmset -g assertions`.\n\nNur der Ruhezustand aus Untätigkeit: der Bildschirm schläft weiterhin ein, ein zugeklapptes Gerät\nschläft weiterhin ein, und „Ruhezustand\" aus dem Menü funktioniert weiterhin. Die Sperre gehört dem\nDienstprozess, ein Absturz gibt sie also frei, statt den Rechner dauerhaft wachzuhalten.\n\n### Facade-Kontrakt\n- `breaking` · **Ein Rechner kann jetzt mehrere nexus-flow-Hintergrunddienste fahren.** Setzen Sie für eine\nArbeitskopie `NXS_SERVICE_INSTANCE=nexus-flow-dev` (eine `.envrc` ist der naheliegende Ort), und\ndiese Arbeitskopie bekommt ihren eigenen Dienst: einen eigenen launchd-Agenten, ein eigenes\nVerzeichnis `~/.nexusflow-dev` und damit eine eigene Arbeitsbereichs-Registry, einen eigenen\nEinzelinstanz-Riegel, einen eigenen Herzschlag und ein eigenes Protokoll. Die Installation der einen\nentfernt nie eine andere, und der laufende Dienst erkennt an dem Namen, unter dem er gestartet\nwurde, welche Instanz er ist — es muss also nichts doppelt eingestellt werden. `nxs sync daemon\nstatus`, `install` und `uninstall` nennen jeweils die Instanz, um die es geht.\n\nZwei Instanzen können sich nicht um einen Riegel streiten, wohl aber denselben Arbeitsbereich\nbetreuen — und würden dann beide seine Fristen ticken. `nxs sync bind`, `nxs sync daemon status` und\nder laufende Dienst sagen es jetzt, wenn das so ist.\n\nFür Rechner, die nur den Standarddienst fahren, ändert sich nichts: Etikett, Verzeichnis und\nVerknüpfung bleiben, wie sie waren — eine bestehende Installation behält den Dienst, den sie hat,\nunter dem Namen und im Verzeichnis, das sie hat.\n\n*Für Einbettende:* die launchd-Naht von `nxs-service` wird jetzt von der Instanz bestimmt und hat\nsich dabei in der Form geändert. `launchd::LABEL` ist weg (`Instance::label()` beantwortet das),\n`render_plist`, `install_with` und `uninstall_with` nehmen die Instanz entgegen, und `Heartbeat`\nträgt ein zusätzliches optionales `instance`. `launchd::install()`/`uninstall()`/`plist_path()`\nbehalten ihre Signatur und wirken auf die Instanz, die der Prozess auflöst.\n- `changed` · **Der Hintergrunddienst hält den Rechner jetzt wach, solange ein Lauf arbeitet.** Eine Runde, die\nstundenlang unbeaufsichtigt läuft, ging bisher verloren, wenn der Mac einschlief — und nichts\nkonnte eine Sperre für sie halten: `nxc` ist nach dem Absenden vorbei, und eine einbettende App ist\ngenau dann weg, wenn sie gebraucht würde. Der Dienst hält eine Sperre, solange in irgendeinem\nbetreuten Arbeitsbereich eine Sitzung lebt, und gibt sie in dem Moment zurück, in dem die letzte\nvorbei ist — sichtbar unter ihrem Namen, samt haltender Instanz, in `pmset -g assertions`.\n\nNur der Ruhezustand aus Untätigkeit: der Bildschirm schläft weiterhin ein, ein zugeklapptes Gerät\nschläft weiterhin ein, und „Ruhezustand\" aus dem Menü funktioniert weiterhin. Die Sperre gehört dem\nDienstprozess, ein Absturz gibt sie also frei, statt den Rechner dauerhaft wachzuhalten." - } - }, - { - "version": "0.73.0", - "date": "2026-08-30", - "items": [ - { - "type": "added", - "en": "**A background service that is not running now says so, instead of leaving you to notice.** One\nservice keeps every deadline on your machine and is the only thing that syncs, so while it is down,\ndeclared windows do not fire — a round that should release hangs indefinitely — and your op log\nneither pushes nor pulls. Both halves used to be silent. `nxc send`, `nxc reply` and `nxc tick` now\ncarry a `service_not_running` entry in their `warnings` array, naming both consequences and the\ncommand that starts it. It appears only in a workspace registered with the service, and it does not\nchange the exit code: your call did everything it was asked.\n\n**The service also says which binary it runs, and a dead program link is loud everywhere.**\n`nxs sync daemon status` now reports the program the running service started from, its version, and\nwhat the `nexus-flow` link points at right now — and says so when those have drifted apart.\n`nxs self-update` names the divergence after an update rather than letting the service quietly stay\non an older build. And if the link points at a file that is gone — the one failure launchd cannot\nreport, whose only symptom is a service that never starts — every `nxs`, `nxf`, `nxm` and `nxc`\ninvocation says it, names the dead target, and gives you the command that repairs it.", - "de": "**Ein Hintergrunddienst, der nicht läuft, sagt das jetzt, statt es Sie bemerken zu lassen.** Ein\nDienst hält jede Frist Ihrer Maschine und ist das Einzige, was synchronisiert. Solange er steht,\nfeuern deklarierte Fenster nicht — eine Runde, die sich lösen sollte, hängt unbegrenzt — und Ihr\nOps-Log schiebt und holt nichts mehr. Beide Hälften waren bisher still. `nxc send`, `nxc reply` und\n`nxc tick` tragen jetzt einen Eintrag `service_not_running` im `warnings`-Feld, der beide Folgen und\nden Befehl zum Starten benennt. Er erscheint nur in einem Arbeitsbereich, der beim Dienst angemeldet\nist, und ändert den Exit-Code nicht: Ihr Aufruf hat alles getan, was er sollte.\n\n**Der Dienst sagt außerdem, welche Binärdatei er fährt, und eine tote Programmverknüpfung ist\nüberall laut.** `nxs sync daemon status` meldet jetzt das Programm, aus dem der laufende Dienst\ngestartet ist, dessen Version und worauf die `nexus-flow`-Verknüpfung gerade zeigt — und sagt es,\nwenn beides auseinandergelaufen ist. `nxs self-update` benennt die Abweichung nach einer\nAktualisierung, statt den Dienst still auf einem älteren Bau stehen zu lassen. Und zeigt die\nVerknüpfung auf eine Datei, die es nicht mehr gibt — der eine Fehler, den launchd nicht melden kann\nund dessen einziges Symptom ein Dienst ist, der nie startet —, sagt das jeder Aufruf von `nxs`,\n`nxf`, `nxm` und `nxc`, benennt das tote Ziel und nennt den Befehl, der es behebt.", - "facade": "changed" - } - ], - "notes": { - "en": "### Added\n- **A background service that is not running now says so, instead of leaving you to notice.** One\nservice keeps every deadline on your machine and is the only thing that syncs, so while it is down,\ndeclared windows do not fire — a round that should release hangs indefinitely — and your op log\nneither pushes nor pulls. Both halves used to be silent. `nxc send`, `nxc reply` and `nxc tick` now\ncarry a `service_not_running` entry in their `warnings` array, naming both consequences and the\ncommand that starts it. It appears only in a workspace registered with the service, and it does not\nchange the exit code: your call did everything it was asked.\n\n**The service also says which binary it runs, and a dead program link is loud everywhere.**\n`nxs sync daemon status` now reports the program the running service started from, its version, and\nwhat the `nexus-flow` link points at right now — and says so when those have drifted apart.\n`nxs self-update` names the divergence after an update rather than letting the service quietly stay\non an older build. And if the link points at a file that is gone — the one failure launchd cannot\nreport, whose only symptom is a service that never starts — every `nxs`, `nxf`, `nxm` and `nxc`\ninvocation says it, names the dead target, and gives you the command that repairs it.\n\n### Facade Contract\n- `changed` · **A background service that is not running now says so, instead of leaving you to notice.** One\nservice keeps every deadline on your machine and is the only thing that syncs, so while it is down,\ndeclared windows do not fire — a round that should release hangs indefinitely — and your op log\nneither pushes nor pulls. Both halves used to be silent. `nxc send`, `nxc reply` and `nxc tick` now\ncarry a `service_not_running` entry in their `warnings` array, naming both consequences and the\ncommand that starts it. It appears only in a workspace registered with the service, and it does not\nchange the exit code: your call did everything it was asked.\n\n**The service also says which binary it runs, and a dead program link is loud everywhere.**\n`nxs sync daemon status` now reports the program the running service started from, its version, and\nwhat the `nexus-flow` link points at right now — and says so when those have drifted apart.\n`nxs self-update` names the divergence after an update rather than letting the service quietly stay\non an older build. And if the link points at a file that is gone — the one failure launchd cannot\nreport, whose only symptom is a service that never starts — every `nxs`, `nxf`, `nxm` and `nxc`\ninvocation says it, names the dead target, and gives you the command that repairs it.", - "de": "### Neu\n- **Ein Hintergrunddienst, der nicht läuft, sagt das jetzt, statt es Sie bemerken zu lassen.** Ein\nDienst hält jede Frist Ihrer Maschine und ist das Einzige, was synchronisiert. Solange er steht,\nfeuern deklarierte Fenster nicht — eine Runde, die sich lösen sollte, hängt unbegrenzt — und Ihr\nOps-Log schiebt und holt nichts mehr. Beide Hälften waren bisher still. `nxc send`, `nxc reply` und\n`nxc tick` tragen jetzt einen Eintrag `service_not_running` im `warnings`-Feld, der beide Folgen und\nden Befehl zum Starten benennt. Er erscheint nur in einem Arbeitsbereich, der beim Dienst angemeldet\nist, und ändert den Exit-Code nicht: Ihr Aufruf hat alles getan, was er sollte.\n\n**Der Dienst sagt außerdem, welche Binärdatei er fährt, und eine tote Programmverknüpfung ist\nüberall laut.** `nxs sync daemon status` meldet jetzt das Programm, aus dem der laufende Dienst\ngestartet ist, dessen Version und worauf die `nexus-flow`-Verknüpfung gerade zeigt — und sagt es,\nwenn beides auseinandergelaufen ist. `nxs self-update` benennt die Abweichung nach einer\nAktualisierung, statt den Dienst still auf einem älteren Bau stehen zu lassen. Und zeigt die\nVerknüpfung auf eine Datei, die es nicht mehr gibt — der eine Fehler, den launchd nicht melden kann\nund dessen einziges Symptom ein Dienst ist, der nie startet —, sagt das jeder Aufruf von `nxs`,\n`nxf`, `nxm` und `nxc`, benennt das tote Ziel und nennt den Befehl, der es behebt.\n\n### Facade-Kontrakt\n- `changed` · **Ein Hintergrunddienst, der nicht läuft, sagt das jetzt, statt es Sie bemerken zu lassen.** Ein\nDienst hält jede Frist Ihrer Maschine und ist das Einzige, was synchronisiert. Solange er steht,\nfeuern deklarierte Fenster nicht — eine Runde, die sich lösen sollte, hängt unbegrenzt — und Ihr\nOps-Log schiebt und holt nichts mehr. Beide Hälften waren bisher still. `nxc send`, `nxc reply` und\n`nxc tick` tragen jetzt einen Eintrag `service_not_running` im `warnings`-Feld, der beide Folgen und\nden Befehl zum Starten benennt. Er erscheint nur in einem Arbeitsbereich, der beim Dienst angemeldet\nist, und ändert den Exit-Code nicht: Ihr Aufruf hat alles getan, was er sollte.\n\n**Der Dienst sagt außerdem, welche Binärdatei er fährt, und eine tote Programmverknüpfung ist\nüberall laut.** `nxs sync daemon status` meldet jetzt das Programm, aus dem der laufende Dienst\ngestartet ist, dessen Version und worauf die `nexus-flow`-Verknüpfung gerade zeigt — und sagt es,\nwenn beides auseinandergelaufen ist. `nxs self-update` benennt die Abweichung nach einer\nAktualisierung, statt den Dienst still auf einem älteren Bau stehen zu lassen. Und zeigt die\nVerknüpfung auf eine Datei, die es nicht mehr gibt — der eine Fehler, den launchd nicht melden kann\nund dessen einziges Symptom ein Dienst ist, der nie startet —, sagt das jeder Aufruf von `nxs`,\n`nxf`, `nxm` und `nxc`, benennt das tote Ziel und nennt den Befehl, der es behebt." - } - }, - { - "version": "0.72.0", - "date": "2026-08-29", - "items": [ - { - "type": "added", - "en": "Channels can now declare **preconditions** — deterministic hurdles the supervisor clears before it\nstarts a step, in the working copy the sessions run in. A hurdle is a command with an optional\nexpected output (`run: git status --porcelain`, `expect: \"\"`); it passes on a zero exit and the\ndeclared output, and **anything else stops the step**, including a command that crashes, does not\nexist, or runs past its bound. The hurdles are asked in declaration order and the first refusal\nwins: `nxc send --to ` fails outright when the first step is refused, and a step further\nalong the flow is reported in the reply's `warnings` under the new class `precondition_refused`,\nwhich carries the hurdle's name and its output so a requester can branch instead of reading prose.\nOne hurdle applies with nothing declared at all, because it is a property of the runtime rather than\nof your project: on a channel that claims the working copy, no session of an earlier step of that\nchannel may still be running when the next one starts.\n\nAlongside it, a running **operation is now bound to the declarations it opened under**: editing\n`.nxs-personas/` while a chain is in flight no longer changes that chain's later steps, and a\npersona deleted mid-flight still resolves for the operation it stands in. The change takes effect in\nthe next operation. Full description in `nxc guide channels`.\n\nEmbedding hosts: `Worker` gains `run_precondition`, with a default that REFUSES — a host that does\nnot implement it loses exactly the channels that declare hurdles, loudly, rather than running their\nsteps unguarded. `ChannelDecl` gains `preconditions` and `FailedConsequence` gains `precondition`,\nso struct literals of either need one more field.", - "de": "Kanäle können jetzt **Vorbedingungen** deklarieren — deterministische Hürden, die der Betreuer nimmt,\nbevor er einen Schritt startet, und zwar in der Arbeitskopie, in der auch die Sitzungen laufen. Eine\nHürde ist ein Befehl mit optional erwarteter Ausgabe (`run: git status --porcelain`, `expect: \"\"`);\nsie ist genommen bei Rückgabewert Null und der deklarierten Ausgabe, und **alles andere hält den\nSchritt an** — auch ein Befehl, der abstürzt, den es nicht gibt oder der in seine Frist läuft. Die\nHürden werden in Deklarationsreihenfolge gefragt, und die erste Absage gewinnt: `nxc send --to\n` scheitert unmittelbar, wenn der erste Schritt abgelehnt wird, und ein späterer Schritt im\nAblauf wird in den `warnings` der Antwort unter der neuen Klasse `precondition_refused` gemeldet —\nmit dem Namen der Hürde und ihrer Ausgabe, damit ein Anforderer verzweigen kann, statt Prosa zu\nlesen. Eine Hürde gilt ohne jede Deklaration, weil sie eine Eigenschaft der Laufzeit ist und nicht\nDeines Projekts: auf einem Kanal, der die Arbeitskopie beansprucht, darf keine Sitzung eines\nfrüheren Schritts desselben Kanals mehr laufen, wenn der nächste startet.\n\nDazu gehört: ein laufender **Vorgang ist jetzt an die Deklarationen gebunden, unter denen er geöffnet\nwurde**. Eine Änderung an `.nxs-personas/` mitten in einer laufenden Kette ändert deren spätere\nSchritte nicht mehr, und eine mitten im Lauf gelöschte Persona löst für den Vorgang, in dem sie\nsteht, weiterhin auf. Die Änderung wirkt im nächsten Vorgang. Ausführlich in `nxc guide channels`.\n\nFür einbettende Anwendungen: `Worker` bekommt `run_precondition` mit einer Vorgabe, die ABLEHNT —\nwer sie nicht implementiert, verliert genau die Kanäle, die Hürden deklarieren, und zwar hörbar,\nstatt deren Schritte ungeschützt zu starten. `ChannelDecl` bekommt `preconditions` und\n`FailedConsequence` bekommt `precondition`; Struct-Literale beider brauchen also ein Feld mehr.", - "facade": "breaking" - } - ], - "notes": { - "en": "### Added\n- Channels can now declare **preconditions** — deterministic hurdles the supervisor clears before it\nstarts a step, in the working copy the sessions run in. A hurdle is a command with an optional\nexpected output (`run: git status --porcelain`, `expect: \"\"`); it passes on a zero exit and the\ndeclared output, and **anything else stops the step**, including a command that crashes, does not\nexist, or runs past its bound. The hurdles are asked in declaration order and the first refusal\nwins: `nxc send --to ` fails outright when the first step is refused, and a step further\nalong the flow is reported in the reply's `warnings` under the new class `precondition_refused`,\nwhich carries the hurdle's name and its output so a requester can branch instead of reading prose.\nOne hurdle applies with nothing declared at all, because it is a property of the runtime rather than\nof your project: on a channel that claims the working copy, no session of an earlier step of that\nchannel may still be running when the next one starts.\n\nAlongside it, a running **operation is now bound to the declarations it opened under**: editing\n`.nxs-personas/` while a chain is in flight no longer changes that chain's later steps, and a\npersona deleted mid-flight still resolves for the operation it stands in. The change takes effect in\nthe next operation. Full description in `nxc guide channels`.\n\nEmbedding hosts: `Worker` gains `run_precondition`, with a default that REFUSES — a host that does\nnot implement it loses exactly the channels that declare hurdles, loudly, rather than running their\nsteps unguarded. `ChannelDecl` gains `preconditions` and `FailedConsequence` gains `precondition`,\nso struct literals of either need one more field.\n\n### Facade Contract\n- `breaking` · Channels can now declare **preconditions** — deterministic hurdles the supervisor clears before it\nstarts a step, in the working copy the sessions run in. A hurdle is a command with an optional\nexpected output (`run: git status --porcelain`, `expect: \"\"`); it passes on a zero exit and the\ndeclared output, and **anything else stops the step**, including a command that crashes, does not\nexist, or runs past its bound. The hurdles are asked in declaration order and the first refusal\nwins: `nxc send --to ` fails outright when the first step is refused, and a step further\nalong the flow is reported in the reply's `warnings` under the new class `precondition_refused`,\nwhich carries the hurdle's name and its output so a requester can branch instead of reading prose.\nOne hurdle applies with nothing declared at all, because it is a property of the runtime rather than\nof your project: on a channel that claims the working copy, no session of an earlier step of that\nchannel may still be running when the next one starts.\n\nAlongside it, a running **operation is now bound to the declarations it opened under**: editing\n`.nxs-personas/` while a chain is in flight no longer changes that chain's later steps, and a\npersona deleted mid-flight still resolves for the operation it stands in. The change takes effect in\nthe next operation. Full description in `nxc guide channels`.\n\nEmbedding hosts: `Worker` gains `run_precondition`, with a default that REFUSES — a host that does\nnot implement it loses exactly the channels that declare hurdles, loudly, rather than running their\nsteps unguarded. `ChannelDecl` gains `preconditions` and `FailedConsequence` gains `precondition`,\nso struct literals of either need one more field.", - "de": "### Neu\n- Kanäle können jetzt **Vorbedingungen** deklarieren — deterministische Hürden, die der Betreuer nimmt,\nbevor er einen Schritt startet, und zwar in der Arbeitskopie, in der auch die Sitzungen laufen. Eine\nHürde ist ein Befehl mit optional erwarteter Ausgabe (`run: git status --porcelain`, `expect: \"\"`);\nsie ist genommen bei Rückgabewert Null und der deklarierten Ausgabe, und **alles andere hält den\nSchritt an** — auch ein Befehl, der abstürzt, den es nicht gibt oder der in seine Frist läuft. Die\nHürden werden in Deklarationsreihenfolge gefragt, und die erste Absage gewinnt: `nxc send --to\n` scheitert unmittelbar, wenn der erste Schritt abgelehnt wird, und ein späterer Schritt im\nAblauf wird in den `warnings` der Antwort unter der neuen Klasse `precondition_refused` gemeldet —\nmit dem Namen der Hürde und ihrer Ausgabe, damit ein Anforderer verzweigen kann, statt Prosa zu\nlesen. Eine Hürde gilt ohne jede Deklaration, weil sie eine Eigenschaft der Laufzeit ist und nicht\nDeines Projekts: auf einem Kanal, der die Arbeitskopie beansprucht, darf keine Sitzung eines\nfrüheren Schritts desselben Kanals mehr laufen, wenn der nächste startet.\n\nDazu gehört: ein laufender **Vorgang ist jetzt an die Deklarationen gebunden, unter denen er geöffnet\nwurde**. Eine Änderung an `.nxs-personas/` mitten in einer laufenden Kette ändert deren spätere\nSchritte nicht mehr, und eine mitten im Lauf gelöschte Persona löst für den Vorgang, in dem sie\nsteht, weiterhin auf. Die Änderung wirkt im nächsten Vorgang. Ausführlich in `nxc guide channels`.\n\nFür einbettende Anwendungen: `Worker` bekommt `run_precondition` mit einer Vorgabe, die ABLEHNT —\nwer sie nicht implementiert, verliert genau die Kanäle, die Hürden deklarieren, und zwar hörbar,\nstatt deren Schritte ungeschützt zu starten. `ChannelDecl` bekommt `preconditions` und\n`FailedConsequence` bekommt `precondition`; Struct-Literale beider brauchen also ein Feld mehr.\n\n### Facade-Kontrakt\n- `breaking` · Kanäle können jetzt **Vorbedingungen** deklarieren — deterministische Hürden, die der Betreuer nimmt,\nbevor er einen Schritt startet, und zwar in der Arbeitskopie, in der auch die Sitzungen laufen. Eine\nHürde ist ein Befehl mit optional erwarteter Ausgabe (`run: git status --porcelain`, `expect: \"\"`);\nsie ist genommen bei Rückgabewert Null und der deklarierten Ausgabe, und **alles andere hält den\nSchritt an** — auch ein Befehl, der abstürzt, den es nicht gibt oder der in seine Frist läuft. Die\nHürden werden in Deklarationsreihenfolge gefragt, und die erste Absage gewinnt: `nxc send --to\n` scheitert unmittelbar, wenn der erste Schritt abgelehnt wird, und ein späterer Schritt im\nAblauf wird in den `warnings` der Antwort unter der neuen Klasse `precondition_refused` gemeldet —\nmit dem Namen der Hürde und ihrer Ausgabe, damit ein Anforderer verzweigen kann, statt Prosa zu\nlesen. Eine Hürde gilt ohne jede Deklaration, weil sie eine Eigenschaft der Laufzeit ist und nicht\nDeines Projekts: auf einem Kanal, der die Arbeitskopie beansprucht, darf keine Sitzung eines\nfrüheren Schritts desselben Kanals mehr laufen, wenn der nächste startet.\n\nDazu gehört: ein laufender **Vorgang ist jetzt an die Deklarationen gebunden, unter denen er geöffnet\nwurde**. Eine Änderung an `.nxs-personas/` mitten in einer laufenden Kette ändert deren spätere\nSchritte nicht mehr, und eine mitten im Lauf gelöschte Persona löst für den Vorgang, in dem sie\nsteht, weiterhin auf. Die Änderung wirkt im nächsten Vorgang. Ausführlich in `nxc guide channels`.\n\nFür einbettende Anwendungen: `Worker` bekommt `run_precondition` mit einer Vorgabe, die ABLEHNT —\nwer sie nicht implementiert, verliert genau die Kanäle, die Hürden deklarieren, und zwar hörbar,\nstatt deren Schritte ungeschützt zu starten. `ChannelDecl` bekommt `preconditions` und\n`FailedConsequence` bekommt `precondition`; Struct-Literale beider brauchen also ein Feld mehr." - } - }, - { - "version": "0.71.0", - "date": "2026-08-29", - "items": [ - { - "type": "fixed", - "en": "`nxs migrate` now brings an out-of-date SessionStart wiring up to the current one — one hook per\nactive module — whichever shape it finds. It used to look for a pre-v0.6.0 `nxf prime` hook and\nnothing else, so a workspace on the single `nxs prime` umbrella hook (with or without the\n`|| cat NEXUS_MEMORY.md` tail) came out unchanged, still delivering one composed block instead of\nthree that each fit what a host passes through. Measured across 19 workspaces with an nxs hook, all\n19 were on that shape and `migrate` would have converged none of them.\n\nIt also adds the entry of a module that has joined since the hooks were written, and it still leaves\na workspace whose SessionStart hooks are all somebody else's untouched: `migrate` repairs a wiring\nthat exists, it does not decide you want one.", - "de": "`nxs migrate` bringt eine veraltete SessionStart-Verdrahtung jetzt auf die aktuelle — ein Hook je\naktivem Modul —, welche Form es auch vorfindet. Es suchte bisher nur nach einem\nvor-v0.6.0-Hook `nxf prime` und sonst nichts, sodass ein Arbeitsbereich mit dem einzelnen\n`nxs prime`-Umbrella-Hook (mit oder ohne `|| cat NEXUS_MEMORY.md`-Anhang) unveraendert blieb und\nweiterhin einen zusammengesetzten Block auslieferte statt dreier, die je für sich durch den Host\npassen. Gemessen ueber 19 Arbeitsbereiche mit nxs-Hook: alle 19 standen auf dieser Form, und\n`migrate` haette keinen einzigen davon umgestellt.\n\nEs ergaenzt ausserdem den Eintrag eines Moduls, das seit dem Schreiben der Hooks dazugekommen ist,\nund laesst einen Arbeitsbereich, dessen SessionStart-Hooks alle fremd sind, weiterhin unangetastet:\n`migrate` repariert eine vorhandene Verdrahtung, es entscheidet nicht, dass Du eine willst." - }, - { - "type": "added", - "en": "`nxm index` prints the whole memory index — one written line per memory, key and introduction, in\nthe order a session start replays them. It is the full form of the index `nxm prime` renders: read\nthe lines, then open the one that matters with `nxm recall `.\n\n`nxm prime`'s own index is now bounded by a byte budget rather than growing a line per memory\nforever. A host delivers nothing at all above 10 KiB of SessionStart hook output — not a shorter\nblock, nothing — so a workspace that had remembered enough was losing its whole memory block. The\nblock now carries as many lines as fit, headed `## Memories (showing 34 of 80)`, and names\n`nxm index` for the rest. Every workspace whose index already fits renders exactly the bytes it\nrendered before, and `nxm prime --json`, `nxm memories` and the generated `NEXUS_MEMORY.md` are\nunbounded as they always were.\n\nFor an embedding app: `Engine::index` serves the same whole index, and `PrimeReport::render_within`\ntakes the budget of whichever host the app is composing for — `render_markdown` now renders against\nthis repo's own host limit, which is a change in what it returns for a large workspace.", - "de": "`nxm index` gibt das ganze Verzeichnis der Erinnerungen aus — eine geschriebene Zeile je\nErinnerung, Schlüssel und Einleitung, in der Reihenfolge, die ein Sitzungsstart wiedergibt. Es ist\ndie vollständige Fassung des Verzeichnisses aus `nxm prime`: erst die Zeilen lesen, dann mit\n`nxm recall ` den Eintrag öffnen, auf den es ankommt.\n\nDas Verzeichnis in `nxm prime` ist jetzt durch ein Byte-Budget begrenzt, statt für immer je\nErinnerung eine Zeile zu wachsen. Oberhalb von 10 KiB Hook-Ausgabe liefert der Host gar nichts mehr\n— keinen kürzeren Block, sondern nichts —, sodass ein Arbeitsbereich mit genug Erinnerungen seinen\nganzen Gedächtnisblock verlor. Der Block trägt nun so viele Zeilen, wie hineinpassen, überschrieben\nmit `## Memories (showing 34 of 80)`, und nennt für den Rest `nxm index`. Jeder Arbeitsbereich,\ndessen Verzeichnis ohnehin passt, liefert exakt die Bytes wie zuvor; `nxm prime --json`,\n`nxm memories` und das erzeugte `NEXUS_MEMORY.md` bleiben unbegrenzt wie bisher.\n\nFür eine einbettende App: `Engine::index` liefert dasselbe ganze Verzeichnis, und\n`PrimeReport::render_within` nimmt das Budget des Hosts entgegen, für den die App zusammenstellt —\n`render_markdown` rendert jetzt gegen das Host-Limit dieses Repos, was für einen großen\nArbeitsbereich eine Änderung dessen ist, was es zurückgibt.", - "facade": "breaking" - }, - { - "type": "fixed", - "en": "The SessionStart hooks are now written in the same order `nxs prime` fans out — `nxf`, `nxc`, `nxm`,\nand only for modules that are active. It used to be the order `config.toml` happened to list the\nmodules in, which is the order they were set up in: measured across 16 workspaces, 15 had flow first\nand one had memory first, for no reason anybody chose. Memory goes last for the reason the fan-out\nhas it last — it is the one block a session can fetch back afterwards with `nxm recall`. The\n`AGENTS.md` pointer names the active tools in the same sequence.\n\nAny init path (`nxs init`, `nxf/nxm/nxc init`, `nxs setup claude`) and `nxs migrate` bring an\nexisting workspace into that order. The wiring is now rewritten in full on every run and \"nothing\nchanged\" is decided by comparing the file, so a steady-state re-run still writes nothing — but a\nworkspace whose entries sat in another order is reordered once, and a hook group written by hand\nwithout `\"matcher\"` is normalized once.", - "de": "Die SessionStart-Hooks werden jetzt in derselben Reihenfolge geschrieben, in der `nxs prime`\nauffächert — `nxf`, `nxc`, `nxm`, und nur für aktive Module. Bisher war es die Reihenfolge, in der\n`config.toml` die Module zufällig aufführte, also die ihrer Einrichtung: über 16 Arbeitsbereiche\ngemessen stand bei 15 flow vorn und bei einem memory, ohne dass das jemand entschieden hätte. Memory\nsteht aus demselben Grund zuletzt wie im Fanout — es ist der einzige Block, den eine Sitzung\ndanach mit `nxm recall` selbst nachholen kann. Der Zeiger in `AGENTS.md` nennt die aktiven Werkzeuge\nin derselben Folge.\n\nJeder init-Pfad (`nxs init`, `nxf/nxm/nxc init`, `nxs setup claude`) und `nxs migrate` bringen einen\nbestehenden Arbeitsbereich in diese Reihenfolge. Die Verdrahtung wird nun bei jedem Lauf vollständig\nneu geschrieben, und „nichts geändert\" entscheidet ein Vergleich der Datei — ein Lauf ohne Änderung\nschreibt also weiterhin nichts, aber ein Arbeitsbereich mit anderer Reihenfolge wird einmal\numsortiert, und eine von Hand geschriebene Hook-Gruppe ohne `\"matcher\"` einmal normalisiert." - }, - { - "type": "fixed", - "en": "The `|| cat NEXUS_MEMORY.md 2>/dev/null` fallback on the SessionStart hooks now hangs on memory's\nentry instead of on whichever entry happens to be written first — in practice `nxf prime`. The\ndocument is the projection of exactly the memories `nxm prime` replays, so that is the one command\nit can stand in for; on any other entry it answered a failure with something unrelated to what\nfailed. A flow-only workspace carried it where the file does not exist and cannot, and adding\nmemory later did not move it, so the arrangement went from useless to wrong at the moment the\ndocument began to exist. A workspace without memory now carries no fallback at all.\n\nNothing catches the other modules' failure, deliberately: a contributor without `nxs` installed\ngets the shell's own `command not found` and a failing hook, which is the honest report that\nnothing was delivered. Any init path (`nxs init`, `nxf/nxm/nxc init`, `nxs setup claude`) and\n`nxs migrate` move the tail on an existing workspace — moved, never duplicated.", - "de": "Der Anhang `|| cat NEXUS_MEMORY.md 2>/dev/null` an den SessionStart-Hooks hängt jetzt am Eintrag von\nmemory statt an dem, der zufällig zuerst geschrieben wurde — in der Praxis `nxf prime`. Das Dokument\nist die Projektion genau der Erinnerungen, die `nxm prime` wiedergibt, also ist das der einzige\nBefehl, für den es einspringen kann; an jedem anderen Eintrag beantwortete es einen Fehlschlag mit\netwas, das mit dem Ausgefallenen nichts zu tun hat. Ein Arbeitsbereich nur mit flow trug ihn dort,\nwo die Datei nicht existiert und nicht existieren kann, und ein später hinzugefügtes memory\nverschob ihn nicht — die Anordnung wurde also in dem Moment falsch, in dem das Dokument überhaupt\nentstehen konnte. Ohne memory trägt jetzt kein Eintrag mehr einen Anhang.\n\nDer Fehlschlag der anderen Module wird bewusst nicht abgefangen: wer `nxs` nicht installiert hat,\nbekommt das `command not found` der Shell und einen fehlschlagenden Hook — die ehrliche Auskunft,\ndass nichts geliefert wurde. Jeder init-Pfad (`nxs init`, `nxf/nxm/nxc init`, `nxs setup claude`)\nund `nxs migrate` versetzen den Anhang in einem bestehenden Arbeitsbereich — versetzt, nie\nverdoppelt." - } - ], - "notes": { - "en": "### Added\n- `nxm index` prints the whole memory index — one written line per memory, key and introduction, in\nthe order a session start replays them. It is the full form of the index `nxm prime` renders: read\nthe lines, then open the one that matters with `nxm recall `.\n\n`nxm prime`'s own index is now bounded by a byte budget rather than growing a line per memory\nforever. A host delivers nothing at all above 10 KiB of SessionStart hook output — not a shorter\nblock, nothing — so a workspace that had remembered enough was losing its whole memory block. The\nblock now carries as many lines as fit, headed `## Memories (showing 34 of 80)`, and names\n`nxm index` for the rest. Every workspace whose index already fits renders exactly the bytes it\nrendered before, and `nxm prime --json`, `nxm memories` and the generated `NEXUS_MEMORY.md` are\nunbounded as they always were.\n\nFor an embedding app: `Engine::index` serves the same whole index, and `PrimeReport::render_within`\ntakes the budget of whichever host the app is composing for — `render_markdown` now renders against\nthis repo's own host limit, which is a change in what it returns for a large workspace.\n\n### Fixed\n- `nxs migrate` now brings an out-of-date SessionStart wiring up to the current one — one hook per\nactive module — whichever shape it finds. It used to look for a pre-v0.6.0 `nxf prime` hook and\nnothing else, so a workspace on the single `nxs prime` umbrella hook (with or without the\n`|| cat NEXUS_MEMORY.md` tail) came out unchanged, still delivering one composed block instead of\nthree that each fit what a host passes through. Measured across 19 workspaces with an nxs hook, all\n19 were on that shape and `migrate` would have converged none of them.\n\nIt also adds the entry of a module that has joined since the hooks were written, and it still leaves\na workspace whose SessionStart hooks are all somebody else's untouched: `migrate` repairs a wiring\nthat exists, it does not decide you want one.\n- The SessionStart hooks are now written in the same order `nxs prime` fans out — `nxf`, `nxc`, `nxm`,\nand only for modules that are active. It used to be the order `config.toml` happened to list the\nmodules in, which is the order they were set up in: measured across 16 workspaces, 15 had flow first\nand one had memory first, for no reason anybody chose. Memory goes last for the reason the fan-out\nhas it last — it is the one block a session can fetch back afterwards with `nxm recall`. The\n`AGENTS.md` pointer names the active tools in the same sequence.\n\nAny init path (`nxs init`, `nxf/nxm/nxc init`, `nxs setup claude`) and `nxs migrate` bring an\nexisting workspace into that order. The wiring is now rewritten in full on every run and \"nothing\nchanged\" is decided by comparing the file, so a steady-state re-run still writes nothing — but a\nworkspace whose entries sat in another order is reordered once, and a hook group written by hand\nwithout `\"matcher\"` is normalized once.\n- The `|| cat NEXUS_MEMORY.md 2>/dev/null` fallback on the SessionStart hooks now hangs on memory's\nentry instead of on whichever entry happens to be written first — in practice `nxf prime`. The\ndocument is the projection of exactly the memories `nxm prime` replays, so that is the one command\nit can stand in for; on any other entry it answered a failure with something unrelated to what\nfailed. A flow-only workspace carried it where the file does not exist and cannot, and adding\nmemory later did not move it, so the arrangement went from useless to wrong at the moment the\ndocument began to exist. A workspace without memory now carries no fallback at all.\n\nNothing catches the other modules' failure, deliberately: a contributor without `nxs` installed\ngets the shell's own `command not found` and a failing hook, which is the honest report that\nnothing was delivered. Any init path (`nxs init`, `nxf/nxm/nxc init`, `nxs setup claude`) and\n`nxs migrate` move the tail on an existing workspace — moved, never duplicated.\n\n### Facade Contract\n- `breaking` · `nxm index` prints the whole memory index — one written line per memory, key and introduction, in\nthe order a session start replays them. It is the full form of the index `nxm prime` renders: read\nthe lines, then open the one that matters with `nxm recall `.\n\n`nxm prime`'s own index is now bounded by a byte budget rather than growing a line per memory\nforever. A host delivers nothing at all above 10 KiB of SessionStart hook output — not a shorter\nblock, nothing — so a workspace that had remembered enough was losing its whole memory block. The\nblock now carries as many lines as fit, headed `## Memories (showing 34 of 80)`, and names\n`nxm index` for the rest. Every workspace whose index already fits renders exactly the bytes it\nrendered before, and `nxm prime --json`, `nxm memories` and the generated `NEXUS_MEMORY.md` are\nunbounded as they always were.\n\nFor an embedding app: `Engine::index` serves the same whole index, and `PrimeReport::render_within`\ntakes the budget of whichever host the app is composing for — `render_markdown` now renders against\nthis repo's own host limit, which is a change in what it returns for a large workspace.", - "de": "### Neu\n- `nxm index` gibt das ganze Verzeichnis der Erinnerungen aus — eine geschriebene Zeile je\nErinnerung, Schlüssel und Einleitung, in der Reihenfolge, die ein Sitzungsstart wiedergibt. Es ist\ndie vollständige Fassung des Verzeichnisses aus `nxm prime`: erst die Zeilen lesen, dann mit\n`nxm recall ` den Eintrag öffnen, auf den es ankommt.\n\nDas Verzeichnis in `nxm prime` ist jetzt durch ein Byte-Budget begrenzt, statt für immer je\nErinnerung eine Zeile zu wachsen. Oberhalb von 10 KiB Hook-Ausgabe liefert der Host gar nichts mehr\n— keinen kürzeren Block, sondern nichts —, sodass ein Arbeitsbereich mit genug Erinnerungen seinen\nganzen Gedächtnisblock verlor. Der Block trägt nun so viele Zeilen, wie hineinpassen, überschrieben\nmit `## Memories (showing 34 of 80)`, und nennt für den Rest `nxm index`. Jeder Arbeitsbereich,\ndessen Verzeichnis ohnehin passt, liefert exakt die Bytes wie zuvor; `nxm prime --json`,\n`nxm memories` und das erzeugte `NEXUS_MEMORY.md` bleiben unbegrenzt wie bisher.\n\nFür eine einbettende App: `Engine::index` liefert dasselbe ganze Verzeichnis, und\n`PrimeReport::render_within` nimmt das Budget des Hosts entgegen, für den die App zusammenstellt —\n`render_markdown` rendert jetzt gegen das Host-Limit dieses Repos, was für einen großen\nArbeitsbereich eine Änderung dessen ist, was es zurückgibt.\n\n### Behoben\n- `nxs migrate` bringt eine veraltete SessionStart-Verdrahtung jetzt auf die aktuelle — ein Hook je\naktivem Modul —, welche Form es auch vorfindet. Es suchte bisher nur nach einem\nvor-v0.6.0-Hook `nxf prime` und sonst nichts, sodass ein Arbeitsbereich mit dem einzelnen\n`nxs prime`-Umbrella-Hook (mit oder ohne `|| cat NEXUS_MEMORY.md`-Anhang) unveraendert blieb und\nweiterhin einen zusammengesetzten Block auslieferte statt dreier, die je für sich durch den Host\npassen. Gemessen ueber 19 Arbeitsbereiche mit nxs-Hook: alle 19 standen auf dieser Form, und\n`migrate` haette keinen einzigen davon umgestellt.\n\nEs ergaenzt ausserdem den Eintrag eines Moduls, das seit dem Schreiben der Hooks dazugekommen ist,\nund laesst einen Arbeitsbereich, dessen SessionStart-Hooks alle fremd sind, weiterhin unangetastet:\n`migrate` repariert eine vorhandene Verdrahtung, es entscheidet nicht, dass Du eine willst.\n- Die SessionStart-Hooks werden jetzt in derselben Reihenfolge geschrieben, in der `nxs prime`\nauffächert — `nxf`, `nxc`, `nxm`, und nur für aktive Module. Bisher war es die Reihenfolge, in der\n`config.toml` die Module zufällig aufführte, also die ihrer Einrichtung: über 16 Arbeitsbereiche\ngemessen stand bei 15 flow vorn und bei einem memory, ohne dass das jemand entschieden hätte. Memory\nsteht aus demselben Grund zuletzt wie im Fanout — es ist der einzige Block, den eine Sitzung\ndanach mit `nxm recall` selbst nachholen kann. Der Zeiger in `AGENTS.md` nennt die aktiven Werkzeuge\nin derselben Folge.\n\nJeder init-Pfad (`nxs init`, `nxf/nxm/nxc init`, `nxs setup claude`) und `nxs migrate` bringen einen\nbestehenden Arbeitsbereich in diese Reihenfolge. Die Verdrahtung wird nun bei jedem Lauf vollständig\nneu geschrieben, und „nichts geändert\" entscheidet ein Vergleich der Datei — ein Lauf ohne Änderung\nschreibt also weiterhin nichts, aber ein Arbeitsbereich mit anderer Reihenfolge wird einmal\numsortiert, und eine von Hand geschriebene Hook-Gruppe ohne `\"matcher\"` einmal normalisiert.\n- Der Anhang `|| cat NEXUS_MEMORY.md 2>/dev/null` an den SessionStart-Hooks hängt jetzt am Eintrag von\nmemory statt an dem, der zufällig zuerst geschrieben wurde — in der Praxis `nxf prime`. Das Dokument\nist die Projektion genau der Erinnerungen, die `nxm prime` wiedergibt, also ist das der einzige\nBefehl, für den es einspringen kann; an jedem anderen Eintrag beantwortete es einen Fehlschlag mit\netwas, das mit dem Ausgefallenen nichts zu tun hat. Ein Arbeitsbereich nur mit flow trug ihn dort,\nwo die Datei nicht existiert und nicht existieren kann, und ein später hinzugefügtes memory\nverschob ihn nicht — die Anordnung wurde also in dem Moment falsch, in dem das Dokument überhaupt\nentstehen konnte. Ohne memory trägt jetzt kein Eintrag mehr einen Anhang.\n\nDer Fehlschlag der anderen Module wird bewusst nicht abgefangen: wer `nxs` nicht installiert hat,\nbekommt das `command not found` der Shell und einen fehlschlagenden Hook — die ehrliche Auskunft,\ndass nichts geliefert wurde. Jeder init-Pfad (`nxs init`, `nxf/nxm/nxc init`, `nxs setup claude`)\nund `nxs migrate` versetzen den Anhang in einem bestehenden Arbeitsbereich — versetzt, nie\nverdoppelt.\n\n### Facade-Kontrakt\n- `breaking` · `nxm index` gibt das ganze Verzeichnis der Erinnerungen aus — eine geschriebene Zeile je\nErinnerung, Schlüssel und Einleitung, in der Reihenfolge, die ein Sitzungsstart wiedergibt. Es ist\ndie vollständige Fassung des Verzeichnisses aus `nxm prime`: erst die Zeilen lesen, dann mit\n`nxm recall ` den Eintrag öffnen, auf den es ankommt.\n\nDas Verzeichnis in `nxm prime` ist jetzt durch ein Byte-Budget begrenzt, statt für immer je\nErinnerung eine Zeile zu wachsen. Oberhalb von 10 KiB Hook-Ausgabe liefert der Host gar nichts mehr\n— keinen kürzeren Block, sondern nichts —, sodass ein Arbeitsbereich mit genug Erinnerungen seinen\nganzen Gedächtnisblock verlor. Der Block trägt nun so viele Zeilen, wie hineinpassen, überschrieben\nmit `## Memories (showing 34 of 80)`, und nennt für den Rest `nxm index`. Jeder Arbeitsbereich,\ndessen Verzeichnis ohnehin passt, liefert exakt die Bytes wie zuvor; `nxm prime --json`,\n`nxm memories` und das erzeugte `NEXUS_MEMORY.md` bleiben unbegrenzt wie bisher.\n\nFür eine einbettende App: `Engine::index` liefert dasselbe ganze Verzeichnis, und\n`PrimeReport::render_within` nimmt das Budget des Hosts entgegen, für den die App zusammenstellt —\n`render_markdown` rendert jetzt gegen das Host-Limit dieses Repos, was für einen großen\nArbeitsbereich eine Änderung dessen ist, was es zurückgibt." - } - }, - { - "version": "0.70.0", - "date": "2026-08-28", - "items": [ - { - "type": "changed", - "en": "`nxs init`, `nxf init`, `nxm init`, `nxc init` and `nxs setup claude` now wire **one SessionStart\nhook per active module** (`nxf prime`, `nxm prime`, `nxc prime`) instead of a single `nxs prime`\nhook. The host caps each hook's output separately, and an oversized block is dropped whole rather\nthan truncated — three smaller blocks each stay under that cap where one combined block did not.\n`nxs migrate` upgrades an existing workspace, and `nxs prime` still exists for re-priming by hand\nafter a context compaction.\n\n**Breaking, `--json` consumers:** the init verbs' `hook.command` (one string) is now\n`hook.commands` (a list). A consumer reading the old key gets nothing rather than the first of\nthree silently mistaken for the whole wiring. `nxs setup claude --json` and `nxs migrate --json` additively gain\nthe same list as `hook_commands`; their existing `hook_added`/`hook_rewritten` fields are\nunchanged.", - "de": "`nxs init`, `nxf init`, `nxm init`, `nxc init` und `nxs setup claude` verdrahten jetzt **einen\nSessionStart-Hook je aktivem Modul** (`nxf prime`, `nxm prime`, `nxc prime`) statt eines einzelnen\n`nxs prime`-Hooks. Der Host begrenzt jede Hook-Ausgabe einzeln, und ein zu großer Block wird ganz\nverworfen statt gekürzt — drei kleinere Blöcke bleiben jeder unter dieser Grenze, wo ein\nzusammengesetzter es nicht tat. `nxs migrate` rüstet bestehende Workspaces um, und `nxs prime` gibt\nes weiterhin, um nach einer Kontext-Verdichtung von Hand neu zu primen.\n\n**Brechend für `--json`-Verbraucher:** aus `hook.command` (eine Zeichenkette) der init-Verben wird\n`hook.commands` (eine Liste). Wer den alten Schlüssel liest, bekommt nichts — statt den ersten von\ndreien stillschweigend für die ganze Verdrahtung zu halten. `nxs setup claude --json` und `nxs migrate --json` bekommen\ndieselbe Liste additiv als `hook_commands`; die bestehenden Felder `hook_added` bzw.\n`hook_rewritten` bleiben unverändert." - } - ], - "notes": { - "en": "### Changed\n- `nxs init`, `nxf init`, `nxm init`, `nxc init` and `nxs setup claude` now wire **one SessionStart\nhook per active module** (`nxf prime`, `nxm prime`, `nxc prime`) instead of a single `nxs prime`\nhook. The host caps each hook's output separately, and an oversized block is dropped whole rather\nthan truncated — three smaller blocks each stay under that cap where one combined block did not.\n`nxs migrate` upgrades an existing workspace, and `nxs prime` still exists for re-priming by hand\nafter a context compaction.\n\n**Breaking, `--json` consumers:** the init verbs' `hook.command` (one string) is now\n`hook.commands` (a list). A consumer reading the old key gets nothing rather than the first of\nthree silently mistaken for the whole wiring. `nxs setup claude --json` and `nxs migrate --json` additively gain\nthe same list as `hook_commands`; their existing `hook_added`/`hook_rewritten` fields are\nunchanged.", - "de": "### Geändert\n- `nxs init`, `nxf init`, `nxm init`, `nxc init` und `nxs setup claude` verdrahten jetzt **einen\nSessionStart-Hook je aktivem Modul** (`nxf prime`, `nxm prime`, `nxc prime`) statt eines einzelnen\n`nxs prime`-Hooks. Der Host begrenzt jede Hook-Ausgabe einzeln, und ein zu großer Block wird ganz\nverworfen statt gekürzt — drei kleinere Blöcke bleiben jeder unter dieser Grenze, wo ein\nzusammengesetzter es nicht tat. `nxs migrate` rüstet bestehende Workspaces um, und `nxs prime` gibt\nes weiterhin, um nach einer Kontext-Verdichtung von Hand neu zu primen.\n\n**Brechend für `--json`-Verbraucher:** aus `hook.command` (eine Zeichenkette) der init-Verben wird\n`hook.commands` (eine Liste). Wer den alten Schlüssel liest, bekommt nichts — statt den ersten von\ndreien stillschweigend für die ganze Verdrahtung zu halten. `nxs setup claude --json` und `nxs migrate --json` bekommen\ndieselbe Liste additiv als `hook_commands`; die bestehenden Felder `hook_added` bzw.\n`hook_rewritten` bleiben unverändert." - } - }, - { - "version": "0.69.0", - "date": "2026-08-28", - "items": [ - { - "type": "removed", - "en": "**No message text reaches a session start any more, and `nxc inbox` / `nxc read` are gone.** The block a session is handed when it opens still carried, under `## Threads you opened`, the full conversation of every board you had opened and that had since finished — the request, every reply, in full. On the workspace where this was measured that section alone was 46 KB of a 63 KB session start: 74 % of it, twenty-four boards, six of them finished, so a work order completed yesterday was read out in full to every session that started today. It is now not rendered at all, and that workspace's session start went from 63.787 to 16.742 bytes.\n\n**The reason it was never needed is that all three ways a message is delivered already push it.** `nxc send --to` starts a session with the message in its prompt; `nxc reply --thread` resumes the session on the other side with the reply's body; a board whose quorum completes wakes whoever opened it. The section was a second copy of what had already arrived — held down by a read cursor that only `nxc read` moved, a verb used zero times across sixty-six measured agent sessions, alongside `nxc inbox`, also zero. Both verbs are removed with the section they served.\n\n**What replaces them is the boundary this draws: people pull, agents get pushed.** A person at a terminal has no prompt for anything to be pushed into, so the two reads a person actually uses are untouched — `nxc threads show ` for one conversation in full, `nxc status` for where an operation stands. Nothing that a session start used to say has moved somewhere harder to reach: a thread waiting on an answer from the session reading the block was never in that section in the first place (it lists boards you OPENED, where somebody else owes the reply), so nothing was quietly dropped along with it.\n\n**For an embedding application nothing about the data changes.** `nxc prime --json` still carries `threads_you_opened` with every board and every message body, `in_turn`/`next_session` still carry the unread, and the acknowledgement that moves the read cursor (`Engine::mark_read`) is untouched. What was removed from the library surface is the Markdown renderer for that section, `nexus_chat::facade::render_opener_wake`, and the two command-line entrances.", - "de": "**In einen Sitzungsstart gelangt kein Nachrichtentext mehr, und `nxc inbox` / `nxc read` sind entfallen.** Der Block, den eine Sitzung beim Öffnen bekommt, trug unter `## Threads you opened` weiterhin das vollständige Gespräch jedes Bretts, das Sie geöffnet hatten und das inzwischen fertig war — die Anfrage, jede Antwort, im Volltext. Im gemessenen Arbeitsbereich waren allein das 46 KB eines 63-KB-Sitzungsstarts: 74 %, vierundzwanzig Bretter, sechs davon abgeschlossen — ein gestern erledigter Arbeitsauftrag wurde also jeder heute gestarteten Sitzung vollständig vorgelesen. Er wird jetzt gar nicht mehr gezeichnet, und der Sitzungsstart dieses Arbeitsbereichs fiel von 63.787 auf 16.742 Byte.\n\n**Gebraucht wurde er nie, weil alle drei Zustellwege die Nachricht ohnehin schieben.** `nxc send --to` startet eine Sitzung mit der Nachricht im Prompt; `nxc reply --thread` nimmt die Sitzung auf der anderen Seite mit dem Rumpf der Antwort wieder auf; ein Brett, dessen Quorum vollständig wird, weckt den, der es geöffnet hat. Der Abschnitt war die zweite Kopie dessen, was schon angekommen war — begrenzt durch einen Lesezeiger, den nur `nxc read` bewegte, ein Verb, das in sechsundsechzig gemessenen Agentensitzungen null Mal benutzt wurde, ebenso wie `nxc inbox`. Beide Verben entfallen mit dem Abschnitt, dem sie dienten.\n\n**An ihre Stelle tritt die Grenze, die das zieht: Menschen ziehen, Agenten bekommen geschoben.** Ein Mensch am Terminal hat keinen Prompt, in den etwas geschoben würde — die beiden Lesungen, die ein Mensch tatsächlich benutzt, bleiben deshalb unangetastet: `nxc threads show ` für ein Gespräch in voller Länge, `nxc status` für den Stand eines Vorgangs. Nichts, was der Sitzungsstart bisher sagte, ist dadurch schwerer erreichbar geworden: ein Faden, der auf eine Antwort der lesenden Sitzung wartet, stand in diesem Abschnitt ohnehin nie (er listet Bretter, die SIE geöffnet haben und auf die jemand anderes antworten muss) — es fällt also nichts stillschweigend mit weg.\n\n**Für eine einbettende Anwendung ändert sich an den Daten nichts.** `nxc prime --json` trägt `threads_you_opened` weiterhin mit jedem Brett und jedem Nachrichtenrumpf, `in_turn`/`next_session` weiterhin das Ungelesene, und die Bestätigung, die den Lesezeiger bewegt (`Engine::mark_read`), ist unberührt. Von der Bibliotheksoberfläche entfallen ist der Markdown-Zeichner dieses Abschnitts, `nexus_chat::facade::render_opener_wake`, sowie die beiden Kommandozeilen-Eingänge.", - "migration": { - "en": "Automatic: nothing to do, no data changes, and no stored state is touched. Manual, only if you script against the CLI: `nxc inbox` and `nxc read` no longer exist. Read a conversation with `nxc threads show ` or `nxc status`; read the unread set as data from `nxc prime --json` (`in_turn`, `next_session`, `count`), which is the same derivation `inbox` printed. An application embedding `nexus-chat` acknowledges messages with `Engine::mark_read` exactly as before; only `facade::render_opener_wake` is gone, and a caller that drew that block itself must now render it from `PrimeReport::wake`.", - "de": "Automatisch: nichts zu tun, keine Datenänderung, kein gespeicherter Zustand wird angefasst. Von Hand, nur wenn Sie gegen die Kommandozeile skripten: `nxc inbox` und `nxc read` gibt es nicht mehr. Ein Gespräch lesen Sie mit `nxc threads show ` oder `nxc status`; das Ungelesene als Daten liefert `nxc prime --json` (`in_turn`, `next_session`, `count`) — dieselbe Ableitung, die `inbox` gedruckt hat. Eine Anwendung, die `nexus-chat` einbettet, bestätigt Nachrichten unverändert mit `Engine::mark_read`; entfallen ist allein `facade::render_opener_wake`, und wer diesen Block selbst gezeichnet hat, zeichnet ihn jetzt aus `PrimeReport::wake`." - }, - "facade": "breaking" - } - ], - "notes": { - "en": "### Removed\n- **No message text reaches a session start any more, and `nxc inbox` / `nxc read` are gone.** The block a session is handed when it opens still carried, under `## Threads you opened`, the full conversation of every board you had opened and that had since finished — the request, every reply, in full. On the workspace where this was measured that section alone was 46 KB of a 63 KB session start: 74 % of it, twenty-four boards, six of them finished, so a work order completed yesterday was read out in full to every session that started today. It is now not rendered at all, and that workspace's session start went from 63.787 to 16.742 bytes.\n\n**The reason it was never needed is that all three ways a message is delivered already push it.** `nxc send --to` starts a session with the message in its prompt; `nxc reply --thread` resumes the session on the other side with the reply's body; a board whose quorum completes wakes whoever opened it. The section was a second copy of what had already arrived — held down by a read cursor that only `nxc read` moved, a verb used zero times across sixty-six measured agent sessions, alongside `nxc inbox`, also zero. Both verbs are removed with the section they served.\n\n**What replaces them is the boundary this draws: people pull, agents get pushed.** A person at a terminal has no prompt for anything to be pushed into, so the two reads a person actually uses are untouched — `nxc threads show ` for one conversation in full, `nxc status` for where an operation stands. Nothing that a session start used to say has moved somewhere harder to reach: a thread waiting on an answer from the session reading the block was never in that section in the first place (it lists boards you OPENED, where somebody else owes the reply), so nothing was quietly dropped along with it.\n\n**For an embedding application nothing about the data changes.** `nxc prime --json` still carries `threads_you_opened` with every board and every message body, `in_turn`/`next_session` still carry the unread, and the acknowledgement that moves the read cursor (`Engine::mark_read`) is untouched. What was removed from the library surface is the Markdown renderer for that section, `nexus_chat::facade::render_opener_wake`, and the two command-line entrances.\n\n### Facade Contract\n- `breaking` · **No message text reaches a session start any more, and `nxc inbox` / `nxc read` are gone.** The block a session is handed when it opens still carried, under `## Threads you opened`, the full conversation of every board you had opened and that had since finished — the request, every reply, in full. On the workspace where this was measured that section alone was 46 KB of a 63 KB session start: 74 % of it, twenty-four boards, six of them finished, so a work order completed yesterday was read out in full to every session that started today. It is now not rendered at all, and that workspace's session start went from 63.787 to 16.742 bytes.\n\n**The reason it was never needed is that all three ways a message is delivered already push it.** `nxc send --to` starts a session with the message in its prompt; `nxc reply --thread` resumes the session on the other side with the reply's body; a board whose quorum completes wakes whoever opened it. The section was a second copy of what had already arrived — held down by a read cursor that only `nxc read` moved, a verb used zero times across sixty-six measured agent sessions, alongside `nxc inbox`, also zero. Both verbs are removed with the section they served.\n\n**What replaces them is the boundary this draws: people pull, agents get pushed.** A person at a terminal has no prompt for anything to be pushed into, so the two reads a person actually uses are untouched — `nxc threads show ` for one conversation in full, `nxc status` for where an operation stands. Nothing that a session start used to say has moved somewhere harder to reach: a thread waiting on an answer from the session reading the block was never in that section in the first place (it lists boards you OPENED, where somebody else owes the reply), so nothing was quietly dropped along with it.\n\n**For an embedding application nothing about the data changes.** `nxc prime --json` still carries `threads_you_opened` with every board and every message body, `in_turn`/`next_session` still carry the unread, and the acknowledgement that moves the read cursor (`Engine::mark_read`) is untouched. What was removed from the library surface is the Markdown renderer for that section, `nexus_chat::facade::render_opener_wake`, and the two command-line entrances.", - "de": "### Entfernt\n- **In einen Sitzungsstart gelangt kein Nachrichtentext mehr, und `nxc inbox` / `nxc read` sind entfallen.** Der Block, den eine Sitzung beim Öffnen bekommt, trug unter `## Threads you opened` weiterhin das vollständige Gespräch jedes Bretts, das Sie geöffnet hatten und das inzwischen fertig war — die Anfrage, jede Antwort, im Volltext. Im gemessenen Arbeitsbereich waren allein das 46 KB eines 63-KB-Sitzungsstarts: 74 %, vierundzwanzig Bretter, sechs davon abgeschlossen — ein gestern erledigter Arbeitsauftrag wurde also jeder heute gestarteten Sitzung vollständig vorgelesen. Er wird jetzt gar nicht mehr gezeichnet, und der Sitzungsstart dieses Arbeitsbereichs fiel von 63.787 auf 16.742 Byte.\n\n**Gebraucht wurde er nie, weil alle drei Zustellwege die Nachricht ohnehin schieben.** `nxc send --to` startet eine Sitzung mit der Nachricht im Prompt; `nxc reply --thread` nimmt die Sitzung auf der anderen Seite mit dem Rumpf der Antwort wieder auf; ein Brett, dessen Quorum vollständig wird, weckt den, der es geöffnet hat. Der Abschnitt war die zweite Kopie dessen, was schon angekommen war — begrenzt durch einen Lesezeiger, den nur `nxc read` bewegte, ein Verb, das in sechsundsechzig gemessenen Agentensitzungen null Mal benutzt wurde, ebenso wie `nxc inbox`. Beide Verben entfallen mit dem Abschnitt, dem sie dienten.\n\n**An ihre Stelle tritt die Grenze, die das zieht: Menschen ziehen, Agenten bekommen geschoben.** Ein Mensch am Terminal hat keinen Prompt, in den etwas geschoben würde — die beiden Lesungen, die ein Mensch tatsächlich benutzt, bleiben deshalb unangetastet: `nxc threads show ` für ein Gespräch in voller Länge, `nxc status` für den Stand eines Vorgangs. Nichts, was der Sitzungsstart bisher sagte, ist dadurch schwerer erreichbar geworden: ein Faden, der auf eine Antwort der lesenden Sitzung wartet, stand in diesem Abschnitt ohnehin nie (er listet Bretter, die SIE geöffnet haben und auf die jemand anderes antworten muss) — es fällt also nichts stillschweigend mit weg.\n\n**Für eine einbettende Anwendung ändert sich an den Daten nichts.** `nxc prime --json` trägt `threads_you_opened` weiterhin mit jedem Brett und jedem Nachrichtenrumpf, `in_turn`/`next_session` weiterhin das Ungelesene, und die Bestätigung, die den Lesezeiger bewegt (`Engine::mark_read`), ist unberührt. Von der Bibliotheksoberfläche entfallen ist der Markdown-Zeichner dieses Abschnitts, `nexus_chat::facade::render_opener_wake`, sowie die beiden Kommandozeilen-Eingänge.\n\n### Facade-Kontrakt\n- `breaking` · **In einen Sitzungsstart gelangt kein Nachrichtentext mehr, und `nxc inbox` / `nxc read` sind entfallen.** Der Block, den eine Sitzung beim Öffnen bekommt, trug unter `## Threads you opened` weiterhin das vollständige Gespräch jedes Bretts, das Sie geöffnet hatten und das inzwischen fertig war — die Anfrage, jede Antwort, im Volltext. Im gemessenen Arbeitsbereich waren allein das 46 KB eines 63-KB-Sitzungsstarts: 74 %, vierundzwanzig Bretter, sechs davon abgeschlossen — ein gestern erledigter Arbeitsauftrag wurde also jeder heute gestarteten Sitzung vollständig vorgelesen. Er wird jetzt gar nicht mehr gezeichnet, und der Sitzungsstart dieses Arbeitsbereichs fiel von 63.787 auf 16.742 Byte.\n\n**Gebraucht wurde er nie, weil alle drei Zustellwege die Nachricht ohnehin schieben.** `nxc send --to` startet eine Sitzung mit der Nachricht im Prompt; `nxc reply --thread` nimmt die Sitzung auf der anderen Seite mit dem Rumpf der Antwort wieder auf; ein Brett, dessen Quorum vollständig wird, weckt den, der es geöffnet hat. Der Abschnitt war die zweite Kopie dessen, was schon angekommen war — begrenzt durch einen Lesezeiger, den nur `nxc read` bewegte, ein Verb, das in sechsundsechzig gemessenen Agentensitzungen null Mal benutzt wurde, ebenso wie `nxc inbox`. Beide Verben entfallen mit dem Abschnitt, dem sie dienten.\n\n**An ihre Stelle tritt die Grenze, die das zieht: Menschen ziehen, Agenten bekommen geschoben.** Ein Mensch am Terminal hat keinen Prompt, in den etwas geschoben würde — die beiden Lesungen, die ein Mensch tatsächlich benutzt, bleiben deshalb unangetastet: `nxc threads show ` für ein Gespräch in voller Länge, `nxc status` für den Stand eines Vorgangs. Nichts, was der Sitzungsstart bisher sagte, ist dadurch schwerer erreichbar geworden: ein Faden, der auf eine Antwort der lesenden Sitzung wartet, stand in diesem Abschnitt ohnehin nie (er listet Bretter, die SIE geöffnet haben und auf die jemand anderes antworten muss) — es fällt also nichts stillschweigend mit weg.\n\n**Für eine einbettende Anwendung ändert sich an den Daten nichts.** `nxc prime --json` trägt `threads_you_opened` weiterhin mit jedem Brett und jedem Nachrichtenrumpf, `in_turn`/`next_session` weiterhin das Ungelesene, und die Bestätigung, die den Lesezeiger bewegt (`Engine::mark_read`), ist unberührt. Von der Bibliotheksoberfläche entfallen ist der Markdown-Zeichner dieses Abschnitts, `nexus_chat::facade::render_opener_wake`, sowie die beiden Kommandozeilen-Eingänge." - } - }, - { - "version": "0.68.1", - "date": "2026-08-27", - "items": [ - { - "type": "fixed", - "en": "**An introduction written on one machine no longer disappears on another.** When a memory's introduction reached a second machine before that machine had been upgraded, the older version had nowhere to put it — it kept the record but could not read it, and marked everything up to that point as processed. After the upgrade it never looked back, so the line stayed invisible on that machine while every other one showed it. Opening a workspace whose storage this version upgrades now re-reads the whole history, which brings back not only introductions but anything else an older version had to set aside — the same is true of a memory's section, its reach and its references, which have had this gap since they were introduced.\n\n**A malformed introduction arriving from another machine can no longer break the session start.** Writing one is checked here — one line, at most 200 characters — but a record arriving from elsewhere was taken as-is, so an older or faulty peer could turn one memory into several bullet points, or into a very long one. The session start now bounds what it shows, so \"one memory, one line\" holds for records this version did not write either.\n\n**And the session start now says when it is running out of room.** Past roughly 26 KB the host stops delivering it and hands the agent a short preview and a file path instead. `nxs prime` now measures itself and, once it comes close, adds a line naming its size, the limit, and the two commands that take space back. It stays silent the rest of the time — and because it warns while there is still room, the warning arrives when it can still be acted on.", - "de": "**Eine Einleitung, die auf einem Rechner geschrieben wurde, verschwindet auf einem anderen nicht mehr.** Erreichte die Einleitung einer Erinnerung einen zweiten Rechner, bevor dieser aktualisiert war, konnte die ältere Fassung nichts damit anfangen — sie hob den Eintrag auf, konnte ihn aber nicht lesen, und vermerkte alles bis dahin als verarbeitet. Nach dem Upgrade schaute sie nie wieder zurück, und so blieb die Zeile auf diesem Rechner unsichtbar, während jeder andere sie zeigte. Beim Öffnen eines Arbeitsbereichs, dessen Speicher diese Fassung aktualisiert, wird die Historie jetzt vollständig neu gelesen. Das holt nicht nur Einleitungen zurück, sondern alles, was eine ältere Fassung beiseitelegen musste — dasselbe gilt für Abschnitt, Reichweite und Referenzen einer Erinnerung, die diese Lücke seit ihrer Einführung hatten.\n\n**Eine fehlerhafte Einleitung von einem anderen Rechner kann den Sitzungsstart nicht mehr zerreißen.** Beim Schreiben wird geprüft — eine Zeile, höchstens 200 Zeichen —, ein von anderswo eintreffender Eintrag wurde aber ungeprüft übernommen. Ein älterer oder fehlerhafter Gegenüber konnte so aus einer Erinnerung mehrere Aufzählungspunkte machen, oder einen sehr langen. Der Sitzungsstart begrenzt jetzt, was er zeigt; „eine Erinnerung, eine Zeile\" gilt damit auch für Einträge, die diese Fassung nicht selbst geschrieben hat.\n\n**Und der Sitzungsstart sagt jetzt Bescheid, wenn ihm der Platz ausgeht.** Oberhalb von rund 26 KB liefert der Wirt ihn nicht mehr aus, sondern gibt dem Agenten eine kurze Vorschau und einen Dateipfad. `nxs prime` misst sich jetzt selbst und hängt, sobald es eng wird, eine Zeile an, die die eigene Größe, die Grenze und die zwei Befehle nennt, die wieder Platz schaffen. Sonst schweigt er — und weil er warnt, solange noch Platz ist, kommt die Warnung an, während man noch handeln kann.", - "facade": "changed" - } - ], - "notes": { - "en": "### Fixed\n- **An introduction written on one machine no longer disappears on another.** When a memory's introduction reached a second machine before that machine had been upgraded, the older version had nowhere to put it — it kept the record but could not read it, and marked everything up to that point as processed. After the upgrade it never looked back, so the line stayed invisible on that machine while every other one showed it. Opening a workspace whose storage this version upgrades now re-reads the whole history, which brings back not only introductions but anything else an older version had to set aside — the same is true of a memory's section, its reach and its references, which have had this gap since they were introduced.\n\n**A malformed introduction arriving from another machine can no longer break the session start.** Writing one is checked here — one line, at most 200 characters — but a record arriving from elsewhere was taken as-is, so an older or faulty peer could turn one memory into several bullet points, or into a very long one. The session start now bounds what it shows, so \"one memory, one line\" holds for records this version did not write either.\n\n**And the session start now says when it is running out of room.** Past roughly 26 KB the host stops delivering it and hands the agent a short preview and a file path instead. `nxs prime` now measures itself and, once it comes close, adds a line naming its size, the limit, and the two commands that take space back. It stays silent the rest of the time — and because it warns while there is still room, the warning arrives when it can still be acted on.\n\n### Facade Contract\n- `changed` · **An introduction written on one machine no longer disappears on another.** When a memory's introduction reached a second machine before that machine had been upgraded, the older version had nowhere to put it — it kept the record but could not read it, and marked everything up to that point as processed. After the upgrade it never looked back, so the line stayed invisible on that machine while every other one showed it. Opening a workspace whose storage this version upgrades now re-reads the whole history, which brings back not only introductions but anything else an older version had to set aside — the same is true of a memory's section, its reach and its references, which have had this gap since they were introduced.\n\n**A malformed introduction arriving from another machine can no longer break the session start.** Writing one is checked here — one line, at most 200 characters — but a record arriving from elsewhere was taken as-is, so an older or faulty peer could turn one memory into several bullet points, or into a very long one. The session start now bounds what it shows, so \"one memory, one line\" holds for records this version did not write either.\n\n**And the session start now says when it is running out of room.** Past roughly 26 KB the host stops delivering it and hands the agent a short preview and a file path instead. `nxs prime` now measures itself and, once it comes close, adds a line naming its size, the limit, and the two commands that take space back. It stays silent the rest of the time — and because it warns while there is still room, the warning arrives when it can still be acted on.", - "de": "### Behoben\n- **Eine Einleitung, die auf einem Rechner geschrieben wurde, verschwindet auf einem anderen nicht mehr.** Erreichte die Einleitung einer Erinnerung einen zweiten Rechner, bevor dieser aktualisiert war, konnte die ältere Fassung nichts damit anfangen — sie hob den Eintrag auf, konnte ihn aber nicht lesen, und vermerkte alles bis dahin als verarbeitet. Nach dem Upgrade schaute sie nie wieder zurück, und so blieb die Zeile auf diesem Rechner unsichtbar, während jeder andere sie zeigte. Beim Öffnen eines Arbeitsbereichs, dessen Speicher diese Fassung aktualisiert, wird die Historie jetzt vollständig neu gelesen. Das holt nicht nur Einleitungen zurück, sondern alles, was eine ältere Fassung beiseitelegen musste — dasselbe gilt für Abschnitt, Reichweite und Referenzen einer Erinnerung, die diese Lücke seit ihrer Einführung hatten.\n\n**Eine fehlerhafte Einleitung von einem anderen Rechner kann den Sitzungsstart nicht mehr zerreißen.** Beim Schreiben wird geprüft — eine Zeile, höchstens 200 Zeichen —, ein von anderswo eintreffender Eintrag wurde aber ungeprüft übernommen. Ein älterer oder fehlerhafter Gegenüber konnte so aus einer Erinnerung mehrere Aufzählungspunkte machen, oder einen sehr langen. Der Sitzungsstart begrenzt jetzt, was er zeigt; „eine Erinnerung, eine Zeile\" gilt damit auch für Einträge, die diese Fassung nicht selbst geschrieben hat.\n\n**Und der Sitzungsstart sagt jetzt Bescheid, wenn ihm der Platz ausgeht.** Oberhalb von rund 26 KB liefert der Wirt ihn nicht mehr aus, sondern gibt dem Agenten eine kurze Vorschau und einen Dateipfad. `nxs prime` misst sich jetzt selbst und hängt, sobald es eng wird, eine Zeile an, die die eigene Größe, die Grenze und die zwei Befehle nennt, die wieder Platz schaffen. Sonst schweigt er — und weil er warnt, solange noch Platz ist, kommt die Warnung an, während man noch handeln kann.\n\n### Facade-Kontrakt\n- `changed` · **Eine Einleitung, die auf einem Rechner geschrieben wurde, verschwindet auf einem anderen nicht mehr.** Erreichte die Einleitung einer Erinnerung einen zweiten Rechner, bevor dieser aktualisiert war, konnte die ältere Fassung nichts damit anfangen — sie hob den Eintrag auf, konnte ihn aber nicht lesen, und vermerkte alles bis dahin als verarbeitet. Nach dem Upgrade schaute sie nie wieder zurück, und so blieb die Zeile auf diesem Rechner unsichtbar, während jeder andere sie zeigte. Beim Öffnen eines Arbeitsbereichs, dessen Speicher diese Fassung aktualisiert, wird die Historie jetzt vollständig neu gelesen. Das holt nicht nur Einleitungen zurück, sondern alles, was eine ältere Fassung beiseitelegen musste — dasselbe gilt für Abschnitt, Reichweite und Referenzen einer Erinnerung, die diese Lücke seit ihrer Einführung hatten.\n\n**Eine fehlerhafte Einleitung von einem anderen Rechner kann den Sitzungsstart nicht mehr zerreißen.** Beim Schreiben wird geprüft — eine Zeile, höchstens 200 Zeichen —, ein von anderswo eintreffender Eintrag wurde aber ungeprüft übernommen. Ein älterer oder fehlerhafter Gegenüber konnte so aus einer Erinnerung mehrere Aufzählungspunkte machen, oder einen sehr langen. Der Sitzungsstart begrenzt jetzt, was er zeigt; „eine Erinnerung, eine Zeile\" gilt damit auch für Einträge, die diese Fassung nicht selbst geschrieben hat.\n\n**Und der Sitzungsstart sagt jetzt Bescheid, wenn ihm der Platz ausgeht.** Oberhalb von rund 26 KB liefert der Wirt ihn nicht mehr aus, sondern gibt dem Agenten eine kurze Vorschau und einen Dateipfad. `nxs prime` misst sich jetzt selbst und hängt, sobald es eng wird, eine Zeile an, die die eigene Größe, die Grenze und die zwei Befehle nennt, die wieder Platz schaffen. Sonst schweigt er — und weil er warnt, solange noch Platz ist, kommt die Warnung an, während man noch handeln kann." - } - }, - { - "version": "0.68.0", - "date": "2026-08-27", - "items": [ - { - "type": "changed", - "en": "**Your session start is an index now, and it fits.** Until this release, every memory a workspace held was replayed in full at the start of every session. On the four largest workspaces we measured that came to 90 KB of text — and the host that feeds it to the agent does not simply charge more for a large block, it *files it away*: past roughly 26 KB it hands the session a two-kilobyte preview and a file path instead. So the block that was meant to carry the board, the core rules and the command vocabulary delivered none of them, and the agent went off to read files by hand. Memories are now replayed as one line each, and the whole session start of this project's own workspace fits comfortably under the cut-off.\n\n**That one line is written, not derived — and `nxm remember` now requires it.** `--introduction` takes one line of at most 200 characters; longer is refused at the write, with the length it measured named in the message. It is the only part of a memory a session start ever sees, so it should say what the memory *says*, not what it is about — and for a memory filed under `rules` it should speak the rule outright (\"Never run the lean build while the tests are running\"), because nobody looks a prohibition up before breaking one. The full text is a `nxm recall ` away and is unchanged; `nxm prime --json` still carries every body in full, so anything built on that surface is unaffected. A memory written before this release has no line yet: it reads as `_(no introduction written yet)_`, the block says how many are in that state, and `nxm classify --introduction \"…\"` closes the gap without touching the text.\n\n**Two smaller changes come with it.** `nxs prime` now runs the tools in a fixed order — board, chat, memory — so that if a session start is ever truncated anyway, what falls away is the one part an agent can fetch back (`nxm recall`, `nxm memories`) rather than its board or its vocabulary. And `NEXUS_MEMORY.md` now opens with the same index, under a note saying that this half is already in the session's context; the full text stays underneath it, so a change to a memory is still visible in a pull request.", - "de": "**Ihr Sitzungsstart ist jetzt ein Index — und er passt.** Bis zu dieser Fassung wurde jede Erinnerung eines Arbeitsbereichs zu Beginn jeder Sitzung im Volltext eingespielt. Auf den vier größten gemessenen Arbeitsbereichen waren das 90 KB Text — und der Wirt, der das an den Agenten weitergibt, berechnet einen großen Block nicht einfach teurer, er *legt ihn ab*: oberhalb von rund 26 KB bekommt die Sitzung statt des Blocks eine Zwei-Kilobyte-Vorschau und einen Dateipfad. Der Block, der Brett, Kernregeln und Befehlsvokabular tragen sollte, lieferte also nichts davon, und der Agent las die Dateien anschließend von Hand. Erinnerungen werden jetzt als je eine Zeile eingespielt, und der gesamte Sitzungsstart des Arbeitsbereichs dieses Projekts liegt bequem unter der Grenze.\n\n**Diese eine Zeile wird geschrieben, nicht abgeleitet — und `nxm remember` verlangt sie.** `--introduction` nimmt eine Zeile mit höchstens 200 Zeichen; länger wird beim Schreiben abgewiesen, mit der gemessenen Länge in der Meldung. Sie ist das Einzige an einer Erinnerung, was ein Sitzungsstart je zu sehen bekommt — sie sollte also sagen, was die Erinnerung *sagt*, nicht wovon sie handelt, und bei einer Erinnerung unter `rules` das Verbot direkt aussprechen („Never run the lean build while the tests are running\"), denn niemand schlägt ein Verbot nach, bevor er es bricht. Der Volltext ist ein `nxm recall ` entfernt und unverändert; `nxm prime --json` liefert weiterhin jeden Rumpf vollständig, alles darauf Aufbauende bleibt also unberührt. Eine vor dieser Fassung geschriebene Erinnerung hat noch keine Zeile: sie erscheint als `_(no introduction written yet)_`, der Block nennt die Anzahl, und `nxm classify --introduction \"…\"` schließt die Lücke, ohne den Text anzufassen.\n\n**Zwei kleinere Änderungen kommen mit.** `nxs prime` führt die Werkzeuge jetzt in fester Reihenfolge aus — Brett, Chat, Erinnerungen —, damit bei einer Kürzung genau das wegfällt, was ein Agent nachholen kann (`nxm recall`, `nxm memories`), und nie das Brett oder das Vokabular. Und `NEXUS_MEMORY.md` beginnt jetzt mit demselben Index, unter einem Hinweis, dass diese Hälfte bereits im Kontext der Sitzung steht; der Volltext bleibt darunter, damit eine Änderung an einer Erinnerung weiterhin im Änderungsvorschlag sichtbar ist.", - "migration": { - "en": "Automatic: the new `introduction` column appears on upgrade, and every existing memory keeps its text, its filing and its order untouched. Manual: every memory written before this release needs a line, and until it has one it reads as a named placeholder at session start. Write them with `nxm classify --introduction \"\"` — and do it before the first agent session on this version, because `nxm remember` will not evolve a memory in place without one.", - "de": "Automatisch: die neue Spalte `introduction` erscheint beim Upgrade, und jede bestehende Erinnerung behält Text, Einordnung und Reihenfolge unverändert. Von Hand: jede vor dieser Fassung geschriebene Erinnerung braucht eine Zeile, und bis sie eine hat, erscheint sie beim Sitzungsstart als benannter Platzhalter. Schreiben Sie sie mit `nxm classify --introduction \"\"` — und zwar vor der ersten Agenten-Sitzung auf dieser Fassung, denn `nxm remember` entwickelt eine Erinnerung ohne sie nicht weiter." - }, - "facade": "breaking" - }, - { - "type": "changed", - "en": "**A task you send to a single agent now reaches a coordinator first, exactly as a task sent to a channel already did.** Until now the two were different: a message to a channel was taken in by a named piece of software that decided what happened next, while a message to one agent went straight through to the agent, with nobody in between. That gap is where the two worst failures of the overnight proving runs came from — in both, a correctly built agent followed a correctly written instruction and the damage happened anyway, because what has to happen every time was written in a prompt instead of built into the structure. The coordinator now takes the message in, opens the conversation thread and hands its id back, records that the agent owes an answer on it, and states up front what happens if that answer never comes. Nothing about how you use `nxc send --to ` changes, and a direct conversation stays a direct conversation — the coordinator is software standing in the path, not a third party in your thread.\n\nTwo failures that used to look alike are now told apart. An agent that ends its turn without answering is reminded once and given the turn back, as before. A run the model runtime never actually started — an overloaded API answering `529` before the agent got to think — is something else entirely: it is retried, with a growing pause between attempts, and it is no longer sent a reminder about a rule it never had the chance to break. Both paths are bounded, and past either bound the round is escalated to a human with the reason named: \"the runtime never ran this session, 3 attempts\" reads differently from \"this session ended without answering\", and they call for different things from whoever reads them. Both limits are decided by the engine and travel with the task, so a host that plugs in its own agent runtime inherits them instead of having to re-invent them.", - "de": "**Ein Auftrag an einen einzelnen Agenten erreicht jetzt zuerst einen Koordinator — genau wie ein Auftrag an einen Kanal es schon tat.** Bisher waren die beiden verschieden: eine Nachricht an einen Kanal nahm eine benannte Stelle in der Software entgegen, die entschied, was als Nächstes geschah; eine Nachricht an einen einzelnen Agenten ging direkt durch, ohne dass jemand dazwischen stand. Genau dort sind die beiden schwersten Fehler der nächtlichen Prüfläufe entstanden — beide Male hat eine korrekt gebaute Persona eine korrekt formulierte Anweisung befolgt, und der Schaden trat trotzdem ein, weil das, was zwingend passieren muss, im Prompt stand statt in der Struktur. Der Koordinator nimmt die Nachricht jetzt entgegen, legt den Gesprächsfaden an und gibt dessen Id zurück, hält fest, dass der Agent darauf eine Antwort schuldet, und sagt vorab, was geschieht, wenn diese Antwort ausbleibt. An der Bedienung von `nxc send --to ` ändert sich nichts, und ein direktes Gespräch bleibt ein direktes Gespräch — der Koordinator ist Software im Weg, keine dritte Partei in Ihrem Faden.\n\nZwei Fehlschläge, die bisher gleich aussahen, werden jetzt unterschieden. Ein Agent, der seinen Zug beendet, ohne zu antworten, wird wie bisher einmal erinnert und bekommt den Zug zurück. Ein Lauf, den die Modell-Laufzeit nie gestartet hat — eine überlastete API, die mit `529` antwortet, bevor der Agent zum Denken kam — ist etwas völlig anderes: er wird wiederholt, mit wachsender Pause zwischen den Versuchen, und er bekommt keine Erinnerung mehr an eine Regel, die er nie brechen konnte. Beide Wege haben eine Obergrenze, und jenseits davon geht die Runde mit benanntem Grund an einen Menschen: „die Laufzeit hat diese Sitzung nie gestartet, 3 Versuche\" liest sich anders als „diese Sitzung endete ohne Antwort\", und beide verlangen etwas anderes von dem, der es liest. Beide Grenzen entscheidet die Engine und gibt sie dem Auftrag mit, damit ein Wirt mit eigener Agenten-Laufzeit sie erbt, statt sie neu erfinden zu müssen.", - "facade": "breaking" - }, - { - "type": "fixed", - "en": "**A chain that is waiting for the working copy is no longer overtaken by one that just arrived.** When a role session dies hard, its claim on the repository's working copy runs to a backstop and is then reclaimable — and until now the next chain to *ask* took it, even when a rival had been standing in line since long before. Waiting was punished by waiting, and in the worst case indefinitely. Reclaiming an expired claim now hands the turn to whoever is already in the queue, in the same step, so the order you can see on the board (`nxc threads show`) is the order the work actually starts in. It still takes no daemon, no cleanup run and no command of your own: the chain that arrives is what makes it happen.\n\n**And the hand-over no longer has a gap.** Giving the working copy back and starting the chain that was waiting for it were already one indivisible step, but the claim was briefly free between that step and the waiting session actually starting — long enough for a third chain to slip in and put the promoted one back at the end of the line. The copy now passes straight from one chain to the next, so there is no instant at which it is free while somebody is waiting for it. Two concurrent replies that both finish the same piece of work still start the next chain exactly once.", - "de": "**Eine Kette, die auf die Arbeitskopie wartet, wird nicht mehr von einer gerade eingetroffenen überholt.** Stirbt eine Rollen-Sitzung hart, läuft ihr Anspruch auf die Arbeitskopie des Projekts in eine Frist und ist danach wieder zu holen — und bisher bekam sie, wer als Nächstes *fragte*, auch wenn ein Rivale längst in der Schlange stand. Wer wartete, wurde durchs Warten bestraft, im schlimmsten Fall unbegrenzt. Das Zurückholen eines abgelaufenen Anspruchs gibt den Zug jetzt im selben Schritt an den weiter, der schon ansteht — die Reihenfolge auf dem Brett (`nxc threads show`) ist damit die Reihenfolge, in der wirklich gearbeitet wird. Es braucht dafür weiterhin keinen Dienst, keinen Aufräumlauf und keinen eigenen Befehl: die Kette, die kommt, löst es aus.\n\n**Und die Übergabe hat keine Lücke mehr.** Die Arbeitskopie zurückzugeben und die wartende Kette zu starten war schon bisher ein einziger, unteilbarer Schritt — frei war der Anspruch aber kurz zwischen diesem Schritt und dem tatsächlichen Start der wartenden Sitzung, lang genug, dass eine dritte Kette dazwischenrutschen und die eben beförderte wieder ans Ende der Schlange stellen konnte. Die Kopie geht jetzt direkt von einer Kette zur nächsten; es gibt keinen Augenblick mehr, in dem sie frei ist, während jemand auf sie wartet. Zwei gleichzeitige Antworten, die dieselbe Arbeit abschließen, starten die nächste Kette weiterhin genau einmal.", - "facade": "breaking" - }, - { - "type": "changed", - "en": "**An agent started by nexus-flow now begins with the same briefing you get at your own terminal.** Until now it did not: a spawned agent runs deliberately sealed off from your editor's plugins and settings, and the session-start briefing arrived through exactly those settings — so it never ran. What such an agent knew about talking to its colleagues was four commands, written out by hand in the source, and measurably the wrong four. It now receives the whole briefing as text on the same path the project's conventions already take: who it is, whom it may address and what for, the board, the project's memories, and how it answers. Each agent's declaration can leave services out — `prime: {flow: false}` gives a pure reviewer no board, and only that reviewer — and `nxs prime --persona ` prints exactly what that agent reads, so you can check it rather than infer it.\n\n**The briefing itself got smaller and more useful.** It used to open with every unread message in full text: measured at 195 KB, 98 % of it message bodies, and back tenfold within a working day — a bill every session paid for something no agent ever needed, since an agent is handed its task in the task itself. Those bodies are gone from the text (nothing changed about what apps can read). In their place are the two commands that were measurably missing — `nxc threads show`, the second-most-used command of all and the only place a held working copy is visible, and `nxc withdraw` — plus a short paragraph on what to do when a task will not move. The project's memories are now split the way the board always was: rules are replayed in full, because a rule is only worth something when it is present *before* the mistake, and everything else is one line and a key you can look up. That class has a hard size ceiling, and a memory that would exceed it is refused when it is written, with the ways out named.", - "de": "**Ein von nexus-flow gestarteter Agent beginnt jetzt mit derselben Einweisung, die Sie an Ihrem eigenen Terminal bekommen.** Bisher nicht: ein gestarteter Agent läuft bewusst abgeschottet von den Erweiterungen und Einstellungen Ihres Editors — und genau über diese Einstellungen kam die Einweisung zum Sitzungsstart, also lief sie nie. Was so ein Agent über das Gespräch mit seinen Kollegen wusste, waren vier von Hand in den Quelltext geschriebene Befehle, und nachweislich die falschen vier. Er bekommt die ganze Einweisung jetzt als Text, auf demselben Weg, den die Projektkonventionen ohnehin nehmen: wer er ist, wen er ansprechen darf und wofür, das Aufgabenbrett, die Erinnerungen des Projekts und wie er antwortet. Die Deklaration jedes Agenten kann Dienste weglassen — `prime: {flow: false}` gibt einem reinen Prüfer kein Brett, und nur ihm —, und `nxs prime --persona ` druckt genau das, was dieser Agent liest, sodass Sie es prüfen können, statt es zu vermuten.\n\n**Die Einweisung selbst ist kleiner und nützlicher geworden.** Sie begann bisher mit jeder ungelesenen Nachricht im Volltext: gemessen 195 KB, davon 98 % Nachrichtentext, und nach einem Arbeitstag zehnfach zurückgewachsen — eine Rechnung, die jede Sitzung für etwas bezahlte, das kein Agent je brauchte, denn ein Agent bekommt seine Aufgabe in der Aufgabe selbst. Diese Texte sind aus der Einweisung verschwunden (an dem, was Anwendungen lesen können, ändert sich nichts). An ihrer Stelle stehen die zwei Befehle, die nachweislich fehlten — `nxc threads show`, der zweithäufigst benutzte überhaupt und der einzige Ort, an dem eine gehaltene Arbeitskopie sichtbar wird, und `nxc withdraw` — sowie ein kurzer Abschnitt darüber, was zu tun ist, wenn eine Aufgabe nicht vorankommt. Die Erinnerungen des Projekts werden jetzt so geteilt, wie es das Brett immer schon tat: Regeln kommen vollständig, weil eine Regel nur dann etwas wert ist, wenn sie *vor* dem Fehler da ist, und alles andere ist eine Zeile und ein Schlüssel zum Nachschlagen. Diese Klasse hat eine harte Obergrenze, und eine Erinnerung, die sie sprengen würde, wird beim Schreiben abgewiesen — mit den Auswegen im Klartext.", - "facade": "breaking" - } - ], - "notes": { - "en": "### Changed\n- **Your session start is an index now, and it fits.** Until this release, every memory a workspace held was replayed in full at the start of every session. On the four largest workspaces we measured that came to 90 KB of text — and the host that feeds it to the agent does not simply charge more for a large block, it *files it away*: past roughly 26 KB it hands the session a two-kilobyte preview and a file path instead. So the block that was meant to carry the board, the core rules and the command vocabulary delivered none of them, and the agent went off to read files by hand. Memories are now replayed as one line each, and the whole session start of this project's own workspace fits comfortably under the cut-off.\n\n**That one line is written, not derived — and `nxm remember` now requires it.** `--introduction` takes one line of at most 200 characters; longer is refused at the write, with the length it measured named in the message. It is the only part of a memory a session start ever sees, so it should say what the memory *says*, not what it is about — and for a memory filed under `rules` it should speak the rule outright (\"Never run the lean build while the tests are running\"), because nobody looks a prohibition up before breaking one. The full text is a `nxm recall ` away and is unchanged; `nxm prime --json` still carries every body in full, so anything built on that surface is unaffected. A memory written before this release has no line yet: it reads as `_(no introduction written yet)_`, the block says how many are in that state, and `nxm classify --introduction \"…\"` closes the gap without touching the text.\n\n**Two smaller changes come with it.** `nxs prime` now runs the tools in a fixed order — board, chat, memory — so that if a session start is ever truncated anyway, what falls away is the one part an agent can fetch back (`nxm recall`, `nxm memories`) rather than its board or its vocabulary. And `NEXUS_MEMORY.md` now opens with the same index, under a note saying that this half is already in the session's context; the full text stays underneath it, so a change to a memory is still visible in a pull request.\n- **A task you send to a single agent now reaches a coordinator first, exactly as a task sent to a channel already did.** Until now the two were different: a message to a channel was taken in by a named piece of software that decided what happened next, while a message to one agent went straight through to the agent, with nobody in between. That gap is where the two worst failures of the overnight proving runs came from — in both, a correctly built agent followed a correctly written instruction and the damage happened anyway, because what has to happen every time was written in a prompt instead of built into the structure. The coordinator now takes the message in, opens the conversation thread and hands its id back, records that the agent owes an answer on it, and states up front what happens if that answer never comes. Nothing about how you use `nxc send --to ` changes, and a direct conversation stays a direct conversation — the coordinator is software standing in the path, not a third party in your thread.\n\nTwo failures that used to look alike are now told apart. An agent that ends its turn without answering is reminded once and given the turn back, as before. A run the model runtime never actually started — an overloaded API answering `529` before the agent got to think — is something else entirely: it is retried, with a growing pause between attempts, and it is no longer sent a reminder about a rule it never had the chance to break. Both paths are bounded, and past either bound the round is escalated to a human with the reason named: \"the runtime never ran this session, 3 attempts\" reads differently from \"this session ended without answering\", and they call for different things from whoever reads them. Both limits are decided by the engine and travel with the task, so a host that plugs in its own agent runtime inherits them instead of having to re-invent them.\n- **An agent started by nexus-flow now begins with the same briefing you get at your own terminal.** Until now it did not: a spawned agent runs deliberately sealed off from your editor's plugins and settings, and the session-start briefing arrived through exactly those settings — so it never ran. What such an agent knew about talking to its colleagues was four commands, written out by hand in the source, and measurably the wrong four. It now receives the whole briefing as text on the same path the project's conventions already take: who it is, whom it may address and what for, the board, the project's memories, and how it answers. Each agent's declaration can leave services out — `prime: {flow: false}` gives a pure reviewer no board, and only that reviewer — and `nxs prime --persona ` prints exactly what that agent reads, so you can check it rather than infer it.\n\n**The briefing itself got smaller and more useful.** It used to open with every unread message in full text: measured at 195 KB, 98 % of it message bodies, and back tenfold within a working day — a bill every session paid for something no agent ever needed, since an agent is handed its task in the task itself. Those bodies are gone from the text (nothing changed about what apps can read). In their place are the two commands that were measurably missing — `nxc threads show`, the second-most-used command of all and the only place a held working copy is visible, and `nxc withdraw` — plus a short paragraph on what to do when a task will not move. The project's memories are now split the way the board always was: rules are replayed in full, because a rule is only worth something when it is present *before* the mistake, and everything else is one line and a key you can look up. That class has a hard size ceiling, and a memory that would exceed it is refused when it is written, with the ways out named.\n\n### Fixed\n- **A chain that is waiting for the working copy is no longer overtaken by one that just arrived.** When a role session dies hard, its claim on the repository's working copy runs to a backstop and is then reclaimable — and until now the next chain to *ask* took it, even when a rival had been standing in line since long before. Waiting was punished by waiting, and in the worst case indefinitely. Reclaiming an expired claim now hands the turn to whoever is already in the queue, in the same step, so the order you can see on the board (`nxc threads show`) is the order the work actually starts in. It still takes no daemon, no cleanup run and no command of your own: the chain that arrives is what makes it happen.\n\n**And the hand-over no longer has a gap.** Giving the working copy back and starting the chain that was waiting for it were already one indivisible step, but the claim was briefly free between that step and the waiting session actually starting — long enough for a third chain to slip in and put the promoted one back at the end of the line. The copy now passes straight from one chain to the next, so there is no instant at which it is free while somebody is waiting for it. Two concurrent replies that both finish the same piece of work still start the next chain exactly once.\n\n### Facade Contract\n- `breaking` · **Your session start is an index now, and it fits.** Until this release, every memory a workspace held was replayed in full at the start of every session. On the four largest workspaces we measured that came to 90 KB of text — and the host that feeds it to the agent does not simply charge more for a large block, it *files it away*: past roughly 26 KB it hands the session a two-kilobyte preview and a file path instead. So the block that was meant to carry the board, the core rules and the command vocabulary delivered none of them, and the agent went off to read files by hand. Memories are now replayed as one line each, and the whole session start of this project's own workspace fits comfortably under the cut-off.\n\n**That one line is written, not derived — and `nxm remember` now requires it.** `--introduction` takes one line of at most 200 characters; longer is refused at the write, with the length it measured named in the message. It is the only part of a memory a session start ever sees, so it should say what the memory *says*, not what it is about — and for a memory filed under `rules` it should speak the rule outright (\"Never run the lean build while the tests are running\"), because nobody looks a prohibition up before breaking one. The full text is a `nxm recall ` away and is unchanged; `nxm prime --json` still carries every body in full, so anything built on that surface is unaffected. A memory written before this release has no line yet: it reads as `_(no introduction written yet)_`, the block says how many are in that state, and `nxm classify --introduction \"…\"` closes the gap without touching the text.\n\n**Two smaller changes come with it.** `nxs prime` now runs the tools in a fixed order — board, chat, memory — so that if a session start is ever truncated anyway, what falls away is the one part an agent can fetch back (`nxm recall`, `nxm memories`) rather than its board or its vocabulary. And `NEXUS_MEMORY.md` now opens with the same index, under a note saying that this half is already in the session's context; the full text stays underneath it, so a change to a memory is still visible in a pull request.\n- `breaking` · **A task you send to a single agent now reaches a coordinator first, exactly as a task sent to a channel already did.** Until now the two were different: a message to a channel was taken in by a named piece of software that decided what happened next, while a message to one agent went straight through to the agent, with nobody in between. That gap is where the two worst failures of the overnight proving runs came from — in both, a correctly built agent followed a correctly written instruction and the damage happened anyway, because what has to happen every time was written in a prompt instead of built into the structure. The coordinator now takes the message in, opens the conversation thread and hands its id back, records that the agent owes an answer on it, and states up front what happens if that answer never comes. Nothing about how you use `nxc send --to ` changes, and a direct conversation stays a direct conversation — the coordinator is software standing in the path, not a third party in your thread.\n\nTwo failures that used to look alike are now told apart. An agent that ends its turn without answering is reminded once and given the turn back, as before. A run the model runtime never actually started — an overloaded API answering `529` before the agent got to think — is something else entirely: it is retried, with a growing pause between attempts, and it is no longer sent a reminder about a rule it never had the chance to break. Both paths are bounded, and past either bound the round is escalated to a human with the reason named: \"the runtime never ran this session, 3 attempts\" reads differently from \"this session ended without answering\", and they call for different things from whoever reads them. Both limits are decided by the engine and travel with the task, so a host that plugs in its own agent runtime inherits them instead of having to re-invent them.\n- `breaking` · **A chain that is waiting for the working copy is no longer overtaken by one that just arrived.** When a role session dies hard, its claim on the repository's working copy runs to a backstop and is then reclaimable — and until now the next chain to *ask* took it, even when a rival had been standing in line since long before. Waiting was punished by waiting, and in the worst case indefinitely. Reclaiming an expired claim now hands the turn to whoever is already in the queue, in the same step, so the order you can see on the board (`nxc threads show`) is the order the work actually starts in. It still takes no daemon, no cleanup run and no command of your own: the chain that arrives is what makes it happen.\n\n**And the hand-over no longer has a gap.** Giving the working copy back and starting the chain that was waiting for it were already one indivisible step, but the claim was briefly free between that step and the waiting session actually starting — long enough for a third chain to slip in and put the promoted one back at the end of the line. The copy now passes straight from one chain to the next, so there is no instant at which it is free while somebody is waiting for it. Two concurrent replies that both finish the same piece of work still start the next chain exactly once.\n- `breaking` · **An agent started by nexus-flow now begins with the same briefing you get at your own terminal.** Until now it did not: a spawned agent runs deliberately sealed off from your editor's plugins and settings, and the session-start briefing arrived through exactly those settings — so it never ran. What such an agent knew about talking to its colleagues was four commands, written out by hand in the source, and measurably the wrong four. It now receives the whole briefing as text on the same path the project's conventions already take: who it is, whom it may address and what for, the board, the project's memories, and how it answers. Each agent's declaration can leave services out — `prime: {flow: false}` gives a pure reviewer no board, and only that reviewer — and `nxs prime --persona ` prints exactly what that agent reads, so you can check it rather than infer it.\n\n**The briefing itself got smaller and more useful.** It used to open with every unread message in full text: measured at 195 KB, 98 % of it message bodies, and back tenfold within a working day — a bill every session paid for something no agent ever needed, since an agent is handed its task in the task itself. Those bodies are gone from the text (nothing changed about what apps can read). In their place are the two commands that were measurably missing — `nxc threads show`, the second-most-used command of all and the only place a held working copy is visible, and `nxc withdraw` — plus a short paragraph on what to do when a task will not move. The project's memories are now split the way the board always was: rules are replayed in full, because a rule is only worth something when it is present *before* the mistake, and everything else is one line and a key you can look up. That class has a hard size ceiling, and a memory that would exceed it is refused when it is written, with the ways out named.", - "de": "### Geändert\n- **Ihr Sitzungsstart ist jetzt ein Index — und er passt.** Bis zu dieser Fassung wurde jede Erinnerung eines Arbeitsbereichs zu Beginn jeder Sitzung im Volltext eingespielt. Auf den vier größten gemessenen Arbeitsbereichen waren das 90 KB Text — und der Wirt, der das an den Agenten weitergibt, berechnet einen großen Block nicht einfach teurer, er *legt ihn ab*: oberhalb von rund 26 KB bekommt die Sitzung statt des Blocks eine Zwei-Kilobyte-Vorschau und einen Dateipfad. Der Block, der Brett, Kernregeln und Befehlsvokabular tragen sollte, lieferte also nichts davon, und der Agent las die Dateien anschließend von Hand. Erinnerungen werden jetzt als je eine Zeile eingespielt, und der gesamte Sitzungsstart des Arbeitsbereichs dieses Projekts liegt bequem unter der Grenze.\n\n**Diese eine Zeile wird geschrieben, nicht abgeleitet — und `nxm remember` verlangt sie.** `--introduction` nimmt eine Zeile mit höchstens 200 Zeichen; länger wird beim Schreiben abgewiesen, mit der gemessenen Länge in der Meldung. Sie ist das Einzige an einer Erinnerung, was ein Sitzungsstart je zu sehen bekommt — sie sollte also sagen, was die Erinnerung *sagt*, nicht wovon sie handelt, und bei einer Erinnerung unter `rules` das Verbot direkt aussprechen („Never run the lean build while the tests are running\"), denn niemand schlägt ein Verbot nach, bevor er es bricht. Der Volltext ist ein `nxm recall ` entfernt und unverändert; `nxm prime --json` liefert weiterhin jeden Rumpf vollständig, alles darauf Aufbauende bleibt also unberührt. Eine vor dieser Fassung geschriebene Erinnerung hat noch keine Zeile: sie erscheint als `_(no introduction written yet)_`, der Block nennt die Anzahl, und `nxm classify --introduction \"…\"` schließt die Lücke, ohne den Text anzufassen.\n\n**Zwei kleinere Änderungen kommen mit.** `nxs prime` führt die Werkzeuge jetzt in fester Reihenfolge aus — Brett, Chat, Erinnerungen —, damit bei einer Kürzung genau das wegfällt, was ein Agent nachholen kann (`nxm recall`, `nxm memories`), und nie das Brett oder das Vokabular. Und `NEXUS_MEMORY.md` beginnt jetzt mit demselben Index, unter einem Hinweis, dass diese Hälfte bereits im Kontext der Sitzung steht; der Volltext bleibt darunter, damit eine Änderung an einer Erinnerung weiterhin im Änderungsvorschlag sichtbar ist.\n- **Ein Auftrag an einen einzelnen Agenten erreicht jetzt zuerst einen Koordinator — genau wie ein Auftrag an einen Kanal es schon tat.** Bisher waren die beiden verschieden: eine Nachricht an einen Kanal nahm eine benannte Stelle in der Software entgegen, die entschied, was als Nächstes geschah; eine Nachricht an einen einzelnen Agenten ging direkt durch, ohne dass jemand dazwischen stand. Genau dort sind die beiden schwersten Fehler der nächtlichen Prüfläufe entstanden — beide Male hat eine korrekt gebaute Persona eine korrekt formulierte Anweisung befolgt, und der Schaden trat trotzdem ein, weil das, was zwingend passieren muss, im Prompt stand statt in der Struktur. Der Koordinator nimmt die Nachricht jetzt entgegen, legt den Gesprächsfaden an und gibt dessen Id zurück, hält fest, dass der Agent darauf eine Antwort schuldet, und sagt vorab, was geschieht, wenn diese Antwort ausbleibt. An der Bedienung von `nxc send --to ` ändert sich nichts, und ein direktes Gespräch bleibt ein direktes Gespräch — der Koordinator ist Software im Weg, keine dritte Partei in Ihrem Faden.\n\nZwei Fehlschläge, die bisher gleich aussahen, werden jetzt unterschieden. Ein Agent, der seinen Zug beendet, ohne zu antworten, wird wie bisher einmal erinnert und bekommt den Zug zurück. Ein Lauf, den die Modell-Laufzeit nie gestartet hat — eine überlastete API, die mit `529` antwortet, bevor der Agent zum Denken kam — ist etwas völlig anderes: er wird wiederholt, mit wachsender Pause zwischen den Versuchen, und er bekommt keine Erinnerung mehr an eine Regel, die er nie brechen konnte. Beide Wege haben eine Obergrenze, und jenseits davon geht die Runde mit benanntem Grund an einen Menschen: „die Laufzeit hat diese Sitzung nie gestartet, 3 Versuche\" liest sich anders als „diese Sitzung endete ohne Antwort\", und beide verlangen etwas anderes von dem, der es liest. Beide Grenzen entscheidet die Engine und gibt sie dem Auftrag mit, damit ein Wirt mit eigener Agenten-Laufzeit sie erbt, statt sie neu erfinden zu müssen.\n- **Ein von nexus-flow gestarteter Agent beginnt jetzt mit derselben Einweisung, die Sie an Ihrem eigenen Terminal bekommen.** Bisher nicht: ein gestarteter Agent läuft bewusst abgeschottet von den Erweiterungen und Einstellungen Ihres Editors — und genau über diese Einstellungen kam die Einweisung zum Sitzungsstart, also lief sie nie. Was so ein Agent über das Gespräch mit seinen Kollegen wusste, waren vier von Hand in den Quelltext geschriebene Befehle, und nachweislich die falschen vier. Er bekommt die ganze Einweisung jetzt als Text, auf demselben Weg, den die Projektkonventionen ohnehin nehmen: wer er ist, wen er ansprechen darf und wofür, das Aufgabenbrett, die Erinnerungen des Projekts und wie er antwortet. Die Deklaration jedes Agenten kann Dienste weglassen — `prime: {flow: false}` gibt einem reinen Prüfer kein Brett, und nur ihm —, und `nxs prime --persona ` druckt genau das, was dieser Agent liest, sodass Sie es prüfen können, statt es zu vermuten.\n\n**Die Einweisung selbst ist kleiner und nützlicher geworden.** Sie begann bisher mit jeder ungelesenen Nachricht im Volltext: gemessen 195 KB, davon 98 % Nachrichtentext, und nach einem Arbeitstag zehnfach zurückgewachsen — eine Rechnung, die jede Sitzung für etwas bezahlte, das kein Agent je brauchte, denn ein Agent bekommt seine Aufgabe in der Aufgabe selbst. Diese Texte sind aus der Einweisung verschwunden (an dem, was Anwendungen lesen können, ändert sich nichts). An ihrer Stelle stehen die zwei Befehle, die nachweislich fehlten — `nxc threads show`, der zweithäufigst benutzte überhaupt und der einzige Ort, an dem eine gehaltene Arbeitskopie sichtbar wird, und `nxc withdraw` — sowie ein kurzer Abschnitt darüber, was zu tun ist, wenn eine Aufgabe nicht vorankommt. Die Erinnerungen des Projekts werden jetzt so geteilt, wie es das Brett immer schon tat: Regeln kommen vollständig, weil eine Regel nur dann etwas wert ist, wenn sie *vor* dem Fehler da ist, und alles andere ist eine Zeile und ein Schlüssel zum Nachschlagen. Diese Klasse hat eine harte Obergrenze, und eine Erinnerung, die sie sprengen würde, wird beim Schreiben abgewiesen — mit den Auswegen im Klartext.\n\n### Behoben\n- **Eine Kette, die auf die Arbeitskopie wartet, wird nicht mehr von einer gerade eingetroffenen überholt.** Stirbt eine Rollen-Sitzung hart, läuft ihr Anspruch auf die Arbeitskopie des Projekts in eine Frist und ist danach wieder zu holen — und bisher bekam sie, wer als Nächstes *fragte*, auch wenn ein Rivale längst in der Schlange stand. Wer wartete, wurde durchs Warten bestraft, im schlimmsten Fall unbegrenzt. Das Zurückholen eines abgelaufenen Anspruchs gibt den Zug jetzt im selben Schritt an den weiter, der schon ansteht — die Reihenfolge auf dem Brett (`nxc threads show`) ist damit die Reihenfolge, in der wirklich gearbeitet wird. Es braucht dafür weiterhin keinen Dienst, keinen Aufräumlauf und keinen eigenen Befehl: die Kette, die kommt, löst es aus.\n\n**Und die Übergabe hat keine Lücke mehr.** Die Arbeitskopie zurückzugeben und die wartende Kette zu starten war schon bisher ein einziger, unteilbarer Schritt — frei war der Anspruch aber kurz zwischen diesem Schritt und dem tatsächlichen Start der wartenden Sitzung, lang genug, dass eine dritte Kette dazwischenrutschen und die eben beförderte wieder ans Ende der Schlange stellen konnte. Die Kopie geht jetzt direkt von einer Kette zur nächsten; es gibt keinen Augenblick mehr, in dem sie frei ist, während jemand auf sie wartet. Zwei gleichzeitige Antworten, die dieselbe Arbeit abschließen, starten die nächste Kette weiterhin genau einmal.\n\n### Facade-Kontrakt\n- `breaking` · **Ihr Sitzungsstart ist jetzt ein Index — und er passt.** Bis zu dieser Fassung wurde jede Erinnerung eines Arbeitsbereichs zu Beginn jeder Sitzung im Volltext eingespielt. Auf den vier größten gemessenen Arbeitsbereichen waren das 90 KB Text — und der Wirt, der das an den Agenten weitergibt, berechnet einen großen Block nicht einfach teurer, er *legt ihn ab*: oberhalb von rund 26 KB bekommt die Sitzung statt des Blocks eine Zwei-Kilobyte-Vorschau und einen Dateipfad. Der Block, der Brett, Kernregeln und Befehlsvokabular tragen sollte, lieferte also nichts davon, und der Agent las die Dateien anschließend von Hand. Erinnerungen werden jetzt als je eine Zeile eingespielt, und der gesamte Sitzungsstart des Arbeitsbereichs dieses Projekts liegt bequem unter der Grenze.\n\n**Diese eine Zeile wird geschrieben, nicht abgeleitet — und `nxm remember` verlangt sie.** `--introduction` nimmt eine Zeile mit höchstens 200 Zeichen; länger wird beim Schreiben abgewiesen, mit der gemessenen Länge in der Meldung. Sie ist das Einzige an einer Erinnerung, was ein Sitzungsstart je zu sehen bekommt — sie sollte also sagen, was die Erinnerung *sagt*, nicht wovon sie handelt, und bei einer Erinnerung unter `rules` das Verbot direkt aussprechen („Never run the lean build while the tests are running\"), denn niemand schlägt ein Verbot nach, bevor er es bricht. Der Volltext ist ein `nxm recall ` entfernt und unverändert; `nxm prime --json` liefert weiterhin jeden Rumpf vollständig, alles darauf Aufbauende bleibt also unberührt. Eine vor dieser Fassung geschriebene Erinnerung hat noch keine Zeile: sie erscheint als `_(no introduction written yet)_`, der Block nennt die Anzahl, und `nxm classify --introduction \"…\"` schließt die Lücke, ohne den Text anzufassen.\n\n**Zwei kleinere Änderungen kommen mit.** `nxs prime` führt die Werkzeuge jetzt in fester Reihenfolge aus — Brett, Chat, Erinnerungen —, damit bei einer Kürzung genau das wegfällt, was ein Agent nachholen kann (`nxm recall`, `nxm memories`), und nie das Brett oder das Vokabular. Und `NEXUS_MEMORY.md` beginnt jetzt mit demselben Index, unter einem Hinweis, dass diese Hälfte bereits im Kontext der Sitzung steht; der Volltext bleibt darunter, damit eine Änderung an einer Erinnerung weiterhin im Änderungsvorschlag sichtbar ist.\n- `breaking` · **Ein Auftrag an einen einzelnen Agenten erreicht jetzt zuerst einen Koordinator — genau wie ein Auftrag an einen Kanal es schon tat.** Bisher waren die beiden verschieden: eine Nachricht an einen Kanal nahm eine benannte Stelle in der Software entgegen, die entschied, was als Nächstes geschah; eine Nachricht an einen einzelnen Agenten ging direkt durch, ohne dass jemand dazwischen stand. Genau dort sind die beiden schwersten Fehler der nächtlichen Prüfläufe entstanden — beide Male hat eine korrekt gebaute Persona eine korrekt formulierte Anweisung befolgt, und der Schaden trat trotzdem ein, weil das, was zwingend passieren muss, im Prompt stand statt in der Struktur. Der Koordinator nimmt die Nachricht jetzt entgegen, legt den Gesprächsfaden an und gibt dessen Id zurück, hält fest, dass der Agent darauf eine Antwort schuldet, und sagt vorab, was geschieht, wenn diese Antwort ausbleibt. An der Bedienung von `nxc send --to ` ändert sich nichts, und ein direktes Gespräch bleibt ein direktes Gespräch — der Koordinator ist Software im Weg, keine dritte Partei in Ihrem Faden.\n\nZwei Fehlschläge, die bisher gleich aussahen, werden jetzt unterschieden. Ein Agent, der seinen Zug beendet, ohne zu antworten, wird wie bisher einmal erinnert und bekommt den Zug zurück. Ein Lauf, den die Modell-Laufzeit nie gestartet hat — eine überlastete API, die mit `529` antwortet, bevor der Agent zum Denken kam — ist etwas völlig anderes: er wird wiederholt, mit wachsender Pause zwischen den Versuchen, und er bekommt keine Erinnerung mehr an eine Regel, die er nie brechen konnte. Beide Wege haben eine Obergrenze, und jenseits davon geht die Runde mit benanntem Grund an einen Menschen: „die Laufzeit hat diese Sitzung nie gestartet, 3 Versuche\" liest sich anders als „diese Sitzung endete ohne Antwort\", und beide verlangen etwas anderes von dem, der es liest. Beide Grenzen entscheidet die Engine und gibt sie dem Auftrag mit, damit ein Wirt mit eigener Agenten-Laufzeit sie erbt, statt sie neu erfinden zu müssen.\n- `breaking` · **Eine Kette, die auf die Arbeitskopie wartet, wird nicht mehr von einer gerade eingetroffenen überholt.** Stirbt eine Rollen-Sitzung hart, läuft ihr Anspruch auf die Arbeitskopie des Projekts in eine Frist und ist danach wieder zu holen — und bisher bekam sie, wer als Nächstes *fragte*, auch wenn ein Rivale längst in der Schlange stand. Wer wartete, wurde durchs Warten bestraft, im schlimmsten Fall unbegrenzt. Das Zurückholen eines abgelaufenen Anspruchs gibt den Zug jetzt im selben Schritt an den weiter, der schon ansteht — die Reihenfolge auf dem Brett (`nxc threads show`) ist damit die Reihenfolge, in der wirklich gearbeitet wird. Es braucht dafür weiterhin keinen Dienst, keinen Aufräumlauf und keinen eigenen Befehl: die Kette, die kommt, löst es aus.\n\n**Und die Übergabe hat keine Lücke mehr.** Die Arbeitskopie zurückzugeben und die wartende Kette zu starten war schon bisher ein einziger, unteilbarer Schritt — frei war der Anspruch aber kurz zwischen diesem Schritt und dem tatsächlichen Start der wartenden Sitzung, lang genug, dass eine dritte Kette dazwischenrutschen und die eben beförderte wieder ans Ende der Schlange stellen konnte. Die Kopie geht jetzt direkt von einer Kette zur nächsten; es gibt keinen Augenblick mehr, in dem sie frei ist, während jemand auf sie wartet. Zwei gleichzeitige Antworten, die dieselbe Arbeit abschließen, starten die nächste Kette weiterhin genau einmal.\n- `breaking` · **Ein von nexus-flow gestarteter Agent beginnt jetzt mit derselben Einweisung, die Sie an Ihrem eigenen Terminal bekommen.** Bisher nicht: ein gestarteter Agent läuft bewusst abgeschottet von den Erweiterungen und Einstellungen Ihres Editors — und genau über diese Einstellungen kam die Einweisung zum Sitzungsstart, also lief sie nie. Was so ein Agent über das Gespräch mit seinen Kollegen wusste, waren vier von Hand in den Quelltext geschriebene Befehle, und nachweislich die falschen vier. Er bekommt die ganze Einweisung jetzt als Text, auf demselben Weg, den die Projektkonventionen ohnehin nehmen: wer er ist, wen er ansprechen darf und wofür, das Aufgabenbrett, die Erinnerungen des Projekts und wie er antwortet. Die Deklaration jedes Agenten kann Dienste weglassen — `prime: {flow: false}` gibt einem reinen Prüfer kein Brett, und nur ihm —, und `nxs prime --persona ` druckt genau das, was dieser Agent liest, sodass Sie es prüfen können, statt es zu vermuten.\n\n**Die Einweisung selbst ist kleiner und nützlicher geworden.** Sie begann bisher mit jeder ungelesenen Nachricht im Volltext: gemessen 195 KB, davon 98 % Nachrichtentext, und nach einem Arbeitstag zehnfach zurückgewachsen — eine Rechnung, die jede Sitzung für etwas bezahlte, das kein Agent je brauchte, denn ein Agent bekommt seine Aufgabe in der Aufgabe selbst. Diese Texte sind aus der Einweisung verschwunden (an dem, was Anwendungen lesen können, ändert sich nichts). An ihrer Stelle stehen die zwei Befehle, die nachweislich fehlten — `nxc threads show`, der zweithäufigst benutzte überhaupt und der einzige Ort, an dem eine gehaltene Arbeitskopie sichtbar wird, und `nxc withdraw` — sowie ein kurzer Abschnitt darüber, was zu tun ist, wenn eine Aufgabe nicht vorankommt. Die Erinnerungen des Projekts werden jetzt so geteilt, wie es das Brett immer schon tat: Regeln kommen vollständig, weil eine Regel nur dann etwas wert ist, wenn sie *vor* dem Fehler da ist, und alles andere ist eine Zeile und ein Schlüssel zum Nachschlagen. Diese Klasse hat eine harte Obergrenze, und eine Erinnerung, die sie sprengen würde, wird beim Schreiben abgewiesen — mit den Auswegen im Klartext." - } - }, - { - "version": "0.67.0", - "date": "2026-08-26", - "items": [ - { - "type": "added", - "en": "`nxc session state ` (and `Engine::session_state`) says what became of a session: `running`,\n`ended` with the instant it announced, or `unknown` — nothing announced an end and no live process\nanswers for it. `--thread ` reports every session that ran on a thread, ended ones included, for\nthe question you ask before touching a working copy a previous step may still have hands on. The\nsession map had two writers (`session bind`, `session ended`) and no reader, so the only way to ask\nwas to read a pid file out of `nxc`'s own directory.", - "de": "`nxc session state ` (und `Engine::session_state`) sagt, was aus einer Sitzung geworden ist:\n`running`, `ended` mit dem gemeldeten Zeitpunkt, oder `unknown` — niemand hat ein Ende gemeldet, und\nkein lebender Prozess antwortet für sie. `--thread ` meldet alle Sitzungen eines Fadens,\nbeendete eingeschlossen — für die Frage, die man stellt, bevor man eine Arbeitskopie anfasst, an der\nein vorheriger Schritt noch die Hände haben könnte. Die Sitzungstabelle hatte zwei Schreiber\n(`session bind`, `session ended`) und keinen Leser; die einzige Art zu fragen war, eine pid-Datei aus\neinem Verzeichnis von `nxc` zu lesen.", - "facade": "changed" - }, - { - "type": "fixed", - "en": "A workspace's working copy held by a chain that is over can be given back again. An escalation holds the\ncheckout on purpose — the task is mid-flight — but when every session in that chain has ended,\nnothing was left that could ever say it is finished, and whatever was queued behind it waited\nforever. `nxc release --thread ` (and `Engine::release_working_tree`) names a thread inside the\nholding chain, hands the copy on and starts whoever was waiting. It refuses while any session in\nthat chain still has a live process, and says which. The two-hour backstop now drains the queue as\nwell: a `tick` past the bound reclaims the expired lease and starts what was parked, and a\ncommission that parks schedules one for exactly that instant — until now the bound only stopped an\nabandoned lease from refusing *future* acquisitions, so a commission already waiting waited past it\nindefinitely. Two corrections to `nxc guide limits-and-safety` come with it: answering a hand-back\nclears the hold inside a channel but not on a direct thread, and the queue is no longer never\ndrained.", - "de": "Eine Arbeitskopie, die von einer beendeten Kette gehalten wird, lässt sich wieder zurückgeben. Eine\nEskalation hält die Kopie mit Absicht — die Aufgabe ist noch in der Luft —, aber wenn alle\nSitzungen dieser Kette beendet sind, blieb nichts übrig, das je hätte sagen können, dass sie fertig\nist, und was dahinter wartete, wartete für immer. `nxc release --thread ` (und\n`Engine::release_working_tree`) nennt einen Faden aus der haltenden Kette, gibt die Kopie weiter und\nstartet, wer gewartet hat. Der Aufruf lehnt ab, solange eine Sitzung dieser Kette noch einen\nlebenden Prozess hat, und sagt welche. Die Zwei-Stunden-Grenze leert jetzt auch die Warteschlange:\nein `tick` nach Ablauf holt den verfallenen Anspruch zurück und startet das Geparkte, und eine\nBeauftragung, die parkt, plant einen für genau diesen Zeitpunkt ein — bisher hinderte die Grenze\neinen verwaisten Anspruch nur daran, *künftige* Erwerbungen abzulehnen, eine bereits wartende\nBeauftragung wartete unbegrenzt weiter. Zwei Korrekturen an `nxc guide limits-and-safety` kommen\nmit: eine Rückgabe zu beantworten löst den Halt in einem Kanal, aber nicht auf einem direkten Faden,\nund die Warteschlange wird nicht mehr nie geleert.", - "facade": "changed" - }, - { - "type": "fixed", - "en": "The background service now paces the deadlines it fires instead of starting them all at once. Every\narmed window that comes due used to start its own process in the same instant — which is what\nhappens to every workspace at once after a laptop has been closed for a weekend, and what a\nhand-written deadline file could ask for on purpose. It now starts at most eight at a time and says\nin its log how many it deferred; the rest stay armed and go on the next second's turn, so a burst\ndrains instead of flooding the machine, and nothing is lost.\nTwo smaller guards came with it: a deadline file past a megabyte is refused rather than re-parsed\nevery second, and the id gate that was applied when a deadline is written is now applied again when\none is read — the file lives inside a workspace, and a workspace can be a clone of anything. The\nbackground service also no longer inherits a relative directory from your `PATH`, which would\notherwise have resolved against whichever project it was working in.", - "de": "Der Hintergrunddienst teilt sich die fälligen Fristen jetzt ein, statt sie alle gleichzeitig zu\nstarten. Bisher startete jede fällig gewordene Frist im selben Augenblick ihren eigenen Prozess — was\nnach einem Wochenende mit zugeklapptem Laptop für alle Arbeitsbereiche zugleich passiert, und was\neine von Hand geschriebene Fristendatei absichtlich verlangen konnte. Jetzt laufen höchstens acht\ngleichzeitig, und im Protokoll steht, wie viele zurückgestellt wurden; der Rest bleibt gestellt und\nkommt in der nächsten Sekunde dran. Ein Schwall fliesst also ab, statt die Maschine zu überrollen,\nund nichts geht verloren.\nZwei kleinere Sicherungen kamen dazu: eine Fristendatei über einem Megabyte wird abgelehnt statt\njede Sekunde neu gelesen, und die Prüfung der Kennung, die beim Schreiben einer Frist galt, gilt\njetzt auch beim Lesen — die Datei liegt im Arbeitsbereich, und ein Arbeitsbereich kann ein Klon von\nirgendetwas sein. Ausserdem übernimmt der Dienst kein relatives Verzeichnis mehr aus Ihrem `PATH`;\nein solches hätte sich sonst gegen das jeweils bearbeitete Projekt aufgelöst." - }, - { - "type": "changed", - "en": "There is now ONE nexus-flow background service, and it is both the thing that syncs and the thing\nthat keeps time. In your Mac's Login Items it appears once, as `nexus-flow`.\nA declared channel `timeout:` fires on macOS — it never used to. `nxc` scheduled its one-shot\ndeadline check through `at`, and macOS ships `atrun` disabled, so `at` took the job, exited 0,\nprinted a job id, and nothing ever ran it: a member that went silent held its round indefinitely on\nthe platform most of this is run on. Deadlines are no longer OS jobs at all. Arming a window writes\none line into the workspace's own book (`.nxs/timers.json`), and the service — already visiting that\nworkspace to sync it — runs the check when the moment arrives. That means the deadline is honoured\nto the second rather than rounded up to the next whole minute, a board closed early leaves nothing\nbehind to clean up, and the same mechanism works identically on Linux.\nInstall the service with `nxs sync daemon install` (macOS) or run `nxs sync daemon` under your own\nsupervisor. Its log is `~/.nexusflow/logs/service.log`. `nexus-flow` is also a command: it is\n`nxs sync daemon`, so `nexus-flow status` answers the same question.\nNothing pretends when the service is not there. Arming a window in a workspace no service attends —\nor with no service running — still records the deadline, and still tells you, in `warnings` as a\n`tick_unscheduled` entry, that nothing is watching it right now.\n`NXC_TIMER=at` still selects the `at` backend explicitly. `NXC_TIMER=launchd` is gone and is now an\nerror rather than a silent fallback: it named a backend that installed one macOS agent per deadline,\nand that second clock is exactly what this replaces. Installing the service removes those leftover\nagents.", - "de": "Es gibt jetzt EINEN nexus-flow-Hintergrunddienst, und er ist beides: das, was synchronisiert, und\ndas, was die Zeit hält. In den Hintergrundobjekten Ihres Macs steht er einmal, als `nexus-flow`.\nEin deklariertes Kanal-`timeout:` feuert auf macOS — bisher nie. `nxc` plante seine Einmal-Prüfung\nüber `at`, und macOS liefert `atrun` deaktiviert aus: `at` nahm den Job an, beendete sich mit 0,\ndruckte eine Job-Nummer, und niemand führte ihn je aus. Ein Mitglied, das verstummte, hielt seine\nRunde damit unbegrenzt an, ausgerechnet auf der Plattform, auf der das meiste davon läuft. Fristen\nsind jetzt überhaupt keine Betriebssystem-Jobs mehr. Eine Frist zu stellen schreibt eine Zeile in\ndas Buch des Arbeitsbereichs (`.nxs/timers.json`), und der Dienst — der diesen Arbeitsbereich zum\nSynchronisieren ohnehin besucht — führt die Prüfung aus, wenn es so weit ist. Damit gilt die Frist\nauf die Sekunde statt auf die nächste volle Minute aufgerundet, ein früh geschlossenes Brett lässt\nnichts zum Aufräumen zurück, und auf Linux arbeitet dieselbe Mechanik unverändert.\nDen Dienst einrichten: `nxs sync daemon install` (macOS), oder `nxs sync daemon` unter der eigenen\nProzessverwaltung. Sein Protokoll liegt in `~/.nexusflow/logs/service.log`. `nexus-flow` ist auch ein\nBefehl: er ist `nxs sync daemon`, `nexus-flow status` beantwortet also dieselbe Frage.\nNichts tut so, als ob. Eine Frist in einem Arbeitsbereich zu stellen, den kein Dienst betreut — oder\nwährend keiner läuft — trägt die Frist trotzdem ein und sagt Ihnen zugleich, als `tick_unscheduled`\nin `warnings`, dass sie gerade niemand beobachtet.\n`NXC_TIMER=at` wählt den `at`-Zeitgeber weiterhin ausdrücklich. `NXC_TIMER=launchd` ist weg und\njetzt ein Fehler statt eines stillen Ausweichens: es benannte einen Zeitgeber, der je Frist einen\neigenen macOS-Agenten einrichtete — genau die zweite Uhr, die hier abgelöst wird. Beim Einrichten\ndes Dienstes werden diese übrig gebliebenen Agenten entfernt.", - "facade": "changed" - }, - { - "type": "fixed", - "en": "`nxs sync daemon status` no longer reports a service that is long gone as running. It asked only\nwhether the recorded process id currently belongs to *a* process — and after the service dies, the\noperating system eventually hands that id to something unrelated, at which point the answer was yes,\nforever. The check now also compares the process's real start time against the instant the service\nrecorded when it started: a mismatch means the id belongs to somebody else. On a platform with no\nway to read a start time the answer is honestly \"unknown\" rather than a confident \"running\".", - "de": "`nxs sync daemon status` meldet einen längst beendeten Dienst nicht mehr als laufend. Bisher fragte\ndie Prüfung nur, ob die eingetragene Prozessnummer gerade *irgendeinem* Prozess gehört — und nachdem\nder Dienst gestorben ist, vergibt das Betriebssystem diese Nummer irgendwann weiter, ab da lautete\ndie Antwort für immer „ja\". Jetzt wird zusätzlich die echte Startzeit des Prozesses mit dem\nZeitpunkt verglichen, den der Dienst beim Start eingetragen hat: stimmen sie nicht überein, gehört\ndie Nummer jemand anderem. Auf einer Plattform, die keine Startzeit liefert, lautet die Antwort\nehrlich „unbekannt\" statt eines zuversichtlichen „läuft\"." - }, - { - "type": "added", - "en": "An embedding app can now tell the nexus-flow background service which workspaces to attend, without\nshelling out to the CLI. The new `nxs-service` crate carries the seam: `register` a workspace,\n`deregister` it again, and `attendance` reads back whether the service is actually up and what it\nlast did with that workspace. Registering is idempotent; deregistering something that was never\nregistered is a successful no-op. Nothing inside the workspace is touched either way — the stream\nbinding in `.nxs/sync.toml` survives a deregister, so registering again resumes where it left off.\nThe CLI gains the counterpart it never had: `nxs sync unregister []` takes a workspace off the\nservice's list. Pass a path to prune an entry whose directory is long gone — until now the only way\nto clean up `~/.nexusflow/workspaces.toml` was to edit it by hand, and it only ever grew.", - "de": "Eine einbettende App kann dem nexus-flow-Hintergrunddienst jetzt selbst sagen, welche\nArbeitsbereiche er betreuen soll, ohne dafür die CLI aufzurufen. Die neue Kiste `nxs-service` trägt\ndie Naht: einen Arbeitsbereich `register`n, ihn wieder `deregister`n, und `attendance` liest zurück,\nob der Dienst überhaupt läuft und was er zuletzt mit diesem Arbeitsbereich getan hat. Anmelden ist\nidempotent; etwas abzumelden, das nie angemeldet war, ist ein erfolgreicher Leerlauf. Im\nArbeitsbereich selbst wird dabei nichts angefasst — die Stream-Bindung in `.nxs/sync.toml` übersteht\nein Abmelden, ein erneutes Anmelden setzt also dort fort, wo es aufgehört hat.\nDie CLI bekommt das Gegenstück, das ihr fehlte: `nxs sync unregister []` nimmt einen\nArbeitsbereich von der Liste des Dienstes. Mit einem Pfad lässt sich auch ein Eintrag entfernen,\ndessen Verzeichnis längst weg ist — bisher ließ sich `~/.nexusflow/workspaces.toml` nur von Hand\naufräumen, und die Datei wuchs ausschließlich." - }, - { - "type": "fixed", - "en": "One slow relay can no longer hold up every other workspace. A sync pass was capped only by the\nnumber of pages it fetched (20,000), so a relay that answered each request slowly while nudging the\ncursor by any amount at all could hold a single pass for days — and the background service works\nthrough registered workspaces one at a time, so that starves all the others, deadlines included.\nEach pass now also has a wall-clock budget of 60 seconds. Stopping is clean: the watermark advances\nper page, so the next pass resumes exactly where the last one stopped and a genuinely large backlog\ndrains over several passes instead of one long one.\nWhen a pass does stop at a limit, it now says which situation it is in. A pass that fetched full\npages has more history to fetch; a pass whose pages came back empty while the cursor kept moving is\nlooking at a relay that is trickling and will never drain. Those two were indistinguishable before,\nand `nxs sync run` and the service log now report them in the same words, with the page counts they\nare based on. `--json` carries `budget_exhausted`, `pull_pages`, `pull_empty_pages` and\n`stopped_early`.", - "de": "Ein langsames Relay kann nicht mehr alle anderen Arbeitsbereiche aufhalten. Ein Sync-Durchlauf war\nallein über die Seitenzahl gedeckelt (20.000) — ein Relay, das jede Anfrage langsam beantwortet und\nden Cursor um einen beliebig kleinen Betrag weiterschiebt, konnte einen einzelnen Durchlauf damit\nüber Tage ziehen. Und der Hintergrunddienst arbeitet die angemeldeten Arbeitsbereiche nacheinander\nab: einer, der hängt, verhungert also alle übrigen, ihre Fristen eingeschlossen. Jeder Durchlauf hat\njetzt zusätzlich ein Zeitbudget von 60 Sekunden. Der Abbruch ist sauber: die Wasserlinie rückt je\nSeite vor, der nächste Durchlauf setzt also genau dort fort, und ein wirklich grosser Rückstand\nfliesst über mehrere Durchläufe ab statt in einem langen.\nUnd wenn ein Durchlauf an einer Grenze endet, sagt er jetzt, in welcher Lage er ist. Ein Durchlauf\nmit vollen Seiten hat noch Geschichte zu holen; ein Durchlauf, dessen Seiten leer zurückkamen,\nwährend der Cursor weiterlief, sieht ein tröpfelndes Relay, das nie abfliesst. Beides war vorher\nnicht zu unterscheiden. `nxs sync run` und das Protokoll des Dienstes melden es jetzt in denselben\nWorten, mit den Seitenzahlen, auf denen das Urteil beruht. `--json` trägt `budget_exhausted`,\n`pull_pages`, `pull_empty_pages` und `stopped_early`." - }, - { - "type": "fixed", - "en": "An escalation now STOPS an ordered channel instead of advancing it. `nxc reply --escalate` says \"I\ncannot carry out this task\", and a task that was not carried out no longer commissions the work that\nwas to follow it: on a `flow: sequential` channel the next step is not started, the round goes up as\nit stands, and whoever commissioned it decides. Measured before the fix, a coder used `--escalate`\nexpressly to hold a flow — he had understood the incident it would otherwise cause and said so — and\nthe next step's session started ten seconds later anyway, into a checkout still standing on the\npre-review commit. Four threads of that one run carried the escalation mark, each deliberately, none\nwith any effect; the only thing that held the flow was a sentence in the commissioning prompt. A\n`parallel` channel is untouched. One thing still has no spelling and the guide now says so plainly:\n`reply` has two answers, \"I am finished\" and \"I cannot\", and there is no way to say \"not yet\" — a\nstep waiting on a round of its own should wait and answer once it has the result.\n\nAnd the working copy is now claimed for the whole OPERATION rather than for the branch the exclusive\nstep happened to start in. The claim still arises only at the first `working_tree: exclusive` step —\nnothing is held while a work order is merely being planned — but once taken it is given back when the\noperation is finished. That closes the gap in front of a second round: a planner that commissions\ncoding twice keeps the checkout across the pause between them, where an unrelated chain could\npreviously step in and interrupt the operation from the caller's point of view.\n\nWith it, the flat two-hour lease bound stops applying to every lease. A lease now runs to the latest\n`timeout:` still outstanding in its own claim area: a round that declares six hours holds the copy\nfor six hours (it used to lose it under itself at two and hand the checkout to the next chain while\nit was still building), and an operation whose steps ALL declare minutes is reclaimable in minutes after\na crash instead of wedging the machine for two hours. The \"all\" is load-bearing: one outstanding\nobligation without a window — and a conversation a human opened with a plain `nxc send` is one —\nputs the whole operation back on the two-hour backstop, unchanged. So this is a bound you opt into by\ndeclaring `timeout:` along the chain, not one that changes under an existing declaration. A bound running out is no longer enough to take the copy\neither — a chain whose lease has expired but whose process is still alive keeps it, which is the same\nrefusal `nxc release` has always made, now asked at the other end as well.\n\nThe shipped example role collection says all of this where a role author copies from, including the\npart it cannot fix: three of its four reviewers run the project's gates themselves, so that quorum\nputs three concurrent builds in one `target/`, and no `working_tree:` value separates members of one\nround from each other.", - "de": "Eine Eskalation HÄLT jetzt einen geordneten Kanal an, statt ihn vorzurücken. `nxc reply --escalate`\nsagt „das kann ich nicht ausführen\", und eine Aufgabe, die nicht ausgeführt wurde, beauftragt nicht\nmehr die Arbeit, die auf sie folgen sollte: an einem Kanal mit `flow: sequential` wird der nächste\nSchritt nicht gestartet, die Runde geht so nach oben, wie sie ist, und wer sie beauftragt hat,\nentscheidet. Vor der Korrektur gemessen: ein Coder benutzte `--escalate` ausdrücklich, um einen\nAblauf anzuhalten — er hatte den Vorfall verstanden, den er sonst auslösen würde, und schrieb das\nhin —, und die Sitzung des nächsten Schritts startete zehn Sekunden später trotzdem, in eine\nArbeitskopie, die noch auf dem Stand vor der Review stand. Vier Fäden desselben Laufs trugen die\nEskalationsmarke, jedes Mal absichtlich, keines mit Wirkung; gehalten hat den Ablauf am Ende nur ein\nSatz in der Beauftragung. Ein `parallel`-Kanal bleibt unberührt. Eines hat weiterhin keine\nSchreibweise, und der Leitfaden sagt es jetzt deutlich: `reply` hat zwei Antworten, „ich bin fertig\"\nund „ich kann nicht\", und „noch nicht\" ist keine davon — ein Schritt, der auf eine eigene Runde\nwartet, wartet und antwortet, wenn deren Ergebnis da ist.\n\nUnd die Arbeitskopie wird jetzt für die ganze OPERATION beansprucht statt für den Zweig, in dem der\nexklusive Schritt zufällig begann. Der Anspruch entsteht weiterhin erst beim ersten Schritt mit\n`working_tree: exclusive` — während ein Auftrag nur geplant wird, ist nichts gehalten —, wird aber\nerst zurückgegeben, wenn die Operation fertig ist. Das schliesst die Lücke vor einer zweiten Runde:\nein Planer, der Coding zweimal beauftragt, behält die Arbeitskopie über die Pause dazwischen, in die\nbisher eine fremde Kette hineinrutschen und den Vorgang aus Sicht des Aufrufers unterbrechen konnte.\n\nDamit gilt die pauschale Zwei-Stunden-Frist nicht mehr für jeden Anspruch. Ein Anspruch läuft jetzt\nbis zum spätesten `timeout:`, das in seinem eigenen Gebiet noch aussteht: eine Runde, die sechs\nStunden deklariert, hält die Kopie sechs Stunden (bisher verlor sie sie nach zwei Stunden unter sich\nund reichte die Arbeitskopie an die nächste Kette weiter, während sie noch baute), und eine\nOperation, deren Schritte ALLE Minuten deklarieren, ist nach einem Absturz in Minuten wieder frei,\nstatt die Maschine zwei Stunden zu blockieren. Das „alle\" trägt: eine einzige ausstehende\nVerpflichtung ohne Fenster — und ein Gespräch, das ein Mensch mit einem schlichten `nxc send`\neröffnet, ist eine — setzt die ganze Operation unverändert auf die Zwei-Stunden-Grenze zurück. Es ist\nalso eine Frist, für die man sich mit `timeout:` entlang der Kette entscheidet, und keine, die sich\nunter einer bestehenden Deklaration ändert. Und eine abgelaufene Frist genügt allein nicht mehr, um die\nKopie zu nehmen: eine Kette, deren Anspruch abgelaufen ist, deren Prozess aber noch lebt, behält sie\n— dieselbe Ablehnung, die `nxc release` immer schon ausspricht, jetzt auch am anderen Ende gefragt.\n\nDie ausgelieferte Beispiel-Rollensammlung sagt das alles an der Stelle, von der ein Rollen-Autor\nabschreibt — einschliesslich des Teils, den sie nicht beheben kann: drei ihrer vier Reviewer führen\ndie Gates des Projekts selbst aus, dieses Quorum stellt also drei gleichzeitige Builds in ein\n`target/`, und kein Wert von `working_tree:` trennt die Mitglieder einer Runde voneinander.", - "facade": "changed" - }, - { - "type": "fixed", - "en": "A channel that needs the working copy to itself no longer stalls when a step's session dies without\nsaying so. Since a step of such a channel ends when its session ends, a lost end announcement — a\nsidecar killed before its teardown, an older `nxc`, a host runtime that never reports one — left the\nround standing with nothing scheduled to look again; the documented way out, `nxc tick --thread\n`, then either folded the round and skipped every remaining step or did nothing at all. Now a\ndecline whose only remaining blocker is a live session schedules a re-check of that board a minute\nlater, and keeps doing so while the process is there: a session that was killed hard is noticed\nwithin the minute and the flow advances by itself, correctly, without consolidating. A process that\nhangs instead of exiting is still a wait — but a visible one that something is looking at.\n\nAnd `nxc` now resolves its agent runtime at the workspace root rather than at whatever directory you\nhappen to stand in. A `send` from a subdirectory used to create a second `.nxs/agent-logs/` there and\nstart the agent in it, and a later call from anywhere else could not find a running session's pid\nfile — reading \"not running\" for a session that was, which is the damaging direction at every point\nthat asks.\n\n**Before upgrading, let any session in flight finish.** One narrow case is not covered by the fix:\na session that is still running at the moment you upgrade, and that was started from a subdirectory,\nkeeps its pid file there — so it now reads as not running, and the channel it holds can hand the\nworking copy to its next step while it is still writing. It affects that one session and nothing\nafter it. `nxc status` shows what is in flight, and `nxc session state --thread ` says whether\nthe sessions under it are over.", - "de": "Ein Kanal, der die Arbeitskopie für sich braucht, bleibt nicht mehr stehen, wenn die Sitzung eines\nSchritts stirbt, ohne es zu melden. Da ein Schritt eines solchen Kanals endet, wenn seine Sitzung\nendet, ließ eine verlorene Endmeldung — ein vor dem Abbau getöteter Sidecar, ein älteres `nxc`, eine\nWirt-Laufzeit, die keine meldet — die Runde stehen, ohne dass etwas eingeplant war, das noch einmal\nnachsieht; der dokumentierte Weg heraus, `nxc tick --thread `, faltete die Runde dann entweder\nzusammen und übersprang alle verbleibenden Schritte, oder er tat gar nichts. Jetzt plant eine\nAblehnung, deren einziger verbliebener Hinderungsgrund eine lebende Sitzung ist, eine Minute später\neine Nachprüfung desselben Bretts ein, und tut das weiter, solange der Prozess da ist: eine hart\ngetötete Sitzung fällt binnen einer Minute auf, und der Ablauf geht von selbst weiter — richtig, und\nohne zu konsolidieren. Ein Prozess, der hängt statt zu enden, ist weiterhin ein Warten — aber ein\nsichtbares, auf das etwas schaut.\n\nUnd `nxc` löst seine Agenten-Laufzeit jetzt an der Wurzel des Arbeitsbereichs auf statt in dem\nVerzeichnis, in dem man gerade steht. Ein `send` aus einem Unterverzeichnis legte dort ein zweites\n`.nxs/agent-logs/` an und startete den Agenten darin, und ein späterer Aufruf von anderswo fand die\npid-Datei einer laufenden Sitzung nicht — er las „läuft nicht\" für eine Sitzung, die lief, und das\nist an jeder fragenden Stelle die schädliche Richtung.\n\n**Vor dem Aktualisieren laufende Sitzungen auslaufen lassen.** Ein enger Fall bleibt: eine Sitzung,\ndie im Moment der Aktualisierung noch läuft und aus einem Unterverzeichnis gestartet wurde, behält\nihre pid-Datei dort — sie liest sich danach als nicht laufend, und der Kanal, den sie hält, kann die\nArbeitskopie an seinen nächsten Schritt weitergeben, während sie noch schreibt. Betroffen ist diese\neine Sitzung und nichts danach. `nxc status` zeigt, was in der Luft ist, und\n`nxc session state --thread ` sagt, ob die Sitzungen darunter vorbei sind.", - "facade": "changed" - } - ], - "notes": { - "en": "### Added\n- `nxc session state ` (and `Engine::session_state`) says what became of a session: `running`,\n`ended` with the instant it announced, or `unknown` — nothing announced an end and no live process\nanswers for it. `--thread ` reports every session that ran on a thread, ended ones included, for\nthe question you ask before touching a working copy a previous step may still have hands on. The\nsession map had two writers (`session bind`, `session ended`) and no reader, so the only way to ask\nwas to read a pid file out of `nxc`'s own directory.\n- An embedding app can now tell the nexus-flow background service which workspaces to attend, without\nshelling out to the CLI. The new `nxs-service` crate carries the seam: `register` a workspace,\n`deregister` it again, and `attendance` reads back whether the service is actually up and what it\nlast did with that workspace. Registering is idempotent; deregistering something that was never\nregistered is a successful no-op. Nothing inside the workspace is touched either way — the stream\nbinding in `.nxs/sync.toml` survives a deregister, so registering again resumes where it left off.\nThe CLI gains the counterpart it never had: `nxs sync unregister []` takes a workspace off the\nservice's list. Pass a path to prune an entry whose directory is long gone — until now the only way\nto clean up `~/.nexusflow/workspaces.toml` was to edit it by hand, and it only ever grew.\n\n### Changed\n- There is now ONE nexus-flow background service, and it is both the thing that syncs and the thing\nthat keeps time. In your Mac's Login Items it appears once, as `nexus-flow`.\nA declared channel `timeout:` fires on macOS — it never used to. `nxc` scheduled its one-shot\ndeadline check through `at`, and macOS ships `atrun` disabled, so `at` took the job, exited 0,\nprinted a job id, and nothing ever ran it: a member that went silent held its round indefinitely on\nthe platform most of this is run on. Deadlines are no longer OS jobs at all. Arming a window writes\none line into the workspace's own book (`.nxs/timers.json`), and the service — already visiting that\nworkspace to sync it — runs the check when the moment arrives. That means the deadline is honoured\nto the second rather than rounded up to the next whole minute, a board closed early leaves nothing\nbehind to clean up, and the same mechanism works identically on Linux.\nInstall the service with `nxs sync daemon install` (macOS) or run `nxs sync daemon` under your own\nsupervisor. Its log is `~/.nexusflow/logs/service.log`. `nexus-flow` is also a command: it is\n`nxs sync daemon`, so `nexus-flow status` answers the same question.\nNothing pretends when the service is not there. Arming a window in a workspace no service attends —\nor with no service running — still records the deadline, and still tells you, in `warnings` as a\n`tick_unscheduled` entry, that nothing is watching it right now.\n`NXC_TIMER=at` still selects the `at` backend explicitly. `NXC_TIMER=launchd` is gone and is now an\nerror rather than a silent fallback: it named a backend that installed one macOS agent per deadline,\nand that second clock is exactly what this replaces. Installing the service removes those leftover\nagents.\n\n### Fixed\n- A workspace's working copy held by a chain that is over can be given back again. An escalation holds the\ncheckout on purpose — the task is mid-flight — but when every session in that chain has ended,\nnothing was left that could ever say it is finished, and whatever was queued behind it waited\nforever. `nxc release --thread ` (and `Engine::release_working_tree`) names a thread inside the\nholding chain, hands the copy on and starts whoever was waiting. It refuses while any session in\nthat chain still has a live process, and says which. The two-hour backstop now drains the queue as\nwell: a `tick` past the bound reclaims the expired lease and starts what was parked, and a\ncommission that parks schedules one for exactly that instant — until now the bound only stopped an\nabandoned lease from refusing *future* acquisitions, so a commission already waiting waited past it\nindefinitely. Two corrections to `nxc guide limits-and-safety` come with it: answering a hand-back\nclears the hold inside a channel but not on a direct thread, and the queue is no longer never\ndrained.\n- The background service now paces the deadlines it fires instead of starting them all at once. Every\narmed window that comes due used to start its own process in the same instant — which is what\nhappens to every workspace at once after a laptop has been closed for a weekend, and what a\nhand-written deadline file could ask for on purpose. It now starts at most eight at a time and says\nin its log how many it deferred; the rest stay armed and go on the next second's turn, so a burst\ndrains instead of flooding the machine, and nothing is lost.\nTwo smaller guards came with it: a deadline file past a megabyte is refused rather than re-parsed\nevery second, and the id gate that was applied when a deadline is written is now applied again when\none is read — the file lives inside a workspace, and a workspace can be a clone of anything. The\nbackground service also no longer inherits a relative directory from your `PATH`, which would\notherwise have resolved against whichever project it was working in.\n- `nxs sync daemon status` no longer reports a service that is long gone as running. It asked only\nwhether the recorded process id currently belongs to *a* process — and after the service dies, the\noperating system eventually hands that id to something unrelated, at which point the answer was yes,\nforever. The check now also compares the process's real start time against the instant the service\nrecorded when it started: a mismatch means the id belongs to somebody else. On a platform with no\nway to read a start time the answer is honestly \"unknown\" rather than a confident \"running\".\n- One slow relay can no longer hold up every other workspace. A sync pass was capped only by the\nnumber of pages it fetched (20,000), so a relay that answered each request slowly while nudging the\ncursor by any amount at all could hold a single pass for days — and the background service works\nthrough registered workspaces one at a time, so that starves all the others, deadlines included.\nEach pass now also has a wall-clock budget of 60 seconds. Stopping is clean: the watermark advances\nper page, so the next pass resumes exactly where the last one stopped and a genuinely large backlog\ndrains over several passes instead of one long one.\nWhen a pass does stop at a limit, it now says which situation it is in. A pass that fetched full\npages has more history to fetch; a pass whose pages came back empty while the cursor kept moving is\nlooking at a relay that is trickling and will never drain. Those two were indistinguishable before,\nand `nxs sync run` and the service log now report them in the same words, with the page counts they\nare based on. `--json` carries `budget_exhausted`, `pull_pages`, `pull_empty_pages` and\n`stopped_early`.\n- An escalation now STOPS an ordered channel instead of advancing it. `nxc reply --escalate` says \"I\ncannot carry out this task\", and a task that was not carried out no longer commissions the work that\nwas to follow it: on a `flow: sequential` channel the next step is not started, the round goes up as\nit stands, and whoever commissioned it decides. Measured before the fix, a coder used `--escalate`\nexpressly to hold a flow — he had understood the incident it would otherwise cause and said so — and\nthe next step's session started ten seconds later anyway, into a checkout still standing on the\npre-review commit. Four threads of that one run carried the escalation mark, each deliberately, none\nwith any effect; the only thing that held the flow was a sentence in the commissioning prompt. A\n`parallel` channel is untouched. One thing still has no spelling and the guide now says so plainly:\n`reply` has two answers, \"I am finished\" and \"I cannot\", and there is no way to say \"not yet\" — a\nstep waiting on a round of its own should wait and answer once it has the result.\n\nAnd the working copy is now claimed for the whole OPERATION rather than for the branch the exclusive\nstep happened to start in. The claim still arises only at the first `working_tree: exclusive` step —\nnothing is held while a work order is merely being planned — but once taken it is given back when the\noperation is finished. That closes the gap in front of a second round: a planner that commissions\ncoding twice keeps the checkout across the pause between them, where an unrelated chain could\npreviously step in and interrupt the operation from the caller's point of view.\n\nWith it, the flat two-hour lease bound stops applying to every lease. A lease now runs to the latest\n`timeout:` still outstanding in its own claim area: a round that declares six hours holds the copy\nfor six hours (it used to lose it under itself at two and hand the checkout to the next chain while\nit was still building), and an operation whose steps ALL declare minutes is reclaimable in minutes after\na crash instead of wedging the machine for two hours. The \"all\" is load-bearing: one outstanding\nobligation without a window — and a conversation a human opened with a plain `nxc send` is one —\nputs the whole operation back on the two-hour backstop, unchanged. So this is a bound you opt into by\ndeclaring `timeout:` along the chain, not one that changes under an existing declaration. A bound running out is no longer enough to take the copy\neither — a chain whose lease has expired but whose process is still alive keeps it, which is the same\nrefusal `nxc release` has always made, now asked at the other end as well.\n\nThe shipped example role collection says all of this where a role author copies from, including the\npart it cannot fix: three of its four reviewers run the project's gates themselves, so that quorum\nputs three concurrent builds in one `target/`, and no `working_tree:` value separates members of one\nround from each other.\n- A channel that needs the working copy to itself no longer stalls when a step's session dies without\nsaying so. Since a step of such a channel ends when its session ends, a lost end announcement — a\nsidecar killed before its teardown, an older `nxc`, a host runtime that never reports one — left the\nround standing with nothing scheduled to look again; the documented way out, `nxc tick --thread\n`, then either folded the round and skipped every remaining step or did nothing at all. Now a\ndecline whose only remaining blocker is a live session schedules a re-check of that board a minute\nlater, and keeps doing so while the process is there: a session that was killed hard is noticed\nwithin the minute and the flow advances by itself, correctly, without consolidating. A process that\nhangs instead of exiting is still a wait — but a visible one that something is looking at.\n\nAnd `nxc` now resolves its agent runtime at the workspace root rather than at whatever directory you\nhappen to stand in. A `send` from a subdirectory used to create a second `.nxs/agent-logs/` there and\nstart the agent in it, and a later call from anywhere else could not find a running session's pid\nfile — reading \"not running\" for a session that was, which is the damaging direction at every point\nthat asks.\n\n**Before upgrading, let any session in flight finish.** One narrow case is not covered by the fix:\na session that is still running at the moment you upgrade, and that was started from a subdirectory,\nkeeps its pid file there — so it now reads as not running, and the channel it holds can hand the\nworking copy to its next step while it is still writing. It affects that one session and nothing\nafter it. `nxc status` shows what is in flight, and `nxc session state --thread ` says whether\nthe sessions under it are over.\n\n### Facade Contract\n- `changed` · `nxc session state ` (and `Engine::session_state`) says what became of a session: `running`,\n`ended` with the instant it announced, or `unknown` — nothing announced an end and no live process\nanswers for it. `--thread ` reports every session that ran on a thread, ended ones included, for\nthe question you ask before touching a working copy a previous step may still have hands on. The\nsession map had two writers (`session bind`, `session ended`) and no reader, so the only way to ask\nwas to read a pid file out of `nxc`'s own directory.\n- `changed` · A workspace's working copy held by a chain that is over can be given back again. An escalation holds the\ncheckout on purpose — the task is mid-flight — but when every session in that chain has ended,\nnothing was left that could ever say it is finished, and whatever was queued behind it waited\nforever. `nxc release --thread ` (and `Engine::release_working_tree`) names a thread inside the\nholding chain, hands the copy on and starts whoever was waiting. It refuses while any session in\nthat chain still has a live process, and says which. The two-hour backstop now drains the queue as\nwell: a `tick` past the bound reclaims the expired lease and starts what was parked, and a\ncommission that parks schedules one for exactly that instant — until now the bound only stopped an\nabandoned lease from refusing *future* acquisitions, so a commission already waiting waited past it\nindefinitely. Two corrections to `nxc guide limits-and-safety` come with it: answering a hand-back\nclears the hold inside a channel but not on a direct thread, and the queue is no longer never\ndrained.\n- `changed` · There is now ONE nexus-flow background service, and it is both the thing that syncs and the thing\nthat keeps time. In your Mac's Login Items it appears once, as `nexus-flow`.\nA declared channel `timeout:` fires on macOS — it never used to. `nxc` scheduled its one-shot\ndeadline check through `at`, and macOS ships `atrun` disabled, so `at` took the job, exited 0,\nprinted a job id, and nothing ever ran it: a member that went silent held its round indefinitely on\nthe platform most of this is run on. Deadlines are no longer OS jobs at all. Arming a window writes\none line into the workspace's own book (`.nxs/timers.json`), and the service — already visiting that\nworkspace to sync it — runs the check when the moment arrives. That means the deadline is honoured\nto the second rather than rounded up to the next whole minute, a board closed early leaves nothing\nbehind to clean up, and the same mechanism works identically on Linux.\nInstall the service with `nxs sync daemon install` (macOS) or run `nxs sync daemon` under your own\nsupervisor. Its log is `~/.nexusflow/logs/service.log`. `nexus-flow` is also a command: it is\n`nxs sync daemon`, so `nexus-flow status` answers the same question.\nNothing pretends when the service is not there. Arming a window in a workspace no service attends —\nor with no service running — still records the deadline, and still tells you, in `warnings` as a\n`tick_unscheduled` entry, that nothing is watching it right now.\n`NXC_TIMER=at` still selects the `at` backend explicitly. `NXC_TIMER=launchd` is gone and is now an\nerror rather than a silent fallback: it named a backend that installed one macOS agent per deadline,\nand that second clock is exactly what this replaces. Installing the service removes those leftover\nagents.\n- `changed` · An escalation now STOPS an ordered channel instead of advancing it. `nxc reply --escalate` says \"I\ncannot carry out this task\", and a task that was not carried out no longer commissions the work that\nwas to follow it: on a `flow: sequential` channel the next step is not started, the round goes up as\nit stands, and whoever commissioned it decides. Measured before the fix, a coder used `--escalate`\nexpressly to hold a flow — he had understood the incident it would otherwise cause and said so — and\nthe next step's session started ten seconds later anyway, into a checkout still standing on the\npre-review commit. Four threads of that one run carried the escalation mark, each deliberately, none\nwith any effect; the only thing that held the flow was a sentence in the commissioning prompt. A\n`parallel` channel is untouched. One thing still has no spelling and the guide now says so plainly:\n`reply` has two answers, \"I am finished\" and \"I cannot\", and there is no way to say \"not yet\" — a\nstep waiting on a round of its own should wait and answer once it has the result.\n\nAnd the working copy is now claimed for the whole OPERATION rather than for the branch the exclusive\nstep happened to start in. The claim still arises only at the first `working_tree: exclusive` step —\nnothing is held while a work order is merely being planned — but once taken it is given back when the\noperation is finished. That closes the gap in front of a second round: a planner that commissions\ncoding twice keeps the checkout across the pause between them, where an unrelated chain could\npreviously step in and interrupt the operation from the caller's point of view.\n\nWith it, the flat two-hour lease bound stops applying to every lease. A lease now runs to the latest\n`timeout:` still outstanding in its own claim area: a round that declares six hours holds the copy\nfor six hours (it used to lose it under itself at two and hand the checkout to the next chain while\nit was still building), and an operation whose steps ALL declare minutes is reclaimable in minutes after\na crash instead of wedging the machine for two hours. The \"all\" is load-bearing: one outstanding\nobligation without a window — and a conversation a human opened with a plain `nxc send` is one —\nputs the whole operation back on the two-hour backstop, unchanged. So this is a bound you opt into by\ndeclaring `timeout:` along the chain, not one that changes under an existing declaration. A bound running out is no longer enough to take the copy\neither — a chain whose lease has expired but whose process is still alive keeps it, which is the same\nrefusal `nxc release` has always made, now asked at the other end as well.\n\nThe shipped example role collection says all of this where a role author copies from, including the\npart it cannot fix: three of its four reviewers run the project's gates themselves, so that quorum\nputs three concurrent builds in one `target/`, and no `working_tree:` value separates members of one\nround from each other.\n- `changed` · A channel that needs the working copy to itself no longer stalls when a step's session dies without\nsaying so. Since a step of such a channel ends when its session ends, a lost end announcement — a\nsidecar killed before its teardown, an older `nxc`, a host runtime that never reports one — left the\nround standing with nothing scheduled to look again; the documented way out, `nxc tick --thread\n`, then either folded the round and skipped every remaining step or did nothing at all. Now a\ndecline whose only remaining blocker is a live session schedules a re-check of that board a minute\nlater, and keeps doing so while the process is there: a session that was killed hard is noticed\nwithin the minute and the flow advances by itself, correctly, without consolidating. A process that\nhangs instead of exiting is still a wait — but a visible one that something is looking at.\n\nAnd `nxc` now resolves its agent runtime at the workspace root rather than at whatever directory you\nhappen to stand in. A `send` from a subdirectory used to create a second `.nxs/agent-logs/` there and\nstart the agent in it, and a later call from anywhere else could not find a running session's pid\nfile — reading \"not running\" for a session that was, which is the damaging direction at every point\nthat asks.\n\n**Before upgrading, let any session in flight finish.** One narrow case is not covered by the fix:\na session that is still running at the moment you upgrade, and that was started from a subdirectory,\nkeeps its pid file there — so it now reads as not running, and the channel it holds can hand the\nworking copy to its next step while it is still writing. It affects that one session and nothing\nafter it. `nxc status` shows what is in flight, and `nxc session state --thread ` says whether\nthe sessions under it are over.", - "de": "### Neu\n- `nxc session state ` (und `Engine::session_state`) sagt, was aus einer Sitzung geworden ist:\n`running`, `ended` mit dem gemeldeten Zeitpunkt, oder `unknown` — niemand hat ein Ende gemeldet, und\nkein lebender Prozess antwortet für sie. `--thread ` meldet alle Sitzungen eines Fadens,\nbeendete eingeschlossen — für die Frage, die man stellt, bevor man eine Arbeitskopie anfasst, an der\nein vorheriger Schritt noch die Hände haben könnte. Die Sitzungstabelle hatte zwei Schreiber\n(`session bind`, `session ended`) und keinen Leser; die einzige Art zu fragen war, eine pid-Datei aus\neinem Verzeichnis von `nxc` zu lesen.\n- Eine einbettende App kann dem nexus-flow-Hintergrunddienst jetzt selbst sagen, welche\nArbeitsbereiche er betreuen soll, ohne dafür die CLI aufzurufen. Die neue Kiste `nxs-service` trägt\ndie Naht: einen Arbeitsbereich `register`n, ihn wieder `deregister`n, und `attendance` liest zurück,\nob der Dienst überhaupt läuft und was er zuletzt mit diesem Arbeitsbereich getan hat. Anmelden ist\nidempotent; etwas abzumelden, das nie angemeldet war, ist ein erfolgreicher Leerlauf. Im\nArbeitsbereich selbst wird dabei nichts angefasst — die Stream-Bindung in `.nxs/sync.toml` übersteht\nein Abmelden, ein erneutes Anmelden setzt also dort fort, wo es aufgehört hat.\nDie CLI bekommt das Gegenstück, das ihr fehlte: `nxs sync unregister []` nimmt einen\nArbeitsbereich von der Liste des Dienstes. Mit einem Pfad lässt sich auch ein Eintrag entfernen,\ndessen Verzeichnis längst weg ist — bisher ließ sich `~/.nexusflow/workspaces.toml` nur von Hand\naufräumen, und die Datei wuchs ausschließlich.\n\n### Geändert\n- Es gibt jetzt EINEN nexus-flow-Hintergrunddienst, und er ist beides: das, was synchronisiert, und\ndas, was die Zeit hält. In den Hintergrundobjekten Ihres Macs steht er einmal, als `nexus-flow`.\nEin deklariertes Kanal-`timeout:` feuert auf macOS — bisher nie. `nxc` plante seine Einmal-Prüfung\nüber `at`, und macOS liefert `atrun` deaktiviert aus: `at` nahm den Job an, beendete sich mit 0,\ndruckte eine Job-Nummer, und niemand führte ihn je aus. Ein Mitglied, das verstummte, hielt seine\nRunde damit unbegrenzt an, ausgerechnet auf der Plattform, auf der das meiste davon läuft. Fristen\nsind jetzt überhaupt keine Betriebssystem-Jobs mehr. Eine Frist zu stellen schreibt eine Zeile in\ndas Buch des Arbeitsbereichs (`.nxs/timers.json`), und der Dienst — der diesen Arbeitsbereich zum\nSynchronisieren ohnehin besucht — führt die Prüfung aus, wenn es so weit ist. Damit gilt die Frist\nauf die Sekunde statt auf die nächste volle Minute aufgerundet, ein früh geschlossenes Brett lässt\nnichts zum Aufräumen zurück, und auf Linux arbeitet dieselbe Mechanik unverändert.\nDen Dienst einrichten: `nxs sync daemon install` (macOS), oder `nxs sync daemon` unter der eigenen\nProzessverwaltung. Sein Protokoll liegt in `~/.nexusflow/logs/service.log`. `nexus-flow` ist auch ein\nBefehl: er ist `nxs sync daemon`, `nexus-flow status` beantwortet also dieselbe Frage.\nNichts tut so, als ob. Eine Frist in einem Arbeitsbereich zu stellen, den kein Dienst betreut — oder\nwährend keiner läuft — trägt die Frist trotzdem ein und sagt Ihnen zugleich, als `tick_unscheduled`\nin `warnings`, dass sie gerade niemand beobachtet.\n`NXC_TIMER=at` wählt den `at`-Zeitgeber weiterhin ausdrücklich. `NXC_TIMER=launchd` ist weg und\njetzt ein Fehler statt eines stillen Ausweichens: es benannte einen Zeitgeber, der je Frist einen\neigenen macOS-Agenten einrichtete — genau die zweite Uhr, die hier abgelöst wird. Beim Einrichten\ndes Dienstes werden diese übrig gebliebenen Agenten entfernt.\n\n### Behoben\n- Eine Arbeitskopie, die von einer beendeten Kette gehalten wird, lässt sich wieder zurückgeben. Eine\nEskalation hält die Kopie mit Absicht — die Aufgabe ist noch in der Luft —, aber wenn alle\nSitzungen dieser Kette beendet sind, blieb nichts übrig, das je hätte sagen können, dass sie fertig\nist, und was dahinter wartete, wartete für immer. `nxc release --thread ` (und\n`Engine::release_working_tree`) nennt einen Faden aus der haltenden Kette, gibt die Kopie weiter und\nstartet, wer gewartet hat. Der Aufruf lehnt ab, solange eine Sitzung dieser Kette noch einen\nlebenden Prozess hat, und sagt welche. Die Zwei-Stunden-Grenze leert jetzt auch die Warteschlange:\nein `tick` nach Ablauf holt den verfallenen Anspruch zurück und startet das Geparkte, und eine\nBeauftragung, die parkt, plant einen für genau diesen Zeitpunkt ein — bisher hinderte die Grenze\neinen verwaisten Anspruch nur daran, *künftige* Erwerbungen abzulehnen, eine bereits wartende\nBeauftragung wartete unbegrenzt weiter. Zwei Korrekturen an `nxc guide limits-and-safety` kommen\nmit: eine Rückgabe zu beantworten löst den Halt in einem Kanal, aber nicht auf einem direkten Faden,\nund die Warteschlange wird nicht mehr nie geleert.\n- Der Hintergrunddienst teilt sich die fälligen Fristen jetzt ein, statt sie alle gleichzeitig zu\nstarten. Bisher startete jede fällig gewordene Frist im selben Augenblick ihren eigenen Prozess — was\nnach einem Wochenende mit zugeklapptem Laptop für alle Arbeitsbereiche zugleich passiert, und was\neine von Hand geschriebene Fristendatei absichtlich verlangen konnte. Jetzt laufen höchstens acht\ngleichzeitig, und im Protokoll steht, wie viele zurückgestellt wurden; der Rest bleibt gestellt und\nkommt in der nächsten Sekunde dran. Ein Schwall fliesst also ab, statt die Maschine zu überrollen,\nund nichts geht verloren.\nZwei kleinere Sicherungen kamen dazu: eine Fristendatei über einem Megabyte wird abgelehnt statt\njede Sekunde neu gelesen, und die Prüfung der Kennung, die beim Schreiben einer Frist galt, gilt\njetzt auch beim Lesen — die Datei liegt im Arbeitsbereich, und ein Arbeitsbereich kann ein Klon von\nirgendetwas sein. Ausserdem übernimmt der Dienst kein relatives Verzeichnis mehr aus Ihrem `PATH`;\nein solches hätte sich sonst gegen das jeweils bearbeitete Projekt aufgelöst.\n- `nxs sync daemon status` meldet einen längst beendeten Dienst nicht mehr als laufend. Bisher fragte\ndie Prüfung nur, ob die eingetragene Prozessnummer gerade *irgendeinem* Prozess gehört — und nachdem\nder Dienst gestorben ist, vergibt das Betriebssystem diese Nummer irgendwann weiter, ab da lautete\ndie Antwort für immer „ja\". Jetzt wird zusätzlich die echte Startzeit des Prozesses mit dem\nZeitpunkt verglichen, den der Dienst beim Start eingetragen hat: stimmen sie nicht überein, gehört\ndie Nummer jemand anderem. Auf einer Plattform, die keine Startzeit liefert, lautet die Antwort\nehrlich „unbekannt\" statt eines zuversichtlichen „läuft\".\n- Ein langsames Relay kann nicht mehr alle anderen Arbeitsbereiche aufhalten. Ein Sync-Durchlauf war\nallein über die Seitenzahl gedeckelt (20.000) — ein Relay, das jede Anfrage langsam beantwortet und\nden Cursor um einen beliebig kleinen Betrag weiterschiebt, konnte einen einzelnen Durchlauf damit\nüber Tage ziehen. Und der Hintergrunddienst arbeitet die angemeldeten Arbeitsbereiche nacheinander\nab: einer, der hängt, verhungert also alle übrigen, ihre Fristen eingeschlossen. Jeder Durchlauf hat\njetzt zusätzlich ein Zeitbudget von 60 Sekunden. Der Abbruch ist sauber: die Wasserlinie rückt je\nSeite vor, der nächste Durchlauf setzt also genau dort fort, und ein wirklich grosser Rückstand\nfliesst über mehrere Durchläufe ab statt in einem langen.\nUnd wenn ein Durchlauf an einer Grenze endet, sagt er jetzt, in welcher Lage er ist. Ein Durchlauf\nmit vollen Seiten hat noch Geschichte zu holen; ein Durchlauf, dessen Seiten leer zurückkamen,\nwährend der Cursor weiterlief, sieht ein tröpfelndes Relay, das nie abfliesst. Beides war vorher\nnicht zu unterscheiden. `nxs sync run` und das Protokoll des Dienstes melden es jetzt in denselben\nWorten, mit den Seitenzahlen, auf denen das Urteil beruht. `--json` trägt `budget_exhausted`,\n`pull_pages`, `pull_empty_pages` und `stopped_early`.\n- Eine Eskalation HÄLT jetzt einen geordneten Kanal an, statt ihn vorzurücken. `nxc reply --escalate`\nsagt „das kann ich nicht ausführen\", und eine Aufgabe, die nicht ausgeführt wurde, beauftragt nicht\nmehr die Arbeit, die auf sie folgen sollte: an einem Kanal mit `flow: sequential` wird der nächste\nSchritt nicht gestartet, die Runde geht so nach oben, wie sie ist, und wer sie beauftragt hat,\nentscheidet. Vor der Korrektur gemessen: ein Coder benutzte `--escalate` ausdrücklich, um einen\nAblauf anzuhalten — er hatte den Vorfall verstanden, den er sonst auslösen würde, und schrieb das\nhin —, und die Sitzung des nächsten Schritts startete zehn Sekunden später trotzdem, in eine\nArbeitskopie, die noch auf dem Stand vor der Review stand. Vier Fäden desselben Laufs trugen die\nEskalationsmarke, jedes Mal absichtlich, keines mit Wirkung; gehalten hat den Ablauf am Ende nur ein\nSatz in der Beauftragung. Ein `parallel`-Kanal bleibt unberührt. Eines hat weiterhin keine\nSchreibweise, und der Leitfaden sagt es jetzt deutlich: `reply` hat zwei Antworten, „ich bin fertig\"\nund „ich kann nicht\", und „noch nicht\" ist keine davon — ein Schritt, der auf eine eigene Runde\nwartet, wartet und antwortet, wenn deren Ergebnis da ist.\n\nUnd die Arbeitskopie wird jetzt für die ganze OPERATION beansprucht statt für den Zweig, in dem der\nexklusive Schritt zufällig begann. Der Anspruch entsteht weiterhin erst beim ersten Schritt mit\n`working_tree: exclusive` — während ein Auftrag nur geplant wird, ist nichts gehalten —, wird aber\nerst zurückgegeben, wenn die Operation fertig ist. Das schliesst die Lücke vor einer zweiten Runde:\nein Planer, der Coding zweimal beauftragt, behält die Arbeitskopie über die Pause dazwischen, in die\nbisher eine fremde Kette hineinrutschen und den Vorgang aus Sicht des Aufrufers unterbrechen konnte.\n\nDamit gilt die pauschale Zwei-Stunden-Frist nicht mehr für jeden Anspruch. Ein Anspruch läuft jetzt\nbis zum spätesten `timeout:`, das in seinem eigenen Gebiet noch aussteht: eine Runde, die sechs\nStunden deklariert, hält die Kopie sechs Stunden (bisher verlor sie sie nach zwei Stunden unter sich\nund reichte die Arbeitskopie an die nächste Kette weiter, während sie noch baute), und eine\nOperation, deren Schritte ALLE Minuten deklarieren, ist nach einem Absturz in Minuten wieder frei,\nstatt die Maschine zwei Stunden zu blockieren. Das „alle\" trägt: eine einzige ausstehende\nVerpflichtung ohne Fenster — und ein Gespräch, das ein Mensch mit einem schlichten `nxc send`\neröffnet, ist eine — setzt die ganze Operation unverändert auf die Zwei-Stunden-Grenze zurück. Es ist\nalso eine Frist, für die man sich mit `timeout:` entlang der Kette entscheidet, und keine, die sich\nunter einer bestehenden Deklaration ändert. Und eine abgelaufene Frist genügt allein nicht mehr, um die\nKopie zu nehmen: eine Kette, deren Anspruch abgelaufen ist, deren Prozess aber noch lebt, behält sie\n— dieselbe Ablehnung, die `nxc release` immer schon ausspricht, jetzt auch am anderen Ende gefragt.\n\nDie ausgelieferte Beispiel-Rollensammlung sagt das alles an der Stelle, von der ein Rollen-Autor\nabschreibt — einschliesslich des Teils, den sie nicht beheben kann: drei ihrer vier Reviewer führen\ndie Gates des Projekts selbst aus, dieses Quorum stellt also drei gleichzeitige Builds in ein\n`target/`, und kein Wert von `working_tree:` trennt die Mitglieder einer Runde voneinander.\n- Ein Kanal, der die Arbeitskopie für sich braucht, bleibt nicht mehr stehen, wenn die Sitzung eines\nSchritts stirbt, ohne es zu melden. Da ein Schritt eines solchen Kanals endet, wenn seine Sitzung\nendet, ließ eine verlorene Endmeldung — ein vor dem Abbau getöteter Sidecar, ein älteres `nxc`, eine\nWirt-Laufzeit, die keine meldet — die Runde stehen, ohne dass etwas eingeplant war, das noch einmal\nnachsieht; der dokumentierte Weg heraus, `nxc tick --thread `, faltete die Runde dann entweder\nzusammen und übersprang alle verbleibenden Schritte, oder er tat gar nichts. Jetzt plant eine\nAblehnung, deren einziger verbliebener Hinderungsgrund eine lebende Sitzung ist, eine Minute später\neine Nachprüfung desselben Bretts ein, und tut das weiter, solange der Prozess da ist: eine hart\ngetötete Sitzung fällt binnen einer Minute auf, und der Ablauf geht von selbst weiter — richtig, und\nohne zu konsolidieren. Ein Prozess, der hängt statt zu enden, ist weiterhin ein Warten — aber ein\nsichtbares, auf das etwas schaut.\n\nUnd `nxc` löst seine Agenten-Laufzeit jetzt an der Wurzel des Arbeitsbereichs auf statt in dem\nVerzeichnis, in dem man gerade steht. Ein `send` aus einem Unterverzeichnis legte dort ein zweites\n`.nxs/agent-logs/` an und startete den Agenten darin, und ein späterer Aufruf von anderswo fand die\npid-Datei einer laufenden Sitzung nicht — er las „läuft nicht\" für eine Sitzung, die lief, und das\nist an jeder fragenden Stelle die schädliche Richtung.\n\n**Vor dem Aktualisieren laufende Sitzungen auslaufen lassen.** Ein enger Fall bleibt: eine Sitzung,\ndie im Moment der Aktualisierung noch läuft und aus einem Unterverzeichnis gestartet wurde, behält\nihre pid-Datei dort — sie liest sich danach als nicht laufend, und der Kanal, den sie hält, kann die\nArbeitskopie an seinen nächsten Schritt weitergeben, während sie noch schreibt. Betroffen ist diese\neine Sitzung und nichts danach. `nxc status` zeigt, was in der Luft ist, und\n`nxc session state --thread ` sagt, ob die Sitzungen darunter vorbei sind.\n\n### Facade-Kontrakt\n- `changed` · `nxc session state ` (und `Engine::session_state`) sagt, was aus einer Sitzung geworden ist:\n`running`, `ended` mit dem gemeldeten Zeitpunkt, oder `unknown` — niemand hat ein Ende gemeldet, und\nkein lebender Prozess antwortet für sie. `--thread ` meldet alle Sitzungen eines Fadens,\nbeendete eingeschlossen — für die Frage, die man stellt, bevor man eine Arbeitskopie anfasst, an der\nein vorheriger Schritt noch die Hände haben könnte. Die Sitzungstabelle hatte zwei Schreiber\n(`session bind`, `session ended`) und keinen Leser; die einzige Art zu fragen war, eine pid-Datei aus\neinem Verzeichnis von `nxc` zu lesen.\n- `changed` · Eine Arbeitskopie, die von einer beendeten Kette gehalten wird, lässt sich wieder zurückgeben. Eine\nEskalation hält die Kopie mit Absicht — die Aufgabe ist noch in der Luft —, aber wenn alle\nSitzungen dieser Kette beendet sind, blieb nichts übrig, das je hätte sagen können, dass sie fertig\nist, und was dahinter wartete, wartete für immer. `nxc release --thread ` (und\n`Engine::release_working_tree`) nennt einen Faden aus der haltenden Kette, gibt die Kopie weiter und\nstartet, wer gewartet hat. Der Aufruf lehnt ab, solange eine Sitzung dieser Kette noch einen\nlebenden Prozess hat, und sagt welche. Die Zwei-Stunden-Grenze leert jetzt auch die Warteschlange:\nein `tick` nach Ablauf holt den verfallenen Anspruch zurück und startet das Geparkte, und eine\nBeauftragung, die parkt, plant einen für genau diesen Zeitpunkt ein — bisher hinderte die Grenze\neinen verwaisten Anspruch nur daran, *künftige* Erwerbungen abzulehnen, eine bereits wartende\nBeauftragung wartete unbegrenzt weiter. Zwei Korrekturen an `nxc guide limits-and-safety` kommen\nmit: eine Rückgabe zu beantworten löst den Halt in einem Kanal, aber nicht auf einem direkten Faden,\nund die Warteschlange wird nicht mehr nie geleert.\n- `changed` · Es gibt jetzt EINEN nexus-flow-Hintergrunddienst, und er ist beides: das, was synchronisiert, und\ndas, was die Zeit hält. In den Hintergrundobjekten Ihres Macs steht er einmal, als `nexus-flow`.\nEin deklariertes Kanal-`timeout:` feuert auf macOS — bisher nie. `nxc` plante seine Einmal-Prüfung\nüber `at`, und macOS liefert `atrun` deaktiviert aus: `at` nahm den Job an, beendete sich mit 0,\ndruckte eine Job-Nummer, und niemand führte ihn je aus. Ein Mitglied, das verstummte, hielt seine\nRunde damit unbegrenzt an, ausgerechnet auf der Plattform, auf der das meiste davon läuft. Fristen\nsind jetzt überhaupt keine Betriebssystem-Jobs mehr. Eine Frist zu stellen schreibt eine Zeile in\ndas Buch des Arbeitsbereichs (`.nxs/timers.json`), und der Dienst — der diesen Arbeitsbereich zum\nSynchronisieren ohnehin besucht — führt die Prüfung aus, wenn es so weit ist. Damit gilt die Frist\nauf die Sekunde statt auf die nächste volle Minute aufgerundet, ein früh geschlossenes Brett lässt\nnichts zum Aufräumen zurück, und auf Linux arbeitet dieselbe Mechanik unverändert.\nDen Dienst einrichten: `nxs sync daemon install` (macOS), oder `nxs sync daemon` unter der eigenen\nProzessverwaltung. Sein Protokoll liegt in `~/.nexusflow/logs/service.log`. `nexus-flow` ist auch ein\nBefehl: er ist `nxs sync daemon`, `nexus-flow status` beantwortet also dieselbe Frage.\nNichts tut so, als ob. Eine Frist in einem Arbeitsbereich zu stellen, den kein Dienst betreut — oder\nwährend keiner läuft — trägt die Frist trotzdem ein und sagt Ihnen zugleich, als `tick_unscheduled`\nin `warnings`, dass sie gerade niemand beobachtet.\n`NXC_TIMER=at` wählt den `at`-Zeitgeber weiterhin ausdrücklich. `NXC_TIMER=launchd` ist weg und\njetzt ein Fehler statt eines stillen Ausweichens: es benannte einen Zeitgeber, der je Frist einen\neigenen macOS-Agenten einrichtete — genau die zweite Uhr, die hier abgelöst wird. Beim Einrichten\ndes Dienstes werden diese übrig gebliebenen Agenten entfernt.\n- `changed` · Eine Eskalation HÄLT jetzt einen geordneten Kanal an, statt ihn vorzurücken. `nxc reply --escalate`\nsagt „das kann ich nicht ausführen\", und eine Aufgabe, die nicht ausgeführt wurde, beauftragt nicht\nmehr die Arbeit, die auf sie folgen sollte: an einem Kanal mit `flow: sequential` wird der nächste\nSchritt nicht gestartet, die Runde geht so nach oben, wie sie ist, und wer sie beauftragt hat,\nentscheidet. Vor der Korrektur gemessen: ein Coder benutzte `--escalate` ausdrücklich, um einen\nAblauf anzuhalten — er hatte den Vorfall verstanden, den er sonst auslösen würde, und schrieb das\nhin —, und die Sitzung des nächsten Schritts startete zehn Sekunden später trotzdem, in eine\nArbeitskopie, die noch auf dem Stand vor der Review stand. Vier Fäden desselben Laufs trugen die\nEskalationsmarke, jedes Mal absichtlich, keines mit Wirkung; gehalten hat den Ablauf am Ende nur ein\nSatz in der Beauftragung. Ein `parallel`-Kanal bleibt unberührt. Eines hat weiterhin keine\nSchreibweise, und der Leitfaden sagt es jetzt deutlich: `reply` hat zwei Antworten, „ich bin fertig\"\nund „ich kann nicht\", und „noch nicht\" ist keine davon — ein Schritt, der auf eine eigene Runde\nwartet, wartet und antwortet, wenn deren Ergebnis da ist.\n\nUnd die Arbeitskopie wird jetzt für die ganze OPERATION beansprucht statt für den Zweig, in dem der\nexklusive Schritt zufällig begann. Der Anspruch entsteht weiterhin erst beim ersten Schritt mit\n`working_tree: exclusive` — während ein Auftrag nur geplant wird, ist nichts gehalten —, wird aber\nerst zurückgegeben, wenn die Operation fertig ist. Das schliesst die Lücke vor einer zweiten Runde:\nein Planer, der Coding zweimal beauftragt, behält die Arbeitskopie über die Pause dazwischen, in die\nbisher eine fremde Kette hineinrutschen und den Vorgang aus Sicht des Aufrufers unterbrechen konnte.\n\nDamit gilt die pauschale Zwei-Stunden-Frist nicht mehr für jeden Anspruch. Ein Anspruch läuft jetzt\nbis zum spätesten `timeout:`, das in seinem eigenen Gebiet noch aussteht: eine Runde, die sechs\nStunden deklariert, hält die Kopie sechs Stunden (bisher verlor sie sie nach zwei Stunden unter sich\nund reichte die Arbeitskopie an die nächste Kette weiter, während sie noch baute), und eine\nOperation, deren Schritte ALLE Minuten deklarieren, ist nach einem Absturz in Minuten wieder frei,\nstatt die Maschine zwei Stunden zu blockieren. Das „alle\" trägt: eine einzige ausstehende\nVerpflichtung ohne Fenster — und ein Gespräch, das ein Mensch mit einem schlichten `nxc send`\neröffnet, ist eine — setzt die ganze Operation unverändert auf die Zwei-Stunden-Grenze zurück. Es ist\nalso eine Frist, für die man sich mit `timeout:` entlang der Kette entscheidet, und keine, die sich\nunter einer bestehenden Deklaration ändert. Und eine abgelaufene Frist genügt allein nicht mehr, um die\nKopie zu nehmen: eine Kette, deren Anspruch abgelaufen ist, deren Prozess aber noch lebt, behält sie\n— dieselbe Ablehnung, die `nxc release` immer schon ausspricht, jetzt auch am anderen Ende gefragt.\n\nDie ausgelieferte Beispiel-Rollensammlung sagt das alles an der Stelle, von der ein Rollen-Autor\nabschreibt — einschliesslich des Teils, den sie nicht beheben kann: drei ihrer vier Reviewer führen\ndie Gates des Projekts selbst aus, dieses Quorum stellt also drei gleichzeitige Builds in ein\n`target/`, und kein Wert von `working_tree:` trennt die Mitglieder einer Runde voneinander.\n- `changed` · Ein Kanal, der die Arbeitskopie für sich braucht, bleibt nicht mehr stehen, wenn die Sitzung eines\nSchritts stirbt, ohne es zu melden. Da ein Schritt eines solchen Kanals endet, wenn seine Sitzung\nendet, ließ eine verlorene Endmeldung — ein vor dem Abbau getöteter Sidecar, ein älteres `nxc`, eine\nWirt-Laufzeit, die keine meldet — die Runde stehen, ohne dass etwas eingeplant war, das noch einmal\nnachsieht; der dokumentierte Weg heraus, `nxc tick --thread `, faltete die Runde dann entweder\nzusammen und übersprang alle verbleibenden Schritte, oder er tat gar nichts. Jetzt plant eine\nAblehnung, deren einziger verbliebener Hinderungsgrund eine lebende Sitzung ist, eine Minute später\neine Nachprüfung desselben Bretts ein, und tut das weiter, solange der Prozess da ist: eine hart\ngetötete Sitzung fällt binnen einer Minute auf, und der Ablauf geht von selbst weiter — richtig, und\nohne zu konsolidieren. Ein Prozess, der hängt statt zu enden, ist weiterhin ein Warten — aber ein\nsichtbares, auf das etwas schaut.\n\nUnd `nxc` löst seine Agenten-Laufzeit jetzt an der Wurzel des Arbeitsbereichs auf statt in dem\nVerzeichnis, in dem man gerade steht. Ein `send` aus einem Unterverzeichnis legte dort ein zweites\n`.nxs/agent-logs/` an und startete den Agenten darin, und ein späterer Aufruf von anderswo fand die\npid-Datei einer laufenden Sitzung nicht — er las „läuft nicht\" für eine Sitzung, die lief, und das\nist an jeder fragenden Stelle die schädliche Richtung.\n\n**Vor dem Aktualisieren laufende Sitzungen auslaufen lassen.** Ein enger Fall bleibt: eine Sitzung,\ndie im Moment der Aktualisierung noch läuft und aus einem Unterverzeichnis gestartet wurde, behält\nihre pid-Datei dort — sie liest sich danach als nicht laufend, und der Kanal, den sie hält, kann die\nArbeitskopie an seinen nächsten Schritt weitergeben, während sie noch schreibt. Betroffen ist diese\neine Sitzung und nichts danach. `nxc status` zeigt, was in der Luft ist, und\n`nxc session state --thread ` sagt, ob die Sitzungen darunter vorbei sind." - } - }, - { - "version": "0.66.0", - "date": "2026-08-24", - "items": [ - { - "type": "added", - "en": "`nxf next` reads like a list a person can scan. On a real terminal the ticket title is bold — it is\nthe one field you actually look for, and it used to weigh the same as the id in front of it — the id\nand the `↳` epic line are muted, and the priority is coloured along one ramp from Deep Ember through\nEmber and Flame down to graphite, so the weighting is visible without reading the label. The ramp\nlives in the shared presentation layer and maps the canonical priority ORDINAL, not its name, so it\nis just as right under a plugin that calls its levels now/soon/later.\n\nNothing changes anywhere else: `--json`, a pipe, CI and an agent's `prime` context still receive the\nsame plain ASCII, byte for byte, with no escape sequences at all. `NO_COLOR` and `TERM=dumb` on a\nterminal keep the weight (bold at the hot end of the ramp, dim at the cold one) and drop the colour.\n\n`nxf next --limit ` is new: show only the head of the list. It never cuts silently — a truncated\nlist is headed `showing 6 of 181`, and under `--json` the flag wraps the records as\n`{\"items\": [...], \"total\": 181}` so a consumer reads the true total instead of guessing from the\narray's length. Without `--limit` the command is unchanged and complete, and its `--json` payload is\nstill the same bare array. The limit applies last, after `--sort` and after `--label`, so a filtered\nlist discloses how much of YOUR filter you are seeing. `prime`'s own \"showing 15 of 180\" now goes\nthrough this one mechanism instead of a second, private one.", - "de": "`nxf next` liest sich wie eine Liste, die ein Mensch überfliegen kann. Auf einem echten Terminal\nsteht der Ticket-Titel fett — er ist das Einzige, wonach man wirklich sucht, und wog bisher genauso\nschwer wie die Id davor —, Id und `↳`-Epic-Zeile sind gedämpft, und die Priorität ist entlang einer\nRampe eingefärbt: von Deep Ember über Ember und Flame hinunter zu Graphit. Die Gewichtung ist damit\nsichtbar, ohne das Label zu lesen. Die Rampe liegt in der gemeinsamen Präsentationsschicht und bildet\nauf den kanonischen ORDINALWERT der Priorität ab, nicht auf ihren Namen — sie stimmt deshalb genauso\nunter einem Plugin, das seine Stufen jetzt/bald/später nennt.\n\nSonst ändert sich nichts: `--json`, eine Pipe, CI und der `prime`-Kontext eines Agenten bekommen\nweiterhin dasselbe schlichte ASCII, Byte für Byte, ohne jede Escape-Sequenz. `NO_COLOR` und\n`TERM=dumb` auf einem Terminal behalten das Gewicht (fett am heißen, gedimmt am kalten Ende der\nRampe) und lassen die Farbe weg.\n\nNeu ist `nxf next --limit `: nur den Kopf der Liste zeigen. Gekürzt wird nie stillschweigend —\nüber einer gekürzten Liste steht `showing 6 of 181`, und unter `--json` verpackt die Option die\nDatensätze als `{\"items\": [...], \"total\": 181}`, damit ein Konsument die echte Gesamtzahl liest,\nstatt sie aus der Länge des Arrays zu erraten. Ohne `--limit` ist der Befehl unverändert vollständig,\nund seine `--json`-Ausgabe bleibt dasselbe blanke Array. Das Limit greift zuletzt, nach `--sort` und\nnach `--label` — eine gefilterte Liste weist also aus, wie viel von IHREM Filter Sie sehen. Auch\n`prime`s eigenes „showing 15 of 180\" läuft jetzt über diesen einen Mechanismus statt über einen\nzweiten, privaten.", - "facade": "changed" - } - ], - "notes": { - "en": "### Added\n- `nxf next` reads like a list a person can scan. On a real terminal the ticket title is bold — it is\nthe one field you actually look for, and it used to weigh the same as the id in front of it — the id\nand the `↳` epic line are muted, and the priority is coloured along one ramp from Deep Ember through\nEmber and Flame down to graphite, so the weighting is visible without reading the label. The ramp\nlives in the shared presentation layer and maps the canonical priority ORDINAL, not its name, so it\nis just as right under a plugin that calls its levels now/soon/later.\n\nNothing changes anywhere else: `--json`, a pipe, CI and an agent's `prime` context still receive the\nsame plain ASCII, byte for byte, with no escape sequences at all. `NO_COLOR` and `TERM=dumb` on a\nterminal keep the weight (bold at the hot end of the ramp, dim at the cold one) and drop the colour.\n\n`nxf next --limit ` is new: show only the head of the list. It never cuts silently — a truncated\nlist is headed `showing 6 of 181`, and under `--json` the flag wraps the records as\n`{\"items\": [...], \"total\": 181}` so a consumer reads the true total instead of guessing from the\narray's length. Without `--limit` the command is unchanged and complete, and its `--json` payload is\nstill the same bare array. The limit applies last, after `--sort` and after `--label`, so a filtered\nlist discloses how much of YOUR filter you are seeing. `prime`'s own \"showing 15 of 180\" now goes\nthrough this one mechanism instead of a second, private one.\n\n### Facade Contract\n- `changed` · `nxf next` reads like a list a person can scan. On a real terminal the ticket title is bold — it is\nthe one field you actually look for, and it used to weigh the same as the id in front of it — the id\nand the `↳` epic line are muted, and the priority is coloured along one ramp from Deep Ember through\nEmber and Flame down to graphite, so the weighting is visible without reading the label. The ramp\nlives in the shared presentation layer and maps the canonical priority ORDINAL, not its name, so it\nis just as right under a plugin that calls its levels now/soon/later.\n\nNothing changes anywhere else: `--json`, a pipe, CI and an agent's `prime` context still receive the\nsame plain ASCII, byte for byte, with no escape sequences at all. `NO_COLOR` and `TERM=dumb` on a\nterminal keep the weight (bold at the hot end of the ramp, dim at the cold one) and drop the colour.\n\n`nxf next --limit ` is new: show only the head of the list. It never cuts silently — a truncated\nlist is headed `showing 6 of 181`, and under `--json` the flag wraps the records as\n`{\"items\": [...], \"total\": 181}` so a consumer reads the true total instead of guessing from the\narray's length. Without `--limit` the command is unchanged and complete, and its `--json` payload is\nstill the same bare array. The limit applies last, after `--sort` and after `--label`, so a filtered\nlist discloses how much of YOUR filter you are seeing. `prime`'s own \"showing 15 of 180\" now goes\nthrough this one mechanism instead of a second, private one.", - "de": "### Neu\n- `nxf next` liest sich wie eine Liste, die ein Mensch überfliegen kann. Auf einem echten Terminal\nsteht der Ticket-Titel fett — er ist das Einzige, wonach man wirklich sucht, und wog bisher genauso\nschwer wie die Id davor —, Id und `↳`-Epic-Zeile sind gedämpft, und die Priorität ist entlang einer\nRampe eingefärbt: von Deep Ember über Ember und Flame hinunter zu Graphit. Die Gewichtung ist damit\nsichtbar, ohne das Label zu lesen. Die Rampe liegt in der gemeinsamen Präsentationsschicht und bildet\nauf den kanonischen ORDINALWERT der Priorität ab, nicht auf ihren Namen — sie stimmt deshalb genauso\nunter einem Plugin, das seine Stufen jetzt/bald/später nennt.\n\nSonst ändert sich nichts: `--json`, eine Pipe, CI und der `prime`-Kontext eines Agenten bekommen\nweiterhin dasselbe schlichte ASCII, Byte für Byte, ohne jede Escape-Sequenz. `NO_COLOR` und\n`TERM=dumb` auf einem Terminal behalten das Gewicht (fett am heißen, gedimmt am kalten Ende der\nRampe) und lassen die Farbe weg.\n\nNeu ist `nxf next --limit `: nur den Kopf der Liste zeigen. Gekürzt wird nie stillschweigend —\nüber einer gekürzten Liste steht `showing 6 of 181`, und unter `--json` verpackt die Option die\nDatensätze als `{\"items\": [...], \"total\": 181}`, damit ein Konsument die echte Gesamtzahl liest,\nstatt sie aus der Länge des Arrays zu erraten. Ohne `--limit` ist der Befehl unverändert vollständig,\nund seine `--json`-Ausgabe bleibt dasselbe blanke Array. Das Limit greift zuletzt, nach `--sort` und\nnach `--label` — eine gefilterte Liste weist also aus, wie viel von IHREM Filter Sie sehen. Auch\n`prime`s eigenes „showing 15 of 180\" läuft jetzt über diesen einen Mechanismus statt über einen\nzweiten, privaten.\n\n### Facade-Kontrakt\n- `changed` · `nxf next` liest sich wie eine Liste, die ein Mensch überfliegen kann. Auf einem echten Terminal\nsteht der Ticket-Titel fett — er ist das Einzige, wonach man wirklich sucht, und wog bisher genauso\nschwer wie die Id davor —, Id und `↳`-Epic-Zeile sind gedämpft, und die Priorität ist entlang einer\nRampe eingefärbt: von Deep Ember über Ember und Flame hinunter zu Graphit. Die Gewichtung ist damit\nsichtbar, ohne das Label zu lesen. Die Rampe liegt in der gemeinsamen Präsentationsschicht und bildet\nauf den kanonischen ORDINALWERT der Priorität ab, nicht auf ihren Namen — sie stimmt deshalb genauso\nunter einem Plugin, das seine Stufen jetzt/bald/später nennt.\n\nSonst ändert sich nichts: `--json`, eine Pipe, CI und der `prime`-Kontext eines Agenten bekommen\nweiterhin dasselbe schlichte ASCII, Byte für Byte, ohne jede Escape-Sequenz. `NO_COLOR` und\n`TERM=dumb` auf einem Terminal behalten das Gewicht (fett am heißen, gedimmt am kalten Ende der\nRampe) und lassen die Farbe weg.\n\nNeu ist `nxf next --limit `: nur den Kopf der Liste zeigen. Gekürzt wird nie stillschweigend —\nüber einer gekürzten Liste steht `showing 6 of 181`, und unter `--json` verpackt die Option die\nDatensätze als `{\"items\": [...], \"total\": 181}`, damit ein Konsument die echte Gesamtzahl liest,\nstatt sie aus der Länge des Arrays zu erraten. Ohne `--limit` ist der Befehl unverändert vollständig,\nund seine `--json`-Ausgabe bleibt dasselbe blanke Array. Das Limit greift zuletzt, nach `--sort` und\nnach `--label` — eine gefilterte Liste weist also aus, wie viel von IHREM Filter Sie sehen. Auch\n`prime`s eigenes „showing 15 of 180\" läuft jetzt über diesen einen Mechanismus statt über einen\nzweiten, privaten." - } - }, - { - "version": "0.65.0", - "date": "2026-08-24", - "items": [ - { - "type": "changed", - "en": "A session that ends without answering the thread it owed is now given its turn back and reminded —\nonce, naming the thread, both ways to end a turn, and any round it commissioned that is still open —\nbefore the sidecar posts anything in its name. Most sessions answer then. One that is reminded and\nends silent again is handed back as an escalation, so `escalated: true` on `nxc status` tells the\ncaller that no result exists and somebody has to decide.\n\nAn escalation is now recognisable in the text the receiving session reads FIRST, on all three paths\nit can travel (a channel's pass-through, a 1:1 resume, a quorum wake). The notice says what it is,\nthat the working copy is held while it stands, and what is expected in return — `escalated: true`\nwas already on the thread and did not help, because an agent reads the message that woke it, not\n`nxc status`.\n\n`nxc status` gains two operation-level flags: `needs_decision` (somewhere under this root a task was\nhanded back and nobody took it up — read it beside `awaiting_human`, which looks identical and means\nthe opposite) and `holds_working_tree` (somewhere under this root the working copy is held, so you\nknow whether to go looking at all).\n\nA round whose every commission was withdrawn no longer counts as an open operation: `nxc withdraw`\nnow discharges the thread you named as well, instead of leaving it waiting for a supervisor whose\nround had just been taken away.\n\n`nxc tick` now says what a board is waiting for. A settled round whose only remaining blocker is a\nlive session reports `waiting_for_a_session` instead of `not_due` — the difference matters, because\nnothing re-checks that one on a clock and this verb is the way out.\n\nThe agent sidecar's reminder round is bounded (five minutes) and every `nxc` call it makes now\ncarries a timeout (thirty seconds). Both close the same shape: this teardown runs inside the process\nwhose EXIT an exclusive channel is waiting for, so one call that never returns holds a checkout and\nnot merely a process.", - "de": "Eine Sitzung, die endet, ohne den geschuldeten Faden zu beantworten, bekommt ihren Zug jetzt zurück\nund wird erinnert — einmal, mit dem konkreten Faden, beiden Arten einen Zug zu beenden, und jeder von\nihr beauftragten Runde, die noch offen ist —, bevor der Sidecar in ihrem Namen etwas postet. Die\nmeisten Sitzungen antworten dann. Eine, die erinnert wurde und erneut stumm endet, wird als\nEskalation zurückgegeben; `escalated: true` in `nxc status` sagt dem Aufrufer damit, dass es kein\nErgebnis gibt und jemand entscheiden muss.\n\nEine Eskalation ist jetzt in dem Text erkennbar, den die empfangende Sitzung ZUERST liest — auf allen\ndrei Wegen, die sie nehmen kann (Kanal-Durchreichung, 1:1-Wiederaufnahme, Quorum-Weckruf). Der\nHinweis sagt, was es ist, dass die Arbeitskopie währenddessen gehalten wird und was erwartet wird.\n`escalated: true` stand schon am Faden und half nicht: ein Agent liest die Nachricht, die ihn\ngeweckt hat, nicht `nxc status`.\n\n`nxc status` bekommt zwei Kennzeichen am Vorgang: `needs_decision` (irgendwo unter dieser Wurzel\nwurde eine Aufgabe zurückgegeben und niemand hat sie aufgenommen — neben `awaiting_human` zu lesen,\ndas genauso aussieht und das Gegenteil bedeutet) und `holds_working_tree` (irgendwo darunter wird\ndie Arbeitskopie gehalten, Sie wissen also, ob Sie überhaupt nachsehen müssen).\n\nEine Runde, deren sämtliche Beauftragungen zurückgenommen wurden, zählt nicht mehr als offener\nVorgang: `nxc withdraw` entlastet jetzt auch den genannten Faden, statt ihn auf einen Betreuer warten\nzu lassen, dem man gerade die Runde weggenommen hat.\n\n`nxc tick` sagt jetzt, worauf ein Board wartet. Eine erledigte Runde, deren einziger verbleibender\nBlocker eine lebende Sitzung ist, meldet `waiting_for_a_session` statt `not_due` — der Unterschied\nzählt, denn genau das prüft keine Uhr nach, und dieses Verb ist der Weg heraus.\n\nDie Erinnerungsrunde des Agent-Sidecars ist zeitlich begrenzt (fünf Minuten), und jeder `nxc`-Aufruf,\nden er macht, trägt jetzt ein Zeitlimit (dreißig Sekunden). Beides schließt dieselbe Form: dieser\nAbbau läuft in dem Prozess, auf dessen ENDE ein exklusiver Kanal wartet — ein Aufruf, der nie\nzurückkommt, hält also eine Arbeitskopie und nicht bloß einen Prozess.", - "facade": "changed" - }, - { - "type": "fixed", - "en": "A step of a `flow: sequential` channel that declares `working_tree: exclusive` now ends when its\nsession's process ends, not when its reply arrives. A member that answers and keeps working no\nlonger lets the next step start into the same checkout — measured in a real run, a coder answered\nat 00:16:33 and edited on until 00:34:27 while the next step had been running since 00:16:36.\nSessions report their own end (`nxc session ended`, called by the agent sidecar's teardown, and\n`Engine::session_ended` for a host that runs its own runtime); where that never arrives, the\nworker's process check answers the next time anything asks. **Nothing re-checks it on a clock**: the\nchannel's declared `timeout:` is a deadline for an *answer*, and a member that has replied has\nanswered — so if the announcement is lost (a sidecar killed before its teardown, an older `nxc`, a\nhost runtime that never wires `Engine::session_ended`) the round waits until somebody runs\n`nxc tick --thread `, which now says exactly that instead of \"nothing to do\". Channels without\nan exclusive working-copy claim are unchanged.\n\nEvery session that owes a reply is now told, in its system prompt, that its turn must end in one of\nexactly two ways — `nxc reply --thread \"\"` or `nxc reply --thread --escalate\n\"\"`. That obligation is delivered even to a persona declared `prime: false`, so\na declaration cannot leave out the one rule the engine depends on.", - "de": "Ein Schritt eines `flow: sequential`-Kanals mit `working_tree: exclusive` endet jetzt, wenn der\nProzess seiner Sitzung endet — nicht, wenn seine Antwort eintrifft. Ein Mitglied, das antwortet und\nweiterarbeitet, lässt den nächsten Schritt nicht mehr in dieselbe Arbeitskopie starten; in einem\nechten Lauf gemessen, antwortete ein Coder um 00:16:33 und schrieb bis 00:34:27 weiter, während der\nNachfolger seit 00:16:36 lief. Sitzungen melden ihr Ende selbst (`nxc session ended`, vom\nAgent-Sidecar beim Abbau aufgerufen, und `Engine::session_ended` für einen Wirt mit eigener\nLaufzeit); bleibt das aus, antwortet die Prozessprüfung des Workers beim nächsten Mal, dass\nüberhaupt jemand fragt. **Eine Uhr prüft das nicht nach**: das deklarierte `timeout:` des Kanals ist\neine Frist für eine ANTWORT, und ein Mitglied, das geantwortet hat, hat geantwortet — geht die\nMeldung also verloren (ein vor dem Abbau getöteter Sidecar, ein älteres `nxc`, eine Wirt-Laufzeit\nohne `Engine::session_ended`), wartet die Runde, bis jemand `nxc tick --thread ` ausführt, das\njetzt genau das sagt statt „nichts zu tun\". Kanäle ohne exklusiven Anspruch auf die Arbeitskopie\nbleiben unverändert.\n\nJede Sitzung, die eine Antwort schuldet, erfährt jetzt in ihrem System-Prompt, dass ihr Zug auf\ngenau zwei Arten enden muss — `nxc reply --thread \"\"` oder `nxc reply --thread \n--escalate \"\"`. Diese Pflicht wird auch an eine Persona mit `prime: false`\nausgeliefert, damit eine Deklaration die eine Regel, auf die sich die Engine verlässt, nicht\nweglassen kann.", - "facade": "changed" - } - ], - "notes": { - "en": "### Changed\n- A session that ends without answering the thread it owed is now given its turn back and reminded —\nonce, naming the thread, both ways to end a turn, and any round it commissioned that is still open —\nbefore the sidecar posts anything in its name. Most sessions answer then. One that is reminded and\nends silent again is handed back as an escalation, so `escalated: true` on `nxc status` tells the\ncaller that no result exists and somebody has to decide.\n\nAn escalation is now recognisable in the text the receiving session reads FIRST, on all three paths\nit can travel (a channel's pass-through, a 1:1 resume, a quorum wake). The notice says what it is,\nthat the working copy is held while it stands, and what is expected in return — `escalated: true`\nwas already on the thread and did not help, because an agent reads the message that woke it, not\n`nxc status`.\n\n`nxc status` gains two operation-level flags: `needs_decision` (somewhere under this root a task was\nhanded back and nobody took it up — read it beside `awaiting_human`, which looks identical and means\nthe opposite) and `holds_working_tree` (somewhere under this root the working copy is held, so you\nknow whether to go looking at all).\n\nA round whose every commission was withdrawn no longer counts as an open operation: `nxc withdraw`\nnow discharges the thread you named as well, instead of leaving it waiting for a supervisor whose\nround had just been taken away.\n\n`nxc tick` now says what a board is waiting for. A settled round whose only remaining blocker is a\nlive session reports `waiting_for_a_session` instead of `not_due` — the difference matters, because\nnothing re-checks that one on a clock and this verb is the way out.\n\nThe agent sidecar's reminder round is bounded (five minutes) and every `nxc` call it makes now\ncarries a timeout (thirty seconds). Both close the same shape: this teardown runs inside the process\nwhose EXIT an exclusive channel is waiting for, so one call that never returns holds a checkout and\nnot merely a process.\n\n### Fixed\n- A step of a `flow: sequential` channel that declares `working_tree: exclusive` now ends when its\nsession's process ends, not when its reply arrives. A member that answers and keeps working no\nlonger lets the next step start into the same checkout — measured in a real run, a coder answered\nat 00:16:33 and edited on until 00:34:27 while the next step had been running since 00:16:36.\nSessions report their own end (`nxc session ended`, called by the agent sidecar's teardown, and\n`Engine::session_ended` for a host that runs its own runtime); where that never arrives, the\nworker's process check answers the next time anything asks. **Nothing re-checks it on a clock**: the\nchannel's declared `timeout:` is a deadline for an *answer*, and a member that has replied has\nanswered — so if the announcement is lost (a sidecar killed before its teardown, an older `nxc`, a\nhost runtime that never wires `Engine::session_ended`) the round waits until somebody runs\n`nxc tick --thread `, which now says exactly that instead of \"nothing to do\". Channels without\nan exclusive working-copy claim are unchanged.\n\nEvery session that owes a reply is now told, in its system prompt, that its turn must end in one of\nexactly two ways — `nxc reply --thread \"\"` or `nxc reply --thread --escalate\n\"\"`. That obligation is delivered even to a persona declared `prime: false`, so\na declaration cannot leave out the one rule the engine depends on.\n\n### Facade Contract\n- `changed` · A session that ends without answering the thread it owed is now given its turn back and reminded —\nonce, naming the thread, both ways to end a turn, and any round it commissioned that is still open —\nbefore the sidecar posts anything in its name. Most sessions answer then. One that is reminded and\nends silent again is handed back as an escalation, so `escalated: true` on `nxc status` tells the\ncaller that no result exists and somebody has to decide.\n\nAn escalation is now recognisable in the text the receiving session reads FIRST, on all three paths\nit can travel (a channel's pass-through, a 1:1 resume, a quorum wake). The notice says what it is,\nthat the working copy is held while it stands, and what is expected in return — `escalated: true`\nwas already on the thread and did not help, because an agent reads the message that woke it, not\n`nxc status`.\n\n`nxc status` gains two operation-level flags: `needs_decision` (somewhere under this root a task was\nhanded back and nobody took it up — read it beside `awaiting_human`, which looks identical and means\nthe opposite) and `holds_working_tree` (somewhere under this root the working copy is held, so you\nknow whether to go looking at all).\n\nA round whose every commission was withdrawn no longer counts as an open operation: `nxc withdraw`\nnow discharges the thread you named as well, instead of leaving it waiting for a supervisor whose\nround had just been taken away.\n\n`nxc tick` now says what a board is waiting for. A settled round whose only remaining blocker is a\nlive session reports `waiting_for_a_session` instead of `not_due` — the difference matters, because\nnothing re-checks that one on a clock and this verb is the way out.\n\nThe agent sidecar's reminder round is bounded (five minutes) and every `nxc` call it makes now\ncarries a timeout (thirty seconds). Both close the same shape: this teardown runs inside the process\nwhose EXIT an exclusive channel is waiting for, so one call that never returns holds a checkout and\nnot merely a process.\n- `changed` · A step of a `flow: sequential` channel that declares `working_tree: exclusive` now ends when its\nsession's process ends, not when its reply arrives. A member that answers and keeps working no\nlonger lets the next step start into the same checkout — measured in a real run, a coder answered\nat 00:16:33 and edited on until 00:34:27 while the next step had been running since 00:16:36.\nSessions report their own end (`nxc session ended`, called by the agent sidecar's teardown, and\n`Engine::session_ended` for a host that runs its own runtime); where that never arrives, the\nworker's process check answers the next time anything asks. **Nothing re-checks it on a clock**: the\nchannel's declared `timeout:` is a deadline for an *answer*, and a member that has replied has\nanswered — so if the announcement is lost (a sidecar killed before its teardown, an older `nxc`, a\nhost runtime that never wires `Engine::session_ended`) the round waits until somebody runs\n`nxc tick --thread `, which now says exactly that instead of \"nothing to do\". Channels without\nan exclusive working-copy claim are unchanged.\n\nEvery session that owes a reply is now told, in its system prompt, that its turn must end in one of\nexactly two ways — `nxc reply --thread \"\"` or `nxc reply --thread --escalate\n\"\"`. That obligation is delivered even to a persona declared `prime: false`, so\na declaration cannot leave out the one rule the engine depends on.", - "de": "### Geändert\n- Eine Sitzung, die endet, ohne den geschuldeten Faden zu beantworten, bekommt ihren Zug jetzt zurück\nund wird erinnert — einmal, mit dem konkreten Faden, beiden Arten einen Zug zu beenden, und jeder von\nihr beauftragten Runde, die noch offen ist —, bevor der Sidecar in ihrem Namen etwas postet. Die\nmeisten Sitzungen antworten dann. Eine, die erinnert wurde und erneut stumm endet, wird als\nEskalation zurückgegeben; `escalated: true` in `nxc status` sagt dem Aufrufer damit, dass es kein\nErgebnis gibt und jemand entscheiden muss.\n\nEine Eskalation ist jetzt in dem Text erkennbar, den die empfangende Sitzung ZUERST liest — auf allen\ndrei Wegen, die sie nehmen kann (Kanal-Durchreichung, 1:1-Wiederaufnahme, Quorum-Weckruf). Der\nHinweis sagt, was es ist, dass die Arbeitskopie währenddessen gehalten wird und was erwartet wird.\n`escalated: true` stand schon am Faden und half nicht: ein Agent liest die Nachricht, die ihn\ngeweckt hat, nicht `nxc status`.\n\n`nxc status` bekommt zwei Kennzeichen am Vorgang: `needs_decision` (irgendwo unter dieser Wurzel\nwurde eine Aufgabe zurückgegeben und niemand hat sie aufgenommen — neben `awaiting_human` zu lesen,\ndas genauso aussieht und das Gegenteil bedeutet) und `holds_working_tree` (irgendwo darunter wird\ndie Arbeitskopie gehalten, Sie wissen also, ob Sie überhaupt nachsehen müssen).\n\nEine Runde, deren sämtliche Beauftragungen zurückgenommen wurden, zählt nicht mehr als offener\nVorgang: `nxc withdraw` entlastet jetzt auch den genannten Faden, statt ihn auf einen Betreuer warten\nzu lassen, dem man gerade die Runde weggenommen hat.\n\n`nxc tick` sagt jetzt, worauf ein Board wartet. Eine erledigte Runde, deren einziger verbleibender\nBlocker eine lebende Sitzung ist, meldet `waiting_for_a_session` statt `not_due` — der Unterschied\nzählt, denn genau das prüft keine Uhr nach, und dieses Verb ist der Weg heraus.\n\nDie Erinnerungsrunde des Agent-Sidecars ist zeitlich begrenzt (fünf Minuten), und jeder `nxc`-Aufruf,\nden er macht, trägt jetzt ein Zeitlimit (dreißig Sekunden). Beides schließt dieselbe Form: dieser\nAbbau läuft in dem Prozess, auf dessen ENDE ein exklusiver Kanal wartet — ein Aufruf, der nie\nzurückkommt, hält also eine Arbeitskopie und nicht bloß einen Prozess.\n\n### Behoben\n- Ein Schritt eines `flow: sequential`-Kanals mit `working_tree: exclusive` endet jetzt, wenn der\nProzess seiner Sitzung endet — nicht, wenn seine Antwort eintrifft. Ein Mitglied, das antwortet und\nweiterarbeitet, lässt den nächsten Schritt nicht mehr in dieselbe Arbeitskopie starten; in einem\nechten Lauf gemessen, antwortete ein Coder um 00:16:33 und schrieb bis 00:34:27 weiter, während der\nNachfolger seit 00:16:36 lief. Sitzungen melden ihr Ende selbst (`nxc session ended`, vom\nAgent-Sidecar beim Abbau aufgerufen, und `Engine::session_ended` für einen Wirt mit eigener\nLaufzeit); bleibt das aus, antwortet die Prozessprüfung des Workers beim nächsten Mal, dass\nüberhaupt jemand fragt. **Eine Uhr prüft das nicht nach**: das deklarierte `timeout:` des Kanals ist\neine Frist für eine ANTWORT, und ein Mitglied, das geantwortet hat, hat geantwortet — geht die\nMeldung also verloren (ein vor dem Abbau getöteter Sidecar, ein älteres `nxc`, eine Wirt-Laufzeit\nohne `Engine::session_ended`), wartet die Runde, bis jemand `nxc tick --thread ` ausführt, das\njetzt genau das sagt statt „nichts zu tun\". Kanäle ohne exklusiven Anspruch auf die Arbeitskopie\nbleiben unverändert.\n\nJede Sitzung, die eine Antwort schuldet, erfährt jetzt in ihrem System-Prompt, dass ihr Zug auf\ngenau zwei Arten enden muss — `nxc reply --thread \"\"` oder `nxc reply --thread \n--escalate \"\"`. Diese Pflicht wird auch an eine Persona mit `prime: false`\nausgeliefert, damit eine Deklaration die eine Regel, auf die sich die Engine verlässt, nicht\nweglassen kann.\n\n### Facade-Kontrakt\n- `changed` · Eine Sitzung, die endet, ohne den geschuldeten Faden zu beantworten, bekommt ihren Zug jetzt zurück\nund wird erinnert — einmal, mit dem konkreten Faden, beiden Arten einen Zug zu beenden, und jeder von\nihr beauftragten Runde, die noch offen ist —, bevor der Sidecar in ihrem Namen etwas postet. Die\nmeisten Sitzungen antworten dann. Eine, die erinnert wurde und erneut stumm endet, wird als\nEskalation zurückgegeben; `escalated: true` in `nxc status` sagt dem Aufrufer damit, dass es kein\nErgebnis gibt und jemand entscheiden muss.\n\nEine Eskalation ist jetzt in dem Text erkennbar, den die empfangende Sitzung ZUERST liest — auf allen\ndrei Wegen, die sie nehmen kann (Kanal-Durchreichung, 1:1-Wiederaufnahme, Quorum-Weckruf). Der\nHinweis sagt, was es ist, dass die Arbeitskopie währenddessen gehalten wird und was erwartet wird.\n`escalated: true` stand schon am Faden und half nicht: ein Agent liest die Nachricht, die ihn\ngeweckt hat, nicht `nxc status`.\n\n`nxc status` bekommt zwei Kennzeichen am Vorgang: `needs_decision` (irgendwo unter dieser Wurzel\nwurde eine Aufgabe zurückgegeben und niemand hat sie aufgenommen — neben `awaiting_human` zu lesen,\ndas genauso aussieht und das Gegenteil bedeutet) und `holds_working_tree` (irgendwo darunter wird\ndie Arbeitskopie gehalten, Sie wissen also, ob Sie überhaupt nachsehen müssen).\n\nEine Runde, deren sämtliche Beauftragungen zurückgenommen wurden, zählt nicht mehr als offener\nVorgang: `nxc withdraw` entlastet jetzt auch den genannten Faden, statt ihn auf einen Betreuer warten\nzu lassen, dem man gerade die Runde weggenommen hat.\n\n`nxc tick` sagt jetzt, worauf ein Board wartet. Eine erledigte Runde, deren einziger verbleibender\nBlocker eine lebende Sitzung ist, meldet `waiting_for_a_session` statt `not_due` — der Unterschied\nzählt, denn genau das prüft keine Uhr nach, und dieses Verb ist der Weg heraus.\n\nDie Erinnerungsrunde des Agent-Sidecars ist zeitlich begrenzt (fünf Minuten), und jeder `nxc`-Aufruf,\nden er macht, trägt jetzt ein Zeitlimit (dreißig Sekunden). Beides schließt dieselbe Form: dieser\nAbbau läuft in dem Prozess, auf dessen ENDE ein exklusiver Kanal wartet — ein Aufruf, der nie\nzurückkommt, hält also eine Arbeitskopie und nicht bloß einen Prozess.\n- `changed` · Ein Schritt eines `flow: sequential`-Kanals mit `working_tree: exclusive` endet jetzt, wenn der\nProzess seiner Sitzung endet — nicht, wenn seine Antwort eintrifft. Ein Mitglied, das antwortet und\nweiterarbeitet, lässt den nächsten Schritt nicht mehr in dieselbe Arbeitskopie starten; in einem\nechten Lauf gemessen, antwortete ein Coder um 00:16:33 und schrieb bis 00:34:27 weiter, während der\nNachfolger seit 00:16:36 lief. Sitzungen melden ihr Ende selbst (`nxc session ended`, vom\nAgent-Sidecar beim Abbau aufgerufen, und `Engine::session_ended` für einen Wirt mit eigener\nLaufzeit); bleibt das aus, antwortet die Prozessprüfung des Workers beim nächsten Mal, dass\nüberhaupt jemand fragt. **Eine Uhr prüft das nicht nach**: das deklarierte `timeout:` des Kanals ist\neine Frist für eine ANTWORT, und ein Mitglied, das geantwortet hat, hat geantwortet — geht die\nMeldung also verloren (ein vor dem Abbau getöteter Sidecar, ein älteres `nxc`, eine Wirt-Laufzeit\nohne `Engine::session_ended`), wartet die Runde, bis jemand `nxc tick --thread ` ausführt, das\njetzt genau das sagt statt „nichts zu tun\". Kanäle ohne exklusiven Anspruch auf die Arbeitskopie\nbleiben unverändert.\n\nJede Sitzung, die eine Antwort schuldet, erfährt jetzt in ihrem System-Prompt, dass ihr Zug auf\ngenau zwei Arten enden muss — `nxc reply --thread \"\"` oder `nxc reply --thread \n--escalate \"\"`. Diese Pflicht wird auch an eine Persona mit `prime: false`\nausgeliefert, damit eine Deklaration die eine Regel, auf die sich die Engine verlässt, nicht\nweglassen kann." - } - }, - { - "version": "0.64.0", - "date": "2026-08-23", - "items": [ - { - "type": "added", - "en": "`Engine::withdraw` — an embedding app can now take back a commission that is still parked behind\nthe working copy, the same narrow case `nxc withdraw` covers. The seam already handed out\n`queue_position`/`queued_behind` on a send receipt (\"not started, 3rd in line\") with no call to\nanswer them; this closes that.", - "de": "`Engine::withdraw` — eine einbettende App kann einen Auftrag, der noch vor der Arbeitskopie\nwartet, jetzt zurücknehmen; derselbe enge Fall, den `nxc withdraw` abdeckt. Die Naht gab auf dem\nSende-Beleg schon `queue_position`/`queued_behind` aus („nicht gestartet, Platz 3\") — ohne einen\nAufruf, mit dem man darauf hätte antworten können. Das ist damit geschlossen.", - "facade": "changed" - }, - { - "type": "added", - "en": "**`nxm guide` — memory has documentation now.** Five guides, in English and German, shipped inside\nthe binary and published to the docs page: an introduction, the core concepts (keys and auto-keys,\ncategory/reach/references, the retrieval rule that decides where a memory is read, reading order,\nthe reversible tombstone), the complete command reference with every `--json` shape, the `memory_*`\nMCP tools and what `nxs prime` contributes, and how to bring an existing Claude memory store in.\nUntil now `nxm` had `--help` and nothing else, and nxsflow.com/open-source/docs showed two building\nblocks where the suite promises three.", - "de": "**`nxm guide` — memory hat jetzt eine Anleitung.** Fünf Kapitel auf Deutsch und Englisch, im Binary\nmitgeliefert und auf der Doku-Seite veröffentlicht: eine Einleitung, die Kernkonzepte (Schlüssel und\nAuto-Schlüssel, Kategorie/Reichweite/Referenzen, die Abrufregel, die entscheidet, wo eine Erinnerung\ngelesen wird, die Lesereihenfolge, der umkehrbare Grabstein), die vollständige Befehlsreferenz mit\njeder `--json`-Form, die `memory_*`-MCP-Werkzeuge samt dem, was `nxs prime` beiträgt, und der Weg,\neinen vorhandenen Claude-Gedächtnisspeicher zu übernehmen. Bisher hatte `nxm` nur `--help`, und\nnxsflow.com/open-source/docs zeigte zwei Bausteine, wo die Suite drei verspricht." - } - ], - "notes": { - "en": "### Added\n- `Engine::withdraw` — an embedding app can now take back a commission that is still parked behind\nthe working copy, the same narrow case `nxc withdraw` covers. The seam already handed out\n`queue_position`/`queued_behind` on a send receipt (\"not started, 3rd in line\") with no call to\nanswer them; this closes that.\n- **`nxm guide` — memory has documentation now.** Five guides, in English and German, shipped inside\nthe binary and published to the docs page: an introduction, the core concepts (keys and auto-keys,\ncategory/reach/references, the retrieval rule that decides where a memory is read, reading order,\nthe reversible tombstone), the complete command reference with every `--json` shape, the `memory_*`\nMCP tools and what `nxs prime` contributes, and how to bring an existing Claude memory store in.\nUntil now `nxm` had `--help` and nothing else, and nxsflow.com/open-source/docs showed two building\nblocks where the suite promises three.\n\n### Facade Contract\n- `changed` · `Engine::withdraw` — an embedding app can now take back a commission that is still parked behind\nthe working copy, the same narrow case `nxc withdraw` covers. The seam already handed out\n`queue_position`/`queued_behind` on a send receipt (\"not started, 3rd in line\") with no call to\nanswer them; this closes that.", - "de": "### Neu\n- `Engine::withdraw` — eine einbettende App kann einen Auftrag, der noch vor der Arbeitskopie\nwartet, jetzt zurücknehmen; derselbe enge Fall, den `nxc withdraw` abdeckt. Die Naht gab auf dem\nSende-Beleg schon `queue_position`/`queued_behind` aus („nicht gestartet, Platz 3\") — ohne einen\nAufruf, mit dem man darauf hätte antworten können. Das ist damit geschlossen.\n- **`nxm guide` — memory hat jetzt eine Anleitung.** Fünf Kapitel auf Deutsch und Englisch, im Binary\nmitgeliefert und auf der Doku-Seite veröffentlicht: eine Einleitung, die Kernkonzepte (Schlüssel und\nAuto-Schlüssel, Kategorie/Reichweite/Referenzen, die Abrufregel, die entscheidet, wo eine Erinnerung\ngelesen wird, die Lesereihenfolge, der umkehrbare Grabstein), die vollständige Befehlsreferenz mit\njeder `--json`-Form, die `memory_*`-MCP-Werkzeuge samt dem, was `nxs prime` beiträgt, und der Weg,\neinen vorhandenen Claude-Gedächtnisspeicher zu übernehmen. Bisher hatte `nxm` nur `--help`, und\nnxsflow.com/open-source/docs zeigte zwei Bausteine, wo die Suite drei verspricht.\n\n### Facade-Kontrakt\n- `changed` · `Engine::withdraw` — eine einbettende App kann einen Auftrag, der noch vor der Arbeitskopie\nwartet, jetzt zurücknehmen; derselbe enge Fall, den `nxc withdraw` abdeckt. Die Naht gab auf dem\nSende-Beleg schon `queue_position`/`queued_behind` aus („nicht gestartet, Platz 3\") — ohne einen\nAufruf, mit dem man darauf hätte antworten können. Das ist damit geschlossen." - } - }, - { - "version": "0.63.0", - "date": "2026-08-22", - "items": [ - { - "type": "fixed", - "en": "**One internal session, one process.** A wake that arrived while the session it named was still working started a SECOND agent process on the same spec file, in the same working directory, under the same session id — and then a third and a fourth, because every new process produced events that woke again. The two overwrote each other's edits with no error and no warning. `nxc` now refuses the second start while the first is alive, reports it as `already_running`, and never rewrites the spec a running process is still reading.\nA persona session that DIES is now machine-readable as such: the sidecar's teardown hands the task back with `--escalate` instead of posting an ordinary reply, so `nxc status` carries `escalated: true` and a crash is no longer indistinguishable from a delivered answer. On a `summarize` channel the abort is passed through instead of being folded into the summary.\nWhoever opened an operation can now read its whole thread tree, across the channel borders an agent opened along the way — `nxc status` always showed the shape; the messages were `forbidden` from the first channel onward. The channel's declared `visibility` still decides what is shown inside a thread.\nA declared channel's `members:` is now the membership for real: an edit takes effect on the next READ instead of on the next `send`, in both directions, so `nxc list` and the actual access gate can no longer disagree.\nThe error `nxc send --to` gives for a channel that exists but no declaration names had two runs of stray whitespace in the middle of its sentences; it reads as one sentence again.\n**Facade contract.** `nexus_chat::channel::declared_visibility` is gone — one lookup now resolves a declared channel's visibility AND its `members:` together, as `channel::declared_policy` returning a `ChannelPolicy`. `facade::thread`/`thread_board` take that policy instead of a bare `Visibility`; a caller passing a `Visibility` still compiles unchanged (`From for ChannelPolicy`). `Engine`'s own methods are untouched.", - "de": "**Eine interne Sitzung, ein Prozess.** Ein Wecken, das eintraf, während die genannte Sitzung noch arbeitete, startete einen ZWEITEN Agentenprozess auf derselben Spec-Datei, im selben Arbeitsverzeichnis, unter derselben Sitzungs-Id — und dann einen dritten und vierten, weil jeder neue Prozess Ereignisse erzeugte, die erneut weckten. Beide überschrieben sich gegenseitig die Änderungen, ohne Fehler und ohne Warnung. `nxc` lehnt den zweiten Start jetzt ab, solange der erste lebt, meldet ihn als `already_running` und schreibt die Spec eines laufenden Prozesses nicht mehr unter ihm weg.\nEine Persona-Sitzung, die STIRBT, ist jetzt maschinell als solche erkennbar: der Abbau des Sidecars gibt die Aufgabe mit `--escalate` zurück statt eine gewöhnliche Antwort zu posten, `nxc status` trägt `escalated: true`, und ein Absturz ist nicht länger von einer gelieferten Antwort ununterscheidbar. Auf einem `summarize`-Kanal wird der Abbruch durchgereicht statt in die Zusammenfassung gefaltet.\nWer eine Operation eröffnet hat, kann jetzt ihren ganzen Fadenbaum lesen — über die Kanalgrenzen hinweg, die ein Agent im Verlauf eröffnet hat. `nxc status` zeigte die Form schon immer; die Nachrichten waren ab dem ersten Kanal `forbidden`. Was innerhalb eines Fadens sichtbar ist, entscheidet weiterhin die deklarierte `visibility` des Kanals.\nDie `members:` eines deklarierten Kanals sind jetzt wirklich die Mitgliedschaft: eine Änderung wirkt beim nächsten LESEN statt beim nächsten `send`, in beide Richtungen — `nxc list` und das tatsächliche Zugriffstor können nicht mehr auseinanderlaufen.\nDie Fehlermeldung, die `nxc send --to` für einen Kanal gibt, den es zwar gibt, den aber keine Deklaration nennt, hatte zwei Lücken aus verirrten Leerzeichen mitten im Satz; sie liest sich wieder als ein Satz.\n**Facade-Kontrakt.** `nexus_chat::channel::declared_visibility` ist entfallen — ein Lookup löst jetzt Sichtbarkeit UND `members:` eines deklarierten Kanals gemeinsam auf, als `channel::declared_policy` mit `ChannelPolicy`. `facade::thread`/`thread_board` nehmen diese Politik statt einer blanken `Visibility`; ein Aufrufer, der eine `Visibility` übergibt, kompiliert unverändert (`From for ChannelPolicy`). Die Methoden von `Engine` selbst sind unberührt.", - "facade": "breaking" - }, - { - "type": "changed", - "en": "A declared channel `timeout:` that cannot fire now SAYS so. `nxc` checks whether the platform will actually run the one-shot job before submitting one, and reports a `tick_unscheduled` entry in the receipt's `warnings` when it will not — naming the board and `nxc tick --thread `, the same check callable by hand. It is deliberately not a non-zero exit: the board is open and its members are running; what is missing is the safety net. **On macOS this is every timed board**: `atrun` ships disabled, so `at` accepts a job, exits 0 and prints a job id while nothing ever runs it. Both guides now state the condition where `timeout:` is described. As a side effect, a `send`/`reply` on a timed thread no longer pays up to eight seconds waiting on an `at` that cannot help it.\n`address_book:` has three states now, exactly like `tools:`: an omitted key still means \"not written down\" and shows the whole declared team, and `address_book: []` now means \"commissions nothing\" — the declaration for a pure reviewer or any role at a leaf of the tree, which previously had to be a sentence in the system prompt. A persona is also no longer offered the channel it is itself a member of.\n**Facade contract.** `nexus_chat::timer::AtTimer` is no longer a unit struct — it carries the probe's verdict, so build it with `AtTimer::new()` (or, as before, let `TimerConfig::At` build it). `RoleDecl.address_book` is `Option>` instead of `Vec<_>`, which is what makes the three states expressible. `orchestration::channel_open` returns `ChannelOpened` (the board plus its warnings) instead of a bare `AskReceipt`; `Engine` does not carry that verb.", - "de": "Ein deklariertes Kanal-`timeout:`, das nicht feuern kann, SAGT das jetzt. `nxc` prüft vor dem Einreichen, ob die Plattform den Einmal-Job überhaupt ausführen wird, und meldet andernfalls einen Eintrag `tick_unscheduled` im `warnings`-Feld der Quittung — mit dem betroffenen Brett und `nxc tick --thread `, derselben Prüfung von Hand. Ein von Null verschiedener Exit-Code ist es bewusst nicht: das Brett ist offen und seine Mitglieder laufen, es fehlt die Sicherung. **Auf macOS betrifft das jedes Brett mit Frist**: `atrun` wird deaktiviert ausgeliefert, `at` nimmt den Job an, endet mit 0 und druckt eine Job-Nummer, während ihn niemand ausführt. Beide Anleitungen nennen die Bedingung jetzt dort, wo `timeout:` beschrieben wird. Nebenbei kostet ein `send`/`reply` auf einem Faden mit Frist nicht mehr bis zu acht Sekunden Wartezeit auf ein `at`, das nicht helfen kann.\n`address_book:` hat jetzt drei Zustände, genau wie `tools:`: ein fehlender Schlüssel heißt weiterhin „nicht aufgeschrieben\" und zeigt das ganze deklarierte Team, und `address_book: []` heißt jetzt „beauftragt nichts\" — die Deklaration für einen reinen Prüfer oder jede Rolle am Blatt des Baums, für die bisher nur ein Satz im System-Prompt blieb. Außerdem wird einer Persona nicht länger der Kanal angeboten, in dem sie selbst Mitglied ist.\n**Facade-Kontrakt.** `nexus_chat::timer::AtTimer` ist kein Unit-Struct mehr — es trägt das Ergebnis der Prüfung, wird also mit `AtTimer::new()` gebaut (oder wie bisher von `TimerConfig::At`). `RoleDecl.address_book` ist `Option>` statt `Vec<_>`; genau das macht die drei Zustände ausdrückbar. `orchestration::channel_open` liefert `ChannelOpened` (das Brett samt seiner Warnungen) statt eines blanken `AskReceipt`; `Engine` führt dieses Verb nicht.", - "facade": "breaking" - }, - { - "type": "added", - "en": "`nxc withdraw --thread ` takes back a commission that is still waiting for the working copy — one that never started. A persona or channel declaring `working_tree: exclusive` runs one chain at a time, and a commission that arrives while another is holding is parked until the holder lets go; until then there is no session, no transcript and no model call, so taking it back loses nothing. Every parked commission under the named thread is withdrawn and each waiting thread is discharged with a message saying who took it back. Only a caller who can READ the board may withdraw what is parked on it. It does not stop a round that has started — one that begins between reading the queue and writing to it is reported as `started_meanwhile` and left alone.\n`nxc guide limits-and-safety` now says what to do INSTEAD of putting a secret in a message body: the persona reads it from the platform's own secret store, and the message names at most the location.", - "de": "`nxc withdraw --thread ` nimmt eine Beauftragung zurück, die noch auf die Arbeitskopie wartet — eine, die nie startete. Eine Persona oder ein Kanal mit `working_tree: exclusive` lässt eine Kette zur Zeit laufen; eine Beauftragung, die eintrifft, während eine andere hält, wird geparkt, bis die Halterin loslässt. Bis dahin gibt es keine Sitzung, keinen Verlauf und keinen Modellaufruf — das Zurücknehmen verliert also nichts. Jede geparkte Beauftragung unter dem genannten Faden wird zurückgezogen, und jeder wartende Faden wird mit einer Nachricht entlastet, die sagt, wer sie zurückgenommen hat. Zurücknehmen darf nur, wer das Brett auch lesen kann. Eine bereits gestartete Runde hält es nicht an — eine, die zwischen dem Lesen der Warteschlange und dem Schreiben beginnt, wird als `started_meanwhile` gemeldet und in Ruhe gelassen.\n`nxc guide limits-and-safety` sagt jetzt, was STATTDESSEN gilt, wenn ein Geheimnis nicht in einen Nachrichtentext gehört: die Persona liest es aus dem Geheimnisspeicher der Plattform, und die Nachricht nennt höchstens den Ort.", - "facade": "changed" - } - ], - "notes": { - "en": "### Added\n- `nxc withdraw --thread ` takes back a commission that is still waiting for the working copy — one that never started. A persona or channel declaring `working_tree: exclusive` runs one chain at a time, and a commission that arrives while another is holding is parked until the holder lets go; until then there is no session, no transcript and no model call, so taking it back loses nothing. Every parked commission under the named thread is withdrawn and each waiting thread is discharged with a message saying who took it back. Only a caller who can READ the board may withdraw what is parked on it. It does not stop a round that has started — one that begins between reading the queue and writing to it is reported as `started_meanwhile` and left alone.\n`nxc guide limits-and-safety` now says what to do INSTEAD of putting a secret in a message body: the persona reads it from the platform's own secret store, and the message names at most the location.\n\n### Changed\n- A declared channel `timeout:` that cannot fire now SAYS so. `nxc` checks whether the platform will actually run the one-shot job before submitting one, and reports a `tick_unscheduled` entry in the receipt's `warnings` when it will not — naming the board and `nxc tick --thread `, the same check callable by hand. It is deliberately not a non-zero exit: the board is open and its members are running; what is missing is the safety net. **On macOS this is every timed board**: `atrun` ships disabled, so `at` accepts a job, exits 0 and prints a job id while nothing ever runs it. Both guides now state the condition where `timeout:` is described. As a side effect, a `send`/`reply` on a timed thread no longer pays up to eight seconds waiting on an `at` that cannot help it.\n`address_book:` has three states now, exactly like `tools:`: an omitted key still means \"not written down\" and shows the whole declared team, and `address_book: []` now means \"commissions nothing\" — the declaration for a pure reviewer or any role at a leaf of the tree, which previously had to be a sentence in the system prompt. A persona is also no longer offered the channel it is itself a member of.\n**Facade contract.** `nexus_chat::timer::AtTimer` is no longer a unit struct — it carries the probe's verdict, so build it with `AtTimer::new()` (or, as before, let `TimerConfig::At` build it). `RoleDecl.address_book` is `Option>` instead of `Vec<_>`, which is what makes the three states expressible. `orchestration::channel_open` returns `ChannelOpened` (the board plus its warnings) instead of a bare `AskReceipt`; `Engine` does not carry that verb.\n\n### Fixed\n- **One internal session, one process.** A wake that arrived while the session it named was still working started a SECOND agent process on the same spec file, in the same working directory, under the same session id — and then a third and a fourth, because every new process produced events that woke again. The two overwrote each other's edits with no error and no warning. `nxc` now refuses the second start while the first is alive, reports it as `already_running`, and never rewrites the spec a running process is still reading.\nA persona session that DIES is now machine-readable as such: the sidecar's teardown hands the task back with `--escalate` instead of posting an ordinary reply, so `nxc status` carries `escalated: true` and a crash is no longer indistinguishable from a delivered answer. On a `summarize` channel the abort is passed through instead of being folded into the summary.\nWhoever opened an operation can now read its whole thread tree, across the channel borders an agent opened along the way — `nxc status` always showed the shape; the messages were `forbidden` from the first channel onward. The channel's declared `visibility` still decides what is shown inside a thread.\nA declared channel's `members:` is now the membership for real: an edit takes effect on the next READ instead of on the next `send`, in both directions, so `nxc list` and the actual access gate can no longer disagree.\nThe error `nxc send --to` gives for a channel that exists but no declaration names had two runs of stray whitespace in the middle of its sentences; it reads as one sentence again.\n**Facade contract.** `nexus_chat::channel::declared_visibility` is gone — one lookup now resolves a declared channel's visibility AND its `members:` together, as `channel::declared_policy` returning a `ChannelPolicy`. `facade::thread`/`thread_board` take that policy instead of a bare `Visibility`; a caller passing a `Visibility` still compiles unchanged (`From for ChannelPolicy`). `Engine`'s own methods are untouched.\n\n### Facade Contract\n- `breaking` · **One internal session, one process.** A wake that arrived while the session it named was still working started a SECOND agent process on the same spec file, in the same working directory, under the same session id — and then a third and a fourth, because every new process produced events that woke again. The two overwrote each other's edits with no error and no warning. `nxc` now refuses the second start while the first is alive, reports it as `already_running`, and never rewrites the spec a running process is still reading.\nA persona session that DIES is now machine-readable as such: the sidecar's teardown hands the task back with `--escalate` instead of posting an ordinary reply, so `nxc status` carries `escalated: true` and a crash is no longer indistinguishable from a delivered answer. On a `summarize` channel the abort is passed through instead of being folded into the summary.\nWhoever opened an operation can now read its whole thread tree, across the channel borders an agent opened along the way — `nxc status` always showed the shape; the messages were `forbidden` from the first channel onward. The channel's declared `visibility` still decides what is shown inside a thread.\nA declared channel's `members:` is now the membership for real: an edit takes effect on the next READ instead of on the next `send`, in both directions, so `nxc list` and the actual access gate can no longer disagree.\nThe error `nxc send --to` gives for a channel that exists but no declaration names had two runs of stray whitespace in the middle of its sentences; it reads as one sentence again.\n**Facade contract.** `nexus_chat::channel::declared_visibility` is gone — one lookup now resolves a declared channel's visibility AND its `members:` together, as `channel::declared_policy` returning a `ChannelPolicy`. `facade::thread`/`thread_board` take that policy instead of a bare `Visibility`; a caller passing a `Visibility` still compiles unchanged (`From for ChannelPolicy`). `Engine`'s own methods are untouched.\n- `breaking` · A declared channel `timeout:` that cannot fire now SAYS so. `nxc` checks whether the platform will actually run the one-shot job before submitting one, and reports a `tick_unscheduled` entry in the receipt's `warnings` when it will not — naming the board and `nxc tick --thread `, the same check callable by hand. It is deliberately not a non-zero exit: the board is open and its members are running; what is missing is the safety net. **On macOS this is every timed board**: `atrun` ships disabled, so `at` accepts a job, exits 0 and prints a job id while nothing ever runs it. Both guides now state the condition where `timeout:` is described. As a side effect, a `send`/`reply` on a timed thread no longer pays up to eight seconds waiting on an `at` that cannot help it.\n`address_book:` has three states now, exactly like `tools:`: an omitted key still means \"not written down\" and shows the whole declared team, and `address_book: []` now means \"commissions nothing\" — the declaration for a pure reviewer or any role at a leaf of the tree, which previously had to be a sentence in the system prompt. A persona is also no longer offered the channel it is itself a member of.\n**Facade contract.** `nexus_chat::timer::AtTimer` is no longer a unit struct — it carries the probe's verdict, so build it with `AtTimer::new()` (or, as before, let `TimerConfig::At` build it). `RoleDecl.address_book` is `Option>` instead of `Vec<_>`, which is what makes the three states expressible. `orchestration::channel_open` returns `ChannelOpened` (the board plus its warnings) instead of a bare `AskReceipt`; `Engine` does not carry that verb.\n- `changed` · `nxc withdraw --thread ` takes back a commission that is still waiting for the working copy — one that never started. A persona or channel declaring `working_tree: exclusive` runs one chain at a time, and a commission that arrives while another is holding is parked until the holder lets go; until then there is no session, no transcript and no model call, so taking it back loses nothing. Every parked commission under the named thread is withdrawn and each waiting thread is discharged with a message saying who took it back. Only a caller who can READ the board may withdraw what is parked on it. It does not stop a round that has started — one that begins between reading the queue and writing to it is reported as `started_meanwhile` and left alone.\n`nxc guide limits-and-safety` now says what to do INSTEAD of putting a secret in a message body: the persona reads it from the platform's own secret store, and the message names at most the location.", - "de": "### Neu\n- `nxc withdraw --thread ` nimmt eine Beauftragung zurück, die noch auf die Arbeitskopie wartet — eine, die nie startete. Eine Persona oder ein Kanal mit `working_tree: exclusive` lässt eine Kette zur Zeit laufen; eine Beauftragung, die eintrifft, während eine andere hält, wird geparkt, bis die Halterin loslässt. Bis dahin gibt es keine Sitzung, keinen Verlauf und keinen Modellaufruf — das Zurücknehmen verliert also nichts. Jede geparkte Beauftragung unter dem genannten Faden wird zurückgezogen, und jeder wartende Faden wird mit einer Nachricht entlastet, die sagt, wer sie zurückgenommen hat. Zurücknehmen darf nur, wer das Brett auch lesen kann. Eine bereits gestartete Runde hält es nicht an — eine, die zwischen dem Lesen der Warteschlange und dem Schreiben beginnt, wird als `started_meanwhile` gemeldet und in Ruhe gelassen.\n`nxc guide limits-and-safety` sagt jetzt, was STATTDESSEN gilt, wenn ein Geheimnis nicht in einen Nachrichtentext gehört: die Persona liest es aus dem Geheimnisspeicher der Plattform, und die Nachricht nennt höchstens den Ort.\n\n### Geändert\n- Ein deklariertes Kanal-`timeout:`, das nicht feuern kann, SAGT das jetzt. `nxc` prüft vor dem Einreichen, ob die Plattform den Einmal-Job überhaupt ausführen wird, und meldet andernfalls einen Eintrag `tick_unscheduled` im `warnings`-Feld der Quittung — mit dem betroffenen Brett und `nxc tick --thread `, derselben Prüfung von Hand. Ein von Null verschiedener Exit-Code ist es bewusst nicht: das Brett ist offen und seine Mitglieder laufen, es fehlt die Sicherung. **Auf macOS betrifft das jedes Brett mit Frist**: `atrun` wird deaktiviert ausgeliefert, `at` nimmt den Job an, endet mit 0 und druckt eine Job-Nummer, während ihn niemand ausführt. Beide Anleitungen nennen die Bedingung jetzt dort, wo `timeout:` beschrieben wird. Nebenbei kostet ein `send`/`reply` auf einem Faden mit Frist nicht mehr bis zu acht Sekunden Wartezeit auf ein `at`, das nicht helfen kann.\n`address_book:` hat jetzt drei Zustände, genau wie `tools:`: ein fehlender Schlüssel heißt weiterhin „nicht aufgeschrieben\" und zeigt das ganze deklarierte Team, und `address_book: []` heißt jetzt „beauftragt nichts\" — die Deklaration für einen reinen Prüfer oder jede Rolle am Blatt des Baums, für die bisher nur ein Satz im System-Prompt blieb. Außerdem wird einer Persona nicht länger der Kanal angeboten, in dem sie selbst Mitglied ist.\n**Facade-Kontrakt.** `nexus_chat::timer::AtTimer` ist kein Unit-Struct mehr — es trägt das Ergebnis der Prüfung, wird also mit `AtTimer::new()` gebaut (oder wie bisher von `TimerConfig::At`). `RoleDecl.address_book` ist `Option>` statt `Vec<_>`; genau das macht die drei Zustände ausdrückbar. `orchestration::channel_open` liefert `ChannelOpened` (das Brett samt seiner Warnungen) statt eines blanken `AskReceipt`; `Engine` führt dieses Verb nicht.\n\n### Behoben\n- **Eine interne Sitzung, ein Prozess.** Ein Wecken, das eintraf, während die genannte Sitzung noch arbeitete, startete einen ZWEITEN Agentenprozess auf derselben Spec-Datei, im selben Arbeitsverzeichnis, unter derselben Sitzungs-Id — und dann einen dritten und vierten, weil jeder neue Prozess Ereignisse erzeugte, die erneut weckten. Beide überschrieben sich gegenseitig die Änderungen, ohne Fehler und ohne Warnung. `nxc` lehnt den zweiten Start jetzt ab, solange der erste lebt, meldet ihn als `already_running` und schreibt die Spec eines laufenden Prozesses nicht mehr unter ihm weg.\nEine Persona-Sitzung, die STIRBT, ist jetzt maschinell als solche erkennbar: der Abbau des Sidecars gibt die Aufgabe mit `--escalate` zurück statt eine gewöhnliche Antwort zu posten, `nxc status` trägt `escalated: true`, und ein Absturz ist nicht länger von einer gelieferten Antwort ununterscheidbar. Auf einem `summarize`-Kanal wird der Abbruch durchgereicht statt in die Zusammenfassung gefaltet.\nWer eine Operation eröffnet hat, kann jetzt ihren ganzen Fadenbaum lesen — über die Kanalgrenzen hinweg, die ein Agent im Verlauf eröffnet hat. `nxc status` zeigte die Form schon immer; die Nachrichten waren ab dem ersten Kanal `forbidden`. Was innerhalb eines Fadens sichtbar ist, entscheidet weiterhin die deklarierte `visibility` des Kanals.\nDie `members:` eines deklarierten Kanals sind jetzt wirklich die Mitgliedschaft: eine Änderung wirkt beim nächsten LESEN statt beim nächsten `send`, in beide Richtungen — `nxc list` und das tatsächliche Zugriffstor können nicht mehr auseinanderlaufen.\nDie Fehlermeldung, die `nxc send --to` für einen Kanal gibt, den es zwar gibt, den aber keine Deklaration nennt, hatte zwei Lücken aus verirrten Leerzeichen mitten im Satz; sie liest sich wieder als ein Satz.\n**Facade-Kontrakt.** `nexus_chat::channel::declared_visibility` ist entfallen — ein Lookup löst jetzt Sichtbarkeit UND `members:` eines deklarierten Kanals gemeinsam auf, als `channel::declared_policy` mit `ChannelPolicy`. `facade::thread`/`thread_board` nehmen diese Politik statt einer blanken `Visibility`; ein Aufrufer, der eine `Visibility` übergibt, kompiliert unverändert (`From for ChannelPolicy`). Die Methoden von `Engine` selbst sind unberührt.\n\n### Facade-Kontrakt\n- `breaking` · **Eine interne Sitzung, ein Prozess.** Ein Wecken, das eintraf, während die genannte Sitzung noch arbeitete, startete einen ZWEITEN Agentenprozess auf derselben Spec-Datei, im selben Arbeitsverzeichnis, unter derselben Sitzungs-Id — und dann einen dritten und vierten, weil jeder neue Prozess Ereignisse erzeugte, die erneut weckten. Beide überschrieben sich gegenseitig die Änderungen, ohne Fehler und ohne Warnung. `nxc` lehnt den zweiten Start jetzt ab, solange der erste lebt, meldet ihn als `already_running` und schreibt die Spec eines laufenden Prozesses nicht mehr unter ihm weg.\nEine Persona-Sitzung, die STIRBT, ist jetzt maschinell als solche erkennbar: der Abbau des Sidecars gibt die Aufgabe mit `--escalate` zurück statt eine gewöhnliche Antwort zu posten, `nxc status` trägt `escalated: true`, und ein Absturz ist nicht länger von einer gelieferten Antwort ununterscheidbar. Auf einem `summarize`-Kanal wird der Abbruch durchgereicht statt in die Zusammenfassung gefaltet.\nWer eine Operation eröffnet hat, kann jetzt ihren ganzen Fadenbaum lesen — über die Kanalgrenzen hinweg, die ein Agent im Verlauf eröffnet hat. `nxc status` zeigte die Form schon immer; die Nachrichten waren ab dem ersten Kanal `forbidden`. Was innerhalb eines Fadens sichtbar ist, entscheidet weiterhin die deklarierte `visibility` des Kanals.\nDie `members:` eines deklarierten Kanals sind jetzt wirklich die Mitgliedschaft: eine Änderung wirkt beim nächsten LESEN statt beim nächsten `send`, in beide Richtungen — `nxc list` und das tatsächliche Zugriffstor können nicht mehr auseinanderlaufen.\nDie Fehlermeldung, die `nxc send --to` für einen Kanal gibt, den es zwar gibt, den aber keine Deklaration nennt, hatte zwei Lücken aus verirrten Leerzeichen mitten im Satz; sie liest sich wieder als ein Satz.\n**Facade-Kontrakt.** `nexus_chat::channel::declared_visibility` ist entfallen — ein Lookup löst jetzt Sichtbarkeit UND `members:` eines deklarierten Kanals gemeinsam auf, als `channel::declared_policy` mit `ChannelPolicy`. `facade::thread`/`thread_board` nehmen diese Politik statt einer blanken `Visibility`; ein Aufrufer, der eine `Visibility` übergibt, kompiliert unverändert (`From for ChannelPolicy`). Die Methoden von `Engine` selbst sind unberührt.\n- `breaking` · Ein deklariertes Kanal-`timeout:`, das nicht feuern kann, SAGT das jetzt. `nxc` prüft vor dem Einreichen, ob die Plattform den Einmal-Job überhaupt ausführen wird, und meldet andernfalls einen Eintrag `tick_unscheduled` im `warnings`-Feld der Quittung — mit dem betroffenen Brett und `nxc tick --thread `, derselben Prüfung von Hand. Ein von Null verschiedener Exit-Code ist es bewusst nicht: das Brett ist offen und seine Mitglieder laufen, es fehlt die Sicherung. **Auf macOS betrifft das jedes Brett mit Frist**: `atrun` wird deaktiviert ausgeliefert, `at` nimmt den Job an, endet mit 0 und druckt eine Job-Nummer, während ihn niemand ausführt. Beide Anleitungen nennen die Bedingung jetzt dort, wo `timeout:` beschrieben wird. Nebenbei kostet ein `send`/`reply` auf einem Faden mit Frist nicht mehr bis zu acht Sekunden Wartezeit auf ein `at`, das nicht helfen kann.\n`address_book:` hat jetzt drei Zustände, genau wie `tools:`: ein fehlender Schlüssel heißt weiterhin „nicht aufgeschrieben\" und zeigt das ganze deklarierte Team, und `address_book: []` heißt jetzt „beauftragt nichts\" — die Deklaration für einen reinen Prüfer oder jede Rolle am Blatt des Baums, für die bisher nur ein Satz im System-Prompt blieb. Außerdem wird einer Persona nicht länger der Kanal angeboten, in dem sie selbst Mitglied ist.\n**Facade-Kontrakt.** `nexus_chat::timer::AtTimer` ist kein Unit-Struct mehr — es trägt das Ergebnis der Prüfung, wird also mit `AtTimer::new()` gebaut (oder wie bisher von `TimerConfig::At`). `RoleDecl.address_book` ist `Option>` statt `Vec<_>`; genau das macht die drei Zustände ausdrückbar. `orchestration::channel_open` liefert `ChannelOpened` (das Brett samt seiner Warnungen) statt eines blanken `AskReceipt`; `Engine` führt dieses Verb nicht.\n- `changed` · `nxc withdraw --thread ` nimmt eine Beauftragung zurück, die noch auf die Arbeitskopie wartet — eine, die nie startete. Eine Persona oder ein Kanal mit `working_tree: exclusive` lässt eine Kette zur Zeit laufen; eine Beauftragung, die eintrifft, während eine andere hält, wird geparkt, bis die Halterin loslässt. Bis dahin gibt es keine Sitzung, keinen Verlauf und keinen Modellaufruf — das Zurücknehmen verliert also nichts. Jede geparkte Beauftragung unter dem genannten Faden wird zurückgezogen, und jeder wartende Faden wird mit einer Nachricht entlastet, die sagt, wer sie zurückgenommen hat. Zurücknehmen darf nur, wer das Brett auch lesen kann. Eine bereits gestartete Runde hält es nicht an — eine, die zwischen dem Lesen der Warteschlange und dem Schreiben beginnt, wird als `started_meanwhile` gemeldet und in Ruhe gelassen.\n`nxc guide limits-and-safety` sagt jetzt, was STATTDESSEN gilt, wenn ein Geheimnis nicht in einen Nachrichtentext gehört: die Persona liest es aus dem Geheimnisspeicher der Plattform, und die Nachricht nennt höchstens den Ort." - } - }, - { - "version": "0.62.0", - "date": "2026-08-22", - "items": [ - { - "type": "fixed", - "en": "`nxc guide personas`: the \"declared but not yet read\" section said `session: fresh | continue` was\ninert because \"every persona's turn currently starts a fresh session\". The field is inert; the\nreason was false, and an agent that looked it up built five persona declarations on it. Both\nlanguage editions now state the real rule — the CALL decides, not the declaration: `nxc send --to\n` mints a fresh session, and `nxc reply --thread` into that persona's own thread resumes\nthe one it already has. The same section's `claude_md: override` entry is corrected too: it is\nonly half unread, since declaring it composes the persona WITHOUT the project's `CLAUDE.md`,\nexactly as `ignore` does.", - "de": "`nxc guide personas`: Der Abschnitt „deklariert, aber noch nicht gelesen\" begründete die\nWirkungslosigkeit von `session: fresh | continue` damit, dass „der Zug jeder Persona derzeit auf\neiner frischen Sitzung beginnt\". Das Feld ist wirkungslos; die Begründung war falsch, und ein\nAgent, der sie nachgeschlagen hat, hat fünf Persona-Deklarationen darauf gebaut. Beide\nSprachfassungen nennen jetzt die tatsächliche Regel — der AUFRUF entscheidet, nicht die\nDeklaration: `nxc send --to ` prägt eine frische Sitzung, `nxc reply --thread` in den\neigenen Faden dieser Persona setzt die bestehende fort. Ebenfalls korrigiert: `claude_md:\noverride` ist nur zur Hälfte ungelesen — wer es deklariert, setzt die Persona OHNE die `CLAUDE.md`\ndes Projekts zusammen, genau wie bei `ignore`." - }, - { - "type": "removed", - "en": "`nxc`: the channel declaration key `member_session: fresh | resume` is gone, together with the\n`nexus_chat::channel::MemberSession` type and the `ChannelDecl::member_session` field. It named a\nper-member session policy it never steered — a fan-out mints a fresh session either way — and its\nonly effect was to refuse its own second value in preflight, with the error pointing at a closed\nticket about something else. The question it asked is answered by the call: a supervisor addresses\neach member as a persona on that member's own thread, and a reply into that thread resumes the\nsession. An existing `channels.yaml` that still sets the key keeps loading unchanged; the key is\nignored.", - "de": "`nxc`: Der Kanal-Deklarationsschlüssel `member_session: fresh | resume` entfällt, zusammen mit dem\nTyp `nexus_chat::channel::MemberSession` und dem Feld `ChannelDecl::member_session`. Er benannte\neine Sitzungspolitik je Mitglied, die er nie steuerte — ein Auffächern prägt ohnehin immer eine\nfrische Sitzung —, und bewirkte nur, dass sein eigener zweiter Wert im Vorlauf abgelehnt wurde,\nmit einem Fehler, der auf ein geschlossenes Ticket zu einem anderen Thema verwies. Seine Frage\nbeantwortet der Aufruf: Der Betreuer spricht jedes Mitglied als Persona auf dessen eigenem Faden\nan, und eine Antwort in diesen Faden setzt die Sitzung fort. Eine bestehende `channels.yaml`, die\nden Schlüssel noch setzt, lädt unverändert weiter; der Schlüssel wird ignoriert.", - "facade": "breaking" - }, - { - "type": "added", - "en": "`nxc prime`: a persona is now told that asking a question does not cost it its context. The \"How\nyou use `nxc`\" block ends its asking rule with one sentence — asking and ending your turn is safe,\nwhen the answer comes you are resumed with everything you already know. Without it, a careful\nagent guesses pessimistically: five declarations in a live testbed had grown a ritual of state\ntrailer lines and a mandatory thread lookup at the top of every turn, a memory substitute against\nan amnesia that does not exist.", - "de": "`nxc prime`: Einer Persona wird jetzt gesagt, dass eine Frage sie ihren Kontext nicht kostet. Der\nBlock „How you use `nxc`\" schließt seine Frage-Regel mit einem Satz ab — zu fragen und den Zug zu\nbeenden ist sicher, kommt die Antwort, wirst du fortgesetzt, mit allem, was du bereits weißt. Ohne\nihn rät ein sorgfältiger Agent pessimistisch: In einem laufenden Prüfstand hatten fünf\nDeklarationen ein Ritual aus Zustandszeilen und einem pflichtmäßigen Faden-Nachschlagen zu Beginn\njedes Zuges entwickelt — Gedächtnisersatz gegen eine Amnesie, die es nicht gibt.", - "facade": "changed" - }, - { - "type": "fixed", - "en": "`nxc` on macOS: every spawned persona session died at authentication with `Failed to authenticate:\nOAuth session expired and could not be refreshed`, so no agent could be started at all. Nothing had\nexpired — the sidecar is spawned with a cleared environment and an explicit allowlist, and `USER`\nwas missing from it. On macOS the Agent SDK's credential lives in the login keyring rather than in\n`~/.claude/`, and reaching the keyring resolves through `USER`. It is forwarded now, and the\nallowlist's comment no longer claims the ambient credential is always a file under `HOME`.", - "de": "`nxc` auf macOS: Jede gestartete Persona-Sitzung starb an der Authentifizierung mit `Failed to\nauthenticate: OAuth session expired and could not be refreshed` — es ließ sich dort überhaupt kein\nAgent starten. Abgelaufen war nichts: Der Sidecar wird mit geleerter Umgebung und einer\nausdrücklichen Erlaubnisliste gestartet, und `USER` fehlte darin. Auf macOS liegt das Credential\ndes Agent-SDK im Login-Schlüsselbund statt unter `~/.claude/`, und der Zugriff darauf löst über\n`USER` auf. Die Variable wird jetzt durchgereicht, und der Kommentar über der Erlaubnisliste\nbehauptet nicht mehr, das Ambient-Credential sei immer eine Datei unter `HOME`.", - "facade": "changed" - } - ], - "notes": { - "en": "### Added\n- `nxc prime`: a persona is now told that asking a question does not cost it its context. The \"How\nyou use `nxc`\" block ends its asking rule with one sentence — asking and ending your turn is safe,\nwhen the answer comes you are resumed with everything you already know. Without it, a careful\nagent guesses pessimistically: five declarations in a live testbed had grown a ritual of state\ntrailer lines and a mandatory thread lookup at the top of every turn, a memory substitute against\nan amnesia that does not exist.\n\n### Fixed\n- `nxc guide personas`: the \"declared but not yet read\" section said `session: fresh | continue` was\ninert because \"every persona's turn currently starts a fresh session\". The field is inert; the\nreason was false, and an agent that looked it up built five persona declarations on it. Both\nlanguage editions now state the real rule — the CALL decides, not the declaration: `nxc send --to\n` mints a fresh session, and `nxc reply --thread` into that persona's own thread resumes\nthe one it already has. The same section's `claude_md: override` entry is corrected too: it is\nonly half unread, since declaring it composes the persona WITHOUT the project's `CLAUDE.md`,\nexactly as `ignore` does.\n- `nxc` on macOS: every spawned persona session died at authentication with `Failed to authenticate:\nOAuth session expired and could not be refreshed`, so no agent could be started at all. Nothing had\nexpired — the sidecar is spawned with a cleared environment and an explicit allowlist, and `USER`\nwas missing from it. On macOS the Agent SDK's credential lives in the login keyring rather than in\n`~/.claude/`, and reaching the keyring resolves through `USER`. It is forwarded now, and the\nallowlist's comment no longer claims the ambient credential is always a file under `HOME`.\n\n### Removed\n- `nxc`: the channel declaration key `member_session: fresh | resume` is gone, together with the\n`nexus_chat::channel::MemberSession` type and the `ChannelDecl::member_session` field. It named a\nper-member session policy it never steered — a fan-out mints a fresh session either way — and its\nonly effect was to refuse its own second value in preflight, with the error pointing at a closed\nticket about something else. The question it asked is answered by the call: a supervisor addresses\neach member as a persona on that member's own thread, and a reply into that thread resumes the\nsession. An existing `channels.yaml` that still sets the key keeps loading unchanged; the key is\nignored.\n\n### Facade Contract\n- `breaking` · `nxc`: the channel declaration key `member_session: fresh | resume` is gone, together with the\n`nexus_chat::channel::MemberSession` type and the `ChannelDecl::member_session` field. It named a\nper-member session policy it never steered — a fan-out mints a fresh session either way — and its\nonly effect was to refuse its own second value in preflight, with the error pointing at a closed\nticket about something else. The question it asked is answered by the call: a supervisor addresses\neach member as a persona on that member's own thread, and a reply into that thread resumes the\nsession. An existing `channels.yaml` that still sets the key keeps loading unchanged; the key is\nignored.\n- `changed` · `nxc prime`: a persona is now told that asking a question does not cost it its context. The \"How\nyou use `nxc`\" block ends its asking rule with one sentence — asking and ending your turn is safe,\nwhen the answer comes you are resumed with everything you already know. Without it, a careful\nagent guesses pessimistically: five declarations in a live testbed had grown a ritual of state\ntrailer lines and a mandatory thread lookup at the top of every turn, a memory substitute against\nan amnesia that does not exist.\n- `changed` · `nxc` on macOS: every spawned persona session died at authentication with `Failed to authenticate:\nOAuth session expired and could not be refreshed`, so no agent could be started at all. Nothing had\nexpired — the sidecar is spawned with a cleared environment and an explicit allowlist, and `USER`\nwas missing from it. On macOS the Agent SDK's credential lives in the login keyring rather than in\n`~/.claude/`, and reaching the keyring resolves through `USER`. It is forwarded now, and the\nallowlist's comment no longer claims the ambient credential is always a file under `HOME`.", - "de": "### Neu\n- `nxc prime`: Einer Persona wird jetzt gesagt, dass eine Frage sie ihren Kontext nicht kostet. Der\nBlock „How you use `nxc`\" schließt seine Frage-Regel mit einem Satz ab — zu fragen und den Zug zu\nbeenden ist sicher, kommt die Antwort, wirst du fortgesetzt, mit allem, was du bereits weißt. Ohne\nihn rät ein sorgfältiger Agent pessimistisch: In einem laufenden Prüfstand hatten fünf\nDeklarationen ein Ritual aus Zustandszeilen und einem pflichtmäßigen Faden-Nachschlagen zu Beginn\njedes Zuges entwickelt — Gedächtnisersatz gegen eine Amnesie, die es nicht gibt.\n\n### Behoben\n- `nxc guide personas`: Der Abschnitt „deklariert, aber noch nicht gelesen\" begründete die\nWirkungslosigkeit von `session: fresh | continue` damit, dass „der Zug jeder Persona derzeit auf\neiner frischen Sitzung beginnt\". Das Feld ist wirkungslos; die Begründung war falsch, und ein\nAgent, der sie nachgeschlagen hat, hat fünf Persona-Deklarationen darauf gebaut. Beide\nSprachfassungen nennen jetzt die tatsächliche Regel — der AUFRUF entscheidet, nicht die\nDeklaration: `nxc send --to ` prägt eine frische Sitzung, `nxc reply --thread` in den\neigenen Faden dieser Persona setzt die bestehende fort. Ebenfalls korrigiert: `claude_md:\noverride` ist nur zur Hälfte ungelesen — wer es deklariert, setzt die Persona OHNE die `CLAUDE.md`\ndes Projekts zusammen, genau wie bei `ignore`.\n- `nxc` auf macOS: Jede gestartete Persona-Sitzung starb an der Authentifizierung mit `Failed to\nauthenticate: OAuth session expired and could not be refreshed` — es ließ sich dort überhaupt kein\nAgent starten. Abgelaufen war nichts: Der Sidecar wird mit geleerter Umgebung und einer\nausdrücklichen Erlaubnisliste gestartet, und `USER` fehlte darin. Auf macOS liegt das Credential\ndes Agent-SDK im Login-Schlüsselbund statt unter `~/.claude/`, und der Zugriff darauf löst über\n`USER` auf. Die Variable wird jetzt durchgereicht, und der Kommentar über der Erlaubnisliste\nbehauptet nicht mehr, das Ambient-Credential sei immer eine Datei unter `HOME`.\n\n### Entfernt\n- `nxc`: Der Kanal-Deklarationsschlüssel `member_session: fresh | resume` entfällt, zusammen mit dem\nTyp `nexus_chat::channel::MemberSession` und dem Feld `ChannelDecl::member_session`. Er benannte\neine Sitzungspolitik je Mitglied, die er nie steuerte — ein Auffächern prägt ohnehin immer eine\nfrische Sitzung —, und bewirkte nur, dass sein eigener zweiter Wert im Vorlauf abgelehnt wurde,\nmit einem Fehler, der auf ein geschlossenes Ticket zu einem anderen Thema verwies. Seine Frage\nbeantwortet der Aufruf: Der Betreuer spricht jedes Mitglied als Persona auf dessen eigenem Faden\nan, und eine Antwort in diesen Faden setzt die Sitzung fort. Eine bestehende `channels.yaml`, die\nden Schlüssel noch setzt, lädt unverändert weiter; der Schlüssel wird ignoriert.\n\n### Facade-Kontrakt\n- `breaking` · `nxc`: Der Kanal-Deklarationsschlüssel `member_session: fresh | resume` entfällt, zusammen mit dem\nTyp `nexus_chat::channel::MemberSession` und dem Feld `ChannelDecl::member_session`. Er benannte\neine Sitzungspolitik je Mitglied, die er nie steuerte — ein Auffächern prägt ohnehin immer eine\nfrische Sitzung —, und bewirkte nur, dass sein eigener zweiter Wert im Vorlauf abgelehnt wurde,\nmit einem Fehler, der auf ein geschlossenes Ticket zu einem anderen Thema verwies. Seine Frage\nbeantwortet der Aufruf: Der Betreuer spricht jedes Mitglied als Persona auf dessen eigenem Faden\nan, und eine Antwort in diesen Faden setzt die Sitzung fort. Eine bestehende `channels.yaml`, die\nden Schlüssel noch setzt, lädt unverändert weiter; der Schlüssel wird ignoriert.\n- `changed` · `nxc prime`: Einer Persona wird jetzt gesagt, dass eine Frage sie ihren Kontext nicht kostet. Der\nBlock „How you use `nxc`\" schließt seine Frage-Regel mit einem Satz ab — zu fragen und den Zug zu\nbeenden ist sicher, kommt die Antwort, wirst du fortgesetzt, mit allem, was du bereits weißt. Ohne\nihn rät ein sorgfältiger Agent pessimistisch: In einem laufenden Prüfstand hatten fünf\nDeklarationen ein Ritual aus Zustandszeilen und einem pflichtmäßigen Faden-Nachschlagen zu Beginn\njedes Zuges entwickelt — Gedächtnisersatz gegen eine Amnesie, die es nicht gibt.\n- `changed` · `nxc` auf macOS: Jede gestartete Persona-Sitzung starb an der Authentifizierung mit `Failed to\nauthenticate: OAuth session expired and could not be refreshed` — es ließ sich dort überhaupt kein\nAgent starten. Abgelaufen war nichts: Der Sidecar wird mit geleerter Umgebung und einer\nausdrücklichen Erlaubnisliste gestartet, und `USER` fehlte darin. Auf macOS liegt das Credential\ndes Agent-SDK im Login-Schlüsselbund statt unter `~/.claude/`, und der Zugriff darauf löst über\n`USER` auf. Die Variable wird jetzt durchgereicht, und der Kommentar über der Erlaubnisliste\nbehauptet nicht mehr, das Ambient-Credential sei immer eine Datei unter `HOME`." - } - }, - { - "version": "0.61.0", - "date": "2026-08-22", - "items": [ - { - "type": "changed", - "en": "An app calling the nexus-chat engine now says only WHO is calling. The per-call context carried five\nvalues — the clock, the workspace identity, the acting agent, the caller's session and the\ndepth-guard counter — and four of them were things the engine already knew.\n\nThe workspace identity comes from the handle: one handle is one workspace, so there is no longer any\nway for two calls to claim different ones, and that is the value access rights will hang off later.\n`Engine::origin()` reports it, for an app that needs to address what it just wrote. **The identity is\nnow the workspace's own replica prefix** — the four characters `nxc init` prints as `prefix:`. In a\nworkspace whose prefix is `ab12`, an agent is addressed as `ab12/coder`; every workspace on the\nmachine used to mint `local/coder` alike. The depth guard\nis unchanged and needs nothing passed: the chain depth is read back from the session map, where the\nspawning call recorded it, exactly as before — a caller could only ever raise it anyway. The clock\nbecomes optional, with the system clock as the default, so an app stops formatting a timestamp on\nevery call just to say \"now\". And the acting agent is read back from the caller's session: identity\ncomes from where a call came from, not from what it says about itself.\n\nWhat stays is the caller's session — and it is the SENDER's, not the recipient's. It is stamped onto\nthe message as the return address, and that is what lets a chain run in both directions: a\nsupervisor's `send --to` reaches the coder, and the coder's `reply --thread` wakes the supervisor\nagain because the address is on the message. An `actor` is still required where there is no session\nat all — a human at a terminal, or an app acting for its logged-in user — and a call with neither is\nrefused by name rather than being attributed to a default nobody chose.\n\n`nxc` moved with it, and had to: its default origin was the fixed word `local`, and had it stayed\nthat way an app would have posted as `/coder` while a terminal in the same project posted as\n`local/coder` — one declared channel with two disjoint sets of members, and nothing to report it.\nWith `NXC_ORIGIN` unset the command line now resolves the same workspace's prefix the library does.\nA handle spelled out somewhere — an old message, a script that passes `--consumer local/coder` — no\nlonger names the same agent; `NXC_ORIGIN=local` keeps the previous spelling. `NXC_ACTOR`,\n`NXC_SESSION`, `NXC_HOP` and `NXC_NOW` are unchanged, and `--json` stays reproducible.\n\nOne consequence is accepted rather than solved: the prefix is reassigned if syncing finds another\nworkspace already using it, and handles written under the old one do not follow.", - "de": "Eine App, die die nexus-chat-Engine aufruft, sagt jetzt nur noch, WER aufruft. Der Aufruf-Kontext\ntrug fünf Werte — die Uhr, die Workspace-Identität, den handelnden Agenten, die Sitzung des Aufrufers\nund den Zähler des Tiefenschutzes — und vier davon kannte die Engine bereits selbst.\n\nDie Workspace-Identität kommt aus dem Handle: ein Handle ist ein Workspace, also können zwei Aufrufe\nkeine unterschiedlichen Herkünfte mehr behaupten — und genau daran hängen später die Rechte.\n`Engine::origin()` gibt sie aus, für eine App, die adressieren muss, was sie gerade geschrieben hat.\n**Diese Identität ist jetzt der Replica-Prefix des Workspace** — die vier Zeichen, die `nxc init` als\n`prefix:` ausgibt. In einem Workspace mit dem Prefix `ab12` wird ein Agent als `ab12/coder`\nadressiert; früher hieß er in jedem Workspace der Maschine gleichermaßen `local/coder`.\nDer Tiefenschutz wirkt unverändert und braucht keine Angabe: die Kettentiefe wird aus der\nSitzungskarte gelesen, wo der auslösende Aufruf sie hinterlegt hat — erhöhen konnte ein Aufrufer sie\nohnehin nur. Die Uhr wird optional, mit der Systemuhr als Vorgabe, sodass eine App nicht mehr bei\njedem Aufruf einen Zeitstempel formatiert, nur um \"jetzt\" zu sagen. Und der handelnde Agent wird aus\nder Sitzung des Aufrufers gelesen: Identität kommt aus der Herkunft, nicht aus der Auskunft.\n\nWas bleibt, ist die Sitzung des Aufrufers — und zwar die des ABSENDERS, nicht die des Empfängers.\nSie wird als Rückadresse auf die Nachricht gestempelt, und genau daran hängt, dass eine Kette in\nbeide Richtungen läuft: das `send --to` eines Betreuers erreicht den Coder, und das `reply --thread`\ndes Coders weckt den Betreuer wieder, weil die Adresse auf der Nachricht steht. Ein `actor` wird\nweiterhin gebraucht, wo es gar keine Sitzung gibt — ein Mensch am Terminal oder eine App, die für\nihren angemeldeten Nutzer handelt — und ein Aufruf ohne beides wird benannt abgelehnt, statt einem\nVorgabewert zugeschrieben zu werden, den niemand gewählt hat.\n\n`nxc` zieht mit, und musste es: ihre Vorgabe war das feste Wort `local`. Wäre sie es geblieben,\nschriebe eine App als `/coder`, während ein Terminal im selben Projekt als `local/coder`\nschriebe — ein deklarierter Kanal mit zwei disjunkten Mitgliedersätzen, und nichts, das es meldet.\nOhne gesetztes `NXC_ORIGIN` löst die Kommandozeile jetzt denselben Prefix auf wie die Bibliothek.\nEin irgendwo ausgeschriebenes Handle — eine alte Nachricht, ein Skript mit `--consumer local/coder` —\nbenennt damit nicht mehr denselben Agenten; `NXC_ORIGIN=local` behält die bisherige Schreibweise.\n`NXC_ACTOR`, `NXC_SESSION`, `NXC_HOP` und `NXC_NOW` bleiben unverändert, und `--json` bleibt\nreproduzierbar.\n\nEine Folge wird bewusst in Kauf genommen statt gelöst: der Prefix wird neu vergeben, wenn die\nSynchronisation ihn bereits bei einem anderen Workspace vorfindet — bereits geschriebene Handles\nziehen dabei nicht mit.", - "facade": "breaking" - }, - { - "type": "added", - "en": "`nxc` has guides. Six of them, in English and German, served offline by `nxc guide` and published\nunder a **chat** section of the documentation at nxsflow.com/open-source/docs: getting started, core\nconcepts, the full command reference with every `--json` shape, declaring a persona, declaring a\nchannel, and limits and safety. Until now the third building block had nothing but `--help`, while\nthe suite documentation described flow and nothing else.\n\nThey document what actually ships, and say what does not. A channel is a **declaration** in\n`.nxs-personas/channels.yaml`, never something a verb creates; an ordered channel (`flow:\nsequential`) is what replaced the `workflow` verbs; `send --to` and `reply --thread` are the only\ntwo writing verbs there are. Every verb that was removed — `ask`, `channels create/dm/join/leave`,\n`agents register/list/search`, the whole `workflow` group, `send --model/--deadline/--kind` — has a\nline saying what to do instead, because half of a reference you can trust is what it tells you is\nnot there.\n\nEvery command the English and German guides print is executed, and the output shown IS the asserted\noutput — the same \"example = test\" loop flow's guides have always run. `nxs guide` now lists chat's\ntopics beside flow's, and where a topic name exists in two building blocks (`getting-started`,\n`commands`) it names the per-tool command instead of picking one for you.", - "de": "`nxc` hat Anleitungen. Sechs Stück, auf Deutsch und Englisch, offline ausgeliefert von `nxc guide`\nund veröffentlicht unter einem Abschnitt **chat** der Dokumentation auf\nnxsflow.com/open-source/docs: erste Schritte, Kernkonzepte, die vollständige Befehlsreferenz mit\njeder `--json`-Form, eine Persona deklarieren, einen Kanal deklarieren sowie Grenzen und Sicherheit.\nBisher hatte der dritte Baustein nichts außer `--help`, während die Suite-Dokumentation flow\nbeschrieb und sonst nichts.\n\nSie dokumentieren, was tatsächlich ausgeliefert ist, und sagen, was es nicht gibt. Ein Kanal ist eine\n**Deklaration** in `.nxs-personas/channels.yaml` und nie etwas, das ein Verb anlegt; ein geordneter\nKanal (`flow: sequential`) ist das, was die `workflow`-Verben ersetzt hat; `send --to` und `reply\n--thread` sind die einzigen zwei schreibenden Verben. Zu jedem entfernten Verb — `ask`, `channels\ncreate/dm/join/leave`, `agents register/list/search`, die ganze `workflow`-Gruppe, `send\n--model/--deadline/--kind` — steht da, was man stattdessen tut, denn die Hälfte einer\nvertrauenswürdigen Referenz ist das, wovon sie sagt, dass es nicht da ist.\n\nJeder Befehl, den die deutschen und englischen Anleitungen zeigen, wird ausgeführt, und die\ngezeigte Ausgabe IST die geprüfte Ausgabe — dieselbe Schleife „Beispiel = Test\", die die Anleitungen\nvon flow seit jeher fahren. `nxs guide` listet die Themen von chat jetzt neben denen von flow, und\nwo ein Themenname in zwei Bausteinen vorkommt (`getting-started`, `commands`), nennt es den Befehl je\nWerkzeug, statt für Sie auszuwählen." - }, - { - "type": "changed", - "en": "The nexus-chat library seam has seven reading verbs. `status` says where an operation stands, and it\nnow carries per thread what two other reads used to: when the answer is due, and whether the thread\nholds the shared working copy or is queued behind it. `directory` says whom and WHAT you can\naddress — the declared personas, the declared channels together with what each is for, and the\nproject front doors other workspaces have made public, including ones that arrived by sync and that\nnothing here declares. `prime_as` is session start and already carries the unread catch-up and the\n\"threads you opened\" wake. `thread` is the one way to read what was said, and it now applies the\nchannel's declared `visibility`, so a member reading a `requester_only` board sees the request and\ntheir own words and not everybody else's answers. `transcript_page` reads a session's history in a\nwindow. `subscribe` keeps a view alive. `search` finds a message again by a piece of its text, in\nthe channels you are a member of — it is unchanged, and it stays because apps use it.\n\nTen reads went from that seam. Three were duplicates of a verb that stayed: `prime` was\n`prime_as` without a persona, `transcript` was `transcript_page` asked for the whole session, and\nthe third `open`-constructor existed for a single value that is now a field on `EngineConfig`.\n`public_channels` moved into `directory`; `inbox` and `opener_wake` into `prime_as`; `threads`,\n`thread_board` and `thread_quorums` into `status` — reading a named SET of threads is a scope of it\nnow, and still costs one query however many threads are in the set. Two are losses rather than\nmoves, and are worth knowing about: per-channel unread counts and reading a whole channel's history\nin one call have no replacement on the library seam.\n\nNothing changed for anyone typing `nxc`. Every command still exists and still prints what it\nprinted — `nxc list --json` gains the public front doors, which is the only visible difference.", - "de": "Die Bibliotheksnaht von nexus-chat hat sieben lesende Verben. `status` sagt, wo ein Vorgang steht,\nund trägt jetzt je Faden mit, was zwei andere Lesungen bisher trugen: bis wann die Antwort fällig\nist, und ob der Faden die gemeinsame Arbeitskopie hält oder dahinter wartet. `directory` sagt, wen\nund WAS man ansprechen kann — die deklarierten Personas, die deklarierten Kanäle samt dem, wofür sie\nda sind, und die Vordertüren anderer Projekte, auch solche, die per Sync hereingekommen sind und die\nhier nichts deklariert. `prime_as` ist der Sitzungsstart und trägt Ungelesenes und die Weckliste\n\"Fäden, die du geöffnet hast\" ohnehin schon. `thread` ist der eine Weg zu lesen, was gesagt wurde,\nund wendet jetzt die deklarierte `visibility` des Kanals an: Wer bei `requester_only` mitliest, sieht\ndie Anfrage und die eigenen Worte, nicht die Antworten der anderen. `transcript_page` liest den\nVerlauf einer Sitzung fensterweise. `subscribe` hält eine Ansicht lebendig. `search` findet eine\nNachricht über ein Stück ihres Textes wieder, in den Kanälen, in denen man Mitglied ist — unverändert,\nund es bleibt, weil Apps es nutzen.\n\nZehn Lesungen sind von dieser Naht verschwunden. Drei waren Doppelungen eines Verbs, das bleibt:\n`prime` war `prime_as` ohne Persona, `transcript` war `transcript_page` für die ganze Sitzung, und\nder dritte `open`-Konstruktor gab es für einen einzigen Wert, der jetzt ein Feld an `EngineConfig`\nist. `public_channels` ist in `directory` aufgegangen, `inbox` und `opener_wake` in `prime_as`,\n`threads`, `thread_board` und `thread_quorums` in `status` — einen benannten SATZ von Fäden zu\nlesen ist jetzt eine seiner Formen und kostet weiterhin eine einzige Abfrage, wie viele Fäden auch\ndrin sind. Zwei sind Verluste und keine Umzüge, und das gehört gesagt: Ungelesen-Zähler je Kanal\nund der ganze Verlauf eines Kanals in einem Aufruf haben auf der Bibliotheksnaht keinen Ersatz.\n\nFür alle, die `nxc` tippen, ändert sich nichts. Jeder Befehl existiert weiter und gibt aus, was er\nausgegeben hat — `nxc list --json` nennt zusätzlich die öffentlichen Vordertüren, das ist der\neinzige sichtbare Unterschied.", - "facade": "breaking" - }, - { - "type": "changed", - "en": "`nxc` has one way to start a conversation and one way to answer it. `send --to` opens a thread and\nalways starts or wakes the persona or channel it names; `reply --thread` answers in one. The raw\npost that wrote into a channel and woke nobody is gone, and so is the reply that took a message id\nas a second address for a conversation the thread already names. On the library seam the same cut\nremoves `Engine::send` and `Engine::reply`; an app that held a message id now reads its thread — one\nread — and replies into that.\n\nEach of the two verbs takes three statements and nothing else. Which model a session runs on and by\nwhen a board must be answered are DECLARED, at the persona and at the channel, so `send --model` and\n`send --deadline` are gone rather than competing with the declaration. An absolute deadline is still\nexpressible — a channel's `timeout:` accepts an RFC3339 instant as well as a `30m` window.\n`--kind`, `--priority` and `--disposition` come off both verbs; they are interesting enough to bring\nback later, with more knowledge of what is actually needed. Two consequences are deliberate and\nworth knowing: `reply --escalate` still says \"I cannot carry this out\" and still holds the working\ncopy, because it was never really a `--kind` value; and the working-tree queue is now plain\nfirst-come-first-served, which is accepted for the moment so the problems a missing priority causes\ncan be collected before it comes back.\n\n`nxc send --to` now wants to know what a conversation is ABOUT: `--ref nxf_ids=`, repeatable, or\nan explicit `--no-ref` when there really is nothing. Leaving out both still sends — nothing is\nrefused — but it warns, on the terminal AND as a `refs_warning` field in the `--json` receipt, so an\napp sees it too.\n\n`nxc reply --if-unanswered` is unchanged and stays where it was: it is the agent sidecar's teardown\ncall, which writes a failure reply when a session ends without ever answering, so a thread does not\ngo quiet forever.", - "de": "`nxc` hat einen Weg, ein Gespräch zu beginnen, und einen, darauf zu antworten. `send --to` öffnet\neinen Faden und startet oder weckt immer die Persona oder den Kanal, den es benennt; `reply\n--thread` antwortet darin. Der rohe Post, der in einen Kanal schrieb und niemanden weckte, ist weg,\nebenso die Antwort, die eine Nachrichten-ID als zweite Adresse für ein Gespräch nahm, das der Faden\nlängst benennt. Auf der Bibliotheksnaht entfallen mit demselben Schnitt `Engine::send` und\n`Engine::reply`; eine App mit einer Nachrichten-ID liest jetzt deren Faden — eine Lesung — und\nantwortet dort hinein.\n\nJedes der beiden Verben trägt drei Angaben und sonst nichts. Auf welchem Modell eine Sitzung läuft\nund bis wann eine Tafel beantwortet sein muss, ist DEKLARIERT — an der Persona und am Kanal —,\ndeshalb entfallen `send --model` und `send --deadline`, statt neben der Deklaration eine zweite\nAntwort zu geben. Eine absolute Frist bleibt ausdrückbar: das `timeout:` eines Kanals nimmt einen\nRFC3339-Zeitpunkt genauso wie ein Fenster `30m`. `--kind`, `--priority` und `--disposition` fallen an\nbeiden Verben weg; sie sind interessant genug, um sie später zurückzuholen, dann aber mit mehr Wissen\nüber den tatsächlichen Bedarf. Zwei Folgen sind bewusst so gewollt und gut zu wissen: `reply\n--escalate` sagt weiterhin \"das kann ich nicht ausführen\" und hält weiterhin die Arbeitskopie, denn\nes war nie wirklich ein `--kind`-Wert; und die Warteschlange der Arbeitskopie ist jetzt schlicht\nWer-zuerst-kommt, was vorerst akzeptiert ist, damit sich die Probleme sammeln lassen, die eine\nfehlende Priorität verursacht, bevor sie zurückkehrt.\n\n`nxc send --to` will jetzt wissen, WORUM es in einem Gespräch geht: `--ref nxf_ids=`,\nwiederholbar, oder ein ausdrückliches `--no-ref`, wenn es wirklich nichts gibt. Lässt man beides\nweg, wird trotzdem gesendet — nichts wird abgelehnt —, aber es gibt einen Hinweis: im Terminal UND\nals Feld `refs_warning` in der `--json`-Quittung, damit auch eine App ihn sieht.\n\n`nxc reply --if-unanswered` bleibt unverändert an seinem Platz: Es ist der Abbau-Aufruf des\nAgenten-Sidecars, der eine Fehlantwort schreibt, wenn eine Sitzung endet, ohne je geantwortet zu\nhaben — damit ein Faden nicht für immer verstummt.", - "facade": "breaking" - }, - { - "type": "changed", - "en": "The published documentation at nxsflow.com/open-source/docs now shows the suite as the three building\nblocks it is: each block's guides sit under their own heading instead of one flat list that was, in\ntruth, only nxf's. Because the three blocks share one page, every guide has a namespaced address —\n`nxf-getting-started` instead of `getting-started` — so `getting-started` and `commands` can exist in\nall three without colliding. Links already saved to the old page addresses keep working.\n\nNothing changes on the command line: `nxf guide getting-started` is spelled exactly as before. The\nnew address is the published one, and every cross-reference inside the guides moved with it.", - "de": "Die veröffentlichte Dokumentation auf nxsflow.com/open-source/docs zeigt die Suite jetzt als die drei\nBausteine, die sie ist: die Anleitungen jedes Bausteins stehen unter einer eigenen Überschrift statt\nin einer flachen Liste, die in Wahrheit nur nxf gehörte. Weil sich die drei Bausteine eine Seite\nteilen, hat jede Anleitung eine eigene Adresse bekommen — `nxf-getting-started` statt\n`getting-started` —, damit `getting-started` und `commands` in allen dreien vorkommen können, ohne\nsich zu überschreiben. Bereits gespeicherte Links auf die alten Seitenadressen funktionieren weiter.\n\nAuf der Kommandozeile ändert sich nichts: `nxf guide getting-started` schreibt sich genau wie vorher.\nDie neue Adresse ist die veröffentlichte, und jeder Querverweis in den Anleitungen ist mitgezogen.", - "facade": "changed" - }, - { - "type": "added", - "en": "The embedding handle `Engine` now carries the four thread-link operations — `thread_link_add`,\n`thread_link_remove`, `thread_links` and `thread_items` — so an app that embeds the engine can\ncreate and read the conversation↔item edge in process, and no longer only through the `nxf thread`\nCLI. The link types `ThreadLink`, `LinkRelation` and `LinkWeight` are re-exported from the crate\nroot, so a consumer can name them in its own signatures without depending on the core crate.\nBehaviour is unchanged: only the item end of a link is checked for existence, so a thread from a\nchat store flow cannot read stays linkable, and a thread with no links reads back empty rather\nthan being rejected.", - "de": "Das Embedding-Handle `Engine` trägt jetzt die vier Thread-Link-Operationen — `thread_link_add`,\n`thread_link_remove`, `thread_links` und `thread_items` —, sodass eine einbettende App die Kante\nzwischen Gespräch und Eintrag im Prozess anlegen und lesen kann und nicht mehr nur über die CLI\n`nxf thread`. Die Typen `ThreadLink`, `LinkRelation` und `LinkWeight` sind aus der Wurzel des\nCrates re-exportiert und damit in eigenen Signaturen benennbar, ohne das Core-Crate zu binden. Am\nVerhalten ändert sich nichts: Geprüft wird nur das Eintrags-Ende, ein Faden aus einem Chat-Speicher,\nden flow nicht lesen kann, bleibt verknüpfbar, und ein Faden ohne Kanten liefert eine leere Liste,\nstatt abgelehnt zu werden.", - "facade": "changed" - }, - { - "type": "added", - "en": "`nxm guide` and `nxc guide` exist, and `nxs guide` reads the whole suite at once. Until now the only\nnarrative documentation in the suite was `nxf guide`, so \"one binary, three products\" was true of the\nbinary and not of the manual. Each building block now serves its own guides through the same\nmechanism — compiled into the binary, offline, `--json` in the same shape for all three — and\n`nxs guide` fans out over the active modules exactly as `nxs prime` does. It needs no workspace: with\nnone it lists every installed block, because `getting-started` is written for the reader who has not\ninitialized anything yet. A topic several blocks share is never picked for you; you are pointed at\n`nxf guide …` / `nxm guide …` / `nxc guide …` by name.\n\nThe `--json` contract of the existing guide verbs is unchanged, byte for byte. Chat's own guides ship\nin this same release; memory answers an honest empty list until its guides are written, and says so\nrather than pretending the topic is unknown.", - "de": "`nxm guide` und `nxc guide` gibt es jetzt, und `nxs guide` liest die ganze Suite auf einmal. Bisher\nwar `nxf guide` die einzige Anleitung der Suite — \"ein Binary, drei Produkte\" stimmte damit für das\nBinary, aber nicht für das Handbuch. Jeder Baustein liefert seine eigenen Anleitungen jetzt über\ndieselbe Mechanik — ins Binary kompiliert, offline, `--json` in derselben Form für alle drei — und\n`nxs guide` fächert über die aktiven Module, genau wie `nxs prime`. Ein Workspace ist dafür nicht\nnötig: ohne einen werden alle installierten Bausteine gelistet, denn `getting-started` richtet sich\ngerade an die Leserin, die noch nichts eingerichtet hat. Ein Thema, das mehrere Bausteine führen,\nwird nie für Sie ausgewählt — Sie werden namentlich auf `nxf guide …` / `nxm guide …` /\n`nxc guide …` verwiesen.\n\nDer `--json`-Kontrakt der bestehenden Guide-Verben bleibt unverändert, Byte für Byte. Chats eigene\nAnleitungen kommen in derselben Auslieferung mit; memory antwortet mit einer ehrlichen leeren Liste,\nbis seine geschrieben sind — und sagt das, statt das Thema als unbekannt auszugeben.", - "facade": "changed" - }, - { - "type": "fixed", - "en": "In an ordered channel (`flow: sequential`), a step whose declared `timeout` ran out in the MIDDLE of\nthe order no longer ends the flow. The declaration always said a step starts once the one before it\nhas settled, and a settled step is one that answered *or* one whose deadline struck — but only an\nanswer ever reached the mechanism, so a silent middle step made the channel summarise and deliver,\nand every step behind it never ran. A lapsed step now starts the next one, exactly as an answer\ndoes; a lapse on the LAST step still consolidates, because there is nothing after it to start. What\nthe step never said is reported instead of silently absorbed: the receipt of the call that moved the\nflow past it, and the consolidation that carries no answer from it, both name it under the new\n`step_unanswered` finding. `nxc tick` reports such a call as `reason: \"advanced\"` with `acted:\nfalse` — it started a step rather than delivering a result, and it delivers nothing.", - "de": "In einem geordneten Kanal (`flow: sequential`) beendet ein Schritt, dessen deklarierte Frist MITTEN\nin der Reihenfolge zuschlägt, den Ablauf nicht mehr. Die Deklaration sagte immer schon, dass ein\nSchritt beginnt, sobald der davor abgeschlossen ist — abgeschlossen heißt geantwortet *oder* Frist\nzugeschlagen —, aber nur eine Antwort erreichte die Mechanik: ein stummer mittlerer Schritt ließ den\nKanal zusammenfassen und ausliefern, und alle Schritte dahinter liefen nie. Ein zugeschlagener\nSchritt startet jetzt den nächsten, genau wie eine Antwort es tut; schlägt die Frist beim LETZTEN\nSchritt zu, wird weiterhin zusammengefasst, denn danach kommt nichts mehr. Was der Schritt nie\ngesagt hat, wird berichtet statt stillschweigend geschluckt: die Quittung des Aufrufs, der den\nAblauf daran vorbeigeführt hat, und die Zusammenfassung, die keine Antwort von ihm enthält, benennen\nihn beide unter dem neuen Befund `step_unanswered`. `nxc tick` meldet einen solchen Aufruf als\n`reason: \"advanced\"` mit `acted: false` — er hat einen Schritt gestartet und nichts ausgeliefert.", - "facade": "breaking" - } - ], - "notes": { - "en": "### Added\n- `nxc` has guides. Six of them, in English and German, served offline by `nxc guide` and published\nunder a **chat** section of the documentation at nxsflow.com/open-source/docs: getting started, core\nconcepts, the full command reference with every `--json` shape, declaring a persona, declaring a\nchannel, and limits and safety. Until now the third building block had nothing but `--help`, while\nthe suite documentation described flow and nothing else.\n\nThey document what actually ships, and say what does not. A channel is a **declaration** in\n`.nxs-personas/channels.yaml`, never something a verb creates; an ordered channel (`flow:\nsequential`) is what replaced the `workflow` verbs; `send --to` and `reply --thread` are the only\ntwo writing verbs there are. Every verb that was removed — `ask`, `channels create/dm/join/leave`,\n`agents register/list/search`, the whole `workflow` group, `send --model/--deadline/--kind` — has a\nline saying what to do instead, because half of a reference you can trust is what it tells you is\nnot there.\n\nEvery command the English and German guides print is executed, and the output shown IS the asserted\noutput — the same \"example = test\" loop flow's guides have always run. `nxs guide` now lists chat's\ntopics beside flow's, and where a topic name exists in two building blocks (`getting-started`,\n`commands`) it names the per-tool command instead of picking one for you.\n- The embedding handle `Engine` now carries the four thread-link operations — `thread_link_add`,\n`thread_link_remove`, `thread_links` and `thread_items` — so an app that embeds the engine can\ncreate and read the conversation↔item edge in process, and no longer only through the `nxf thread`\nCLI. The link types `ThreadLink`, `LinkRelation` and `LinkWeight` are re-exported from the crate\nroot, so a consumer can name them in its own signatures without depending on the core crate.\nBehaviour is unchanged: only the item end of a link is checked for existence, so a thread from a\nchat store flow cannot read stays linkable, and a thread with no links reads back empty rather\nthan being rejected.\n- `nxm guide` and `nxc guide` exist, and `nxs guide` reads the whole suite at once. Until now the only\nnarrative documentation in the suite was `nxf guide`, so \"one binary, three products\" was true of the\nbinary and not of the manual. Each building block now serves its own guides through the same\nmechanism — compiled into the binary, offline, `--json` in the same shape for all three — and\n`nxs guide` fans out over the active modules exactly as `nxs prime` does. It needs no workspace: with\nnone it lists every installed block, because `getting-started` is written for the reader who has not\ninitialized anything yet. A topic several blocks share is never picked for you; you are pointed at\n`nxf guide …` / `nxm guide …` / `nxc guide …` by name.\n\nThe `--json` contract of the existing guide verbs is unchanged, byte for byte. Chat's own guides ship\nin this same release; memory answers an honest empty list until its guides are written, and says so\nrather than pretending the topic is unknown.\n\n### Changed\n- An app calling the nexus-chat engine now says only WHO is calling. The per-call context carried five\nvalues — the clock, the workspace identity, the acting agent, the caller's session and the\ndepth-guard counter — and four of them were things the engine already knew.\n\nThe workspace identity comes from the handle: one handle is one workspace, so there is no longer any\nway for two calls to claim different ones, and that is the value access rights will hang off later.\n`Engine::origin()` reports it, for an app that needs to address what it just wrote. **The identity is\nnow the workspace's own replica prefix** — the four characters `nxc init` prints as `prefix:`. In a\nworkspace whose prefix is `ab12`, an agent is addressed as `ab12/coder`; every workspace on the\nmachine used to mint `local/coder` alike. The depth guard\nis unchanged and needs nothing passed: the chain depth is read back from the session map, where the\nspawning call recorded it, exactly as before — a caller could only ever raise it anyway. The clock\nbecomes optional, with the system clock as the default, so an app stops formatting a timestamp on\nevery call just to say \"now\". And the acting agent is read back from the caller's session: identity\ncomes from where a call came from, not from what it says about itself.\n\nWhat stays is the caller's session — and it is the SENDER's, not the recipient's. It is stamped onto\nthe message as the return address, and that is what lets a chain run in both directions: a\nsupervisor's `send --to` reaches the coder, and the coder's `reply --thread` wakes the supervisor\nagain because the address is on the message. An `actor` is still required where there is no session\nat all — a human at a terminal, or an app acting for its logged-in user — and a call with neither is\nrefused by name rather than being attributed to a default nobody chose.\n\n`nxc` moved with it, and had to: its default origin was the fixed word `local`, and had it stayed\nthat way an app would have posted as `/coder` while a terminal in the same project posted as\n`local/coder` — one declared channel with two disjoint sets of members, and nothing to report it.\nWith `NXC_ORIGIN` unset the command line now resolves the same workspace's prefix the library does.\nA handle spelled out somewhere — an old message, a script that passes `--consumer local/coder` — no\nlonger names the same agent; `NXC_ORIGIN=local` keeps the previous spelling. `NXC_ACTOR`,\n`NXC_SESSION`, `NXC_HOP` and `NXC_NOW` are unchanged, and `--json` stays reproducible.\n\nOne consequence is accepted rather than solved: the prefix is reassigned if syncing finds another\nworkspace already using it, and handles written under the old one do not follow.\n- The nexus-chat library seam has seven reading verbs. `status` says where an operation stands, and it\nnow carries per thread what two other reads used to: when the answer is due, and whether the thread\nholds the shared working copy or is queued behind it. `directory` says whom and WHAT you can\naddress — the declared personas, the declared channels together with what each is for, and the\nproject front doors other workspaces have made public, including ones that arrived by sync and that\nnothing here declares. `prime_as` is session start and already carries the unread catch-up and the\n\"threads you opened\" wake. `thread` is the one way to read what was said, and it now applies the\nchannel's declared `visibility`, so a member reading a `requester_only` board sees the request and\ntheir own words and not everybody else's answers. `transcript_page` reads a session's history in a\nwindow. `subscribe` keeps a view alive. `search` finds a message again by a piece of its text, in\nthe channels you are a member of — it is unchanged, and it stays because apps use it.\n\nTen reads went from that seam. Three were duplicates of a verb that stayed: `prime` was\n`prime_as` without a persona, `transcript` was `transcript_page` asked for the whole session, and\nthe third `open`-constructor existed for a single value that is now a field on `EngineConfig`.\n`public_channels` moved into `directory`; `inbox` and `opener_wake` into `prime_as`; `threads`,\n`thread_board` and `thread_quorums` into `status` — reading a named SET of threads is a scope of it\nnow, and still costs one query however many threads are in the set. Two are losses rather than\nmoves, and are worth knowing about: per-channel unread counts and reading a whole channel's history\nin one call have no replacement on the library seam.\n\nNothing changed for anyone typing `nxc`. Every command still exists and still prints what it\nprinted — `nxc list --json` gains the public front doors, which is the only visible difference.\n- `nxc` has one way to start a conversation and one way to answer it. `send --to` opens a thread and\nalways starts or wakes the persona or channel it names; `reply --thread` answers in one. The raw\npost that wrote into a channel and woke nobody is gone, and so is the reply that took a message id\nas a second address for a conversation the thread already names. On the library seam the same cut\nremoves `Engine::send` and `Engine::reply`; an app that held a message id now reads its thread — one\nread — and replies into that.\n\nEach of the two verbs takes three statements and nothing else. Which model a session runs on and by\nwhen a board must be answered are DECLARED, at the persona and at the channel, so `send --model` and\n`send --deadline` are gone rather than competing with the declaration. An absolute deadline is still\nexpressible — a channel's `timeout:` accepts an RFC3339 instant as well as a `30m` window.\n`--kind`, `--priority` and `--disposition` come off both verbs; they are interesting enough to bring\nback later, with more knowledge of what is actually needed. Two consequences are deliberate and\nworth knowing: `reply --escalate` still says \"I cannot carry this out\" and still holds the working\ncopy, because it was never really a `--kind` value; and the working-tree queue is now plain\nfirst-come-first-served, which is accepted for the moment so the problems a missing priority causes\ncan be collected before it comes back.\n\n`nxc send --to` now wants to know what a conversation is ABOUT: `--ref nxf_ids=`, repeatable, or\nan explicit `--no-ref` when there really is nothing. Leaving out both still sends — nothing is\nrefused — but it warns, on the terminal AND as a `refs_warning` field in the `--json` receipt, so an\napp sees it too.\n\n`nxc reply --if-unanswered` is unchanged and stays where it was: it is the agent sidecar's teardown\ncall, which writes a failure reply when a session ends without ever answering, so a thread does not\ngo quiet forever.\n- The published documentation at nxsflow.com/open-source/docs now shows the suite as the three building\nblocks it is: each block's guides sit under their own heading instead of one flat list that was, in\ntruth, only nxf's. Because the three blocks share one page, every guide has a namespaced address —\n`nxf-getting-started` instead of `getting-started` — so `getting-started` and `commands` can exist in\nall three without colliding. Links already saved to the old page addresses keep working.\n\nNothing changes on the command line: `nxf guide getting-started` is spelled exactly as before. The\nnew address is the published one, and every cross-reference inside the guides moved with it.\n\n### Fixed\n- In an ordered channel (`flow: sequential`), a step whose declared `timeout` ran out in the MIDDLE of\nthe order no longer ends the flow. The declaration always said a step starts once the one before it\nhas settled, and a settled step is one that answered *or* one whose deadline struck — but only an\nanswer ever reached the mechanism, so a silent middle step made the channel summarise and deliver,\nand every step behind it never ran. A lapsed step now starts the next one, exactly as an answer\ndoes; a lapse on the LAST step still consolidates, because there is nothing after it to start. What\nthe step never said is reported instead of silently absorbed: the receipt of the call that moved the\nflow past it, and the consolidation that carries no answer from it, both name it under the new\n`step_unanswered` finding. `nxc tick` reports such a call as `reason: \"advanced\"` with `acted:\nfalse` — it started a step rather than delivering a result, and it delivers nothing.\n\n### Facade Contract\n- `breaking` · An app calling the nexus-chat engine now says only WHO is calling. The per-call context carried five\nvalues — the clock, the workspace identity, the acting agent, the caller's session and the\ndepth-guard counter — and four of them were things the engine already knew.\n\nThe workspace identity comes from the handle: one handle is one workspace, so there is no longer any\nway for two calls to claim different ones, and that is the value access rights will hang off later.\n`Engine::origin()` reports it, for an app that needs to address what it just wrote. **The identity is\nnow the workspace's own replica prefix** — the four characters `nxc init` prints as `prefix:`. In a\nworkspace whose prefix is `ab12`, an agent is addressed as `ab12/coder`; every workspace on the\nmachine used to mint `local/coder` alike. The depth guard\nis unchanged and needs nothing passed: the chain depth is read back from the session map, where the\nspawning call recorded it, exactly as before — a caller could only ever raise it anyway. The clock\nbecomes optional, with the system clock as the default, so an app stops formatting a timestamp on\nevery call just to say \"now\". And the acting agent is read back from the caller's session: identity\ncomes from where a call came from, not from what it says about itself.\n\nWhat stays is the caller's session — and it is the SENDER's, not the recipient's. It is stamped onto\nthe message as the return address, and that is what lets a chain run in both directions: a\nsupervisor's `send --to` reaches the coder, and the coder's `reply --thread` wakes the supervisor\nagain because the address is on the message. An `actor` is still required where there is no session\nat all — a human at a terminal, or an app acting for its logged-in user — and a call with neither is\nrefused by name rather than being attributed to a default nobody chose.\n\n`nxc` moved with it, and had to: its default origin was the fixed word `local`, and had it stayed\nthat way an app would have posted as `/coder` while a terminal in the same project posted as\n`local/coder` — one declared channel with two disjoint sets of members, and nothing to report it.\nWith `NXC_ORIGIN` unset the command line now resolves the same workspace's prefix the library does.\nA handle spelled out somewhere — an old message, a script that passes `--consumer local/coder` — no\nlonger names the same agent; `NXC_ORIGIN=local` keeps the previous spelling. `NXC_ACTOR`,\n`NXC_SESSION`, `NXC_HOP` and `NXC_NOW` are unchanged, and `--json` stays reproducible.\n\nOne consequence is accepted rather than solved: the prefix is reassigned if syncing finds another\nworkspace already using it, and handles written under the old one do not follow.\n- `breaking` · The nexus-chat library seam has seven reading verbs. `status` says where an operation stands, and it\nnow carries per thread what two other reads used to: when the answer is due, and whether the thread\nholds the shared working copy or is queued behind it. `directory` says whom and WHAT you can\naddress — the declared personas, the declared channels together with what each is for, and the\nproject front doors other workspaces have made public, including ones that arrived by sync and that\nnothing here declares. `prime_as` is session start and already carries the unread catch-up and the\n\"threads you opened\" wake. `thread` is the one way to read what was said, and it now applies the\nchannel's declared `visibility`, so a member reading a `requester_only` board sees the request and\ntheir own words and not everybody else's answers. `transcript_page` reads a session's history in a\nwindow. `subscribe` keeps a view alive. `search` finds a message again by a piece of its text, in\nthe channels you are a member of — it is unchanged, and it stays because apps use it.\n\nTen reads went from that seam. Three were duplicates of a verb that stayed: `prime` was\n`prime_as` without a persona, `transcript` was `transcript_page` asked for the whole session, and\nthe third `open`-constructor existed for a single value that is now a field on `EngineConfig`.\n`public_channels` moved into `directory`; `inbox` and `opener_wake` into `prime_as`; `threads`,\n`thread_board` and `thread_quorums` into `status` — reading a named SET of threads is a scope of it\nnow, and still costs one query however many threads are in the set. Two are losses rather than\nmoves, and are worth knowing about: per-channel unread counts and reading a whole channel's history\nin one call have no replacement on the library seam.\n\nNothing changed for anyone typing `nxc`. Every command still exists and still prints what it\nprinted — `nxc list --json` gains the public front doors, which is the only visible difference.\n- `breaking` · `nxc` has one way to start a conversation and one way to answer it. `send --to` opens a thread and\nalways starts or wakes the persona or channel it names; `reply --thread` answers in one. The raw\npost that wrote into a channel and woke nobody is gone, and so is the reply that took a message id\nas a second address for a conversation the thread already names. On the library seam the same cut\nremoves `Engine::send` and `Engine::reply`; an app that held a message id now reads its thread — one\nread — and replies into that.\n\nEach of the two verbs takes three statements and nothing else. Which model a session runs on and by\nwhen a board must be answered are DECLARED, at the persona and at the channel, so `send --model` and\n`send --deadline` are gone rather than competing with the declaration. An absolute deadline is still\nexpressible — a channel's `timeout:` accepts an RFC3339 instant as well as a `30m` window.\n`--kind`, `--priority` and `--disposition` come off both verbs; they are interesting enough to bring\nback later, with more knowledge of what is actually needed. Two consequences are deliberate and\nworth knowing: `reply --escalate` still says \"I cannot carry this out\" and still holds the working\ncopy, because it was never really a `--kind` value; and the working-tree queue is now plain\nfirst-come-first-served, which is accepted for the moment so the problems a missing priority causes\ncan be collected before it comes back.\n\n`nxc send --to` now wants to know what a conversation is ABOUT: `--ref nxf_ids=`, repeatable, or\nan explicit `--no-ref` when there really is nothing. Leaving out both still sends — nothing is\nrefused — but it warns, on the terminal AND as a `refs_warning` field in the `--json` receipt, so an\napp sees it too.\n\n`nxc reply --if-unanswered` is unchanged and stays where it was: it is the agent sidecar's teardown\ncall, which writes a failure reply when a session ends without ever answering, so a thread does not\ngo quiet forever.\n- `changed` · The published documentation at nxsflow.com/open-source/docs now shows the suite as the three building\nblocks it is: each block's guides sit under their own heading instead of one flat list that was, in\ntruth, only nxf's. Because the three blocks share one page, every guide has a namespaced address —\n`nxf-getting-started` instead of `getting-started` — so `getting-started` and `commands` can exist in\nall three without colliding. Links already saved to the old page addresses keep working.\n\nNothing changes on the command line: `nxf guide getting-started` is spelled exactly as before. The\nnew address is the published one, and every cross-reference inside the guides moved with it.\n- `changed` · The embedding handle `Engine` now carries the four thread-link operations — `thread_link_add`,\n`thread_link_remove`, `thread_links` and `thread_items` — so an app that embeds the engine can\ncreate and read the conversation↔item edge in process, and no longer only through the `nxf thread`\nCLI. The link types `ThreadLink`, `LinkRelation` and `LinkWeight` are re-exported from the crate\nroot, so a consumer can name them in its own signatures without depending on the core crate.\nBehaviour is unchanged: only the item end of a link is checked for existence, so a thread from a\nchat store flow cannot read stays linkable, and a thread with no links reads back empty rather\nthan being rejected.\n- `changed` · `nxm guide` and `nxc guide` exist, and `nxs guide` reads the whole suite at once. Until now the only\nnarrative documentation in the suite was `nxf guide`, so \"one binary, three products\" was true of the\nbinary and not of the manual. Each building block now serves its own guides through the same\nmechanism — compiled into the binary, offline, `--json` in the same shape for all three — and\n`nxs guide` fans out over the active modules exactly as `nxs prime` does. It needs no workspace: with\nnone it lists every installed block, because `getting-started` is written for the reader who has not\ninitialized anything yet. A topic several blocks share is never picked for you; you are pointed at\n`nxf guide …` / `nxm guide …` / `nxc guide …` by name.\n\nThe `--json` contract of the existing guide verbs is unchanged, byte for byte. Chat's own guides ship\nin this same release; memory answers an honest empty list until its guides are written, and says so\nrather than pretending the topic is unknown.\n- `breaking` · In an ordered channel (`flow: sequential`), a step whose declared `timeout` ran out in the MIDDLE of\nthe order no longer ends the flow. The declaration always said a step starts once the one before it\nhas settled, and a settled step is one that answered *or* one whose deadline struck — but only an\nanswer ever reached the mechanism, so a silent middle step made the channel summarise and deliver,\nand every step behind it never ran. A lapsed step now starts the next one, exactly as an answer\ndoes; a lapse on the LAST step still consolidates, because there is nothing after it to start. What\nthe step never said is reported instead of silently absorbed: the receipt of the call that moved the\nflow past it, and the consolidation that carries no answer from it, both name it under the new\n`step_unanswered` finding. `nxc tick` reports such a call as `reason: \"advanced\"` with `acted:\nfalse` — it started a step rather than delivering a result, and it delivers nothing.", - "de": "### Neu\n- `nxc` hat Anleitungen. Sechs Stück, auf Deutsch und Englisch, offline ausgeliefert von `nxc guide`\nund veröffentlicht unter einem Abschnitt **chat** der Dokumentation auf\nnxsflow.com/open-source/docs: erste Schritte, Kernkonzepte, die vollständige Befehlsreferenz mit\njeder `--json`-Form, eine Persona deklarieren, einen Kanal deklarieren sowie Grenzen und Sicherheit.\nBisher hatte der dritte Baustein nichts außer `--help`, während die Suite-Dokumentation flow\nbeschrieb und sonst nichts.\n\nSie dokumentieren, was tatsächlich ausgeliefert ist, und sagen, was es nicht gibt. Ein Kanal ist eine\n**Deklaration** in `.nxs-personas/channels.yaml` und nie etwas, das ein Verb anlegt; ein geordneter\nKanal (`flow: sequential`) ist das, was die `workflow`-Verben ersetzt hat; `send --to` und `reply\n--thread` sind die einzigen zwei schreibenden Verben. Zu jedem entfernten Verb — `ask`, `channels\ncreate/dm/join/leave`, `agents register/list/search`, die ganze `workflow`-Gruppe, `send\n--model/--deadline/--kind` — steht da, was man stattdessen tut, denn die Hälfte einer\nvertrauenswürdigen Referenz ist das, wovon sie sagt, dass es nicht da ist.\n\nJeder Befehl, den die deutschen und englischen Anleitungen zeigen, wird ausgeführt, und die\ngezeigte Ausgabe IST die geprüfte Ausgabe — dieselbe Schleife „Beispiel = Test\", die die Anleitungen\nvon flow seit jeher fahren. `nxs guide` listet die Themen von chat jetzt neben denen von flow, und\nwo ein Themenname in zwei Bausteinen vorkommt (`getting-started`, `commands`), nennt es den Befehl je\nWerkzeug, statt für Sie auszuwählen.\n- Das Embedding-Handle `Engine` trägt jetzt die vier Thread-Link-Operationen — `thread_link_add`,\n`thread_link_remove`, `thread_links` und `thread_items` —, sodass eine einbettende App die Kante\nzwischen Gespräch und Eintrag im Prozess anlegen und lesen kann und nicht mehr nur über die CLI\n`nxf thread`. Die Typen `ThreadLink`, `LinkRelation` und `LinkWeight` sind aus der Wurzel des\nCrates re-exportiert und damit in eigenen Signaturen benennbar, ohne das Core-Crate zu binden. Am\nVerhalten ändert sich nichts: Geprüft wird nur das Eintrags-Ende, ein Faden aus einem Chat-Speicher,\nden flow nicht lesen kann, bleibt verknüpfbar, und ein Faden ohne Kanten liefert eine leere Liste,\nstatt abgelehnt zu werden.\n- `nxm guide` und `nxc guide` gibt es jetzt, und `nxs guide` liest die ganze Suite auf einmal. Bisher\nwar `nxf guide` die einzige Anleitung der Suite — \"ein Binary, drei Produkte\" stimmte damit für das\nBinary, aber nicht für das Handbuch. Jeder Baustein liefert seine eigenen Anleitungen jetzt über\ndieselbe Mechanik — ins Binary kompiliert, offline, `--json` in derselben Form für alle drei — und\n`nxs guide` fächert über die aktiven Module, genau wie `nxs prime`. Ein Workspace ist dafür nicht\nnötig: ohne einen werden alle installierten Bausteine gelistet, denn `getting-started` richtet sich\ngerade an die Leserin, die noch nichts eingerichtet hat. Ein Thema, das mehrere Bausteine führen,\nwird nie für Sie ausgewählt — Sie werden namentlich auf `nxf guide …` / `nxm guide …` /\n`nxc guide …` verwiesen.\n\nDer `--json`-Kontrakt der bestehenden Guide-Verben bleibt unverändert, Byte für Byte. Chats eigene\nAnleitungen kommen in derselben Auslieferung mit; memory antwortet mit einer ehrlichen leeren Liste,\nbis seine geschrieben sind — und sagt das, statt das Thema als unbekannt auszugeben.\n\n### Geändert\n- Eine App, die die nexus-chat-Engine aufruft, sagt jetzt nur noch, WER aufruft. Der Aufruf-Kontext\ntrug fünf Werte — die Uhr, die Workspace-Identität, den handelnden Agenten, die Sitzung des Aufrufers\nund den Zähler des Tiefenschutzes — und vier davon kannte die Engine bereits selbst.\n\nDie Workspace-Identität kommt aus dem Handle: ein Handle ist ein Workspace, also können zwei Aufrufe\nkeine unterschiedlichen Herkünfte mehr behaupten — und genau daran hängen später die Rechte.\n`Engine::origin()` gibt sie aus, für eine App, die adressieren muss, was sie gerade geschrieben hat.\n**Diese Identität ist jetzt der Replica-Prefix des Workspace** — die vier Zeichen, die `nxc init` als\n`prefix:` ausgibt. In einem Workspace mit dem Prefix `ab12` wird ein Agent als `ab12/coder`\nadressiert; früher hieß er in jedem Workspace der Maschine gleichermaßen `local/coder`.\nDer Tiefenschutz wirkt unverändert und braucht keine Angabe: die Kettentiefe wird aus der\nSitzungskarte gelesen, wo der auslösende Aufruf sie hinterlegt hat — erhöhen konnte ein Aufrufer sie\nohnehin nur. Die Uhr wird optional, mit der Systemuhr als Vorgabe, sodass eine App nicht mehr bei\njedem Aufruf einen Zeitstempel formatiert, nur um \"jetzt\" zu sagen. Und der handelnde Agent wird aus\nder Sitzung des Aufrufers gelesen: Identität kommt aus der Herkunft, nicht aus der Auskunft.\n\nWas bleibt, ist die Sitzung des Aufrufers — und zwar die des ABSENDERS, nicht die des Empfängers.\nSie wird als Rückadresse auf die Nachricht gestempelt, und genau daran hängt, dass eine Kette in\nbeide Richtungen läuft: das `send --to` eines Betreuers erreicht den Coder, und das `reply --thread`\ndes Coders weckt den Betreuer wieder, weil die Adresse auf der Nachricht steht. Ein `actor` wird\nweiterhin gebraucht, wo es gar keine Sitzung gibt — ein Mensch am Terminal oder eine App, die für\nihren angemeldeten Nutzer handelt — und ein Aufruf ohne beides wird benannt abgelehnt, statt einem\nVorgabewert zugeschrieben zu werden, den niemand gewählt hat.\n\n`nxc` zieht mit, und musste es: ihre Vorgabe war das feste Wort `local`. Wäre sie es geblieben,\nschriebe eine App als `/coder`, während ein Terminal im selben Projekt als `local/coder`\nschriebe — ein deklarierter Kanal mit zwei disjunkten Mitgliedersätzen, und nichts, das es meldet.\nOhne gesetztes `NXC_ORIGIN` löst die Kommandozeile jetzt denselben Prefix auf wie die Bibliothek.\nEin irgendwo ausgeschriebenes Handle — eine alte Nachricht, ein Skript mit `--consumer local/coder` —\nbenennt damit nicht mehr denselben Agenten; `NXC_ORIGIN=local` behält die bisherige Schreibweise.\n`NXC_ACTOR`, `NXC_SESSION`, `NXC_HOP` und `NXC_NOW` bleiben unverändert, und `--json` bleibt\nreproduzierbar.\n\nEine Folge wird bewusst in Kauf genommen statt gelöst: der Prefix wird neu vergeben, wenn die\nSynchronisation ihn bereits bei einem anderen Workspace vorfindet — bereits geschriebene Handles\nziehen dabei nicht mit.\n- Die Bibliotheksnaht von nexus-chat hat sieben lesende Verben. `status` sagt, wo ein Vorgang steht,\nund trägt jetzt je Faden mit, was zwei andere Lesungen bisher trugen: bis wann die Antwort fällig\nist, und ob der Faden die gemeinsame Arbeitskopie hält oder dahinter wartet. `directory` sagt, wen\nund WAS man ansprechen kann — die deklarierten Personas, die deklarierten Kanäle samt dem, wofür sie\nda sind, und die Vordertüren anderer Projekte, auch solche, die per Sync hereingekommen sind und die\nhier nichts deklariert. `prime_as` ist der Sitzungsstart und trägt Ungelesenes und die Weckliste\n\"Fäden, die du geöffnet hast\" ohnehin schon. `thread` ist der eine Weg zu lesen, was gesagt wurde,\nund wendet jetzt die deklarierte `visibility` des Kanals an: Wer bei `requester_only` mitliest, sieht\ndie Anfrage und die eigenen Worte, nicht die Antworten der anderen. `transcript_page` liest den\nVerlauf einer Sitzung fensterweise. `subscribe` hält eine Ansicht lebendig. `search` findet eine\nNachricht über ein Stück ihres Textes wieder, in den Kanälen, in denen man Mitglied ist — unverändert,\nund es bleibt, weil Apps es nutzen.\n\nZehn Lesungen sind von dieser Naht verschwunden. Drei waren Doppelungen eines Verbs, das bleibt:\n`prime` war `prime_as` ohne Persona, `transcript` war `transcript_page` für die ganze Sitzung, und\nder dritte `open`-Konstruktor gab es für einen einzigen Wert, der jetzt ein Feld an `EngineConfig`\nist. `public_channels` ist in `directory` aufgegangen, `inbox` und `opener_wake` in `prime_as`,\n`threads`, `thread_board` und `thread_quorums` in `status` — einen benannten SATZ von Fäden zu\nlesen ist jetzt eine seiner Formen und kostet weiterhin eine einzige Abfrage, wie viele Fäden auch\ndrin sind. Zwei sind Verluste und keine Umzüge, und das gehört gesagt: Ungelesen-Zähler je Kanal\nund der ganze Verlauf eines Kanals in einem Aufruf haben auf der Bibliotheksnaht keinen Ersatz.\n\nFür alle, die `nxc` tippen, ändert sich nichts. Jeder Befehl existiert weiter und gibt aus, was er\nausgegeben hat — `nxc list --json` nennt zusätzlich die öffentlichen Vordertüren, das ist der\neinzige sichtbare Unterschied.\n- `nxc` hat einen Weg, ein Gespräch zu beginnen, und einen, darauf zu antworten. `send --to` öffnet\neinen Faden und startet oder weckt immer die Persona oder den Kanal, den es benennt; `reply\n--thread` antwortet darin. Der rohe Post, der in einen Kanal schrieb und niemanden weckte, ist weg,\nebenso die Antwort, die eine Nachrichten-ID als zweite Adresse für ein Gespräch nahm, das der Faden\nlängst benennt. Auf der Bibliotheksnaht entfallen mit demselben Schnitt `Engine::send` und\n`Engine::reply`; eine App mit einer Nachrichten-ID liest jetzt deren Faden — eine Lesung — und\nantwortet dort hinein.\n\nJedes der beiden Verben trägt drei Angaben und sonst nichts. Auf welchem Modell eine Sitzung läuft\nund bis wann eine Tafel beantwortet sein muss, ist DEKLARIERT — an der Persona und am Kanal —,\ndeshalb entfallen `send --model` und `send --deadline`, statt neben der Deklaration eine zweite\nAntwort zu geben. Eine absolute Frist bleibt ausdrückbar: das `timeout:` eines Kanals nimmt einen\nRFC3339-Zeitpunkt genauso wie ein Fenster `30m`. `--kind`, `--priority` und `--disposition` fallen an\nbeiden Verben weg; sie sind interessant genug, um sie später zurückzuholen, dann aber mit mehr Wissen\nüber den tatsächlichen Bedarf. Zwei Folgen sind bewusst so gewollt und gut zu wissen: `reply\n--escalate` sagt weiterhin \"das kann ich nicht ausführen\" und hält weiterhin die Arbeitskopie, denn\nes war nie wirklich ein `--kind`-Wert; und die Warteschlange der Arbeitskopie ist jetzt schlicht\nWer-zuerst-kommt, was vorerst akzeptiert ist, damit sich die Probleme sammeln lassen, die eine\nfehlende Priorität verursacht, bevor sie zurückkehrt.\n\n`nxc send --to` will jetzt wissen, WORUM es in einem Gespräch geht: `--ref nxf_ids=`,\nwiederholbar, oder ein ausdrückliches `--no-ref`, wenn es wirklich nichts gibt. Lässt man beides\nweg, wird trotzdem gesendet — nichts wird abgelehnt —, aber es gibt einen Hinweis: im Terminal UND\nals Feld `refs_warning` in der `--json`-Quittung, damit auch eine App ihn sieht.\n\n`nxc reply --if-unanswered` bleibt unverändert an seinem Platz: Es ist der Abbau-Aufruf des\nAgenten-Sidecars, der eine Fehlantwort schreibt, wenn eine Sitzung endet, ohne je geantwortet zu\nhaben — damit ein Faden nicht für immer verstummt.\n- Die veröffentlichte Dokumentation auf nxsflow.com/open-source/docs zeigt die Suite jetzt als die drei\nBausteine, die sie ist: die Anleitungen jedes Bausteins stehen unter einer eigenen Überschrift statt\nin einer flachen Liste, die in Wahrheit nur nxf gehörte. Weil sich die drei Bausteine eine Seite\nteilen, hat jede Anleitung eine eigene Adresse bekommen — `nxf-getting-started` statt\n`getting-started` —, damit `getting-started` und `commands` in allen dreien vorkommen können, ohne\nsich zu überschreiben. Bereits gespeicherte Links auf die alten Seitenadressen funktionieren weiter.\n\nAuf der Kommandozeile ändert sich nichts: `nxf guide getting-started` schreibt sich genau wie vorher.\nDie neue Adresse ist die veröffentlichte, und jeder Querverweis in den Anleitungen ist mitgezogen.\n\n### Behoben\n- In einem geordneten Kanal (`flow: sequential`) beendet ein Schritt, dessen deklarierte Frist MITTEN\nin der Reihenfolge zuschlägt, den Ablauf nicht mehr. Die Deklaration sagte immer schon, dass ein\nSchritt beginnt, sobald der davor abgeschlossen ist — abgeschlossen heißt geantwortet *oder* Frist\nzugeschlagen —, aber nur eine Antwort erreichte die Mechanik: ein stummer mittlerer Schritt ließ den\nKanal zusammenfassen und ausliefern, und alle Schritte dahinter liefen nie. Ein zugeschlagener\nSchritt startet jetzt den nächsten, genau wie eine Antwort es tut; schlägt die Frist beim LETZTEN\nSchritt zu, wird weiterhin zusammengefasst, denn danach kommt nichts mehr. Was der Schritt nie\ngesagt hat, wird berichtet statt stillschweigend geschluckt: die Quittung des Aufrufs, der den\nAblauf daran vorbeigeführt hat, und die Zusammenfassung, die keine Antwort von ihm enthält, benennen\nihn beide unter dem neuen Befund `step_unanswered`. `nxc tick` meldet einen solchen Aufruf als\n`reason: \"advanced\"` mit `acted: false` — er hat einen Schritt gestartet und nichts ausgeliefert.\n\n### Facade-Kontrakt\n- `breaking` · Eine App, die die nexus-chat-Engine aufruft, sagt jetzt nur noch, WER aufruft. Der Aufruf-Kontext\ntrug fünf Werte — die Uhr, die Workspace-Identität, den handelnden Agenten, die Sitzung des Aufrufers\nund den Zähler des Tiefenschutzes — und vier davon kannte die Engine bereits selbst.\n\nDie Workspace-Identität kommt aus dem Handle: ein Handle ist ein Workspace, also können zwei Aufrufe\nkeine unterschiedlichen Herkünfte mehr behaupten — und genau daran hängen später die Rechte.\n`Engine::origin()` gibt sie aus, für eine App, die adressieren muss, was sie gerade geschrieben hat.\n**Diese Identität ist jetzt der Replica-Prefix des Workspace** — die vier Zeichen, die `nxc init` als\n`prefix:` ausgibt. In einem Workspace mit dem Prefix `ab12` wird ein Agent als `ab12/coder`\nadressiert; früher hieß er in jedem Workspace der Maschine gleichermaßen `local/coder`.\nDer Tiefenschutz wirkt unverändert und braucht keine Angabe: die Kettentiefe wird aus der\nSitzungskarte gelesen, wo der auslösende Aufruf sie hinterlegt hat — erhöhen konnte ein Aufrufer sie\nohnehin nur. Die Uhr wird optional, mit der Systemuhr als Vorgabe, sodass eine App nicht mehr bei\njedem Aufruf einen Zeitstempel formatiert, nur um \"jetzt\" zu sagen. Und der handelnde Agent wird aus\nder Sitzung des Aufrufers gelesen: Identität kommt aus der Herkunft, nicht aus der Auskunft.\n\nWas bleibt, ist die Sitzung des Aufrufers — und zwar die des ABSENDERS, nicht die des Empfängers.\nSie wird als Rückadresse auf die Nachricht gestempelt, und genau daran hängt, dass eine Kette in\nbeide Richtungen läuft: das `send --to` eines Betreuers erreicht den Coder, und das `reply --thread`\ndes Coders weckt den Betreuer wieder, weil die Adresse auf der Nachricht steht. Ein `actor` wird\nweiterhin gebraucht, wo es gar keine Sitzung gibt — ein Mensch am Terminal oder eine App, die für\nihren angemeldeten Nutzer handelt — und ein Aufruf ohne beides wird benannt abgelehnt, statt einem\nVorgabewert zugeschrieben zu werden, den niemand gewählt hat.\n\n`nxc` zieht mit, und musste es: ihre Vorgabe war das feste Wort `local`. Wäre sie es geblieben,\nschriebe eine App als `/coder`, während ein Terminal im selben Projekt als `local/coder`\nschriebe — ein deklarierter Kanal mit zwei disjunkten Mitgliedersätzen, und nichts, das es meldet.\nOhne gesetztes `NXC_ORIGIN` löst die Kommandozeile jetzt denselben Prefix auf wie die Bibliothek.\nEin irgendwo ausgeschriebenes Handle — eine alte Nachricht, ein Skript mit `--consumer local/coder` —\nbenennt damit nicht mehr denselben Agenten; `NXC_ORIGIN=local` behält die bisherige Schreibweise.\n`NXC_ACTOR`, `NXC_SESSION`, `NXC_HOP` und `NXC_NOW` bleiben unverändert, und `--json` bleibt\nreproduzierbar.\n\nEine Folge wird bewusst in Kauf genommen statt gelöst: der Prefix wird neu vergeben, wenn die\nSynchronisation ihn bereits bei einem anderen Workspace vorfindet — bereits geschriebene Handles\nziehen dabei nicht mit.\n- `breaking` · Die Bibliotheksnaht von nexus-chat hat sieben lesende Verben. `status` sagt, wo ein Vorgang steht,\nund trägt jetzt je Faden mit, was zwei andere Lesungen bisher trugen: bis wann die Antwort fällig\nist, und ob der Faden die gemeinsame Arbeitskopie hält oder dahinter wartet. `directory` sagt, wen\nund WAS man ansprechen kann — die deklarierten Personas, die deklarierten Kanäle samt dem, wofür sie\nda sind, und die Vordertüren anderer Projekte, auch solche, die per Sync hereingekommen sind und die\nhier nichts deklariert. `prime_as` ist der Sitzungsstart und trägt Ungelesenes und die Weckliste\n\"Fäden, die du geöffnet hast\" ohnehin schon. `thread` ist der eine Weg zu lesen, was gesagt wurde,\nund wendet jetzt die deklarierte `visibility` des Kanals an: Wer bei `requester_only` mitliest, sieht\ndie Anfrage und die eigenen Worte, nicht die Antworten der anderen. `transcript_page` liest den\nVerlauf einer Sitzung fensterweise. `subscribe` hält eine Ansicht lebendig. `search` findet eine\nNachricht über ein Stück ihres Textes wieder, in den Kanälen, in denen man Mitglied ist — unverändert,\nund es bleibt, weil Apps es nutzen.\n\nZehn Lesungen sind von dieser Naht verschwunden. Drei waren Doppelungen eines Verbs, das bleibt:\n`prime` war `prime_as` ohne Persona, `transcript` war `transcript_page` für die ganze Sitzung, und\nder dritte `open`-Konstruktor gab es für einen einzigen Wert, der jetzt ein Feld an `EngineConfig`\nist. `public_channels` ist in `directory` aufgegangen, `inbox` und `opener_wake` in `prime_as`,\n`threads`, `thread_board` und `thread_quorums` in `status` — einen benannten SATZ von Fäden zu\nlesen ist jetzt eine seiner Formen und kostet weiterhin eine einzige Abfrage, wie viele Fäden auch\ndrin sind. Zwei sind Verluste und keine Umzüge, und das gehört gesagt: Ungelesen-Zähler je Kanal\nund der ganze Verlauf eines Kanals in einem Aufruf haben auf der Bibliotheksnaht keinen Ersatz.\n\nFür alle, die `nxc` tippen, ändert sich nichts. Jeder Befehl existiert weiter und gibt aus, was er\nausgegeben hat — `nxc list --json` nennt zusätzlich die öffentlichen Vordertüren, das ist der\neinzige sichtbare Unterschied.\n- `breaking` · `nxc` hat einen Weg, ein Gespräch zu beginnen, und einen, darauf zu antworten. `send --to` öffnet\neinen Faden und startet oder weckt immer die Persona oder den Kanal, den es benennt; `reply\n--thread` antwortet darin. Der rohe Post, der in einen Kanal schrieb und niemanden weckte, ist weg,\nebenso die Antwort, die eine Nachrichten-ID als zweite Adresse für ein Gespräch nahm, das der Faden\nlängst benennt. Auf der Bibliotheksnaht entfallen mit demselben Schnitt `Engine::send` und\n`Engine::reply`; eine App mit einer Nachrichten-ID liest jetzt deren Faden — eine Lesung — und\nantwortet dort hinein.\n\nJedes der beiden Verben trägt drei Angaben und sonst nichts. Auf welchem Modell eine Sitzung läuft\nund bis wann eine Tafel beantwortet sein muss, ist DEKLARIERT — an der Persona und am Kanal —,\ndeshalb entfallen `send --model` und `send --deadline`, statt neben der Deklaration eine zweite\nAntwort zu geben. Eine absolute Frist bleibt ausdrückbar: das `timeout:` eines Kanals nimmt einen\nRFC3339-Zeitpunkt genauso wie ein Fenster `30m`. `--kind`, `--priority` und `--disposition` fallen an\nbeiden Verben weg; sie sind interessant genug, um sie später zurückzuholen, dann aber mit mehr Wissen\nüber den tatsächlichen Bedarf. Zwei Folgen sind bewusst so gewollt und gut zu wissen: `reply\n--escalate` sagt weiterhin \"das kann ich nicht ausführen\" und hält weiterhin die Arbeitskopie, denn\nes war nie wirklich ein `--kind`-Wert; und die Warteschlange der Arbeitskopie ist jetzt schlicht\nWer-zuerst-kommt, was vorerst akzeptiert ist, damit sich die Probleme sammeln lassen, die eine\nfehlende Priorität verursacht, bevor sie zurückkehrt.\n\n`nxc send --to` will jetzt wissen, WORUM es in einem Gespräch geht: `--ref nxf_ids=`,\nwiederholbar, oder ein ausdrückliches `--no-ref`, wenn es wirklich nichts gibt. Lässt man beides\nweg, wird trotzdem gesendet — nichts wird abgelehnt —, aber es gibt einen Hinweis: im Terminal UND\nals Feld `refs_warning` in der `--json`-Quittung, damit auch eine App ihn sieht.\n\n`nxc reply --if-unanswered` bleibt unverändert an seinem Platz: Es ist der Abbau-Aufruf des\nAgenten-Sidecars, der eine Fehlantwort schreibt, wenn eine Sitzung endet, ohne je geantwortet zu\nhaben — damit ein Faden nicht für immer verstummt.\n- `changed` · Die veröffentlichte Dokumentation auf nxsflow.com/open-source/docs zeigt die Suite jetzt als die drei\nBausteine, die sie ist: die Anleitungen jedes Bausteins stehen unter einer eigenen Überschrift statt\nin einer flachen Liste, die in Wahrheit nur nxf gehörte. Weil sich die drei Bausteine eine Seite\nteilen, hat jede Anleitung eine eigene Adresse bekommen — `nxf-getting-started` statt\n`getting-started` —, damit `getting-started` und `commands` in allen dreien vorkommen können, ohne\nsich zu überschreiben. Bereits gespeicherte Links auf die alten Seitenadressen funktionieren weiter.\n\nAuf der Kommandozeile ändert sich nichts: `nxf guide getting-started` schreibt sich genau wie vorher.\nDie neue Adresse ist die veröffentlichte, und jeder Querverweis in den Anleitungen ist mitgezogen.\n- `changed` · Das Embedding-Handle `Engine` trägt jetzt die vier Thread-Link-Operationen — `thread_link_add`,\n`thread_link_remove`, `thread_links` und `thread_items` —, sodass eine einbettende App die Kante\nzwischen Gespräch und Eintrag im Prozess anlegen und lesen kann und nicht mehr nur über die CLI\n`nxf thread`. Die Typen `ThreadLink`, `LinkRelation` und `LinkWeight` sind aus der Wurzel des\nCrates re-exportiert und damit in eigenen Signaturen benennbar, ohne das Core-Crate zu binden. Am\nVerhalten ändert sich nichts: Geprüft wird nur das Eintrags-Ende, ein Faden aus einem Chat-Speicher,\nden flow nicht lesen kann, bleibt verknüpfbar, und ein Faden ohne Kanten liefert eine leere Liste,\nstatt abgelehnt zu werden.\n- `changed` · `nxm guide` und `nxc guide` gibt es jetzt, und `nxs guide` liest die ganze Suite auf einmal. Bisher\nwar `nxf guide` die einzige Anleitung der Suite — \"ein Binary, drei Produkte\" stimmte damit für das\nBinary, aber nicht für das Handbuch. Jeder Baustein liefert seine eigenen Anleitungen jetzt über\ndieselbe Mechanik — ins Binary kompiliert, offline, `--json` in derselben Form für alle drei — und\n`nxs guide` fächert über die aktiven Module, genau wie `nxs prime`. Ein Workspace ist dafür nicht\nnötig: ohne einen werden alle installierten Bausteine gelistet, denn `getting-started` richtet sich\ngerade an die Leserin, die noch nichts eingerichtet hat. Ein Thema, das mehrere Bausteine führen,\nwird nie für Sie ausgewählt — Sie werden namentlich auf `nxf guide …` / `nxm guide …` /\n`nxc guide …` verwiesen.\n\nDer `--json`-Kontrakt der bestehenden Guide-Verben bleibt unverändert, Byte für Byte. Chats eigene\nAnleitungen kommen in derselben Auslieferung mit; memory antwortet mit einer ehrlichen leeren Liste,\nbis seine geschrieben sind — und sagt das, statt das Thema als unbekannt auszugeben.\n- `breaking` · In einem geordneten Kanal (`flow: sequential`) beendet ein Schritt, dessen deklarierte Frist MITTEN\nin der Reihenfolge zuschlägt, den Ablauf nicht mehr. Die Deklaration sagte immer schon, dass ein\nSchritt beginnt, sobald der davor abgeschlossen ist — abgeschlossen heißt geantwortet *oder* Frist\nzugeschlagen —, aber nur eine Antwort erreichte die Mechanik: ein stummer mittlerer Schritt ließ den\nKanal zusammenfassen und ausliefern, und alle Schritte dahinter liefen nie. Ein zugeschlagener\nSchritt startet jetzt den nächsten, genau wie eine Antwort es tut; schlägt die Frist beim LETZTEN\nSchritt zu, wird weiterhin zusammengefasst, denn danach kommt nichts mehr. Was der Schritt nie\ngesagt hat, wird berichtet statt stillschweigend geschluckt: die Quittung des Aufrufs, der den\nAblauf daran vorbeigeführt hat, und die Zusammenfassung, die keine Antwort von ihm enthält, benennen\nihn beide unter dem neuen Befund `step_unanswered`. `nxc tick` meldet einen solchen Aufruf als\n`reason: \"advanced\"` mit `acted: false` — er hat einen Schritt gestartet und nichts ausgeliefert." - } - }, - { - "version": "0.60.0", - "date": "2026-08-21", - "items": [ - { - "type": "added", - "en": "**Every channel declares its consolidator** — the thing that takes the members' answers and produces\nthe one answer the requester gets. It always existed, but it was neither declared nor configurable:\nyou could say *that* your channel summarises, and you could write the prompt, but not which model\nwould run it, and there was nothing written down about the other form at all.\n\nThere are two output forms, and a channel names one:\n\n- **`on_complete: pass_through`** (the default, and what a channel that says nothing gets) hands the\n answers over as they stand. That handover now has a **defined shape**: a short header line naming\n the channel, the thread and how many messages it collected, followed by one delimited block per\n message, each labelled with who wrote it. This replaces a one-line-per-answer rendering that could\n not be read at all once an answer ran to several lines — which, for an agent, is the normal case.\n The blocks themselves sit inside a section marked explicitly as untrusted data, with a note that\n the author label on each block is a claim and not an authenticated fact: what a member writes is\n free text, and the party this handover reaches is an agent that would otherwise read all of it as\n coming from its own operator. It is the same marking a summarising channel already puts around the\n answers it hands to its model.\n- **`on_complete: summarize`** runs a model over the answers. The prompt was already declarable as\n `summary_prompt:`; the model now is too, as **`summary_model: fable | opus | sonnet`** — the same\n three names a persona uses. A summarising channel that names no model folds at the `junior` band\n (`sonnet`) rather than at whatever the underlying default happens to be, so the answer to \"which\n model summarised this?\" is now something you can look up instead of infer. Declaring\n `summary_model:` on a channel that does not summarise is reported as a mistake in the declaration.\n\n**A channel never summarises away an \"I cannot\".** If any member replied with `--escalate`, the\nchannel hands the answers over as they stand — no model runs over them — and says so in a line of\nits own above them. This closes the gap the previous release disclosed: an escalation used to travel\nupward only on a channel that passes its answers through, and stopped at a channel that summarises.\nIt now travels on both, and \"no escalation reached me\" is a real answer again on every channel.\n\n**Where that changes what an ORDERED channel does, and it does:** a step of a `flow: sequential`\nchannel that hands work to another channel is waiting on that channel's verdict, and an escalating\nchannel produces none. The escalation itself is what the supervisor branches on — a declared signal\nrather than a token somebody had to invent — so the flow does not move on as if the work had been\ndone, and the escalation stays readable on the channel it happened on.\n\n**Two callers finishing the same channel at the same instant now deliver once.** A completing reply\nlanding at the same moment as a timeout re-check could previously hand the requester the same\nanswers twice on a pass-through channel; a summarising one was already protected. Both are now\nguarded by the same mechanism.\n\nExisting channel declarations are unaffected: every field is optional, a declaration written before\nthis release parses and behaves exactly as it did, and a channel that says nothing about its\nconsolidator still has one.", - "de": "**Jeder Kanal deklariert seinen Konsolidierer** — das, was aus den Antworten der Mitglieder die eine\nAntwort macht, die der Anforderer bekommt. Es gab ihn immer, aber er war weder deklariert noch\neinstellbar: Man konnte sagen, *dass* ein Kanal zusammenfasst, und den Prompt schreiben — aber nicht,\nwelches Modell ihn ausführt; und über die andere Ausgabeform stand nirgends etwas.\n\nEs gibt zwei Ausgabeformen, und ein Kanal benennt eine davon:\n\n- **`on_complete: pass_through`** (die Voreinstellung, und das, was ein Kanal ohne Angabe bekommt)\n reicht die Antworten durch, wie sie sind. Diese Übergabe hat jetzt eine **definierte Form**: eine\n kurze Kopfzeile mit Kanal, Faden und Anzahl der gesammelten Nachrichten, danach je Nachricht ein\n abgegrenzter Block mit dem Namen dessen, der sie geschrieben hat. Das ersetzt eine Darstellung mit\n einer Zeile je Antwort, die schlicht nicht lesbar war, sobald eine Antwort mehrere Zeilen hatte —\n und für einen Agenten ist das der Normalfall. Die Blöcke selbst stehen in einem Abschnitt, der\n ausdrücklich als nicht vertrauenswürdige Daten gekennzeichnet ist, samt Hinweis, dass die\n Absenderangabe an jedem Block eine Behauptung ist und keine geprüfte Tatsache: Was ein Mitglied\n schreibt, ist freier Text, und die Gegenstelle ist ein Agent, der sonst alles davon als Anweisung\n der eigenen Seite liest. Es ist dieselbe Kennzeichnung, die ein zusammenfassender Kanal schon\n bisher um die Antworten legt, die er seinem Modell übergibt.\n- **`on_complete: summarize`** lässt ein Modell über die Antworten laufen. Der Prompt war schon als\n `summary_prompt:` deklarierbar; das Modell jetzt ebenfalls, als **`summary_model: fable | opus |\n sonnet`** — dieselben drei Namen, die auch eine Persona verwendet. Ein zusammenfassender Kanal ohne\n Modellangabe faltet auf der Stufe `junior` (`sonnet`) statt auf einer stillschweigenden\n Voreinstellung; die Frage \"welches Modell hat das zusammengefasst?\" lässt sich also nachschlagen\n statt erraten. Ein `summary_model:` an einem Kanal, der gar nicht zusammenfasst, wird als Fehler in\n der Deklaration gemeldet.\n\n**Ein Kanal fasst ein \"ich kann nicht\" niemals weg.** Hat ein Mitglied mit `--escalate` geantwortet,\nreicht der Kanal die Antworten durch, wie sie sind — kein Modell läuft darüber — und sagt es in einer\neigenen Zeile darüber. Damit ist die Lücke geschlossen, die das vorige Release offengelegt hat: Eine\nEskalation drang bisher nur auf einem durchreichenden Kanal nach oben vor und blieb an einem\nzusammenfassenden stehen. Jetzt dringt sie auf beiden vor, und \"bei mir ist keine Eskalation\nangekommen\" ist auf jedem Kanal wieder eine echte Auskunft.\n\n**Wo das ändert, was ein GEORDNETER Kanal tut — und das tut es:** Ein Schritt eines\n`flow: sequential`-Kanals, der Arbeit an einen weiteren Kanal übergibt, wartet auf dessen Urteil, und\nein eskalierender Kanal liefert keines. Worauf der Betreuer verzweigt, ist die Eskalation selbst —\nein deklariertes Signal statt eines Tokens, das sich jemand ausdenken musste. Der Ablauf schaltet\nalso nicht weiter, als wäre die Arbeit getan, und die Eskalation bleibt an dem Kanal lesbar, an dem\nsie passiert ist.\n\n**Zwei Aufrufer, die denselben Kanal im selben Augenblick abschließen, stellen jetzt einmal zu.**\nTraf eine abschließende Antwort mit einer Fristprüfung zusammen, konnte ein durchreichender Kanal dem\nAnforderer dieselben Antworten zweimal übergeben; ein zusammenfassender war bereits geschützt. Beide\nsichert jetzt dieselbe Mechanik.\n\nBestehende Kanal-Deklarationen sind nicht betroffen: Jedes Feld ist optional, eine vor diesem Release\ngeschriebene Deklaration wird unverändert gelesen und verhält sich unverändert, und ein Kanal, der\nüber seinen Konsolidierer nichts sagt, hat trotzdem einen.", - "facade": "breaking" - }, - { - "type": "added", - "en": "**A channel can now declare its FLOW**, and `nxc send --to ` starts it. Write\n`flow: sequential` on a channel in `channels.yaml` and the order of its `members` stops being a list\nand becomes a fixed order: the channel's supervisor starts the first one alone, and each later one\nonly once the one before it has answered or its deadline has struck. The default is unchanged —\na channel that says nothing about `flow` still starts every member at once, exactly as before.\n\n**A step of that flow may address a whole CHANNEL, not only a persona** — the same two things\n`nxc send --to ` has always reached. A channel named as a step gets its own channel thread\nbelow the flow's, runs its own members and its own consolidator, and hands back its consolidated\nanswer as that step's answer. Where a role and a channel are declared under the same name, the ROLE\nwins, so every `members:` list written before this release means exactly what it meant.\n\nTwo rules refuse a flow that cannot work. A `sequential` channel may not name the same step twice\n(a repeat would be a loop, and there is no declared signal that could route one), and no channel's\nflow may reach ITSELF through its members — the latter is refused when the declarations are loaded,\nnot merely reported, because such a flow does not degrade: it opens a thread and starts a paid\nsession at every hop until the depth guard stops it.\n\n**This is what made the `nxc workflow` group removable in the same release.** A fixed order of a\nrole step followed by a channel step — the shape the shipped example's declared workflow had — runs\nhere in the same order, with no run record behind it, using only the two mutation verbs `send --to`\nand `reply --thread`.\n\nNeither `--expect` nor a declared `expects: ` can be combined with `flow: sequential`. Both\ndecide who answers and in what order, which on an ordered channel is what `members` already decides —\nand a subset is the more dangerous of the two, because it is what the engine actually reads when one\nis present, so an ordered channel declaring `expects: [b, a]` would have run in the subset's order\nwhile its `members` said otherwise. Both are refused by name rather than silently honoured in the\nother list's favour, so on an ordered channel `members` is the only list there is.", - "de": "**Ein Kanal kann jetzt seinen ABLAUF deklarieren**, und `nxc send --to ` startet ihn. Wer\n`flow: sequential` an einen Kanal in `channels.yaml` schreibt, macht aus der Reihenfolge seiner\n`members` statt einer Aufzählung eine feste Reihenfolge: der Betreuer des Kanals startet den ersten\nSchritt allein, und jeden weiteren erst, wenn der vorige geantwortet hat oder seine Frist zugeschlagen\nist. Der Standard bleibt unverändert — ein Kanal ohne `flow` startet weiterhin alle Mitglieder auf\neinmal.\n\n**Ein Schritt dieses Ablaufs darf einen ganzen KANAL adressieren, nicht nur eine Persona** — dieselben\nzwei Dinge, die `nxc send --to ` immer schon erreicht hat. Ein als Schritt genannter Kanal\nbekommt seinen eigenen Kanal-Faden unterhalb des Ablaufs, führt seine eigenen Mitglieder und seinen\neigenen Konsolidierer aus und gibt dessen Ergebnis als Antwort dieses Schritts zurück. Tragen eine\nRolle und ein Kanal denselben Namen, gewinnt die ROLLE — jede vor diesem Release geschriebene\n`members:`-Liste bedeutet damit genau das, was sie bedeutet hat.\n\nZwei Regeln weisen einen Ablauf ab, der nicht laufen kann. Ein `sequential`-Kanal darf denselben\nSchritt nicht zweimal nennen (eine Wiederholung wäre eine Schleife, und es gibt kein deklariertes\nSignal, das eine solche verzweigen könnte), und kein Kanal-Ablauf darf über seine Mitglieder SICH\nSELBST erreichen — Letzteres wird beim Laden der Deklarationen abgelehnt und nicht bloß gemeldet,\ndenn ein solcher Ablauf verschlechtert sich nicht, sondern öffnet bei jedem Sprung einen Faden und\nstartet eine bezahlte Sitzung, bis die Tiefenbremse ihn stoppt.\n\n**Genau das machte die Gruppe `nxc workflow` im selben Release entfernbar.** Eine feste Reihenfolge\naus einem Rollen-Schritt und einem Kanal-Schritt — die Form, die der deklarierte Ablauf des\nmitgelieferten Beispiels hatte — läuft hier in derselben Reihenfolge, ohne Lauf-Datensatz dahinter\nund allein mit den beiden Mutationsverben `send --to` und `reply --thread`.\n\nWeder `--expect` noch ein deklariertes `expects: ` lässt sich mit `flow: sequential`\nkombinieren. Beide entscheiden, wer antwortet und in welcher Reihenfolge — was auf einem geordneten\nKanal bereits `members` entscheidet. Die Teilmenge ist dabei die gefährlichere von beiden, denn sie\nist es, die die Engine tatsächlich liest, wenn eine da ist: ein geordneter Kanal mit\n`expects: [b, a]` wäre in deren Reihenfolge gelaufen, während seine `members` etwas anderes sagten.\nBeides wird ausdrücklich abgewiesen statt still zugunsten der anderen Liste befolgt — auf einem\ngeordneten Kanal ist `members` damit die einzige Liste, die es gibt.", - "facade": "breaking" - }, - { - "type": "changed", - "en": "A declared channel's `timeout` is now a deadline PER MEMBER, and every transcript output by that\nmember restarts it.\n\nUntil now the `timeout` produced one absolute instant, stamped on every member of the channel, that\nthen ran stubbornly to its end: a channel declaring `timeout: 30m` gave up on its members thirty\nminutes after it was opened, whatever they were doing. That breaks exactly the case a timeout exists\nto survive — an agent that starts a shell command running for an hour is perfectly healthy at minute\nthirty, and cutting it off there destroys an hour of work and hands the requester an answer that is\nmissing the one member who was actually working.\n\nWhat `timeout: 30m` means now is \"thirty minutes of SILENCE from this member\". Behind every member of\na channel stands exactly one session, and every write to that session's transcript — a thought, a\ntool call, a result — restarts its window from that moment. A member that keeps showing signs of life\nis never given up on, however long its work takes; a member that shows none for the whole window\nstill times out, and the channel consolidates with the answers it has, noting who did not respond.\nThe clocks are independent: one member running out of time settles that member and nobody else.\n\nThree smaller consequences of the same change. A member asked AGAIN — a second turn on the same\nchannel — starts its window again from the moment it was asked, instead of inheriting one that had\nalready run out and being over before it began; the re-check that eventually gives up on it is\nscheduled along with that window, so a second turn is given up on automatically just like the first.\nA member that has to WAIT for the working copy before it can start now begins its window when it\nactually starts, rather than being given up on while it is still queued. And a channel's own\nscheduled re-check no longer forces a consolidation just because the instant it was scheduled for\nhas arrived — it finds the members alive, declines, and re-schedules itself for the next moment one\nof them falls due, so the automatic give-up still happens; it just happens at the right time now.\n\nAn unreadable `timeout` (`timeout: twenty-minutes`) is now refused with an error naming the channel\nfield and the value, before anything is opened, instead of being carried further as an argument\nerror about a `--deadline` flag nobody typed. A `--deadline` given as an absolute instant is\nunchanged and stays deliberately stubborn: naming a point in time means that point in time, and\nnothing moves it.", - "de": "Der `timeout` eines deklarierten Kanals ist jetzt eine Frist JE MITGLIED, und jede Transkript-Ausgabe\ndieses Mitglieds setzt sie zurück.\n\nBisher erzeugte der `timeout` einen einzigen absoluten Zeitpunkt, der jedem Mitglied des Kanals\naufgestempelt wurde und danach stur bis zum Ende lief: ein Kanal mit `timeout: 30m` gab seine\nMitglieder dreißig Minuten nach dem Öffnen auf, ganz gleich, was sie gerade taten. Genau den Fall, den\neine Frist überleben soll, macht das kaputt — ein Agent, der einen Shell-Befehl startet, der eine\nStunde läuft, ist in Minute dreißig kerngesund, und ihn dort abzuschneiden vernichtet eine Stunde\nArbeit und liefert dem Anforderer eine Antwort, in der ausgerechnet das Mitglied fehlt, das wirklich\ngearbeitet hat.\n\n`timeout: 30m` heißt jetzt \"dreißig Minuten STILLE von diesem Mitglied\". Hinter jedem Mitglied eines\nKanals steht genau eine Sitzung, und jeder Schreibvorgang in deren Transkript — ein Gedanke, ein\nWerkzeugaufruf, ein Ergebnis — startet das Fenster von diesem Moment an neu. Ein Mitglied, das\nweiterhin Lebenszeichen gibt, wird nie aufgegeben, wie lange seine Arbeit auch dauert; ein Mitglied,\ndas über das ganze Fenster keines gibt, läuft weiterhin ab, und der Kanal fasst zusammen, was er hat,\nund vermerkt, wer nicht geantwortet hat. Die Uhren sind unabhängig: läuft eine ab, betrifft das genau\ndieses Mitglied und sonst niemanden.\n\nDrei kleinere Folgen derselben Änderung. Ein erneut gefragtes Mitglied — ein zweiter Zug im selben\nKanal — startet sein Fenster wieder ab dem Moment der Frage, statt eine längst abgelaufene Frist zu\nerben und vorbei zu sein, bevor es begonnen hat; die Nachprüfung, die es am Ende aufgibt, wird\nzusammen mit diesem Fenster eingeplant, sodass auch ein zweiter Zug automatisch aufgegeben wird und\nnicht nur der erste. Ein Mitglied, das erst auf die Arbeitskopie warten muss, beginnt sein Fenster,\nwenn es wirklich anfängt, statt aufgegeben zu werden, während es noch in der Warteschlange steht.\nUnd die eingeplante Nachprüfung eines Kanals erzwingt keine Zusammenfassung mehr, bloß weil der\nZeitpunkt erreicht ist, für den sie eingeplant war — sie findet die Mitglieder lebendig, lehnt ab\nund plant sich selbst auf den nächsten Zeitpunkt neu ein, an dem eines von ihnen fällig wird. Das\nautomatische Aufgeben passiert also weiterhin; es passiert jetzt nur zur richtigen Zeit.\n\nEin unlesbarer `timeout` (`timeout: twenty-minutes`) wird jetzt mit einem Fehler abgelehnt, der das\nKanal-Feld und den Wert nennt, bevor irgendetwas geöffnet wird — statt weiter unten als\nArgumentfehler über ein `--deadline` aufzutauchen, das niemand getippt hat. Ein `--deadline` als\nabsoluter Zeitpunkt bleibt unverändert und bewusst stur: wer einen Zeitpunkt nennt, meint diesen\nZeitpunkt, und nichts verschiebt ihn.", - "facade": "breaking" - }, - { - "type": "changed", - "en": "A declared channel now has TWO LEVELS, and every thread in it has exactly two ends. Until now\n`nxc send --to ` stamped ONE thread, wrote every member into its `expects_reply_from` and\nstarted all of them on that same thread: the requester and all three reviewers sat in one\nconversation, each of them read every other one's answer, and the data model's normal case was a\nthread with N+1 ends.\n\nWhat a `send --to ` produces now is the CHANNEL THREAD — the requester at one end, the\nchannel's SUPERVISOR at the other — and, hanging under it, one thread per member. Each member thread\nhas the supervisor at one end and that one member at the other. Three reviewers are four threads, not\none, and the reviewers cannot see each other, because they are not in the same conversation rather\nthan because a filter hides them.\n\n**The supervisor is machinery of the engine**, never a person and not a declared role either. It is\nthe reserved identity `/__channel__`, and it does three things: it takes the incoming request\n(a `send --to `, and every later `reply` into the channel thread) and opens the per-member\nsends itself; it waits for the SET of member threads rather than for one thread's list of names; and\nwhen they have all answered — or their deadlines have struck — it runs the channel's declared\n`on_complete` policy and delivers the result to the requester. It can be OCCUPIED by an agent\nsession: that is exactly what `on_complete: summarize` is, and that session's reply is what\ndischarges the channel thread.\n\nAll of it runs SYNCHRONOUSLY, inside the write path of the `reply` that caused it: the message is\nwritten, the obligation is recognised as discharged, the supervisor runs, the next threads are opened\nand their sessions are STARTED, and only then does the call return. What is waited for is the\nstarting, never the result. The reason is not latency — it is that only here does a failed\nconsequence still have an addressee: asynchronously the replying session is long gone by the time its\nconsequence runs, and a failure is noticed by nobody.\n\nTwo things a caller will see differently. `nxc send --to --json` reports `expects:\n[\"/__channel__\"]` — the channel thread's other end — and the per-member expectations are one\nlevel down, one per member thread, where `nxc status --thread ` shows them. And a member answers\nin ITS OWN thread: the id in the message a member is started with is its own, and nothing ever points\na member at the channel thread.\n\nA member's turn is now also RE-OPENED properly. A `reply` into the channel thread from its requester\nis the same door as the first message: the supervisor re-declares each member's expectation on its\nexisting thread and resumes that member's session with the obligation attached — so\n`nxc reply --thread --if-unanswered`, the call an agent session makes as it shuts down, has\nsomething to discharge from the second turn onwards. Before this, nothing re-declared an expectation\non a resume at all, and that safety net was a silent no-op past turn one.\n\nThis is what makes a channel a whole flow with no run record behind it — which is why the\n`nxc workflow` group could be taken away in this same release.", - "de": "Ein deklarierter Kanal hat jetzt ZWEI EBENEN, und jeder Faden darin hat genau zwei Enden. Bisher\nstempelte `nxc send --to ` EINEN Faden, schrieb sämtliche Mitglieder in dessen\n`expects_reply_from` und startete alle auf genau diesem Faden: Anforderer und alle drei Reviewer\nsaßen in einer Unterhaltung, jeder las die Antwort jedes anderen, und der Normalfall des Datenmodells\nwar ein Faden mit N+1 Enden.\n\nEin `send --to ` erzeugt jetzt den KANAL-FADEN — der Anforderer an einem Ende, der BETREUER\ndes Kanals am anderen — und darunter je Mitglied einen eigenen Faden. Jeder Mitglieder-Faden hat den\nBetreuer an einem Ende und genau dieses eine Mitglied am anderen. Drei Reviewer sind vier Fäden,\nnicht einer, und die Reviewer sehen einander nicht — weil sie nicht in derselben Unterhaltung sitzen,\nnicht weil ein Filter etwas verbirgt.\n\n**Der Betreuer ist Maschinerie der Engine**, nie ein Mensch und auch keine deklarierte Rolle. Er ist\ndie reservierte Identität `/__channel__` und tut dreierlei: er nimmt den eingehenden Auftrag\nentgegen (ein `send --to ` und jedes spätere `reply` in den Kanal-Faden) und öffnet die\nMitglieder-Sends selbst; er wartet auf die MENGE der Mitglieder-Fäden statt auf die Namensliste eines\neinzelnen Fadens; und wenn alle geantwortet haben — oder ihre Fristen zugeschlagen sind — führt er\ndie deklarierte `on_complete`-Regel des Kanals aus und liefert das Ergebnis an den Anforderer. Er\nKANN von einer Agenten-Sitzung besetzt werden: genau das ist `on_complete: summarize`, und die\nAntwort dieser Sitzung ist es, die den Kanal-Faden einlöst.\n\nDas alles läuft SYNCHRON, im Schreibpfad des `reply`, das es ausgelöst hat: Nachricht schreiben,\nVerpflichtung als eingelöst erkennen, Betreuer laufen lassen, nächste Fäden öffnen und die Sitzungen\nSTARTEN — und erst dann kehrt der Aufruf zurück. Gewartet wird auf das Starten, nie auf das Ergebnis.\nDer Grund ist nicht die Latenz, sondern dass nur hier eine gescheiterte Folge noch einen Adressaten\nhat: asynchron ist die antwortende Sitzung längst beendet, wenn ihre Folge läuft, und ein Fehlschlag\nfällt niemandem auf.\n\nZwei Dinge sieht ein Aufrufer anders. `nxc send --to --json` meldet `expects:\n[\"/__channel__\"]` — das andere Ende des Kanal-Fadens —, und die Erwartungen je Mitglied\nliegen eine Ebene tiefer, eine je Mitglieder-Faden, wo `nxc status --thread ` sie zeigt. Und ein\nMitglied antwortet in SEINEM EIGENEN Faden: die Id in der Nachricht, mit der ein Mitglied gestartet\nwird, ist seine eigene, und nichts verweist ein Mitglied je auf den Kanal-Faden.\n\nDer Zug eines Mitglieds wird jetzt auch richtig WIEDER GEÖFFNET. Ein `reply` in den Kanal-Faden durch\ndessen Anforderer ist dieselbe Tür wie die erste Nachricht: der Betreuer deklariert die Erwartung\njedes Mitglieds auf dessen vorhandenem Faden neu und setzt dessen Sitzung samt Verpflichtung fort —\ndamit `nxc reply --thread --if-unanswered`, der Aufruf, den eine Agenten-Sitzung beim\nHerunterfahren macht, ab dem zweiten Zug etwas einzulösen hat. Vorher deklarierte überhaupt nichts\neine Erwartung bei einer Fortsetzung neu, und dieses Sicherheitsnetz war ab Zug zwei ein stiller\nLeerlauf.\n\nDamit trägt ein Kanal einen ganzen Ablauf ohne jeden Lauf-Datensatz dahinter — und genau deshalb\nkonnte die Gruppe `nxc workflow` in demselben Release entfallen.", - "facade": "breaking" - }, - { - "type": "fixed", - "en": "**The reply that completed a board is now the one that actually completed it.** Once every handle a\nthread was waiting on has answered, the requester is woken with the collected answers and one of them\nis marked as the completing reply — the answer that closed the turn. That mark was picked by\ncomparing message ids. A message id begins with the millisecond it was minted, so two answers written\nin the same millisecond differ only in the random tail that follows and the mark fell on whichever of\nthem happened to sort higher. Between two synced devices the ids can disagree with the real order by\nany distance at all, because each device stamps its own.\n\nThe completing reply is now chosen by the same causal order the rest of a thread is read in — the\norder the messages themselves appear in, the one that decides which answer `nxc status` reports as\nthe last, and the one that decides whether the last answer was an escalation. Those three already\nagreed with each other; this one read did not, and it was the last place in the message store still\nordering by the raw id.\n\nWhat changes for you: on a thread whose answers land close together — the normal case when a\nchannel's members finish at once — the answer highlighted as the completing one, and the message id\nreported next to it, can differ from what an earlier release reported for the very same conversation.\nThe new answer is the correct one.\n\nUnchanged, and deliberately so: whether a completed board still needs your attention is still decided\nby your read cursor in the ordinary way, so nothing you had already read comes back.", - "de": "**Die Antwort, die eine Tafel abgeschlossen hat, ist jetzt die, die sie wirklich abgeschlossen hat.**\nSobald jeder Beteiligte geantwortet hat, auf den ein Faden gewartet hat, wird der Anforderer mit den\ngesammelten Antworten geweckt, und eine davon ist als die abschließende gekennzeichnet — die Antwort,\ndie den Zug beendet hat. Diese Kennzeichnung wurde über einen Vergleich der Nachrichten-Ids gewählt.\nEine Nachrichten-Id beginnt mit der Millisekunde, in der sie erzeugt wurde; zwei Antworten aus\nderselben Millisekunde unterscheiden sich also nur noch im Zufallsteil dahinter, und die\nKennzeichnung fiel auf diejenige, die zufällig höher sortierte. Zwischen zwei synchronisierten\nGeräten können die Ids der tatsächlichen Reihenfolge sogar beliebig weit widersprechen, weil jedes\nGerät seine eigenen stempelt.\n\nDie abschließende Antwort wird jetzt nach derselben kausalen Ordnung bestimmt, in der auch der Rest\neines Fadens gelesen wird — der Reihenfolge, in der die Nachrichten selbst erscheinen, der\nReihenfolge, aus der `nxc status` die letzte Antwort meldet, und der, die entscheidet, ob die letzte\nAntwort eine Eskalation war. Diese drei waren sich längst einig; diese eine Lesestelle war es nicht,\nund sie war die letzte im Nachrichtenspeicher, die noch nach der rohen Id ordnete.\n\nWas sich für Sie ändert: Bei einem Faden, dessen Antworten dicht beieinander eintreffen — der\nNormalfall, wenn die Mitglieder eines Kanals gleichzeitig fertig werden —, kann die als abschließend\nhervorgehobene Antwort und die daneben gemeldete Nachrichten-Id von dem abweichen, was eine frühere\nVersion für exakt dasselbe Gespräch gemeldet hat. Die neue Antwort ist die richtige.\n\nUnverändert, und zwar mit Absicht: Ob eine abgeschlossene Tafel noch Ihre Aufmerksamkeit braucht,\nentscheidet weiterhin Ihr Lesezeiger auf die gewohnte Weise — es kommt also nichts zurück, was Sie\nschon gelesen hatten.", - "facade": "breaking" - }, - { - "type": "added", - "en": "**A consequence that did not happen now has one defined place to appear.** Until now, when an answer\narrived and the thing it should have caused did not — a channel member that could not be started, a\nrequester that was never woken — the answering side got a clean receipt and the failure went to\nstderr, where no app and no agent reading `--json` ever sees\nit. The party that could have done something about it was, by then, the only party that never heard.\n\nEvery receipt now carries `warnings`: a list of what this very call should have caused and did not,\neach entry naming its class (`step_skipped`, `requester_not_woken`), the thread and session it\nconcerns, why it did not happen, and the underlying failure in words. It is\nalways present, empty included, so a reader cannot miss it by forgetting to look. Because a channel's\nconsolidation runs inside the reply that completes it, the session that answered learns about the\nfailure **in the same call, while it is still running** — `nxc --json reply` shows it, and\n`Engine::reply` returns it.\n\nOne thing this fixes that was previously not merely quiet but lost: a channel handing out a second\nround of work reported only the FIRST member it could not start; now every one of them is named.\n\n**`nxc status` is sharper about dead ends.** A thread that is answered with nothing following it used\nto read ORPHANED whenever the thread above it owed nothing — which is equally true of a branch that\nfinished perfectly. It now stays ORPHANED only if the thread above it never moved on from that\nanswer, and never for a thread hanging under an ordinary conversation that asked nobody anything. The\nrule errs towards saying nothing rather than raising a false alarm, because a place that cries wolf\nis a place nobody reads.\n\nA failed trigger's receipt also names WHY it did not start as a value rather than a sentence, telling\na runtime that refused the spawn (worth retrying) from a session the runtime had already reaped\n(needs a fresh one). `warnings` entries used to be plain strings and are now records; the sentence\nthey used to be is each record's `detail`, and the terminal output is unchanged.", - "de": "**Eine ausgebliebene Konsequenz hat jetzt einen definierten Ort, an dem sie auftaucht.** Bisher galt:\nKam eine Antwort an, blieb aber das aus, was sie hätte auslösen sollen — ein Kanalmitglied, das nicht\ngestartet werden konnte, ein Anforderer, der nie geweckt wurde —, dann bekam die antwortende Seite\neine saubere Quittung, und der Fehlschlag ging auf stderr, wo ihn keine App und kein Agent sieht, der `--json` liest. Ausgerechnet die Seite,\ndie etwas hätte tun können, erfuhr als Einzige nichts davon.\n\nJede Quittung trägt jetzt `warnings`: eine Liste dessen, was genau dieser Aufruf hätte auslösen\nsollen und nicht ausgelöst hat — je Eintrag mit Klasse (`step_skipped`, `requester_not_woken`),\nbetroffenem Faden und betroffener Sitzung, dem Grund und dem zugrundeliegenden Fehler im Klartext. Das Feld ist immer da, auch leer, damit man es nicht dadurch\nübersieht, dass man nicht hinschaut. Weil die Konsolidierung eines Kanals im selben Schreibvorgang\nläuft wie die Antwort, die sie auslöst, erfährt die antwortende Sitzung davon **im selben Aufruf,\nwährend sie noch läuft** — `nxc --json reply` zeigt es, `Engine::reply` gibt es zurück.\n\nEin Ding, das damit nicht mehr bloß leise, sondern gar nicht mehr verloren ist: Ein Kanal, der eine\nzweite Runde Arbeit verteilt, meldete bisher nur das ERSTE Mitglied, das er nicht starten konnte;\njetzt wird jedes einzelne genannt.\n\n**`nxc status` unterscheidet tote Enden jetzt schärfer.** Ein beantworteter Faden, aus dem nichts\nfolgte, galt bisher als VERWAIST, sobald der Faden darüber nichts mehr schuldete — was auf einen\nperfekt abgeschlossenen Zweig genauso zutrifft. Verwaist bleibt er jetzt nur, wenn der Faden darüber\nnach dieser Antwort nicht weitergegangen ist, und nie unterhalb einer gewöhnlichen Unterhaltung, die\nniemanden gefragt hat. Die Regel irrt lieber Richtung Schweigen als Richtung Fehlalarm: ein Ort, der\nzu oft Alarm schlägt, ist ein Ort, den niemand liest.\n\nDie Quittung eines fehlgeschlagenen Triggers nennt außerdem den GRUND als Wert statt als Satz und\nunterscheidet damit eine Laufzeit, die den Start verweigert hat (Wiederholen sinnvoll), von einer\nSitzung, die die Laufzeit längst eingesammelt hat (braucht eine neue). `warnings`-Einträge waren\nbisher einfache Zeichenketten und sind jetzt Datensätze; der Satz, der sie waren, ist das Feld\n`detail` jedes Eintrags, und die Ausgabe im Terminal bleibt unverändert.", - "facade": "breaking" - }, - { - "type": "fixed", - "en": "Two processes working on one workspace at the same time can no longer lose each other's writes.\n\nThe logical clock that orders every change used to be read once — when a process opened the\nworkspace — and never looked at the file again. Two processes that started from the same reading\nstamped their changes with the same number, and the merge rule, which keeps a change only if it\nstrictly beats what is already recorded, then dropped the second one without a word: the command\nreported success, exit code 0, and the board still showed the old value. A long-running app holding\nthe workspace beside short-lived `nxf`/`nxc`/`nxm` calls drifted the same way, and further with\nevery minute it stayed open.\n\nThat is exactly how this platform is meant to be used — several agents in one workspace — and it is\nwhy the defect went unseen: the test suite runs everything in a single process, where one handle\nobserves every change and the divergence cannot form. It reached every last-write-wins field, on the\nboard and in chat and memory alike. And it was not theoretical: on the day of the fix, three of the\ntwenty workspaces on the development machine carried its marks, the nexus-flow board among them.\n\nEvery change now re-reads the workspace's own high-water mark at the moment it is written, holding\nthe write lock across the read and the write — so a change is always stamped above everything\nalready in the file, whichever process put it there. Underneath it, the log now refuses a duplicate\nstamp outright rather than accepting it and leaving the merge rule to discard it in silence. A\nworkspace that already carries duplicates from before opens and works exactly as it did; it simply\ncannot be given the second guarantee retroactively, and is never blocked or rewritten for it.\n\nFacade (breaking): no signature moves, one documented behaviour does.\n`nxs_foundation::store::Store::apply` returns the ops it did not fold into the views; that vector\nnow also carries a merged op the log REFUSED, because a different op already holds its\n`(lamport, site)` coordinate. Previously such an op was stored and then silently outranked. A\nconsumer that reads the vector strictly as \"op shapes this version does not understand\" sees a new\nkind of member. It is reachable only when two replicas share a `site_id` — which 63 bits of entropy\nput out of reach for real replicas, and which the prefix registry already treats as a repairable\nmisconfiguration — so in practice no correct caller sees a change. `Store::emit` gains the matching\nassertion for a locally minted op, unreachable once the clock is refreshed under the write lock.", - "de": "Zwei Prozesse, die gleichzeitig an einem Arbeitsbereich arbeiten, können einander keine Schreibvorgänge\nmehr verlieren.\n\nDie logische Uhr, die jede Änderung ordnet, wurde bisher einmal gelesen — beim Öffnen des\nArbeitsbereichs — und sah danach nie wieder in die Datei. Zwei Prozesse, die vom selben Stand\nstarteten, prägten ihren Änderungen dieselbe Zahl auf, und die Zusammenführungsregel, die eine\nÄnderung nur behält, wenn sie das Verzeichnete echt übertrifft, verwarf die zweite daraufhin\nwortlos: Der Befehl meldete Erfolg, Exit-Code 0, und das Brett zeigte weiter den alten Wert. Eine\nlanglebige App, die den Arbeitsbereich neben kurzlebigen `nxf`/`nxc`/`nxm`-Aufrufen offen hält,\ndriftete auf dieselbe Weise ab — und mit jeder Minute weiter.\n\nGenau so ist diese Plattform gedacht — mehrere Agenten in einem Arbeitsbereich —, und genau deshalb\nblieb der Fehler unentdeckt: Die Testsuite läuft vollständig in EINEM Prozess, wo ein Handle jede\nÄnderung beobachtet und die Divergenz gar nicht entstehen kann. Betroffen war jedes\nLast-Write-Wins-Feld, auf dem Brett wie in Chat und Memory. Und es war nicht theoretisch: Am Tag der\nReparatur trugen drei der zwanzig Arbeitsbereiche auf der Entwicklungsmaschine seine Spuren, das\nnexus-flow-Brett darunter.\n\nJede Änderung liest den Höchststand des Arbeitsbereichs jetzt im Moment des Schreibens neu und hält\ndie Schreibsperre über Lesen und Schreiben hinweg — eine Änderung wird also immer über allem\ngestempelt, was schon in der Datei steht, gleich welcher Prozess es hineingeschrieben hat. Darunter\nweist das Protokoll einen doppelten Stempel jetzt rundheraus ab, statt ihn anzunehmen und der\nZusammenführungsregel zu überlassen, ihn still zu verwerfen. Ein Arbeitsbereich, der aus der Zeit\ndavor bereits Doppelstempel trägt, öffnet und arbeitet unverändert; ihm lässt sich die zweite\nZusicherung nur nicht rückwirkend geben — blockiert oder umgeschrieben wird er dafür nie.\n\nFassade (Bruch): Keine Signatur bewegt sich, ein dokumentiertes Verhalten schon.\n`nxs_foundation::store::Store::apply` liefert die Ops zurück, die es nicht in die Sichten gefaltet\nhat; dieser Vektor trägt jetzt auch eine übernommene Op, die das Protokoll ABGEWIESEN hat, weil eine\nandere Op deren Koordinate `(lamport, site)` bereits hält. Zuvor wurde eine solche Op gespeichert\nund danach still überstimmt. Ein Konsument, der den Vektor strikt als „Op-Formen, die diese Version\nnicht versteht\" liest, sieht darin eine neue Art von Eintrag. Erreichbar ist das nur, wenn zwei\nRepliken sich eine `site_id` teilen — was 63 Bit Entropie für echte Repliken ausschließen und was\ndie Präfix-Registry ohnehin als reparierbare Fehlkonfiguration behandelt —, in der Praxis sieht also\nkein korrekter Aufrufer eine Änderung. `Store::emit` bekommt die entsprechende Zusicherung für eine\nlokal geprägte Op; sie ist unerreichbar, sobald die Uhr unter der Schreibsperre aufgefrischt wird.", - "facade": "breaking" - }, - { - "type": "fixed", - "en": "A chain that holds the working copy can now commission more work on it without blocking itself.\n\nUntil now, any exclusive claim opened from inside a chain that was already holding the working copy\nwent to the back of the queue — behind the very chain that opened it. That chain could then never\nfinish, because what it was waiting for was the trigger it had just parked. Nothing started, nothing\nreported an error, and the queue would not drain even after the two-hour backstop. It took no exotic\nsetup: a plain `nxc send --to ` for a role that declares `working_tree: exclusive`, sent\nfrom a session already inside a protected chain, was enough — and a nested protected channel did the\nsame.\n\nThe cause was an asymmetry. The claim area has been \"the conversation the work was commissioned in,\ntogether with everything opened beneath it\" since the claim-area change, but only the RELEASE side\never asked it that way; the acquire side compared claim keys exactly and resolved a thread just one\nlevel upward. So work opened deeper in an area the caller already held came out with a different key\nand looked like a rival. Both ends now read the same area, so such a trigger inherits the claim\ninstead of queuing behind it — while a genuinely unrelated chain still waits its turn exactly as\nbefore.\n\nNot covered by this, and tracked separately: when a holder dies outright, nothing drains its queue,\nso whoever is already parked waits past the two-hour bound until some other chain acquires and\nreleases.", - "de": "Eine Kette, die die Arbeitskopie hält, kann jetzt weitere Arbeit daran beauftragen, ohne sich selbst\nzu blockieren.\n\nBisher stellte sich jeder exklusive Anspruch, der aus einer bereits haltenden Kette heraus geöffnet\nwurde, hinten in die Warteschlange — hinter genau die Kette, die ihn geöffnet hatte. Diese Kette\nkonnte dann nie fertig werden, denn worauf sie wartete, war der Auslöser, den sie selbst gerade\nangestellt hatte. Nichts startete, nichts meldete einen Fehler, und die Warteschlange leerte sich\nauch nach der Zwei-Stunden-Grenze nicht. Dafür brauchte es keinen ausgefallenen Aufbau: Ein\nschlichtes `nxc send --to ` für eine Rolle, die `working_tree: exclusive` deklariert,\nabgesetzt aus einer Sitzung, die schon in einer geschützten Kette steht, genügte — und ein\nverschachtelter geschützter Kanal ebenso.\n\nDie Ursache war eine Unsymmetrie. Der Anspruchsbereich ist seit der Anspruchsbereichs-Änderung „die\nUnterhaltung, in der die Arbeit beauftragt wurde, samt allem, was darunter geöffnet wird\" — aber nur\ndie FREIGABE-Seite hat ihn je so gelesen; die Erwerbsseite verglich Anspruchsschlüssel exakt und löste\neinen Faden nur eine Ebene nach oben auf. Arbeit, die tiefer in einem bereits gehaltenen Bereich\ngeöffnet wurde, bekam so einen anderen Schlüssel und sah aus wie ein Rivale. Beide Enden lesen jetzt\ndenselben Bereich, ein solcher Auslöser erbt den Anspruch, statt sich dahinter anzustellen — und eine\nwirklich fremde Kette wartet weiterhin genau wie zuvor.\n\nNicht davon erfasst und getrennt erfasst: Stirbt ein Halter hart, leert niemand seine Warteschlange;\nwer dort schon steht, wartet über die Zwei-Stunden-Grenze hinaus, bis irgendeine andere Kette\nerwirbt und wieder freigibt." - }, - { - "type": "removed", - "en": "**Declarations move to `.nxs-personas/`.** Who your agents are, and where they talk, is declared in\n`/.nxs-personas/` from now on — one `.yaml` per persona, and `channels.yaml`\nfor the channels they share, which is also where a flow is declared. The old `/roles/` folder is\nstill READ, so an existing project keeps working untouched, and the engine now says out loud that it\nread one: `nxc list` and `nxc prime` name the legacy folder and the path the declarations belong at,\nand `nxc prime --json` carries the same fact as data. **Nothing is moved for you.** An application\nthat has only opened someone else's project must not silently rewrite it — that is an irreversible\nchange to somebody else's property triggered by merely looking — so the move stays a thing you do\ndeliberately: copy the folder, and `.nxs-personas/` takes over the moment it carries a declaration.\n\n**Where a catalogue came from is now part of the answer.** `Engine::definitions()` still succeeds on\na workspace that declares nothing — a read that fails because there is nothing to read is a bad read,\nand it would turn a legitimate state into an exception an app has to catch just to render an\nonboarding screen — but the catalogue it returns now carries the resolution beside it: the path a\ndeclaration belongs at, which folder was actually read (`.nxs-personas/`, a legacy `roles/`, or\nneither), whether a legacy folder was found at all, and how many declarations there are. One\nstructure answers both \"where did this come from\" and \"is this project still on the old layout\".\n\n**The empty workspace stops being anonymous**, and each surface answers it in its own register. `nxc\nlist` and `nxc prime` are orientation: they do not fail, they say that nothing is declared and where\na declaration goes, and exit 0 — an empty list on its own is a lie by omission, and an error would\nmake a legitimate state exceptional. `nxc send --to ` is the one that genuinely refuses: it\nis an action with a mandatory target, and in a workspace with no declaration there is none, so it\nfails by name instead of reporting a missing target the caller would go looking for a typo in.\n\n**`nxc init` leaves the folder behind**, empty, with a short explanation of the format inside it and\nno shipped personas. It never touches an existing `roles/` folder, and the empty `.nxs-personas/` it\ncreates does not shadow one: the new location wins only once it carries a declaration of its own, so\nrunning `init` in an existing project cannot silently unteam it. A second `init` keeps an explainer\nyou have edited.\n\nThe two shipped example teams move with the folder:\n`crates/chat/examples/role-runtime-v2/.nxs-personas/` and\n`crates/chat/examples/role-runtime-v3/.nxs-personas/`.\n\n**The injection path is gone with it.** `EngineConfig` no longer takes a definition source and\n`Engine::set_definitions` no longer exists: personas and channels come from `.nxs-personas/` for an\nembedding app exactly as for the command line, and a later in-app role editor writes into that same\nfolder rather than into an app-owned database. The property the injection path existed for is\nunchanged and now costs nothing to state — an edit is visible to the next call on the handle and on\nevery existing clone of it, with no reopen and no torn-down subscription, because nothing is cached.\n**`Engine::definitions()` — the READ — stays**, and a gate holds it there rather than leaving it to\ncare: `crates/chat/tests/seam_disposition.rs` names it with its reason and fails if it disappears.\n\n**Three entrances come off the surface, and the session-start block is re-cut to what is left.**\n`nxc agents list` / `register` / `search` are gone: a team is DECLARED, not registered — a persona\nis a file in `.nxs-personas/`, which is readable, reviewable and survives the run, where a runtime\nprofile row lived in one workspace's database and nothing ever reviewed it. `nxc list` is the read\nover it and carries the two fields `agents search` searched. `nxc workflow tickets add` is gone with no replacement — as, in this\nsame release, is the whole `nxc workflow` group it hung under. What a message is about is what\n`--ref nxf_ids=` says on it. And `nxc inbox` / `nxc read` come off the AGENT surface while staying for a human and on\nthe app seam: what a session is TAUGHT at its start is now exactly `list`, `send --to`,\n`reply --thread`, `nxc status`, `search` and `transcript show`, because a persona is handed its\nunread at session start and on resume rather than asking for it.\n\n**A gate now records what happens to the library seam when an entrance goes.** For every verb taken\noff the command line it is decided and written down whether the corresponding read or write stays on\n`Engine` — and the gate holds both halves, so a removal cannot land without the record moving with\nit, and a read an app renders cannot quietly disappear with the verb that used to reach it.\n\n**And every `nxc --help` page is a golden now.** Nothing watched that text before, and it had\nalready bitten once; with verbs coming off the surface, a help page naming one that no longer exists\nis a false map an agent acts on.\n\nFacade (breaking): `Engine::workflow_tickets_add`, `orchestration::workflow_tickets_add`,\n`WorkflowTicketsAddRequest` and `WorkflowTicketsReceipt` are removed (and the rest of the workflow\nsurface with them, in this same release — see its own entry). `DefinitionSource` and\n`EngineConfig::definitions` are removed, as is\n`Engine::set_definitions`. `Workspace::roles_dir()` is gone, replaced by `workspace_root()`,\n`personas_dir()`, `legacy_roles_dir()` and `declaration_dir()` on `ChatWorkspaceExt`.\n`Definitions::from_roles_dir` is now `Definitions::from_dir` (read ONE named folder) beside the new\n`Definitions::resolve(workspace_root)` (resolve both locations and record which was read).\n`Definitions` gains `source()`, `len()` and `is_empty()`. `facade::prime`/`facade::prime_for` take a\n`&DeclarationSource` where they took a `&Path`, and `facade::PrimeReport` gains a required\n`declarations: DeclarationSource` field — so `nxc prime --json` gains a `declarations` object, always\npresent.", - "de": "**Deklarationen ziehen nach `.nxs-personas/` um.** Wer Ihre Agenten sind und wo sie sprechen, wird\nab jetzt in `/.nxs-personas/` deklariert — eine `.yaml` je Persona,\nund `channels.yaml` für die gemeinsamen Kanäle, worin auch ein Ablauf deklariert wird. Der alte Ordner\n`/roles/` wird weiterhin GELESEN, ein bestehendes Projekt läuft also unverändert\nweiter, und die Engine sagt jetzt laut, dass sie ihn gelesen hat: `nxc list` und `nxc prime` nennen\nden alten Ordner und den Pfad, an den die Deklarationen gehören, und `nxc prime --json` trägt\ndieselbe Tatsache als Daten. **Es wird nichts für Sie verschoben.** Eine Anwendung, die ein fremdes\nProjekt nur geöffnet hat, darf es nicht still umschreiben — das ist eine unumkehrbare Änderung am\nEigentum eines Dritten, ausgelöst durch bloßes Hinsehen. Der Umzug bleibt deshalb etwas, das Sie\nabsichtlich tun: Ordner kopieren, und `.nxs-personas/` übernimmt in dem Moment, in dem es selbst eine\nDeklaration trägt.\n\n**Woher ein Katalog stammt, gehört jetzt zur Antwort.** `Engine::definitions()` gelingt weiterhin in\neinem Arbeitsbereich, der nichts deklariert — eine Lesung, die scheitert, WEIL es nichts zu lesen\ngibt, ist eine schlechte Lesung, und sie machte aus einem legitimen Zustand eine Ausnahme, die eine\nApp nur abfangen müsste, um einen Einstiegsbildschirm zu zeichnen. Der zurückgegebene Katalog trägt\ndie Auflösung jetzt aber daneben: den Pfad, an den eine Deklaration gehört, den tatsächlich gelesenen\nOrdner (`.nxs-personas/`, ein altes `roles/`, oder keiner), ob überhaupt ein alter Ordner gefunden\nwurde, und die Anzahl. Eine Struktur beantwortet sowohl „woher kommt das hier\" als auch „liegt dieses\nProjekt noch im alten Format\".\n\n**Der leere Arbeitsbereich hört auf, anonym zu sein**, und jede Oberfläche antwortet in ihrem eigenen\nRegister. `nxc list` und `nxc prime` sind ORIENTIERUNG: Sie scheitern nicht, sie sagen, dass nichts\ndeklariert ist und wo eine Deklaration hingehört, und enden mit 0 — eine leere Liste allein ist eine\nLüge durch Auslassung, ein Fehler wäre falsch. `nxc send --to ` ist das eine, das wirklich\nablehnt: Es ist eine Handlung mit Pflichtziel, und ohne Deklaration gibt es keines. Es scheitert\nbenannt, statt ein fehlendes Ziel zu melden, in dem der Aufrufer dann einen Tippfehler suchen würde.\n\n**`nxc init` lässt den Ordner da**, leer, mit einer kurzen Erklärung der Form darin und ohne\nmitgelieferte Personas. Ein vorhandenes `roles/` wird nie angefasst, und das leere `.nxs-personas/`\nverdeckt es auch nicht: Der neue Ort gewinnt erst, sobald er selbst eine Deklaration trägt — ein\n`init` in einem bestehenden Projekt kann es also nicht still entteamen. Ein zweites `init` lässt eine\nvon Ihnen bearbeitete Erklärung stehen.\n\nDie beiden mitgelieferten Beispiel-Teams ziehen mit um:\n`crates/chat/examples/role-runtime-v2/.nxs-personas/` und\n`crates/chat/examples/role-runtime-v3/.nxs-personas/`.\n\n**Der Injektionsweg entfällt mit ihm.** `EngineConfig` nimmt keine Deklarationsquelle mehr, und\n`Engine::set_definitions` gibt es nicht mehr: Personas und Kanäle kommen für eine einbettende App\ngenauso aus `.nxs-personas/` wie für die Kommandozeile, und auch ein späterer Rollen-Editor in der\nApp schreibt in diesen Ordner statt in eine app-eigene Datenbank. Die Eigenschaft, für die es den\nInjektionsweg gab, bleibt unverändert und kostet jetzt nichts mehr: Eine Änderung ist beim nächsten\nAufruf auf dem Handle und auf jedem bestehenden Klon davon sichtbar, ohne Neuöffnen und ohne\nabgerissenes Abonnement — weil nichts zwischengespeichert wird. **`Engine::definitions()` — die\nLESUNG — bleibt**, und ein Tor hält sie dort fest, statt es der Sorgfalt zu überlassen:\n`crates/chat/tests/seam_disposition.rs` nennt sie samt Begründung und wird rot, wenn sie verschwindet.\n\n**Drei Eingänge verlassen die Oberfläche, und der Sitzungsstart-Block wird auf das neu\nzugeschnitten, was bleibt.** `nxc agents list` / `register` / `search` entfallen: Ein Team wird\nDEKLARIERT, nicht registriert — eine Persona ist eine Datei in `.nxs-personas/`, lesbar, prüfbar\nund den Lauf überdauernd, während eine Laufzeit-Profilzeile in genau einer Arbeitsbereichs-\nDatenbank lag und niemand sie je geprüft hat. `nxc list` ist die Lesung darüber und trägt die\nbeiden Felder, die `agents search` durchsuchte. `nxc workflow tickets add` entfällt ersatzlos — wie in\ndemselben Release die ganze Gruppe `nxc workflow`, unter der es hing. Womit eine Nachricht zu tun\nhat, sagt `--ref nxf_ids=` an ihr. Und `nxc inbox` / `nxc read` gehen von der AGENTEN-Oberfläche runter, bleiben aber\nfür einen Menschen und auf der App-Naht: Was eine Sitzung bei ihrem Start GELEHRT bekommt, ist\njetzt genau `list`, `send --to`, `reply --thread`, `nxc status`, `search` und `transcript show` —\neine Persona bekommt ihr Ungelesenes bei Sitzungsstart und bei Fortsetzung, statt danach zu fragen.\n\n**Ein Tor hält jetzt fest, was beim Wegnehmen mit der Bibliotheksnaht geschieht.** Für jedes von\nder Kommandozeile genommene Verb ist entschieden und aufgeschrieben, ob die zugehörige Lesung oder\nSchreibung auf `Engine` bleibt — und das Tor hält beide Hälften: Eine Wegnahme kann nicht landen,\nohne dass der Datensatz mitgeht, und eine Lesung, die eine App zeichnet, kann nicht still mit dem\nVerb verschwinden, das sie erreichte.\n\n**Und jede `--help`-Seite von `nxc` ist jetzt ein Golden.** Bisher sah kein Tor diesen Text an, und\nes hatte schon einmal zugebissen; wenn Verben von der Oberfläche gehen, ist eine Hilfeseite, die\neines nennt, das es nicht mehr gibt, eine falsche Karte, nach der ein Agent handelt.\n\nFassade (Bruch): `Engine::workflow_tickets_add`, `orchestration::workflow_tickets_add`,\n`WorkflowTicketsAddRequest` und `WorkflowTicketsReceipt` entfallen (und mit ihnen, in demselben\nRelease, die übrige Workflow-Oberfläche — siehe deren eigenen Eintrag). `DefinitionSource` und\n`EngineConfig::definitions` entfallen, ebenso\n`Engine::set_definitions`. `Workspace::roles_dir()` entfällt; an seine Stelle treten `workspace_root()`,\n`personas_dir()`, `legacy_roles_dir()` und `declaration_dir()` auf `ChatWorkspaceExt`.\n`Definitions::from_roles_dir` heißt jetzt `Definitions::from_dir` (liest EINEN benannten Ordner),\ndaneben steht das neue `Definitions::resolve(workspace_root)` (löst beide Orte auf und hält fest,\nwelcher gelesen wurde). `Definitions` bekommt `source()`, `len()` und `is_empty()`.\n`facade::prime`/`facade::prime_for` nehmen ein `&DeclarationSource`, wo sie ein `&Path` nahmen, und\n`facade::PrimeReport` bekommt das Pflichtfeld `declarations: DeclarationSource` — `nxc prime --json`\nträgt damit ein immer vorhandenes `declarations`-Objekt.", - "facade": "breaking" - }, - { - "type": "added", - "en": "An operation is now a THREAD TREE, and `nxc status` reads it. Until now nothing in the data model\nconnected two threads: a real chain — you ask the PM, the PM commissions the coding channel, that\nchannel's lead hands the work to a coder, the review of it fans out to three reviewers — was five to\nseven separate threads across three channels, and nobody could answer \"where do we stand in this?\".\n\nEvery thread now records the thread it was opened OUT OF. The rule is mechanical and needs nothing\nfrom the agent: a `nxc send --to ` issued from inside a running session hangs\nthe new thread under the thread that session is currently working in — the thread it was triggered\ninto, or the one whose reply woke it. A send with no session around it (you, at a terminal) opens a\nROOT, which is what makes it the top of a chain. Nothing is stored to say \"this is a root\": it is\nthe absence of an edge.\n\n`nxc status` answers \"where do we stand\" in three forms. `nxc status --thread ` shows the whole\ntree of the operation that thread belongs to, from the root down and across every channel it\ncrossed; `nxc status --channel ` lists the live operations that STARTED in a channel, as a way\nin; plain `nxc status` shows everything still going on in the workspace. `--json` carries the same\nrecord, and the identical read sits on the library seam as `Engine::status`, so an app builds its\noperation view from it rather than rebuilding the derivation. It is what replaces `workflow status` and\n`workflow list`, which this same release removes.\n\nEach thread reports one of three derived states: **open** (somebody still owes a reply here),\n**answered**, and **ORPHANED** — answered, nothing was opened out of it, nothing upstream is waiting\nfor it either, and the thread above it never moved on from it. The orphaned state is where a\nconsequence that silently failed to happen becomes visible without anyone reporting it; the last of\nthose conditions is what keeps it to the branches that FAILED rather than the ones that finished, and\nit is described in the entry about the place for failed consequences. One deliberate exception at the very top: a ROOT thread\nthat has been answered says `awaiting you` rather than reading like a thread nobody is working on\nany more. That is the normal end of an operation, not a standstill, and it is the only place a human\nappears in the flow at all.\n\nEach thread also carries the session of whoever is working on it, so a consumer holding a thread id\ncan follow that assignee's transcript without ever being told a session id — `nxc status --json`\ngives you the session, `nxc transcript show ` gives you the running account. **This works\nwhile the thread is still open and the assignee has said nothing**, which is the case it exists for: a\nconversation that has gone quiet is exactly when you want to see whether the other side is thinking,\nstuck, or gone. An open thread names the party it is still waiting for; a settled one names whoever\nanswered last. Nothing new is recorded for either — the first comes from where the trigger put that\nsession, the second from the return address its own message already carries. The read is the same for\nevery caller; there is no human-versus-agent distinction anywhere in it.\n\nNothing new is stored for any of this. The status is computed from the thread edges, the reply\nexpectations and the return addresses already in the log — there is no operation record, and nothing\ntakes the place of the run record this release removes.", - "de": "Ein Vorgang ist jetzt ein FADENBAUM, und `nxc status` liest ihn. Bisher verband nichts im Datenmodell\nzwei Fäden: eine echte Kette — Sie beauftragen den PM, der PM beauftragt den Coding-Kanal, dessen\nLeitung gibt die Arbeit an einen Entwickler, das Review dazu fächert auf drei Prüfer auf — waren fünf\nbis sieben einzelne Fäden über drei Kanäle, und niemand konnte beantworten: \"Wo stehen wir hier\neigentlich?\"\n\nJeder Faden merkt sich jetzt den Faden, AUS DEM HERAUS er geöffnet wurde. Die Regel ist mechanisch\nund verlangt vom Agenten nichts: ein `nxc send --to ` aus einer laufenden Sitzung\nhängt den neuen Faden unter den Faden, in dem diese Sitzung gerade steht — den, auf den sie\ngetriggert wurde, oder den, dessen Antwort sie geweckt hat. Ein Send ohne Sitzung drumherum (Sie, am\nTerminal) öffnet eine WURZEL, und genau das macht sie zum Anfang einer Kette. Gespeichert wird dafür\nnichts: die Wurzel ist das Fehlen einer Kante.\n\n`nxc status` beantwortet \"wo stehen wir\" in drei Formen. `nxc status --thread ` zeigt den ganzen\nBaum des Vorgangs, zu dem dieser Faden gehört, von der Wurzel aus und über alle Kanalgrenzen hinweg;\n`nxc status --channel ` listet die laufenden Vorgänge, die in einem Kanal BEGONNEN haben, als\nEinstieg; `nxc status` ohne alles zeigt, was im Arbeitsbereich noch läuft. `--json` trägt denselben\nDatensatz, und dieselbe Lesung liegt als `Engine::status` auf der Bibliotheks-Naht — eine App baut\nihre Vorgangsansicht daraus, statt die Ableitung nachzubauen. Es ist der Ersatz für\n`workflow status` und `workflow list`, die dasselbe Release entfernt.\n\nJeder Faden meldet einen von drei abgeleiteten Zuständen: **offen** (hier schuldet noch jemand eine\nAntwort), **beantwortet** und **VERWAIST** — beantwortet, nichts daraus geöffnet, oberhalb wartet\nniemand mehr darauf, und der Faden darüber ist auch nicht weitergegangen. Der verwaiste Zustand ist\nder Ort, an dem eine still ausgebliebene Konsequenz sichtbar wird, ohne dass sie jemand meldet; die\nletzte dieser Bedingungen ist es, die ihn auf die GESCHEITERTEN Zweige beschränkt statt auf die\nfertigen — beschrieben im Eintrag über den Ort für gescheiterte Konsequenzen. Eine bewusste Ausnahme ganz oben: ein\nbeantworteter WURZEL-Faden meldet `awaiting you`, statt wie ein Faden zu lesen, an dem niemand mehr\narbeitet. Das ist das normale Ende eines Vorgangs, kein Stillstand — und die einzige Stelle, an der\nim Ablauf überhaupt ein Mensch vorkommt.\n\nJeder Faden trägt außerdem die Sitzung dessen, der gerade daran arbeitet: Wer eine Faden-Kennung hat,\nkann damit das Transkript des Beauftragten mitlesen, ohne dessen Sitzungskennung je erfahren zu haben\n— `nxc status --json` nennt die Sitzung, `nxc transcript show ` zeigt den laufenden Verlauf.\n**Das funktioniert, solange der Faden offen ist und der Beauftragte noch nichts gesagt hat** — genau\ndafür gibt es das: Ein Gespräch, das still geworden ist, ist der Moment, in dem man sehen will, ob die\nGegenseite denkt, hängt oder weg ist. Ein offener Faden nennt den, auf den er noch wartet; ein\nabgeschlossener den, der zuletzt geantwortet hat. Aufgezeichnet wird für beides nichts Neues — das\nErste steht dort, wo der Trigger diese Sitzung hingestellt hat, das Zweite ist die Rückadresse, die\nihre Nachricht ohnehin trägt. Die Lesung ist für jeden Aufrufer dieselbe; eine Unterscheidung zwischen\nMensch und Agent gibt es darin nirgends.\n\nGespeichert wird für all das nichts Neues. Der Status wird aus den Faden-Kanten, den\nAntwort-Erwartungen und den Rückadressen gerechnet, die ohnehin im Protokoll stehen — es gibt keinen\nVorgangs-Datensatz, und nichts tritt an die Stelle des Lauf-Datensatzes, den dieses Release\nentfernt.", - "facade": "breaking" - }, - { - "type": "removed", - "en": "**A channel is a declaration now, or it is nothing.** Until this release you could address a channel\nthat nothing declared — one you had minted with `nxc channels create`, or whose id you simply\nhappened to know. Such a channel is something addressable with no policy behind it: nobody said who\nanswers on it, by when, or what becomes of the answers, so nothing could route them. That is the\nstate this rebuild set out to abolish, and this release is where it goes.\n\n**`nxc channels` is gone, whole.** `create` and `dm` minted channels; a channel is declared in\n`.nxs-personas/channels.yaml` and materialises the first time somebody sends to it, and a direct\nconversation opens itself the moment you write `send --to `. `join` and `leave` wrote\nmembership; membership is the declaration's `members:` list. `list` is `nxc list`, which reads the\ndeclared team and its channels.\n\n**`channels public` has no successor on the command line, and that is a decision rather than an\noversight.** It listed the PUBLIC channels in this workspace's store — including front doors that\narrived by sync from other projects, which no declaration here names and which `nxc list` therefore\ncannot show. Cross-project discovery leaves the agent surface; the read stays on the library seam as\n`Engine::public_channels`, for an application to render.\n\n**`nxc ask` is gone.** `send --to ` opens the board, and the channel's own declaration says\nwho must answer, by when, and what becomes of the answers — so a per-call `--expect` was a second\nanswer to a question the declaration already answers, and it goes with the verb. `--deadline` does\nNOT: it moved to **`nxc send --deadline`**, and it still takes either an absolute RFC3339 instant or\na `` duration. A duration has a declared form (a channel's `timeout:`) but an absolute\ninstant has none, so dropping the flag would have taken a capability away instead of collapsing two\nentrances into one.\n\n**`send` and `reply` each take exactly one positional argument now: the body.** The target is a flag.\n\n- `nxc send \"\"` → `nxc send --to \"\"`\n- `nxc reply \"\"` → `nxc reply --thread \"\"`\n\n`--thread` is required. A first token that means \"channel\" in one invocation and \"body\" in the next\nis also the one shape the argument parser could never check for you.\n\n**Replying to a single MESSAGE id is gone with it.** `reply ` used to answer \"the\nconversation that message is in\", which is what `--thread` says outright. A conversation has one\naddress.\n\nTwo consequences worth knowing before you upgrade:\n\n- **`nxc reply --json` renders one shape.** The positional form built its own JSON object and\n carried both `persisted` and `posted` for one fact; `reply --thread` has always serialized the\n typed receipt, which carries `posted`. With one form left there is one name. Everything else in\n that receipt — `thread_id`, `resumed`, `message_id`, `warnings` and the `wake_skipped` finding —\n is unchanged.\n**One label changed with the verb.** `nxc ask` treated a message as a `question` unless you said\notherwise; `nxc send` treats it as `info`. So a board you used to open with a bare\n`nxc ask \"\"` now carries `info` where it carried `question`. This is a label — what\n`search` and `inbox` show you — and nothing routes, waits or decides on it. Type\n`--kind question` if you want it back. The default was not carried over on purpose: `ask` could\nhave one because it only ever addressed a channel, while `send --to` addresses a persona or a\nchannel, and a default that changed depending on which one your target turned out to be is the kind\nof hidden difference this release exists to remove.\n\n**And a board still wakes its requester exactly once.** Answering a thread hands the turn back to\nwhoever is on the other side — but a fan-in board has no other side, and its requester is woken by\nthe completion, once. That was previously true because the two `reply` shapes reached different\ncode; with one shape left it is stated as a rule instead, so a partial answer, a late answer, or an\nanswer from somebody who was never expected no longer wakes the requester again.\n\n**What happens to channels you already have.** They stay in your workspace and their messages stay\nreadable through `nxc search` and `nxc inbox` — but a channel no declaration names stops being a\ntarget. `nxc send --to ` says so by name, and says where a declaration goes, rather than\nanswering \"no such target\" and sending you looking for a typo in an id that is spelled perfectly.", - "de": "**Ein Kanal ist jetzt eine Deklaration — oder er ist nichts.** Bis zu dieser Version konnten Sie\neinen Kanal ansprechen, den nichts deklariert: einen, den Sie mit `nxc channels create` erzeugt\nhatten, oder dessen Id Sie schlicht kannten. Ein solcher Kanal ist etwas Ansprechbares ohne Politik\ndahinter — niemand hat gesagt, wer darauf antwortet, bis wann, und was aus den Antworten wird, also\nkonnte sie auch nichts weiterleiten. Genau diesen Zustand sollte der Umbau abschaffen, und hier\ngeschieht es.\n\n**`nxc channels` ist vollständig entfallen.** `create` und `dm` erzeugten Kanäle; ein Kanal wird in\n`.nxs-personas/channels.yaml` deklariert und materialisiert sich, sobald jemand zum ersten Mal an\nihn sendet, und ein Direktgespräch öffnet sich von selbst, sobald Sie `send --to `\nschreiben. `join` und `leave` schrieben Mitgliedschaft; die Mitgliedschaft steht in der `members:`-\nListe der Deklaration. `list` ist `nxc list` und liest das deklarierte Team samt seinen Kanälen.\n\n**`channels public` hat auf der Kommandozeile keinen Nachfolger, und das ist eine Entscheidung und\nkein Versehen.** Es listete die ÖFFENTLICHEN Kanäle im Speicher dieses Arbeitsbereichs — auch\nEingangstüren, die per Synchronisation aus anderen Projekten hereingekommen sind, die hier keine\nDeklaration benennt und die `nxc list` deshalb nicht zeigen kann. Die projektübergreifende Entdeckung\nverlässt die Agenten-Oberfläche; die Lesung bleibt als `Engine::public_channels` auf der\nBibliotheksnaht, damit eine Anwendung sie darstellen kann.\n\n**`nxc ask` ist entfallen.** `send --to ` öffnet die Tafel, und die Deklaration des Kanals\nsagt, wer antworten muss, bis wann und was aus den Antworten wird — ein `--expect` je Aufruf war\ndamit eine zweite Antwort auf eine Frage, die die Deklaration schon beantwortet, und geht mit dem\nVerb. `--deadline` NICHT: es ist zu **`nxc send --deadline`** gewandert und nimmt weiterhin entweder\neinen absoluten RFC3339-Zeitpunkt oder eine Dauer ``. Eine Dauer hat eine deklarierte\nForm (der `timeout:` eines Kanals), ein absoluter Zeitpunkt hat keine — den Flag wegzulassen hätte\nalso etwas weggenommen, statt zwei Eingänge zu einem zusammenzuführen.\n\n**`send` und `reply` nehmen jetzt genau ein Positionsargument: den Text.** Das Ziel ist ein Flag.\n\n- `nxc send \"\"` → `nxc send --to \"\"`\n- `nxc reply \"\"` → `nxc reply --thread \"\"`\n\n`--thread` ist Pflicht. Ein erstes Token, das in einem Aufruf \"Kanal\" und im nächsten \"Text\"\nbedeutet, war außerdem die eine Form, die Ihnen die Argumentprüfung nie abnehmen konnte.\n\n**Die Antwort auf eine einzelne NACHRICHTEN-Id entfällt mit.** `reply ` bedeutete\n\"das Gespräch, in dem diese Nachricht steht\" — und genau das sagt `--thread` unmissverständlich. Ein\nGespräch hat eine Adresse.\n\nZwei Folgen, die Sie vor dem Umstieg kennen sollten:\n\n- **`nxc reply --json` liefert eine Form.** Die positionale Form baute ihr JSON-Objekt selbst und\n trug für eine Tatsache beide Namen, `persisted` und `posted`; `reply --thread` serialisiert seit\n jeher die typisierte Quittung, und die trägt `posted`. Mit einer verbleibenden Form gibt es einen\n Namen. Alles Übrige in dieser Quittung — `thread_id`, `resumed`, `message_id`, `warnings` sowie\n der Befund `wake_skipped` — ist unverändert.\n**Eine Beschriftung hat sich mit dem Verb geändert.** `nxc ask` verstand eine Nachricht als\n`question`, solange Sie nichts anderes sagten; `nxc send` versteht sie als `info`. Eine Tafel, die\nSie bisher mit einem blossen `nxc ask \"\"` geöffnet haben, trägt jetzt also `info`, wo\n`question` stand. Das ist eine Beschriftung — das, was `search` und `inbox` Ihnen zeigen — und\nnichts leitet, wartet oder entscheidet danach. Mit `--kind question` bekommen Sie sie zurück. Der\nDefault wurde bewusst nicht mitgenommen: `ask` konnte einen haben, weil es immer nur einen Kanal\nansprach, während `send --to` eine Persona oder einen Kanal anspricht — und ein Default, der sich\ndanach richtet, als was sich Ihr Ziel herausstellt, ist genau die verborgene Unterscheidung, die\ndieses Release abschafft.\n\n**Und eine Tafel weckt ihren Anforderer weiterhin genau einmal.** Wer einen Faden beantwortet, gibt\nden Zug an die Gegenseite zurück — eine sammelnde Tafel hat aber keine Gegenseite, und ihr Anforderer\nwird vom Abschluss geweckt, einmal. Das galt bisher, weil die beiden `reply`-Formen verschiedenen\nCode erreichten; mit nur noch einer Form ist es als Regel festgeschrieben. Eine Teilantwort, eine\nnachgereichte Antwort oder eine Antwort von jemandem, der nie erwartet wurde, weckt den Anforderer\nalso nicht erneut.\n\n**Was aus Ihren bestehenden Kanälen wird.** Sie bleiben im Arbeitsbereich, und ihre Nachrichten\nbleiben über `nxc search` und `nxc inbox` lesbar — aber ein Kanal, den keine Deklaration benennt, ist\nkein Ziel mehr. `nxc send --to ` sagt das beim Namen und sagt auch, wohin eine Deklaration\ngehört, statt mit \"no such target\" zu antworten und Sie einen Tippfehler in einer Id suchen zu\nlassen, die vollkommen richtig geschrieben ist.", - "facade": "breaking" - }, - { - "type": "added", - "en": "`nxc reply --escalate \"\"` — the note \"I cannot carry out this task\". It is the SECOND and last\nthing an agent may say with `reply`; the first is \"I am finished\". Everything else an agent produces\nbelongs in the transcript and never in the agent-to-agent conversation, because a waiting agent would\nread it as a result.\n\nIt is deliberately not a wastebasket for everything that is not a result. A real follow-up question —\n\"what do you mean by X?\", from somebody who can still do the work — stays `--kind question`, a\ndifferent thing with its own value. `--escalate` carries one meaning and stays committed to it.\n\n**The turn ends either way.** An escalating reply discharges the sender's obligation exactly like a\nfinished one: the session ends, the debt is gone, and nothing about who owes what changes. What is\ndifferent is the OUTCOME, and the channel's supervisor is what decides where that goes.\n\n**The escalation travels upward, on every channel.** A channel member that says \"I cannot\" settles\nits own thread like any other answer, so the set is complete and the channel consolidates — but the\nchannel's own answer to the requester carries the escalation onward instead of folding it into a\nresult. The question can therefore reach the person who commissioned the work, one level at a time.\n\nThis holds for both of a channel's output forms, including one that SUMMARISES its answers\n(`on_complete: summarize`), because a channel never summarises away an \"I cannot\": a set that\ncarried one is handed over as it stands. See the consolidator entry in this same release for what\nthat looks like from the requester's side.\n\nIt is carried by the message's `kind`, a field that already existed and already syncs, so nothing\nabout the synchronised payload changes shape. `--kind escalation` is deliberately not accepted:\n`--escalate` is the one door to it, and asking for both at once is refused. Applications reach the\nsame thing by setting `MessageKind::Escalation` on the reply they already build — there is no flag\nhere that only the command line can send.\n\nIt is DECLARED rather than free, which is exactly what makes it a signal: the channel's supervisor\nbranches on a bit whose meaning is written in the channel, not on a token an agent invents on the\nspot. That is also why the free-text branching token the removed `nxc workflow step done` carried\nneeds no other replacement.", - "de": "`nxc reply --escalate \"\"` — der Vermerk \"ich kann diese Aufgabe nicht erfüllen\". Es ist das\nZWEITE und letzte, was ein Agent mit `reply` sagen darf; das erste ist \"ich bin fertig\". Alles andere,\nwas ein Agent produziert, gehört ins Transkript und niemals in die Agent-zu-Agent-Kommunikation, denn\nein abwartender Agent würde es als Ergebnis deuten.\n\nEs ist ausdrücklich kein Mülleimer für alles, was kein Ergebnis ist. Die echte Rückfrage — \"was\nmeinst du mit X?\", von jemandem, der die Arbeit sehr wohl tun kann — bleibt `--kind question`, eine\nandere Sache mit eigenem Wert. `--escalate` trägt eine Bedeutung und bleibt darauf verpflichtet.\n\n**Der Zug endet in beiden Fällen.** Ein eskalierendes `reply` löst die Verpflichtung des Absenders\ngenau so ein wie ein fertiges: die Sitzung endet, die Schuld ist weg, und an der Frage, wer wem was\nschuldet, ändert sich nichts. Anders ist der AUSGANG — und darüber entscheidet der Betreuer des\nKanals.\n\n**Die Eskalation dringt nach oben vor, auf jedem Kanal.** Ein Kanalmitglied, das \"ich kann nicht\"\nsagt, löst seinen eigenen Faden ein wie jede andere Antwort, die Menge ist also vollständig und der\nKanal konsolidiert — aber die Antwort des Kanals an den Anforderer trägt die Eskalation weiter, statt\nsie zu einem Ergebnis zu falten. Die Frage kann so, Ebene für Ebene, bis zu dem vordringen, der die\nArbeit in Auftrag gegeben hat.\n\nDas gilt für beide Ausgabeformen eines Kanals, auch für die, die ZUSAMMENFASST\n(`on_complete: summarize`) — denn ein Kanal fasst ein \"ich kann nicht\" niemals weg: Eine Menge, die\neines trägt, wird durchgereicht, wie sie ist. Wie das von der Seite des Anforderers aussieht, steht\nim Eintrag zum Konsolidierer aus demselben Release.\n\nGetragen wird sie von der `kind`-Angabe der Nachricht — einem Feld, das es bereits gab und das\nbereits synchronisiert wird; an der synchronisierten Nutzlast ändert sich also nichts. `--kind\nescalation` wird bewusst nicht angenommen: `--escalate` ist die eine Tür dorthin, und beides zugleich\nzu verlangen wird abgelehnt. Anwendungen erreichen dasselbe, indem sie `MessageKind::Escalation` auf\nder Antwort setzen, die sie ohnehin bauen — es gibt hier keine Fahne, die nur die Kommandozeile\nsenden kann.\n\nEs ist DEKLARIERT statt frei, und genau das macht es zu einem Signal: Der Betreuer des Kanals\nverzweigt auf ein Bit, dessen Bedeutung im Kanal steht, nicht auf einen Token, den ein Agent sich\nausdenkt. Deshalb braucht auch der freie Verzweigungs-Token, den das entfernte\n`nxc workflow step done` trug, keinen anderen Ersatz.", - "facade": "breaking" - }, - { - "type": "removed", - "en": "**`nxc send` has one target now: `--to`.** `--role` and `--session` are gone, and with them the two\nlibrary methods behind them — `Engine::role_trigger` and `Engine::role_resume`.\n\n**`send --role ` collapses into `send --to `.** Both hand a persona a task, both run\nthe same code, and since the previous release neither could name a channel — so both open the direct\nconversation between caller and persona by themselves. What `--to` adds is the reason the collapse\ngoes this way round: it opens a THREAD and tells the persona which one to answer into. `--role`\nposted without a thread, which left its answers with no address at all, because `reply --thread` is\nthe one way left to answer. Anywhere you wrote `nxc send --role coder \"…\"`, write\n`nxc send --to coder \"…\"`; you get a thread id back, and that is what a reply needs.\n\n**`send --session ` has no successor, and that is a decision rather than an oversight.** It\npushed a new task into a persona's LIVE session. `reply --thread` is a different act: it answers a\nconversation, and it reaches the other side through the thread's return address — the newest message\nsomebody else posted there. A persona you have just summoned and that has not answered yet has\nposted none, so a follow-up is recorded in the thread but wakes nobody until the persona replies of\nits own accord. Delivering into a session that is mid-turn needs a signal into a running agent that\ndoes not exist yet; it is tracked as its own piece of work and will arrive as\n`reply --thread --force`. Until then this is a capability the command line gives up, named here\nrather than left to be discovered.\n\n**For applications embedding the library:** `Engine::send_to` is the one door to a persona. It takes\na target rather than a role handle, returns a thread id beside the session id, and declares who the\nthread expects a reply from. `Engine::role_resume` has no replacement, for the reason above. The\nmechanism underneath both is untouched — only the flat handle methods are gone.", - "de": "**`nxc send` hat jetzt genau ein Ziel: `--to`.** `--role` und `--session` sind entfallen, und mit\nihnen die beiden Bibliotheksmethoden dahinter — `Engine::role_trigger` und `Engine::role_resume`.\n\n**`send --role ` geht in `send --to ` auf.** Beide übergeben einer Persona eine\nAufgabe, beide laufen durch denselben Code, und seit dem vorigen Release konnte keines von beiden\nmehr einen Kanal benennen — also öffnen beide das Direktgespräch zwischen Aufrufer und Persona\nselbst. Was `--to` hinzufügt, ist der Grund für die Richtung dieses Zusammenlegens: es öffnet einen\nFADEN und sagt der Persona, in welchen sie antworten muss. `--role` postete ohne Faden, und damit\nhatten seine Antworten überhaupt keine Adresse — denn `reply --thread` ist die einzige verbliebene\nForm zu antworten. Wo `nxc send --role coder \"…\"` stand, steht künftig `nxc send --to coder \"…\"`;\nzurück kommt eine Faden-Id, und genau die braucht eine Antwort.\n\n**`send --session ` hat keinen Nachfolger, und das ist eine Entscheidung, kein Versehen.** Es\nschob einer Persona eine neue Aufgabe in die LAUFENDE Sitzung. `reply --thread` ist eine andere\nHandlung: es antwortet in einem Gespräch und erreicht die Gegenseite über die Rückadresse des Fadens\n— die neueste Nachricht, die jemand anderes dort abgelegt hat. Eine gerade erst gerufene Persona, die\nnoch nicht geantwortet hat, hat keine abgelegt; ein Nachtrag steht dann zwar im Faden, weckt aber\nniemanden, bis die Persona von sich aus antwortet. Die Zustellung in eine Sitzung mitten im Zug\nbraucht ein Signal in einen laufenden Agenten hinein, das es noch nicht gibt; sie ist als eigener\nAuftrag erfasst und kommt als `reply --thread --force`. Bis dahin gibt die Kommandozeile hier\neine Fähigkeit auf — hier benannt, statt sie entdecken zu lassen.\n\n**Für Anwendungen, die die Bibliothek einbetten:** `Engine::send_to` ist die eine Tür zu einer\nPersona. Sie nimmt ein Ziel statt eines Rollen-Handles, gibt neben der Sitzungs-Id eine Faden-Id\nzurück und hält fest, von wem der Faden eine Antwort erwartet. Für `Engine::role_resume` gibt es aus\ndem oben genannten Grund keinen Ersatz. Der Mechanismus unter beiden bleibt unangetastet — entfallen\nsind nur die flachen Handle-Methoden.", - "facade": "breaking", - "unreleased": true - }, - { - "type": "removed", - "en": "The `--model` option on `nxc send` is gone. Choosing a model for one single call is no longer\nsomething typed on the command line: it was on the list of entrances being retired as the\nagent-to-agent surface is cut back to what an agent actually needs (item 6j6v.dvyq §3), and the\ndeclared consolidator landing in this same release brought its removal forward — with a channel now\nnaming the model its answers are folded with, a per-call override and a declared model would be two\nanswers to the same question.\n\n**Which model a session runs on is declared, not typed.** A persona says so with `model:` (or the\ncoarser `stage:`), and a channel now says so for its fold with `summary_model:`. Nothing about that precedence changed, and a role that names no model\nstill runs on the default exactly as before.\n\n**The capability itself has not gone anywhere.** An application embedding the engine still chooses a\nmodel per call — the field is unchanged on every request it already builds. Only the hand-typed door\nclosed.", - "de": "Die Option `--model` an `nxc send` entfällt. Für einen einzelnen Aufruf ein Modell zu wählen, ist\nnichts mehr, was auf der Kommandozeile getippt wird: Der Eingang stand ohnehin auf der Liste derer,\ndie entfallen, während die Agent-zu-Agent-Oberfläche auf das zurückgeschnitten wird, was ein Agent\nwirklich braucht (Item 6j6v.dvyq §3) — und der deklarierte Konsolidierer aus demselben Release hat\ndie Entfernung vorgezogen: Wenn ein Kanal jetzt selbst nennt, mit welchem Modell seine Antworten\ngefaltet werden, wären eine Übersteuerung je Aufruf und eine Deklaration zwei Antworten auf dieselbe\nFrage.\n\n**Auf welchem Modell eine Sitzung läuft, wird deklariert, nicht getippt.** Eine Persona sagt es mit\n`model:` (oder gröber mit `stage:`), und ein Kanal sagt es jetzt mit `summary_model:` für seine\nFaltung. An dieser Rangfolge ändert sich\nnichts, und eine Rolle ohne Modellangabe läuft unverändert auf der Voreinstellung.\n\n**Die Fähigkeit selbst ist nicht verschwunden.** Eine Anwendung, die die Engine einbettet, wählt\nweiterhin je Aufruf ein Modell — das Feld an den Anfragen, die sie ohnehin baut, ist unverändert.\nZugegangen ist nur eine handgetippte Tür." - }, - { - "type": "removed", - "en": "The `nxc threads expect` command is gone. Re-declaring who a thread expects a reply from is no\nlonger something a human types: it was on the list of `nxc` commands being retired as the\nagent-to-agent surface is cut back to what an agent actually needs (item 6j6v.dvyq §3), and the\nturn-scoped reply rule landing in this same release is what brought its removal forward — under that\nrule a re-declaration asks the named handles again rather than closing a board, so the one thing this\nverb was typed for is no longer what it does.\n\n**The capability itself has not gone anywhere.** The write is still on the library seam as\n`facade::set_expects`, unchanged and still opener-only with the same errors, and it is what a channel\nsupervisor uses to hand a role its next turn. Only the hand-typed door closed.\n\nTo close a board nobody is going to answer, drop the expectation instead: a set that no longer names\nthat handle leaves nothing outstanding, which is what the working-tree release, the\n`reply --if-unanswered` discharge and the deadline advisory all read.", - "de": "Der Befehl `nxc threads expect` entfällt. Neu zu erklären, von wem ein Faden eine Antwort erwartet,\nist nichts mehr, was ein Mensch tippt: Der Befehl stand ohnehin auf der Liste der `nxc`-Kommandos,\ndie entfallen, während die Agent-zu-Agent-Oberfläche auf das zurückgeschnitten wird, was ein Agent\nwirklich braucht (Item 6j6v.dvyq §3) — und die zug-bezogene Einlösungsregel aus demselben Release hat\ndie Entfernung vorgezogen: Unter dieser Regel fragt eine Neu-Erklärung die genannten Handles erneut,\nstatt ein Board zu schließen. Das Einzige, wofür der Befehl getippt wurde, tut er also nicht mehr.\n\n**Die Fähigkeit selbst ist nicht verschwunden.** Der Schreibzugriff liegt weiterhin auf der\nBibliotheks-Naht als `facade::set_expects` — unverändert, weiterhin nur für den Öffner, mit\ndenselben Fehlern — und der Kanal-Betreuer erklärt darüber den nächsten Zug einer Rolle. Zugegangen\nist nur eine handgetippte Tür.\n\nUm ein Board zu schließen, das niemand mehr beantworten wird, lässt man die Erwartung stattdessen\nfallen: Eine Menge, die dieses Handle nicht mehr nennt, hat nichts mehr offen — und genau das lesen\ndie Freigabe der Arbeitskopie, die Einlösung per `reply --if-unanswered` und der Fristhinweis." - }, - { - "type": "fixed", - "en": "A reply now settles the TURN it answers, not the whole thread. Until now, a thread counted a role\nas \"has replied\" from its first message there onwards, forever — which is the right reading for a\none-shot review board and the wrong one for a conversation that goes back and forth. The moment a\nsupervisor came back onto the same thread with a second piece of work, everything downstream of that\ncount was already convinced the role owed nothing: `nxc reply --if-unanswered` — the safety net the\nagent sidecar fires at the end of every session, so a session that dies without answering still\nleaves an answer behind — silently did nothing from the second turn on, and the working-tree lease\nread the chain as finished while the role was still writing its final report, handing the working\ncopy to a rival chain mid-flight.\n\nWhether a reply is still owed is now measured from the moment it was asked for: a thread's\n`expects_reply_from` declaration carries its own position in the log, and only what a handle wrote\nAFTER the current declaration counts as an answer to it. Re-declaring the expectation — the same\nhandles or different ones — therefore opens a new turn, and everyone the new declaration names owes\nan answer to it. Nothing about the synced message format changes; this is derived from what the log\nalready carried, so no device needs a migration and a peer that only ever received the ops reaches\nthe same answer.\n\n**One caveat, and it belongs to the substrate rather than to this rule.** \"After the current\ndeclaration\" is decided in the underlying log's own causal order, and that order has a known hole:\nthe counter it uses belongs to an OPEN STORE HANDLE — recovered once when the handle opens, never\nre-read from the file afterwards — while every process on one device shares a replica identity. Two\nwriters holding the same workspace open at the same moment therefore do not observe each other's\npositions: a long-lived host that embeds the engine for its lifetime beside short-lived `nxc` calls,\nor simply two `nxc` calls running at once. Where that happens, a message written BEFORE a\nre-declaration can be counted as an answer to it — on one device, with no sync involved — and the\nconsequences are the ones the rule above is there to prevent: a turn that reads as discharged the\ninstant it is opened, and a working-tree lease released while the work is still running. It is\ntracked as its own item (`6j6v.fc5p`), the fix belongs in the substrate every one of these reads\nsits on, and nothing about the rule described here changes when it lands.\n\n**Removed with it: the `nxc threads expect` verb.** Re-declaring who a thread expects a reply from\nused to be something a human could type, and its purpose was to complete a board stuck on a reviewer\nwho never came back — narrow the expected set to whoever had answered, and the board closed. Under\nthe rule above that is no longer what a re-declaration means (it asks the named handles again, as of\nnow), and rather than build a special case to keep the old effect, the verb goes: it was already on\nthe list of `nxc` commands being retired as the agent-to-agent surface is cut back to what an agent\nactually needs. **The capability itself has not gone anywhere** — the write is still there for\nembedding apps and hosts as `facade::set_expects` (opener-only, same errors), and it is what the\nchannel supervisor uses to hand a role its next turn. What disappeared is a hand-typed door.\nTo close a board nobody is going to answer, drop the expectation instead — a set that no longer names\nthat handle leaves nothing outstanding, which is what the working-tree release, the `--if-unanswered`\ndischarge and the deadline advisory all read.\n\n**On an existing workspace this applies retroactively, and it is visible.** These are derived reads,\nnot stored state, so every board is re-derived under the new rule the first time it is read after the\nupgrade. A board that was completed by narrowing it in the past reads as outstanding again (its\nanswers predate the narrowing), which also means the chain holding the working copy for that thread\nkeeps holding it until somebody answers the current declaration or the expectation is dropped — and\nthat a thread with a past deadline can show up as waiting past it again. Nothing is lost or rewritten;\nwhat changed is the question being asked of the same log.\n\nThe kind of a message is deliberately not consulted anywhere in this: an answer that says \"I cannot\ndo this\" settles the turn exactly like a finished one, because the session ends either way.\n\nFacade (breaking, behavioural — no signature moves): `nexus_chat::store::ChatStore::thread_quorum`\nand `thread_quorums` return the same shape, and `ThreadQuorum`'s `replied` / `outstanding` /\n`complete` now answer per turn rather than per thread. A consumer that renders a board sees a\nre-declared thread reopen; a consumer that treated \"replied once\" as permanent must read the\ncurrent declaration instead. `facade::set_expects`'s receipt reports the re-asked set for the same\nreason.", - "de": "Eine Antwort löst jetzt den ZUG ein, den sie beantwortet, und nicht den ganzen Faden. Bisher galt\neine Rolle in einem Faden ab ihrer ersten Nachricht dort für immer als „hat geantwortet\" — die\nrichtige Lesart für ein einmaliges Review-Board und die falsche für ein Gespräch, das hin und her\ngeht. Sobald ein Betreuer mit einem zweiten Arbeitspaket auf denselben Faden zurückkam, war alles,\nwas auf dieser Zählung aufsetzt, bereits überzeugt, die Rolle schulde nichts mehr: `nxc reply\n--if-unanswered` — das Sicherheitsnetz, das der Agenten-Sidecar am Ende jeder Sitzung auslöst, damit\neine ohne Antwort gestorbene Sitzung trotzdem eine Antwort hinterlässt — tat ab dem zweiten Zug still\nnichts mehr, und die Arbeitsbaum-Sperre las die Kette als fertig, während die Rolle noch am\nAbschlussbericht schrieb, und übergab die Arbeitskopie mitten im Lauf an eine fremde Kette.\n\nOb eine Antwort noch geschuldet wird, wird jetzt ab dem Zeitpunkt der Frage gemessen: Die Erklärung\n`expects_reply_from` eines Fadens trägt ihre eigene Position im Protokoll, und als Antwort darauf\nzählt nur, was ein Handle NACH der aktuellen Erklärung geschrieben hat. Eine erneute Erklärung —\nmit denselben Handles oder mit anderen — eröffnet damit einen neuen Zug, und alle, die sie nennt,\nschulden ihr eine Antwort. Am synchronisierten Nachrichtenformat ändert sich nichts; das Ganze wird\naus dem abgeleitet, was das Protokoll ohnehin trug, also braucht kein Gerät eine Migration, und ein\nPeer, der nur die Ops empfangen hat, kommt zum selben Ergebnis.\n\n**Eine Einschränkung, und sie gehört dem Unterbau, nicht dieser Regel.** „Nach der aktuellen\nErklärung\" wird in der kausalen Ordnung des zugrunde liegenden Protokolls entschieden, und diese\nOrdnung hat eine bekannte Lücke: Der Zähler, den sie verwendet, gehört einem GEÖFFNETEN\nSpeicher-Handle — beim Öffnen einmal aus der Datei gewonnen, danach nie wieder mit ihr abgeglichen —\nwährend alle Prozesse eines Geräts dieselbe Replikat-Identität teilen. Zwei Schreiber, die denselben\nArbeitsbereich gleichzeitig offen halten, sehen die Position des jeweils anderen also nicht: ein\nlanglebiger Host, der die Engine für seine Laufzeit einbettet, neben kurzlebigen `nxc`-Aufrufen —\noder schlicht zwei gleichzeitig laufende `nxc`-Aufrufe. Wo das passiert, kann eine Nachricht, die VOR\neiner erneuten Erklärung geschrieben wurde, als Antwort auf sie gezählt werden — auf einem Gerät,\nganz ohne Synchronisation — und die Folgen sind genau die, gegen die die Regel oben antritt: ein Zug,\nder in dem Augenblick als eingelöst gilt, in dem er eröffnet wird, und eine Arbeitsbaum-Sperre, die\nfreigegeben wird, während die Arbeit noch läuft. Das ist als eigenes Item erfasst (`6j6v.fc5p`), der\nFix gehört in den Unterbau, auf dem alle diese Lesungen sitzen, und an der hier beschriebenen Regel\nändert sich dadurch nichts.\n\n**Damit entfällt der Befehl `nxc threads expect`.** Neu zu erklären, von wem ein Faden eine Antwort\nerwartet, war bisher etwas, das ein Mensch tippen konnte, und der Zweck war, ein Board abzuschließen,\ndas an einem nie zurückkehrenden Reviewer feststeckte: den erwarteten Satz auf die verengen, die\ngeantwortet hatten, und das Board war zu. Nach der Regel oben bedeutet eine erneute Erklärung genau\ndas nicht mehr (sie fragt die genannten Handles erneut, mit Wirkung ab jetzt) — und statt einen\nSonderfall zu bauen, der die alte Wirkung rettet, entfällt der Befehl: Er stand ohnehin auf der Liste\nder `nxc`-Kommandos, die zurückgebaut werden, während die Agent-zu-Agent-Oberfläche auf das gekürzt\nwird, was ein Agent wirklich braucht. **Die Fähigkeit selbst ist nicht verschwunden** — der Schreibweg\nsteht Apps und Hosts weiterhin als `facade::set_expects` zur Verfügung (nur der Öffner, gleiche\nFehler), und genau ihn benutzt der Kanal-Betreuer, um einer Rolle ihren nächsten Zug zu geben.\nWeggefallen ist eine von Hand zu tippende Tür. Um ein Board zu schließen, das niemand mehr beantworten\nwird, lässt man die Erwartung stattdessen fallen: Ein Satz, der dieses Handle nicht mehr nennt, lässt\nnichts offen — und genau das lesen die Freigabe der Arbeitskopie, die Einlösung per `--if-unanswered`\nund der Fristhinweis.\n\n**In einem bestehenden Arbeitsbereich wirkt das rückwirkend, und man sieht es.** Es handelt sich um\nabgeleitete Lesungen, nicht um gespeicherten Zustand: Jedes Board wird beim ersten Lesen nach dem\nUpdate nach der neuen Regel neu berechnet. Ein Board, das früher durch Verengen abgeschlossen wurde,\nliest sich wieder als offen (seine Antworten liegen vor der Verengung) — womit auch die Kette, die für\ndiesen Faden die Arbeitskopie hält, sie weiter hält, bis jemand die aktuelle Erklärung beantwortet\noder die Erwartung fallengelassen wird; und ein Faden mit abgelaufener Frist kann erneut als\n„wartet über die Frist hinaus\" auftauchen. Nichts geht verloren und nichts wird umgeschrieben:\nGeändert hat sich die Frage, die an dasselbe Protokoll gestellt wird.\n\nDie Art einer Nachricht wird dabei bewusst nirgends herangezogen: Eine Antwort „ich kann das nicht\"\nlöst den Zug genauso ein wie eine fertige, denn die Sitzung endet so oder so.\n\nFassade (Bruch, verhaltensseitig — keine Signatur bewegt sich): `nexus_chat::store::ChatStore::\nthread_quorum` und `thread_quorums` liefern dieselbe Form, und `replied` / `outstanding` /\n`complete` von `ThreadQuorum` antworten jetzt pro Zug statt pro Faden. Ein Konsument, der ein Board\ndarstellt, sieht einen neu erklärten Faden wieder aufgehen; ein Konsument, der „einmal geantwortet\"\nals dauerhaft behandelt hat, muss stattdessen die aktuelle Erklärung lesen. Die Quittung von\n`facade::set_expects` meldet aus demselben Grund den erneut gefragten Satz.", - "facade": "breaking" - }, - { - "type": "removed", - "en": "The `nxc workflow` command group is gone, and so is the declarative workflow-run engine behind it —\n`workflow start`, `workflow step done` (with its `--outcome` flag), `workflow status`,\n`workflow list` and `workflow liveness`, the `workflow_runs` record they wrote and read, and the\n`.nxs-personas/workflow.yaml` / `.nxs-personas/workflows/*.yaml` declarations that described them. A\nworkspace that still has those files simply no longer has anything that reads them.\n\n**A channel IS the flow now, and that is where every one of those verbs went.** A declared channel\ncarries its own members, its own order (`flow: sequential`), its own deadline and its own\nconsolidation, so `send --to ` starts a run of it and `reply --thread` moves it on — the\nchannel's supervisor decides what comes next, which is why no agent has to utter a branching token\nany more. Where an operation stands is `nxc status`: `--thread` for one operation, `--channel` for\nthe ones that started in a channel, and with no argument, everything still going on here.\n\n**On the library seam** `Engine::workflow_start`, `workflow_step_done`, `workflow_tick`,\n`workflow_liveness`, `workflow_status` and `workflow_list` are removed, and so is the workflow event\nstream — `workflow_events`, `workflow_event_cursor` and `subscribe_workflow`. What an app watching a\nworkspace uses instead already exists: `Engine::subscribe` for the \"someone wrote, re-read\" tick, and\n`Engine::status` for where an operation stands. `Definitions::new` takes two arguments rather than\nthree, and `PrimeRoster` no longer carries a `workflows` list.\n\n**Two capabilities are given up rather than replaced, and both are named here rather than left to be\nfound.** The step-liveness watch — a nudge and then an escalation when a running step went quiet —\nwas keyed on a run's current step and goes with the runs; what remains against a member that falls\nsilent is the per-member channel deadline, which strikes on a declared `timeout` but does not nudge.\nAnd a message no longer inherits a run's commissioned ticket set automatically: name the items it is\nabout with `--ref nxf_ids=`, which always took precedence over the automatic stamp anyway.\n\nThe `workflow_runs` tables are dropped from an existing workspace on its next open. Messages,\nthreads and channels are untouched, and an older `nxc` opening the same workspace afterwards\nrecreates its own empty tables rather than failing. The one shape that does not self-heal, said\nplainly rather than left to be met: an older process that already holds the workspace open while a\nnewer one drops the tables under it will panic if it then folds a workflow op. Close the old process\nbefore upgrading — or, since this is a local tool rather than a fleet, simply do not run two\nversions against one workspace at the same time.", - "de": "Die Befehlsgruppe `nxc workflow` entfällt, und mit ihr die deklarative Ablauf-Maschine dahinter —\n`workflow start`, `workflow step done` (samt `--outcome`), `workflow status`, `workflow list` und\n`workflow liveness`, der `workflow_runs`-Datensatz, den sie schrieben und lasen, und die\nDeklarationen `.nxs-personas/workflow.yaml` / `.nxs-personas/workflows/*.yaml`, die sie beschrieben.\nEin Arbeitsbereich, in dem diese Dateien noch liegen, hat schlicht nichts mehr, was sie liest.\n\n**Der Kanal IST der Ablauf, und dorthin ist jedes dieser Verben gegangen.** Ein deklarierter Kanal\nträgt seine Mitglieder, seine Reihenfolge (`flow: sequential`), seine Frist und seine\nZusammenführung selbst — also startet `send --to ` einen Durchlauf davon, und `reply --thread`\nbringt ihn weiter: Der Betreuer des Kanals entscheidet, was als Nächstes kommt, weshalb kein Agent\nmehr ein Verzweigungs-Token aussprechen muss. Wo ein Vorgang steht, sagt `nxc status`: `--thread` für\neinen Vorgang, `--channel` für die, die in einem Kanal begonnen haben, und ohne Argument alles, was\nhier gerade läuft.\n\n**An der Bibliotheks-Naht** entfallen `Engine::workflow_start`, `workflow_step_done`,\n`workflow_tick`, `workflow_liveness`, `workflow_status` und `workflow_list` — ebenso der\nAblauf-Ereignisstrom `workflow_events`, `workflow_event_cursor` und `subscribe_workflow`. Was eine\nApp stattdessen benutzt, gibt es bereits: `Engine::subscribe` für den „jemand hat geschrieben, lies\nneu\"-Tick und `Engine::status` dafür, wo ein Vorgang steht. `Definitions::new` nimmt zwei statt drei\nArgumente, und `PrimeRoster` führt keine `workflows`-Liste mehr.\n\n**Zwei Fähigkeiten werden aufgegeben statt ersetzt, und beide stehen hier statt gefunden zu werden.**\nDie Totmann-Überwachung eines Schritts — erst ein Anstupsen, dann eine Eskalation, wenn ein laufender\nSchritt still wurde — hing am aktuellen Schritt eines Durchlaufs und geht mit den Durchläufen; gegen\nein still gewordenes Mitglied bleibt die kanalweise Mitglieder-Frist, die bei deklariertem `timeout`\nzuschlägt, aber nicht anstupst. Und eine Nachricht erbt nicht mehr automatisch die beauftragten\nTicket-Ids eines Durchlaufs: Womit sie zu tun hat, sagt `--ref nxf_ids=` — was ohnehin immer\nVorrang vor dem automatischen Stempel hatte.\n\nDie `workflow_runs`-Tabellen werden beim nächsten Öffnen eines bestehenden Arbeitsbereichs entfernt.\nNachrichten, Fäden und Kanäle bleiben unberührt, und ein älteres `nxc`, das denselben\nArbeitsbereich danach öffnet, legt seine eigenen leeren Tabellen wieder an, statt zu scheitern. Die\neine Form, die sich nicht selbst heilt — hier gesagt, statt sie erleben zu lassen: Ein älterer\nProzess, der den Arbeitsbereich bereits offen hält, während ein neuerer die Tabellen darunter\nentfernt, stürzt ab, sobald er danach eine Workflow-Op faltet. Also den alten Prozess vor dem\nUpgrade beenden — oder, da dies ein lokales Werkzeug ist und keine Flotte, schlicht nicht zwei\nVersionen gleichzeitig gegen einen Arbeitsbereich laufen lassen.", - "facade": "breaking" - }, - { - "type": "changed", - "en": "The working copy is now protected for as long as the TASK runs. Two questions decide it, and they\nhave two different answers.\n\n**Whether a chain is protected is derived upward from the declared members, one step.** A channel\nwhose declared member says `working_tree: exclusive` counts as needing the working copy, without the\nauthor repeating it on the channel — say it once, on the persona that builds, and every channel that\npersona works in is covered. It stops after one step on purpose: a channel does NOT catch the need\nfrom another channel through a member the two happen to share. Otherwise a coding channel would hand\nits need to a review channel whose members only read, that channel would hand it on again, and every\nchannel in a workspace would end up exclusive. Declaring `working_tree: exclusive` on the channel\nitself still works exactly as before, and is still the right way to say \"this ROUND needs it\" when no\nsingle member does.\n\nThe practical difference: a protected channel now waits as a whole. Before, a channel that said\nnothing itself let the member that declared the need wait while its silent siblings started anyway —\nthe board opened half queued and half running, on opposite sides of a lease meant to cover the chain.\n\n**How far the protection reaches is the conversation the work was commissioned in, plus everything\nopened underneath it.** So it holds while a coding round waits on a review round it started: the\nreview conversation and the reviewers' own threads hang under the coding one and are inside the same\nclaim area. It also finally covers a side conversation a working session opens for itself — that used\nto show on the thread board as belonging to no lease at all. And it stops going upward: the\nconversation you had with the agent that commissioned the work is ABOVE the claim and holds nothing,\nso an unanswered human never keeps the working copy overnight.\n\n**The release happens when that conversation is CLOSED, which is not the same as \"nobody owes an\nanswer right now\".** A plain reply closes it and the next order in the queue starts. A\n`reply --escalate` — \"I cannot carry out this task\" — does NOT close it, even though the sender's\nobligation is discharged either way: the question is travelling upward, possibly as far as you, the\ntask is mid-flight, and the working copy stays protected. Not even a long silence changes that. **An\nunanswered follow-up question (`--kind question`) holds it the same way**, for the same reason: in\nboth cases nobody is working, and releasing would let a second chain start into a working copy the\nquestioner is about to resume into. This holds **anywhere in the protected area**, not only in the\nconversation the work was commissioned in — a reviewer three levels down who asked a question and is\nwaiting for the answer keeps the copy, even when every conversation above it has already wrapped up\nwithout noticing.\n\n**Answering it puts the round back to work, and the copy is free when that round finishes.**\nAnswering means giving the round its next turn — a reply into the channel's own conversation, the\nsame door the work came in by — which re-opens every member's turn and starts them again. Nothing is\nreleased at that instant, and nothing should be: the members are working again. What the answer ends\nis the WAITING, so the copy comes free on the round's own completion, without going anywhere near the\ntwo-hour limit. The limit is only what happens if the question is never answered at all.\n\nThe error direction is deliberate — somebody who escalates or asks needlessly holds the copy a\nlittle too long, which is harmless beside a copy handed away while the task is still running.\n\n**Gone with it: the rule that a run of the removed workflow engine held the working copy for as long\nas it was `running`.** That was a lid on a narrower problem — a role step of such a run left no\nobligation anywhere, so the run looked finished the moment it advanced onto one — and it cost two\nthings: a wedged run held the copy until the two-hour bound, and the release came when the RUN ended\nrather than when the work did. The claim area now CONTAINS the conversations the work happens in,\nwhich is what the lid was substituting for, and on this release's surface a channel member is always\ncommissioned with a conversation and an obligation, so the shape that needed the lid cannot arise.\n\nFacade (breaking): `nexus_chat::working_tree`'s release derivation changes behaviour for the same\ninputs. `ChatStore::work_scope_threads` returns the whole subtree from a `Thread` scope instead of the\nthread plus its direct member threads, and `WorkScope` itself now has two variants rather than three\n— the `Run` arm went with the workflow engine this release removes. New alongside them: `ChatStore::work_scope_handed_back`,\n`ChatStore::thread_subtree`/`thread_subtrees`, `ChatStore::last_reply_kind` (the wider read\n`last_reply_escalated` now delegates to — that function is unchanged), and\n`Definitions::channel_needs_working_tree`.", - "de": "Die Arbeitskopie ist jetzt so lange geschützt, wie die AUFGABE läuft. Darüber entscheiden zwei\nFragen, und sie haben zwei verschiedene Antworten.\n\n**OB eine Kette geschützt wird, wird von den deklarierten Mitgliedern nach oben abgeleitet, einen\nSchritt weit.** Ein Kanal, dessen deklariertes Mitglied `working_tree: exclusive` sagt, gilt als\nschutzbedürftig, ohne dass der Autor es am Kanal wiederholt — einmal an der Persona gesagt, die baut,\nund jeder Kanal, in dem sie arbeitet, ist abgedeckt. Nach einem Schritt ist Schluss, und das mit\nAbsicht: Ein Kanal fängt sich die Schutzbedürftigkeit NICHT von einem anderen Kanal ein, nur weil die\nbeiden ein Mitglied teilen. Sonst gäbe ein Coding-Kanal seinen Bedarf an einen Review-Kanal weiter,\ndessen Mitglieder nur lesen, dieser gäbe ihn weiter, und am Ende wäre jeder Kanal im Arbeitsbereich\nexklusiv. `working_tree: exclusive` direkt am Kanal zu deklarieren wirkt unverändert und bleibt der\nrichtige Weg für „diese RUNDE braucht sie\", wenn es kein einzelnes Mitglied ist.\n\nDer praktische Unterschied: Ein geschützter Kanal wartet jetzt als Ganzes. Vorher ließ ein Kanal, der\nselbst nichts sagte, das Mitglied warten, das den Bedarf deklariert hatte, während die schweigenden\nGeschwister trotzdem starteten — das Board öffnete sich halb angestellt und halb laufend, auf beiden\nSeiten einer Sperre, die die Kette abdecken sollte.\n\n**WIE WEIT der Schutz reicht: die Unterhaltung, in der die Arbeit beauftragt wurde, samt allem, was\ndarunter geöffnet wird.** Er hält also, während eine Coding-Runde auf eine von ihr gestartete\nReview-Runde wartet: Die Review-Unterhaltung und die Fäden der Reviewer hängen darunter und liegen im\nselben Anspruchsbereich. Er deckt endlich auch eine Nebenunterhaltung ab, die eine arbeitende Sitzung\nfür sich öffnet — die tauchte am Faden-Board bisher so auf, als gehöre sie zu gar keiner Sperre. Und\nnach oben ist Schluss: Die Unterhaltung mit dem Agenten, der die Arbeit beauftragt hat, liegt ÜBER\ndem Anspruch und hält nichts — ein unbeantworteter Mensch behält die Arbeitskopie also nie bis\nmorgen.\n\n**Freigegeben wird, wenn diese Unterhaltung GESCHLOSSEN ist — und das ist nicht dasselbe wie\n„gerade schuldet niemand eine Antwort\".** Eine gewöhnliche Antwort schließt sie, und der nächste\nAuftrag aus der Warteschlange startet. Ein `reply --escalate` — „ich kann diese Aufgabe nicht\nerfüllen\" — schließt sie NICHT, obwohl die Verpflichtung des Absenders so oder so eingelöst ist: Die\nFrage dringt nach oben vor, womöglich bis zu Ihnen, die Aufgabe ist mitten im Gange, und die\nArbeitskopie bleibt geschützt. Auch langes Schweigen ändert daran nichts. **Eine unbeantwortete\nRückfrage (`--kind question`) hält sie genauso**, aus demselben Grund: In beiden Fällen arbeitet\nniemand, und eine Freigabe ließe eine zweite Kette in eine Arbeitskopie starten, in die der Fragende\ngleich zurückkehrt. Das gilt **überall im geschützten Bereich**, nicht nur in der Unterhaltung, in\nder die Arbeit beauftragt wurde: Ein Reviewer drei Ebenen tiefer, der eine Rückfrage gestellt hat und\nauf die Antwort wartet, hält die Kopie — auch dann, wenn jede Unterhaltung darüber längst\nabgeschlossen hat, ohne es zu bemerken.\n\n**Eine Antwort setzt die Runde wieder in Gang, und die Kopie wird frei, wenn diese Runde fertig\nist.** Antworten heißt: der Runde ihren nächsten Zug geben — eine Antwort in die Unterhaltung des\nKanals selbst, durch dieselbe Tür, durch die die Arbeit kam. Damit wird der Zug jedes Mitglieds neu\neröffnet und alle laufen wieder. In diesem Moment wird nichts freigegeben, und das ist richtig so:\nDie Mitglieder arbeiten ja. Was die Antwort beendet, ist das WARTEN — die Kopie wird also mit dem\nAbschluss der Runde frei, ohne die Zwei-Stunden-Grenze auch nur in die Nähe zu kommen. Die Grenze\ngreift nur, wenn die Frage überhaupt nie beantwortet wird.\n\nDie Fehlerrichtung ist gewollt: Wer unnötig eskaliert oder fragt, hält die Kopie etwas zu lange —\nharmlos neben einer Kopie, die weggegeben wird, während die Aufgabe noch läuft.\n\n**Damit entfällt auch die Regel, dass ein Lauf der entfernten Ablauf-Maschine die Arbeitskopie\nhielt, solange er `running` war.** Das war ein Deckel auf einem engeren Problem — ein Rollenschritt\neines solchen Laufs hinterließ nirgends eine Verpflichtung, der Lauf sah also fertig aus, sobald er\nauf einen solchen Schritt weiterging — und er kostete zweierlei: Ein festgefahrener Lauf hielt die\nKopie bis zur Zwei-Stunden-Grenze, und die Freigabe kam, wenn der LAUF endete, statt wenn die Arbeit\nfertig war. Der Anspruchsbereich ENTHÄLT jetzt die Unterhaltungen, in denen die Arbeit stattfindet —\ngenau das, wofür der Deckel einsprang —, und auf der Oberfläche dieses Releases wird ein\nKanalmitglied immer mit Unterhaltung und Verpflichtung beauftragt, die Lage, für die es den Deckel\nbrauchte, kann also gar nicht mehr entstehen.\n\nFassade (Bruch): Die Freigabe-Ableitung in `nexus_chat::working_tree` verhält sich bei gleicher\nEingabe anders. `ChatStore::work_scope_threads` liefert für einen `Thread`-Bereich den ganzen Teilbaum statt des\nFadens plus seiner direkten Mitglieds-Fäden, und `WorkScope` hat jetzt zwei statt drei Varianten —\nder `Run`-Zweig ist mit der Ablauf-Maschine entfallen, die dieses Release entfernt. Neu daneben: `ChatStore::work_scope_handed_back`,\n`ChatStore::thread_subtree`/`thread_subtrees`, `ChatStore::last_reply_kind` (die weitere Lesung, an die\n`last_reply_escalated` jetzt delegiert — jene Funktion ist unverändert) und\n`Definitions::channel_needs_working_tree`.", - "facade": "breaking" - }, - { - "type": "added", - "en": "A role or a declared channel can now declare `working_tree: exclusive` (the default, `shared`, is\nexactly today's behaviour — no lease, no waiting). Only one chain of role sessions then works\nagainst the repository's working copy at a time: a second chain that also needs it queues instead\nof starting and colliding with the first on the git index and the Cargo build lock — the collision\na coding-and-review pair hit for real in an earlier trial of the role runtime. The holder is the\nCHAIN — the thread the work was commissioned in — not a single session.\n\nHow far \"the chain\" reaches is described by the \"claim area\" entry in this same release, which\nsupersedes what an earlier draft of this entry said about it: the area is the conversation the work\nwas commissioned in TOGETHER WITH everything opened underneath it, so a coding round keeps the\nworking copy while it is waiting on a review round it started. A PERSONA chain\n(`nxc send --to `, one call per step) opens a new conversation per call, so between steps the lease goes free and the release starts whatever is\nat the head of the queue; if an unrelated order queued earlier, that order runs next and the chain's\nown next step queues behind it.\n\nWaiting is never silent. A queued trigger's own receipt says so: `nxc send --to \n--json` gains `queued_behind` (who is holding it) and `queue_position` (1-based) whenever a\ntrigger has to wait, and the human-readable line adds `QUEUED at position n behind …`. `nxc\nthreads list`/`show` carry the same fact afterwards, for a chain that never checked back or a\nclient reading the board later: every thread now reports `working_tree: \"holding\" | \"waiting\" |\nnull` alongside its queue position.\n\nA release hands the working copy to a whole CLAIM AREA, not to one waiting trigger. Several triggers\nshare one claim area whenever a multi-member `working_tree: exclusive` channel had to wait at open\ntime: every member of that board belongs to the board's own thread, so each one queues separately\nunder the same key. Starting only the first of them would strand the rest for good — the board owes\ntheir replies, so it could never reach the release that would start them. They are therefore started\ntogether, which is exactly what happens when the working copy is free (the first member takes the\nlease and the rest inherit it). Which claim area goes next is still decided by the queue's\n`(priority, enqueued_at, id)` order; only after the head is picked does the rest of its area come\nwith it.\n\nRelease is a deterministic fact now, not a guess at quiet. A triggered role's own primed prompt\nnames the thread it owes a reply on and the exact command that closes it, and the new `nxc reply\n--if-unanswered` flag lets a caller that genuinely cannot tell whether it already answered call\nunconditionally — it posts only when a reply is still owed, and is a safe no-op otherwise. The\nagent sidecar's teardown, which always runs at the end of a session, now uses exactly that flag: a\nsession that ends without ever answering the thread it owed gets a failure reply posted on its\nbehalf instead of leaving the thread hanging silently. The moment the last open reply in a chain's\nscope lands, that same reply call frees the working-tree lease, takes the head of the queue, and\nstarts it — no daemon, no poller.\n\nThat two-hour bound (`working_tree::WORKING_TREE_LEASE_BOUND`) is only a backstop for a hard\ndeath, never a budget or a timer: nothing fires when it elapses, and it does NOT start anything\nalready waiting — it only stops an abandoned lease from refusing every FUTURE acquire, so the next\nchain that asks can take the working copy. A queued trigger stays queued until a later chain\nactually releases the lease, not until the bound passes; and a lease over a scope that owes nothing\nat all (what a resume takes, since it registers no obligation) is instead freed by the very next\nreply into that thread.\n\nSeparately, a `send --to ` trigger that fails after posting its message no\nlonger discards it: the receipt now carries `spawned: bool` (`false` for both \"queued behind the\nlease\" and \"the spawn itself failed\") and `warnings: [...]` naming what went wrong, so a caller can\nfinally tell \"nothing happened\" apart from \"the message is in the channel and nobody is working on\nit yet\" — closing long-standing bug 6j6v.hpv8. `nxc`'s exit code still turns non-zero on a real\nspawn failure; an `Engine` caller checks `spawned` instead.\n\nFacade (breaking): `crates/chat`'s public API moves across all of the above. `ReplyRequest` (on\n`facade`, `orchestration`, and `surface::ReplyThreadRequest`) each gain a required\n`if_unanswered: bool` — pass `false` for the previous behaviour. `facade::reply` now returns\n`Result>` (`None` is the new no-op outcome). `orchestration::ReplyReceipt`\ngains `posted: bool` and its `message_id` is now `Option`. `worker::TriggerRequest` gains\n`reply_thread: Option`. `orchestration::RoleSpawn` gains required `priority: Priority` and\n`thread: Option<&str>` (plus `queued_since: Option<&str>`). `working_tree::QueuedTrigger` gains\n`priority: crate::model::Priority` and `enqueued_at: Option` — both components of the\nqueue's `(priority, enqueued_at, id)` order now travel with a queued entry, so a promoted trigger\nthat has to wait again keeps the urgency and the wait it already accrued instead of re-entering as\n`normal`, stamped now. `orchestration::trigger_role` now returns\n`Result` instead of `Result<(), TriggerError>`;\n`orchestration::TriggerReceipt` and `surface::SendToReceipt` each gain `queued_behind`/\n`queue_position` and required `spawned`/`warnings`. `facade::threads` now returns\n`Vec` instead of `Vec` (the existing fields move under\n`.quorum`), and `facade::thread_board`'s `ThreadBoardView` gains the same two working-tree fields;\n`facade::thread_quorums` is unchanged. On the store side, `ChatStore::enqueue_working_tree` loses\nits `priority: i64` parameter (now carried on the queued entry itself), and\n`ChatStore::release_working_tree` is gone — `release_working_tree_and_take_next` is the only\nrelease path left, and it returns `Vec` (the head's whole claim area, in queue\norder) rather than `Option`; an empty vector is the former `None`.\n\nBecause this breaks `nexus-chat`'s public API, this epic ships as a MINOR release (`0.60.0`), not\na `0.59.x` patch: `enforce_facade_breaking_axis` fail-closes a patch bump carrying a\n`facade: breaking` fragment.", - "de": "Eine Rolle oder ein deklarierter Kanal kann jetzt `working_tree: exclusive` deklarieren (die\nVorgabe `shared` bleibt exakt das heutige Verhalten — keine Sperre, kein Warten). Danach arbeitet\nimmer nur eine Kette von Rollen-Sitzungen an der Arbeitskopie des Repositorys; eine zweite Kette,\ndie sie ebenfalls braucht, stellt sich an, statt zu starten und mit der ersten am git-Index und an\nder Cargo-Build-Sperre zu kollidieren — genau die Kollision, die ein Coding-und-Review-Paar in\neinem früheren Praxistest der Rollen-Laufzeit real erlebt hat. Halter ist die KETTE — der Faden, in dem die Arbeit\nbeauftragt wurde — nicht die einzelne Sitzung.\n\nWie weit „die Kette\" reicht, beschreibt der Eintrag zum „Anspruchsbereich\" aus demselben Release; er\nlöst ab, was ein früherer Entwurf dieses Eintrags dazu sagte. Der Bereich ist die Unterhaltung, in\nder die Arbeit beauftragt wurde, SAMT allem, was darunter geöffnet wird — eine Coding-Runde behält\ndie Arbeitskopie also, während sie auf eine von ihr gestartete Review-Runde wartet. Ein\nWORKFLOW-LAUF ist ebenfalls über seine Schritte hinweg EIN Anspruchsbereich. Eine PERSONA-Kette\n(`nxc send --to `, ein Aufruf je Schritt) öffnet je Aufruf eine neue Unterhaltung; zwischen\nden Schritten wird die Sperre also frei, und die Freigabe startet, was am Kopf der Warteschlange\nsteht. Stand dort ein früher angestellter fremder Auftrag, läuft dieser als Nächstes, und der eigene\nnächste Schritt der Kette stellt sich dahinter an.\n\nWarten ist nie still. Die Quittung eines angestellten Triggers sagt es selbst: `nxc send --to\n --json` bekommt `queued_behind` (wer gerade hält) und `queue_position`\n(1-basiert), sobald ein Trigger warten muss, und die menschenlesbare Zeile ergänzt `QUEUED at\nposition n behind …`. `nxc threads list`/`show` tragen dieselbe Tatsache im Nachhinein — für eine\nKette, die nie nachschaut, oder eine App, die das Board erst später liest: Jeder Faden meldet jetzt\n`working_tree: \"holding\" | \"waiting\" | null` samt seiner Warteschlangen-Position.\n\nEine Freigabe übergibt die Arbeitskopie an einen ganzen ANSPRUCHSBEREICH, nicht an einen einzelnen\nwartenden Trigger. Mehrere Trigger teilen sich einen Anspruchsbereich immer dann, wenn ein\nmehrköpfiger Kanal mit `working_tree: exclusive` beim Öffnen warten musste: Jedes Mitglied dieses\nBoards gehört zum Faden des Boards, stellt sich also einzeln unter demselben Schlüssel an. Nur das\nerste zu starten, würde die übrigen dauerhaft stranden lassen — das Board schuldet deren Antworten\nund könnte die Freigabe, die sie starten würde, deshalb nie erreichen. Sie werden daher gemeinsam\ngestartet, und genau das passiert auch, wenn die Arbeitskopie frei ist (das erste Mitglied erwirbt\ndie Sperre, die übrigen erben sie). Welcher Anspruchsbereich als Nächstes drankommt, entscheidet\nweiterhin die Ordnung `(priority, enqueued_at, id)` der Warteschlange; erst nachdem der Kopf gewählt\nist, kommt der Rest seines Bereichs mit.\n\nFreigabe ist jetzt eine deterministische Tatsache, keine Schätzung von Ruhe. Der geprimte Prompt\neiner getriggerten Rolle nennt den Faden, dem sie eine Antwort schuldet, und den genauen Befehl,\nder das abschließt, und das neue Flag `nxc reply --if-unanswered` erlaubt einem Aufrufer, der\nwirklich nicht weiß, ob er schon geantwortet hat, bedingungslos aufzurufen — es postet nur, wenn\nnoch eine Antwort geschuldet wird, und ist sonst ein sicherer No-op. Der Abbau des Agenten-Sidecars,\nder am Ende jeder Sitzung immer läuft, nutzt jetzt genau dieses Flag: Eine Sitzung, die endet, ohne\nden ihr geschuldeten Faden je beantwortet zu haben, bekommt an ihrer statt eine Fehlantwort gepostet,\nstatt den Faden still hängen zu lassen. Sobald die letzte offene Antwort im Bereich einer Kette\neintrifft, gibt genau dieser Antwort-Aufruf die Arbeitsbaum-Sperre frei, zieht den Kopf der\nWarteschlange und startet ihn — kein Daemon, kein Wartender.\n\nDiese Zwei-Stunden-Grenze (`working_tree::WORKING_TREE_LEASE_BOUND`) ist nur ein Rückfall für den\nharten Tod, nie ein Budget oder ein Wecker: Beim Ablauf passiert nichts von selbst, und sie startet\nNICHTS bereits Wartendes — sie sorgt nur dafür, dass eine verwaiste Sperre nicht jeden KÜNFTIGEN\nErwerb abweist, sodass die nächste Kette, die fragt, die Arbeitskopie nehmen kann. Ein angestellter\nTrigger bleibt angestellt, bis eine spätere Kette die Sperre tatsächlich freigibt, nicht bis die\nGrenze verstreicht; und eine Sperre über einen Bereich, in dem gar nichts offen ist (was ein nacktes\nResume nimmt, da es keine Verpflichtung anmeldet), wird stattdessen von der nächsten Antwort in\ngenau diesem Faden freigegeben.\n\nGetrennt davon verwirft ein `send --to `-Trigger, der nach dem Posten seiner\nNachricht scheitert, diese nicht mehr: Die Quittung trägt jetzt `spawned: bool` (`false` sowohl für\n„hinter der Sperre angestellt\" als auch für „der Start selbst ist gescheitert\") und\n`warnings: [...]` mit dem, was schiefging, sodass ein Aufrufer endlich „nichts ist passiert\" von\n„die Nachricht steht im Kanal, aber niemand arbeitet noch daran\" unterscheiden kann — damit ist der\nlange offene Fehler 6j6v.hpv8 miterledigt. Der Exit-Code von `nxc` wird bei einem echten\nStart-Fehlschlag weiterhin ungleich null; ein `Engine`-Aufrufer prüft stattdessen `spawned`.\n\nFassade (Bruch): Die öffentliche API von `crates/chat` bewegt sich durch all das oben Genannte.\n`ReplyRequest` (auf `facade`, `orchestration` und `surface::ReplyThreadRequest`) bekommt je ein\nPflichtfeld `if_unanswered: bool` — `false` ergibt das bisherige Verhalten. `facade::reply` liefert\njetzt `Result>` (`None` ist das neue No-op-Ergebnis).\n`orchestration::ReplyReceipt` bekommt `posted: bool`, ihr `message_id` ist jetzt\n`Option`. `worker::TriggerRequest` bekommt `reply_thread: Option`.\n`orchestration::RoleSpawn` bekommt die Pflichtfelder `priority: Priority` und\n`thread: Option<&str>` (dazu `queued_since: Option<&str>`). `working_tree::QueuedTrigger` bekommt\n`priority: crate::model::Priority` und `enqueued_at: Option` — beide Bestandteile der\nWarteschlangen-Ordnung `(priority, enqueued_at, id)` reisen jetzt mit einem angestellten Eintrag\nmit, damit ein nachgerückter Trigger, der erneut warten muss, Dringlichkeit und bereits gewartete\nZeit behält, statt als `normal` mit aktuellem Zeitstempel wieder einzureihen.\n`orchestration::trigger_role` liefert jetzt `Result` statt\n`Result<(), TriggerError>`;\n`orchestration::TriggerReceipt` und `surface::SendToReceipt` bekommen je `queued_behind`/\n`queue_position` sowie die Pflichtfelder `spawned`/`warnings`. `facade::threads` liefert jetzt\n`Vec` statt `Vec` (die bisherigen Felder wandern unter\n`.quorum`), und `facade::thread_board`s `ThreadBoardView` bekommt dieselben zwei\nArbeitsbaum-Felder; `facade::thread_quorums` bleibt unverändert. Auf der Store-Seite verliert\n`ChatStore::enqueue_working_tree` seinen Parameter `priority: i64` (er reist jetzt am Warteschlangen-\nEintrag selbst mit), und `ChatStore::release_working_tree` entfällt — `release_working_tree_and_take_next`\nbleibt als einziger Freigabeweg übrig und liefert jetzt `Vec` (den gesamten\nAnspruchsbereich am Kopf, in Warteschlangen-Reihenfolge) statt `Option`; ein leerer\nVektor ist das bisherige `None`.\n\nWeil dies die öffentliche API von `nexus-chat` bricht, erscheint diese Epic als MINOR-Release\n(`0.60.0`), nicht als `0.59.x`-Patch: `enforce_facade_breaking_axis` blockiert einen Patch-Bump mit\neinem `facade: breaking`-Fragment fail-closed.", - "facade": "breaking" - } - ], - "notes": { - "en": "### Added\n- **Every channel declares its consolidator** — the thing that takes the members' answers and produces\nthe one answer the requester gets. It always existed, but it was neither declared nor configurable:\nyou could say *that* your channel summarises, and you could write the prompt, but not which model\nwould run it, and there was nothing written down about the other form at all.\n\nThere are two output forms, and a channel names one:\n\n- **`on_complete: pass_through`** (the default, and what a channel that says nothing gets) hands the\n answers over as they stand. That handover now has a **defined shape**: a short header line naming\n the channel, the thread and how many messages it collected, followed by one delimited block per\n message, each labelled with who wrote it. This replaces a one-line-per-answer rendering that could\n not be read at all once an answer ran to several lines — which, for an agent, is the normal case.\n The blocks themselves sit inside a section marked explicitly as untrusted data, with a note that\n the author label on each block is a claim and not an authenticated fact: what a member writes is\n free text, and the party this handover reaches is an agent that would otherwise read all of it as\n coming from its own operator. It is the same marking a summarising channel already puts around the\n answers it hands to its model.\n- **`on_complete: summarize`** runs a model over the answers. The prompt was already declarable as\n `summary_prompt:`; the model now is too, as **`summary_model: fable | opus | sonnet`** — the same\n three names a persona uses. A summarising channel that names no model folds at the `junior` band\n (`sonnet`) rather than at whatever the underlying default happens to be, so the answer to \"which\n model summarised this?\" is now something you can look up instead of infer. Declaring\n `summary_model:` on a channel that does not summarise is reported as a mistake in the declaration.\n\n**A channel never summarises away an \"I cannot\".** If any member replied with `--escalate`, the\nchannel hands the answers over as they stand — no model runs over them — and says so in a line of\nits own above them. This closes the gap the previous release disclosed: an escalation used to travel\nupward only on a channel that passes its answers through, and stopped at a channel that summarises.\nIt now travels on both, and \"no escalation reached me\" is a real answer again on every channel.\n\n**Where that changes what an ORDERED channel does, and it does:** a step of a `flow: sequential`\nchannel that hands work to another channel is waiting on that channel's verdict, and an escalating\nchannel produces none. The escalation itself is what the supervisor branches on — a declared signal\nrather than a token somebody had to invent — so the flow does not move on as if the work had been\ndone, and the escalation stays readable on the channel it happened on.\n\n**Two callers finishing the same channel at the same instant now deliver once.** A completing reply\nlanding at the same moment as a timeout re-check could previously hand the requester the same\nanswers twice on a pass-through channel; a summarising one was already protected. Both are now\nguarded by the same mechanism.\n\nExisting channel declarations are unaffected: every field is optional, a declaration written before\nthis release parses and behaves exactly as it did, and a channel that says nothing about its\nconsolidator still has one.\n- **A channel can now declare its FLOW**, and `nxc send --to ` starts it. Write\n`flow: sequential` on a channel in `channels.yaml` and the order of its `members` stops being a list\nand becomes a fixed order: the channel's supervisor starts the first one alone, and each later one\nonly once the one before it has answered or its deadline has struck. The default is unchanged —\na channel that says nothing about `flow` still starts every member at once, exactly as before.\n\n**A step of that flow may address a whole CHANNEL, not only a persona** — the same two things\n`nxc send --to ` has always reached. A channel named as a step gets its own channel thread\nbelow the flow's, runs its own members and its own consolidator, and hands back its consolidated\nanswer as that step's answer. Where a role and a channel are declared under the same name, the ROLE\nwins, so every `members:` list written before this release means exactly what it meant.\n\nTwo rules refuse a flow that cannot work. A `sequential` channel may not name the same step twice\n(a repeat would be a loop, and there is no declared signal that could route one), and no channel's\nflow may reach ITSELF through its members — the latter is refused when the declarations are loaded,\nnot merely reported, because such a flow does not degrade: it opens a thread and starts a paid\nsession at every hop until the depth guard stops it.\n\n**This is what made the `nxc workflow` group removable in the same release.** A fixed order of a\nrole step followed by a channel step — the shape the shipped example's declared workflow had — runs\nhere in the same order, with no run record behind it, using only the two mutation verbs `send --to`\nand `reply --thread`.\n\nNeither `--expect` nor a declared `expects: ` can be combined with `flow: sequential`. Both\ndecide who answers and in what order, which on an ordered channel is what `members` already decides —\nand a subset is the more dangerous of the two, because it is what the engine actually reads when one\nis present, so an ordered channel declaring `expects: [b, a]` would have run in the subset's order\nwhile its `members` said otherwise. Both are refused by name rather than silently honoured in the\nother list's favour, so on an ordered channel `members` is the only list there is.\n- **A consequence that did not happen now has one defined place to appear.** Until now, when an answer\narrived and the thing it should have caused did not — a channel member that could not be started, a\nrequester that was never woken — the answering side got a clean receipt and the failure went to\nstderr, where no app and no agent reading `--json` ever sees\nit. The party that could have done something about it was, by then, the only party that never heard.\n\nEvery receipt now carries `warnings`: a list of what this very call should have caused and did not,\neach entry naming its class (`step_skipped`, `requester_not_woken`), the thread and session it\nconcerns, why it did not happen, and the underlying failure in words. It is\nalways present, empty included, so a reader cannot miss it by forgetting to look. Because a channel's\nconsolidation runs inside the reply that completes it, the session that answered learns about the\nfailure **in the same call, while it is still running** — `nxc --json reply` shows it, and\n`Engine::reply` returns it.\n\nOne thing this fixes that was previously not merely quiet but lost: a channel handing out a second\nround of work reported only the FIRST member it could not start; now every one of them is named.\n\n**`nxc status` is sharper about dead ends.** A thread that is answered with nothing following it used\nto read ORPHANED whenever the thread above it owed nothing — which is equally true of a branch that\nfinished perfectly. It now stays ORPHANED only if the thread above it never moved on from that\nanswer, and never for a thread hanging under an ordinary conversation that asked nobody anything. The\nrule errs towards saying nothing rather than raising a false alarm, because a place that cries wolf\nis a place nobody reads.\n\nA failed trigger's receipt also names WHY it did not start as a value rather than a sentence, telling\na runtime that refused the spawn (worth retrying) from a session the runtime had already reaped\n(needs a fresh one). `warnings` entries used to be plain strings and are now records; the sentence\nthey used to be is each record's `detail`, and the terminal output is unchanged.\n- An operation is now a THREAD TREE, and `nxc status` reads it. Until now nothing in the data model\nconnected two threads: a real chain — you ask the PM, the PM commissions the coding channel, that\nchannel's lead hands the work to a coder, the review of it fans out to three reviewers — was five to\nseven separate threads across three channels, and nobody could answer \"where do we stand in this?\".\n\nEvery thread now records the thread it was opened OUT OF. The rule is mechanical and needs nothing\nfrom the agent: a `nxc send --to ` issued from inside a running session hangs\nthe new thread under the thread that session is currently working in — the thread it was triggered\ninto, or the one whose reply woke it. A send with no session around it (you, at a terminal) opens a\nROOT, which is what makes it the top of a chain. Nothing is stored to say \"this is a root\": it is\nthe absence of an edge.\n\n`nxc status` answers \"where do we stand\" in three forms. `nxc status --thread ` shows the whole\ntree of the operation that thread belongs to, from the root down and across every channel it\ncrossed; `nxc status --channel ` lists the live operations that STARTED in a channel, as a way\nin; plain `nxc status` shows everything still going on in the workspace. `--json` carries the same\nrecord, and the identical read sits on the library seam as `Engine::status`, so an app builds its\noperation view from it rather than rebuilding the derivation. It is what replaces `workflow status` and\n`workflow list`, which this same release removes.\n\nEach thread reports one of three derived states: **open** (somebody still owes a reply here),\n**answered**, and **ORPHANED** — answered, nothing was opened out of it, nothing upstream is waiting\nfor it either, and the thread above it never moved on from it. The orphaned state is where a\nconsequence that silently failed to happen becomes visible without anyone reporting it; the last of\nthose conditions is what keeps it to the branches that FAILED rather than the ones that finished, and\nit is described in the entry about the place for failed consequences. One deliberate exception at the very top: a ROOT thread\nthat has been answered says `awaiting you` rather than reading like a thread nobody is working on\nany more. That is the normal end of an operation, not a standstill, and it is the only place a human\nappears in the flow at all.\n\nEach thread also carries the session of whoever is working on it, so a consumer holding a thread id\ncan follow that assignee's transcript without ever being told a session id — `nxc status --json`\ngives you the session, `nxc transcript show ` gives you the running account. **This works\nwhile the thread is still open and the assignee has said nothing**, which is the case it exists for: a\nconversation that has gone quiet is exactly when you want to see whether the other side is thinking,\nstuck, or gone. An open thread names the party it is still waiting for; a settled one names whoever\nanswered last. Nothing new is recorded for either — the first comes from where the trigger put that\nsession, the second from the return address its own message already carries. The read is the same for\nevery caller; there is no human-versus-agent distinction anywhere in it.\n\nNothing new is stored for any of this. The status is computed from the thread edges, the reply\nexpectations and the return addresses already in the log — there is no operation record, and nothing\ntakes the place of the run record this release removes.\n- `nxc reply --escalate \"\"` — the note \"I cannot carry out this task\". It is the SECOND and last\nthing an agent may say with `reply`; the first is \"I am finished\". Everything else an agent produces\nbelongs in the transcript and never in the agent-to-agent conversation, because a waiting agent would\nread it as a result.\n\nIt is deliberately not a wastebasket for everything that is not a result. A real follow-up question —\n\"what do you mean by X?\", from somebody who can still do the work — stays `--kind question`, a\ndifferent thing with its own value. `--escalate` carries one meaning and stays committed to it.\n\n**The turn ends either way.** An escalating reply discharges the sender's obligation exactly like a\nfinished one: the session ends, the debt is gone, and nothing about who owes what changes. What is\ndifferent is the OUTCOME, and the channel's supervisor is what decides where that goes.\n\n**The escalation travels upward, on every channel.** A channel member that says \"I cannot\" settles\nits own thread like any other answer, so the set is complete and the channel consolidates — but the\nchannel's own answer to the requester carries the escalation onward instead of folding it into a\nresult. The question can therefore reach the person who commissioned the work, one level at a time.\n\nThis holds for both of a channel's output forms, including one that SUMMARISES its answers\n(`on_complete: summarize`), because a channel never summarises away an \"I cannot\": a set that\ncarried one is handed over as it stands. See the consolidator entry in this same release for what\nthat looks like from the requester's side.\n\nIt is carried by the message's `kind`, a field that already existed and already syncs, so nothing\nabout the synchronised payload changes shape. `--kind escalation` is deliberately not accepted:\n`--escalate` is the one door to it, and asking for both at once is refused. Applications reach the\nsame thing by setting `MessageKind::Escalation` on the reply they already build — there is no flag\nhere that only the command line can send.\n\nIt is DECLARED rather than free, which is exactly what makes it a signal: the channel's supervisor\nbranches on a bit whose meaning is written in the channel, not on a token an agent invents on the\nspot. That is also why the free-text branching token the removed `nxc workflow step done` carried\nneeds no other replacement.\n- A role or a declared channel can now declare `working_tree: exclusive` (the default, `shared`, is\nexactly today's behaviour — no lease, no waiting). Only one chain of role sessions then works\nagainst the repository's working copy at a time: a second chain that also needs it queues instead\nof starting and colliding with the first on the git index and the Cargo build lock — the collision\na coding-and-review pair hit for real in an earlier trial of the role runtime. The holder is the\nCHAIN — the thread the work was commissioned in — not a single session.\n\nHow far \"the chain\" reaches is described by the \"claim area\" entry in this same release, which\nsupersedes what an earlier draft of this entry said about it: the area is the conversation the work\nwas commissioned in TOGETHER WITH everything opened underneath it, so a coding round keeps the\nworking copy while it is waiting on a review round it started. A PERSONA chain\n(`nxc send --to `, one call per step) opens a new conversation per call, so between steps the lease goes free and the release starts whatever is\nat the head of the queue; if an unrelated order queued earlier, that order runs next and the chain's\nown next step queues behind it.\n\nWaiting is never silent. A queued trigger's own receipt says so: `nxc send --to \n--json` gains `queued_behind` (who is holding it) and `queue_position` (1-based) whenever a\ntrigger has to wait, and the human-readable line adds `QUEUED at position n behind …`. `nxc\nthreads list`/`show` carry the same fact afterwards, for a chain that never checked back or a\nclient reading the board later: every thread now reports `working_tree: \"holding\" | \"waiting\" |\nnull` alongside its queue position.\n\nA release hands the working copy to a whole CLAIM AREA, not to one waiting trigger. Several triggers\nshare one claim area whenever a multi-member `working_tree: exclusive` channel had to wait at open\ntime: every member of that board belongs to the board's own thread, so each one queues separately\nunder the same key. Starting only the first of them would strand the rest for good — the board owes\ntheir replies, so it could never reach the release that would start them. They are therefore started\ntogether, which is exactly what happens when the working copy is free (the first member takes the\nlease and the rest inherit it). Which claim area goes next is still decided by the queue's\n`(priority, enqueued_at, id)` order; only after the head is picked does the rest of its area come\nwith it.\n\nRelease is a deterministic fact now, not a guess at quiet. A triggered role's own primed prompt\nnames the thread it owes a reply on and the exact command that closes it, and the new `nxc reply\n--if-unanswered` flag lets a caller that genuinely cannot tell whether it already answered call\nunconditionally — it posts only when a reply is still owed, and is a safe no-op otherwise. The\nagent sidecar's teardown, which always runs at the end of a session, now uses exactly that flag: a\nsession that ends without ever answering the thread it owed gets a failure reply posted on its\nbehalf instead of leaving the thread hanging silently. The moment the last open reply in a chain's\nscope lands, that same reply call frees the working-tree lease, takes the head of the queue, and\nstarts it — no daemon, no poller.\n\nThat two-hour bound (`working_tree::WORKING_TREE_LEASE_BOUND`) is only a backstop for a hard\ndeath, never a budget or a timer: nothing fires when it elapses, and it does NOT start anything\nalready waiting — it only stops an abandoned lease from refusing every FUTURE acquire, so the next\nchain that asks can take the working copy. A queued trigger stays queued until a later chain\nactually releases the lease, not until the bound passes; and a lease over a scope that owes nothing\nat all (what a resume takes, since it registers no obligation) is instead freed by the very next\nreply into that thread.\n\nSeparately, a `send --to ` trigger that fails after posting its message no\nlonger discards it: the receipt now carries `spawned: bool` (`false` for both \"queued behind the\nlease\" and \"the spawn itself failed\") and `warnings: [...]` naming what went wrong, so a caller can\nfinally tell \"nothing happened\" apart from \"the message is in the channel and nobody is working on\nit yet\" — closing long-standing bug 6j6v.hpv8. `nxc`'s exit code still turns non-zero on a real\nspawn failure; an `Engine` caller checks `spawned` instead.\n\nFacade (breaking): `crates/chat`'s public API moves across all of the above. `ReplyRequest` (on\n`facade`, `orchestration`, and `surface::ReplyThreadRequest`) each gain a required\n`if_unanswered: bool` — pass `false` for the previous behaviour. `facade::reply` now returns\n`Result>` (`None` is the new no-op outcome). `orchestration::ReplyReceipt`\ngains `posted: bool` and its `message_id` is now `Option`. `worker::TriggerRequest` gains\n`reply_thread: Option`. `orchestration::RoleSpawn` gains required `priority: Priority` and\n`thread: Option<&str>` (plus `queued_since: Option<&str>`). `working_tree::QueuedTrigger` gains\n`priority: crate::model::Priority` and `enqueued_at: Option` — both components of the\nqueue's `(priority, enqueued_at, id)` order now travel with a queued entry, so a promoted trigger\nthat has to wait again keeps the urgency and the wait it already accrued instead of re-entering as\n`normal`, stamped now. `orchestration::trigger_role` now returns\n`Result` instead of `Result<(), TriggerError>`;\n`orchestration::TriggerReceipt` and `surface::SendToReceipt` each gain `queued_behind`/\n`queue_position` and required `spawned`/`warnings`. `facade::threads` now returns\n`Vec` instead of `Vec` (the existing fields move under\n`.quorum`), and `facade::thread_board`'s `ThreadBoardView` gains the same two working-tree fields;\n`facade::thread_quorums` is unchanged. On the store side, `ChatStore::enqueue_working_tree` loses\nits `priority: i64` parameter (now carried on the queued entry itself), and\n`ChatStore::release_working_tree` is gone — `release_working_tree_and_take_next` is the only\nrelease path left, and it returns `Vec` (the head's whole claim area, in queue\norder) rather than `Option`; an empty vector is the former `None`.\n\nBecause this breaks `nexus-chat`'s public API, this epic ships as a MINOR release (`0.60.0`), not\na `0.59.x` patch: `enforce_facade_breaking_axis` fail-closes a patch bump carrying a\n`facade: breaking` fragment.\n\n### Changed\n- A declared channel's `timeout` is now a deadline PER MEMBER, and every transcript output by that\nmember restarts it.\n\nUntil now the `timeout` produced one absolute instant, stamped on every member of the channel, that\nthen ran stubbornly to its end: a channel declaring `timeout: 30m` gave up on its members thirty\nminutes after it was opened, whatever they were doing. That breaks exactly the case a timeout exists\nto survive — an agent that starts a shell command running for an hour is perfectly healthy at minute\nthirty, and cutting it off there destroys an hour of work and hands the requester an answer that is\nmissing the one member who was actually working.\n\nWhat `timeout: 30m` means now is \"thirty minutes of SILENCE from this member\". Behind every member of\na channel stands exactly one session, and every write to that session's transcript — a thought, a\ntool call, a result — restarts its window from that moment. A member that keeps showing signs of life\nis never given up on, however long its work takes; a member that shows none for the whole window\nstill times out, and the channel consolidates with the answers it has, noting who did not respond.\nThe clocks are independent: one member running out of time settles that member and nobody else.\n\nThree smaller consequences of the same change. A member asked AGAIN — a second turn on the same\nchannel — starts its window again from the moment it was asked, instead of inheriting one that had\nalready run out and being over before it began; the re-check that eventually gives up on it is\nscheduled along with that window, so a second turn is given up on automatically just like the first.\nA member that has to WAIT for the working copy before it can start now begins its window when it\nactually starts, rather than being given up on while it is still queued. And a channel's own\nscheduled re-check no longer forces a consolidation just because the instant it was scheduled for\nhas arrived — it finds the members alive, declines, and re-schedules itself for the next moment one\nof them falls due, so the automatic give-up still happens; it just happens at the right time now.\n\nAn unreadable `timeout` (`timeout: twenty-minutes`) is now refused with an error naming the channel\nfield and the value, before anything is opened, instead of being carried further as an argument\nerror about a `--deadline` flag nobody typed. A `--deadline` given as an absolute instant is\nunchanged and stays deliberately stubborn: naming a point in time means that point in time, and\nnothing moves it.\n- A declared channel now has TWO LEVELS, and every thread in it has exactly two ends. Until now\n`nxc send --to ` stamped ONE thread, wrote every member into its `expects_reply_from` and\nstarted all of them on that same thread: the requester and all three reviewers sat in one\nconversation, each of them read every other one's answer, and the data model's normal case was a\nthread with N+1 ends.\n\nWhat a `send --to ` produces now is the CHANNEL THREAD — the requester at one end, the\nchannel's SUPERVISOR at the other — and, hanging under it, one thread per member. Each member thread\nhas the supervisor at one end and that one member at the other. Three reviewers are four threads, not\none, and the reviewers cannot see each other, because they are not in the same conversation rather\nthan because a filter hides them.\n\n**The supervisor is machinery of the engine**, never a person and not a declared role either. It is\nthe reserved identity `/__channel__`, and it does three things: it takes the incoming request\n(a `send --to `, and every later `reply` into the channel thread) and opens the per-member\nsends itself; it waits for the SET of member threads rather than for one thread's list of names; and\nwhen they have all answered — or their deadlines have struck — it runs the channel's declared\n`on_complete` policy and delivers the result to the requester. It can be OCCUPIED by an agent\nsession: that is exactly what `on_complete: summarize` is, and that session's reply is what\ndischarges the channel thread.\n\nAll of it runs SYNCHRONOUSLY, inside the write path of the `reply` that caused it: the message is\nwritten, the obligation is recognised as discharged, the supervisor runs, the next threads are opened\nand their sessions are STARTED, and only then does the call return. What is waited for is the\nstarting, never the result. The reason is not latency — it is that only here does a failed\nconsequence still have an addressee: asynchronously the replying session is long gone by the time its\nconsequence runs, and a failure is noticed by nobody.\n\nTwo things a caller will see differently. `nxc send --to --json` reports `expects:\n[\"/__channel__\"]` — the channel thread's other end — and the per-member expectations are one\nlevel down, one per member thread, where `nxc status --thread ` shows them. And a member answers\nin ITS OWN thread: the id in the message a member is started with is its own, and nothing ever points\na member at the channel thread.\n\nA member's turn is now also RE-OPENED properly. A `reply` into the channel thread from its requester\nis the same door as the first message: the supervisor re-declares each member's expectation on its\nexisting thread and resumes that member's session with the obligation attached — so\n`nxc reply --thread --if-unanswered`, the call an agent session makes as it shuts down, has\nsomething to discharge from the second turn onwards. Before this, nothing re-declared an expectation\non a resume at all, and that safety net was a silent no-op past turn one.\n\nThis is what makes a channel a whole flow with no run record behind it — which is why the\n`nxc workflow` group could be taken away in this same release.\n- The working copy is now protected for as long as the TASK runs. Two questions decide it, and they\nhave two different answers.\n\n**Whether a chain is protected is derived upward from the declared members, one step.** A channel\nwhose declared member says `working_tree: exclusive` counts as needing the working copy, without the\nauthor repeating it on the channel — say it once, on the persona that builds, and every channel that\npersona works in is covered. It stops after one step on purpose: a channel does NOT catch the need\nfrom another channel through a member the two happen to share. Otherwise a coding channel would hand\nits need to a review channel whose members only read, that channel would hand it on again, and every\nchannel in a workspace would end up exclusive. Declaring `working_tree: exclusive` on the channel\nitself still works exactly as before, and is still the right way to say \"this ROUND needs it\" when no\nsingle member does.\n\nThe practical difference: a protected channel now waits as a whole. Before, a channel that said\nnothing itself let the member that declared the need wait while its silent siblings started anyway —\nthe board opened half queued and half running, on opposite sides of a lease meant to cover the chain.\n\n**How far the protection reaches is the conversation the work was commissioned in, plus everything\nopened underneath it.** So it holds while a coding round waits on a review round it started: the\nreview conversation and the reviewers' own threads hang under the coding one and are inside the same\nclaim area. It also finally covers a side conversation a working session opens for itself — that used\nto show on the thread board as belonging to no lease at all. And it stops going upward: the\nconversation you had with the agent that commissioned the work is ABOVE the claim and holds nothing,\nso an unanswered human never keeps the working copy overnight.\n\n**The release happens when that conversation is CLOSED, which is not the same as \"nobody owes an\nanswer right now\".** A plain reply closes it and the next order in the queue starts. A\n`reply --escalate` — \"I cannot carry out this task\" — does NOT close it, even though the sender's\nobligation is discharged either way: the question is travelling upward, possibly as far as you, the\ntask is mid-flight, and the working copy stays protected. Not even a long silence changes that. **An\nunanswered follow-up question (`--kind question`) holds it the same way**, for the same reason: in\nboth cases nobody is working, and releasing would let a second chain start into a working copy the\nquestioner is about to resume into. This holds **anywhere in the protected area**, not only in the\nconversation the work was commissioned in — a reviewer three levels down who asked a question and is\nwaiting for the answer keeps the copy, even when every conversation above it has already wrapped up\nwithout noticing.\n\n**Answering it puts the round back to work, and the copy is free when that round finishes.**\nAnswering means giving the round its next turn — a reply into the channel's own conversation, the\nsame door the work came in by — which re-opens every member's turn and starts them again. Nothing is\nreleased at that instant, and nothing should be: the members are working again. What the answer ends\nis the WAITING, so the copy comes free on the round's own completion, without going anywhere near the\ntwo-hour limit. The limit is only what happens if the question is never answered at all.\n\nThe error direction is deliberate — somebody who escalates or asks needlessly holds the copy a\nlittle too long, which is harmless beside a copy handed away while the task is still running.\n\n**Gone with it: the rule that a run of the removed workflow engine held the working copy for as long\nas it was `running`.** That was a lid on a narrower problem — a role step of such a run left no\nobligation anywhere, so the run looked finished the moment it advanced onto one — and it cost two\nthings: a wedged run held the copy until the two-hour bound, and the release came when the RUN ended\nrather than when the work did. The claim area now CONTAINS the conversations the work happens in,\nwhich is what the lid was substituting for, and on this release's surface a channel member is always\ncommissioned with a conversation and an obligation, so the shape that needed the lid cannot arise.\n\nFacade (breaking): `nexus_chat::working_tree`'s release derivation changes behaviour for the same\ninputs. `ChatStore::work_scope_threads` returns the whole subtree from a `Thread` scope instead of the\nthread plus its direct member threads, and `WorkScope` itself now has two variants rather than three\n— the `Run` arm went with the workflow engine this release removes. New alongside them: `ChatStore::work_scope_handed_back`,\n`ChatStore::thread_subtree`/`thread_subtrees`, `ChatStore::last_reply_kind` (the wider read\n`last_reply_escalated` now delegates to — that function is unchanged), and\n`Definitions::channel_needs_working_tree`.\n\n### Fixed\n- **The reply that completed a board is now the one that actually completed it.** Once every handle a\nthread was waiting on has answered, the requester is woken with the collected answers and one of them\nis marked as the completing reply — the answer that closed the turn. That mark was picked by\ncomparing message ids. A message id begins with the millisecond it was minted, so two answers written\nin the same millisecond differ only in the random tail that follows and the mark fell on whichever of\nthem happened to sort higher. Between two synced devices the ids can disagree with the real order by\nany distance at all, because each device stamps its own.\n\nThe completing reply is now chosen by the same causal order the rest of a thread is read in — the\norder the messages themselves appear in, the one that decides which answer `nxc status` reports as\nthe last, and the one that decides whether the last answer was an escalation. Those three already\nagreed with each other; this one read did not, and it was the last place in the message store still\nordering by the raw id.\n\nWhat changes for you: on a thread whose answers land close together — the normal case when a\nchannel's members finish at once — the answer highlighted as the completing one, and the message id\nreported next to it, can differ from what an earlier release reported for the very same conversation.\nThe new answer is the correct one.\n\nUnchanged, and deliberately so: whether a completed board still needs your attention is still decided\nby your read cursor in the ordinary way, so nothing you had already read comes back.\n- Two processes working on one workspace at the same time can no longer lose each other's writes.\n\nThe logical clock that orders every change used to be read once — when a process opened the\nworkspace — and never looked at the file again. Two processes that started from the same reading\nstamped their changes with the same number, and the merge rule, which keeps a change only if it\nstrictly beats what is already recorded, then dropped the second one without a word: the command\nreported success, exit code 0, and the board still showed the old value. A long-running app holding\nthe workspace beside short-lived `nxf`/`nxc`/`nxm` calls drifted the same way, and further with\nevery minute it stayed open.\n\nThat is exactly how this platform is meant to be used — several agents in one workspace — and it is\nwhy the defect went unseen: the test suite runs everything in a single process, where one handle\nobserves every change and the divergence cannot form. It reached every last-write-wins field, on the\nboard and in chat and memory alike. And it was not theoretical: on the day of the fix, three of the\ntwenty workspaces on the development machine carried its marks, the nexus-flow board among them.\n\nEvery change now re-reads the workspace's own high-water mark at the moment it is written, holding\nthe write lock across the read and the write — so a change is always stamped above everything\nalready in the file, whichever process put it there. Underneath it, the log now refuses a duplicate\nstamp outright rather than accepting it and leaving the merge rule to discard it in silence. A\nworkspace that already carries duplicates from before opens and works exactly as it did; it simply\ncannot be given the second guarantee retroactively, and is never blocked or rewritten for it.\n\nFacade (breaking): no signature moves, one documented behaviour does.\n`nxs_foundation::store::Store::apply` returns the ops it did not fold into the views; that vector\nnow also carries a merged op the log REFUSED, because a different op already holds its\n`(lamport, site)` coordinate. Previously such an op was stored and then silently outranked. A\nconsumer that reads the vector strictly as \"op shapes this version does not understand\" sees a new\nkind of member. It is reachable only when two replicas share a `site_id` — which 63 bits of entropy\nput out of reach for real replicas, and which the prefix registry already treats as a repairable\nmisconfiguration — so in practice no correct caller sees a change. `Store::emit` gains the matching\nassertion for a locally minted op, unreachable once the clock is refreshed under the write lock.\n- A chain that holds the working copy can now commission more work on it without blocking itself.\n\nUntil now, any exclusive claim opened from inside a chain that was already holding the working copy\nwent to the back of the queue — behind the very chain that opened it. That chain could then never\nfinish, because what it was waiting for was the trigger it had just parked. Nothing started, nothing\nreported an error, and the queue would not drain even after the two-hour backstop. It took no exotic\nsetup: a plain `nxc send --to ` for a role that declares `working_tree: exclusive`, sent\nfrom a session already inside a protected chain, was enough — and a nested protected channel did the\nsame.\n\nThe cause was an asymmetry. The claim area has been \"the conversation the work was commissioned in,\ntogether with everything opened beneath it\" since the claim-area change, but only the RELEASE side\never asked it that way; the acquire side compared claim keys exactly and resolved a thread just one\nlevel upward. So work opened deeper in an area the caller already held came out with a different key\nand looked like a rival. Both ends now read the same area, so such a trigger inherits the claim\ninstead of queuing behind it — while a genuinely unrelated chain still waits its turn exactly as\nbefore.\n\nNot covered by this, and tracked separately: when a holder dies outright, nothing drains its queue,\nso whoever is already parked waits past the two-hour bound until some other chain acquires and\nreleases.\n- A reply now settles the TURN it answers, not the whole thread. Until now, a thread counted a role\nas \"has replied\" from its first message there onwards, forever — which is the right reading for a\none-shot review board and the wrong one for a conversation that goes back and forth. The moment a\nsupervisor came back onto the same thread with a second piece of work, everything downstream of that\ncount was already convinced the role owed nothing: `nxc reply --if-unanswered` — the safety net the\nagent sidecar fires at the end of every session, so a session that dies without answering still\nleaves an answer behind — silently did nothing from the second turn on, and the working-tree lease\nread the chain as finished while the role was still writing its final report, handing the working\ncopy to a rival chain mid-flight.\n\nWhether a reply is still owed is now measured from the moment it was asked for: a thread's\n`expects_reply_from` declaration carries its own position in the log, and only what a handle wrote\nAFTER the current declaration counts as an answer to it. Re-declaring the expectation — the same\nhandles or different ones — therefore opens a new turn, and everyone the new declaration names owes\nan answer to it. Nothing about the synced message format changes; this is derived from what the log\nalready carried, so no device needs a migration and a peer that only ever received the ops reaches\nthe same answer.\n\n**One caveat, and it belongs to the substrate rather than to this rule.** \"After the current\ndeclaration\" is decided in the underlying log's own causal order, and that order has a known hole:\nthe counter it uses belongs to an OPEN STORE HANDLE — recovered once when the handle opens, never\nre-read from the file afterwards — while every process on one device shares a replica identity. Two\nwriters holding the same workspace open at the same moment therefore do not observe each other's\npositions: a long-lived host that embeds the engine for its lifetime beside short-lived `nxc` calls,\nor simply two `nxc` calls running at once. Where that happens, a message written BEFORE a\nre-declaration can be counted as an answer to it — on one device, with no sync involved — and the\nconsequences are the ones the rule above is there to prevent: a turn that reads as discharged the\ninstant it is opened, and a working-tree lease released while the work is still running. It is\ntracked as its own item (`6j6v.fc5p`), the fix belongs in the substrate every one of these reads\nsits on, and nothing about the rule described here changes when it lands.\n\n**Removed with it: the `nxc threads expect` verb.** Re-declaring who a thread expects a reply from\nused to be something a human could type, and its purpose was to complete a board stuck on a reviewer\nwho never came back — narrow the expected set to whoever had answered, and the board closed. Under\nthe rule above that is no longer what a re-declaration means (it asks the named handles again, as of\nnow), and rather than build a special case to keep the old effect, the verb goes: it was already on\nthe list of `nxc` commands being retired as the agent-to-agent surface is cut back to what an agent\nactually needs. **The capability itself has not gone anywhere** — the write is still there for\nembedding apps and hosts as `facade::set_expects` (opener-only, same errors), and it is what the\nchannel supervisor uses to hand a role its next turn. What disappeared is a hand-typed door.\nTo close a board nobody is going to answer, drop the expectation instead — a set that no longer names\nthat handle leaves nothing outstanding, which is what the working-tree release, the `--if-unanswered`\ndischarge and the deadline advisory all read.\n\n**On an existing workspace this applies retroactively, and it is visible.** These are derived reads,\nnot stored state, so every board is re-derived under the new rule the first time it is read after the\nupgrade. A board that was completed by narrowing it in the past reads as outstanding again (its\nanswers predate the narrowing), which also means the chain holding the working copy for that thread\nkeeps holding it until somebody answers the current declaration or the expectation is dropped — and\nthat a thread with a past deadline can show up as waiting past it again. Nothing is lost or rewritten;\nwhat changed is the question being asked of the same log.\n\nThe kind of a message is deliberately not consulted anywhere in this: an answer that says \"I cannot\ndo this\" settles the turn exactly like a finished one, because the session ends either way.\n\nFacade (breaking, behavioural — no signature moves): `nexus_chat::store::ChatStore::thread_quorum`\nand `thread_quorums` return the same shape, and `ThreadQuorum`'s `replied` / `outstanding` /\n`complete` now answer per turn rather than per thread. A consumer that renders a board sees a\nre-declared thread reopen; a consumer that treated \"replied once\" as permanent must read the\ncurrent declaration instead. `facade::set_expects`'s receipt reports the re-asked set for the same\nreason.\n\n### Removed\n- **Declarations move to `.nxs-personas/`.** Who your agents are, and where they talk, is declared in\n`/.nxs-personas/` from now on — one `.yaml` per persona, and `channels.yaml`\nfor the channels they share, which is also where a flow is declared. The old `/roles/` folder is\nstill READ, so an existing project keeps working untouched, and the engine now says out loud that it\nread one: `nxc list` and `nxc prime` name the legacy folder and the path the declarations belong at,\nand `nxc prime --json` carries the same fact as data. **Nothing is moved for you.** An application\nthat has only opened someone else's project must not silently rewrite it — that is an irreversible\nchange to somebody else's property triggered by merely looking — so the move stays a thing you do\ndeliberately: copy the folder, and `.nxs-personas/` takes over the moment it carries a declaration.\n\n**Where a catalogue came from is now part of the answer.** `Engine::definitions()` still succeeds on\na workspace that declares nothing — a read that fails because there is nothing to read is a bad read,\nand it would turn a legitimate state into an exception an app has to catch just to render an\nonboarding screen — but the catalogue it returns now carries the resolution beside it: the path a\ndeclaration belongs at, which folder was actually read (`.nxs-personas/`, a legacy `roles/`, or\nneither), whether a legacy folder was found at all, and how many declarations there are. One\nstructure answers both \"where did this come from\" and \"is this project still on the old layout\".\n\n**The empty workspace stops being anonymous**, and each surface answers it in its own register. `nxc\nlist` and `nxc prime` are orientation: they do not fail, they say that nothing is declared and where\na declaration goes, and exit 0 — an empty list on its own is a lie by omission, and an error would\nmake a legitimate state exceptional. `nxc send --to ` is the one that genuinely refuses: it\nis an action with a mandatory target, and in a workspace with no declaration there is none, so it\nfails by name instead of reporting a missing target the caller would go looking for a typo in.\n\n**`nxc init` leaves the folder behind**, empty, with a short explanation of the format inside it and\nno shipped personas. It never touches an existing `roles/` folder, and the empty `.nxs-personas/` it\ncreates does not shadow one: the new location wins only once it carries a declaration of its own, so\nrunning `init` in an existing project cannot silently unteam it. A second `init` keeps an explainer\nyou have edited.\n\nThe two shipped example teams move with the folder:\n`crates/chat/examples/role-runtime-v2/.nxs-personas/` and\n`crates/chat/examples/role-runtime-v3/.nxs-personas/`.\n\n**The injection path is gone with it.** `EngineConfig` no longer takes a definition source and\n`Engine::set_definitions` no longer exists: personas and channels come from `.nxs-personas/` for an\nembedding app exactly as for the command line, and a later in-app role editor writes into that same\nfolder rather than into an app-owned database. The property the injection path existed for is\nunchanged and now costs nothing to state — an edit is visible to the next call on the handle and on\nevery existing clone of it, with no reopen and no torn-down subscription, because nothing is cached.\n**`Engine::definitions()` — the READ — stays**, and a gate holds it there rather than leaving it to\ncare: `crates/chat/tests/seam_disposition.rs` names it with its reason and fails if it disappears.\n\n**Three entrances come off the surface, and the session-start block is re-cut to what is left.**\n`nxc agents list` / `register` / `search` are gone: a team is DECLARED, not registered — a persona\nis a file in `.nxs-personas/`, which is readable, reviewable and survives the run, where a runtime\nprofile row lived in one workspace's database and nothing ever reviewed it. `nxc list` is the read\nover it and carries the two fields `agents search` searched. `nxc workflow tickets add` is gone with no replacement — as, in this\nsame release, is the whole `nxc workflow` group it hung under. What a message is about is what\n`--ref nxf_ids=` says on it. And `nxc inbox` / `nxc read` come off the AGENT surface while staying for a human and on\nthe app seam: what a session is TAUGHT at its start is now exactly `list`, `send --to`,\n`reply --thread`, `nxc status`, `search` and `transcript show`, because a persona is handed its\nunread at session start and on resume rather than asking for it.\n\n**A gate now records what happens to the library seam when an entrance goes.** For every verb taken\noff the command line it is decided and written down whether the corresponding read or write stays on\n`Engine` — and the gate holds both halves, so a removal cannot land without the record moving with\nit, and a read an app renders cannot quietly disappear with the verb that used to reach it.\n\n**And every `nxc --help` page is a golden now.** Nothing watched that text before, and it had\nalready bitten once; with verbs coming off the surface, a help page naming one that no longer exists\nis a false map an agent acts on.\n\nFacade (breaking): `Engine::workflow_tickets_add`, `orchestration::workflow_tickets_add`,\n`WorkflowTicketsAddRequest` and `WorkflowTicketsReceipt` are removed (and the rest of the workflow\nsurface with them, in this same release — see its own entry). `DefinitionSource` and\n`EngineConfig::definitions` are removed, as is\n`Engine::set_definitions`. `Workspace::roles_dir()` is gone, replaced by `workspace_root()`,\n`personas_dir()`, `legacy_roles_dir()` and `declaration_dir()` on `ChatWorkspaceExt`.\n`Definitions::from_roles_dir` is now `Definitions::from_dir` (read ONE named folder) beside the new\n`Definitions::resolve(workspace_root)` (resolve both locations and record which was read).\n`Definitions` gains `source()`, `len()` and `is_empty()`. `facade::prime`/`facade::prime_for` take a\n`&DeclarationSource` where they took a `&Path`, and `facade::PrimeReport` gains a required\n`declarations: DeclarationSource` field — so `nxc prime --json` gains a `declarations` object, always\npresent.\n- **A channel is a declaration now, or it is nothing.** Until this release you could address a channel\nthat nothing declared — one you had minted with `nxc channels create`, or whose id you simply\nhappened to know. Such a channel is something addressable with no policy behind it: nobody said who\nanswers on it, by when, or what becomes of the answers, so nothing could route them. That is the\nstate this rebuild set out to abolish, and this release is where it goes.\n\n**`nxc channels` is gone, whole.** `create` and `dm` minted channels; a channel is declared in\n`.nxs-personas/channels.yaml` and materialises the first time somebody sends to it, and a direct\nconversation opens itself the moment you write `send --to `. `join` and `leave` wrote\nmembership; membership is the declaration's `members:` list. `list` is `nxc list`, which reads the\ndeclared team and its channels.\n\n**`channels public` has no successor on the command line, and that is a decision rather than an\noversight.** It listed the PUBLIC channels in this workspace's store — including front doors that\narrived by sync from other projects, which no declaration here names and which `nxc list` therefore\ncannot show. Cross-project discovery leaves the agent surface; the read stays on the library seam as\n`Engine::public_channels`, for an application to render.\n\n**`nxc ask` is gone.** `send --to ` opens the board, and the channel's own declaration says\nwho must answer, by when, and what becomes of the answers — so a per-call `--expect` was a second\nanswer to a question the declaration already answers, and it goes with the verb. `--deadline` does\nNOT: it moved to **`nxc send --deadline`**, and it still takes either an absolute RFC3339 instant or\na `` duration. A duration has a declared form (a channel's `timeout:`) but an absolute\ninstant has none, so dropping the flag would have taken a capability away instead of collapsing two\nentrances into one.\n\n**`send` and `reply` each take exactly one positional argument now: the body.** The target is a flag.\n\n- `nxc send \"\"` → `nxc send --to \"\"`\n- `nxc reply \"\"` → `nxc reply --thread \"\"`\n\n`--thread` is required. A first token that means \"channel\" in one invocation and \"body\" in the next\nis also the one shape the argument parser could never check for you.\n\n**Replying to a single MESSAGE id is gone with it.** `reply ` used to answer \"the\nconversation that message is in\", which is what `--thread` says outright. A conversation has one\naddress.\n\nTwo consequences worth knowing before you upgrade:\n\n- **`nxc reply --json` renders one shape.** The positional form built its own JSON object and\n carried both `persisted` and `posted` for one fact; `reply --thread` has always serialized the\n typed receipt, which carries `posted`. With one form left there is one name. Everything else in\n that receipt — `thread_id`, `resumed`, `message_id`, `warnings` and the `wake_skipped` finding —\n is unchanged.\n**One label changed with the verb.** `nxc ask` treated a message as a `question` unless you said\notherwise; `nxc send` treats it as `info`. So a board you used to open with a bare\n`nxc ask \"\"` now carries `info` where it carried `question`. This is a label — what\n`search` and `inbox` show you — and nothing routes, waits or decides on it. Type\n`--kind question` if you want it back. The default was not carried over on purpose: `ask` could\nhave one because it only ever addressed a channel, while `send --to` addresses a persona or a\nchannel, and a default that changed depending on which one your target turned out to be is the kind\nof hidden difference this release exists to remove.\n\n**And a board still wakes its requester exactly once.** Answering a thread hands the turn back to\nwhoever is on the other side — but a fan-in board has no other side, and its requester is woken by\nthe completion, once. That was previously true because the two `reply` shapes reached different\ncode; with one shape left it is stated as a rule instead, so a partial answer, a late answer, or an\nanswer from somebody who was never expected no longer wakes the requester again.\n\n**What happens to channels you already have.** They stay in your workspace and their messages stay\nreadable through `nxc search` and `nxc inbox` — but a channel no declaration names stops being a\ntarget. `nxc send --to ` says so by name, and says where a declaration goes, rather than\nanswering \"no such target\" and sending you looking for a typo in an id that is spelled perfectly.\n- **`nxc send` has one target now: `--to`.** `--role` and `--session` are gone, and with them the two\nlibrary methods behind them — `Engine::role_trigger` and `Engine::role_resume`.\n\n**`send --role ` collapses into `send --to `.** Both hand a persona a task, both run\nthe same code, and since the previous release neither could name a channel — so both open the direct\nconversation between caller and persona by themselves. What `--to` adds is the reason the collapse\ngoes this way round: it opens a THREAD and tells the persona which one to answer into. `--role`\nposted without a thread, which left its answers with no address at all, because `reply --thread` is\nthe one way left to answer. Anywhere you wrote `nxc send --role coder \"…\"`, write\n`nxc send --to coder \"…\"`; you get a thread id back, and that is what a reply needs.\n\n**`send --session ` has no successor, and that is a decision rather than an oversight.** It\npushed a new task into a persona's LIVE session. `reply --thread` is a different act: it answers a\nconversation, and it reaches the other side through the thread's return address — the newest message\nsomebody else posted there. A persona you have just summoned and that has not answered yet has\nposted none, so a follow-up is recorded in the thread but wakes nobody until the persona replies of\nits own accord. Delivering into a session that is mid-turn needs a signal into a running agent that\ndoes not exist yet; it is tracked as its own piece of work and will arrive as\n`reply --thread --force`. Until then this is a capability the command line gives up, named here\nrather than left to be discovered.\n\n**For applications embedding the library:** `Engine::send_to` is the one door to a persona. It takes\na target rather than a role handle, returns a thread id beside the session id, and declares who the\nthread expects a reply from. `Engine::role_resume` has no replacement, for the reason above. The\nmechanism underneath both is untouched — only the flat handle methods are gone.\n- The `--model` option on `nxc send` is gone. Choosing a model for one single call is no longer\nsomething typed on the command line: it was on the list of entrances being retired as the\nagent-to-agent surface is cut back to what an agent actually needs (item 6j6v.dvyq §3), and the\ndeclared consolidator landing in this same release brought its removal forward — with a channel now\nnaming the model its answers are folded with, a per-call override and a declared model would be two\nanswers to the same question.\n\n**Which model a session runs on is declared, not typed.** A persona says so with `model:` (or the\ncoarser `stage:`), and a channel now says so for its fold with `summary_model:`. Nothing about that precedence changed, and a role that names no model\nstill runs on the default exactly as before.\n\n**The capability itself has not gone anywhere.** An application embedding the engine still chooses a\nmodel per call — the field is unchanged on every request it already builds. Only the hand-typed door\nclosed.\n- The `nxc threads expect` command is gone. Re-declaring who a thread expects a reply from is no\nlonger something a human types: it was on the list of `nxc` commands being retired as the\nagent-to-agent surface is cut back to what an agent actually needs (item 6j6v.dvyq §3), and the\nturn-scoped reply rule landing in this same release is what brought its removal forward — under that\nrule a re-declaration asks the named handles again rather than closing a board, so the one thing this\nverb was typed for is no longer what it does.\n\n**The capability itself has not gone anywhere.** The write is still on the library seam as\n`facade::set_expects`, unchanged and still opener-only with the same errors, and it is what a channel\nsupervisor uses to hand a role its next turn. Only the hand-typed door closed.\n\nTo close a board nobody is going to answer, drop the expectation instead: a set that no longer names\nthat handle leaves nothing outstanding, which is what the working-tree release, the\n`reply --if-unanswered` discharge and the deadline advisory all read.\n- The `nxc workflow` command group is gone, and so is the declarative workflow-run engine behind it —\n`workflow start`, `workflow step done` (with its `--outcome` flag), `workflow status`,\n`workflow list` and `workflow liveness`, the `workflow_runs` record they wrote and read, and the\n`.nxs-personas/workflow.yaml` / `.nxs-personas/workflows/*.yaml` declarations that described them. A\nworkspace that still has those files simply no longer has anything that reads them.\n\n**A channel IS the flow now, and that is where every one of those verbs went.** A declared channel\ncarries its own members, its own order (`flow: sequential`), its own deadline and its own\nconsolidation, so `send --to ` starts a run of it and `reply --thread` moves it on — the\nchannel's supervisor decides what comes next, which is why no agent has to utter a branching token\nany more. Where an operation stands is `nxc status`: `--thread` for one operation, `--channel` for\nthe ones that started in a channel, and with no argument, everything still going on here.\n\n**On the library seam** `Engine::workflow_start`, `workflow_step_done`, `workflow_tick`,\n`workflow_liveness`, `workflow_status` and `workflow_list` are removed, and so is the workflow event\nstream — `workflow_events`, `workflow_event_cursor` and `subscribe_workflow`. What an app watching a\nworkspace uses instead already exists: `Engine::subscribe` for the \"someone wrote, re-read\" tick, and\n`Engine::status` for where an operation stands. `Definitions::new` takes two arguments rather than\nthree, and `PrimeRoster` no longer carries a `workflows` list.\n\n**Two capabilities are given up rather than replaced, and both are named here rather than left to be\nfound.** The step-liveness watch — a nudge and then an escalation when a running step went quiet —\nwas keyed on a run's current step and goes with the runs; what remains against a member that falls\nsilent is the per-member channel deadline, which strikes on a declared `timeout` but does not nudge.\nAnd a message no longer inherits a run's commissioned ticket set automatically: name the items it is\nabout with `--ref nxf_ids=`, which always took precedence over the automatic stamp anyway.\n\nThe `workflow_runs` tables are dropped from an existing workspace on its next open. Messages,\nthreads and channels are untouched, and an older `nxc` opening the same workspace afterwards\nrecreates its own empty tables rather than failing. The one shape that does not self-heal, said\nplainly rather than left to be met: an older process that already holds the workspace open while a\nnewer one drops the tables under it will panic if it then folds a workflow op. Close the old process\nbefore upgrading — or, since this is a local tool rather than a fleet, simply do not run two\nversions against one workspace at the same time.\n\n### Facade Contract\n- `breaking` · **Every channel declares its consolidator** — the thing that takes the members' answers and produces\nthe one answer the requester gets. It always existed, but it was neither declared nor configurable:\nyou could say *that* your channel summarises, and you could write the prompt, but not which model\nwould run it, and there was nothing written down about the other form at all.\n\nThere are two output forms, and a channel names one:\n\n- **`on_complete: pass_through`** (the default, and what a channel that says nothing gets) hands the\n answers over as they stand. That handover now has a **defined shape**: a short header line naming\n the channel, the thread and how many messages it collected, followed by one delimited block per\n message, each labelled with who wrote it. This replaces a one-line-per-answer rendering that could\n not be read at all once an answer ran to several lines — which, for an agent, is the normal case.\n The blocks themselves sit inside a section marked explicitly as untrusted data, with a note that\n the author label on each block is a claim and not an authenticated fact: what a member writes is\n free text, and the party this handover reaches is an agent that would otherwise read all of it as\n coming from its own operator. It is the same marking a summarising channel already puts around the\n answers it hands to its model.\n- **`on_complete: summarize`** runs a model over the answers. The prompt was already declarable as\n `summary_prompt:`; the model now is too, as **`summary_model: fable | opus | sonnet`** — the same\n three names a persona uses. A summarising channel that names no model folds at the `junior` band\n (`sonnet`) rather than at whatever the underlying default happens to be, so the answer to \"which\n model summarised this?\" is now something you can look up instead of infer. Declaring\n `summary_model:` on a channel that does not summarise is reported as a mistake in the declaration.\n\n**A channel never summarises away an \"I cannot\".** If any member replied with `--escalate`, the\nchannel hands the answers over as they stand — no model runs over them — and says so in a line of\nits own above them. This closes the gap the previous release disclosed: an escalation used to travel\nupward only on a channel that passes its answers through, and stopped at a channel that summarises.\nIt now travels on both, and \"no escalation reached me\" is a real answer again on every channel.\n\n**Where that changes what an ORDERED channel does, and it does:** a step of a `flow: sequential`\nchannel that hands work to another channel is waiting on that channel's verdict, and an escalating\nchannel produces none. The escalation itself is what the supervisor branches on — a declared signal\nrather than a token somebody had to invent — so the flow does not move on as if the work had been\ndone, and the escalation stays readable on the channel it happened on.\n\n**Two callers finishing the same channel at the same instant now deliver once.** A completing reply\nlanding at the same moment as a timeout re-check could previously hand the requester the same\nanswers twice on a pass-through channel; a summarising one was already protected. Both are now\nguarded by the same mechanism.\n\nExisting channel declarations are unaffected: every field is optional, a declaration written before\nthis release parses and behaves exactly as it did, and a channel that says nothing about its\nconsolidator still has one.\n- `breaking` · **A channel can now declare its FLOW**, and `nxc send --to ` starts it. Write\n`flow: sequential` on a channel in `channels.yaml` and the order of its `members` stops being a list\nand becomes a fixed order: the channel's supervisor starts the first one alone, and each later one\nonly once the one before it has answered or its deadline has struck. The default is unchanged —\na channel that says nothing about `flow` still starts every member at once, exactly as before.\n\n**A step of that flow may address a whole CHANNEL, not only a persona** — the same two things\n`nxc send --to ` has always reached. A channel named as a step gets its own channel thread\nbelow the flow's, runs its own members and its own consolidator, and hands back its consolidated\nanswer as that step's answer. Where a role and a channel are declared under the same name, the ROLE\nwins, so every `members:` list written before this release means exactly what it meant.\n\nTwo rules refuse a flow that cannot work. A `sequential` channel may not name the same step twice\n(a repeat would be a loop, and there is no declared signal that could route one), and no channel's\nflow may reach ITSELF through its members — the latter is refused when the declarations are loaded,\nnot merely reported, because such a flow does not degrade: it opens a thread and starts a paid\nsession at every hop until the depth guard stops it.\n\n**This is what made the `nxc workflow` group removable in the same release.** A fixed order of a\nrole step followed by a channel step — the shape the shipped example's declared workflow had — runs\nhere in the same order, with no run record behind it, using only the two mutation verbs `send --to`\nand `reply --thread`.\n\nNeither `--expect` nor a declared `expects: ` can be combined with `flow: sequential`. Both\ndecide who answers and in what order, which on an ordered channel is what `members` already decides —\nand a subset is the more dangerous of the two, because it is what the engine actually reads when one\nis present, so an ordered channel declaring `expects: [b, a]` would have run in the subset's order\nwhile its `members` said otherwise. Both are refused by name rather than silently honoured in the\nother list's favour, so on an ordered channel `members` is the only list there is.\n- `breaking` · A declared channel's `timeout` is now a deadline PER MEMBER, and every transcript output by that\nmember restarts it.\n\nUntil now the `timeout` produced one absolute instant, stamped on every member of the channel, that\nthen ran stubbornly to its end: a channel declaring `timeout: 30m` gave up on its members thirty\nminutes after it was opened, whatever they were doing. That breaks exactly the case a timeout exists\nto survive — an agent that starts a shell command running for an hour is perfectly healthy at minute\nthirty, and cutting it off there destroys an hour of work and hands the requester an answer that is\nmissing the one member who was actually working.\n\nWhat `timeout: 30m` means now is \"thirty minutes of SILENCE from this member\". Behind every member of\na channel stands exactly one session, and every write to that session's transcript — a thought, a\ntool call, a result — restarts its window from that moment. A member that keeps showing signs of life\nis never given up on, however long its work takes; a member that shows none for the whole window\nstill times out, and the channel consolidates with the answers it has, noting who did not respond.\nThe clocks are independent: one member running out of time settles that member and nobody else.\n\nThree smaller consequences of the same change. A member asked AGAIN — a second turn on the same\nchannel — starts its window again from the moment it was asked, instead of inheriting one that had\nalready run out and being over before it began; the re-check that eventually gives up on it is\nscheduled along with that window, so a second turn is given up on automatically just like the first.\nA member that has to WAIT for the working copy before it can start now begins its window when it\nactually starts, rather than being given up on while it is still queued. And a channel's own\nscheduled re-check no longer forces a consolidation just because the instant it was scheduled for\nhas arrived — it finds the members alive, declines, and re-schedules itself for the next moment one\nof them falls due, so the automatic give-up still happens; it just happens at the right time now.\n\nAn unreadable `timeout` (`timeout: twenty-minutes`) is now refused with an error naming the channel\nfield and the value, before anything is opened, instead of being carried further as an argument\nerror about a `--deadline` flag nobody typed. A `--deadline` given as an absolute instant is\nunchanged and stays deliberately stubborn: naming a point in time means that point in time, and\nnothing moves it.\n- `breaking` · A declared channel now has TWO LEVELS, and every thread in it has exactly two ends. Until now\n`nxc send --to ` stamped ONE thread, wrote every member into its `expects_reply_from` and\nstarted all of them on that same thread: the requester and all three reviewers sat in one\nconversation, each of them read every other one's answer, and the data model's normal case was a\nthread with N+1 ends.\n\nWhat a `send --to ` produces now is the CHANNEL THREAD — the requester at one end, the\nchannel's SUPERVISOR at the other — and, hanging under it, one thread per member. Each member thread\nhas the supervisor at one end and that one member at the other. Three reviewers are four threads, not\none, and the reviewers cannot see each other, because they are not in the same conversation rather\nthan because a filter hides them.\n\n**The supervisor is machinery of the engine**, never a person and not a declared role either. It is\nthe reserved identity `/__channel__`, and it does three things: it takes the incoming request\n(a `send --to `, and every later `reply` into the channel thread) and opens the per-member\nsends itself; it waits for the SET of member threads rather than for one thread's list of names; and\nwhen they have all answered — or their deadlines have struck — it runs the channel's declared\n`on_complete` policy and delivers the result to the requester. It can be OCCUPIED by an agent\nsession: that is exactly what `on_complete: summarize` is, and that session's reply is what\ndischarges the channel thread.\n\nAll of it runs SYNCHRONOUSLY, inside the write path of the `reply` that caused it: the message is\nwritten, the obligation is recognised as discharged, the supervisor runs, the next threads are opened\nand their sessions are STARTED, and only then does the call return. What is waited for is the\nstarting, never the result. The reason is not latency — it is that only here does a failed\nconsequence still have an addressee: asynchronously the replying session is long gone by the time its\nconsequence runs, and a failure is noticed by nobody.\n\nTwo things a caller will see differently. `nxc send --to --json` reports `expects:\n[\"/__channel__\"]` — the channel thread's other end — and the per-member expectations are one\nlevel down, one per member thread, where `nxc status --thread ` shows them. And a member answers\nin ITS OWN thread: the id in the message a member is started with is its own, and nothing ever points\na member at the channel thread.\n\nA member's turn is now also RE-OPENED properly. A `reply` into the channel thread from its requester\nis the same door as the first message: the supervisor re-declares each member's expectation on its\nexisting thread and resumes that member's session with the obligation attached — so\n`nxc reply --thread --if-unanswered`, the call an agent session makes as it shuts down, has\nsomething to discharge from the second turn onwards. Before this, nothing re-declared an expectation\non a resume at all, and that safety net was a silent no-op past turn one.\n\nThis is what makes a channel a whole flow with no run record behind it — which is why the\n`nxc workflow` group could be taken away in this same release.\n- `breaking` · **The reply that completed a board is now the one that actually completed it.** Once every handle a\nthread was waiting on has answered, the requester is woken with the collected answers and one of them\nis marked as the completing reply — the answer that closed the turn. That mark was picked by\ncomparing message ids. A message id begins with the millisecond it was minted, so two answers written\nin the same millisecond differ only in the random tail that follows and the mark fell on whichever of\nthem happened to sort higher. Between two synced devices the ids can disagree with the real order by\nany distance at all, because each device stamps its own.\n\nThe completing reply is now chosen by the same causal order the rest of a thread is read in — the\norder the messages themselves appear in, the one that decides which answer `nxc status` reports as\nthe last, and the one that decides whether the last answer was an escalation. Those three already\nagreed with each other; this one read did not, and it was the last place in the message store still\nordering by the raw id.\n\nWhat changes for you: on a thread whose answers land close together — the normal case when a\nchannel's members finish at once — the answer highlighted as the completing one, and the message id\nreported next to it, can differ from what an earlier release reported for the very same conversation.\nThe new answer is the correct one.\n\nUnchanged, and deliberately so: whether a completed board still needs your attention is still decided\nby your read cursor in the ordinary way, so nothing you had already read comes back.\n- `breaking` · **A consequence that did not happen now has one defined place to appear.** Until now, when an answer\narrived and the thing it should have caused did not — a channel member that could not be started, a\nrequester that was never woken — the answering side got a clean receipt and the failure went to\nstderr, where no app and no agent reading `--json` ever sees\nit. The party that could have done something about it was, by then, the only party that never heard.\n\nEvery receipt now carries `warnings`: a list of what this very call should have caused and did not,\neach entry naming its class (`step_skipped`, `requester_not_woken`), the thread and session it\nconcerns, why it did not happen, and the underlying failure in words. It is\nalways present, empty included, so a reader cannot miss it by forgetting to look. Because a channel's\nconsolidation runs inside the reply that completes it, the session that answered learns about the\nfailure **in the same call, while it is still running** — `nxc --json reply` shows it, and\n`Engine::reply` returns it.\n\nOne thing this fixes that was previously not merely quiet but lost: a channel handing out a second\nround of work reported only the FIRST member it could not start; now every one of them is named.\n\n**`nxc status` is sharper about dead ends.** A thread that is answered with nothing following it used\nto read ORPHANED whenever the thread above it owed nothing — which is equally true of a branch that\nfinished perfectly. It now stays ORPHANED only if the thread above it never moved on from that\nanswer, and never for a thread hanging under an ordinary conversation that asked nobody anything. The\nrule errs towards saying nothing rather than raising a false alarm, because a place that cries wolf\nis a place nobody reads.\n\nA failed trigger's receipt also names WHY it did not start as a value rather than a sentence, telling\na runtime that refused the spawn (worth retrying) from a session the runtime had already reaped\n(needs a fresh one). `warnings` entries used to be plain strings and are now records; the sentence\nthey used to be is each record's `detail`, and the terminal output is unchanged.\n- `breaking` · Two processes working on one workspace at the same time can no longer lose each other's writes.\n\nThe logical clock that orders every change used to be read once — when a process opened the\nworkspace — and never looked at the file again. Two processes that started from the same reading\nstamped their changes with the same number, and the merge rule, which keeps a change only if it\nstrictly beats what is already recorded, then dropped the second one without a word: the command\nreported success, exit code 0, and the board still showed the old value. A long-running app holding\nthe workspace beside short-lived `nxf`/`nxc`/`nxm` calls drifted the same way, and further with\nevery minute it stayed open.\n\nThat is exactly how this platform is meant to be used — several agents in one workspace — and it is\nwhy the defect went unseen: the test suite runs everything in a single process, where one handle\nobserves every change and the divergence cannot form. It reached every last-write-wins field, on the\nboard and in chat and memory alike. And it was not theoretical: on the day of the fix, three of the\ntwenty workspaces on the development machine carried its marks, the nexus-flow board among them.\n\nEvery change now re-reads the workspace's own high-water mark at the moment it is written, holding\nthe write lock across the read and the write — so a change is always stamped above everything\nalready in the file, whichever process put it there. Underneath it, the log now refuses a duplicate\nstamp outright rather than accepting it and leaving the merge rule to discard it in silence. A\nworkspace that already carries duplicates from before opens and works exactly as it did; it simply\ncannot be given the second guarantee retroactively, and is never blocked or rewritten for it.\n\nFacade (breaking): no signature moves, one documented behaviour does.\n`nxs_foundation::store::Store::apply` returns the ops it did not fold into the views; that vector\nnow also carries a merged op the log REFUSED, because a different op already holds its\n`(lamport, site)` coordinate. Previously such an op was stored and then silently outranked. A\nconsumer that reads the vector strictly as \"op shapes this version does not understand\" sees a new\nkind of member. It is reachable only when two replicas share a `site_id` — which 63 bits of entropy\nput out of reach for real replicas, and which the prefix registry already treats as a repairable\nmisconfiguration — so in practice no correct caller sees a change. `Store::emit` gains the matching\nassertion for a locally minted op, unreachable once the clock is refreshed under the write lock.\n- `breaking` · **Declarations move to `.nxs-personas/`.** Who your agents are, and where they talk, is declared in\n`/.nxs-personas/` from now on — one `.yaml` per persona, and `channels.yaml`\nfor the channels they share, which is also where a flow is declared. The old `/roles/` folder is\nstill READ, so an existing project keeps working untouched, and the engine now says out loud that it\nread one: `nxc list` and `nxc prime` name the legacy folder and the path the declarations belong at,\nand `nxc prime --json` carries the same fact as data. **Nothing is moved for you.** An application\nthat has only opened someone else's project must not silently rewrite it — that is an irreversible\nchange to somebody else's property triggered by merely looking — so the move stays a thing you do\ndeliberately: copy the folder, and `.nxs-personas/` takes over the moment it carries a declaration.\n\n**Where a catalogue came from is now part of the answer.** `Engine::definitions()` still succeeds on\na workspace that declares nothing — a read that fails because there is nothing to read is a bad read,\nand it would turn a legitimate state into an exception an app has to catch just to render an\nonboarding screen — but the catalogue it returns now carries the resolution beside it: the path a\ndeclaration belongs at, which folder was actually read (`.nxs-personas/`, a legacy `roles/`, or\nneither), whether a legacy folder was found at all, and how many declarations there are. One\nstructure answers both \"where did this come from\" and \"is this project still on the old layout\".\n\n**The empty workspace stops being anonymous**, and each surface answers it in its own register. `nxc\nlist` and `nxc prime` are orientation: they do not fail, they say that nothing is declared and where\na declaration goes, and exit 0 — an empty list on its own is a lie by omission, and an error would\nmake a legitimate state exceptional. `nxc send --to ` is the one that genuinely refuses: it\nis an action with a mandatory target, and in a workspace with no declaration there is none, so it\nfails by name instead of reporting a missing target the caller would go looking for a typo in.\n\n**`nxc init` leaves the folder behind**, empty, with a short explanation of the format inside it and\nno shipped personas. It never touches an existing `roles/` folder, and the empty `.nxs-personas/` it\ncreates does not shadow one: the new location wins only once it carries a declaration of its own, so\nrunning `init` in an existing project cannot silently unteam it. A second `init` keeps an explainer\nyou have edited.\n\nThe two shipped example teams move with the folder:\n`crates/chat/examples/role-runtime-v2/.nxs-personas/` and\n`crates/chat/examples/role-runtime-v3/.nxs-personas/`.\n\n**The injection path is gone with it.** `EngineConfig` no longer takes a definition source and\n`Engine::set_definitions` no longer exists: personas and channels come from `.nxs-personas/` for an\nembedding app exactly as for the command line, and a later in-app role editor writes into that same\nfolder rather than into an app-owned database. The property the injection path existed for is\nunchanged and now costs nothing to state — an edit is visible to the next call on the handle and on\nevery existing clone of it, with no reopen and no torn-down subscription, because nothing is cached.\n**`Engine::definitions()` — the READ — stays**, and a gate holds it there rather than leaving it to\ncare: `crates/chat/tests/seam_disposition.rs` names it with its reason and fails if it disappears.\n\n**Three entrances come off the surface, and the session-start block is re-cut to what is left.**\n`nxc agents list` / `register` / `search` are gone: a team is DECLARED, not registered — a persona\nis a file in `.nxs-personas/`, which is readable, reviewable and survives the run, where a runtime\nprofile row lived in one workspace's database and nothing ever reviewed it. `nxc list` is the read\nover it and carries the two fields `agents search` searched. `nxc workflow tickets add` is gone with no replacement — as, in this\nsame release, is the whole `nxc workflow` group it hung under. What a message is about is what\n`--ref nxf_ids=` says on it. And `nxc inbox` / `nxc read` come off the AGENT surface while staying for a human and on\nthe app seam: what a session is TAUGHT at its start is now exactly `list`, `send --to`,\n`reply --thread`, `nxc status`, `search` and `transcript show`, because a persona is handed its\nunread at session start and on resume rather than asking for it.\n\n**A gate now records what happens to the library seam when an entrance goes.** For every verb taken\noff the command line it is decided and written down whether the corresponding read or write stays on\n`Engine` — and the gate holds both halves, so a removal cannot land without the record moving with\nit, and a read an app renders cannot quietly disappear with the verb that used to reach it.\n\n**And every `nxc --help` page is a golden now.** Nothing watched that text before, and it had\nalready bitten once; with verbs coming off the surface, a help page naming one that no longer exists\nis a false map an agent acts on.\n\nFacade (breaking): `Engine::workflow_tickets_add`, `orchestration::workflow_tickets_add`,\n`WorkflowTicketsAddRequest` and `WorkflowTicketsReceipt` are removed (and the rest of the workflow\nsurface with them, in this same release — see its own entry). `DefinitionSource` and\n`EngineConfig::definitions` are removed, as is\n`Engine::set_definitions`. `Workspace::roles_dir()` is gone, replaced by `workspace_root()`,\n`personas_dir()`, `legacy_roles_dir()` and `declaration_dir()` on `ChatWorkspaceExt`.\n`Definitions::from_roles_dir` is now `Definitions::from_dir` (read ONE named folder) beside the new\n`Definitions::resolve(workspace_root)` (resolve both locations and record which was read).\n`Definitions` gains `source()`, `len()` and `is_empty()`. `facade::prime`/`facade::prime_for` take a\n`&DeclarationSource` where they took a `&Path`, and `facade::PrimeReport` gains a required\n`declarations: DeclarationSource` field — so `nxc prime --json` gains a `declarations` object, always\npresent.\n- `breaking` · An operation is now a THREAD TREE, and `nxc status` reads it. Until now nothing in the data model\nconnected two threads: a real chain — you ask the PM, the PM commissions the coding channel, that\nchannel's lead hands the work to a coder, the review of it fans out to three reviewers — was five to\nseven separate threads across three channels, and nobody could answer \"where do we stand in this?\".\n\nEvery thread now records the thread it was opened OUT OF. The rule is mechanical and needs nothing\nfrom the agent: a `nxc send --to ` issued from inside a running session hangs\nthe new thread under the thread that session is currently working in — the thread it was triggered\ninto, or the one whose reply woke it. A send with no session around it (you, at a terminal) opens a\nROOT, which is what makes it the top of a chain. Nothing is stored to say \"this is a root\": it is\nthe absence of an edge.\n\n`nxc status` answers \"where do we stand\" in three forms. `nxc status --thread ` shows the whole\ntree of the operation that thread belongs to, from the root down and across every channel it\ncrossed; `nxc status --channel ` lists the live operations that STARTED in a channel, as a way\nin; plain `nxc status` shows everything still going on in the workspace. `--json` carries the same\nrecord, and the identical read sits on the library seam as `Engine::status`, so an app builds its\noperation view from it rather than rebuilding the derivation. It is what replaces `workflow status` and\n`workflow list`, which this same release removes.\n\nEach thread reports one of three derived states: **open** (somebody still owes a reply here),\n**answered**, and **ORPHANED** — answered, nothing was opened out of it, nothing upstream is waiting\nfor it either, and the thread above it never moved on from it. The orphaned state is where a\nconsequence that silently failed to happen becomes visible without anyone reporting it; the last of\nthose conditions is what keeps it to the branches that FAILED rather than the ones that finished, and\nit is described in the entry about the place for failed consequences. One deliberate exception at the very top: a ROOT thread\nthat has been answered says `awaiting you` rather than reading like a thread nobody is working on\nany more. That is the normal end of an operation, not a standstill, and it is the only place a human\nappears in the flow at all.\n\nEach thread also carries the session of whoever is working on it, so a consumer holding a thread id\ncan follow that assignee's transcript without ever being told a session id — `nxc status --json`\ngives you the session, `nxc transcript show ` gives you the running account. **This works\nwhile the thread is still open and the assignee has said nothing**, which is the case it exists for: a\nconversation that has gone quiet is exactly when you want to see whether the other side is thinking,\nstuck, or gone. An open thread names the party it is still waiting for; a settled one names whoever\nanswered last. Nothing new is recorded for either — the first comes from where the trigger put that\nsession, the second from the return address its own message already carries. The read is the same for\nevery caller; there is no human-versus-agent distinction anywhere in it.\n\nNothing new is stored for any of this. The status is computed from the thread edges, the reply\nexpectations and the return addresses already in the log — there is no operation record, and nothing\ntakes the place of the run record this release removes.\n- `breaking` · **A channel is a declaration now, or it is nothing.** Until this release you could address a channel\nthat nothing declared — one you had minted with `nxc channels create`, or whose id you simply\nhappened to know. Such a channel is something addressable with no policy behind it: nobody said who\nanswers on it, by when, or what becomes of the answers, so nothing could route them. That is the\nstate this rebuild set out to abolish, and this release is where it goes.\n\n**`nxc channels` is gone, whole.** `create` and `dm` minted channels; a channel is declared in\n`.nxs-personas/channels.yaml` and materialises the first time somebody sends to it, and a direct\nconversation opens itself the moment you write `send --to `. `join` and `leave` wrote\nmembership; membership is the declaration's `members:` list. `list` is `nxc list`, which reads the\ndeclared team and its channels.\n\n**`channels public` has no successor on the command line, and that is a decision rather than an\noversight.** It listed the PUBLIC channels in this workspace's store — including front doors that\narrived by sync from other projects, which no declaration here names and which `nxc list` therefore\ncannot show. Cross-project discovery leaves the agent surface; the read stays on the library seam as\n`Engine::public_channels`, for an application to render.\n\n**`nxc ask` is gone.** `send --to ` opens the board, and the channel's own declaration says\nwho must answer, by when, and what becomes of the answers — so a per-call `--expect` was a second\nanswer to a question the declaration already answers, and it goes with the verb. `--deadline` does\nNOT: it moved to **`nxc send --deadline`**, and it still takes either an absolute RFC3339 instant or\na `` duration. A duration has a declared form (a channel's `timeout:`) but an absolute\ninstant has none, so dropping the flag would have taken a capability away instead of collapsing two\nentrances into one.\n\n**`send` and `reply` each take exactly one positional argument now: the body.** The target is a flag.\n\n- `nxc send \"\"` → `nxc send --to \"\"`\n- `nxc reply \"\"` → `nxc reply --thread \"\"`\n\n`--thread` is required. A first token that means \"channel\" in one invocation and \"body\" in the next\nis also the one shape the argument parser could never check for you.\n\n**Replying to a single MESSAGE id is gone with it.** `reply ` used to answer \"the\nconversation that message is in\", which is what `--thread` says outright. A conversation has one\naddress.\n\nTwo consequences worth knowing before you upgrade:\n\n- **`nxc reply --json` renders one shape.** The positional form built its own JSON object and\n carried both `persisted` and `posted` for one fact; `reply --thread` has always serialized the\n typed receipt, which carries `posted`. With one form left there is one name. Everything else in\n that receipt — `thread_id`, `resumed`, `message_id`, `warnings` and the `wake_skipped` finding —\n is unchanged.\n**One label changed with the verb.** `nxc ask` treated a message as a `question` unless you said\notherwise; `nxc send` treats it as `info`. So a board you used to open with a bare\n`nxc ask \"\"` now carries `info` where it carried `question`. This is a label — what\n`search` and `inbox` show you — and nothing routes, waits or decides on it. Type\n`--kind question` if you want it back. The default was not carried over on purpose: `ask` could\nhave one because it only ever addressed a channel, while `send --to` addresses a persona or a\nchannel, and a default that changed depending on which one your target turned out to be is the kind\nof hidden difference this release exists to remove.\n\n**And a board still wakes its requester exactly once.** Answering a thread hands the turn back to\nwhoever is on the other side — but a fan-in board has no other side, and its requester is woken by\nthe completion, once. That was previously true because the two `reply` shapes reached different\ncode; with one shape left it is stated as a rule instead, so a partial answer, a late answer, or an\nanswer from somebody who was never expected no longer wakes the requester again.\n\n**What happens to channels you already have.** They stay in your workspace and their messages stay\nreadable through `nxc search` and `nxc inbox` — but a channel no declaration names stops being a\ntarget. `nxc send --to ` says so by name, and says where a declaration goes, rather than\nanswering \"no such target\" and sending you looking for a typo in an id that is spelled perfectly.\n- `breaking` · `nxc reply --escalate \"\"` — the note \"I cannot carry out this task\". It is the SECOND and last\nthing an agent may say with `reply`; the first is \"I am finished\". Everything else an agent produces\nbelongs in the transcript and never in the agent-to-agent conversation, because a waiting agent would\nread it as a result.\n\nIt is deliberately not a wastebasket for everything that is not a result. A real follow-up question —\n\"what do you mean by X?\", from somebody who can still do the work — stays `--kind question`, a\ndifferent thing with its own value. `--escalate` carries one meaning and stays committed to it.\n\n**The turn ends either way.** An escalating reply discharges the sender's obligation exactly like a\nfinished one: the session ends, the debt is gone, and nothing about who owes what changes. What is\ndifferent is the OUTCOME, and the channel's supervisor is what decides where that goes.\n\n**The escalation travels upward, on every channel.** A channel member that says \"I cannot\" settles\nits own thread like any other answer, so the set is complete and the channel consolidates — but the\nchannel's own answer to the requester carries the escalation onward instead of folding it into a\nresult. The question can therefore reach the person who commissioned the work, one level at a time.\n\nThis holds for both of a channel's output forms, including one that SUMMARISES its answers\n(`on_complete: summarize`), because a channel never summarises away an \"I cannot\": a set that\ncarried one is handed over as it stands. See the consolidator entry in this same release for what\nthat looks like from the requester's side.\n\nIt is carried by the message's `kind`, a field that already existed and already syncs, so nothing\nabout the synchronised payload changes shape. `--kind escalation` is deliberately not accepted:\n`--escalate` is the one door to it, and asking for both at once is refused. Applications reach the\nsame thing by setting `MessageKind::Escalation` on the reply they already build — there is no flag\nhere that only the command line can send.\n\nIt is DECLARED rather than free, which is exactly what makes it a signal: the channel's supervisor\nbranches on a bit whose meaning is written in the channel, not on a token an agent invents on the\nspot. That is also why the free-text branching token the removed `nxc workflow step done` carried\nneeds no other replacement.\n- `breaking` · **`nxc send` has one target now: `--to`.** `--role` and `--session` are gone, and with them the two\nlibrary methods behind them — `Engine::role_trigger` and `Engine::role_resume`.\n\n**`send --role ` collapses into `send --to `.** Both hand a persona a task, both run\nthe same code, and since the previous release neither could name a channel — so both open the direct\nconversation between caller and persona by themselves. What `--to` adds is the reason the collapse\ngoes this way round: it opens a THREAD and tells the persona which one to answer into. `--role`\nposted without a thread, which left its answers with no address at all, because `reply --thread` is\nthe one way left to answer. Anywhere you wrote `nxc send --role coder \"…\"`, write\n`nxc send --to coder \"…\"`; you get a thread id back, and that is what a reply needs.\n\n**`send --session ` has no successor, and that is a decision rather than an oversight.** It\npushed a new task into a persona's LIVE session. `reply --thread` is a different act: it answers a\nconversation, and it reaches the other side through the thread's return address — the newest message\nsomebody else posted there. A persona you have just summoned and that has not answered yet has\nposted none, so a follow-up is recorded in the thread but wakes nobody until the persona replies of\nits own accord. Delivering into a session that is mid-turn needs a signal into a running agent that\ndoes not exist yet; it is tracked as its own piece of work and will arrive as\n`reply --thread --force`. Until then this is a capability the command line gives up, named here\nrather than left to be discovered.\n\n**For applications embedding the library:** `Engine::send_to` is the one door to a persona. It takes\na target rather than a role handle, returns a thread id beside the session id, and declares who the\nthread expects a reply from. `Engine::role_resume` has no replacement, for the reason above. The\nmechanism underneath both is untouched — only the flat handle methods are gone.\n- `breaking` · A reply now settles the TURN it answers, not the whole thread. Until now, a thread counted a role\nas \"has replied\" from its first message there onwards, forever — which is the right reading for a\none-shot review board and the wrong one for a conversation that goes back and forth. The moment a\nsupervisor came back onto the same thread with a second piece of work, everything downstream of that\ncount was already convinced the role owed nothing: `nxc reply --if-unanswered` — the safety net the\nagent sidecar fires at the end of every session, so a session that dies without answering still\nleaves an answer behind — silently did nothing from the second turn on, and the working-tree lease\nread the chain as finished while the role was still writing its final report, handing the working\ncopy to a rival chain mid-flight.\n\nWhether a reply is still owed is now measured from the moment it was asked for: a thread's\n`expects_reply_from` declaration carries its own position in the log, and only what a handle wrote\nAFTER the current declaration counts as an answer to it. Re-declaring the expectation — the same\nhandles or different ones — therefore opens a new turn, and everyone the new declaration names owes\nan answer to it. Nothing about the synced message format changes; this is derived from what the log\nalready carried, so no device needs a migration and a peer that only ever received the ops reaches\nthe same answer.\n\n**One caveat, and it belongs to the substrate rather than to this rule.** \"After the current\ndeclaration\" is decided in the underlying log's own causal order, and that order has a known hole:\nthe counter it uses belongs to an OPEN STORE HANDLE — recovered once when the handle opens, never\nre-read from the file afterwards — while every process on one device shares a replica identity. Two\nwriters holding the same workspace open at the same moment therefore do not observe each other's\npositions: a long-lived host that embeds the engine for its lifetime beside short-lived `nxc` calls,\nor simply two `nxc` calls running at once. Where that happens, a message written BEFORE a\nre-declaration can be counted as an answer to it — on one device, with no sync involved — and the\nconsequences are the ones the rule above is there to prevent: a turn that reads as discharged the\ninstant it is opened, and a working-tree lease released while the work is still running. It is\ntracked as its own item (`6j6v.fc5p`), the fix belongs in the substrate every one of these reads\nsits on, and nothing about the rule described here changes when it lands.\n\n**Removed with it: the `nxc threads expect` verb.** Re-declaring who a thread expects a reply from\nused to be something a human could type, and its purpose was to complete a board stuck on a reviewer\nwho never came back — narrow the expected set to whoever had answered, and the board closed. Under\nthe rule above that is no longer what a re-declaration means (it asks the named handles again, as of\nnow), and rather than build a special case to keep the old effect, the verb goes: it was already on\nthe list of `nxc` commands being retired as the agent-to-agent surface is cut back to what an agent\nactually needs. **The capability itself has not gone anywhere** — the write is still there for\nembedding apps and hosts as `facade::set_expects` (opener-only, same errors), and it is what the\nchannel supervisor uses to hand a role its next turn. What disappeared is a hand-typed door.\nTo close a board nobody is going to answer, drop the expectation instead — a set that no longer names\nthat handle leaves nothing outstanding, which is what the working-tree release, the `--if-unanswered`\ndischarge and the deadline advisory all read.\n\n**On an existing workspace this applies retroactively, and it is visible.** These are derived reads,\nnot stored state, so every board is re-derived under the new rule the first time it is read after the\nupgrade. A board that was completed by narrowing it in the past reads as outstanding again (its\nanswers predate the narrowing), which also means the chain holding the working copy for that thread\nkeeps holding it until somebody answers the current declaration or the expectation is dropped — and\nthat a thread with a past deadline can show up as waiting past it again. Nothing is lost or rewritten;\nwhat changed is the question being asked of the same log.\n\nThe kind of a message is deliberately not consulted anywhere in this: an answer that says \"I cannot\ndo this\" settles the turn exactly like a finished one, because the session ends either way.\n\nFacade (breaking, behavioural — no signature moves): `nexus_chat::store::ChatStore::thread_quorum`\nand `thread_quorums` return the same shape, and `ThreadQuorum`'s `replied` / `outstanding` /\n`complete` now answer per turn rather than per thread. A consumer that renders a board sees a\nre-declared thread reopen; a consumer that treated \"replied once\" as permanent must read the\ncurrent declaration instead. `facade::set_expects`'s receipt reports the re-asked set for the same\nreason.\n- `breaking` · The `nxc workflow` command group is gone, and so is the declarative workflow-run engine behind it —\n`workflow start`, `workflow step done` (with its `--outcome` flag), `workflow status`,\n`workflow list` and `workflow liveness`, the `workflow_runs` record they wrote and read, and the\n`.nxs-personas/workflow.yaml` / `.nxs-personas/workflows/*.yaml` declarations that described them. A\nworkspace that still has those files simply no longer has anything that reads them.\n\n**A channel IS the flow now, and that is where every one of those verbs went.** A declared channel\ncarries its own members, its own order (`flow: sequential`), its own deadline and its own\nconsolidation, so `send --to ` starts a run of it and `reply --thread` moves it on — the\nchannel's supervisor decides what comes next, which is why no agent has to utter a branching token\nany more. Where an operation stands is `nxc status`: `--thread` for one operation, `--channel` for\nthe ones that started in a channel, and with no argument, everything still going on here.\n\n**On the library seam** `Engine::workflow_start`, `workflow_step_done`, `workflow_tick`,\n`workflow_liveness`, `workflow_status` and `workflow_list` are removed, and so is the workflow event\nstream — `workflow_events`, `workflow_event_cursor` and `subscribe_workflow`. What an app watching a\nworkspace uses instead already exists: `Engine::subscribe` for the \"someone wrote, re-read\" tick, and\n`Engine::status` for where an operation stands. `Definitions::new` takes two arguments rather than\nthree, and `PrimeRoster` no longer carries a `workflows` list.\n\n**Two capabilities are given up rather than replaced, and both are named here rather than left to be\nfound.** The step-liveness watch — a nudge and then an escalation when a running step went quiet —\nwas keyed on a run's current step and goes with the runs; what remains against a member that falls\nsilent is the per-member channel deadline, which strikes on a declared `timeout` but does not nudge.\nAnd a message no longer inherits a run's commissioned ticket set automatically: name the items it is\nabout with `--ref nxf_ids=`, which always took precedence over the automatic stamp anyway.\n\nThe `workflow_runs` tables are dropped from an existing workspace on its next open. Messages,\nthreads and channels are untouched, and an older `nxc` opening the same workspace afterwards\nrecreates its own empty tables rather than failing. The one shape that does not self-heal, said\nplainly rather than left to be met: an older process that already holds the workspace open while a\nnewer one drops the tables under it will panic if it then folds a workflow op. Close the old process\nbefore upgrading — or, since this is a local tool rather than a fleet, simply do not run two\nversions against one workspace at the same time.\n- `breaking` · The working copy is now protected for as long as the TASK runs. Two questions decide it, and they\nhave two different answers.\n\n**Whether a chain is protected is derived upward from the declared members, one step.** A channel\nwhose declared member says `working_tree: exclusive` counts as needing the working copy, without the\nauthor repeating it on the channel — say it once, on the persona that builds, and every channel that\npersona works in is covered. It stops after one step on purpose: a channel does NOT catch the need\nfrom another channel through a member the two happen to share. Otherwise a coding channel would hand\nits need to a review channel whose members only read, that channel would hand it on again, and every\nchannel in a workspace would end up exclusive. Declaring `working_tree: exclusive` on the channel\nitself still works exactly as before, and is still the right way to say \"this ROUND needs it\" when no\nsingle member does.\n\nThe practical difference: a protected channel now waits as a whole. Before, a channel that said\nnothing itself let the member that declared the need wait while its silent siblings started anyway —\nthe board opened half queued and half running, on opposite sides of a lease meant to cover the chain.\n\n**How far the protection reaches is the conversation the work was commissioned in, plus everything\nopened underneath it.** So it holds while a coding round waits on a review round it started: the\nreview conversation and the reviewers' own threads hang under the coding one and are inside the same\nclaim area. It also finally covers a side conversation a working session opens for itself — that used\nto show on the thread board as belonging to no lease at all. And it stops going upward: the\nconversation you had with the agent that commissioned the work is ABOVE the claim and holds nothing,\nso an unanswered human never keeps the working copy overnight.\n\n**The release happens when that conversation is CLOSED, which is not the same as \"nobody owes an\nanswer right now\".** A plain reply closes it and the next order in the queue starts. A\n`reply --escalate` — \"I cannot carry out this task\" — does NOT close it, even though the sender's\nobligation is discharged either way: the question is travelling upward, possibly as far as you, the\ntask is mid-flight, and the working copy stays protected. Not even a long silence changes that. **An\nunanswered follow-up question (`--kind question`) holds it the same way**, for the same reason: in\nboth cases nobody is working, and releasing would let a second chain start into a working copy the\nquestioner is about to resume into. This holds **anywhere in the protected area**, not only in the\nconversation the work was commissioned in — a reviewer three levels down who asked a question and is\nwaiting for the answer keeps the copy, even when every conversation above it has already wrapped up\nwithout noticing.\n\n**Answering it puts the round back to work, and the copy is free when that round finishes.**\nAnswering means giving the round its next turn — a reply into the channel's own conversation, the\nsame door the work came in by — which re-opens every member's turn and starts them again. Nothing is\nreleased at that instant, and nothing should be: the members are working again. What the answer ends\nis the WAITING, so the copy comes free on the round's own completion, without going anywhere near the\ntwo-hour limit. The limit is only what happens if the question is never answered at all.\n\nThe error direction is deliberate — somebody who escalates or asks needlessly holds the copy a\nlittle too long, which is harmless beside a copy handed away while the task is still running.\n\n**Gone with it: the rule that a run of the removed workflow engine held the working copy for as long\nas it was `running`.** That was a lid on a narrower problem — a role step of such a run left no\nobligation anywhere, so the run looked finished the moment it advanced onto one — and it cost two\nthings: a wedged run held the copy until the two-hour bound, and the release came when the RUN ended\nrather than when the work did. The claim area now CONTAINS the conversations the work happens in,\nwhich is what the lid was substituting for, and on this release's surface a channel member is always\ncommissioned with a conversation and an obligation, so the shape that needed the lid cannot arise.\n\nFacade (breaking): `nexus_chat::working_tree`'s release derivation changes behaviour for the same\ninputs. `ChatStore::work_scope_threads` returns the whole subtree from a `Thread` scope instead of the\nthread plus its direct member threads, and `WorkScope` itself now has two variants rather than three\n— the `Run` arm went with the workflow engine this release removes. New alongside them: `ChatStore::work_scope_handed_back`,\n`ChatStore::thread_subtree`/`thread_subtrees`, `ChatStore::last_reply_kind` (the wider read\n`last_reply_escalated` now delegates to — that function is unchanged), and\n`Definitions::channel_needs_working_tree`.\n- `breaking` · A role or a declared channel can now declare `working_tree: exclusive` (the default, `shared`, is\nexactly today's behaviour — no lease, no waiting). Only one chain of role sessions then works\nagainst the repository's working copy at a time: a second chain that also needs it queues instead\nof starting and colliding with the first on the git index and the Cargo build lock — the collision\na coding-and-review pair hit for real in an earlier trial of the role runtime. The holder is the\nCHAIN — the thread the work was commissioned in — not a single session.\n\nHow far \"the chain\" reaches is described by the \"claim area\" entry in this same release, which\nsupersedes what an earlier draft of this entry said about it: the area is the conversation the work\nwas commissioned in TOGETHER WITH everything opened underneath it, so a coding round keeps the\nworking copy while it is waiting on a review round it started. A PERSONA chain\n(`nxc send --to `, one call per step) opens a new conversation per call, so between steps the lease goes free and the release starts whatever is\nat the head of the queue; if an unrelated order queued earlier, that order runs next and the chain's\nown next step queues behind it.\n\nWaiting is never silent. A queued trigger's own receipt says so: `nxc send --to \n--json` gains `queued_behind` (who is holding it) and `queue_position` (1-based) whenever a\ntrigger has to wait, and the human-readable line adds `QUEUED at position n behind …`. `nxc\nthreads list`/`show` carry the same fact afterwards, for a chain that never checked back or a\nclient reading the board later: every thread now reports `working_tree: \"holding\" | \"waiting\" |\nnull` alongside its queue position.\n\nA release hands the working copy to a whole CLAIM AREA, not to one waiting trigger. Several triggers\nshare one claim area whenever a multi-member `working_tree: exclusive` channel had to wait at open\ntime: every member of that board belongs to the board's own thread, so each one queues separately\nunder the same key. Starting only the first of them would strand the rest for good — the board owes\ntheir replies, so it could never reach the release that would start them. They are therefore started\ntogether, which is exactly what happens when the working copy is free (the first member takes the\nlease and the rest inherit it). Which claim area goes next is still decided by the queue's\n`(priority, enqueued_at, id)` order; only after the head is picked does the rest of its area come\nwith it.\n\nRelease is a deterministic fact now, not a guess at quiet. A triggered role's own primed prompt\nnames the thread it owes a reply on and the exact command that closes it, and the new `nxc reply\n--if-unanswered` flag lets a caller that genuinely cannot tell whether it already answered call\nunconditionally — it posts only when a reply is still owed, and is a safe no-op otherwise. The\nagent sidecar's teardown, which always runs at the end of a session, now uses exactly that flag: a\nsession that ends without ever answering the thread it owed gets a failure reply posted on its\nbehalf instead of leaving the thread hanging silently. The moment the last open reply in a chain's\nscope lands, that same reply call frees the working-tree lease, takes the head of the queue, and\nstarts it — no daemon, no poller.\n\nThat two-hour bound (`working_tree::WORKING_TREE_LEASE_BOUND`) is only a backstop for a hard\ndeath, never a budget or a timer: nothing fires when it elapses, and it does NOT start anything\nalready waiting — it only stops an abandoned lease from refusing every FUTURE acquire, so the next\nchain that asks can take the working copy. A queued trigger stays queued until a later chain\nactually releases the lease, not until the bound passes; and a lease over a scope that owes nothing\nat all (what a resume takes, since it registers no obligation) is instead freed by the very next\nreply into that thread.\n\nSeparately, a `send --to ` trigger that fails after posting its message no\nlonger discards it: the receipt now carries `spawned: bool` (`false` for both \"queued behind the\nlease\" and \"the spawn itself failed\") and `warnings: [...]` naming what went wrong, so a caller can\nfinally tell \"nothing happened\" apart from \"the message is in the channel and nobody is working on\nit yet\" — closing long-standing bug 6j6v.hpv8. `nxc`'s exit code still turns non-zero on a real\nspawn failure; an `Engine` caller checks `spawned` instead.\n\nFacade (breaking): `crates/chat`'s public API moves across all of the above. `ReplyRequest` (on\n`facade`, `orchestration`, and `surface::ReplyThreadRequest`) each gain a required\n`if_unanswered: bool` — pass `false` for the previous behaviour. `facade::reply` now returns\n`Result>` (`None` is the new no-op outcome). `orchestration::ReplyReceipt`\ngains `posted: bool` and its `message_id` is now `Option`. `worker::TriggerRequest` gains\n`reply_thread: Option`. `orchestration::RoleSpawn` gains required `priority: Priority` and\n`thread: Option<&str>` (plus `queued_since: Option<&str>`). `working_tree::QueuedTrigger` gains\n`priority: crate::model::Priority` and `enqueued_at: Option` — both components of the\nqueue's `(priority, enqueued_at, id)` order now travel with a queued entry, so a promoted trigger\nthat has to wait again keeps the urgency and the wait it already accrued instead of re-entering as\n`normal`, stamped now. `orchestration::trigger_role` now returns\n`Result` instead of `Result<(), TriggerError>`;\n`orchestration::TriggerReceipt` and `surface::SendToReceipt` each gain `queued_behind`/\n`queue_position` and required `spawned`/`warnings`. `facade::threads` now returns\n`Vec` instead of `Vec` (the existing fields move under\n`.quorum`), and `facade::thread_board`'s `ThreadBoardView` gains the same two working-tree fields;\n`facade::thread_quorums` is unchanged. On the store side, `ChatStore::enqueue_working_tree` loses\nits `priority: i64` parameter (now carried on the queued entry itself), and\n`ChatStore::release_working_tree` is gone — `release_working_tree_and_take_next` is the only\nrelease path left, and it returns `Vec` (the head's whole claim area, in queue\norder) rather than `Option`; an empty vector is the former `None`.\n\nBecause this breaks `nexus-chat`'s public API, this epic ships as a MINOR release (`0.60.0`), not\na `0.59.x` patch: `enforce_facade_breaking_axis` fail-closes a patch bump carrying a\n`facade: breaking` fragment.", - "de": "### Neu\n- **Jeder Kanal deklariert seinen Konsolidierer** — das, was aus den Antworten der Mitglieder die eine\nAntwort macht, die der Anforderer bekommt. Es gab ihn immer, aber er war weder deklariert noch\neinstellbar: Man konnte sagen, *dass* ein Kanal zusammenfasst, und den Prompt schreiben — aber nicht,\nwelches Modell ihn ausführt; und über die andere Ausgabeform stand nirgends etwas.\n\nEs gibt zwei Ausgabeformen, und ein Kanal benennt eine davon:\n\n- **`on_complete: pass_through`** (die Voreinstellung, und das, was ein Kanal ohne Angabe bekommt)\n reicht die Antworten durch, wie sie sind. Diese Übergabe hat jetzt eine **definierte Form**: eine\n kurze Kopfzeile mit Kanal, Faden und Anzahl der gesammelten Nachrichten, danach je Nachricht ein\n abgegrenzter Block mit dem Namen dessen, der sie geschrieben hat. Das ersetzt eine Darstellung mit\n einer Zeile je Antwort, die schlicht nicht lesbar war, sobald eine Antwort mehrere Zeilen hatte —\n und für einen Agenten ist das der Normalfall. Die Blöcke selbst stehen in einem Abschnitt, der\n ausdrücklich als nicht vertrauenswürdige Daten gekennzeichnet ist, samt Hinweis, dass die\n Absenderangabe an jedem Block eine Behauptung ist und keine geprüfte Tatsache: Was ein Mitglied\n schreibt, ist freier Text, und die Gegenstelle ist ein Agent, der sonst alles davon als Anweisung\n der eigenen Seite liest. Es ist dieselbe Kennzeichnung, die ein zusammenfassender Kanal schon\n bisher um die Antworten legt, die er seinem Modell übergibt.\n- **`on_complete: summarize`** lässt ein Modell über die Antworten laufen. Der Prompt war schon als\n `summary_prompt:` deklarierbar; das Modell jetzt ebenfalls, als **`summary_model: fable | opus |\n sonnet`** — dieselben drei Namen, die auch eine Persona verwendet. Ein zusammenfassender Kanal ohne\n Modellangabe faltet auf der Stufe `junior` (`sonnet`) statt auf einer stillschweigenden\n Voreinstellung; die Frage \"welches Modell hat das zusammengefasst?\" lässt sich also nachschlagen\n statt erraten. Ein `summary_model:` an einem Kanal, der gar nicht zusammenfasst, wird als Fehler in\n der Deklaration gemeldet.\n\n**Ein Kanal fasst ein \"ich kann nicht\" niemals weg.** Hat ein Mitglied mit `--escalate` geantwortet,\nreicht der Kanal die Antworten durch, wie sie sind — kein Modell läuft darüber — und sagt es in einer\neigenen Zeile darüber. Damit ist die Lücke geschlossen, die das vorige Release offengelegt hat: Eine\nEskalation drang bisher nur auf einem durchreichenden Kanal nach oben vor und blieb an einem\nzusammenfassenden stehen. Jetzt dringt sie auf beiden vor, und \"bei mir ist keine Eskalation\nangekommen\" ist auf jedem Kanal wieder eine echte Auskunft.\n\n**Wo das ändert, was ein GEORDNETER Kanal tut — und das tut es:** Ein Schritt eines\n`flow: sequential`-Kanals, der Arbeit an einen weiteren Kanal übergibt, wartet auf dessen Urteil, und\nein eskalierender Kanal liefert keines. Worauf der Betreuer verzweigt, ist die Eskalation selbst —\nein deklariertes Signal statt eines Tokens, das sich jemand ausdenken musste. Der Ablauf schaltet\nalso nicht weiter, als wäre die Arbeit getan, und die Eskalation bleibt an dem Kanal lesbar, an dem\nsie passiert ist.\n\n**Zwei Aufrufer, die denselben Kanal im selben Augenblick abschließen, stellen jetzt einmal zu.**\nTraf eine abschließende Antwort mit einer Fristprüfung zusammen, konnte ein durchreichender Kanal dem\nAnforderer dieselben Antworten zweimal übergeben; ein zusammenfassender war bereits geschützt. Beide\nsichert jetzt dieselbe Mechanik.\n\nBestehende Kanal-Deklarationen sind nicht betroffen: Jedes Feld ist optional, eine vor diesem Release\ngeschriebene Deklaration wird unverändert gelesen und verhält sich unverändert, und ein Kanal, der\nüber seinen Konsolidierer nichts sagt, hat trotzdem einen.\n- **Ein Kanal kann jetzt seinen ABLAUF deklarieren**, und `nxc send --to ` startet ihn. Wer\n`flow: sequential` an einen Kanal in `channels.yaml` schreibt, macht aus der Reihenfolge seiner\n`members` statt einer Aufzählung eine feste Reihenfolge: der Betreuer des Kanals startet den ersten\nSchritt allein, und jeden weiteren erst, wenn der vorige geantwortet hat oder seine Frist zugeschlagen\nist. Der Standard bleibt unverändert — ein Kanal ohne `flow` startet weiterhin alle Mitglieder auf\neinmal.\n\n**Ein Schritt dieses Ablaufs darf einen ganzen KANAL adressieren, nicht nur eine Persona** — dieselben\nzwei Dinge, die `nxc send --to ` immer schon erreicht hat. Ein als Schritt genannter Kanal\nbekommt seinen eigenen Kanal-Faden unterhalb des Ablaufs, führt seine eigenen Mitglieder und seinen\neigenen Konsolidierer aus und gibt dessen Ergebnis als Antwort dieses Schritts zurück. Tragen eine\nRolle und ein Kanal denselben Namen, gewinnt die ROLLE — jede vor diesem Release geschriebene\n`members:`-Liste bedeutet damit genau das, was sie bedeutet hat.\n\nZwei Regeln weisen einen Ablauf ab, der nicht laufen kann. Ein `sequential`-Kanal darf denselben\nSchritt nicht zweimal nennen (eine Wiederholung wäre eine Schleife, und es gibt kein deklariertes\nSignal, das eine solche verzweigen könnte), und kein Kanal-Ablauf darf über seine Mitglieder SICH\nSELBST erreichen — Letzteres wird beim Laden der Deklarationen abgelehnt und nicht bloß gemeldet,\ndenn ein solcher Ablauf verschlechtert sich nicht, sondern öffnet bei jedem Sprung einen Faden und\nstartet eine bezahlte Sitzung, bis die Tiefenbremse ihn stoppt.\n\n**Genau das machte die Gruppe `nxc workflow` im selben Release entfernbar.** Eine feste Reihenfolge\naus einem Rollen-Schritt und einem Kanal-Schritt — die Form, die der deklarierte Ablauf des\nmitgelieferten Beispiels hatte — läuft hier in derselben Reihenfolge, ohne Lauf-Datensatz dahinter\nund allein mit den beiden Mutationsverben `send --to` und `reply --thread`.\n\nWeder `--expect` noch ein deklariertes `expects: ` lässt sich mit `flow: sequential`\nkombinieren. Beide entscheiden, wer antwortet und in welcher Reihenfolge — was auf einem geordneten\nKanal bereits `members` entscheidet. Die Teilmenge ist dabei die gefährlichere von beiden, denn sie\nist es, die die Engine tatsächlich liest, wenn eine da ist: ein geordneter Kanal mit\n`expects: [b, a]` wäre in deren Reihenfolge gelaufen, während seine `members` etwas anderes sagten.\nBeides wird ausdrücklich abgewiesen statt still zugunsten der anderen Liste befolgt — auf einem\ngeordneten Kanal ist `members` damit die einzige Liste, die es gibt.\n- **Eine ausgebliebene Konsequenz hat jetzt einen definierten Ort, an dem sie auftaucht.** Bisher galt:\nKam eine Antwort an, blieb aber das aus, was sie hätte auslösen sollen — ein Kanalmitglied, das nicht\ngestartet werden konnte, ein Anforderer, der nie geweckt wurde —, dann bekam die antwortende Seite\neine saubere Quittung, und der Fehlschlag ging auf stderr, wo ihn keine App und kein Agent sieht, der `--json` liest. Ausgerechnet die Seite,\ndie etwas hätte tun können, erfuhr als Einzige nichts davon.\n\nJede Quittung trägt jetzt `warnings`: eine Liste dessen, was genau dieser Aufruf hätte auslösen\nsollen und nicht ausgelöst hat — je Eintrag mit Klasse (`step_skipped`, `requester_not_woken`),\nbetroffenem Faden und betroffener Sitzung, dem Grund und dem zugrundeliegenden Fehler im Klartext. Das Feld ist immer da, auch leer, damit man es nicht dadurch\nübersieht, dass man nicht hinschaut. Weil die Konsolidierung eines Kanals im selben Schreibvorgang\nläuft wie die Antwort, die sie auslöst, erfährt die antwortende Sitzung davon **im selben Aufruf,\nwährend sie noch läuft** — `nxc --json reply` zeigt es, `Engine::reply` gibt es zurück.\n\nEin Ding, das damit nicht mehr bloß leise, sondern gar nicht mehr verloren ist: Ein Kanal, der eine\nzweite Runde Arbeit verteilt, meldete bisher nur das ERSTE Mitglied, das er nicht starten konnte;\njetzt wird jedes einzelne genannt.\n\n**`nxc status` unterscheidet tote Enden jetzt schärfer.** Ein beantworteter Faden, aus dem nichts\nfolgte, galt bisher als VERWAIST, sobald der Faden darüber nichts mehr schuldete — was auf einen\nperfekt abgeschlossenen Zweig genauso zutrifft. Verwaist bleibt er jetzt nur, wenn der Faden darüber\nnach dieser Antwort nicht weitergegangen ist, und nie unterhalb einer gewöhnlichen Unterhaltung, die\nniemanden gefragt hat. Die Regel irrt lieber Richtung Schweigen als Richtung Fehlalarm: ein Ort, der\nzu oft Alarm schlägt, ist ein Ort, den niemand liest.\n\nDie Quittung eines fehlgeschlagenen Triggers nennt außerdem den GRUND als Wert statt als Satz und\nunterscheidet damit eine Laufzeit, die den Start verweigert hat (Wiederholen sinnvoll), von einer\nSitzung, die die Laufzeit längst eingesammelt hat (braucht eine neue). `warnings`-Einträge waren\nbisher einfache Zeichenketten und sind jetzt Datensätze; der Satz, der sie waren, ist das Feld\n`detail` jedes Eintrags, und die Ausgabe im Terminal bleibt unverändert.\n- Ein Vorgang ist jetzt ein FADENBAUM, und `nxc status` liest ihn. Bisher verband nichts im Datenmodell\nzwei Fäden: eine echte Kette — Sie beauftragen den PM, der PM beauftragt den Coding-Kanal, dessen\nLeitung gibt die Arbeit an einen Entwickler, das Review dazu fächert auf drei Prüfer auf — waren fünf\nbis sieben einzelne Fäden über drei Kanäle, und niemand konnte beantworten: \"Wo stehen wir hier\neigentlich?\"\n\nJeder Faden merkt sich jetzt den Faden, AUS DEM HERAUS er geöffnet wurde. Die Regel ist mechanisch\nund verlangt vom Agenten nichts: ein `nxc send --to ` aus einer laufenden Sitzung\nhängt den neuen Faden unter den Faden, in dem diese Sitzung gerade steht — den, auf den sie\ngetriggert wurde, oder den, dessen Antwort sie geweckt hat. Ein Send ohne Sitzung drumherum (Sie, am\nTerminal) öffnet eine WURZEL, und genau das macht sie zum Anfang einer Kette. Gespeichert wird dafür\nnichts: die Wurzel ist das Fehlen einer Kante.\n\n`nxc status` beantwortet \"wo stehen wir\" in drei Formen. `nxc status --thread ` zeigt den ganzen\nBaum des Vorgangs, zu dem dieser Faden gehört, von der Wurzel aus und über alle Kanalgrenzen hinweg;\n`nxc status --channel ` listet die laufenden Vorgänge, die in einem Kanal BEGONNEN haben, als\nEinstieg; `nxc status` ohne alles zeigt, was im Arbeitsbereich noch läuft. `--json` trägt denselben\nDatensatz, und dieselbe Lesung liegt als `Engine::status` auf der Bibliotheks-Naht — eine App baut\nihre Vorgangsansicht daraus, statt die Ableitung nachzubauen. Es ist der Ersatz für\n`workflow status` und `workflow list`, die dasselbe Release entfernt.\n\nJeder Faden meldet einen von drei abgeleiteten Zuständen: **offen** (hier schuldet noch jemand eine\nAntwort), **beantwortet** und **VERWAIST** — beantwortet, nichts daraus geöffnet, oberhalb wartet\nniemand mehr darauf, und der Faden darüber ist auch nicht weitergegangen. Der verwaiste Zustand ist\nder Ort, an dem eine still ausgebliebene Konsequenz sichtbar wird, ohne dass sie jemand meldet; die\nletzte dieser Bedingungen ist es, die ihn auf die GESCHEITERTEN Zweige beschränkt statt auf die\nfertigen — beschrieben im Eintrag über den Ort für gescheiterte Konsequenzen. Eine bewusste Ausnahme ganz oben: ein\nbeantworteter WURZEL-Faden meldet `awaiting you`, statt wie ein Faden zu lesen, an dem niemand mehr\narbeitet. Das ist das normale Ende eines Vorgangs, kein Stillstand — und die einzige Stelle, an der\nim Ablauf überhaupt ein Mensch vorkommt.\n\nJeder Faden trägt außerdem die Sitzung dessen, der gerade daran arbeitet: Wer eine Faden-Kennung hat,\nkann damit das Transkript des Beauftragten mitlesen, ohne dessen Sitzungskennung je erfahren zu haben\n— `nxc status --json` nennt die Sitzung, `nxc transcript show ` zeigt den laufenden Verlauf.\n**Das funktioniert, solange der Faden offen ist und der Beauftragte noch nichts gesagt hat** — genau\ndafür gibt es das: Ein Gespräch, das still geworden ist, ist der Moment, in dem man sehen will, ob die\nGegenseite denkt, hängt oder weg ist. Ein offener Faden nennt den, auf den er noch wartet; ein\nabgeschlossener den, der zuletzt geantwortet hat. Aufgezeichnet wird für beides nichts Neues — das\nErste steht dort, wo der Trigger diese Sitzung hingestellt hat, das Zweite ist die Rückadresse, die\nihre Nachricht ohnehin trägt. Die Lesung ist für jeden Aufrufer dieselbe; eine Unterscheidung zwischen\nMensch und Agent gibt es darin nirgends.\n\nGespeichert wird für all das nichts Neues. Der Status wird aus den Faden-Kanten, den\nAntwort-Erwartungen und den Rückadressen gerechnet, die ohnehin im Protokoll stehen — es gibt keinen\nVorgangs-Datensatz, und nichts tritt an die Stelle des Lauf-Datensatzes, den dieses Release\nentfernt.\n- `nxc reply --escalate \"\"` — der Vermerk \"ich kann diese Aufgabe nicht erfüllen\". Es ist das\nZWEITE und letzte, was ein Agent mit `reply` sagen darf; das erste ist \"ich bin fertig\". Alles andere,\nwas ein Agent produziert, gehört ins Transkript und niemals in die Agent-zu-Agent-Kommunikation, denn\nein abwartender Agent würde es als Ergebnis deuten.\n\nEs ist ausdrücklich kein Mülleimer für alles, was kein Ergebnis ist. Die echte Rückfrage — \"was\nmeinst du mit X?\", von jemandem, der die Arbeit sehr wohl tun kann — bleibt `--kind question`, eine\nandere Sache mit eigenem Wert. `--escalate` trägt eine Bedeutung und bleibt darauf verpflichtet.\n\n**Der Zug endet in beiden Fällen.** Ein eskalierendes `reply` löst die Verpflichtung des Absenders\ngenau so ein wie ein fertiges: die Sitzung endet, die Schuld ist weg, und an der Frage, wer wem was\nschuldet, ändert sich nichts. Anders ist der AUSGANG — und darüber entscheidet der Betreuer des\nKanals.\n\n**Die Eskalation dringt nach oben vor, auf jedem Kanal.** Ein Kanalmitglied, das \"ich kann nicht\"\nsagt, löst seinen eigenen Faden ein wie jede andere Antwort, die Menge ist also vollständig und der\nKanal konsolidiert — aber die Antwort des Kanals an den Anforderer trägt die Eskalation weiter, statt\nsie zu einem Ergebnis zu falten. Die Frage kann so, Ebene für Ebene, bis zu dem vordringen, der die\nArbeit in Auftrag gegeben hat.\n\nDas gilt für beide Ausgabeformen eines Kanals, auch für die, die ZUSAMMENFASST\n(`on_complete: summarize`) — denn ein Kanal fasst ein \"ich kann nicht\" niemals weg: Eine Menge, die\neines trägt, wird durchgereicht, wie sie ist. Wie das von der Seite des Anforderers aussieht, steht\nim Eintrag zum Konsolidierer aus demselben Release.\n\nGetragen wird sie von der `kind`-Angabe der Nachricht — einem Feld, das es bereits gab und das\nbereits synchronisiert wird; an der synchronisierten Nutzlast ändert sich also nichts. `--kind\nescalation` wird bewusst nicht angenommen: `--escalate` ist die eine Tür dorthin, und beides zugleich\nzu verlangen wird abgelehnt. Anwendungen erreichen dasselbe, indem sie `MessageKind::Escalation` auf\nder Antwort setzen, die sie ohnehin bauen — es gibt hier keine Fahne, die nur die Kommandozeile\nsenden kann.\n\nEs ist DEKLARIERT statt frei, und genau das macht es zu einem Signal: Der Betreuer des Kanals\nverzweigt auf ein Bit, dessen Bedeutung im Kanal steht, nicht auf einen Token, den ein Agent sich\nausdenkt. Deshalb braucht auch der freie Verzweigungs-Token, den das entfernte\n`nxc workflow step done` trug, keinen anderen Ersatz.\n- Eine Rolle oder ein deklarierter Kanal kann jetzt `working_tree: exclusive` deklarieren (die\nVorgabe `shared` bleibt exakt das heutige Verhalten — keine Sperre, kein Warten). Danach arbeitet\nimmer nur eine Kette von Rollen-Sitzungen an der Arbeitskopie des Repositorys; eine zweite Kette,\ndie sie ebenfalls braucht, stellt sich an, statt zu starten und mit der ersten am git-Index und an\nder Cargo-Build-Sperre zu kollidieren — genau die Kollision, die ein Coding-und-Review-Paar in\neinem früheren Praxistest der Rollen-Laufzeit real erlebt hat. Halter ist die KETTE — der Faden, in dem die Arbeit\nbeauftragt wurde — nicht die einzelne Sitzung.\n\nWie weit „die Kette\" reicht, beschreibt der Eintrag zum „Anspruchsbereich\" aus demselben Release; er\nlöst ab, was ein früherer Entwurf dieses Eintrags dazu sagte. Der Bereich ist die Unterhaltung, in\nder die Arbeit beauftragt wurde, SAMT allem, was darunter geöffnet wird — eine Coding-Runde behält\ndie Arbeitskopie also, während sie auf eine von ihr gestartete Review-Runde wartet. Ein\nWORKFLOW-LAUF ist ebenfalls über seine Schritte hinweg EIN Anspruchsbereich. Eine PERSONA-Kette\n(`nxc send --to `, ein Aufruf je Schritt) öffnet je Aufruf eine neue Unterhaltung; zwischen\nden Schritten wird die Sperre also frei, und die Freigabe startet, was am Kopf der Warteschlange\nsteht. Stand dort ein früher angestellter fremder Auftrag, läuft dieser als Nächstes, und der eigene\nnächste Schritt der Kette stellt sich dahinter an.\n\nWarten ist nie still. Die Quittung eines angestellten Triggers sagt es selbst: `nxc send --to\n --json` bekommt `queued_behind` (wer gerade hält) und `queue_position`\n(1-basiert), sobald ein Trigger warten muss, und die menschenlesbare Zeile ergänzt `QUEUED at\nposition n behind …`. `nxc threads list`/`show` tragen dieselbe Tatsache im Nachhinein — für eine\nKette, die nie nachschaut, oder eine App, die das Board erst später liest: Jeder Faden meldet jetzt\n`working_tree: \"holding\" | \"waiting\" | null` samt seiner Warteschlangen-Position.\n\nEine Freigabe übergibt die Arbeitskopie an einen ganzen ANSPRUCHSBEREICH, nicht an einen einzelnen\nwartenden Trigger. Mehrere Trigger teilen sich einen Anspruchsbereich immer dann, wenn ein\nmehrköpfiger Kanal mit `working_tree: exclusive` beim Öffnen warten musste: Jedes Mitglied dieses\nBoards gehört zum Faden des Boards, stellt sich also einzeln unter demselben Schlüssel an. Nur das\nerste zu starten, würde die übrigen dauerhaft stranden lassen — das Board schuldet deren Antworten\nund könnte die Freigabe, die sie starten würde, deshalb nie erreichen. Sie werden daher gemeinsam\ngestartet, und genau das passiert auch, wenn die Arbeitskopie frei ist (das erste Mitglied erwirbt\ndie Sperre, die übrigen erben sie). Welcher Anspruchsbereich als Nächstes drankommt, entscheidet\nweiterhin die Ordnung `(priority, enqueued_at, id)` der Warteschlange; erst nachdem der Kopf gewählt\nist, kommt der Rest seines Bereichs mit.\n\nFreigabe ist jetzt eine deterministische Tatsache, keine Schätzung von Ruhe. Der geprimte Prompt\neiner getriggerten Rolle nennt den Faden, dem sie eine Antwort schuldet, und den genauen Befehl,\nder das abschließt, und das neue Flag `nxc reply --if-unanswered` erlaubt einem Aufrufer, der\nwirklich nicht weiß, ob er schon geantwortet hat, bedingungslos aufzurufen — es postet nur, wenn\nnoch eine Antwort geschuldet wird, und ist sonst ein sicherer No-op. Der Abbau des Agenten-Sidecars,\nder am Ende jeder Sitzung immer läuft, nutzt jetzt genau dieses Flag: Eine Sitzung, die endet, ohne\nden ihr geschuldeten Faden je beantwortet zu haben, bekommt an ihrer statt eine Fehlantwort gepostet,\nstatt den Faden still hängen zu lassen. Sobald die letzte offene Antwort im Bereich einer Kette\neintrifft, gibt genau dieser Antwort-Aufruf die Arbeitsbaum-Sperre frei, zieht den Kopf der\nWarteschlange und startet ihn — kein Daemon, kein Wartender.\n\nDiese Zwei-Stunden-Grenze (`working_tree::WORKING_TREE_LEASE_BOUND`) ist nur ein Rückfall für den\nharten Tod, nie ein Budget oder ein Wecker: Beim Ablauf passiert nichts von selbst, und sie startet\nNICHTS bereits Wartendes — sie sorgt nur dafür, dass eine verwaiste Sperre nicht jeden KÜNFTIGEN\nErwerb abweist, sodass die nächste Kette, die fragt, die Arbeitskopie nehmen kann. Ein angestellter\nTrigger bleibt angestellt, bis eine spätere Kette die Sperre tatsächlich freigibt, nicht bis die\nGrenze verstreicht; und eine Sperre über einen Bereich, in dem gar nichts offen ist (was ein nacktes\nResume nimmt, da es keine Verpflichtung anmeldet), wird stattdessen von der nächsten Antwort in\ngenau diesem Faden freigegeben.\n\nGetrennt davon verwirft ein `send --to `-Trigger, der nach dem Posten seiner\nNachricht scheitert, diese nicht mehr: Die Quittung trägt jetzt `spawned: bool` (`false` sowohl für\n„hinter der Sperre angestellt\" als auch für „der Start selbst ist gescheitert\") und\n`warnings: [...]` mit dem, was schiefging, sodass ein Aufrufer endlich „nichts ist passiert\" von\n„die Nachricht steht im Kanal, aber niemand arbeitet noch daran\" unterscheiden kann — damit ist der\nlange offene Fehler 6j6v.hpv8 miterledigt. Der Exit-Code von `nxc` wird bei einem echten\nStart-Fehlschlag weiterhin ungleich null; ein `Engine`-Aufrufer prüft stattdessen `spawned`.\n\nFassade (Bruch): Die öffentliche API von `crates/chat` bewegt sich durch all das oben Genannte.\n`ReplyRequest` (auf `facade`, `orchestration` und `surface::ReplyThreadRequest`) bekommt je ein\nPflichtfeld `if_unanswered: bool` — `false` ergibt das bisherige Verhalten. `facade::reply` liefert\njetzt `Result>` (`None` ist das neue No-op-Ergebnis).\n`orchestration::ReplyReceipt` bekommt `posted: bool`, ihr `message_id` ist jetzt\n`Option`. `worker::TriggerRequest` bekommt `reply_thread: Option`.\n`orchestration::RoleSpawn` bekommt die Pflichtfelder `priority: Priority` und\n`thread: Option<&str>` (dazu `queued_since: Option<&str>`). `working_tree::QueuedTrigger` bekommt\n`priority: crate::model::Priority` und `enqueued_at: Option` — beide Bestandteile der\nWarteschlangen-Ordnung `(priority, enqueued_at, id)` reisen jetzt mit einem angestellten Eintrag\nmit, damit ein nachgerückter Trigger, der erneut warten muss, Dringlichkeit und bereits gewartete\nZeit behält, statt als `normal` mit aktuellem Zeitstempel wieder einzureihen.\n`orchestration::trigger_role` liefert jetzt `Result` statt\n`Result<(), TriggerError>`;\n`orchestration::TriggerReceipt` und `surface::SendToReceipt` bekommen je `queued_behind`/\n`queue_position` sowie die Pflichtfelder `spawned`/`warnings`. `facade::threads` liefert jetzt\n`Vec` statt `Vec` (die bisherigen Felder wandern unter\n`.quorum`), und `facade::thread_board`s `ThreadBoardView` bekommt dieselben zwei\nArbeitsbaum-Felder; `facade::thread_quorums` bleibt unverändert. Auf der Store-Seite verliert\n`ChatStore::enqueue_working_tree` seinen Parameter `priority: i64` (er reist jetzt am Warteschlangen-\nEintrag selbst mit), und `ChatStore::release_working_tree` entfällt — `release_working_tree_and_take_next`\nbleibt als einziger Freigabeweg übrig und liefert jetzt `Vec` (den gesamten\nAnspruchsbereich am Kopf, in Warteschlangen-Reihenfolge) statt `Option`; ein leerer\nVektor ist das bisherige `None`.\n\nWeil dies die öffentliche API von `nexus-chat` bricht, erscheint diese Epic als MINOR-Release\n(`0.60.0`), nicht als `0.59.x`-Patch: `enforce_facade_breaking_axis` blockiert einen Patch-Bump mit\neinem `facade: breaking`-Fragment fail-closed.\n\n### Geändert\n- Der `timeout` eines deklarierten Kanals ist jetzt eine Frist JE MITGLIED, und jede Transkript-Ausgabe\ndieses Mitglieds setzt sie zurück.\n\nBisher erzeugte der `timeout` einen einzigen absoluten Zeitpunkt, der jedem Mitglied des Kanals\naufgestempelt wurde und danach stur bis zum Ende lief: ein Kanal mit `timeout: 30m` gab seine\nMitglieder dreißig Minuten nach dem Öffnen auf, ganz gleich, was sie gerade taten. Genau den Fall, den\neine Frist überleben soll, macht das kaputt — ein Agent, der einen Shell-Befehl startet, der eine\nStunde läuft, ist in Minute dreißig kerngesund, und ihn dort abzuschneiden vernichtet eine Stunde\nArbeit und liefert dem Anforderer eine Antwort, in der ausgerechnet das Mitglied fehlt, das wirklich\ngearbeitet hat.\n\n`timeout: 30m` heißt jetzt \"dreißig Minuten STILLE von diesem Mitglied\". Hinter jedem Mitglied eines\nKanals steht genau eine Sitzung, und jeder Schreibvorgang in deren Transkript — ein Gedanke, ein\nWerkzeugaufruf, ein Ergebnis — startet das Fenster von diesem Moment an neu. Ein Mitglied, das\nweiterhin Lebenszeichen gibt, wird nie aufgegeben, wie lange seine Arbeit auch dauert; ein Mitglied,\ndas über das ganze Fenster keines gibt, läuft weiterhin ab, und der Kanal fasst zusammen, was er hat,\nund vermerkt, wer nicht geantwortet hat. Die Uhren sind unabhängig: läuft eine ab, betrifft das genau\ndieses Mitglied und sonst niemanden.\n\nDrei kleinere Folgen derselben Änderung. Ein erneut gefragtes Mitglied — ein zweiter Zug im selben\nKanal — startet sein Fenster wieder ab dem Moment der Frage, statt eine längst abgelaufene Frist zu\nerben und vorbei zu sein, bevor es begonnen hat; die Nachprüfung, die es am Ende aufgibt, wird\nzusammen mit diesem Fenster eingeplant, sodass auch ein zweiter Zug automatisch aufgegeben wird und\nnicht nur der erste. Ein Mitglied, das erst auf die Arbeitskopie warten muss, beginnt sein Fenster,\nwenn es wirklich anfängt, statt aufgegeben zu werden, während es noch in der Warteschlange steht.\nUnd die eingeplante Nachprüfung eines Kanals erzwingt keine Zusammenfassung mehr, bloß weil der\nZeitpunkt erreicht ist, für den sie eingeplant war — sie findet die Mitglieder lebendig, lehnt ab\nund plant sich selbst auf den nächsten Zeitpunkt neu ein, an dem eines von ihnen fällig wird. Das\nautomatische Aufgeben passiert also weiterhin; es passiert jetzt nur zur richtigen Zeit.\n\nEin unlesbarer `timeout` (`timeout: twenty-minutes`) wird jetzt mit einem Fehler abgelehnt, der das\nKanal-Feld und den Wert nennt, bevor irgendetwas geöffnet wird — statt weiter unten als\nArgumentfehler über ein `--deadline` aufzutauchen, das niemand getippt hat. Ein `--deadline` als\nabsoluter Zeitpunkt bleibt unverändert und bewusst stur: wer einen Zeitpunkt nennt, meint diesen\nZeitpunkt, und nichts verschiebt ihn.\n- Ein deklarierter Kanal hat jetzt ZWEI EBENEN, und jeder Faden darin hat genau zwei Enden. Bisher\nstempelte `nxc send --to ` EINEN Faden, schrieb sämtliche Mitglieder in dessen\n`expects_reply_from` und startete alle auf genau diesem Faden: Anforderer und alle drei Reviewer\nsaßen in einer Unterhaltung, jeder las die Antwort jedes anderen, und der Normalfall des Datenmodells\nwar ein Faden mit N+1 Enden.\n\nEin `send --to ` erzeugt jetzt den KANAL-FADEN — der Anforderer an einem Ende, der BETREUER\ndes Kanals am anderen — und darunter je Mitglied einen eigenen Faden. Jeder Mitglieder-Faden hat den\nBetreuer an einem Ende und genau dieses eine Mitglied am anderen. Drei Reviewer sind vier Fäden,\nnicht einer, und die Reviewer sehen einander nicht — weil sie nicht in derselben Unterhaltung sitzen,\nnicht weil ein Filter etwas verbirgt.\n\n**Der Betreuer ist Maschinerie der Engine**, nie ein Mensch und auch keine deklarierte Rolle. Er ist\ndie reservierte Identität `/__channel__` und tut dreierlei: er nimmt den eingehenden Auftrag\nentgegen (ein `send --to ` und jedes spätere `reply` in den Kanal-Faden) und öffnet die\nMitglieder-Sends selbst; er wartet auf die MENGE der Mitglieder-Fäden statt auf die Namensliste eines\neinzelnen Fadens; und wenn alle geantwortet haben — oder ihre Fristen zugeschlagen sind — führt er\ndie deklarierte `on_complete`-Regel des Kanals aus und liefert das Ergebnis an den Anforderer. Er\nKANN von einer Agenten-Sitzung besetzt werden: genau das ist `on_complete: summarize`, und die\nAntwort dieser Sitzung ist es, die den Kanal-Faden einlöst.\n\nDas alles läuft SYNCHRON, im Schreibpfad des `reply`, das es ausgelöst hat: Nachricht schreiben,\nVerpflichtung als eingelöst erkennen, Betreuer laufen lassen, nächste Fäden öffnen und die Sitzungen\nSTARTEN — und erst dann kehrt der Aufruf zurück. Gewartet wird auf das Starten, nie auf das Ergebnis.\nDer Grund ist nicht die Latenz, sondern dass nur hier eine gescheiterte Folge noch einen Adressaten\nhat: asynchron ist die antwortende Sitzung längst beendet, wenn ihre Folge läuft, und ein Fehlschlag\nfällt niemandem auf.\n\nZwei Dinge sieht ein Aufrufer anders. `nxc send --to --json` meldet `expects:\n[\"/__channel__\"]` — das andere Ende des Kanal-Fadens —, und die Erwartungen je Mitglied\nliegen eine Ebene tiefer, eine je Mitglieder-Faden, wo `nxc status --thread ` sie zeigt. Und ein\nMitglied antwortet in SEINEM EIGENEN Faden: die Id in der Nachricht, mit der ein Mitglied gestartet\nwird, ist seine eigene, und nichts verweist ein Mitglied je auf den Kanal-Faden.\n\nDer Zug eines Mitglieds wird jetzt auch richtig WIEDER GEÖFFNET. Ein `reply` in den Kanal-Faden durch\ndessen Anforderer ist dieselbe Tür wie die erste Nachricht: der Betreuer deklariert die Erwartung\njedes Mitglieds auf dessen vorhandenem Faden neu und setzt dessen Sitzung samt Verpflichtung fort —\ndamit `nxc reply --thread --if-unanswered`, der Aufruf, den eine Agenten-Sitzung beim\nHerunterfahren macht, ab dem zweiten Zug etwas einzulösen hat. Vorher deklarierte überhaupt nichts\neine Erwartung bei einer Fortsetzung neu, und dieses Sicherheitsnetz war ab Zug zwei ein stiller\nLeerlauf.\n\nDamit trägt ein Kanal einen ganzen Ablauf ohne jeden Lauf-Datensatz dahinter — und genau deshalb\nkonnte die Gruppe `nxc workflow` in demselben Release entfallen.\n- Die Arbeitskopie ist jetzt so lange geschützt, wie die AUFGABE läuft. Darüber entscheiden zwei\nFragen, und sie haben zwei verschiedene Antworten.\n\n**OB eine Kette geschützt wird, wird von den deklarierten Mitgliedern nach oben abgeleitet, einen\nSchritt weit.** Ein Kanal, dessen deklariertes Mitglied `working_tree: exclusive` sagt, gilt als\nschutzbedürftig, ohne dass der Autor es am Kanal wiederholt — einmal an der Persona gesagt, die baut,\nund jeder Kanal, in dem sie arbeitet, ist abgedeckt. Nach einem Schritt ist Schluss, und das mit\nAbsicht: Ein Kanal fängt sich die Schutzbedürftigkeit NICHT von einem anderen Kanal ein, nur weil die\nbeiden ein Mitglied teilen. Sonst gäbe ein Coding-Kanal seinen Bedarf an einen Review-Kanal weiter,\ndessen Mitglieder nur lesen, dieser gäbe ihn weiter, und am Ende wäre jeder Kanal im Arbeitsbereich\nexklusiv. `working_tree: exclusive` direkt am Kanal zu deklarieren wirkt unverändert und bleibt der\nrichtige Weg für „diese RUNDE braucht sie\", wenn es kein einzelnes Mitglied ist.\n\nDer praktische Unterschied: Ein geschützter Kanal wartet jetzt als Ganzes. Vorher ließ ein Kanal, der\nselbst nichts sagte, das Mitglied warten, das den Bedarf deklariert hatte, während die schweigenden\nGeschwister trotzdem starteten — das Board öffnete sich halb angestellt und halb laufend, auf beiden\nSeiten einer Sperre, die die Kette abdecken sollte.\n\n**WIE WEIT der Schutz reicht: die Unterhaltung, in der die Arbeit beauftragt wurde, samt allem, was\ndarunter geöffnet wird.** Er hält also, während eine Coding-Runde auf eine von ihr gestartete\nReview-Runde wartet: Die Review-Unterhaltung und die Fäden der Reviewer hängen darunter und liegen im\nselben Anspruchsbereich. Er deckt endlich auch eine Nebenunterhaltung ab, die eine arbeitende Sitzung\nfür sich öffnet — die tauchte am Faden-Board bisher so auf, als gehöre sie zu gar keiner Sperre. Und\nnach oben ist Schluss: Die Unterhaltung mit dem Agenten, der die Arbeit beauftragt hat, liegt ÜBER\ndem Anspruch und hält nichts — ein unbeantworteter Mensch behält die Arbeitskopie also nie bis\nmorgen.\n\n**Freigegeben wird, wenn diese Unterhaltung GESCHLOSSEN ist — und das ist nicht dasselbe wie\n„gerade schuldet niemand eine Antwort\".** Eine gewöhnliche Antwort schließt sie, und der nächste\nAuftrag aus der Warteschlange startet. Ein `reply --escalate` — „ich kann diese Aufgabe nicht\nerfüllen\" — schließt sie NICHT, obwohl die Verpflichtung des Absenders so oder so eingelöst ist: Die\nFrage dringt nach oben vor, womöglich bis zu Ihnen, die Aufgabe ist mitten im Gange, und die\nArbeitskopie bleibt geschützt. Auch langes Schweigen ändert daran nichts. **Eine unbeantwortete\nRückfrage (`--kind question`) hält sie genauso**, aus demselben Grund: In beiden Fällen arbeitet\nniemand, und eine Freigabe ließe eine zweite Kette in eine Arbeitskopie starten, in die der Fragende\ngleich zurückkehrt. Das gilt **überall im geschützten Bereich**, nicht nur in der Unterhaltung, in\nder die Arbeit beauftragt wurde: Ein Reviewer drei Ebenen tiefer, der eine Rückfrage gestellt hat und\nauf die Antwort wartet, hält die Kopie — auch dann, wenn jede Unterhaltung darüber längst\nabgeschlossen hat, ohne es zu bemerken.\n\n**Eine Antwort setzt die Runde wieder in Gang, und die Kopie wird frei, wenn diese Runde fertig\nist.** Antworten heißt: der Runde ihren nächsten Zug geben — eine Antwort in die Unterhaltung des\nKanals selbst, durch dieselbe Tür, durch die die Arbeit kam. Damit wird der Zug jedes Mitglieds neu\neröffnet und alle laufen wieder. In diesem Moment wird nichts freigegeben, und das ist richtig so:\nDie Mitglieder arbeiten ja. Was die Antwort beendet, ist das WARTEN — die Kopie wird also mit dem\nAbschluss der Runde frei, ohne die Zwei-Stunden-Grenze auch nur in die Nähe zu kommen. Die Grenze\ngreift nur, wenn die Frage überhaupt nie beantwortet wird.\n\nDie Fehlerrichtung ist gewollt: Wer unnötig eskaliert oder fragt, hält die Kopie etwas zu lange —\nharmlos neben einer Kopie, die weggegeben wird, während die Aufgabe noch läuft.\n\n**Damit entfällt auch die Regel, dass ein Lauf der entfernten Ablauf-Maschine die Arbeitskopie\nhielt, solange er `running` war.** Das war ein Deckel auf einem engeren Problem — ein Rollenschritt\neines solchen Laufs hinterließ nirgends eine Verpflichtung, der Lauf sah also fertig aus, sobald er\nauf einen solchen Schritt weiterging — und er kostete zweierlei: Ein festgefahrener Lauf hielt die\nKopie bis zur Zwei-Stunden-Grenze, und die Freigabe kam, wenn der LAUF endete, statt wenn die Arbeit\nfertig war. Der Anspruchsbereich ENTHÄLT jetzt die Unterhaltungen, in denen die Arbeit stattfindet —\ngenau das, wofür der Deckel einsprang —, und auf der Oberfläche dieses Releases wird ein\nKanalmitglied immer mit Unterhaltung und Verpflichtung beauftragt, die Lage, für die es den Deckel\nbrauchte, kann also gar nicht mehr entstehen.\n\nFassade (Bruch): Die Freigabe-Ableitung in `nexus_chat::working_tree` verhält sich bei gleicher\nEingabe anders. `ChatStore::work_scope_threads` liefert für einen `Thread`-Bereich den ganzen Teilbaum statt des\nFadens plus seiner direkten Mitglieds-Fäden, und `WorkScope` hat jetzt zwei statt drei Varianten —\nder `Run`-Zweig ist mit der Ablauf-Maschine entfallen, die dieses Release entfernt. Neu daneben: `ChatStore::work_scope_handed_back`,\n`ChatStore::thread_subtree`/`thread_subtrees`, `ChatStore::last_reply_kind` (die weitere Lesung, an die\n`last_reply_escalated` jetzt delegiert — jene Funktion ist unverändert) und\n`Definitions::channel_needs_working_tree`.\n\n### Behoben\n- **Die Antwort, die eine Tafel abgeschlossen hat, ist jetzt die, die sie wirklich abgeschlossen hat.**\nSobald jeder Beteiligte geantwortet hat, auf den ein Faden gewartet hat, wird der Anforderer mit den\ngesammelten Antworten geweckt, und eine davon ist als die abschließende gekennzeichnet — die Antwort,\ndie den Zug beendet hat. Diese Kennzeichnung wurde über einen Vergleich der Nachrichten-Ids gewählt.\nEine Nachrichten-Id beginnt mit der Millisekunde, in der sie erzeugt wurde; zwei Antworten aus\nderselben Millisekunde unterscheiden sich also nur noch im Zufallsteil dahinter, und die\nKennzeichnung fiel auf diejenige, die zufällig höher sortierte. Zwischen zwei synchronisierten\nGeräten können die Ids der tatsächlichen Reihenfolge sogar beliebig weit widersprechen, weil jedes\nGerät seine eigenen stempelt.\n\nDie abschließende Antwort wird jetzt nach derselben kausalen Ordnung bestimmt, in der auch der Rest\neines Fadens gelesen wird — der Reihenfolge, in der die Nachrichten selbst erscheinen, der\nReihenfolge, aus der `nxc status` die letzte Antwort meldet, und der, die entscheidet, ob die letzte\nAntwort eine Eskalation war. Diese drei waren sich längst einig; diese eine Lesestelle war es nicht,\nund sie war die letzte im Nachrichtenspeicher, die noch nach der rohen Id ordnete.\n\nWas sich für Sie ändert: Bei einem Faden, dessen Antworten dicht beieinander eintreffen — der\nNormalfall, wenn die Mitglieder eines Kanals gleichzeitig fertig werden —, kann die als abschließend\nhervorgehobene Antwort und die daneben gemeldete Nachrichten-Id von dem abweichen, was eine frühere\nVersion für exakt dasselbe Gespräch gemeldet hat. Die neue Antwort ist die richtige.\n\nUnverändert, und zwar mit Absicht: Ob eine abgeschlossene Tafel noch Ihre Aufmerksamkeit braucht,\nentscheidet weiterhin Ihr Lesezeiger auf die gewohnte Weise — es kommt also nichts zurück, was Sie\nschon gelesen hatten.\n- Zwei Prozesse, die gleichzeitig an einem Arbeitsbereich arbeiten, können einander keine Schreibvorgänge\nmehr verlieren.\n\nDie logische Uhr, die jede Änderung ordnet, wurde bisher einmal gelesen — beim Öffnen des\nArbeitsbereichs — und sah danach nie wieder in die Datei. Zwei Prozesse, die vom selben Stand\nstarteten, prägten ihren Änderungen dieselbe Zahl auf, und die Zusammenführungsregel, die eine\nÄnderung nur behält, wenn sie das Verzeichnete echt übertrifft, verwarf die zweite daraufhin\nwortlos: Der Befehl meldete Erfolg, Exit-Code 0, und das Brett zeigte weiter den alten Wert. Eine\nlanglebige App, die den Arbeitsbereich neben kurzlebigen `nxf`/`nxc`/`nxm`-Aufrufen offen hält,\ndriftete auf dieselbe Weise ab — und mit jeder Minute weiter.\n\nGenau so ist diese Plattform gedacht — mehrere Agenten in einem Arbeitsbereich —, und genau deshalb\nblieb der Fehler unentdeckt: Die Testsuite läuft vollständig in EINEM Prozess, wo ein Handle jede\nÄnderung beobachtet und die Divergenz gar nicht entstehen kann. Betroffen war jedes\nLast-Write-Wins-Feld, auf dem Brett wie in Chat und Memory. Und es war nicht theoretisch: Am Tag der\nReparatur trugen drei der zwanzig Arbeitsbereiche auf der Entwicklungsmaschine seine Spuren, das\nnexus-flow-Brett darunter.\n\nJede Änderung liest den Höchststand des Arbeitsbereichs jetzt im Moment des Schreibens neu und hält\ndie Schreibsperre über Lesen und Schreiben hinweg — eine Änderung wird also immer über allem\ngestempelt, was schon in der Datei steht, gleich welcher Prozess es hineingeschrieben hat. Darunter\nweist das Protokoll einen doppelten Stempel jetzt rundheraus ab, statt ihn anzunehmen und der\nZusammenführungsregel zu überlassen, ihn still zu verwerfen. Ein Arbeitsbereich, der aus der Zeit\ndavor bereits Doppelstempel trägt, öffnet und arbeitet unverändert; ihm lässt sich die zweite\nZusicherung nur nicht rückwirkend geben — blockiert oder umgeschrieben wird er dafür nie.\n\nFassade (Bruch): Keine Signatur bewegt sich, ein dokumentiertes Verhalten schon.\n`nxs_foundation::store::Store::apply` liefert die Ops zurück, die es nicht in die Sichten gefaltet\nhat; dieser Vektor trägt jetzt auch eine übernommene Op, die das Protokoll ABGEWIESEN hat, weil eine\nandere Op deren Koordinate `(lamport, site)` bereits hält. Zuvor wurde eine solche Op gespeichert\nund danach still überstimmt. Ein Konsument, der den Vektor strikt als „Op-Formen, die diese Version\nnicht versteht\" liest, sieht darin eine neue Art von Eintrag. Erreichbar ist das nur, wenn zwei\nRepliken sich eine `site_id` teilen — was 63 Bit Entropie für echte Repliken ausschließen und was\ndie Präfix-Registry ohnehin als reparierbare Fehlkonfiguration behandelt —, in der Praxis sieht also\nkein korrekter Aufrufer eine Änderung. `Store::emit` bekommt die entsprechende Zusicherung für eine\nlokal geprägte Op; sie ist unerreichbar, sobald die Uhr unter der Schreibsperre aufgefrischt wird.\n- Eine Kette, die die Arbeitskopie hält, kann jetzt weitere Arbeit daran beauftragen, ohne sich selbst\nzu blockieren.\n\nBisher stellte sich jeder exklusive Anspruch, der aus einer bereits haltenden Kette heraus geöffnet\nwurde, hinten in die Warteschlange — hinter genau die Kette, die ihn geöffnet hatte. Diese Kette\nkonnte dann nie fertig werden, denn worauf sie wartete, war der Auslöser, den sie selbst gerade\nangestellt hatte. Nichts startete, nichts meldete einen Fehler, und die Warteschlange leerte sich\nauch nach der Zwei-Stunden-Grenze nicht. Dafür brauchte es keinen ausgefallenen Aufbau: Ein\nschlichtes `nxc send --to ` für eine Rolle, die `working_tree: exclusive` deklariert,\nabgesetzt aus einer Sitzung, die schon in einer geschützten Kette steht, genügte — und ein\nverschachtelter geschützter Kanal ebenso.\n\nDie Ursache war eine Unsymmetrie. Der Anspruchsbereich ist seit der Anspruchsbereichs-Änderung „die\nUnterhaltung, in der die Arbeit beauftragt wurde, samt allem, was darunter geöffnet wird\" — aber nur\ndie FREIGABE-Seite hat ihn je so gelesen; die Erwerbsseite verglich Anspruchsschlüssel exakt und löste\neinen Faden nur eine Ebene nach oben auf. Arbeit, die tiefer in einem bereits gehaltenen Bereich\ngeöffnet wurde, bekam so einen anderen Schlüssel und sah aus wie ein Rivale. Beide Enden lesen jetzt\ndenselben Bereich, ein solcher Auslöser erbt den Anspruch, statt sich dahinter anzustellen — und eine\nwirklich fremde Kette wartet weiterhin genau wie zuvor.\n\nNicht davon erfasst und getrennt erfasst: Stirbt ein Halter hart, leert niemand seine Warteschlange;\nwer dort schon steht, wartet über die Zwei-Stunden-Grenze hinaus, bis irgendeine andere Kette\nerwirbt und wieder freigibt.\n- Eine Antwort löst jetzt den ZUG ein, den sie beantwortet, und nicht den ganzen Faden. Bisher galt\neine Rolle in einem Faden ab ihrer ersten Nachricht dort für immer als „hat geantwortet\" — die\nrichtige Lesart für ein einmaliges Review-Board und die falsche für ein Gespräch, das hin und her\ngeht. Sobald ein Betreuer mit einem zweiten Arbeitspaket auf denselben Faden zurückkam, war alles,\nwas auf dieser Zählung aufsetzt, bereits überzeugt, die Rolle schulde nichts mehr: `nxc reply\n--if-unanswered` — das Sicherheitsnetz, das der Agenten-Sidecar am Ende jeder Sitzung auslöst, damit\neine ohne Antwort gestorbene Sitzung trotzdem eine Antwort hinterlässt — tat ab dem zweiten Zug still\nnichts mehr, und die Arbeitsbaum-Sperre las die Kette als fertig, während die Rolle noch am\nAbschlussbericht schrieb, und übergab die Arbeitskopie mitten im Lauf an eine fremde Kette.\n\nOb eine Antwort noch geschuldet wird, wird jetzt ab dem Zeitpunkt der Frage gemessen: Die Erklärung\n`expects_reply_from` eines Fadens trägt ihre eigene Position im Protokoll, und als Antwort darauf\nzählt nur, was ein Handle NACH der aktuellen Erklärung geschrieben hat. Eine erneute Erklärung —\nmit denselben Handles oder mit anderen — eröffnet damit einen neuen Zug, und alle, die sie nennt,\nschulden ihr eine Antwort. Am synchronisierten Nachrichtenformat ändert sich nichts; das Ganze wird\naus dem abgeleitet, was das Protokoll ohnehin trug, also braucht kein Gerät eine Migration, und ein\nPeer, der nur die Ops empfangen hat, kommt zum selben Ergebnis.\n\n**Eine Einschränkung, und sie gehört dem Unterbau, nicht dieser Regel.** „Nach der aktuellen\nErklärung\" wird in der kausalen Ordnung des zugrunde liegenden Protokolls entschieden, und diese\nOrdnung hat eine bekannte Lücke: Der Zähler, den sie verwendet, gehört einem GEÖFFNETEN\nSpeicher-Handle — beim Öffnen einmal aus der Datei gewonnen, danach nie wieder mit ihr abgeglichen —\nwährend alle Prozesse eines Geräts dieselbe Replikat-Identität teilen. Zwei Schreiber, die denselben\nArbeitsbereich gleichzeitig offen halten, sehen die Position des jeweils anderen also nicht: ein\nlanglebiger Host, der die Engine für seine Laufzeit einbettet, neben kurzlebigen `nxc`-Aufrufen —\noder schlicht zwei gleichzeitig laufende `nxc`-Aufrufe. Wo das passiert, kann eine Nachricht, die VOR\neiner erneuten Erklärung geschrieben wurde, als Antwort auf sie gezählt werden — auf einem Gerät,\nganz ohne Synchronisation — und die Folgen sind genau die, gegen die die Regel oben antritt: ein Zug,\nder in dem Augenblick als eingelöst gilt, in dem er eröffnet wird, und eine Arbeitsbaum-Sperre, die\nfreigegeben wird, während die Arbeit noch läuft. Das ist als eigenes Item erfasst (`6j6v.fc5p`), der\nFix gehört in den Unterbau, auf dem alle diese Lesungen sitzen, und an der hier beschriebenen Regel\nändert sich dadurch nichts.\n\n**Damit entfällt der Befehl `nxc threads expect`.** Neu zu erklären, von wem ein Faden eine Antwort\nerwartet, war bisher etwas, das ein Mensch tippen konnte, und der Zweck war, ein Board abzuschließen,\ndas an einem nie zurückkehrenden Reviewer feststeckte: den erwarteten Satz auf die verengen, die\ngeantwortet hatten, und das Board war zu. Nach der Regel oben bedeutet eine erneute Erklärung genau\ndas nicht mehr (sie fragt die genannten Handles erneut, mit Wirkung ab jetzt) — und statt einen\nSonderfall zu bauen, der die alte Wirkung rettet, entfällt der Befehl: Er stand ohnehin auf der Liste\nder `nxc`-Kommandos, die zurückgebaut werden, während die Agent-zu-Agent-Oberfläche auf das gekürzt\nwird, was ein Agent wirklich braucht. **Die Fähigkeit selbst ist nicht verschwunden** — der Schreibweg\nsteht Apps und Hosts weiterhin als `facade::set_expects` zur Verfügung (nur der Öffner, gleiche\nFehler), und genau ihn benutzt der Kanal-Betreuer, um einer Rolle ihren nächsten Zug zu geben.\nWeggefallen ist eine von Hand zu tippende Tür. Um ein Board zu schließen, das niemand mehr beantworten\nwird, lässt man die Erwartung stattdessen fallen: Ein Satz, der dieses Handle nicht mehr nennt, lässt\nnichts offen — und genau das lesen die Freigabe der Arbeitskopie, die Einlösung per `--if-unanswered`\nund der Fristhinweis.\n\n**In einem bestehenden Arbeitsbereich wirkt das rückwirkend, und man sieht es.** Es handelt sich um\nabgeleitete Lesungen, nicht um gespeicherten Zustand: Jedes Board wird beim ersten Lesen nach dem\nUpdate nach der neuen Regel neu berechnet. Ein Board, das früher durch Verengen abgeschlossen wurde,\nliest sich wieder als offen (seine Antworten liegen vor der Verengung) — womit auch die Kette, die für\ndiesen Faden die Arbeitskopie hält, sie weiter hält, bis jemand die aktuelle Erklärung beantwortet\noder die Erwartung fallengelassen wird; und ein Faden mit abgelaufener Frist kann erneut als\n„wartet über die Frist hinaus\" auftauchen. Nichts geht verloren und nichts wird umgeschrieben:\nGeändert hat sich die Frage, die an dasselbe Protokoll gestellt wird.\n\nDie Art einer Nachricht wird dabei bewusst nirgends herangezogen: Eine Antwort „ich kann das nicht\"\nlöst den Zug genauso ein wie eine fertige, denn die Sitzung endet so oder so.\n\nFassade (Bruch, verhaltensseitig — keine Signatur bewegt sich): `nexus_chat::store::ChatStore::\nthread_quorum` und `thread_quorums` liefern dieselbe Form, und `replied` / `outstanding` /\n`complete` von `ThreadQuorum` antworten jetzt pro Zug statt pro Faden. Ein Konsument, der ein Board\ndarstellt, sieht einen neu erklärten Faden wieder aufgehen; ein Konsument, der „einmal geantwortet\"\nals dauerhaft behandelt hat, muss stattdessen die aktuelle Erklärung lesen. Die Quittung von\n`facade::set_expects` meldet aus demselben Grund den erneut gefragten Satz.\n\n### Entfernt\n- **Deklarationen ziehen nach `.nxs-personas/` um.** Wer Ihre Agenten sind und wo sie sprechen, wird\nab jetzt in `/.nxs-personas/` deklariert — eine `.yaml` je Persona,\nund `channels.yaml` für die gemeinsamen Kanäle, worin auch ein Ablauf deklariert wird. Der alte Ordner\n`/roles/` wird weiterhin GELESEN, ein bestehendes Projekt läuft also unverändert\nweiter, und die Engine sagt jetzt laut, dass sie ihn gelesen hat: `nxc list` und `nxc prime` nennen\nden alten Ordner und den Pfad, an den die Deklarationen gehören, und `nxc prime --json` trägt\ndieselbe Tatsache als Daten. **Es wird nichts für Sie verschoben.** Eine Anwendung, die ein fremdes\nProjekt nur geöffnet hat, darf es nicht still umschreiben — das ist eine unumkehrbare Änderung am\nEigentum eines Dritten, ausgelöst durch bloßes Hinsehen. Der Umzug bleibt deshalb etwas, das Sie\nabsichtlich tun: Ordner kopieren, und `.nxs-personas/` übernimmt in dem Moment, in dem es selbst eine\nDeklaration trägt.\n\n**Woher ein Katalog stammt, gehört jetzt zur Antwort.** `Engine::definitions()` gelingt weiterhin in\neinem Arbeitsbereich, der nichts deklariert — eine Lesung, die scheitert, WEIL es nichts zu lesen\ngibt, ist eine schlechte Lesung, und sie machte aus einem legitimen Zustand eine Ausnahme, die eine\nApp nur abfangen müsste, um einen Einstiegsbildschirm zu zeichnen. Der zurückgegebene Katalog trägt\ndie Auflösung jetzt aber daneben: den Pfad, an den eine Deklaration gehört, den tatsächlich gelesenen\nOrdner (`.nxs-personas/`, ein altes `roles/`, oder keiner), ob überhaupt ein alter Ordner gefunden\nwurde, und die Anzahl. Eine Struktur beantwortet sowohl „woher kommt das hier\" als auch „liegt dieses\nProjekt noch im alten Format\".\n\n**Der leere Arbeitsbereich hört auf, anonym zu sein**, und jede Oberfläche antwortet in ihrem eigenen\nRegister. `nxc list` und `nxc prime` sind ORIENTIERUNG: Sie scheitern nicht, sie sagen, dass nichts\ndeklariert ist und wo eine Deklaration hingehört, und enden mit 0 — eine leere Liste allein ist eine\nLüge durch Auslassung, ein Fehler wäre falsch. `nxc send --to ` ist das eine, das wirklich\nablehnt: Es ist eine Handlung mit Pflichtziel, und ohne Deklaration gibt es keines. Es scheitert\nbenannt, statt ein fehlendes Ziel zu melden, in dem der Aufrufer dann einen Tippfehler suchen würde.\n\n**`nxc init` lässt den Ordner da**, leer, mit einer kurzen Erklärung der Form darin und ohne\nmitgelieferte Personas. Ein vorhandenes `roles/` wird nie angefasst, und das leere `.nxs-personas/`\nverdeckt es auch nicht: Der neue Ort gewinnt erst, sobald er selbst eine Deklaration trägt — ein\n`init` in einem bestehenden Projekt kann es also nicht still entteamen. Ein zweites `init` lässt eine\nvon Ihnen bearbeitete Erklärung stehen.\n\nDie beiden mitgelieferten Beispiel-Teams ziehen mit um:\n`crates/chat/examples/role-runtime-v2/.nxs-personas/` und\n`crates/chat/examples/role-runtime-v3/.nxs-personas/`.\n\n**Der Injektionsweg entfällt mit ihm.** `EngineConfig` nimmt keine Deklarationsquelle mehr, und\n`Engine::set_definitions` gibt es nicht mehr: Personas und Kanäle kommen für eine einbettende App\ngenauso aus `.nxs-personas/` wie für die Kommandozeile, und auch ein späterer Rollen-Editor in der\nApp schreibt in diesen Ordner statt in eine app-eigene Datenbank. Die Eigenschaft, für die es den\nInjektionsweg gab, bleibt unverändert und kostet jetzt nichts mehr: Eine Änderung ist beim nächsten\nAufruf auf dem Handle und auf jedem bestehenden Klon davon sichtbar, ohne Neuöffnen und ohne\nabgerissenes Abonnement — weil nichts zwischengespeichert wird. **`Engine::definitions()` — die\nLESUNG — bleibt**, und ein Tor hält sie dort fest, statt es der Sorgfalt zu überlassen:\n`crates/chat/tests/seam_disposition.rs` nennt sie samt Begründung und wird rot, wenn sie verschwindet.\n\n**Drei Eingänge verlassen die Oberfläche, und der Sitzungsstart-Block wird auf das neu\nzugeschnitten, was bleibt.** `nxc agents list` / `register` / `search` entfallen: Ein Team wird\nDEKLARIERT, nicht registriert — eine Persona ist eine Datei in `.nxs-personas/`, lesbar, prüfbar\nund den Lauf überdauernd, während eine Laufzeit-Profilzeile in genau einer Arbeitsbereichs-\nDatenbank lag und niemand sie je geprüft hat. `nxc list` ist die Lesung darüber und trägt die\nbeiden Felder, die `agents search` durchsuchte. `nxc workflow tickets add` entfällt ersatzlos — wie in\ndemselben Release die ganze Gruppe `nxc workflow`, unter der es hing. Womit eine Nachricht zu tun\nhat, sagt `--ref nxf_ids=` an ihr. Und `nxc inbox` / `nxc read` gehen von der AGENTEN-Oberfläche runter, bleiben aber\nfür einen Menschen und auf der App-Naht: Was eine Sitzung bei ihrem Start GELEHRT bekommt, ist\njetzt genau `list`, `send --to`, `reply --thread`, `nxc status`, `search` und `transcript show` —\neine Persona bekommt ihr Ungelesenes bei Sitzungsstart und bei Fortsetzung, statt danach zu fragen.\n\n**Ein Tor hält jetzt fest, was beim Wegnehmen mit der Bibliotheksnaht geschieht.** Für jedes von\nder Kommandozeile genommene Verb ist entschieden und aufgeschrieben, ob die zugehörige Lesung oder\nSchreibung auf `Engine` bleibt — und das Tor hält beide Hälften: Eine Wegnahme kann nicht landen,\nohne dass der Datensatz mitgeht, und eine Lesung, die eine App zeichnet, kann nicht still mit dem\nVerb verschwinden, das sie erreichte.\n\n**Und jede `--help`-Seite von `nxc` ist jetzt ein Golden.** Bisher sah kein Tor diesen Text an, und\nes hatte schon einmal zugebissen; wenn Verben von der Oberfläche gehen, ist eine Hilfeseite, die\neines nennt, das es nicht mehr gibt, eine falsche Karte, nach der ein Agent handelt.\n\nFassade (Bruch): `Engine::workflow_tickets_add`, `orchestration::workflow_tickets_add`,\n`WorkflowTicketsAddRequest` und `WorkflowTicketsReceipt` entfallen (und mit ihnen, in demselben\nRelease, die übrige Workflow-Oberfläche — siehe deren eigenen Eintrag). `DefinitionSource` und\n`EngineConfig::definitions` entfallen, ebenso\n`Engine::set_definitions`. `Workspace::roles_dir()` entfällt; an seine Stelle treten `workspace_root()`,\n`personas_dir()`, `legacy_roles_dir()` und `declaration_dir()` auf `ChatWorkspaceExt`.\n`Definitions::from_roles_dir` heißt jetzt `Definitions::from_dir` (liest EINEN benannten Ordner),\ndaneben steht das neue `Definitions::resolve(workspace_root)` (löst beide Orte auf und hält fest,\nwelcher gelesen wurde). `Definitions` bekommt `source()`, `len()` und `is_empty()`.\n`facade::prime`/`facade::prime_for` nehmen ein `&DeclarationSource`, wo sie ein `&Path` nahmen, und\n`facade::PrimeReport` bekommt das Pflichtfeld `declarations: DeclarationSource` — `nxc prime --json`\nträgt damit ein immer vorhandenes `declarations`-Objekt.\n- **Ein Kanal ist jetzt eine Deklaration — oder er ist nichts.** Bis zu dieser Version konnten Sie\neinen Kanal ansprechen, den nichts deklariert: einen, den Sie mit `nxc channels create` erzeugt\nhatten, oder dessen Id Sie schlicht kannten. Ein solcher Kanal ist etwas Ansprechbares ohne Politik\ndahinter — niemand hat gesagt, wer darauf antwortet, bis wann, und was aus den Antworten wird, also\nkonnte sie auch nichts weiterleiten. Genau diesen Zustand sollte der Umbau abschaffen, und hier\ngeschieht es.\n\n**`nxc channels` ist vollständig entfallen.** `create` und `dm` erzeugten Kanäle; ein Kanal wird in\n`.nxs-personas/channels.yaml` deklariert und materialisiert sich, sobald jemand zum ersten Mal an\nihn sendet, und ein Direktgespräch öffnet sich von selbst, sobald Sie `send --to `\nschreiben. `join` und `leave` schrieben Mitgliedschaft; die Mitgliedschaft steht in der `members:`-\nListe der Deklaration. `list` ist `nxc list` und liest das deklarierte Team samt seinen Kanälen.\n\n**`channels public` hat auf der Kommandozeile keinen Nachfolger, und das ist eine Entscheidung und\nkein Versehen.** Es listete die ÖFFENTLICHEN Kanäle im Speicher dieses Arbeitsbereichs — auch\nEingangstüren, die per Synchronisation aus anderen Projekten hereingekommen sind, die hier keine\nDeklaration benennt und die `nxc list` deshalb nicht zeigen kann. Die projektübergreifende Entdeckung\nverlässt die Agenten-Oberfläche; die Lesung bleibt als `Engine::public_channels` auf der\nBibliotheksnaht, damit eine Anwendung sie darstellen kann.\n\n**`nxc ask` ist entfallen.** `send --to ` öffnet die Tafel, und die Deklaration des Kanals\nsagt, wer antworten muss, bis wann und was aus den Antworten wird — ein `--expect` je Aufruf war\ndamit eine zweite Antwort auf eine Frage, die die Deklaration schon beantwortet, und geht mit dem\nVerb. `--deadline` NICHT: es ist zu **`nxc send --deadline`** gewandert und nimmt weiterhin entweder\neinen absoluten RFC3339-Zeitpunkt oder eine Dauer ``. Eine Dauer hat eine deklarierte\nForm (der `timeout:` eines Kanals), ein absoluter Zeitpunkt hat keine — den Flag wegzulassen hätte\nalso etwas weggenommen, statt zwei Eingänge zu einem zusammenzuführen.\n\n**`send` und `reply` nehmen jetzt genau ein Positionsargument: den Text.** Das Ziel ist ein Flag.\n\n- `nxc send \"\"` → `nxc send --to \"\"`\n- `nxc reply \"\"` → `nxc reply --thread \"\"`\n\n`--thread` ist Pflicht. Ein erstes Token, das in einem Aufruf \"Kanal\" und im nächsten \"Text\"\nbedeutet, war außerdem die eine Form, die Ihnen die Argumentprüfung nie abnehmen konnte.\n\n**Die Antwort auf eine einzelne NACHRICHTEN-Id entfällt mit.** `reply ` bedeutete\n\"das Gespräch, in dem diese Nachricht steht\" — und genau das sagt `--thread` unmissverständlich. Ein\nGespräch hat eine Adresse.\n\nZwei Folgen, die Sie vor dem Umstieg kennen sollten:\n\n- **`nxc reply --json` liefert eine Form.** Die positionale Form baute ihr JSON-Objekt selbst und\n trug für eine Tatsache beide Namen, `persisted` und `posted`; `reply --thread` serialisiert seit\n jeher die typisierte Quittung, und die trägt `posted`. Mit einer verbleibenden Form gibt es einen\n Namen. Alles Übrige in dieser Quittung — `thread_id`, `resumed`, `message_id`, `warnings` sowie\n der Befund `wake_skipped` — ist unverändert.\n**Eine Beschriftung hat sich mit dem Verb geändert.** `nxc ask` verstand eine Nachricht als\n`question`, solange Sie nichts anderes sagten; `nxc send` versteht sie als `info`. Eine Tafel, die\nSie bisher mit einem blossen `nxc ask \"\"` geöffnet haben, trägt jetzt also `info`, wo\n`question` stand. Das ist eine Beschriftung — das, was `search` und `inbox` Ihnen zeigen — und\nnichts leitet, wartet oder entscheidet danach. Mit `--kind question` bekommen Sie sie zurück. Der\nDefault wurde bewusst nicht mitgenommen: `ask` konnte einen haben, weil es immer nur einen Kanal\nansprach, während `send --to` eine Persona oder einen Kanal anspricht — und ein Default, der sich\ndanach richtet, als was sich Ihr Ziel herausstellt, ist genau die verborgene Unterscheidung, die\ndieses Release abschafft.\n\n**Und eine Tafel weckt ihren Anforderer weiterhin genau einmal.** Wer einen Faden beantwortet, gibt\nden Zug an die Gegenseite zurück — eine sammelnde Tafel hat aber keine Gegenseite, und ihr Anforderer\nwird vom Abschluss geweckt, einmal. Das galt bisher, weil die beiden `reply`-Formen verschiedenen\nCode erreichten; mit nur noch einer Form ist es als Regel festgeschrieben. Eine Teilantwort, eine\nnachgereichte Antwort oder eine Antwort von jemandem, der nie erwartet wurde, weckt den Anforderer\nalso nicht erneut.\n\n**Was aus Ihren bestehenden Kanälen wird.** Sie bleiben im Arbeitsbereich, und ihre Nachrichten\nbleiben über `nxc search` und `nxc inbox` lesbar — aber ein Kanal, den keine Deklaration benennt, ist\nkein Ziel mehr. `nxc send --to ` sagt das beim Namen und sagt auch, wohin eine Deklaration\ngehört, statt mit \"no such target\" zu antworten und Sie einen Tippfehler in einer Id suchen zu\nlassen, die vollkommen richtig geschrieben ist.\n- **`nxc send` hat jetzt genau ein Ziel: `--to`.** `--role` und `--session` sind entfallen, und mit\nihnen die beiden Bibliotheksmethoden dahinter — `Engine::role_trigger` und `Engine::role_resume`.\n\n**`send --role ` geht in `send --to ` auf.** Beide übergeben einer Persona eine\nAufgabe, beide laufen durch denselben Code, und seit dem vorigen Release konnte keines von beiden\nmehr einen Kanal benennen — also öffnen beide das Direktgespräch zwischen Aufrufer und Persona\nselbst. Was `--to` hinzufügt, ist der Grund für die Richtung dieses Zusammenlegens: es öffnet einen\nFADEN und sagt der Persona, in welchen sie antworten muss. `--role` postete ohne Faden, und damit\nhatten seine Antworten überhaupt keine Adresse — denn `reply --thread` ist die einzige verbliebene\nForm zu antworten. Wo `nxc send --role coder \"…\"` stand, steht künftig `nxc send --to coder \"…\"`;\nzurück kommt eine Faden-Id, und genau die braucht eine Antwort.\n\n**`send --session ` hat keinen Nachfolger, und das ist eine Entscheidung, kein Versehen.** Es\nschob einer Persona eine neue Aufgabe in die LAUFENDE Sitzung. `reply --thread` ist eine andere\nHandlung: es antwortet in einem Gespräch und erreicht die Gegenseite über die Rückadresse des Fadens\n— die neueste Nachricht, die jemand anderes dort abgelegt hat. Eine gerade erst gerufene Persona, die\nnoch nicht geantwortet hat, hat keine abgelegt; ein Nachtrag steht dann zwar im Faden, weckt aber\nniemanden, bis die Persona von sich aus antwortet. Die Zustellung in eine Sitzung mitten im Zug\nbraucht ein Signal in einen laufenden Agenten hinein, das es noch nicht gibt; sie ist als eigener\nAuftrag erfasst und kommt als `reply --thread --force`. Bis dahin gibt die Kommandozeile hier\neine Fähigkeit auf — hier benannt, statt sie entdecken zu lassen.\n\n**Für Anwendungen, die die Bibliothek einbetten:** `Engine::send_to` ist die eine Tür zu einer\nPersona. Sie nimmt ein Ziel statt eines Rollen-Handles, gibt neben der Sitzungs-Id eine Faden-Id\nzurück und hält fest, von wem der Faden eine Antwort erwartet. Für `Engine::role_resume` gibt es aus\ndem oben genannten Grund keinen Ersatz. Der Mechanismus unter beiden bleibt unangetastet — entfallen\nsind nur die flachen Handle-Methoden.\n- Die Option `--model` an `nxc send` entfällt. Für einen einzelnen Aufruf ein Modell zu wählen, ist\nnichts mehr, was auf der Kommandozeile getippt wird: Der Eingang stand ohnehin auf der Liste derer,\ndie entfallen, während die Agent-zu-Agent-Oberfläche auf das zurückgeschnitten wird, was ein Agent\nwirklich braucht (Item 6j6v.dvyq §3) — und der deklarierte Konsolidierer aus demselben Release hat\ndie Entfernung vorgezogen: Wenn ein Kanal jetzt selbst nennt, mit welchem Modell seine Antworten\ngefaltet werden, wären eine Übersteuerung je Aufruf und eine Deklaration zwei Antworten auf dieselbe\nFrage.\n\n**Auf welchem Modell eine Sitzung läuft, wird deklariert, nicht getippt.** Eine Persona sagt es mit\n`model:` (oder gröber mit `stage:`), und ein Kanal sagt es jetzt mit `summary_model:` für seine\nFaltung. An dieser Rangfolge ändert sich\nnichts, und eine Rolle ohne Modellangabe läuft unverändert auf der Voreinstellung.\n\n**Die Fähigkeit selbst ist nicht verschwunden.** Eine Anwendung, die die Engine einbettet, wählt\nweiterhin je Aufruf ein Modell — das Feld an den Anfragen, die sie ohnehin baut, ist unverändert.\nZugegangen ist nur eine handgetippte Tür.\n- Der Befehl `nxc threads expect` entfällt. Neu zu erklären, von wem ein Faden eine Antwort erwartet,\nist nichts mehr, was ein Mensch tippt: Der Befehl stand ohnehin auf der Liste der `nxc`-Kommandos,\ndie entfallen, während die Agent-zu-Agent-Oberfläche auf das zurückgeschnitten wird, was ein Agent\nwirklich braucht (Item 6j6v.dvyq §3) — und die zug-bezogene Einlösungsregel aus demselben Release hat\ndie Entfernung vorgezogen: Unter dieser Regel fragt eine Neu-Erklärung die genannten Handles erneut,\nstatt ein Board zu schließen. Das Einzige, wofür der Befehl getippt wurde, tut er also nicht mehr.\n\n**Die Fähigkeit selbst ist nicht verschwunden.** Der Schreibzugriff liegt weiterhin auf der\nBibliotheks-Naht als `facade::set_expects` — unverändert, weiterhin nur für den Öffner, mit\ndenselben Fehlern — und der Kanal-Betreuer erklärt darüber den nächsten Zug einer Rolle. Zugegangen\nist nur eine handgetippte Tür.\n\nUm ein Board zu schließen, das niemand mehr beantworten wird, lässt man die Erwartung stattdessen\nfallen: Eine Menge, die dieses Handle nicht mehr nennt, hat nichts mehr offen — und genau das lesen\ndie Freigabe der Arbeitskopie, die Einlösung per `reply --if-unanswered` und der Fristhinweis.\n- Die Befehlsgruppe `nxc workflow` entfällt, und mit ihr die deklarative Ablauf-Maschine dahinter —\n`workflow start`, `workflow step done` (samt `--outcome`), `workflow status`, `workflow list` und\n`workflow liveness`, der `workflow_runs`-Datensatz, den sie schrieben und lasen, und die\nDeklarationen `.nxs-personas/workflow.yaml` / `.nxs-personas/workflows/*.yaml`, die sie beschrieben.\nEin Arbeitsbereich, in dem diese Dateien noch liegen, hat schlicht nichts mehr, was sie liest.\n\n**Der Kanal IST der Ablauf, und dorthin ist jedes dieser Verben gegangen.** Ein deklarierter Kanal\nträgt seine Mitglieder, seine Reihenfolge (`flow: sequential`), seine Frist und seine\nZusammenführung selbst — also startet `send --to ` einen Durchlauf davon, und `reply --thread`\nbringt ihn weiter: Der Betreuer des Kanals entscheidet, was als Nächstes kommt, weshalb kein Agent\nmehr ein Verzweigungs-Token aussprechen muss. Wo ein Vorgang steht, sagt `nxc status`: `--thread` für\neinen Vorgang, `--channel` für die, die in einem Kanal begonnen haben, und ohne Argument alles, was\nhier gerade läuft.\n\n**An der Bibliotheks-Naht** entfallen `Engine::workflow_start`, `workflow_step_done`,\n`workflow_tick`, `workflow_liveness`, `workflow_status` und `workflow_list` — ebenso der\nAblauf-Ereignisstrom `workflow_events`, `workflow_event_cursor` und `subscribe_workflow`. Was eine\nApp stattdessen benutzt, gibt es bereits: `Engine::subscribe` für den „jemand hat geschrieben, lies\nneu\"-Tick und `Engine::status` dafür, wo ein Vorgang steht. `Definitions::new` nimmt zwei statt drei\nArgumente, und `PrimeRoster` führt keine `workflows`-Liste mehr.\n\n**Zwei Fähigkeiten werden aufgegeben statt ersetzt, und beide stehen hier statt gefunden zu werden.**\nDie Totmann-Überwachung eines Schritts — erst ein Anstupsen, dann eine Eskalation, wenn ein laufender\nSchritt still wurde — hing am aktuellen Schritt eines Durchlaufs und geht mit den Durchläufen; gegen\nein still gewordenes Mitglied bleibt die kanalweise Mitglieder-Frist, die bei deklariertem `timeout`\nzuschlägt, aber nicht anstupst. Und eine Nachricht erbt nicht mehr automatisch die beauftragten\nTicket-Ids eines Durchlaufs: Womit sie zu tun hat, sagt `--ref nxf_ids=` — was ohnehin immer\nVorrang vor dem automatischen Stempel hatte.\n\nDie `workflow_runs`-Tabellen werden beim nächsten Öffnen eines bestehenden Arbeitsbereichs entfernt.\nNachrichten, Fäden und Kanäle bleiben unberührt, und ein älteres `nxc`, das denselben\nArbeitsbereich danach öffnet, legt seine eigenen leeren Tabellen wieder an, statt zu scheitern. Die\neine Form, die sich nicht selbst heilt — hier gesagt, statt sie erleben zu lassen: Ein älterer\nProzess, der den Arbeitsbereich bereits offen hält, während ein neuerer die Tabellen darunter\nentfernt, stürzt ab, sobald er danach eine Workflow-Op faltet. Also den alten Prozess vor dem\nUpgrade beenden — oder, da dies ein lokales Werkzeug ist und keine Flotte, schlicht nicht zwei\nVersionen gleichzeitig gegen einen Arbeitsbereich laufen lassen.\n\n### Facade-Kontrakt\n- `breaking` · **Jeder Kanal deklariert seinen Konsolidierer** — das, was aus den Antworten der Mitglieder die eine\nAntwort macht, die der Anforderer bekommt. Es gab ihn immer, aber er war weder deklariert noch\neinstellbar: Man konnte sagen, *dass* ein Kanal zusammenfasst, und den Prompt schreiben — aber nicht,\nwelches Modell ihn ausführt; und über die andere Ausgabeform stand nirgends etwas.\n\nEs gibt zwei Ausgabeformen, und ein Kanal benennt eine davon:\n\n- **`on_complete: pass_through`** (die Voreinstellung, und das, was ein Kanal ohne Angabe bekommt)\n reicht die Antworten durch, wie sie sind. Diese Übergabe hat jetzt eine **definierte Form**: eine\n kurze Kopfzeile mit Kanal, Faden und Anzahl der gesammelten Nachrichten, danach je Nachricht ein\n abgegrenzter Block mit dem Namen dessen, der sie geschrieben hat. Das ersetzt eine Darstellung mit\n einer Zeile je Antwort, die schlicht nicht lesbar war, sobald eine Antwort mehrere Zeilen hatte —\n und für einen Agenten ist das der Normalfall. Die Blöcke selbst stehen in einem Abschnitt, der\n ausdrücklich als nicht vertrauenswürdige Daten gekennzeichnet ist, samt Hinweis, dass die\n Absenderangabe an jedem Block eine Behauptung ist und keine geprüfte Tatsache: Was ein Mitglied\n schreibt, ist freier Text, und die Gegenstelle ist ein Agent, der sonst alles davon als Anweisung\n der eigenen Seite liest. Es ist dieselbe Kennzeichnung, die ein zusammenfassender Kanal schon\n bisher um die Antworten legt, die er seinem Modell übergibt.\n- **`on_complete: summarize`** lässt ein Modell über die Antworten laufen. Der Prompt war schon als\n `summary_prompt:` deklarierbar; das Modell jetzt ebenfalls, als **`summary_model: fable | opus |\n sonnet`** — dieselben drei Namen, die auch eine Persona verwendet. Ein zusammenfassender Kanal ohne\n Modellangabe faltet auf der Stufe `junior` (`sonnet`) statt auf einer stillschweigenden\n Voreinstellung; die Frage \"welches Modell hat das zusammengefasst?\" lässt sich also nachschlagen\n statt erraten. Ein `summary_model:` an einem Kanal, der gar nicht zusammenfasst, wird als Fehler in\n der Deklaration gemeldet.\n\n**Ein Kanal fasst ein \"ich kann nicht\" niemals weg.** Hat ein Mitglied mit `--escalate` geantwortet,\nreicht der Kanal die Antworten durch, wie sie sind — kein Modell läuft darüber — und sagt es in einer\neigenen Zeile darüber. Damit ist die Lücke geschlossen, die das vorige Release offengelegt hat: Eine\nEskalation drang bisher nur auf einem durchreichenden Kanal nach oben vor und blieb an einem\nzusammenfassenden stehen. Jetzt dringt sie auf beiden vor, und \"bei mir ist keine Eskalation\nangekommen\" ist auf jedem Kanal wieder eine echte Auskunft.\n\n**Wo das ändert, was ein GEORDNETER Kanal tut — und das tut es:** Ein Schritt eines\n`flow: sequential`-Kanals, der Arbeit an einen weiteren Kanal übergibt, wartet auf dessen Urteil, und\nein eskalierender Kanal liefert keines. Worauf der Betreuer verzweigt, ist die Eskalation selbst —\nein deklariertes Signal statt eines Tokens, das sich jemand ausdenken musste. Der Ablauf schaltet\nalso nicht weiter, als wäre die Arbeit getan, und die Eskalation bleibt an dem Kanal lesbar, an dem\nsie passiert ist.\n\n**Zwei Aufrufer, die denselben Kanal im selben Augenblick abschließen, stellen jetzt einmal zu.**\nTraf eine abschließende Antwort mit einer Fristprüfung zusammen, konnte ein durchreichender Kanal dem\nAnforderer dieselben Antworten zweimal übergeben; ein zusammenfassender war bereits geschützt. Beide\nsichert jetzt dieselbe Mechanik.\n\nBestehende Kanal-Deklarationen sind nicht betroffen: Jedes Feld ist optional, eine vor diesem Release\ngeschriebene Deklaration wird unverändert gelesen und verhält sich unverändert, und ein Kanal, der\nüber seinen Konsolidierer nichts sagt, hat trotzdem einen.\n- `breaking` · **Ein Kanal kann jetzt seinen ABLAUF deklarieren**, und `nxc send --to ` startet ihn. Wer\n`flow: sequential` an einen Kanal in `channels.yaml` schreibt, macht aus der Reihenfolge seiner\n`members` statt einer Aufzählung eine feste Reihenfolge: der Betreuer des Kanals startet den ersten\nSchritt allein, und jeden weiteren erst, wenn der vorige geantwortet hat oder seine Frist zugeschlagen\nist. Der Standard bleibt unverändert — ein Kanal ohne `flow` startet weiterhin alle Mitglieder auf\neinmal.\n\n**Ein Schritt dieses Ablaufs darf einen ganzen KANAL adressieren, nicht nur eine Persona** — dieselben\nzwei Dinge, die `nxc send --to ` immer schon erreicht hat. Ein als Schritt genannter Kanal\nbekommt seinen eigenen Kanal-Faden unterhalb des Ablaufs, führt seine eigenen Mitglieder und seinen\neigenen Konsolidierer aus und gibt dessen Ergebnis als Antwort dieses Schritts zurück. Tragen eine\nRolle und ein Kanal denselben Namen, gewinnt die ROLLE — jede vor diesem Release geschriebene\n`members:`-Liste bedeutet damit genau das, was sie bedeutet hat.\n\nZwei Regeln weisen einen Ablauf ab, der nicht laufen kann. Ein `sequential`-Kanal darf denselben\nSchritt nicht zweimal nennen (eine Wiederholung wäre eine Schleife, und es gibt kein deklariertes\nSignal, das eine solche verzweigen könnte), und kein Kanal-Ablauf darf über seine Mitglieder SICH\nSELBST erreichen — Letzteres wird beim Laden der Deklarationen abgelehnt und nicht bloß gemeldet,\ndenn ein solcher Ablauf verschlechtert sich nicht, sondern öffnet bei jedem Sprung einen Faden und\nstartet eine bezahlte Sitzung, bis die Tiefenbremse ihn stoppt.\n\n**Genau das machte die Gruppe `nxc workflow` im selben Release entfernbar.** Eine feste Reihenfolge\naus einem Rollen-Schritt und einem Kanal-Schritt — die Form, die der deklarierte Ablauf des\nmitgelieferten Beispiels hatte — läuft hier in derselben Reihenfolge, ohne Lauf-Datensatz dahinter\nund allein mit den beiden Mutationsverben `send --to` und `reply --thread`.\n\nWeder `--expect` noch ein deklariertes `expects: ` lässt sich mit `flow: sequential`\nkombinieren. Beide entscheiden, wer antwortet und in welcher Reihenfolge — was auf einem geordneten\nKanal bereits `members` entscheidet. Die Teilmenge ist dabei die gefährlichere von beiden, denn sie\nist es, die die Engine tatsächlich liest, wenn eine da ist: ein geordneter Kanal mit\n`expects: [b, a]` wäre in deren Reihenfolge gelaufen, während seine `members` etwas anderes sagten.\nBeides wird ausdrücklich abgewiesen statt still zugunsten der anderen Liste befolgt — auf einem\ngeordneten Kanal ist `members` damit die einzige Liste, die es gibt.\n- `breaking` · Der `timeout` eines deklarierten Kanals ist jetzt eine Frist JE MITGLIED, und jede Transkript-Ausgabe\ndieses Mitglieds setzt sie zurück.\n\nBisher erzeugte der `timeout` einen einzigen absoluten Zeitpunkt, der jedem Mitglied des Kanals\naufgestempelt wurde und danach stur bis zum Ende lief: ein Kanal mit `timeout: 30m` gab seine\nMitglieder dreißig Minuten nach dem Öffnen auf, ganz gleich, was sie gerade taten. Genau den Fall, den\neine Frist überleben soll, macht das kaputt — ein Agent, der einen Shell-Befehl startet, der eine\nStunde läuft, ist in Minute dreißig kerngesund, und ihn dort abzuschneiden vernichtet eine Stunde\nArbeit und liefert dem Anforderer eine Antwort, in der ausgerechnet das Mitglied fehlt, das wirklich\ngearbeitet hat.\n\n`timeout: 30m` heißt jetzt \"dreißig Minuten STILLE von diesem Mitglied\". Hinter jedem Mitglied eines\nKanals steht genau eine Sitzung, und jeder Schreibvorgang in deren Transkript — ein Gedanke, ein\nWerkzeugaufruf, ein Ergebnis — startet das Fenster von diesem Moment an neu. Ein Mitglied, das\nweiterhin Lebenszeichen gibt, wird nie aufgegeben, wie lange seine Arbeit auch dauert; ein Mitglied,\ndas über das ganze Fenster keines gibt, läuft weiterhin ab, und der Kanal fasst zusammen, was er hat,\nund vermerkt, wer nicht geantwortet hat. Die Uhren sind unabhängig: läuft eine ab, betrifft das genau\ndieses Mitglied und sonst niemanden.\n\nDrei kleinere Folgen derselben Änderung. Ein erneut gefragtes Mitglied — ein zweiter Zug im selben\nKanal — startet sein Fenster wieder ab dem Moment der Frage, statt eine längst abgelaufene Frist zu\nerben und vorbei zu sein, bevor es begonnen hat; die Nachprüfung, die es am Ende aufgibt, wird\nzusammen mit diesem Fenster eingeplant, sodass auch ein zweiter Zug automatisch aufgegeben wird und\nnicht nur der erste. Ein Mitglied, das erst auf die Arbeitskopie warten muss, beginnt sein Fenster,\nwenn es wirklich anfängt, statt aufgegeben zu werden, während es noch in der Warteschlange steht.\nUnd die eingeplante Nachprüfung eines Kanals erzwingt keine Zusammenfassung mehr, bloß weil der\nZeitpunkt erreicht ist, für den sie eingeplant war — sie findet die Mitglieder lebendig, lehnt ab\nund plant sich selbst auf den nächsten Zeitpunkt neu ein, an dem eines von ihnen fällig wird. Das\nautomatische Aufgeben passiert also weiterhin; es passiert jetzt nur zur richtigen Zeit.\n\nEin unlesbarer `timeout` (`timeout: twenty-minutes`) wird jetzt mit einem Fehler abgelehnt, der das\nKanal-Feld und den Wert nennt, bevor irgendetwas geöffnet wird — statt weiter unten als\nArgumentfehler über ein `--deadline` aufzutauchen, das niemand getippt hat. Ein `--deadline` als\nabsoluter Zeitpunkt bleibt unverändert und bewusst stur: wer einen Zeitpunkt nennt, meint diesen\nZeitpunkt, und nichts verschiebt ihn.\n- `breaking` · Ein deklarierter Kanal hat jetzt ZWEI EBENEN, und jeder Faden darin hat genau zwei Enden. Bisher\nstempelte `nxc send --to ` EINEN Faden, schrieb sämtliche Mitglieder in dessen\n`expects_reply_from` und startete alle auf genau diesem Faden: Anforderer und alle drei Reviewer\nsaßen in einer Unterhaltung, jeder las die Antwort jedes anderen, und der Normalfall des Datenmodells\nwar ein Faden mit N+1 Enden.\n\nEin `send --to ` erzeugt jetzt den KANAL-FADEN — der Anforderer an einem Ende, der BETREUER\ndes Kanals am anderen — und darunter je Mitglied einen eigenen Faden. Jeder Mitglieder-Faden hat den\nBetreuer an einem Ende und genau dieses eine Mitglied am anderen. Drei Reviewer sind vier Fäden,\nnicht einer, und die Reviewer sehen einander nicht — weil sie nicht in derselben Unterhaltung sitzen,\nnicht weil ein Filter etwas verbirgt.\n\n**Der Betreuer ist Maschinerie der Engine**, nie ein Mensch und auch keine deklarierte Rolle. Er ist\ndie reservierte Identität `/__channel__` und tut dreierlei: er nimmt den eingehenden Auftrag\nentgegen (ein `send --to ` und jedes spätere `reply` in den Kanal-Faden) und öffnet die\nMitglieder-Sends selbst; er wartet auf die MENGE der Mitglieder-Fäden statt auf die Namensliste eines\neinzelnen Fadens; und wenn alle geantwortet haben — oder ihre Fristen zugeschlagen sind — führt er\ndie deklarierte `on_complete`-Regel des Kanals aus und liefert das Ergebnis an den Anforderer. Er\nKANN von einer Agenten-Sitzung besetzt werden: genau das ist `on_complete: summarize`, und die\nAntwort dieser Sitzung ist es, die den Kanal-Faden einlöst.\n\nDas alles läuft SYNCHRON, im Schreibpfad des `reply`, das es ausgelöst hat: Nachricht schreiben,\nVerpflichtung als eingelöst erkennen, Betreuer laufen lassen, nächste Fäden öffnen und die Sitzungen\nSTARTEN — und erst dann kehrt der Aufruf zurück. Gewartet wird auf das Starten, nie auf das Ergebnis.\nDer Grund ist nicht die Latenz, sondern dass nur hier eine gescheiterte Folge noch einen Adressaten\nhat: asynchron ist die antwortende Sitzung längst beendet, wenn ihre Folge läuft, und ein Fehlschlag\nfällt niemandem auf.\n\nZwei Dinge sieht ein Aufrufer anders. `nxc send --to --json` meldet `expects:\n[\"/__channel__\"]` — das andere Ende des Kanal-Fadens —, und die Erwartungen je Mitglied\nliegen eine Ebene tiefer, eine je Mitglieder-Faden, wo `nxc status --thread ` sie zeigt. Und ein\nMitglied antwortet in SEINEM EIGENEN Faden: die Id in der Nachricht, mit der ein Mitglied gestartet\nwird, ist seine eigene, und nichts verweist ein Mitglied je auf den Kanal-Faden.\n\nDer Zug eines Mitglieds wird jetzt auch richtig WIEDER GEÖFFNET. Ein `reply` in den Kanal-Faden durch\ndessen Anforderer ist dieselbe Tür wie die erste Nachricht: der Betreuer deklariert die Erwartung\njedes Mitglieds auf dessen vorhandenem Faden neu und setzt dessen Sitzung samt Verpflichtung fort —\ndamit `nxc reply --thread --if-unanswered`, der Aufruf, den eine Agenten-Sitzung beim\nHerunterfahren macht, ab dem zweiten Zug etwas einzulösen hat. Vorher deklarierte überhaupt nichts\neine Erwartung bei einer Fortsetzung neu, und dieses Sicherheitsnetz war ab Zug zwei ein stiller\nLeerlauf.\n\nDamit trägt ein Kanal einen ganzen Ablauf ohne jeden Lauf-Datensatz dahinter — und genau deshalb\nkonnte die Gruppe `nxc workflow` in demselben Release entfallen.\n- `breaking` · **Die Antwort, die eine Tafel abgeschlossen hat, ist jetzt die, die sie wirklich abgeschlossen hat.**\nSobald jeder Beteiligte geantwortet hat, auf den ein Faden gewartet hat, wird der Anforderer mit den\ngesammelten Antworten geweckt, und eine davon ist als die abschließende gekennzeichnet — die Antwort,\ndie den Zug beendet hat. Diese Kennzeichnung wurde über einen Vergleich der Nachrichten-Ids gewählt.\nEine Nachrichten-Id beginnt mit der Millisekunde, in der sie erzeugt wurde; zwei Antworten aus\nderselben Millisekunde unterscheiden sich also nur noch im Zufallsteil dahinter, und die\nKennzeichnung fiel auf diejenige, die zufällig höher sortierte. Zwischen zwei synchronisierten\nGeräten können die Ids der tatsächlichen Reihenfolge sogar beliebig weit widersprechen, weil jedes\nGerät seine eigenen stempelt.\n\nDie abschließende Antwort wird jetzt nach derselben kausalen Ordnung bestimmt, in der auch der Rest\neines Fadens gelesen wird — der Reihenfolge, in der die Nachrichten selbst erscheinen, der\nReihenfolge, aus der `nxc status` die letzte Antwort meldet, und der, die entscheidet, ob die letzte\nAntwort eine Eskalation war. Diese drei waren sich längst einig; diese eine Lesestelle war es nicht,\nund sie war die letzte im Nachrichtenspeicher, die noch nach der rohen Id ordnete.\n\nWas sich für Sie ändert: Bei einem Faden, dessen Antworten dicht beieinander eintreffen — der\nNormalfall, wenn die Mitglieder eines Kanals gleichzeitig fertig werden —, kann die als abschließend\nhervorgehobene Antwort und die daneben gemeldete Nachrichten-Id von dem abweichen, was eine frühere\nVersion für exakt dasselbe Gespräch gemeldet hat. Die neue Antwort ist die richtige.\n\nUnverändert, und zwar mit Absicht: Ob eine abgeschlossene Tafel noch Ihre Aufmerksamkeit braucht,\nentscheidet weiterhin Ihr Lesezeiger auf die gewohnte Weise — es kommt also nichts zurück, was Sie\nschon gelesen hatten.\n- `breaking` · **Eine ausgebliebene Konsequenz hat jetzt einen definierten Ort, an dem sie auftaucht.** Bisher galt:\nKam eine Antwort an, blieb aber das aus, was sie hätte auslösen sollen — ein Kanalmitglied, das nicht\ngestartet werden konnte, ein Anforderer, der nie geweckt wurde —, dann bekam die antwortende Seite\neine saubere Quittung, und der Fehlschlag ging auf stderr, wo ihn keine App und kein Agent sieht, der `--json` liest. Ausgerechnet die Seite,\ndie etwas hätte tun können, erfuhr als Einzige nichts davon.\n\nJede Quittung trägt jetzt `warnings`: eine Liste dessen, was genau dieser Aufruf hätte auslösen\nsollen und nicht ausgelöst hat — je Eintrag mit Klasse (`step_skipped`, `requester_not_woken`),\nbetroffenem Faden und betroffener Sitzung, dem Grund und dem zugrundeliegenden Fehler im Klartext. Das Feld ist immer da, auch leer, damit man es nicht dadurch\nübersieht, dass man nicht hinschaut. Weil die Konsolidierung eines Kanals im selben Schreibvorgang\nläuft wie die Antwort, die sie auslöst, erfährt die antwortende Sitzung davon **im selben Aufruf,\nwährend sie noch läuft** — `nxc --json reply` zeigt es, `Engine::reply` gibt es zurück.\n\nEin Ding, das damit nicht mehr bloß leise, sondern gar nicht mehr verloren ist: Ein Kanal, der eine\nzweite Runde Arbeit verteilt, meldete bisher nur das ERSTE Mitglied, das er nicht starten konnte;\njetzt wird jedes einzelne genannt.\n\n**`nxc status` unterscheidet tote Enden jetzt schärfer.** Ein beantworteter Faden, aus dem nichts\nfolgte, galt bisher als VERWAIST, sobald der Faden darüber nichts mehr schuldete — was auf einen\nperfekt abgeschlossenen Zweig genauso zutrifft. Verwaist bleibt er jetzt nur, wenn der Faden darüber\nnach dieser Antwort nicht weitergegangen ist, und nie unterhalb einer gewöhnlichen Unterhaltung, die\nniemanden gefragt hat. Die Regel irrt lieber Richtung Schweigen als Richtung Fehlalarm: ein Ort, der\nzu oft Alarm schlägt, ist ein Ort, den niemand liest.\n\nDie Quittung eines fehlgeschlagenen Triggers nennt außerdem den GRUND als Wert statt als Satz und\nunterscheidet damit eine Laufzeit, die den Start verweigert hat (Wiederholen sinnvoll), von einer\nSitzung, die die Laufzeit längst eingesammelt hat (braucht eine neue). `warnings`-Einträge waren\nbisher einfache Zeichenketten und sind jetzt Datensätze; der Satz, der sie waren, ist das Feld\n`detail` jedes Eintrags, und die Ausgabe im Terminal bleibt unverändert.\n- `breaking` · Zwei Prozesse, die gleichzeitig an einem Arbeitsbereich arbeiten, können einander keine Schreibvorgänge\nmehr verlieren.\n\nDie logische Uhr, die jede Änderung ordnet, wurde bisher einmal gelesen — beim Öffnen des\nArbeitsbereichs — und sah danach nie wieder in die Datei. Zwei Prozesse, die vom selben Stand\nstarteten, prägten ihren Änderungen dieselbe Zahl auf, und die Zusammenführungsregel, die eine\nÄnderung nur behält, wenn sie das Verzeichnete echt übertrifft, verwarf die zweite daraufhin\nwortlos: Der Befehl meldete Erfolg, Exit-Code 0, und das Brett zeigte weiter den alten Wert. Eine\nlanglebige App, die den Arbeitsbereich neben kurzlebigen `nxf`/`nxc`/`nxm`-Aufrufen offen hält,\ndriftete auf dieselbe Weise ab — und mit jeder Minute weiter.\n\nGenau so ist diese Plattform gedacht — mehrere Agenten in einem Arbeitsbereich —, und genau deshalb\nblieb der Fehler unentdeckt: Die Testsuite läuft vollständig in EINEM Prozess, wo ein Handle jede\nÄnderung beobachtet und die Divergenz gar nicht entstehen kann. Betroffen war jedes\nLast-Write-Wins-Feld, auf dem Brett wie in Chat und Memory. Und es war nicht theoretisch: Am Tag der\nReparatur trugen drei der zwanzig Arbeitsbereiche auf der Entwicklungsmaschine seine Spuren, das\nnexus-flow-Brett darunter.\n\nJede Änderung liest den Höchststand des Arbeitsbereichs jetzt im Moment des Schreibens neu und hält\ndie Schreibsperre über Lesen und Schreiben hinweg — eine Änderung wird also immer über allem\ngestempelt, was schon in der Datei steht, gleich welcher Prozess es hineingeschrieben hat. Darunter\nweist das Protokoll einen doppelten Stempel jetzt rundheraus ab, statt ihn anzunehmen und der\nZusammenführungsregel zu überlassen, ihn still zu verwerfen. Ein Arbeitsbereich, der aus der Zeit\ndavor bereits Doppelstempel trägt, öffnet und arbeitet unverändert; ihm lässt sich die zweite\nZusicherung nur nicht rückwirkend geben — blockiert oder umgeschrieben wird er dafür nie.\n\nFassade (Bruch): Keine Signatur bewegt sich, ein dokumentiertes Verhalten schon.\n`nxs_foundation::store::Store::apply` liefert die Ops zurück, die es nicht in die Sichten gefaltet\nhat; dieser Vektor trägt jetzt auch eine übernommene Op, die das Protokoll ABGEWIESEN hat, weil eine\nandere Op deren Koordinate `(lamport, site)` bereits hält. Zuvor wurde eine solche Op gespeichert\nund danach still überstimmt. Ein Konsument, der den Vektor strikt als „Op-Formen, die diese Version\nnicht versteht\" liest, sieht darin eine neue Art von Eintrag. Erreichbar ist das nur, wenn zwei\nRepliken sich eine `site_id` teilen — was 63 Bit Entropie für echte Repliken ausschließen und was\ndie Präfix-Registry ohnehin als reparierbare Fehlkonfiguration behandelt —, in der Praxis sieht also\nkein korrekter Aufrufer eine Änderung. `Store::emit` bekommt die entsprechende Zusicherung für eine\nlokal geprägte Op; sie ist unerreichbar, sobald die Uhr unter der Schreibsperre aufgefrischt wird.\n- `breaking` · **Deklarationen ziehen nach `.nxs-personas/` um.** Wer Ihre Agenten sind und wo sie sprechen, wird\nab jetzt in `/.nxs-personas/` deklariert — eine `.yaml` je Persona,\nund `channels.yaml` für die gemeinsamen Kanäle, worin auch ein Ablauf deklariert wird. Der alte Ordner\n`/roles/` wird weiterhin GELESEN, ein bestehendes Projekt läuft also unverändert\nweiter, und die Engine sagt jetzt laut, dass sie ihn gelesen hat: `nxc list` und `nxc prime` nennen\nden alten Ordner und den Pfad, an den die Deklarationen gehören, und `nxc prime --json` trägt\ndieselbe Tatsache als Daten. **Es wird nichts für Sie verschoben.** Eine Anwendung, die ein fremdes\nProjekt nur geöffnet hat, darf es nicht still umschreiben — das ist eine unumkehrbare Änderung am\nEigentum eines Dritten, ausgelöst durch bloßes Hinsehen. Der Umzug bleibt deshalb etwas, das Sie\nabsichtlich tun: Ordner kopieren, und `.nxs-personas/` übernimmt in dem Moment, in dem es selbst eine\nDeklaration trägt.\n\n**Woher ein Katalog stammt, gehört jetzt zur Antwort.** `Engine::definitions()` gelingt weiterhin in\neinem Arbeitsbereich, der nichts deklariert — eine Lesung, die scheitert, WEIL es nichts zu lesen\ngibt, ist eine schlechte Lesung, und sie machte aus einem legitimen Zustand eine Ausnahme, die eine\nApp nur abfangen müsste, um einen Einstiegsbildschirm zu zeichnen. Der zurückgegebene Katalog trägt\ndie Auflösung jetzt aber daneben: den Pfad, an den eine Deklaration gehört, den tatsächlich gelesenen\nOrdner (`.nxs-personas/`, ein altes `roles/`, oder keiner), ob überhaupt ein alter Ordner gefunden\nwurde, und die Anzahl. Eine Struktur beantwortet sowohl „woher kommt das hier\" als auch „liegt dieses\nProjekt noch im alten Format\".\n\n**Der leere Arbeitsbereich hört auf, anonym zu sein**, und jede Oberfläche antwortet in ihrem eigenen\nRegister. `nxc list` und `nxc prime` sind ORIENTIERUNG: Sie scheitern nicht, sie sagen, dass nichts\ndeklariert ist und wo eine Deklaration hingehört, und enden mit 0 — eine leere Liste allein ist eine\nLüge durch Auslassung, ein Fehler wäre falsch. `nxc send --to ` ist das eine, das wirklich\nablehnt: Es ist eine Handlung mit Pflichtziel, und ohne Deklaration gibt es keines. Es scheitert\nbenannt, statt ein fehlendes Ziel zu melden, in dem der Aufrufer dann einen Tippfehler suchen würde.\n\n**`nxc init` lässt den Ordner da**, leer, mit einer kurzen Erklärung der Form darin und ohne\nmitgelieferte Personas. Ein vorhandenes `roles/` wird nie angefasst, und das leere `.nxs-personas/`\nverdeckt es auch nicht: Der neue Ort gewinnt erst, sobald er selbst eine Deklaration trägt — ein\n`init` in einem bestehenden Projekt kann es also nicht still entteamen. Ein zweites `init` lässt eine\nvon Ihnen bearbeitete Erklärung stehen.\n\nDie beiden mitgelieferten Beispiel-Teams ziehen mit um:\n`crates/chat/examples/role-runtime-v2/.nxs-personas/` und\n`crates/chat/examples/role-runtime-v3/.nxs-personas/`.\n\n**Der Injektionsweg entfällt mit ihm.** `EngineConfig` nimmt keine Deklarationsquelle mehr, und\n`Engine::set_definitions` gibt es nicht mehr: Personas und Kanäle kommen für eine einbettende App\ngenauso aus `.nxs-personas/` wie für die Kommandozeile, und auch ein späterer Rollen-Editor in der\nApp schreibt in diesen Ordner statt in eine app-eigene Datenbank. Die Eigenschaft, für die es den\nInjektionsweg gab, bleibt unverändert und kostet jetzt nichts mehr: Eine Änderung ist beim nächsten\nAufruf auf dem Handle und auf jedem bestehenden Klon davon sichtbar, ohne Neuöffnen und ohne\nabgerissenes Abonnement — weil nichts zwischengespeichert wird. **`Engine::definitions()` — die\nLESUNG — bleibt**, und ein Tor hält sie dort fest, statt es der Sorgfalt zu überlassen:\n`crates/chat/tests/seam_disposition.rs` nennt sie samt Begründung und wird rot, wenn sie verschwindet.\n\n**Drei Eingänge verlassen die Oberfläche, und der Sitzungsstart-Block wird auf das neu\nzugeschnitten, was bleibt.** `nxc agents list` / `register` / `search` entfallen: Ein Team wird\nDEKLARIERT, nicht registriert — eine Persona ist eine Datei in `.nxs-personas/`, lesbar, prüfbar\nund den Lauf überdauernd, während eine Laufzeit-Profilzeile in genau einer Arbeitsbereichs-\nDatenbank lag und niemand sie je geprüft hat. `nxc list` ist die Lesung darüber und trägt die\nbeiden Felder, die `agents search` durchsuchte. `nxc workflow tickets add` entfällt ersatzlos — wie in\ndemselben Release die ganze Gruppe `nxc workflow`, unter der es hing. Womit eine Nachricht zu tun\nhat, sagt `--ref nxf_ids=` an ihr. Und `nxc inbox` / `nxc read` gehen von der AGENTEN-Oberfläche runter, bleiben aber\nfür einen Menschen und auf der App-Naht: Was eine Sitzung bei ihrem Start GELEHRT bekommt, ist\njetzt genau `list`, `send --to`, `reply --thread`, `nxc status`, `search` und `transcript show` —\neine Persona bekommt ihr Ungelesenes bei Sitzungsstart und bei Fortsetzung, statt danach zu fragen.\n\n**Ein Tor hält jetzt fest, was beim Wegnehmen mit der Bibliotheksnaht geschieht.** Für jedes von\nder Kommandozeile genommene Verb ist entschieden und aufgeschrieben, ob die zugehörige Lesung oder\nSchreibung auf `Engine` bleibt — und das Tor hält beide Hälften: Eine Wegnahme kann nicht landen,\nohne dass der Datensatz mitgeht, und eine Lesung, die eine App zeichnet, kann nicht still mit dem\nVerb verschwinden, das sie erreichte.\n\n**Und jede `--help`-Seite von `nxc` ist jetzt ein Golden.** Bisher sah kein Tor diesen Text an, und\nes hatte schon einmal zugebissen; wenn Verben von der Oberfläche gehen, ist eine Hilfeseite, die\neines nennt, das es nicht mehr gibt, eine falsche Karte, nach der ein Agent handelt.\n\nFassade (Bruch): `Engine::workflow_tickets_add`, `orchestration::workflow_tickets_add`,\n`WorkflowTicketsAddRequest` und `WorkflowTicketsReceipt` entfallen (und mit ihnen, in demselben\nRelease, die übrige Workflow-Oberfläche — siehe deren eigenen Eintrag). `DefinitionSource` und\n`EngineConfig::definitions` entfallen, ebenso\n`Engine::set_definitions`. `Workspace::roles_dir()` entfällt; an seine Stelle treten `workspace_root()`,\n`personas_dir()`, `legacy_roles_dir()` und `declaration_dir()` auf `ChatWorkspaceExt`.\n`Definitions::from_roles_dir` heißt jetzt `Definitions::from_dir` (liest EINEN benannten Ordner),\ndaneben steht das neue `Definitions::resolve(workspace_root)` (löst beide Orte auf und hält fest,\nwelcher gelesen wurde). `Definitions` bekommt `source()`, `len()` und `is_empty()`.\n`facade::prime`/`facade::prime_for` nehmen ein `&DeclarationSource`, wo sie ein `&Path` nahmen, und\n`facade::PrimeReport` bekommt das Pflichtfeld `declarations: DeclarationSource` — `nxc prime --json`\nträgt damit ein immer vorhandenes `declarations`-Objekt.\n- `breaking` · Ein Vorgang ist jetzt ein FADENBAUM, und `nxc status` liest ihn. Bisher verband nichts im Datenmodell\nzwei Fäden: eine echte Kette — Sie beauftragen den PM, der PM beauftragt den Coding-Kanal, dessen\nLeitung gibt die Arbeit an einen Entwickler, das Review dazu fächert auf drei Prüfer auf — waren fünf\nbis sieben einzelne Fäden über drei Kanäle, und niemand konnte beantworten: \"Wo stehen wir hier\neigentlich?\"\n\nJeder Faden merkt sich jetzt den Faden, AUS DEM HERAUS er geöffnet wurde. Die Regel ist mechanisch\nund verlangt vom Agenten nichts: ein `nxc send --to ` aus einer laufenden Sitzung\nhängt den neuen Faden unter den Faden, in dem diese Sitzung gerade steht — den, auf den sie\ngetriggert wurde, oder den, dessen Antwort sie geweckt hat. Ein Send ohne Sitzung drumherum (Sie, am\nTerminal) öffnet eine WURZEL, und genau das macht sie zum Anfang einer Kette. Gespeichert wird dafür\nnichts: die Wurzel ist das Fehlen einer Kante.\n\n`nxc status` beantwortet \"wo stehen wir\" in drei Formen. `nxc status --thread ` zeigt den ganzen\nBaum des Vorgangs, zu dem dieser Faden gehört, von der Wurzel aus und über alle Kanalgrenzen hinweg;\n`nxc status --channel ` listet die laufenden Vorgänge, die in einem Kanal BEGONNEN haben, als\nEinstieg; `nxc status` ohne alles zeigt, was im Arbeitsbereich noch läuft. `--json` trägt denselben\nDatensatz, und dieselbe Lesung liegt als `Engine::status` auf der Bibliotheks-Naht — eine App baut\nihre Vorgangsansicht daraus, statt die Ableitung nachzubauen. Es ist der Ersatz für\n`workflow status` und `workflow list`, die dasselbe Release entfernt.\n\nJeder Faden meldet einen von drei abgeleiteten Zuständen: **offen** (hier schuldet noch jemand eine\nAntwort), **beantwortet** und **VERWAIST** — beantwortet, nichts daraus geöffnet, oberhalb wartet\nniemand mehr darauf, und der Faden darüber ist auch nicht weitergegangen. Der verwaiste Zustand ist\nder Ort, an dem eine still ausgebliebene Konsequenz sichtbar wird, ohne dass sie jemand meldet; die\nletzte dieser Bedingungen ist es, die ihn auf die GESCHEITERTEN Zweige beschränkt statt auf die\nfertigen — beschrieben im Eintrag über den Ort für gescheiterte Konsequenzen. Eine bewusste Ausnahme ganz oben: ein\nbeantworteter WURZEL-Faden meldet `awaiting you`, statt wie ein Faden zu lesen, an dem niemand mehr\narbeitet. Das ist das normale Ende eines Vorgangs, kein Stillstand — und die einzige Stelle, an der\nim Ablauf überhaupt ein Mensch vorkommt.\n\nJeder Faden trägt außerdem die Sitzung dessen, der gerade daran arbeitet: Wer eine Faden-Kennung hat,\nkann damit das Transkript des Beauftragten mitlesen, ohne dessen Sitzungskennung je erfahren zu haben\n— `nxc status --json` nennt die Sitzung, `nxc transcript show ` zeigt den laufenden Verlauf.\n**Das funktioniert, solange der Faden offen ist und der Beauftragte noch nichts gesagt hat** — genau\ndafür gibt es das: Ein Gespräch, das still geworden ist, ist der Moment, in dem man sehen will, ob die\nGegenseite denkt, hängt oder weg ist. Ein offener Faden nennt den, auf den er noch wartet; ein\nabgeschlossener den, der zuletzt geantwortet hat. Aufgezeichnet wird für beides nichts Neues — das\nErste steht dort, wo der Trigger diese Sitzung hingestellt hat, das Zweite ist die Rückadresse, die\nihre Nachricht ohnehin trägt. Die Lesung ist für jeden Aufrufer dieselbe; eine Unterscheidung zwischen\nMensch und Agent gibt es darin nirgends.\n\nGespeichert wird für all das nichts Neues. Der Status wird aus den Faden-Kanten, den\nAntwort-Erwartungen und den Rückadressen gerechnet, die ohnehin im Protokoll stehen — es gibt keinen\nVorgangs-Datensatz, und nichts tritt an die Stelle des Lauf-Datensatzes, den dieses Release\nentfernt.\n- `breaking` · **Ein Kanal ist jetzt eine Deklaration — oder er ist nichts.** Bis zu dieser Version konnten Sie\neinen Kanal ansprechen, den nichts deklariert: einen, den Sie mit `nxc channels create` erzeugt\nhatten, oder dessen Id Sie schlicht kannten. Ein solcher Kanal ist etwas Ansprechbares ohne Politik\ndahinter — niemand hat gesagt, wer darauf antwortet, bis wann, und was aus den Antworten wird, also\nkonnte sie auch nichts weiterleiten. Genau diesen Zustand sollte der Umbau abschaffen, und hier\ngeschieht es.\n\n**`nxc channels` ist vollständig entfallen.** `create` und `dm` erzeugten Kanäle; ein Kanal wird in\n`.nxs-personas/channels.yaml` deklariert und materialisiert sich, sobald jemand zum ersten Mal an\nihn sendet, und ein Direktgespräch öffnet sich von selbst, sobald Sie `send --to `\nschreiben. `join` und `leave` schrieben Mitgliedschaft; die Mitgliedschaft steht in der `members:`-\nListe der Deklaration. `list` ist `nxc list` und liest das deklarierte Team samt seinen Kanälen.\n\n**`channels public` hat auf der Kommandozeile keinen Nachfolger, und das ist eine Entscheidung und\nkein Versehen.** Es listete die ÖFFENTLICHEN Kanäle im Speicher dieses Arbeitsbereichs — auch\nEingangstüren, die per Synchronisation aus anderen Projekten hereingekommen sind, die hier keine\nDeklaration benennt und die `nxc list` deshalb nicht zeigen kann. Die projektübergreifende Entdeckung\nverlässt die Agenten-Oberfläche; die Lesung bleibt als `Engine::public_channels` auf der\nBibliotheksnaht, damit eine Anwendung sie darstellen kann.\n\n**`nxc ask` ist entfallen.** `send --to ` öffnet die Tafel, und die Deklaration des Kanals\nsagt, wer antworten muss, bis wann und was aus den Antworten wird — ein `--expect` je Aufruf war\ndamit eine zweite Antwort auf eine Frage, die die Deklaration schon beantwortet, und geht mit dem\nVerb. `--deadline` NICHT: es ist zu **`nxc send --deadline`** gewandert und nimmt weiterhin entweder\neinen absoluten RFC3339-Zeitpunkt oder eine Dauer ``. Eine Dauer hat eine deklarierte\nForm (der `timeout:` eines Kanals), ein absoluter Zeitpunkt hat keine — den Flag wegzulassen hätte\nalso etwas weggenommen, statt zwei Eingänge zu einem zusammenzuführen.\n\n**`send` und `reply` nehmen jetzt genau ein Positionsargument: den Text.** Das Ziel ist ein Flag.\n\n- `nxc send \"\"` → `nxc send --to \"\"`\n- `nxc reply \"\"` → `nxc reply --thread \"\"`\n\n`--thread` ist Pflicht. Ein erstes Token, das in einem Aufruf \"Kanal\" und im nächsten \"Text\"\nbedeutet, war außerdem die eine Form, die Ihnen die Argumentprüfung nie abnehmen konnte.\n\n**Die Antwort auf eine einzelne NACHRICHTEN-Id entfällt mit.** `reply ` bedeutete\n\"das Gespräch, in dem diese Nachricht steht\" — und genau das sagt `--thread` unmissverständlich. Ein\nGespräch hat eine Adresse.\n\nZwei Folgen, die Sie vor dem Umstieg kennen sollten:\n\n- **`nxc reply --json` liefert eine Form.** Die positionale Form baute ihr JSON-Objekt selbst und\n trug für eine Tatsache beide Namen, `persisted` und `posted`; `reply --thread` serialisiert seit\n jeher die typisierte Quittung, und die trägt `posted`. Mit einer verbleibenden Form gibt es einen\n Namen. Alles Übrige in dieser Quittung — `thread_id`, `resumed`, `message_id`, `warnings` sowie\n der Befund `wake_skipped` — ist unverändert.\n**Eine Beschriftung hat sich mit dem Verb geändert.** `nxc ask` verstand eine Nachricht als\n`question`, solange Sie nichts anderes sagten; `nxc send` versteht sie als `info`. Eine Tafel, die\nSie bisher mit einem blossen `nxc ask \"\"` geöffnet haben, trägt jetzt also `info`, wo\n`question` stand. Das ist eine Beschriftung — das, was `search` und `inbox` Ihnen zeigen — und\nnichts leitet, wartet oder entscheidet danach. Mit `--kind question` bekommen Sie sie zurück. Der\nDefault wurde bewusst nicht mitgenommen: `ask` konnte einen haben, weil es immer nur einen Kanal\nansprach, während `send --to` eine Persona oder einen Kanal anspricht — und ein Default, der sich\ndanach richtet, als was sich Ihr Ziel herausstellt, ist genau die verborgene Unterscheidung, die\ndieses Release abschafft.\n\n**Und eine Tafel weckt ihren Anforderer weiterhin genau einmal.** Wer einen Faden beantwortet, gibt\nden Zug an die Gegenseite zurück — eine sammelnde Tafel hat aber keine Gegenseite, und ihr Anforderer\nwird vom Abschluss geweckt, einmal. Das galt bisher, weil die beiden `reply`-Formen verschiedenen\nCode erreichten; mit nur noch einer Form ist es als Regel festgeschrieben. Eine Teilantwort, eine\nnachgereichte Antwort oder eine Antwort von jemandem, der nie erwartet wurde, weckt den Anforderer\nalso nicht erneut.\n\n**Was aus Ihren bestehenden Kanälen wird.** Sie bleiben im Arbeitsbereich, und ihre Nachrichten\nbleiben über `nxc search` und `nxc inbox` lesbar — aber ein Kanal, den keine Deklaration benennt, ist\nkein Ziel mehr. `nxc send --to ` sagt das beim Namen und sagt auch, wohin eine Deklaration\ngehört, statt mit \"no such target\" zu antworten und Sie einen Tippfehler in einer Id suchen zu\nlassen, die vollkommen richtig geschrieben ist.\n- `breaking` · `nxc reply --escalate \"\"` — der Vermerk \"ich kann diese Aufgabe nicht erfüllen\". Es ist das\nZWEITE und letzte, was ein Agent mit `reply` sagen darf; das erste ist \"ich bin fertig\". Alles andere,\nwas ein Agent produziert, gehört ins Transkript und niemals in die Agent-zu-Agent-Kommunikation, denn\nein abwartender Agent würde es als Ergebnis deuten.\n\nEs ist ausdrücklich kein Mülleimer für alles, was kein Ergebnis ist. Die echte Rückfrage — \"was\nmeinst du mit X?\", von jemandem, der die Arbeit sehr wohl tun kann — bleibt `--kind question`, eine\nandere Sache mit eigenem Wert. `--escalate` trägt eine Bedeutung und bleibt darauf verpflichtet.\n\n**Der Zug endet in beiden Fällen.** Ein eskalierendes `reply` löst die Verpflichtung des Absenders\ngenau so ein wie ein fertiges: die Sitzung endet, die Schuld ist weg, und an der Frage, wer wem was\nschuldet, ändert sich nichts. Anders ist der AUSGANG — und darüber entscheidet der Betreuer des\nKanals.\n\n**Die Eskalation dringt nach oben vor, auf jedem Kanal.** Ein Kanalmitglied, das \"ich kann nicht\"\nsagt, löst seinen eigenen Faden ein wie jede andere Antwort, die Menge ist also vollständig und der\nKanal konsolidiert — aber die Antwort des Kanals an den Anforderer trägt die Eskalation weiter, statt\nsie zu einem Ergebnis zu falten. Die Frage kann so, Ebene für Ebene, bis zu dem vordringen, der die\nArbeit in Auftrag gegeben hat.\n\nDas gilt für beide Ausgabeformen eines Kanals, auch für die, die ZUSAMMENFASST\n(`on_complete: summarize`) — denn ein Kanal fasst ein \"ich kann nicht\" niemals weg: Eine Menge, die\neines trägt, wird durchgereicht, wie sie ist. Wie das von der Seite des Anforderers aussieht, steht\nim Eintrag zum Konsolidierer aus demselben Release.\n\nGetragen wird sie von der `kind`-Angabe der Nachricht — einem Feld, das es bereits gab und das\nbereits synchronisiert wird; an der synchronisierten Nutzlast ändert sich also nichts. `--kind\nescalation` wird bewusst nicht angenommen: `--escalate` ist die eine Tür dorthin, und beides zugleich\nzu verlangen wird abgelehnt. Anwendungen erreichen dasselbe, indem sie `MessageKind::Escalation` auf\nder Antwort setzen, die sie ohnehin bauen — es gibt hier keine Fahne, die nur die Kommandozeile\nsenden kann.\n\nEs ist DEKLARIERT statt frei, und genau das macht es zu einem Signal: Der Betreuer des Kanals\nverzweigt auf ein Bit, dessen Bedeutung im Kanal steht, nicht auf einen Token, den ein Agent sich\nausdenkt. Deshalb braucht auch der freie Verzweigungs-Token, den das entfernte\n`nxc workflow step done` trug, keinen anderen Ersatz.\n- `breaking` · **`nxc send` hat jetzt genau ein Ziel: `--to`.** `--role` und `--session` sind entfallen, und mit\nihnen die beiden Bibliotheksmethoden dahinter — `Engine::role_trigger` und `Engine::role_resume`.\n\n**`send --role ` geht in `send --to ` auf.** Beide übergeben einer Persona eine\nAufgabe, beide laufen durch denselben Code, und seit dem vorigen Release konnte keines von beiden\nmehr einen Kanal benennen — also öffnen beide das Direktgespräch zwischen Aufrufer und Persona\nselbst. Was `--to` hinzufügt, ist der Grund für die Richtung dieses Zusammenlegens: es öffnet einen\nFADEN und sagt der Persona, in welchen sie antworten muss. `--role` postete ohne Faden, und damit\nhatten seine Antworten überhaupt keine Adresse — denn `reply --thread` ist die einzige verbliebene\nForm zu antworten. Wo `nxc send --role coder \"…\"` stand, steht künftig `nxc send --to coder \"…\"`;\nzurück kommt eine Faden-Id, und genau die braucht eine Antwort.\n\n**`send --session ` hat keinen Nachfolger, und das ist eine Entscheidung, kein Versehen.** Es\nschob einer Persona eine neue Aufgabe in die LAUFENDE Sitzung. `reply --thread` ist eine andere\nHandlung: es antwortet in einem Gespräch und erreicht die Gegenseite über die Rückadresse des Fadens\n— die neueste Nachricht, die jemand anderes dort abgelegt hat. Eine gerade erst gerufene Persona, die\nnoch nicht geantwortet hat, hat keine abgelegt; ein Nachtrag steht dann zwar im Faden, weckt aber\nniemanden, bis die Persona von sich aus antwortet. Die Zustellung in eine Sitzung mitten im Zug\nbraucht ein Signal in einen laufenden Agenten hinein, das es noch nicht gibt; sie ist als eigener\nAuftrag erfasst und kommt als `reply --thread --force`. Bis dahin gibt die Kommandozeile hier\neine Fähigkeit auf — hier benannt, statt sie entdecken zu lassen.\n\n**Für Anwendungen, die die Bibliothek einbetten:** `Engine::send_to` ist die eine Tür zu einer\nPersona. Sie nimmt ein Ziel statt eines Rollen-Handles, gibt neben der Sitzungs-Id eine Faden-Id\nzurück und hält fest, von wem der Faden eine Antwort erwartet. Für `Engine::role_resume` gibt es aus\ndem oben genannten Grund keinen Ersatz. Der Mechanismus unter beiden bleibt unangetastet — entfallen\nsind nur die flachen Handle-Methoden.\n- `breaking` · Eine Antwort löst jetzt den ZUG ein, den sie beantwortet, und nicht den ganzen Faden. Bisher galt\neine Rolle in einem Faden ab ihrer ersten Nachricht dort für immer als „hat geantwortet\" — die\nrichtige Lesart für ein einmaliges Review-Board und die falsche für ein Gespräch, das hin und her\ngeht. Sobald ein Betreuer mit einem zweiten Arbeitspaket auf denselben Faden zurückkam, war alles,\nwas auf dieser Zählung aufsetzt, bereits überzeugt, die Rolle schulde nichts mehr: `nxc reply\n--if-unanswered` — das Sicherheitsnetz, das der Agenten-Sidecar am Ende jeder Sitzung auslöst, damit\neine ohne Antwort gestorbene Sitzung trotzdem eine Antwort hinterlässt — tat ab dem zweiten Zug still\nnichts mehr, und die Arbeitsbaum-Sperre las die Kette als fertig, während die Rolle noch am\nAbschlussbericht schrieb, und übergab die Arbeitskopie mitten im Lauf an eine fremde Kette.\n\nOb eine Antwort noch geschuldet wird, wird jetzt ab dem Zeitpunkt der Frage gemessen: Die Erklärung\n`expects_reply_from` eines Fadens trägt ihre eigene Position im Protokoll, und als Antwort darauf\nzählt nur, was ein Handle NACH der aktuellen Erklärung geschrieben hat. Eine erneute Erklärung —\nmit denselben Handles oder mit anderen — eröffnet damit einen neuen Zug, und alle, die sie nennt,\nschulden ihr eine Antwort. Am synchronisierten Nachrichtenformat ändert sich nichts; das Ganze wird\naus dem abgeleitet, was das Protokoll ohnehin trug, also braucht kein Gerät eine Migration, und ein\nPeer, der nur die Ops empfangen hat, kommt zum selben Ergebnis.\n\n**Eine Einschränkung, und sie gehört dem Unterbau, nicht dieser Regel.** „Nach der aktuellen\nErklärung\" wird in der kausalen Ordnung des zugrunde liegenden Protokolls entschieden, und diese\nOrdnung hat eine bekannte Lücke: Der Zähler, den sie verwendet, gehört einem GEÖFFNETEN\nSpeicher-Handle — beim Öffnen einmal aus der Datei gewonnen, danach nie wieder mit ihr abgeglichen —\nwährend alle Prozesse eines Geräts dieselbe Replikat-Identität teilen. Zwei Schreiber, die denselben\nArbeitsbereich gleichzeitig offen halten, sehen die Position des jeweils anderen also nicht: ein\nlanglebiger Host, der die Engine für seine Laufzeit einbettet, neben kurzlebigen `nxc`-Aufrufen —\noder schlicht zwei gleichzeitig laufende `nxc`-Aufrufe. Wo das passiert, kann eine Nachricht, die VOR\neiner erneuten Erklärung geschrieben wurde, als Antwort auf sie gezählt werden — auf einem Gerät,\nganz ohne Synchronisation — und die Folgen sind genau die, gegen die die Regel oben antritt: ein Zug,\nder in dem Augenblick als eingelöst gilt, in dem er eröffnet wird, und eine Arbeitsbaum-Sperre, die\nfreigegeben wird, während die Arbeit noch läuft. Das ist als eigenes Item erfasst (`6j6v.fc5p`), der\nFix gehört in den Unterbau, auf dem alle diese Lesungen sitzen, und an der hier beschriebenen Regel\nändert sich dadurch nichts.\n\n**Damit entfällt der Befehl `nxc threads expect`.** Neu zu erklären, von wem ein Faden eine Antwort\nerwartet, war bisher etwas, das ein Mensch tippen konnte, und der Zweck war, ein Board abzuschließen,\ndas an einem nie zurückkehrenden Reviewer feststeckte: den erwarteten Satz auf die verengen, die\ngeantwortet hatten, und das Board war zu. Nach der Regel oben bedeutet eine erneute Erklärung genau\ndas nicht mehr (sie fragt die genannten Handles erneut, mit Wirkung ab jetzt) — und statt einen\nSonderfall zu bauen, der die alte Wirkung rettet, entfällt der Befehl: Er stand ohnehin auf der Liste\nder `nxc`-Kommandos, die zurückgebaut werden, während die Agent-zu-Agent-Oberfläche auf das gekürzt\nwird, was ein Agent wirklich braucht. **Die Fähigkeit selbst ist nicht verschwunden** — der Schreibweg\nsteht Apps und Hosts weiterhin als `facade::set_expects` zur Verfügung (nur der Öffner, gleiche\nFehler), und genau ihn benutzt der Kanal-Betreuer, um einer Rolle ihren nächsten Zug zu geben.\nWeggefallen ist eine von Hand zu tippende Tür. Um ein Board zu schließen, das niemand mehr beantworten\nwird, lässt man die Erwartung stattdessen fallen: Ein Satz, der dieses Handle nicht mehr nennt, lässt\nnichts offen — und genau das lesen die Freigabe der Arbeitskopie, die Einlösung per `--if-unanswered`\nund der Fristhinweis.\n\n**In einem bestehenden Arbeitsbereich wirkt das rückwirkend, und man sieht es.** Es handelt sich um\nabgeleitete Lesungen, nicht um gespeicherten Zustand: Jedes Board wird beim ersten Lesen nach dem\nUpdate nach der neuen Regel neu berechnet. Ein Board, das früher durch Verengen abgeschlossen wurde,\nliest sich wieder als offen (seine Antworten liegen vor der Verengung) — womit auch die Kette, die für\ndiesen Faden die Arbeitskopie hält, sie weiter hält, bis jemand die aktuelle Erklärung beantwortet\noder die Erwartung fallengelassen wird; und ein Faden mit abgelaufener Frist kann erneut als\n„wartet über die Frist hinaus\" auftauchen. Nichts geht verloren und nichts wird umgeschrieben:\nGeändert hat sich die Frage, die an dasselbe Protokoll gestellt wird.\n\nDie Art einer Nachricht wird dabei bewusst nirgends herangezogen: Eine Antwort „ich kann das nicht\"\nlöst den Zug genauso ein wie eine fertige, denn die Sitzung endet so oder so.\n\nFassade (Bruch, verhaltensseitig — keine Signatur bewegt sich): `nexus_chat::store::ChatStore::\nthread_quorum` und `thread_quorums` liefern dieselbe Form, und `replied` / `outstanding` /\n`complete` von `ThreadQuorum` antworten jetzt pro Zug statt pro Faden. Ein Konsument, der ein Board\ndarstellt, sieht einen neu erklärten Faden wieder aufgehen; ein Konsument, der „einmal geantwortet\"\nals dauerhaft behandelt hat, muss stattdessen die aktuelle Erklärung lesen. Die Quittung von\n`facade::set_expects` meldet aus demselben Grund den erneut gefragten Satz.\n- `breaking` · Die Befehlsgruppe `nxc workflow` entfällt, und mit ihr die deklarative Ablauf-Maschine dahinter —\n`workflow start`, `workflow step done` (samt `--outcome`), `workflow status`, `workflow list` und\n`workflow liveness`, der `workflow_runs`-Datensatz, den sie schrieben und lasen, und die\nDeklarationen `.nxs-personas/workflow.yaml` / `.nxs-personas/workflows/*.yaml`, die sie beschrieben.\nEin Arbeitsbereich, in dem diese Dateien noch liegen, hat schlicht nichts mehr, was sie liest.\n\n**Der Kanal IST der Ablauf, und dorthin ist jedes dieser Verben gegangen.** Ein deklarierter Kanal\nträgt seine Mitglieder, seine Reihenfolge (`flow: sequential`), seine Frist und seine\nZusammenführung selbst — also startet `send --to ` einen Durchlauf davon, und `reply --thread`\nbringt ihn weiter: Der Betreuer des Kanals entscheidet, was als Nächstes kommt, weshalb kein Agent\nmehr ein Verzweigungs-Token aussprechen muss. Wo ein Vorgang steht, sagt `nxc status`: `--thread` für\neinen Vorgang, `--channel` für die, die in einem Kanal begonnen haben, und ohne Argument alles, was\nhier gerade läuft.\n\n**An der Bibliotheks-Naht** entfallen `Engine::workflow_start`, `workflow_step_done`,\n`workflow_tick`, `workflow_liveness`, `workflow_status` und `workflow_list` — ebenso der\nAblauf-Ereignisstrom `workflow_events`, `workflow_event_cursor` und `subscribe_workflow`. Was eine\nApp stattdessen benutzt, gibt es bereits: `Engine::subscribe` für den „jemand hat geschrieben, lies\nneu\"-Tick und `Engine::status` dafür, wo ein Vorgang steht. `Definitions::new` nimmt zwei statt drei\nArgumente, und `PrimeRoster` führt keine `workflows`-Liste mehr.\n\n**Zwei Fähigkeiten werden aufgegeben statt ersetzt, und beide stehen hier statt gefunden zu werden.**\nDie Totmann-Überwachung eines Schritts — erst ein Anstupsen, dann eine Eskalation, wenn ein laufender\nSchritt still wurde — hing am aktuellen Schritt eines Durchlaufs und geht mit den Durchläufen; gegen\nein still gewordenes Mitglied bleibt die kanalweise Mitglieder-Frist, die bei deklariertem `timeout`\nzuschlägt, aber nicht anstupst. Und eine Nachricht erbt nicht mehr automatisch die beauftragten\nTicket-Ids eines Durchlaufs: Womit sie zu tun hat, sagt `--ref nxf_ids=` — was ohnehin immer\nVorrang vor dem automatischen Stempel hatte.\n\nDie `workflow_runs`-Tabellen werden beim nächsten Öffnen eines bestehenden Arbeitsbereichs entfernt.\nNachrichten, Fäden und Kanäle bleiben unberührt, und ein älteres `nxc`, das denselben\nArbeitsbereich danach öffnet, legt seine eigenen leeren Tabellen wieder an, statt zu scheitern. Die\neine Form, die sich nicht selbst heilt — hier gesagt, statt sie erleben zu lassen: Ein älterer\nProzess, der den Arbeitsbereich bereits offen hält, während ein neuerer die Tabellen darunter\nentfernt, stürzt ab, sobald er danach eine Workflow-Op faltet. Also den alten Prozess vor dem\nUpgrade beenden — oder, da dies ein lokales Werkzeug ist und keine Flotte, schlicht nicht zwei\nVersionen gleichzeitig gegen einen Arbeitsbereich laufen lassen.\n- `breaking` · Die Arbeitskopie ist jetzt so lange geschützt, wie die AUFGABE läuft. Darüber entscheiden zwei\nFragen, und sie haben zwei verschiedene Antworten.\n\n**OB eine Kette geschützt wird, wird von den deklarierten Mitgliedern nach oben abgeleitet, einen\nSchritt weit.** Ein Kanal, dessen deklariertes Mitglied `working_tree: exclusive` sagt, gilt als\nschutzbedürftig, ohne dass der Autor es am Kanal wiederholt — einmal an der Persona gesagt, die baut,\nund jeder Kanal, in dem sie arbeitet, ist abgedeckt. Nach einem Schritt ist Schluss, und das mit\nAbsicht: Ein Kanal fängt sich die Schutzbedürftigkeit NICHT von einem anderen Kanal ein, nur weil die\nbeiden ein Mitglied teilen. Sonst gäbe ein Coding-Kanal seinen Bedarf an einen Review-Kanal weiter,\ndessen Mitglieder nur lesen, dieser gäbe ihn weiter, und am Ende wäre jeder Kanal im Arbeitsbereich\nexklusiv. `working_tree: exclusive` direkt am Kanal zu deklarieren wirkt unverändert und bleibt der\nrichtige Weg für „diese RUNDE braucht sie\", wenn es kein einzelnes Mitglied ist.\n\nDer praktische Unterschied: Ein geschützter Kanal wartet jetzt als Ganzes. Vorher ließ ein Kanal, der\nselbst nichts sagte, das Mitglied warten, das den Bedarf deklariert hatte, während die schweigenden\nGeschwister trotzdem starteten — das Board öffnete sich halb angestellt und halb laufend, auf beiden\nSeiten einer Sperre, die die Kette abdecken sollte.\n\n**WIE WEIT der Schutz reicht: die Unterhaltung, in der die Arbeit beauftragt wurde, samt allem, was\ndarunter geöffnet wird.** Er hält also, während eine Coding-Runde auf eine von ihr gestartete\nReview-Runde wartet: Die Review-Unterhaltung und die Fäden der Reviewer hängen darunter und liegen im\nselben Anspruchsbereich. Er deckt endlich auch eine Nebenunterhaltung ab, die eine arbeitende Sitzung\nfür sich öffnet — die tauchte am Faden-Board bisher so auf, als gehöre sie zu gar keiner Sperre. Und\nnach oben ist Schluss: Die Unterhaltung mit dem Agenten, der die Arbeit beauftragt hat, liegt ÜBER\ndem Anspruch und hält nichts — ein unbeantworteter Mensch behält die Arbeitskopie also nie bis\nmorgen.\n\n**Freigegeben wird, wenn diese Unterhaltung GESCHLOSSEN ist — und das ist nicht dasselbe wie\n„gerade schuldet niemand eine Antwort\".** Eine gewöhnliche Antwort schließt sie, und der nächste\nAuftrag aus der Warteschlange startet. Ein `reply --escalate` — „ich kann diese Aufgabe nicht\nerfüllen\" — schließt sie NICHT, obwohl die Verpflichtung des Absenders so oder so eingelöst ist: Die\nFrage dringt nach oben vor, womöglich bis zu Ihnen, die Aufgabe ist mitten im Gange, und die\nArbeitskopie bleibt geschützt. Auch langes Schweigen ändert daran nichts. **Eine unbeantwortete\nRückfrage (`--kind question`) hält sie genauso**, aus demselben Grund: In beiden Fällen arbeitet\nniemand, und eine Freigabe ließe eine zweite Kette in eine Arbeitskopie starten, in die der Fragende\ngleich zurückkehrt. Das gilt **überall im geschützten Bereich**, nicht nur in der Unterhaltung, in\nder die Arbeit beauftragt wurde: Ein Reviewer drei Ebenen tiefer, der eine Rückfrage gestellt hat und\nauf die Antwort wartet, hält die Kopie — auch dann, wenn jede Unterhaltung darüber längst\nabgeschlossen hat, ohne es zu bemerken.\n\n**Eine Antwort setzt die Runde wieder in Gang, und die Kopie wird frei, wenn diese Runde fertig\nist.** Antworten heißt: der Runde ihren nächsten Zug geben — eine Antwort in die Unterhaltung des\nKanals selbst, durch dieselbe Tür, durch die die Arbeit kam. Damit wird der Zug jedes Mitglieds neu\neröffnet und alle laufen wieder. In diesem Moment wird nichts freigegeben, und das ist richtig so:\nDie Mitglieder arbeiten ja. Was die Antwort beendet, ist das WARTEN — die Kopie wird also mit dem\nAbschluss der Runde frei, ohne die Zwei-Stunden-Grenze auch nur in die Nähe zu kommen. Die Grenze\ngreift nur, wenn die Frage überhaupt nie beantwortet wird.\n\nDie Fehlerrichtung ist gewollt: Wer unnötig eskaliert oder fragt, hält die Kopie etwas zu lange —\nharmlos neben einer Kopie, die weggegeben wird, während die Aufgabe noch läuft.\n\n**Damit entfällt auch die Regel, dass ein Lauf der entfernten Ablauf-Maschine die Arbeitskopie\nhielt, solange er `running` war.** Das war ein Deckel auf einem engeren Problem — ein Rollenschritt\neines solchen Laufs hinterließ nirgends eine Verpflichtung, der Lauf sah also fertig aus, sobald er\nauf einen solchen Schritt weiterging — und er kostete zweierlei: Ein festgefahrener Lauf hielt die\nKopie bis zur Zwei-Stunden-Grenze, und die Freigabe kam, wenn der LAUF endete, statt wenn die Arbeit\nfertig war. Der Anspruchsbereich ENTHÄLT jetzt die Unterhaltungen, in denen die Arbeit stattfindet —\ngenau das, wofür der Deckel einsprang —, und auf der Oberfläche dieses Releases wird ein\nKanalmitglied immer mit Unterhaltung und Verpflichtung beauftragt, die Lage, für die es den Deckel\nbrauchte, kann also gar nicht mehr entstehen.\n\nFassade (Bruch): Die Freigabe-Ableitung in `nexus_chat::working_tree` verhält sich bei gleicher\nEingabe anders. `ChatStore::work_scope_threads` liefert für einen `Thread`-Bereich den ganzen Teilbaum statt des\nFadens plus seiner direkten Mitglieds-Fäden, und `WorkScope` hat jetzt zwei statt drei Varianten —\nder `Run`-Zweig ist mit der Ablauf-Maschine entfallen, die dieses Release entfernt. Neu daneben: `ChatStore::work_scope_handed_back`,\n`ChatStore::thread_subtree`/`thread_subtrees`, `ChatStore::last_reply_kind` (die weitere Lesung, an die\n`last_reply_escalated` jetzt delegiert — jene Funktion ist unverändert) und\n`Definitions::channel_needs_working_tree`.\n- `breaking` · Eine Rolle oder ein deklarierter Kanal kann jetzt `working_tree: exclusive` deklarieren (die\nVorgabe `shared` bleibt exakt das heutige Verhalten — keine Sperre, kein Warten). Danach arbeitet\nimmer nur eine Kette von Rollen-Sitzungen an der Arbeitskopie des Repositorys; eine zweite Kette,\ndie sie ebenfalls braucht, stellt sich an, statt zu starten und mit der ersten am git-Index und an\nder Cargo-Build-Sperre zu kollidieren — genau die Kollision, die ein Coding-und-Review-Paar in\neinem früheren Praxistest der Rollen-Laufzeit real erlebt hat. Halter ist die KETTE — der Faden, in dem die Arbeit\nbeauftragt wurde — nicht die einzelne Sitzung.\n\nWie weit „die Kette\" reicht, beschreibt der Eintrag zum „Anspruchsbereich\" aus demselben Release; er\nlöst ab, was ein früherer Entwurf dieses Eintrags dazu sagte. Der Bereich ist die Unterhaltung, in\nder die Arbeit beauftragt wurde, SAMT allem, was darunter geöffnet wird — eine Coding-Runde behält\ndie Arbeitskopie also, während sie auf eine von ihr gestartete Review-Runde wartet. Ein\nWORKFLOW-LAUF ist ebenfalls über seine Schritte hinweg EIN Anspruchsbereich. Eine PERSONA-Kette\n(`nxc send --to `, ein Aufruf je Schritt) öffnet je Aufruf eine neue Unterhaltung; zwischen\nden Schritten wird die Sperre also frei, und die Freigabe startet, was am Kopf der Warteschlange\nsteht. Stand dort ein früher angestellter fremder Auftrag, läuft dieser als Nächstes, und der eigene\nnächste Schritt der Kette stellt sich dahinter an.\n\nWarten ist nie still. Die Quittung eines angestellten Triggers sagt es selbst: `nxc send --to\n --json` bekommt `queued_behind` (wer gerade hält) und `queue_position`\n(1-basiert), sobald ein Trigger warten muss, und die menschenlesbare Zeile ergänzt `QUEUED at\nposition n behind …`. `nxc threads list`/`show` tragen dieselbe Tatsache im Nachhinein — für eine\nKette, die nie nachschaut, oder eine App, die das Board erst später liest: Jeder Faden meldet jetzt\n`working_tree: \"holding\" | \"waiting\" | null` samt seiner Warteschlangen-Position.\n\nEine Freigabe übergibt die Arbeitskopie an einen ganzen ANSPRUCHSBEREICH, nicht an einen einzelnen\nwartenden Trigger. Mehrere Trigger teilen sich einen Anspruchsbereich immer dann, wenn ein\nmehrköpfiger Kanal mit `working_tree: exclusive` beim Öffnen warten musste: Jedes Mitglied dieses\nBoards gehört zum Faden des Boards, stellt sich also einzeln unter demselben Schlüssel an. Nur das\nerste zu starten, würde die übrigen dauerhaft stranden lassen — das Board schuldet deren Antworten\nund könnte die Freigabe, die sie starten würde, deshalb nie erreichen. Sie werden daher gemeinsam\ngestartet, und genau das passiert auch, wenn die Arbeitskopie frei ist (das erste Mitglied erwirbt\ndie Sperre, die übrigen erben sie). Welcher Anspruchsbereich als Nächstes drankommt, entscheidet\nweiterhin die Ordnung `(priority, enqueued_at, id)` der Warteschlange; erst nachdem der Kopf gewählt\nist, kommt der Rest seines Bereichs mit.\n\nFreigabe ist jetzt eine deterministische Tatsache, keine Schätzung von Ruhe. Der geprimte Prompt\neiner getriggerten Rolle nennt den Faden, dem sie eine Antwort schuldet, und den genauen Befehl,\nder das abschließt, und das neue Flag `nxc reply --if-unanswered` erlaubt einem Aufrufer, der\nwirklich nicht weiß, ob er schon geantwortet hat, bedingungslos aufzurufen — es postet nur, wenn\nnoch eine Antwort geschuldet wird, und ist sonst ein sicherer No-op. Der Abbau des Agenten-Sidecars,\nder am Ende jeder Sitzung immer läuft, nutzt jetzt genau dieses Flag: Eine Sitzung, die endet, ohne\nden ihr geschuldeten Faden je beantwortet zu haben, bekommt an ihrer statt eine Fehlantwort gepostet,\nstatt den Faden still hängen zu lassen. Sobald die letzte offene Antwort im Bereich einer Kette\neintrifft, gibt genau dieser Antwort-Aufruf die Arbeitsbaum-Sperre frei, zieht den Kopf der\nWarteschlange und startet ihn — kein Daemon, kein Wartender.\n\nDiese Zwei-Stunden-Grenze (`working_tree::WORKING_TREE_LEASE_BOUND`) ist nur ein Rückfall für den\nharten Tod, nie ein Budget oder ein Wecker: Beim Ablauf passiert nichts von selbst, und sie startet\nNICHTS bereits Wartendes — sie sorgt nur dafür, dass eine verwaiste Sperre nicht jeden KÜNFTIGEN\nErwerb abweist, sodass die nächste Kette, die fragt, die Arbeitskopie nehmen kann. Ein angestellter\nTrigger bleibt angestellt, bis eine spätere Kette die Sperre tatsächlich freigibt, nicht bis die\nGrenze verstreicht; und eine Sperre über einen Bereich, in dem gar nichts offen ist (was ein nacktes\nResume nimmt, da es keine Verpflichtung anmeldet), wird stattdessen von der nächsten Antwort in\ngenau diesem Faden freigegeben.\n\nGetrennt davon verwirft ein `send --to `-Trigger, der nach dem Posten seiner\nNachricht scheitert, diese nicht mehr: Die Quittung trägt jetzt `spawned: bool` (`false` sowohl für\n„hinter der Sperre angestellt\" als auch für „der Start selbst ist gescheitert\") und\n`warnings: [...]` mit dem, was schiefging, sodass ein Aufrufer endlich „nichts ist passiert\" von\n„die Nachricht steht im Kanal, aber niemand arbeitet noch daran\" unterscheiden kann — damit ist der\nlange offene Fehler 6j6v.hpv8 miterledigt. Der Exit-Code von `nxc` wird bei einem echten\nStart-Fehlschlag weiterhin ungleich null; ein `Engine`-Aufrufer prüft stattdessen `spawned`.\n\nFassade (Bruch): Die öffentliche API von `crates/chat` bewegt sich durch all das oben Genannte.\n`ReplyRequest` (auf `facade`, `orchestration` und `surface::ReplyThreadRequest`) bekommt je ein\nPflichtfeld `if_unanswered: bool` — `false` ergibt das bisherige Verhalten. `facade::reply` liefert\njetzt `Result>` (`None` ist das neue No-op-Ergebnis).\n`orchestration::ReplyReceipt` bekommt `posted: bool`, ihr `message_id` ist jetzt\n`Option`. `worker::TriggerRequest` bekommt `reply_thread: Option`.\n`orchestration::RoleSpawn` bekommt die Pflichtfelder `priority: Priority` und\n`thread: Option<&str>` (dazu `queued_since: Option<&str>`). `working_tree::QueuedTrigger` bekommt\n`priority: crate::model::Priority` und `enqueued_at: Option` — beide Bestandteile der\nWarteschlangen-Ordnung `(priority, enqueued_at, id)` reisen jetzt mit einem angestellten Eintrag\nmit, damit ein nachgerückter Trigger, der erneut warten muss, Dringlichkeit und bereits gewartete\nZeit behält, statt als `normal` mit aktuellem Zeitstempel wieder einzureihen.\n`orchestration::trigger_role` liefert jetzt `Result` statt\n`Result<(), TriggerError>`;\n`orchestration::TriggerReceipt` und `surface::SendToReceipt` bekommen je `queued_behind`/\n`queue_position` sowie die Pflichtfelder `spawned`/`warnings`. `facade::threads` liefert jetzt\n`Vec` statt `Vec` (die bisherigen Felder wandern unter\n`.quorum`), und `facade::thread_board`s `ThreadBoardView` bekommt dieselben zwei\nArbeitsbaum-Felder; `facade::thread_quorums` bleibt unverändert. Auf der Store-Seite verliert\n`ChatStore::enqueue_working_tree` seinen Parameter `priority: i64` (er reist jetzt am Warteschlangen-\nEintrag selbst mit), und `ChatStore::release_working_tree` entfällt — `release_working_tree_and_take_next`\nbleibt als einziger Freigabeweg übrig und liefert jetzt `Vec` (den gesamten\nAnspruchsbereich am Kopf, in Warteschlangen-Reihenfolge) statt `Option`; ein leerer\nVektor ist das bisherige `None`.\n\nWeil dies die öffentliche API von `nexus-chat` bricht, erscheint diese Epic als MINOR-Release\n(`0.60.0`), nicht als `0.59.x`-Patch: `enforce_facade_breaking_axis` blockiert einen Patch-Bump mit\neinem `facade: breaking`-Fragment fail-closed." - } - }, - { - "version": "0.59.5", - "date": "2026-08-12", - "items": [ - { - "type": "added", - "en": "Agent transcripts no longer grow forever. Every role session's transcript is now kept for 30 days\nafter its last entry and retired after that — whole sessions only, so a long-running session is\nnever cut in half, and a session still being written to is never a candidate however long it has\nbeen running. The clean-up rides the first flush of each new session, so nothing has to be run by\nhand; `nxc transcript prune [--keep-days N] [--dry-run]` is there to clear out a workspace that has\ngone quiet, and `NXC_TRANSCRIPT_KEEP_DAYS` (a number of days, or `off`) changes or disables the\nautomatic window. Sessions carrying no readable timestamp are of unknown age and are kept — and\nreported, so they are a visible gap rather than a silent one. Note that this stops the workspace\ndatabase growing rather than making it smaller: the freed space is reused by later writes, but the\nfile itself only shrinks on a `VACUUM`, which is deliberately not run for you.\n\n`nxc transcript show` also learned `--from-seq` and `--limit`, so a very long session can be read in\nwindows instead of materialized whole. On the library seam: `facade::transcript_page` /\n`Engine::transcript_page` and `facade::prune_transcripts` / `Engine::prune_transcripts` are new;\n`facade::transcript` is unchanged and now delegates to the windowed read.", - "de": "Agenten-Protokolle wachsen nicht mehr unbegrenzt. Das Protokoll einer Rollen-Sitzung wird ab jetzt\n30 Tage nach seinem letzten Eintrag aufbewahrt und danach verworfen — immer nur ganze Sitzungen, ein\nlange laufendes Protokoll wird also nie in der Mitte durchgeschnitten, und eine Sitzung, in die\ngerade geschrieben wird, kommt nie in Frage, egal wie lange sie schon läuft. Das Aufräumen läuft\nbeim ersten Mitschreiben jeder neuen Sitzung mit, es muss also nichts von Hand angestoßen werden;\n`nxc transcript prune [--keep-days N] [--dry-run]` gibt es, um in einem still gewordenen\nArbeitsbereich aufzuräumen, und `NXC_TRANSCRIPT_KEEP_DAYS` (eine Anzahl Tage oder `off`) ändert\noder deaktiviert das automatische Fenster. Sitzungen ohne lesbaren Zeitstempel sind von unbekanntem\nAlter und bleiben erhalten — und werden ausgewiesen, damit die Lücke sichtbar bleibt. Das bremst das\nWachstum der Arbeitsbereichs-Datenbank, es verkleinert sie nicht: der frei gewordene Platz wird von\nspäteren Schreibvorgängen wiederverwendet, die Datei selbst schrumpft erst bei einem `VACUUM`, das\nhier bewusst nicht mitläuft.\n\n`nxc transcript show` kennt außerdem `--from-seq` und `--limit`, eine sehr lange Sitzung lässt sich\ndamit in Fenstern lesen, statt sie ganz in den Speicher zu holen. Auf der Bibliotheks-Naht sind\n`facade::transcript_page` / `Engine::transcript_page` und `facade::prune_transcripts` /\n`Engine::prune_transcripts` neu; `facade::transcript` bleibt unverändert und ruft jetzt den\ngefensterten Lesepfad auf.", - "facade": "changed" - } - ], - "notes": { - "en": "### Added\n- Agent transcripts no longer grow forever. Every role session's transcript is now kept for 30 days\nafter its last entry and retired after that — whole sessions only, so a long-running session is\nnever cut in half, and a session still being written to is never a candidate however long it has\nbeen running. The clean-up rides the first flush of each new session, so nothing has to be run by\nhand; `nxc transcript prune [--keep-days N] [--dry-run]` is there to clear out a workspace that has\ngone quiet, and `NXC_TRANSCRIPT_KEEP_DAYS` (a number of days, or `off`) changes or disables the\nautomatic window. Sessions carrying no readable timestamp are of unknown age and are kept — and\nreported, so they are a visible gap rather than a silent one. Note that this stops the workspace\ndatabase growing rather than making it smaller: the freed space is reused by later writes, but the\nfile itself only shrinks on a `VACUUM`, which is deliberately not run for you.\n\n`nxc transcript show` also learned `--from-seq` and `--limit`, so a very long session can be read in\nwindows instead of materialized whole. On the library seam: `facade::transcript_page` /\n`Engine::transcript_page` and `facade::prune_transcripts` / `Engine::prune_transcripts` are new;\n`facade::transcript` is unchanged and now delegates to the windowed read.\n\n### Facade Contract\n- `changed` · Agent transcripts no longer grow forever. Every role session's transcript is now kept for 30 days\nafter its last entry and retired after that — whole sessions only, so a long-running session is\nnever cut in half, and a session still being written to is never a candidate however long it has\nbeen running. The clean-up rides the first flush of each new session, so nothing has to be run by\nhand; `nxc transcript prune [--keep-days N] [--dry-run]` is there to clear out a workspace that has\ngone quiet, and `NXC_TRANSCRIPT_KEEP_DAYS` (a number of days, or `off`) changes or disables the\nautomatic window. Sessions carrying no readable timestamp are of unknown age and are kept — and\nreported, so they are a visible gap rather than a silent one. Note that this stops the workspace\ndatabase growing rather than making it smaller: the freed space is reused by later writes, but the\nfile itself only shrinks on a `VACUUM`, which is deliberately not run for you.\n\n`nxc transcript show` also learned `--from-seq` and `--limit`, so a very long session can be read in\nwindows instead of materialized whole. On the library seam: `facade::transcript_page` /\n`Engine::transcript_page` and `facade::prune_transcripts` / `Engine::prune_transcripts` are new;\n`facade::transcript` is unchanged and now delegates to the windowed read.", - "de": "### Neu\n- Agenten-Protokolle wachsen nicht mehr unbegrenzt. Das Protokoll einer Rollen-Sitzung wird ab jetzt\n30 Tage nach seinem letzten Eintrag aufbewahrt und danach verworfen — immer nur ganze Sitzungen, ein\nlange laufendes Protokoll wird also nie in der Mitte durchgeschnitten, und eine Sitzung, in die\ngerade geschrieben wird, kommt nie in Frage, egal wie lange sie schon läuft. Das Aufräumen läuft\nbeim ersten Mitschreiben jeder neuen Sitzung mit, es muss also nichts von Hand angestoßen werden;\n`nxc transcript prune [--keep-days N] [--dry-run]` gibt es, um in einem still gewordenen\nArbeitsbereich aufzuräumen, und `NXC_TRANSCRIPT_KEEP_DAYS` (eine Anzahl Tage oder `off`) ändert\noder deaktiviert das automatische Fenster. Sitzungen ohne lesbaren Zeitstempel sind von unbekanntem\nAlter und bleiben erhalten — und werden ausgewiesen, damit die Lücke sichtbar bleibt. Das bremst das\nWachstum der Arbeitsbereichs-Datenbank, es verkleinert sie nicht: der frei gewordene Platz wird von\nspäteren Schreibvorgängen wiederverwendet, die Datei selbst schrumpft erst bei einem `VACUUM`, das\nhier bewusst nicht mitläuft.\n\n`nxc transcript show` kennt außerdem `--from-seq` und `--limit`, eine sehr lange Sitzung lässt sich\ndamit in Fenstern lesen, statt sie ganz in den Speicher zu holen. Auf der Bibliotheks-Naht sind\n`facade::transcript_page` / `Engine::transcript_page` und `facade::prune_transcripts` /\n`Engine::prune_transcripts` neu; `facade::transcript` bleibt unverändert und ruft jetzt den\ngefensterten Lesepfad auf.\n\n### Facade-Kontrakt\n- `changed` · Agenten-Protokolle wachsen nicht mehr unbegrenzt. Das Protokoll einer Rollen-Sitzung wird ab jetzt\n30 Tage nach seinem letzten Eintrag aufbewahrt und danach verworfen — immer nur ganze Sitzungen, ein\nlange laufendes Protokoll wird also nie in der Mitte durchgeschnitten, und eine Sitzung, in die\ngerade geschrieben wird, kommt nie in Frage, egal wie lange sie schon läuft. Das Aufräumen läuft\nbeim ersten Mitschreiben jeder neuen Sitzung mit, es muss also nichts von Hand angestoßen werden;\n`nxc transcript prune [--keep-days N] [--dry-run]` gibt es, um in einem still gewordenen\nArbeitsbereich aufzuräumen, und `NXC_TRANSCRIPT_KEEP_DAYS` (eine Anzahl Tage oder `off`) ändert\noder deaktiviert das automatische Fenster. Sitzungen ohne lesbaren Zeitstempel sind von unbekanntem\nAlter und bleiben erhalten — und werden ausgewiesen, damit die Lücke sichtbar bleibt. Das bremst das\nWachstum der Arbeitsbereichs-Datenbank, es verkleinert sie nicht: der frei gewordene Platz wird von\nspäteren Schreibvorgängen wiederverwendet, die Datei selbst schrumpft erst bei einem `VACUUM`, das\nhier bewusst nicht mitläuft.\n\n`nxc transcript show` kennt außerdem `--from-seq` und `--limit`, eine sehr lange Sitzung lässt sich\ndamit in Fenstern lesen, statt sie ganz in den Speicher zu holen. Auf der Bibliotheks-Naht sind\n`facade::transcript_page` / `Engine::transcript_page` und `facade::prune_transcripts` /\n`Engine::prune_transcripts` neu; `facade::transcript` bleibt unverändert und ruft jetzt den\ngefensterten Lesepfad auf." - } - }, - { - "version": "0.59.4", - "date": "2026-08-12", - "items": [ - { - "type": "fixed", - "en": "A role session no longer loses its answer when its transcript cannot be written. A failed\ntranscript write in the middle of a turn used to abort the session's message stream, so the caller\ngot no reply at all and sat out the workflow timeout — even though the model was working normally\nand only the local write had failed. After the first such failure the transcript is now switched\noff for the rest of that run — logged loudly, and marked `transcript=incomplete` on the run's\ncompletion line — while the turn itself runs to the end and delivers its answer.\n\nAn agent transcript now also contains the model's reasoning. Thinking was recorded as empty and\nnever appeared, because the session did not ask for a thinking display mode; role sessions now do,\nand a turn that reasons records a summarized thinking entry alongside its messages and tool calls.", - "de": "Eine Rollen-Sitzung verliert ihre Antwort nicht mehr, wenn ihr Transkript nicht geschrieben werden\nkann. Ein fehlgeschlagener Schreibvorgang mitten im Zug brach bisher den Nachrichtenstrom der\nSitzung ab: Der Aufrufer bekam gar keine Antwort und lief in den Zeitablauf — obwohl das Modell\nnormal arbeitete und nur der lokale Schreibvorgang gescheitert war. Nach dem ersten Fehlschlag\nwird die Transkript-Aufzeichnung jetzt für den Rest des Laufs abgeschaltet — laut protokolliert\nund in der Abschlusszeile als `transcript=incomplete` vermerkt —, während der Zug selbst zu Ende\nläuft und seine Antwort ausliefert.\n\nEin Agenten-Transkript enthält jetzt außerdem die Überlegungen des Modells. Denkblöcke wurden\nbisher leer aufgezeichnet und tauchten nie auf, weil die Sitzung keine Anzeigeform dafür anforderte;\nRollen-Sitzungen tun das jetzt, und ein Zug, der nachdenkt, hinterlässt einen zusammengefassten\nDenkblock neben seinen Nachrichten und Werkzeugaufrufen." - } - ], - "notes": { - "en": "### Fixed\n- A role session no longer loses its answer when its transcript cannot be written. A failed\ntranscript write in the middle of a turn used to abort the session's message stream, so the caller\ngot no reply at all and sat out the workflow timeout — even though the model was working normally\nand only the local write had failed. After the first such failure the transcript is now switched\noff for the rest of that run — logged loudly, and marked `transcript=incomplete` on the run's\ncompletion line — while the turn itself runs to the end and delivers its answer.\n\nAn agent transcript now also contains the model's reasoning. Thinking was recorded as empty and\nnever appeared, because the session did not ask for a thinking display mode; role sessions now do,\nand a turn that reasons records a summarized thinking entry alongside its messages and tool calls.", - "de": "### Behoben\n- Eine Rollen-Sitzung verliert ihre Antwort nicht mehr, wenn ihr Transkript nicht geschrieben werden\nkann. Ein fehlgeschlagener Schreibvorgang mitten im Zug brach bisher den Nachrichtenstrom der\nSitzung ab: Der Aufrufer bekam gar keine Antwort und lief in den Zeitablauf — obwohl das Modell\nnormal arbeitete und nur der lokale Schreibvorgang gescheitert war. Nach dem ersten Fehlschlag\nwird die Transkript-Aufzeichnung jetzt für den Rest des Laufs abgeschaltet — laut protokolliert\nund in der Abschlusszeile als `transcript=incomplete` vermerkt —, während der Zug selbst zu Ende\nläuft und seine Antwort ausliefert.\n\nEin Agenten-Transkript enthält jetzt außerdem die Überlegungen des Modells. Denkblöcke wurden\nbisher leer aufgezeichnet und tauchten nie auf, weil die Sitzung keine Anzeigeform dafür anforderte;\nRollen-Sitzungen tun das jetzt, und ein Zug, der nachdenkt, hinterlässt einen zusammengefassten\nDenkblock neben seinen Nachrichten und Werkzeugaufrufen." - } - }, - { - "version": "0.59.3", - "date": "2026-08-11", - "items": [], - "notes": { - "en": "", - "de": "" - } - }, - { - "version": "0.59.2", - "date": "2026-08-11", - "items": [ - { - "type": "fixed", - "en": "The command line now tells the truth about itself. `nxs mcp --help` claimed to expose \"the active\nmodules' read ops\" while the subcommand right below it documented a switch for writes — the server\nactually offers 33 tools, 20 of which write, so a host that connects can create, update, close and\narchive items. That is now stated plainly, along with the trust it implies. `nxf create --type`\ndocumented `project`, `task` and `issue`: all three are rejected by the program, which accepts the\nactive plugin's own vocabulary (`epic`, `feature`, `bug`, `chore`, `decision` under\n`issue-tracker`). The `--type` filter on `list` and `search` said the same and silently matched\nnothing. `nxs self-update` said it relinks the `nxf`/`nxm` personas and left out `nxc`, which it has\nrelinked since v0.22.0. `nxc workflow tick` had no description at all while `nxc workflow liveness`\ncarried both commands' descriptions glued together; each now describes itself.\n\nRunning any command outside a workspace told everyone to run `nxf init`, whichever tool they had\ntyped. Following that from `nxm` or `nxc` produced a workspace their own module was never registered\nin — the retried command appeared to work, but `nxs prime` replayed nothing for it at session start,\nwhich is the point of the product. Each tool now names its own `init`. A workspace whose replica\nfile cannot be read names the full path it tried instead of a bare `replica.toml` the reader never\ntyped and cannot locate.\n\nHelp texts across `nxf`, `nxm`, `nxc` and `nxs` no longer carry internal ticket ids, references to\nspecification documents that are not shipped, or notes about the argument parser's own limitations.\nOptions that had no description — `nxc send`/`reply`/`ask`'s message kind, priority and disposition,\nand `nxc agents register`'s fields — now have one.\n\n**Embedding consumers:** additive only, nothing to do. `nxs_foundation::workspace` gained\n`set_invoked_persona`, which the multicall dispatcher calls so the \"no workspace\" message can name\nthe right tool's `init`; a host that never calls it falls back to `argv[0]` exactly as before. No\nsignature moved. The only behavioural change is the wording of two error messages — their error\nkinds (`no_workspace`, `io`) are unchanged, so only a consumer matching on message text is\naffected.", - "de": "Die Kommandozeile sagt jetzt die Wahrheit über sich selbst. `nxs mcp --help` behauptete, „die\nLese-Operationen der aktiven Module\" anzubieten, während der Unterbefehl direkt darunter einen\nSchalter fürs Schreiben dokumentierte — tatsächlich sind es 33 Werkzeuge, davon 20 schreibend: ein\nverbundener Host kann Einträge anlegen, ändern, schließen und archivieren. Das steht jetzt da, samt\ndem Vertrauen, das es voraussetzt. `nxf create --type` dokumentierte `project`, `task` und `issue` —\nalle drei weist das Programm zurück, es nimmt die Vokabeln des aktiven Plugins (unter\n`issue-tracker`: `epic`, `feature`, `bug`, `chore`, `decision`). Der `--type`-Filter von `list` und\n`search` sagte dasselbe und traf still gar nichts. `nxs self-update` nannte nur die Personas\n`nxf`/`nxm` und ließ `nxc` weg, das es seit v0.22.0 mit verknüpft. `nxc workflow tick` hatte gar\nkeine Beschreibung, während `nxc workflow liveness` die beider Befehle aneinandergehängt trug; jetzt\nbeschreibt sich jeder selbst.\n\nWer einen Befehl außerhalb eines Arbeitsbereichs aufrief, wurde immer auf `nxf init` verwiesen —\ngleich welches Werkzeug er getippt hatte. Wer dem aus `nxm` oder `nxc` heraus folgte, bekam einen\nArbeitsbereich, in dem sein eigenes Modul nie registriert war: der erneute Aufruf schien zu\nfunktionieren, aber `nxs prime` spielte beim Sitzungsstart nichts davon ein — also genau das, wofür\nes das Produkt gibt. Jedes Werkzeug nennt jetzt sein eigenes `init`. Lässt sich die Replica-Datei\neines Arbeitsbereichs nicht lesen, nennt die Meldung den vollen Pfad statt eines blanken\n`replica.toml`, das der Lesende nie getippt hat und nicht finden kann.\n\nDie Hilfetexte von `nxf`, `nxm`, `nxc` und `nxs` tragen keine internen Ticket-Kennungen mehr, keine\nVerweise auf nicht mitgelieferte Spezifikationsdokumente und keine Anmerkungen zu den Grenzen der\nArgument-Bibliothek. Optionen ohne Beschreibung — Art, Dringlichkeit und Zustellung bei `nxc\nsend`/`reply`/`ask` sowie die Felder von `nxc agents register` — haben jetzt eine.\n\n**Für einbettende Anwendungen:** rein additiv, es ist nichts zu tun. `nxs_foundation::workspace`\nhat `set_invoked_persona` bekommen; der Multicall-Dispatcher ruft es auf, damit die Meldung „kein\nArbeitsbereich\" das `init` des richtigen Werkzeugs nennen kann. Ein Wirt, der es nie aufruft, fällt\nwie bisher auf `argv[0]` zurück. Keine Signatur hat sich bewegt. Geändert hat sich allein der\nWortlaut zweier Fehlermeldungen — ihre Fehlerarten (`no_workspace`, `io`) bleiben gleich, betroffen\nist also nur, wer auf den Meldungstext prüft.", - "facade": "changed" - } - ], - "notes": { - "en": "### Fixed\n- The command line now tells the truth about itself. `nxs mcp --help` claimed to expose \"the active\nmodules' read ops\" while the subcommand right below it documented a switch for writes — the server\nactually offers 33 tools, 20 of which write, so a host that connects can create, update, close and\narchive items. That is now stated plainly, along with the trust it implies. `nxf create --type`\ndocumented `project`, `task` and `issue`: all three are rejected by the program, which accepts the\nactive plugin's own vocabulary (`epic`, `feature`, `bug`, `chore`, `decision` under\n`issue-tracker`). The `--type` filter on `list` and `search` said the same and silently matched\nnothing. `nxs self-update` said it relinks the `nxf`/`nxm` personas and left out `nxc`, which it has\nrelinked since v0.22.0. `nxc workflow tick` had no description at all while `nxc workflow liveness`\ncarried both commands' descriptions glued together; each now describes itself.\n\nRunning any command outside a workspace told everyone to run `nxf init`, whichever tool they had\ntyped. Following that from `nxm` or `nxc` produced a workspace their own module was never registered\nin — the retried command appeared to work, but `nxs prime` replayed nothing for it at session start,\nwhich is the point of the product. Each tool now names its own `init`. A workspace whose replica\nfile cannot be read names the full path it tried instead of a bare `replica.toml` the reader never\ntyped and cannot locate.\n\nHelp texts across `nxf`, `nxm`, `nxc` and `nxs` no longer carry internal ticket ids, references to\nspecification documents that are not shipped, or notes about the argument parser's own limitations.\nOptions that had no description — `nxc send`/`reply`/`ask`'s message kind, priority and disposition,\nand `nxc agents register`'s fields — now have one.\n\n**Embedding consumers:** additive only, nothing to do. `nxs_foundation::workspace` gained\n`set_invoked_persona`, which the multicall dispatcher calls so the \"no workspace\" message can name\nthe right tool's `init`; a host that never calls it falls back to `argv[0]` exactly as before. No\nsignature moved. The only behavioural change is the wording of two error messages — their error\nkinds (`no_workspace`, `io`) are unchanged, so only a consumer matching on message text is\naffected.\n\n### Facade Contract\n- `changed` · The command line now tells the truth about itself. `nxs mcp --help` claimed to expose \"the active\nmodules' read ops\" while the subcommand right below it documented a switch for writes — the server\nactually offers 33 tools, 20 of which write, so a host that connects can create, update, close and\narchive items. That is now stated plainly, along with the trust it implies. `nxf create --type`\ndocumented `project`, `task` and `issue`: all three are rejected by the program, which accepts the\nactive plugin's own vocabulary (`epic`, `feature`, `bug`, `chore`, `decision` under\n`issue-tracker`). The `--type` filter on `list` and `search` said the same and silently matched\nnothing. `nxs self-update` said it relinks the `nxf`/`nxm` personas and left out `nxc`, which it has\nrelinked since v0.22.0. `nxc workflow tick` had no description at all while `nxc workflow liveness`\ncarried both commands' descriptions glued together; each now describes itself.\n\nRunning any command outside a workspace told everyone to run `nxf init`, whichever tool they had\ntyped. Following that from `nxm` or `nxc` produced a workspace their own module was never registered\nin — the retried command appeared to work, but `nxs prime` replayed nothing for it at session start,\nwhich is the point of the product. Each tool now names its own `init`. A workspace whose replica\nfile cannot be read names the full path it tried instead of a bare `replica.toml` the reader never\ntyped and cannot locate.\n\nHelp texts across `nxf`, `nxm`, `nxc` and `nxs` no longer carry internal ticket ids, references to\nspecification documents that are not shipped, or notes about the argument parser's own limitations.\nOptions that had no description — `nxc send`/`reply`/`ask`'s message kind, priority and disposition,\nand `nxc agents register`'s fields — now have one.\n\n**Embedding consumers:** additive only, nothing to do. `nxs_foundation::workspace` gained\n`set_invoked_persona`, which the multicall dispatcher calls so the \"no workspace\" message can name\nthe right tool's `init`; a host that never calls it falls back to `argv[0]` exactly as before. No\nsignature moved. The only behavioural change is the wording of two error messages — their error\nkinds (`no_workspace`, `io`) are unchanged, so only a consumer matching on message text is\naffected.", - "de": "### Behoben\n- Die Kommandozeile sagt jetzt die Wahrheit über sich selbst. `nxs mcp --help` behauptete, „die\nLese-Operationen der aktiven Module\" anzubieten, während der Unterbefehl direkt darunter einen\nSchalter fürs Schreiben dokumentierte — tatsächlich sind es 33 Werkzeuge, davon 20 schreibend: ein\nverbundener Host kann Einträge anlegen, ändern, schließen und archivieren. Das steht jetzt da, samt\ndem Vertrauen, das es voraussetzt. `nxf create --type` dokumentierte `project`, `task` und `issue` —\nalle drei weist das Programm zurück, es nimmt die Vokabeln des aktiven Plugins (unter\n`issue-tracker`: `epic`, `feature`, `bug`, `chore`, `decision`). Der `--type`-Filter von `list` und\n`search` sagte dasselbe und traf still gar nichts. `nxs self-update` nannte nur die Personas\n`nxf`/`nxm` und ließ `nxc` weg, das es seit v0.22.0 mit verknüpft. `nxc workflow tick` hatte gar\nkeine Beschreibung, während `nxc workflow liveness` die beider Befehle aneinandergehängt trug; jetzt\nbeschreibt sich jeder selbst.\n\nWer einen Befehl außerhalb eines Arbeitsbereichs aufrief, wurde immer auf `nxf init` verwiesen —\ngleich welches Werkzeug er getippt hatte. Wer dem aus `nxm` oder `nxc` heraus folgte, bekam einen\nArbeitsbereich, in dem sein eigenes Modul nie registriert war: der erneute Aufruf schien zu\nfunktionieren, aber `nxs prime` spielte beim Sitzungsstart nichts davon ein — also genau das, wofür\nes das Produkt gibt. Jedes Werkzeug nennt jetzt sein eigenes `init`. Lässt sich die Replica-Datei\neines Arbeitsbereichs nicht lesen, nennt die Meldung den vollen Pfad statt eines blanken\n`replica.toml`, das der Lesende nie getippt hat und nicht finden kann.\n\nDie Hilfetexte von `nxf`, `nxm`, `nxc` und `nxs` tragen keine internen Ticket-Kennungen mehr, keine\nVerweise auf nicht mitgelieferte Spezifikationsdokumente und keine Anmerkungen zu den Grenzen der\nArgument-Bibliothek. Optionen ohne Beschreibung — Art, Dringlichkeit und Zustellung bei `nxc\nsend`/`reply`/`ask` sowie die Felder von `nxc agents register` — haben jetzt eine.\n\n**Für einbettende Anwendungen:** rein additiv, es ist nichts zu tun. `nxs_foundation::workspace`\nhat `set_invoked_persona` bekommen; der Multicall-Dispatcher ruft es auf, damit die Meldung „kein\nArbeitsbereich\" das `init` des richtigen Werkzeugs nennen kann. Ein Wirt, der es nie aufruft, fällt\nwie bisher auf `argv[0]` zurück. Keine Signatur hat sich bewegt. Geändert hat sich allein der\nWortlaut zweier Fehlermeldungen — ihre Fehlerarten (`no_workspace`, `io`) bleiben gleich, betroffen\nist also nur, wer auf den Meldungstext prüft.\n\n### Facade-Kontrakt\n- `changed` · Die Kommandozeile sagt jetzt die Wahrheit über sich selbst. `nxs mcp --help` behauptete, „die\nLese-Operationen der aktiven Module\" anzubieten, während der Unterbefehl direkt darunter einen\nSchalter fürs Schreiben dokumentierte — tatsächlich sind es 33 Werkzeuge, davon 20 schreibend: ein\nverbundener Host kann Einträge anlegen, ändern, schließen und archivieren. Das steht jetzt da, samt\ndem Vertrauen, das es voraussetzt. `nxf create --type` dokumentierte `project`, `task` und `issue` —\nalle drei weist das Programm zurück, es nimmt die Vokabeln des aktiven Plugins (unter\n`issue-tracker`: `epic`, `feature`, `bug`, `chore`, `decision`). Der `--type`-Filter von `list` und\n`search` sagte dasselbe und traf still gar nichts. `nxs self-update` nannte nur die Personas\n`nxf`/`nxm` und ließ `nxc` weg, das es seit v0.22.0 mit verknüpft. `nxc workflow tick` hatte gar\nkeine Beschreibung, während `nxc workflow liveness` die beider Befehle aneinandergehängt trug; jetzt\nbeschreibt sich jeder selbst.\n\nWer einen Befehl außerhalb eines Arbeitsbereichs aufrief, wurde immer auf `nxf init` verwiesen —\ngleich welches Werkzeug er getippt hatte. Wer dem aus `nxm` oder `nxc` heraus folgte, bekam einen\nArbeitsbereich, in dem sein eigenes Modul nie registriert war: der erneute Aufruf schien zu\nfunktionieren, aber `nxs prime` spielte beim Sitzungsstart nichts davon ein — also genau das, wofür\nes das Produkt gibt. Jedes Werkzeug nennt jetzt sein eigenes `init`. Lässt sich die Replica-Datei\neines Arbeitsbereichs nicht lesen, nennt die Meldung den vollen Pfad statt eines blanken\n`replica.toml`, das der Lesende nie getippt hat und nicht finden kann.\n\nDie Hilfetexte von `nxf`, `nxm`, `nxc` und `nxs` tragen keine internen Ticket-Kennungen mehr, keine\nVerweise auf nicht mitgelieferte Spezifikationsdokumente und keine Anmerkungen zu den Grenzen der\nArgument-Bibliothek. Optionen ohne Beschreibung — Art, Dringlichkeit und Zustellung bei `nxc\nsend`/`reply`/`ask` sowie die Felder von `nxc agents register` — haben jetzt eine.\n\n**Für einbettende Anwendungen:** rein additiv, es ist nichts zu tun. `nxs_foundation::workspace`\nhat `set_invoked_persona` bekommen; der Multicall-Dispatcher ruft es auf, damit die Meldung „kein\nArbeitsbereich\" das `init` des richtigen Werkzeugs nennen kann. Ein Wirt, der es nie aufruft, fällt\nwie bisher auf `argv[0]` zurück. Keine Signatur hat sich bewegt. Geändert hat sich allein der\nWortlaut zweier Fehlermeldungen — ihre Fehlerarten (`no_workspace`, `io`) bleiben gleich, betroffen\nist also nur, wer auf den Meldungstext prüft." - } - }, - { - "version": "0.59.1", - "date": "2026-08-10", - "items": [ - { - "type": "fixed", - "en": "`nxf guide running-a-relay` now names the one extra step a Supabase-backed relay needs: Supabase\nsigns its Postgres and pooler certificates with its own root, so a relay pointed at it failed the\nhandshake with `invalid peer certificate: UnknownIssuer` until `NXF_RELAY_PG_CA_FILE` pointed at\nSupabase's certificate. Following the guide as written did not get you a connection; it does now,\nand the note applies to any provider that runs its own CA.", - "de": "`nxf guide running-a-relay` benennt jetzt den einen zusätzlichen Schritt, den ein Relay auf\nSupabase braucht: Supabase signiert seine Postgres- und Pooler-Zertifikate mit einer eigenen\nWurzel, ein darauf gerichteter Relay scheiterte deshalb im Handschlag mit `invalid peer\ncertificate: UnknownIssuer`, solange `NXF_RELAY_PG_CA_FILE` nicht auf das Supabase-Zertifikat\nzeigte. Wer der Anleitung folgte, bekam keine Verbindung; jetzt schon — und der Hinweis gilt für\njeden Anbieter mit eigener CA.", - "unreleased": true - } - ], - "notes": { - "en": "### Fixed\n- `nxf guide running-a-relay` now names the one extra step a Supabase-backed relay needs: Supabase\nsigns its Postgres and pooler certificates with its own root, so a relay pointed at it failed the\nhandshake with `invalid peer certificate: UnknownIssuer` until `NXF_RELAY_PG_CA_FILE` pointed at\nSupabase's certificate. Following the guide as written did not get you a connection; it does now,\nand the note applies to any provider that runs its own CA.", - "de": "### Behoben\n- `nxf guide running-a-relay` benennt jetzt den einen zusätzlichen Schritt, den ein Relay auf\nSupabase braucht: Supabase signiert seine Postgres- und Pooler-Zertifikate mit einer eigenen\nWurzel, ein darauf gerichteter Relay scheiterte deshalb im Handschlag mit `invalid peer\ncertificate: UnknownIssuer`, solange `NXF_RELAY_PG_CA_FILE` nicht auf das Supabase-Zertifikat\nzeigte. Wer der Anleitung folgte, bekam keine Verbindung; jetzt schon — und der Hinweis gilt für\njeden Anbieter mit eigener CA." - } - }, - { - "version": "0.59.0", - "date": "2026-08-10", - "items": [ - { - "type": "added", - "en": "**Public channels — a project's front door.** A channel can now be `public`: readable and\naddressable by anyone in the workspace, membership or not, and discoverable through the new\n`nxc channels public` (`Engine::public_channels` on the library seam). Create one with\n`nxc channels create --public`, or declare it with `kind: public` in `channels.yaml` —\na declaration without a `kind` still means a member-only group channel, exactly as before.\n\nThis is a READ opening and nothing else. Posting into a channel never required membership and\nstill does not; `nxc read` (the read cursor) and `nxc inbox --channel` stay member-only for every\nkind, because a non-member has no read state to advance — their unread count for a public channel\nis `0` by construction. `nxc channels list` and `search` are unchanged too: they still mean \"the\nchannels I am in\", and discovery is its own separate read.\n\nThe facade flag is about ONE thing, and it affects only Rust callers that build a\n`nexus_chat::channel::ChannelDecl` with a struct literal: the struct gained a `kind` field, so such\na literal must now name it (`kind: ChannelKind::Group` reproduces the previous behaviour, or use\n`serde` to load the declaration as before). Nothing else in the API moved, and the opened read gate\nis unreachable for data that already exists — a channel of kind `public` could not be written by\nany earlier version.\n\nOne further behaviour DID change, in a corner, and it is a fix: `ChatStore::is_degraded_dm` used to\nreturn an i/o error for a channel row whose `kind` column was still NULL — a legitimate state,\nsince ops fold one at a time and a row can carry its name before its kind. It now answers the\nquestion it was asked: a channel with no kind is not a degraded DM, so `false`. Reading the column\nas nullable is what lets the new read gate survive that same half-folded row instead of failing it.", - "de": "**Öffentliche Kanäle — die Vordertür eines Projekts.** Ein Kanal kann jetzt `public` sein: von\njedem im Workspace lesbar und adressierbar, mit oder ohne Mitgliedschaft, und über das neue\n`nxc channels public` auffindbar (auf der Bibliotheks-Naht `Engine::public_channels`). Angelegt\nwird er mit `nxc channels create --public` oder deklariert mit `kind: public` in der\n`channels.yaml` — eine Deklaration ohne `kind` bedeutet weiterhin einen Kanal nur für Mitglieder,\ngenau wie bisher.\n\nDas ist eine Öffnung beim LESEN und sonst nichts. Senden hat noch nie eine Mitgliedschaft verlangt\nund verlangt sie weiterhin nicht; `nxc read` (der Lesestand) und `nxc inbox --channel` bleiben für\njede Kanal-Art Mitgliedern vorbehalten, denn wer nicht Mitglied ist, hat keinen Lesestand — seine\nUngelesen-Zahl für einen öffentlichen Kanal ist per Konstruktion `0`. Auch `nxc channels list` und\n`search` bleiben unverändert: sie heißen weiterhin „die Kanäle, in denen ich bin\"; die\nAuffindbarkeit ist ein eigener Aufruf daneben.\n\nDas Fassaden-Kennzeichen betrifft GENAU eine Sache, und zwar nur Rust-Aufrufer, die ein\n`nexus_chat::channel::ChannelDecl` per Struct-Literal bauen: die Struktur hat ein Feld `kind`\nbekommen, das ein solches Literal jetzt mit angeben muss (`kind: ChannelKind::Group` ergibt das\nbisherige Verhalten; wer die Deklaration über `serde` lädt, ändert nichts). Sonst hat sich an der\nAPI nichts bewegt, und das geöffnete Lese-Tor ist für vorhandene Daten gar nicht erreichbar — ein\nKanal der Art `public` konnte von keiner früheren Version geschrieben werden.\n\nEin weiteres Verhalten hat sich doch geändert, in einem Winkel, und zwar als Korrektur:\n`ChatStore::is_degraded_dm` lieferte bisher einen E/A-Fehler für eine Kanal-Zeile, deren Spalte\n`kind` noch NULL war — ein legitimer Zustand, denn Ops werden einzeln eingefaltet und eine Zeile\nkann ihren Namen vor ihrer Art tragen. Jetzt beantwortet die Funktion die Frage, die ihr gestellt\nwurde: ein Kanal ohne Art ist kein degradierter DM, also `false`. Genau dieses Lesen der Spalte als\n„darf leer sein\" ist es, was das neue Lese-Tor dieselbe halb eingefaltete Zeile überstehen lässt,\nstatt an ihr zu scheitern.", - "facade": "breaking" - } - ], - "notes": { - "en": "### Added\n- **Public channels — a project's front door.** A channel can now be `public`: readable and\naddressable by anyone in the workspace, membership or not, and discoverable through the new\n`nxc channels public` (`Engine::public_channels` on the library seam). Create one with\n`nxc channels create --public`, or declare it with `kind: public` in `channels.yaml` —\na declaration without a `kind` still means a member-only group channel, exactly as before.\n\nThis is a READ opening and nothing else. Posting into a channel never required membership and\nstill does not; `nxc read` (the read cursor) and `nxc inbox --channel` stay member-only for every\nkind, because a non-member has no read state to advance — their unread count for a public channel\nis `0` by construction. `nxc channels list` and `search` are unchanged too: they still mean \"the\nchannels I am in\", and discovery is its own separate read.\n\nThe facade flag is about ONE thing, and it affects only Rust callers that build a\n`nexus_chat::channel::ChannelDecl` with a struct literal: the struct gained a `kind` field, so such\na literal must now name it (`kind: ChannelKind::Group` reproduces the previous behaviour, or use\n`serde` to load the declaration as before). Nothing else in the API moved, and the opened read gate\nis unreachable for data that already exists — a channel of kind `public` could not be written by\nany earlier version.\n\nOne further behaviour DID change, in a corner, and it is a fix: `ChatStore::is_degraded_dm` used to\nreturn an i/o error for a channel row whose `kind` column was still NULL — a legitimate state,\nsince ops fold one at a time and a row can carry its name before its kind. It now answers the\nquestion it was asked: a channel with no kind is not a degraded DM, so `false`. Reading the column\nas nullable is what lets the new read gate survive that same half-folded row instead of failing it.\n\n### Facade Contract\n- `breaking` · **Public channels — a project's front door.** A channel can now be `public`: readable and\naddressable by anyone in the workspace, membership or not, and discoverable through the new\n`nxc channels public` (`Engine::public_channels` on the library seam). Create one with\n`nxc channels create --public`, or declare it with `kind: public` in `channels.yaml` —\na declaration without a `kind` still means a member-only group channel, exactly as before.\n\nThis is a READ opening and nothing else. Posting into a channel never required membership and\nstill does not; `nxc read` (the read cursor) and `nxc inbox --channel` stay member-only for every\nkind, because a non-member has no read state to advance — their unread count for a public channel\nis `0` by construction. `nxc channels list` and `search` are unchanged too: they still mean \"the\nchannels I am in\", and discovery is its own separate read.\n\nThe facade flag is about ONE thing, and it affects only Rust callers that build a\n`nexus_chat::channel::ChannelDecl` with a struct literal: the struct gained a `kind` field, so such\na literal must now name it (`kind: ChannelKind::Group` reproduces the previous behaviour, or use\n`serde` to load the declaration as before). Nothing else in the API moved, and the opened read gate\nis unreachable for data that already exists — a channel of kind `public` could not be written by\nany earlier version.\n\nOne further behaviour DID change, in a corner, and it is a fix: `ChatStore::is_degraded_dm` used to\nreturn an i/o error for a channel row whose `kind` column was still NULL — a legitimate state,\nsince ops fold one at a time and a row can carry its name before its kind. It now answers the\nquestion it was asked: a channel with no kind is not a degraded DM, so `false`. Reading the column\nas nullable is what lets the new read gate survive that same half-folded row instead of failing it.", - "de": "### Neu\n- **Öffentliche Kanäle — die Vordertür eines Projekts.** Ein Kanal kann jetzt `public` sein: von\njedem im Workspace lesbar und adressierbar, mit oder ohne Mitgliedschaft, und über das neue\n`nxc channels public` auffindbar (auf der Bibliotheks-Naht `Engine::public_channels`). Angelegt\nwird er mit `nxc channels create --public` oder deklariert mit `kind: public` in der\n`channels.yaml` — eine Deklaration ohne `kind` bedeutet weiterhin einen Kanal nur für Mitglieder,\ngenau wie bisher.\n\nDas ist eine Öffnung beim LESEN und sonst nichts. Senden hat noch nie eine Mitgliedschaft verlangt\nund verlangt sie weiterhin nicht; `nxc read` (der Lesestand) und `nxc inbox --channel` bleiben für\njede Kanal-Art Mitgliedern vorbehalten, denn wer nicht Mitglied ist, hat keinen Lesestand — seine\nUngelesen-Zahl für einen öffentlichen Kanal ist per Konstruktion `0`. Auch `nxc channels list` und\n`search` bleiben unverändert: sie heißen weiterhin „die Kanäle, in denen ich bin\"; die\nAuffindbarkeit ist ein eigener Aufruf daneben.\n\nDas Fassaden-Kennzeichen betrifft GENAU eine Sache, und zwar nur Rust-Aufrufer, die ein\n`nexus_chat::channel::ChannelDecl` per Struct-Literal bauen: die Struktur hat ein Feld `kind`\nbekommen, das ein solches Literal jetzt mit angeben muss (`kind: ChannelKind::Group` ergibt das\nbisherige Verhalten; wer die Deklaration über `serde` lädt, ändert nichts). Sonst hat sich an der\nAPI nichts bewegt, und das geöffnete Lese-Tor ist für vorhandene Daten gar nicht erreichbar — ein\nKanal der Art `public` konnte von keiner früheren Version geschrieben werden.\n\nEin weiteres Verhalten hat sich doch geändert, in einem Winkel, und zwar als Korrektur:\n`ChatStore::is_degraded_dm` lieferte bisher einen E/A-Fehler für eine Kanal-Zeile, deren Spalte\n`kind` noch NULL war — ein legitimer Zustand, denn Ops werden einzeln eingefaltet und eine Zeile\nkann ihren Namen vor ihrer Art tragen. Jetzt beantwortet die Funktion die Frage, die ihr gestellt\nwurde: ein Kanal ohne Art ist kein degradierter DM, also `false`. Genau dieses Lesen der Spalte als\n„darf leer sein\" ist es, was das neue Lese-Tor dieselbe halb eingefaltete Zeile überstehen lässt,\nstatt an ihr zu scheitern.\n\n### Facade-Kontrakt\n- `breaking` · **Öffentliche Kanäle — die Vordertür eines Projekts.** Ein Kanal kann jetzt `public` sein: von\njedem im Workspace lesbar und adressierbar, mit oder ohne Mitgliedschaft, und über das neue\n`nxc channels public` auffindbar (auf der Bibliotheks-Naht `Engine::public_channels`). Angelegt\nwird er mit `nxc channels create --public` oder deklariert mit `kind: public` in der\n`channels.yaml` — eine Deklaration ohne `kind` bedeutet weiterhin einen Kanal nur für Mitglieder,\ngenau wie bisher.\n\nDas ist eine Öffnung beim LESEN und sonst nichts. Senden hat noch nie eine Mitgliedschaft verlangt\nund verlangt sie weiterhin nicht; `nxc read` (der Lesestand) und `nxc inbox --channel` bleiben für\njede Kanal-Art Mitgliedern vorbehalten, denn wer nicht Mitglied ist, hat keinen Lesestand — seine\nUngelesen-Zahl für einen öffentlichen Kanal ist per Konstruktion `0`. Auch `nxc channels list` und\n`search` bleiben unverändert: sie heißen weiterhin „die Kanäle, in denen ich bin\"; die\nAuffindbarkeit ist ein eigener Aufruf daneben.\n\nDas Fassaden-Kennzeichen betrifft GENAU eine Sache, und zwar nur Rust-Aufrufer, die ein\n`nexus_chat::channel::ChannelDecl` per Struct-Literal bauen: die Struktur hat ein Feld `kind`\nbekommen, das ein solches Literal jetzt mit angeben muss (`kind: ChannelKind::Group` ergibt das\nbisherige Verhalten; wer die Deklaration über `serde` lädt, ändert nichts). Sonst hat sich an der\nAPI nichts bewegt, und das geöffnete Lese-Tor ist für vorhandene Daten gar nicht erreichbar — ein\nKanal der Art `public` konnte von keiner früheren Version geschrieben werden.\n\nEin weiteres Verhalten hat sich doch geändert, in einem Winkel, und zwar als Korrektur:\n`ChatStore::is_degraded_dm` lieferte bisher einen E/A-Fehler für eine Kanal-Zeile, deren Spalte\n`kind` noch NULL war — ein legitimer Zustand, denn Ops werden einzeln eingefaltet und eine Zeile\nkann ihren Namen vor ihrer Art tragen. Jetzt beantwortet die Funktion die Frage, die ihr gestellt\nwurde: ein Kanal ohne Art ist kein degradierter DM, also `false`. Genau dieses Lesen der Spalte als\n„darf leer sein\" ist es, was das neue Lese-Tor dieselbe halb eingefaltete Zeile überstehen lässt,\nstatt an ihr zu scheitern." - } - }, - { - "version": "0.58.0", - "date": "2026-08-09", - "items": [ - { - "type": "changed", - "en": "The sync relay now carries envelope fields it does not itself understand. A newer client can add a\nfield — the end-to-end op signature the auth slice will introduce is the one this exists for — and\nit survives the round trip through an older relay unchanged, on all three backends (SQLite,\nPostgres, DynamoDB). Until now the relay rebuilt every op from its own named columns, so an\nunknown field was dropped in transit and the receiving peer saw a well-formed op with the field\nsilently missing. Existing relay databases gain the passthrough column on the next start; nothing\nhas to be re-pushed.\n\nChat's read surfaces — inbox, unread, thread boards, transcripts — now name the author of the op a\nmessage rode in on, rather than the sender its payload declares. For anything written locally the\ntwo are the same by construction. They can differ only on a message that arrived over sync, where\nthe payload's sender is a claim the sender itself writes; the op's author is the identity the auth\nslice will authenticate. A message that names no author at all is kept in the log but no longer\nshown.\n\n**Embedding consumers:** this narrows the same write API v0.57.0 narrowed. An `actor` is now\nrejected not only when it is empty or whitespace-only, but also when it consists solely of\ncharacters that render as nothing — a zero-width space or joiner, a bidi mark, a BOM, none of which\n`trim` strips. It affects every write entry point that takes an `actor` (`write::create`/`update`/\n`claim`/`close`/`dep_*`/`mention_*`/`contributes_*`/`label_*`/`thread_link_*`/`archive`/\n`unarchive`/`note_add`, memory's `remember`/`classify`/`reorder`/`forget`/`migrate_apply`, chat's\n`send`/`reply`/`ask`/`set_expects`), which now return a `validation` error for such an actor where\nthey previously wrote an op whose author displays as an empty name. No signature changed. If your\napp passes a real identity, nothing changes for you.", - "de": "Der Sync-Relay transportiert jetzt auch Envelope-Felder, die er selbst nicht kennt. Ein neuerer\nClient kann ein Feld ergänzen — gedacht ist es für die Ende-zu-Ende-Signatur der kommenden\nAuth-Stufe — und es übersteht den Weg über einen älteren Relay unverändert, auf allen drei\nBackends (SQLite, Postgres, DynamoDB). Bisher baute der Relay jede Op aus seinen eigenen benannten\nSpalten neu zusammen; ein unbekanntes Feld ging dabei verloren, und der empfangende Peer sah eine\nwohlgeformte Op, der es stillschweigend fehlte. Bestehende Relay-Datenbanken bekommen die\nDurchreiche-Spalte beim nächsten Start; es muss nichts erneut gepusht werden.\n\nDie Leseflächen von Chat — Inbox, Ungelesenes, Thread-Boards, Transkripte — nennen jetzt den Autor\nder Op, mit der eine Nachricht kam, statt des Absenders, den ihre Nutzlast behauptet. Für alles\nlokal Geschriebene sind beide per Konstruktion dieselben. Auseinandergehen können sie nur bei einer\nüber Sync eingetroffenen Nachricht: dort ist der Absender in der Nutzlast eine Behauptung des\nAbsenders selbst, während der Autor der Op die Identität ist, die die Auth-Stufe authentifiziert.\nEine Nachricht ganz ohne Autor bleibt im Log, wird aber nicht mehr angezeigt.\n\n**Für einbettende Anwendungen:** das engt dieselbe Schreib-API weiter ein, die v0.57.0 eingeengt\nhat. Ein `actor` wird jetzt nicht mehr nur abgelehnt, wenn er leer ist oder nur aus Leerraum\nbesteht, sondern auch dann, wenn er ausschließlich aus Zeichen besteht, die nichts darstellen —\nein Zero-Width-Space oder -Joiner, eine Bidi-Marke, ein BOM; keines davon entfernt `trim`. Betroffen\nist jeder Schreib-Einstiegspunkt mit `actor` (`write::create`/`update`/`claim`/`close`/`dep_*`/\n`mention_*`/`contributes_*`/`label_*`/`thread_link_*`/`archive`/`unarchive`/`note_add`, in memory\n`remember`/`classify`/`reorder`/`forget`/`migrate_apply`, in chat `send`/`reply`/`ask`/\n`set_expects`) — sie liefern für einen solchen Actor jetzt einen `validation`-Fehler, wo vorher eine\nOp geschrieben wurde, deren Autor als leerer Name erscheint. Keine Signatur hat sich geändert.\nÜbergibt Deine Anwendung eine echte Identität, ändert sich für Dich nichts.", - "facade": "breaking" - }, - { - "type": "changed", - "en": "The release notes are now a reliable answer to \"does this version change the consumed library\ncontract?\". The SemVer gate no longer watches `nexus-flow-facade` alone — it covers `nexus-chat`\nand `nexus-memory` too, the other two crates a consumer pins by git tag in lockstep. And because\nno API-shape diff can see a behavioural break behind unchanged signatures, a pull request that\ntouches any of those surfaces must now state its facade impact explicitly (`none`, `changed` or\n`breaking`); an omitted marker used to be indistinguishable from \"checked, no impact\" and is now\nrejected.", - "de": "Die Versionshinweise beantworten jetzt verlässlich, ob eine Fassung den konsumierten\nBibliothekskontrakt ändert. Das SemVer-Tor bewacht nicht mehr nur `nexus-flow-facade`, sondern\nebenso `nexus-chat` und `nexus-memory` — die beiden anderen Kisten, die ein Konsument im Gleichschritt\nper Git-Tag pinnt. Und weil kein Vergleich der API-Gestalt einen Verhaltensbruch hinter unveränderten\nSignaturen sehen kann, muss ein Pull Request, der eine dieser Flächen berührt, seine Facade-Auswirkung\nab sofort ausdrücklich benennen (`none`, `changed` oder `breaking`); ein fehlender Marker war bislang\nvon „geprüft, keine Auswirkung\" nicht zu unterscheiden und wird jetzt abgewiesen." - } - ], - "notes": { - "en": "### Changed\n- The sync relay now carries envelope fields it does not itself understand. A newer client can add a\nfield — the end-to-end op signature the auth slice will introduce is the one this exists for — and\nit survives the round trip through an older relay unchanged, on all three backends (SQLite,\nPostgres, DynamoDB). Until now the relay rebuilt every op from its own named columns, so an\nunknown field was dropped in transit and the receiving peer saw a well-formed op with the field\nsilently missing. Existing relay databases gain the passthrough column on the next start; nothing\nhas to be re-pushed.\n\nChat's read surfaces — inbox, unread, thread boards, transcripts — now name the author of the op a\nmessage rode in on, rather than the sender its payload declares. For anything written locally the\ntwo are the same by construction. They can differ only on a message that arrived over sync, where\nthe payload's sender is a claim the sender itself writes; the op's author is the identity the auth\nslice will authenticate. A message that names no author at all is kept in the log but no longer\nshown.\n\n**Embedding consumers:** this narrows the same write API v0.57.0 narrowed. An `actor` is now\nrejected not only when it is empty or whitespace-only, but also when it consists solely of\ncharacters that render as nothing — a zero-width space or joiner, a bidi mark, a BOM, none of which\n`trim` strips. It affects every write entry point that takes an `actor` (`write::create`/`update`/\n`claim`/`close`/`dep_*`/`mention_*`/`contributes_*`/`label_*`/`thread_link_*`/`archive`/\n`unarchive`/`note_add`, memory's `remember`/`classify`/`reorder`/`forget`/`migrate_apply`, chat's\n`send`/`reply`/`ask`/`set_expects`), which now return a `validation` error for such an actor where\nthey previously wrote an op whose author displays as an empty name. No signature changed. If your\napp passes a real identity, nothing changes for you.\n- The release notes are now a reliable answer to \"does this version change the consumed library\ncontract?\". The SemVer gate no longer watches `nexus-flow-facade` alone — it covers `nexus-chat`\nand `nexus-memory` too, the other two crates a consumer pins by git tag in lockstep. And because\nno API-shape diff can see a behavioural break behind unchanged signatures, a pull request that\ntouches any of those surfaces must now state its facade impact explicitly (`none`, `changed` or\n`breaking`); an omitted marker used to be indistinguishable from \"checked, no impact\" and is now\nrejected.\n\n### Facade Contract\n- `breaking` · The sync relay now carries envelope fields it does not itself understand. A newer client can add a\nfield — the end-to-end op signature the auth slice will introduce is the one this exists for — and\nit survives the round trip through an older relay unchanged, on all three backends (SQLite,\nPostgres, DynamoDB). Until now the relay rebuilt every op from its own named columns, so an\nunknown field was dropped in transit and the receiving peer saw a well-formed op with the field\nsilently missing. Existing relay databases gain the passthrough column on the next start; nothing\nhas to be re-pushed.\n\nChat's read surfaces — inbox, unread, thread boards, transcripts — now name the author of the op a\nmessage rode in on, rather than the sender its payload declares. For anything written locally the\ntwo are the same by construction. They can differ only on a message that arrived over sync, where\nthe payload's sender is a claim the sender itself writes; the op's author is the identity the auth\nslice will authenticate. A message that names no author at all is kept in the log but no longer\nshown.\n\n**Embedding consumers:** this narrows the same write API v0.57.0 narrowed. An `actor` is now\nrejected not only when it is empty or whitespace-only, but also when it consists solely of\ncharacters that render as nothing — a zero-width space or joiner, a bidi mark, a BOM, none of which\n`trim` strips. It affects every write entry point that takes an `actor` (`write::create`/`update`/\n`claim`/`close`/`dep_*`/`mention_*`/`contributes_*`/`label_*`/`thread_link_*`/`archive`/\n`unarchive`/`note_add`, memory's `remember`/`classify`/`reorder`/`forget`/`migrate_apply`, chat's\n`send`/`reply`/`ask`/`set_expects`), which now return a `validation` error for such an actor where\nthey previously wrote an op whose author displays as an empty name. No signature changed. If your\napp passes a real identity, nothing changes for you.", - "de": "### Geändert\n- Der Sync-Relay transportiert jetzt auch Envelope-Felder, die er selbst nicht kennt. Ein neuerer\nClient kann ein Feld ergänzen — gedacht ist es für die Ende-zu-Ende-Signatur der kommenden\nAuth-Stufe — und es übersteht den Weg über einen älteren Relay unverändert, auf allen drei\nBackends (SQLite, Postgres, DynamoDB). Bisher baute der Relay jede Op aus seinen eigenen benannten\nSpalten neu zusammen; ein unbekanntes Feld ging dabei verloren, und der empfangende Peer sah eine\nwohlgeformte Op, der es stillschweigend fehlte. Bestehende Relay-Datenbanken bekommen die\nDurchreiche-Spalte beim nächsten Start; es muss nichts erneut gepusht werden.\n\nDie Leseflächen von Chat — Inbox, Ungelesenes, Thread-Boards, Transkripte — nennen jetzt den Autor\nder Op, mit der eine Nachricht kam, statt des Absenders, den ihre Nutzlast behauptet. Für alles\nlokal Geschriebene sind beide per Konstruktion dieselben. Auseinandergehen können sie nur bei einer\nüber Sync eingetroffenen Nachricht: dort ist der Absender in der Nutzlast eine Behauptung des\nAbsenders selbst, während der Autor der Op die Identität ist, die die Auth-Stufe authentifiziert.\nEine Nachricht ganz ohne Autor bleibt im Log, wird aber nicht mehr angezeigt.\n\n**Für einbettende Anwendungen:** das engt dieselbe Schreib-API weiter ein, die v0.57.0 eingeengt\nhat. Ein `actor` wird jetzt nicht mehr nur abgelehnt, wenn er leer ist oder nur aus Leerraum\nbesteht, sondern auch dann, wenn er ausschließlich aus Zeichen besteht, die nichts darstellen —\nein Zero-Width-Space oder -Joiner, eine Bidi-Marke, ein BOM; keines davon entfernt `trim`. Betroffen\nist jeder Schreib-Einstiegspunkt mit `actor` (`write::create`/`update`/`claim`/`close`/`dep_*`/\n`mention_*`/`contributes_*`/`label_*`/`thread_link_*`/`archive`/`unarchive`/`note_add`, in memory\n`remember`/`classify`/`reorder`/`forget`/`migrate_apply`, in chat `send`/`reply`/`ask`/\n`set_expects`) — sie liefern für einen solchen Actor jetzt einen `validation`-Fehler, wo vorher eine\nOp geschrieben wurde, deren Autor als leerer Name erscheint. Keine Signatur hat sich geändert.\nÜbergibt Deine Anwendung eine echte Identität, ändert sich für Dich nichts.\n- Die Versionshinweise beantworten jetzt verlässlich, ob eine Fassung den konsumierten\nBibliothekskontrakt ändert. Das SemVer-Tor bewacht nicht mehr nur `nexus-flow-facade`, sondern\nebenso `nexus-chat` und `nexus-memory` — die beiden anderen Kisten, die ein Konsument im Gleichschritt\nper Git-Tag pinnt. Und weil kein Vergleich der API-Gestalt einen Verhaltensbruch hinter unveränderten\nSignaturen sehen kann, muss ein Pull Request, der eine dieser Flächen berührt, seine Facade-Auswirkung\nab sofort ausdrücklich benennen (`none`, `changed` oder `breaking`); ein fehlender Marker war bislang\nvon „geprüft, keine Auswirkung\" nicht zu unterscheiden und wird jetzt abgewiesen.\n\n### Facade-Kontrakt\n- `breaking` · Der Sync-Relay transportiert jetzt auch Envelope-Felder, die er selbst nicht kennt. Ein neuerer\nClient kann ein Feld ergänzen — gedacht ist es für die Ende-zu-Ende-Signatur der kommenden\nAuth-Stufe — und es übersteht den Weg über einen älteren Relay unverändert, auf allen drei\nBackends (SQLite, Postgres, DynamoDB). Bisher baute der Relay jede Op aus seinen eigenen benannten\nSpalten neu zusammen; ein unbekanntes Feld ging dabei verloren, und der empfangende Peer sah eine\nwohlgeformte Op, der es stillschweigend fehlte. Bestehende Relay-Datenbanken bekommen die\nDurchreiche-Spalte beim nächsten Start; es muss nichts erneut gepusht werden.\n\nDie Leseflächen von Chat — Inbox, Ungelesenes, Thread-Boards, Transkripte — nennen jetzt den Autor\nder Op, mit der eine Nachricht kam, statt des Absenders, den ihre Nutzlast behauptet. Für alles\nlokal Geschriebene sind beide per Konstruktion dieselben. Auseinandergehen können sie nur bei einer\nüber Sync eingetroffenen Nachricht: dort ist der Absender in der Nutzlast eine Behauptung des\nAbsenders selbst, während der Autor der Op die Identität ist, die die Auth-Stufe authentifiziert.\nEine Nachricht ganz ohne Autor bleibt im Log, wird aber nicht mehr angezeigt.\n\n**Für einbettende Anwendungen:** das engt dieselbe Schreib-API weiter ein, die v0.57.0 eingeengt\nhat. Ein `actor` wird jetzt nicht mehr nur abgelehnt, wenn er leer ist oder nur aus Leerraum\nbesteht, sondern auch dann, wenn er ausschließlich aus Zeichen besteht, die nichts darstellen —\nein Zero-Width-Space oder -Joiner, eine Bidi-Marke, ein BOM; keines davon entfernt `trim`. Betroffen\nist jeder Schreib-Einstiegspunkt mit `actor` (`write::create`/`update`/`claim`/`close`/`dep_*`/\n`mention_*`/`contributes_*`/`label_*`/`thread_link_*`/`archive`/`unarchive`/`note_add`, in memory\n`remember`/`classify`/`reorder`/`forget`/`migrate_apply`, in chat `send`/`reply`/`ask`/\n`set_expects`) — sie liefern für einen solchen Actor jetzt einen `validation`-Fehler, wo vorher eine\nOp geschrieben wurde, deren Autor als leerer Name erscheint. Keine Signatur hat sich geändert.\nÜbergibt Deine Anwendung eine echte Identität, ändert sich für Dich nichts." - } - }, - { - "version": "0.57.0", - "date": "2026-08-09", - "items": [ - { - "type": "fixed", - "en": "Ops can no longer be written with a blank author. A `NXF_ACTOR`/`NXM_ACTOR`/`NXC_ACTOR`/`NXS_ACTOR`\nor `USER` that is set but empty now counts as unset and falls through to the usual fallback,\ninstead of stamping every op of that session with no author at all — attribution on an append-only\nlog cannot be repaired afterwards. The same applies to an `actor` passed to an MCP tool call.\n\n**Embedding consumers:** this is a behavioural break on the `nexus-flow-facade` write API. Every\nwrite entry point that takes an `actor` — `write::create`/`update`/`claim`/`close`/`dep_*`/\n`mention_*`/`contributes_*`/`label_*`/`thread_link_*`/`archive`/`unarchive`/`note_add`, memory's\n`remember`/`classify`/`reorder`/`forget`/`migrate_apply`, and chat's `send`/`reply`/`ask`/\n`set_expects` — now returns a `validation` error when the actor is empty or whitespace-only, where\nit previously wrote an unattributed op and succeeded. No signature changed. If your app can pass an\nunvalidated actor through to a write, handle the new error; if it always passes a real identity,\nnothing changes for you.", - "de": "Ops können nicht mehr ohne Autor geschrieben werden. Ein gesetztes, aber leeres\n`NXF_ACTOR`/`NXM_ACTOR`/`NXC_ACTOR`/`NXS_ACTOR` oder `USER` gilt jetzt als nicht gesetzt und fällt\nauf die übliche Ersatzkennung zurück, statt sämtliche Ops dieser Sitzung ohne jeden Autor zu\nstempeln — Zuordnung lässt sich auf einem Append-only-Log nachträglich nicht mehr herstellen.\nDasselbe gilt für einen `actor`, der einem MCP-Tool-Aufruf mitgegeben wird.\n\n**Für einbettende Anwendungen:** das ist ein Verhaltensbruch an der Schreib-API von\n`nexus-flow-facade`. Jeder Schreib-Einstiegspunkt mit `actor` — `write::create`/`update`/`claim`/\n`close`/`dep_*`/`mention_*`/`contributes_*`/`label_*`/`thread_link_*`/`archive`/`unarchive`/\n`note_add`, in memory `remember`/`classify`/`reorder`/`forget`/`migrate_apply`, in chat `send`/\n`reply`/`ask`/`set_expects` — liefert jetzt einen `validation`-Fehler, wenn der Actor leer ist oder\nnur aus Leerraum besteht, wo vorher eine Op ohne Zuordnung geschrieben und der Aufruf erfolgreich\nwar. Keine Signatur hat sich geändert. Wenn Deine Anwendung einen ungeprüften Actor bis zum\nSchreibvorgang durchreichen kann, behandle den neuen Fehler; wenn sie immer eine echte Identität\nübergibt, ändert sich für Dich nichts.", - "facade": "breaking" - }, - { - "type": "changed", - "en": "A sync pull no longer ends a pass because a page came back empty or shorter than requested; it ends\nwhen the relay's cursor stops advancing. Against today's relay the behaviour is unchanged apart\nfrom one extra request per pass. A relay that serves a reader only part of a stream — what\nauthorization will do once it lands — is now drained correctly instead of stalling on the first\nstretch the reader may not see.", - "de": "Ein Sync-Pull endet nicht mehr deshalb, weil eine Seite leer oder kürzer als angefordert zurückkam,\nsondern erst, wenn der Cursor des Relays stehen bleibt. Gegenüber dem heutigen Relay ändert sich\ndas Verhalten bis auf eine zusätzliche Anfrage pro Durchlauf nicht. Ein Relay, das einem Leser nur\neinen Teil eines Streams ausliefert — was die kommende Autorisierung tun wird —, wird jetzt\nvollständig abgeholt, statt am ersten für den Leser unsichtbaren Abschnitt hängenzubleiben." - } - ], - "notes": { - "en": "### Changed\n- A sync pull no longer ends a pass because a page came back empty or shorter than requested; it ends\nwhen the relay's cursor stops advancing. Against today's relay the behaviour is unchanged apart\nfrom one extra request per pass. A relay that serves a reader only part of a stream — what\nauthorization will do once it lands — is now drained correctly instead of stalling on the first\nstretch the reader may not see.\n\n### Fixed\n- Ops can no longer be written with a blank author. A `NXF_ACTOR`/`NXM_ACTOR`/`NXC_ACTOR`/`NXS_ACTOR`\nor `USER` that is set but empty now counts as unset and falls through to the usual fallback,\ninstead of stamping every op of that session with no author at all — attribution on an append-only\nlog cannot be repaired afterwards. The same applies to an `actor` passed to an MCP tool call.\n\n**Embedding consumers:** this is a behavioural break on the `nexus-flow-facade` write API. Every\nwrite entry point that takes an `actor` — `write::create`/`update`/`claim`/`close`/`dep_*`/\n`mention_*`/`contributes_*`/`label_*`/`thread_link_*`/`archive`/`unarchive`/`note_add`, memory's\n`remember`/`classify`/`reorder`/`forget`/`migrate_apply`, and chat's `send`/`reply`/`ask`/\n`set_expects` — now returns a `validation` error when the actor is empty or whitespace-only, where\nit previously wrote an unattributed op and succeeded. No signature changed. If your app can pass an\nunvalidated actor through to a write, handle the new error; if it always passes a real identity,\nnothing changes for you.\n\n### Facade Contract\n- `breaking` · Ops can no longer be written with a blank author. A `NXF_ACTOR`/`NXM_ACTOR`/`NXC_ACTOR`/`NXS_ACTOR`\nor `USER` that is set but empty now counts as unset and falls through to the usual fallback,\ninstead of stamping every op of that session with no author at all — attribution on an append-only\nlog cannot be repaired afterwards. The same applies to an `actor` passed to an MCP tool call.\n\n**Embedding consumers:** this is a behavioural break on the `nexus-flow-facade` write API. Every\nwrite entry point that takes an `actor` — `write::create`/`update`/`claim`/`close`/`dep_*`/\n`mention_*`/`contributes_*`/`label_*`/`thread_link_*`/`archive`/`unarchive`/`note_add`, memory's\n`remember`/`classify`/`reorder`/`forget`/`migrate_apply`, and chat's `send`/`reply`/`ask`/\n`set_expects` — now returns a `validation` error when the actor is empty or whitespace-only, where\nit previously wrote an unattributed op and succeeded. No signature changed. If your app can pass an\nunvalidated actor through to a write, handle the new error; if it always passes a real identity,\nnothing changes for you.", - "de": "### Geändert\n- Ein Sync-Pull endet nicht mehr deshalb, weil eine Seite leer oder kürzer als angefordert zurückkam,\nsondern erst, wenn der Cursor des Relays stehen bleibt. Gegenüber dem heutigen Relay ändert sich\ndas Verhalten bis auf eine zusätzliche Anfrage pro Durchlauf nicht. Ein Relay, das einem Leser nur\neinen Teil eines Streams ausliefert — was die kommende Autorisierung tun wird —, wird jetzt\nvollständig abgeholt, statt am ersten für den Leser unsichtbaren Abschnitt hängenzubleiben.\n\n### Behoben\n- Ops können nicht mehr ohne Autor geschrieben werden. Ein gesetztes, aber leeres\n`NXF_ACTOR`/`NXM_ACTOR`/`NXC_ACTOR`/`NXS_ACTOR` oder `USER` gilt jetzt als nicht gesetzt und fällt\nauf die übliche Ersatzkennung zurück, statt sämtliche Ops dieser Sitzung ohne jeden Autor zu\nstempeln — Zuordnung lässt sich auf einem Append-only-Log nachträglich nicht mehr herstellen.\nDasselbe gilt für einen `actor`, der einem MCP-Tool-Aufruf mitgegeben wird.\n\n**Für einbettende Anwendungen:** das ist ein Verhaltensbruch an der Schreib-API von\n`nexus-flow-facade`. Jeder Schreib-Einstiegspunkt mit `actor` — `write::create`/`update`/`claim`/\n`close`/`dep_*`/`mention_*`/`contributes_*`/`label_*`/`thread_link_*`/`archive`/`unarchive`/\n`note_add`, in memory `remember`/`classify`/`reorder`/`forget`/`migrate_apply`, in chat `send`/\n`reply`/`ask`/`set_expects` — liefert jetzt einen `validation`-Fehler, wenn der Actor leer ist oder\nnur aus Leerraum besteht, wo vorher eine Op ohne Zuordnung geschrieben und der Aufruf erfolgreich\nwar. Keine Signatur hat sich geändert. Wenn Deine Anwendung einen ungeprüften Actor bis zum\nSchreibvorgang durchreichen kann, behandle den neuen Fehler; wenn sie immer eine echte Identität\nübergibt, ändert sich für Dich nichts.\n\n### Facade-Kontrakt\n- `breaking` · Ops können nicht mehr ohne Autor geschrieben werden. Ein gesetztes, aber leeres\n`NXF_ACTOR`/`NXM_ACTOR`/`NXC_ACTOR`/`NXS_ACTOR` oder `USER` gilt jetzt als nicht gesetzt und fällt\nauf die übliche Ersatzkennung zurück, statt sämtliche Ops dieser Sitzung ohne jeden Autor zu\nstempeln — Zuordnung lässt sich auf einem Append-only-Log nachträglich nicht mehr herstellen.\nDasselbe gilt für einen `actor`, der einem MCP-Tool-Aufruf mitgegeben wird.\n\n**Für einbettende Anwendungen:** das ist ein Verhaltensbruch an der Schreib-API von\n`nexus-flow-facade`. Jeder Schreib-Einstiegspunkt mit `actor` — `write::create`/`update`/`claim`/\n`close`/`dep_*`/`mention_*`/`contributes_*`/`label_*`/`thread_link_*`/`archive`/`unarchive`/\n`note_add`, in memory `remember`/`classify`/`reorder`/`forget`/`migrate_apply`, in chat `send`/\n`reply`/`ask`/`set_expects` — liefert jetzt einen `validation`-Fehler, wenn der Actor leer ist oder\nnur aus Leerraum besteht, wo vorher eine Op ohne Zuordnung geschrieben und der Aufruf erfolgreich\nwar. Keine Signatur hat sich geändert. Wenn Deine Anwendung einen ungeprüften Actor bis zum\nSchreibvorgang durchreichen kann, behandle den neuen Fehler; wenn sie immer eine echte Identität\nübergibt, ändert sich für Dich nichts." - } - }, - { - "version": "0.56.0", - "date": "2026-08-08", - "items": [ - { - "type": "changed", - "en": "**An unusable `NXF_RELAY_BACKEND` now says what THIS relay build can do.** The old message\nlisted all three backends and suggested rebuilding with `--features postgres` — a feature the\nreleased relay has carried since 0.55.0, which left operators guessing which one they were\nactually missing. It now reads, for the shipped binary: `this build carries: sqlite, postgres.\n`dynamodb` is a cargo feature this build was compiled WITHOUT …`, and a plain typo is no longer\nanswered with a rebuild instruction.", - "de": "**Ein unbrauchbares `NXF_RELAY_BACKEND` nennt jetzt, was DIESER Relay-Build kann.** Die alte\nMeldung zählte alle drei Backends auf und riet zum Neubau mit `--features postgres` — einem\nFeature, das die ausgelieferte Relay seit 0.55.0 mitbringt; der Betreiber musste raten, welches\nihm wirklich fehlt. Jetzt steht dort, für die ausgelieferte Binary: `this build carries: sqlite,\npostgres. `dynamodb` is a cargo feature this build was compiled WITHOUT …` — und ein simpler\nTippfehler wird nicht mehr mit einer Neubau-Anweisung beantwortet.", - "unreleased": true - }, - { - "type": "removed", - "en": "**The old delivery origin `nxf.nxsflow.com` is switched off for good.** Downloads, `install.sh`\nand `nxs self-update` have been served from `https://nxsflow.com/nxs` since the cutover, and the\nold chain has now been torn down — its hostnames no longer resolve and its AWS stacks are gone.\nNothing to do if you installed or updated with a recent version; only an install that pins the\nretired origin by hand (`NXF_BASE_URL=https://nxf.nxsflow.com`) needs that variable dropped or\npointed at `https://nxsflow.com/nxs`.", - "de": "**Der alte Auslieferungs-Ursprung `nxf.nxsflow.com` ist endgültig abgeschaltet.** Downloads,\n`install.sh` und `nxs self-update` laufen seit der Umstellung über `https://nxsflow.com/nxs`; die\nalte Kette ist jetzt abgebaut — ihre Hostnamen lösen nicht mehr auf, ihre AWS-Stacks sind gelöscht.\nWer mit einer aktuellen Version installiert oder aktualisiert hat, muss nichts tun; nur eine\nInstallation, die den alten Ursprung von Hand festhält (`NXF_BASE_URL=https://nxf.nxsflow.com`),\nlässt die Variable weg oder setzt sie auf `https://nxsflow.com/nxs`." - } - ], - "notes": { - "en": "### Changed\n- **An unusable `NXF_RELAY_BACKEND` now says what THIS relay build can do.** The old message\nlisted all three backends and suggested rebuilding with `--features postgres` — a feature the\nreleased relay has carried since 0.55.0, which left operators guessing which one they were\nactually missing. It now reads, for the shipped binary: `this build carries: sqlite, postgres.\n`dynamodb` is a cargo feature this build was compiled WITHOUT …`, and a plain typo is no longer\nanswered with a rebuild instruction.\n\n### Removed\n- **The old delivery origin `nxf.nxsflow.com` is switched off for good.** Downloads, `install.sh`\nand `nxs self-update` have been served from `https://nxsflow.com/nxs` since the cutover, and the\nold chain has now been torn down — its hostnames no longer resolve and its AWS stacks are gone.\nNothing to do if you installed or updated with a recent version; only an install that pins the\nretired origin by hand (`NXF_BASE_URL=https://nxf.nxsflow.com`) needs that variable dropped or\npointed at `https://nxsflow.com/nxs`.", - "de": "### Geändert\n- **Ein unbrauchbares `NXF_RELAY_BACKEND` nennt jetzt, was DIESER Relay-Build kann.** Die alte\nMeldung zählte alle drei Backends auf und riet zum Neubau mit `--features postgres` — einem\nFeature, das die ausgelieferte Relay seit 0.55.0 mitbringt; der Betreiber musste raten, welches\nihm wirklich fehlt. Jetzt steht dort, für die ausgelieferte Binary: `this build carries: sqlite,\npostgres. `dynamodb` is a cargo feature this build was compiled WITHOUT …` — und ein simpler\nTippfehler wird nicht mehr mit einer Neubau-Anweisung beantwortet.\n\n### Entfernt\n- **Der alte Auslieferungs-Ursprung `nxf.nxsflow.com` ist endgültig abgeschaltet.** Downloads,\n`install.sh` und `nxs self-update` laufen seit der Umstellung über `https://nxsflow.com/nxs`; die\nalte Kette ist jetzt abgebaut — ihre Hostnamen lösen nicht mehr auf, ihre AWS-Stacks sind gelöscht.\nWer mit einer aktuellen Version installiert oder aktualisiert hat, muss nichts tun; nur eine\nInstallation, die den alten Ursprung von Hand festhält (`NXF_BASE_URL=https://nxf.nxsflow.com`),\nlässt die Variable weg oder setzt sie auf `https://nxsflow.com/nxs`." - } - }, - { - "version": "0.55.0", - "date": "2026-08-07", - "items": [ - { - "type": "added", - "en": "**The released `nxf-relay` can now sync to a Postgres — for example Supabase.** The relay in every\nrelease tarball is built with the Postgres backend, and that backend now speaks TLS, so a managed\ndatabase (Supabase, Neon, RDS) is reachable for the first time: set `NXF_RELAY_BACKEND=postgres`\nand an `NXF_RELAY_PG_URL` with `sslmode=require`. A private or self-signed CA is trusted via\n`NXF_RELAY_PG_CA_FILE`; the relay creates its own tables, so there is no migration step. The\ndefault is unchanged — no `NXF_RELAY_BACKEND` still means the single-file SQLite store, which\nremains the right choice for one person or a small team.\n\nThis also fixes a defect that made the backend unusable in the relay binary at all: the\nsynchronous Postgres driver starts its own runtime, which panicked inside the relay's async\n`main`, so `NXF_RELAY_BACKEND=postgres` aborted at boot on any build that carried the feature.\n\nIf you point the relay at a non-local database and leave `sslmode` at its default, it warns at\nstartup rather than quietly sending the op log in the clear.\n\nA new guide, `nxf guide running-a-relay`, walks through running your own relay for backup or\ncollaboration. Please read its warning first: **the relay still has no client authentication** —\nanyone who can reach its address can read and write the whole stream, whichever backend stores it.\nKeep it on a private network until authentication ships.\n\nThe DynamoDB backend is deliberately **not** in the released binary: it pulls a native\ncryptography toolchain into every build and adds ~19 MB to every download. It stays a supported\nsource build (`--features dynamodb`), documented in the new guide, and a released binary told to\nuse it refuses to start and says why.", - "de": "**Der ausgelieferte `nxf-relay` kann jetzt mit einem Postgres synchronisieren — zum Beispiel\nSupabase.** Der Relay in jedem Release-Archiv wird mit dem Postgres-Backend gebaut, und dieses\nBackend spricht nun TLS. Damit ist eine verwaltete Datenbank (Supabase, Neon, RDS) zum ersten Mal\nerreichbar: `NXF_RELAY_BACKEND=postgres` setzen und eine `NXF_RELAY_PG_URL` mit `sslmode=require`\nangeben. Eine eigene oder selbst signierte CA wird über `NXF_RELAY_PG_CA_FILE` vertraut; der Relay\nlegt seine Tabellen selbst an, es gibt also keinen Migrationsschritt. Die Vorgabe bleibt\nunverändert — ohne `NXF_RELAY_BACKEND` weiterhin der SQLite-Speicher in einer Datei, und der ist\nfür eine Person oder ein kleines Team nach wie vor die richtige Wahl.\n\nDamit ist zugleich ein Fehler behoben, der das Backend in der Relay-Binary überhaupt unbrauchbar\nmachte: der synchrone Postgres-Treiber startet eine eigene Laufzeitumgebung, was im asynchronen\n`main` des Relays zum Absturz führte — `NXF_RELAY_BACKEND=postgres` brach also beim Start ab,\nsobald ein Build die Fähigkeit trug.\n\nZeigt der Relay auf eine nicht-lokale Datenbank und bleibt `sslmode` auf der Vorgabe, warnt er beim\nStart — statt das Op-Log still im Klartext zu verschicken.\n\nEin neuer Leitfaden, `nxf guide running-a-relay`, führt durch den Betrieb eines eigenen Relays für\nSicherung oder Zusammenarbeit. Bitte zuerst dessen Warnung lesen: **der Relay hat weiterhin keine\nClient-Authentifizierung** — wer seine Adresse erreicht, liest und schreibt den ganzen Stream,\ngleich welches Backend ihn speichert. Bis die Authentifizierung steht, gehört er in ein privates\nNetz.\n\nDas DynamoDB-Backend liegt bewusst **nicht** in der ausgelieferten Binary: es zieht eine native\nKrypto-Werkzeugkette in jeden Build und macht jeden Download um ~19 MB schwerer. Es bleibt ein\nunterstützter Quellbau (`--features dynamodb`), im neuen Leitfaden beschrieben; eine ausgelieferte\nBinary, die darauf gestellt wird, startet nicht und sagt warum." - } - ], - "notes": { - "en": "### Added\n- **The released `nxf-relay` can now sync to a Postgres — for example Supabase.** The relay in every\nrelease tarball is built with the Postgres backend, and that backend now speaks TLS, so a managed\ndatabase (Supabase, Neon, RDS) is reachable for the first time: set `NXF_RELAY_BACKEND=postgres`\nand an `NXF_RELAY_PG_URL` with `sslmode=require`. A private or self-signed CA is trusted via\n`NXF_RELAY_PG_CA_FILE`; the relay creates its own tables, so there is no migration step. The\ndefault is unchanged — no `NXF_RELAY_BACKEND` still means the single-file SQLite store, which\nremains the right choice for one person or a small team.\n\nThis also fixes a defect that made the backend unusable in the relay binary at all: the\nsynchronous Postgres driver starts its own runtime, which panicked inside the relay's async\n`main`, so `NXF_RELAY_BACKEND=postgres` aborted at boot on any build that carried the feature.\n\nIf you point the relay at a non-local database and leave `sslmode` at its default, it warns at\nstartup rather than quietly sending the op log in the clear.\n\nA new guide, `nxf guide running-a-relay`, walks through running your own relay for backup or\ncollaboration. Please read its warning first: **the relay still has no client authentication** —\nanyone who can reach its address can read and write the whole stream, whichever backend stores it.\nKeep it on a private network until authentication ships.\n\nThe DynamoDB backend is deliberately **not** in the released binary: it pulls a native\ncryptography toolchain into every build and adds ~19 MB to every download. It stays a supported\nsource build (`--features dynamodb`), documented in the new guide, and a released binary told to\nuse it refuses to start and says why.", - "de": "### Neu\n- **Der ausgelieferte `nxf-relay` kann jetzt mit einem Postgres synchronisieren — zum Beispiel\nSupabase.** Der Relay in jedem Release-Archiv wird mit dem Postgres-Backend gebaut, und dieses\nBackend spricht nun TLS. Damit ist eine verwaltete Datenbank (Supabase, Neon, RDS) zum ersten Mal\nerreichbar: `NXF_RELAY_BACKEND=postgres` setzen und eine `NXF_RELAY_PG_URL` mit `sslmode=require`\nangeben. Eine eigene oder selbst signierte CA wird über `NXF_RELAY_PG_CA_FILE` vertraut; der Relay\nlegt seine Tabellen selbst an, es gibt also keinen Migrationsschritt. Die Vorgabe bleibt\nunverändert — ohne `NXF_RELAY_BACKEND` weiterhin der SQLite-Speicher in einer Datei, und der ist\nfür eine Person oder ein kleines Team nach wie vor die richtige Wahl.\n\nDamit ist zugleich ein Fehler behoben, der das Backend in der Relay-Binary überhaupt unbrauchbar\nmachte: der synchrone Postgres-Treiber startet eine eigene Laufzeitumgebung, was im asynchronen\n`main` des Relays zum Absturz führte — `NXF_RELAY_BACKEND=postgres` brach also beim Start ab,\nsobald ein Build die Fähigkeit trug.\n\nZeigt der Relay auf eine nicht-lokale Datenbank und bleibt `sslmode` auf der Vorgabe, warnt er beim\nStart — statt das Op-Log still im Klartext zu verschicken.\n\nEin neuer Leitfaden, `nxf guide running-a-relay`, führt durch den Betrieb eines eigenen Relays für\nSicherung oder Zusammenarbeit. Bitte zuerst dessen Warnung lesen: **der Relay hat weiterhin keine\nClient-Authentifizierung** — wer seine Adresse erreicht, liest und schreibt den ganzen Stream,\ngleich welches Backend ihn speichert. Bis die Authentifizierung steht, gehört er in ein privates\nNetz.\n\nDas DynamoDB-Backend liegt bewusst **nicht** in der ausgelieferten Binary: es zieht eine native\nKrypto-Werkzeugkette in jeden Build und macht jeden Download um ~19 MB schwerer. Es bleibt ein\nunterstützter Quellbau (`--features dynamodb`), im neuen Leitfaden beschrieben; eine ausgelieferte\nBinary, die darauf gestellt wird, startet nicht und sagt warum." - } - }, - { - "version": "0.54.0", - "date": "2026-08-07", - "items": [ - { - "type": "fixed", - "en": "`nxc send --to ` now works on every installation, including one updated from an older\nversion. The agent sidecar is compiled into the `nxs` binary instead of being placed beside it as a\nsecond file, so an update is one file replaced and there is nothing left for an older updater to\nmiss — machines that landed on 0.52.0/0.53.0 without a sidecar heal on their next `nxs self-update`,\nwith no environment variable and no reinstall. A superseded copy left next to the binary is removed\non the way. If the cache directory the sidecar is unpacked into is not writable, `nxc` now says so\nand names the way out instead of failing silently; `NXF_CACHE_DIR` redirects it, and `NXC_SIDECAR`\nremains the developer override.\n\n`nxm prime` (and `nxs prime`, which fans out to it) now also states why `CLAUDE.md` does not import\nthe generated `NEXUS_MEMORY.md`: the SessionStart hook already delivers those memories, so an import\nwould place every one of them in the session's context twice.", - "de": "`nxc send --to ` funktioniert jetzt auf jeder Installation, auch auf einer, die von einer\nälteren Version aktualisiert wurde. Der Agent-Sidecar steckt im `nxs`-Programm selbst, statt als\nzweite Datei danebengelegt zu werden — eine Aktualisierung ersetzt damit wieder nur eine Datei, und\nes bleibt nichts übrig, das eine ältere Version hätte mitbringen müssen. Rechner, die ohne Sidecar\nauf 0.52.0/0.53.0 gelandet sind, heilen beim nächsten `nxs self-update` von selbst, ohne\nUmgebungsvariable und ohne Neuinstallation; eine überholte Kopie neben dem Programm wird dabei\nentfernt. Ist das Cache-Verzeichnis, in das der Sidecar entpackt wird, nicht beschreibbar, sagt\n`nxc` das jetzt verständlich, statt still zu scheitern — `NXF_CACHE_DIR` lenkt es um, `NXC_SIDECAR`\nbleibt der Entwickler-Überschreiber.\n\n`nxm prime` (und `nxs prime`, das darauf verteilt) sagt außerdem, warum `CLAUDE.md` die erzeugte\n`NEXUS_MEMORY.md` nicht importiert: der SessionStart-Hook liefert diese Erinnerungen bereits, ein\nImport würde jede von ihnen ein zweites Mal in den Sitzungskontext legen." - } - ], - "notes": { - "en": "### Fixed\n- `nxc send --to ` now works on every installation, including one updated from an older\nversion. The agent sidecar is compiled into the `nxs` binary instead of being placed beside it as a\nsecond file, so an update is one file replaced and there is nothing left for an older updater to\nmiss — machines that landed on 0.52.0/0.53.0 without a sidecar heal on their next `nxs self-update`,\nwith no environment variable and no reinstall. A superseded copy left next to the binary is removed\non the way. If the cache directory the sidecar is unpacked into is not writable, `nxc` now says so\nand names the way out instead of failing silently; `NXF_CACHE_DIR` redirects it, and `NXC_SIDECAR`\nremains the developer override.\n\n`nxm prime` (and `nxs prime`, which fans out to it) now also states why `CLAUDE.md` does not import\nthe generated `NEXUS_MEMORY.md`: the SessionStart hook already delivers those memories, so an import\nwould place every one of them in the session's context twice.", - "de": "### Behoben\n- `nxc send --to ` funktioniert jetzt auf jeder Installation, auch auf einer, die von einer\nälteren Version aktualisiert wurde. Der Agent-Sidecar steckt im `nxs`-Programm selbst, statt als\nzweite Datei danebengelegt zu werden — eine Aktualisierung ersetzt damit wieder nur eine Datei, und\nes bleibt nichts übrig, das eine ältere Version hätte mitbringen müssen. Rechner, die ohne Sidecar\nauf 0.52.0/0.53.0 gelandet sind, heilen beim nächsten `nxs self-update` von selbst, ohne\nUmgebungsvariable und ohne Neuinstallation; eine überholte Kopie neben dem Programm wird dabei\nentfernt. Ist das Cache-Verzeichnis, in das der Sidecar entpackt wird, nicht beschreibbar, sagt\n`nxc` das jetzt verständlich, statt still zu scheitern — `NXF_CACHE_DIR` lenkt es um, `NXC_SIDECAR`\nbleibt der Entwickler-Überschreiber.\n\n`nxm prime` (und `nxs prime`, das darauf verteilt) sagt außerdem, warum `CLAUDE.md` die erzeugte\n`NEXUS_MEMORY.md` nicht importiert: der SessionStart-Hook liefert diese Erinnerungen bereits, ein\nImport würde jede von ihnen ein zweites Mal in den Sitzungskontext legen." - } - }, - { - "version": "0.53.0", - "date": "2026-08-07", - "items": [ - { - "type": "changed", - "en": "The chat engine's dry worker now takes its trigger log as a value instead of reading the\n`NXC_DRY_LOG` environment variable: `WorkerConfig::Dry` carries a `log: Option`, and\n`DryWorker` a field of the same name. An embedding host that used `WorkerConfig::Dry` states where\nrecording goes — or that it wants none — and is no longer affected by what any other thread put in\nthe process environment. The `nxc` CLI reads `NXC_DRY_LOG` exactly as before; it is resolved once,\nwhere the CLI already resolves `NXC_WORKER`.", - "de": "Der Trockenlauf-Worker der Chat-Engine bekommt sein Protokoll jetzt als Wert übergeben, statt die\nUmgebungsvariable `NXC_DRY_LOG` zu lesen: `WorkerConfig::Dry` trägt ein `log: Option`,\n`DryWorker` ein Feld gleichen Namens. Eine einbettende Anwendung sagt damit, wohin aufgezeichnet\nwird — oder dass sie gar nichts aufzeichnen will — und hängt nicht mehr davon ab, was ein anderer\nThread in die Prozessumgebung geschrieben hat. Die `nxc`-Kommandozeile liest `NXC_DRY_LOG`\nunverändert; sie löst sie einmal auf, dort wo sie ohnehin schon `NXC_WORKER` auflöst." - } - ], - "notes": { - "en": "### Changed\n- The chat engine's dry worker now takes its trigger log as a value instead of reading the\n`NXC_DRY_LOG` environment variable: `WorkerConfig::Dry` carries a `log: Option`, and\n`DryWorker` a field of the same name. An embedding host that used `WorkerConfig::Dry` states where\nrecording goes — or that it wants none — and is no longer affected by what any other thread put in\nthe process environment. The `nxc` CLI reads `NXC_DRY_LOG` exactly as before; it is resolved once,\nwhere the CLI already resolves `NXC_WORKER`.", - "de": "### Geändert\n- Der Trockenlauf-Worker der Chat-Engine bekommt sein Protokoll jetzt als Wert übergeben, statt die\nUmgebungsvariable `NXC_DRY_LOG` zu lesen: `WorkerConfig::Dry` trägt ein `log: Option`,\n`DryWorker` ein Feld gleichen Namens. Eine einbettende Anwendung sagt damit, wohin aufgezeichnet\nwird — oder dass sie gar nichts aufzeichnen will — und hängt nicht mehr davon ab, was ein anderer\nThread in die Prozessumgebung geschrieben hat. Die `nxc`-Kommandozeile liest `NXC_DRY_LOG`\nunverändert; sie löst sie einmal auf, dort wo sie ohnehin schon `NXC_WORKER` auflöst." - } - }, - { - "version": "0.52.0", - "date": "2026-08-06", - "items": [ - { - "type": "changed", - "en": "`nxc list` — and the identical view in `nxc prime` — now shows only what you can actually address,\none paragraph per entry: `**Coder** (handle: `coder`, level: senior) — …`. The way to address\nanyone is stated once in the header instead of repeated on every line, and personas reachable only\nthrough a channel are no longer listed as if you could message them directly; they are named at\ntheir channel. Channels can now declare a `description:` of their own, and every channel entry\nnames its members.", - "de": "`nxc list` — und dieselbe Ansicht in `nxc prime` — zeigt jetzt nur noch, wen man tatsächlich\nansprechen kann, je ein Absatz pro Eintrag: `**Coder** (handle: `coder`, level: senior) — …`. Der\nAdressierungshinweis steht einmal in der Kopfzeile statt in jeder Zeile, und Personas, die nur über\neinen Kanal erreichbar sind, stehen nicht mehr so da, als könnte man ihnen direkt schreiben; sie\nwerden an ihrem Kanal genannt. Kanäle können jetzt eine eigene `description:` deklarieren, und jeder\nKanal-Eintrag nennt seine Mitglieder." - }, - { - "type": "fixed", - "en": "An installed nexus-flow can now start agents. The `nxc` agent sidecar ships in the release archive\nand is placed beside the `nxs` binary by both `install.sh` and `nxs self-update`, so `nxc send --to\n` works out of the box — `NXC_SIDECAR` goes back to being a developer override instead of\nsomething you had to set on a normal install. `nxc send --to` now also refuses up front, with a\nlegible message, when Claude Code is not installed or not on your PATH, instead of accepting the\nsend and failing inside a log file.", - "de": "Eine installierte nexus-flow kann jetzt Agenten starten. Der `nxc`-Sidecar wird im Release-Archiv\nmitgeliefert und von `install.sh` wie von `nxs self-update` neben die `nxs`-Binärdatei gelegt —\n`nxc send --to ` läuft damit sofort. `NXC_SIDECAR` ist wieder das, was es sein sollte: ein\nEntwickler-Überschreiber, keine Voraussetzung einer normalen Installation. `nxc send --to` weist\naußerdem von vornherein verständlich ab, wenn Claude Code nicht installiert oder nicht auf dem PATH\nist, statt den Versand anzunehmen und in einer Logdatei zu scheitern.", - "unreleased": true - } - ], - "notes": { - "en": "### Changed\n- `nxc list` — and the identical view in `nxc prime` — now shows only what you can actually address,\none paragraph per entry: `**Coder** (handle: `coder`, level: senior) — …`. The way to address\nanyone is stated once in the header instead of repeated on every line, and personas reachable only\nthrough a channel are no longer listed as if you could message them directly; they are named at\ntheir channel. Channels can now declare a `description:` of their own, and every channel entry\nnames its members.\n\n### Fixed\n- An installed nexus-flow can now start agents. The `nxc` agent sidecar ships in the release archive\nand is placed beside the `nxs` binary by both `install.sh` and `nxs self-update`, so `nxc send --to\n` works out of the box — `NXC_SIDECAR` goes back to being a developer override instead of\nsomething you had to set on a normal install. `nxc send --to` now also refuses up front, with a\nlegible message, when Claude Code is not installed or not on your PATH, instead of accepting the\nsend and failing inside a log file.", - "de": "### Geändert\n- `nxc list` — und dieselbe Ansicht in `nxc prime` — zeigt jetzt nur noch, wen man tatsächlich\nansprechen kann, je ein Absatz pro Eintrag: `**Coder** (handle: `coder`, level: senior) — …`. Der\nAdressierungshinweis steht einmal in der Kopfzeile statt in jeder Zeile, und Personas, die nur über\neinen Kanal erreichbar sind, stehen nicht mehr so da, als könnte man ihnen direkt schreiben; sie\nwerden an ihrem Kanal genannt. Kanäle können jetzt eine eigene `description:` deklarieren, und jeder\nKanal-Eintrag nennt seine Mitglieder.\n\n### Behoben\n- Eine installierte nexus-flow kann jetzt Agenten starten. Der `nxc`-Sidecar wird im Release-Archiv\nmitgeliefert und von `install.sh` wie von `nxs self-update` neben die `nxs`-Binärdatei gelegt —\n`nxc send --to ` läuft damit sofort. `NXC_SIDECAR` ist wieder das, was es sein sollte: ein\nEntwickler-Überschreiber, keine Voraussetzung einer normalen Installation. `nxc send --to` weist\naußerdem von vornherein verständlich ab, wenn Claude Code nicht installiert oder nicht auf dem PATH\nist, statt den Versand anzunehmen und in einer Logdatei zu scheitern." - } - }, - { - "version": "0.51.0", - "date": "2026-08-05", - "items": [ - { - "type": "added", - "en": "**`nxc` gains a conversation surface: say who you mean, not how the plumbing works.**\n`nxc send --to \"\"` opens a thread and returns its id — what happens next\n(a persona is started, a channel fans out to its members, a plain channel is just posted to) is\ndecided by the target's own declaration instead of by picking the right verb. `nxc reply --thread\n \"\"` answers in that thread and hands the turn back to whoever is on the other side, so\na conversation runs several turns without anyone tracking sessions. `nxc list` shows who can be\naddressed and what for, and `nxc prime` now shows it too — a persona additionally learns who it is\nand how to answer. Add `--stream` (for a human at the keyboard) to watch the answer arrive,\nincluding what the persona is doing while it works; the wait resets whenever there is a sign of\nlife and reports what the transcript last showed before it gives up.\nPersona declarations gain three optional fields: `stage:` (`junior`/`senior`/`principal`, a way to\nchoose how much thinking a job is worth without naming a model), `addressable:` (whether a direct\nmessage is possible at all, or only certain channels), and `address_book:` (who this persona may\napproach, and what for).\n**Nothing was removed or changed:** every verb that worked before works exactly as before, and\ndeclarations without the new fields mean what they always did.", - "de": "**`nxc` bekommt eine Gesprächsfläche: man sagt, wen man meint — nicht, wie die Mechanik geht.**\n`nxc send --to \"\"` öffnet einen Faden und gibt dessen Kennung zurück; was\ndaraufhin geschieht (eine Persona wird gestartet, ein Kanal fächert an seine Mitglieder auf, ein\neinfacher Kanal bekommt nur die Nachricht), entscheidet die Deklaration des Ziels statt die Wahl\ndes richtigen Verbs. `nxc reply --thread \"\"` antwortet in diesen Faden und gibt den\nZug an die Gegenseite zurück — ein Gespräch läuft also über mehrere Züge, ohne dass jemand\nSitzungen mitführt. `nxc list` zeigt, wen man ansprechen darf und wofür; `nxc prime` zeigt es jetzt\nebenfalls, und einer Persona sagt es zusätzlich, wer sie ist und wie sie antwortet. Mit `--stream`\n(für den Menschen an der Tastatur) sieht man der Antwort beim Entstehen zu, samt dem, was die\nPersona dabei tut; die Wartezeit beginnt bei jedem Lebenszeichen von vorn und meldet vor dem\nAufgeben, was das Transkript zuletzt zeigte.\nPersona-Deklarationen kennen drei neue, optionale Felder: `stage:` (`junior`/`senior`/`principal` —\nwie viel Nachdenken eine Aufgabe wert ist, ohne ein Modell zu benennen), `addressable:` (ob eine\nDirektnachricht überhaupt möglich ist oder nur bestimmte Kanäle) und `address_book:` (wen diese\nPersona ansprechen darf und wofür).\n**Es wurde nichts entfernt und nichts geändert:** jedes bisherige Verb funktioniert unverändert,\nund Deklarationen ohne die neuen Felder bedeuten weiterhin genau das, was sie bedeutet haben." - } - ], - "notes": { - "en": "### Added\n- **`nxc` gains a conversation surface: say who you mean, not how the plumbing works.**\n`nxc send --to \"\"` opens a thread and returns its id — what happens next\n(a persona is started, a channel fans out to its members, a plain channel is just posted to) is\ndecided by the target's own declaration instead of by picking the right verb. `nxc reply --thread\n \"\"` answers in that thread and hands the turn back to whoever is on the other side, so\na conversation runs several turns without anyone tracking sessions. `nxc list` shows who can be\naddressed and what for, and `nxc prime` now shows it too — a persona additionally learns who it is\nand how to answer. Add `--stream` (for a human at the keyboard) to watch the answer arrive,\nincluding what the persona is doing while it works; the wait resets whenever there is a sign of\nlife and reports what the transcript last showed before it gives up.\nPersona declarations gain three optional fields: `stage:` (`junior`/`senior`/`principal`, a way to\nchoose how much thinking a job is worth without naming a model), `addressable:` (whether a direct\nmessage is possible at all, or only certain channels), and `address_book:` (who this persona may\napproach, and what for).\n**Nothing was removed or changed:** every verb that worked before works exactly as before, and\ndeclarations without the new fields mean what they always did.", - "de": "### Neu\n- **`nxc` bekommt eine Gesprächsfläche: man sagt, wen man meint — nicht, wie die Mechanik geht.**\n`nxc send --to \"\"` öffnet einen Faden und gibt dessen Kennung zurück; was\ndaraufhin geschieht (eine Persona wird gestartet, ein Kanal fächert an seine Mitglieder auf, ein\neinfacher Kanal bekommt nur die Nachricht), entscheidet die Deklaration des Ziels statt die Wahl\ndes richtigen Verbs. `nxc reply --thread \"\"` antwortet in diesen Faden und gibt den\nZug an die Gegenseite zurück — ein Gespräch läuft also über mehrere Züge, ohne dass jemand\nSitzungen mitführt. `nxc list` zeigt, wen man ansprechen darf und wofür; `nxc prime` zeigt es jetzt\nebenfalls, und einer Persona sagt es zusätzlich, wer sie ist und wie sie antwortet. Mit `--stream`\n(für den Menschen an der Tastatur) sieht man der Antwort beim Entstehen zu, samt dem, was die\nPersona dabei tut; die Wartezeit beginnt bei jedem Lebenszeichen von vorn und meldet vor dem\nAufgeben, was das Transkript zuletzt zeigte.\nPersona-Deklarationen kennen drei neue, optionale Felder: `stage:` (`junior`/`senior`/`principal` —\nwie viel Nachdenken eine Aufgabe wert ist, ohne ein Modell zu benennen), `addressable:` (ob eine\nDirektnachricht überhaupt möglich ist oder nur bestimmte Kanäle) und `address_book:` (wen diese\nPersona ansprechen darf und wofür).\n**Es wurde nichts entfernt und nichts geändert:** jedes bisherige Verb funktioniert unverändert,\nund Deklarationen ohne die neuen Felder bedeuten weiterhin genau das, was sie bedeutet haben." - } - }, - { - "version": "0.50.0", - "date": "2026-08-05", - "items": [ - { - "type": "added", - "en": "`nxm migrate plan --with-judge` no longer waits forever. The judge now has a deadline — generous by\ndefault (30 minutes), announced before the wait starts, and settable with `--judge-timeout [smh]`\n(`0` waits as long as it takes). A judge that is alive but never answers is stopped and reported\ninstead of leaving the command standing until you press Ctrl-C; nothing is written either way, and\nthe plan is rebuilt from the store, so re-running costs only the wait.", - "de": "`nxm migrate plan --with-judge` wartet nicht mehr unbegrenzt. Der Richter bekommt jetzt eine Frist —\ngroßzügig voreingestellt (30 Minuten), vor dem Warten angekündigt und mit `--judge-timeout [smh]`\nsetzbar (`0` wartet so lange wie nötig). Ein Richter, der zwar läuft, aber nie antwortet, wird\ngestoppt und gemeldet, statt den Befehl bis zum Strg-C stehen zu lassen; geschrieben wird in beiden\nFällen nichts, und der Plan entsteht neu aus dem Speicher — ein zweiter Anlauf kostet nur die\nWartezeit." - } - ], - "notes": { - "en": "### Added\n- `nxm migrate plan --with-judge` no longer waits forever. The judge now has a deadline — generous by\ndefault (30 minutes), announced before the wait starts, and settable with `--judge-timeout [smh]`\n(`0` waits as long as it takes). A judge that is alive but never answers is stopped and reported\ninstead of leaving the command standing until you press Ctrl-C; nothing is written either way, and\nthe plan is rebuilt from the store, so re-running costs only the wait.", - "de": "### Neu\n- `nxm migrate plan --with-judge` wartet nicht mehr unbegrenzt. Der Richter bekommt jetzt eine Frist —\ngroßzügig voreingestellt (30 Minuten), vor dem Warten angekündigt und mit `--judge-timeout [smh]`\nsetzbar (`0` wartet so lange wie nötig). Ein Richter, der zwar läuft, aber nie antwortet, wird\ngestoppt und gemeldet, statt den Befehl bis zum Strg-C stehen zu lassen; geschrieben wird in beiden\nFällen nichts, und der Plan entsteht neu aus dem Speicher — ein zweiter Anlauf kostet nur die\nWartezeit." - } - }, - { - "version": "0.49.0", - "date": "2026-08-04", - "items": [ - { - "type": "changed", - "en": "`nxs prime` and `NEXUS_MEMORY.md` now hand you the memories in **reading order** instead of\nalphabetically by key: the category first — `introduction`, `architecture`, `rules`, then every other\ncategory by name, and whatever is still `unsorted` last — and within a category the position\n`nxm reorder` stored, then the order the memories were written in. A filed body of memories therefore\nreads front to back as the argument it was filed into: what the workspace is, then the idea that\ncarries it, then the hard rules. The same order is what `nxm memories --ordered` reports; an ordinal\nplaces a memory within its category and never lifts it out of its section. Nothing changes for a\nworkspace that has filed nothing except that its memories now read in the order they were written\nrather than by key. `nxm memories` without `--ordered` keeps serving key order — it is a directory,\nnot a document.", - "de": "`nxs prime` und `NEXUS_MEMORY.md` geben die Erinnerungen jetzt in **Leseordnung** aus statt\nalphabetisch nach Kennung: zuerst die Kategorie — `introduction`, `architecture`, `rules`, danach\njede weitere nach Namen und ganz zuletzt, was noch `unsorted` ist — und innerhalb einer Kategorie die\nmit `nxm reorder` gespeicherte Position, dann die Reihenfolge, in der geschrieben wurde. Ein\neinsortierter Bestand liest sich damit von vorn nach hinten als die Argumentation, in die er\neinsortiert wurde: erst was der Arbeitsbereich ist, dann die tragende Idee, dann die harten Regeln.\nDieselbe Ordnung meldet `nxm memories --ordered`; eine Position platziert eine Erinnerung innerhalb\nihrer Kategorie und hebt sie nie aus ihrem Abschnitt heraus. Für einen Arbeitsbereich, der nichts\neinsortiert hat, ändert sich nur, dass seine Erinnerungen nun in Schreib- statt in Kennungsreihenfolge\ngelesen werden. `nxm memories` ohne `--ordered` bleibt bei der Kennungsreihenfolge — es ist ein\nVerzeichnis, kein Dokument." - } - ], - "notes": { - "en": "### Changed\n- `nxs prime` and `NEXUS_MEMORY.md` now hand you the memories in **reading order** instead of\nalphabetically by key: the category first — `introduction`, `architecture`, `rules`, then every other\ncategory by name, and whatever is still `unsorted` last — and within a category the position\n`nxm reorder` stored, then the order the memories were written in. A filed body of memories therefore\nreads front to back as the argument it was filed into: what the workspace is, then the idea that\ncarries it, then the hard rules. The same order is what `nxm memories --ordered` reports; an ordinal\nplaces a memory within its category and never lifts it out of its section. Nothing changes for a\nworkspace that has filed nothing except that its memories now read in the order they were written\nrather than by key. `nxm memories` without `--ordered` keeps serving key order — it is a directory,\nnot a document.", - "de": "### Geändert\n- `nxs prime` und `NEXUS_MEMORY.md` geben die Erinnerungen jetzt in **Leseordnung** aus statt\nalphabetisch nach Kennung: zuerst die Kategorie — `introduction`, `architecture`, `rules`, danach\njede weitere nach Namen und ganz zuletzt, was noch `unsorted` ist — und innerhalb einer Kategorie die\nmit `nxm reorder` gespeicherte Position, dann die Reihenfolge, in der geschrieben wurde. Ein\neinsortierter Bestand liest sich damit von vorn nach hinten als die Argumentation, in die er\neinsortiert wurde: erst was der Arbeitsbereich ist, dann die tragende Idee, dann die harten Regeln.\nDieselbe Ordnung meldet `nxm memories --ordered`; eine Position platziert eine Erinnerung innerhalb\nihrer Kategorie und hebt sie nie aus ihrem Abschnitt heraus. Für einen Arbeitsbereich, der nichts\neinsortiert hat, ändert sich nur, dass seine Erinnerungen nun in Schreib- statt in Kennungsreihenfolge\ngelesen werden. `nxm memories` ohne `--ordered` bleibt bei der Kennungsreihenfolge — es ist ein\nVerzeichnis, kein Dokument." - } - }, - { - "version": "0.48.0", - "date": "2026-08-04", - "items": [ - { - "type": "added", - "en": "`nxm migrate` moves a workspace from a hand-maintained `CLAUDE.md` to its generated project memory.\n`nxm migrate plan` proposes: every memory still filed as `unsorted`, plus every section of the\nhand-written context documents, as one plan document. `--with-judge` (today: `claude`) has a coding\nassistant decide the filing — the one place nexus-flow asks a model, and deliberately outside every\nwrite path, so `nxm remember` stays offline and deterministic. You review the plan, then\n`nxm migrate apply --plan ` files the memories, writes the sections as memories, moves them out\nof their source document and regenerates `NEXUS_MEMORY.md`. A plan nobody judged is refused rather\nthan filed in bulk; a workspace whose migration already ran on another device says so and stops\ninstead of filing everything a second time. Documents carrying a generated header are never imported\nback, whatever they are called.\n\n`nxs prime` now names an open migration when this workspace still has unfiled memories, and stays\nsilent when it does not. It also asks you to correct a memory you find to be wrong instead of reading\npast it — every memory is headed by the key you correct it under. `nxs self-update` mentions the new\ncommand once.", - "de": "`nxm migrate` überführt einen Arbeitsbereich von einer handgepflegten `CLAUDE.md` auf sein erzeugtes\nProjektgedächtnis. `nxm migrate plan` schlägt vor: jede noch `unsorted` abgelegte Erinnerung und jeden\nAbschnitt der handgeschriebenen Kontextdokumente, als ein Plandokument. Mit `--with-judge` (heute:\n`claude`) entscheidet ein Coding-Assistent die Einsortierung — die einzige Stelle, an der nexus-flow\nein Modell fragt, und bewusst außerhalb jedes Schreibpfads, damit `nxm remember` offline und\ndeterministisch bleibt. Du prüfst den Plan, dann sortiert `nxm migrate apply --plan ` die\nErinnerungen ein, schreibt die Abschnitte als Erinnerungen, nimmt sie aus ihrem Quelldokument heraus\nund erzeugt `NEXUS_MEMORY.md` neu. Ein Plan, den niemand beurteilt hat, wird abgelehnt statt in\nMasse eingesortiert; ein Arbeitsbereich, dessen Umzug schon auf einem anderen Gerät lief, sagt das\nund hält an, statt alles ein zweites Mal einzusortieren. Dokumente mit Erzeugt-Kopf werden nie\nzurückimportiert, egal wie sie heißen.\n\n`nxs prime` weist jetzt einen offenen Umzug aus, wenn dieser Arbeitsbereich noch nicht einsortierte\nErinnerungen hat, und schweigt sonst. Es fordert außerdem dazu auf, eine als falsch erkannte\nErinnerung zu korrigieren, statt sie zu überlesen — jede Erinnerung trägt die Kennung, unter der man\nsie korrigiert. `nxs self-update` nennt den neuen Befehl genau einmal." - } - ], - "notes": { - "en": "### Added\n- `nxm migrate` moves a workspace from a hand-maintained `CLAUDE.md` to its generated project memory.\n`nxm migrate plan` proposes: every memory still filed as `unsorted`, plus every section of the\nhand-written context documents, as one plan document. `--with-judge` (today: `claude`) has a coding\nassistant decide the filing — the one place nexus-flow asks a model, and deliberately outside every\nwrite path, so `nxm remember` stays offline and deterministic. You review the plan, then\n`nxm migrate apply --plan ` files the memories, writes the sections as memories, moves them out\nof their source document and regenerates `NEXUS_MEMORY.md`. A plan nobody judged is refused rather\nthan filed in bulk; a workspace whose migration already ran on another device says so and stops\ninstead of filing everything a second time. Documents carrying a generated header are never imported\nback, whatever they are called.\n\n`nxs prime` now names an open migration when this workspace still has unfiled memories, and stays\nsilent when it does not. It also asks you to correct a memory you find to be wrong instead of reading\npast it — every memory is headed by the key you correct it under. `nxs self-update` mentions the new\ncommand once.", - "de": "### Neu\n- `nxm migrate` überführt einen Arbeitsbereich von einer handgepflegten `CLAUDE.md` auf sein erzeugtes\nProjektgedächtnis. `nxm migrate plan` schlägt vor: jede noch `unsorted` abgelegte Erinnerung und jeden\nAbschnitt der handgeschriebenen Kontextdokumente, als ein Plandokument. Mit `--with-judge` (heute:\n`claude`) entscheidet ein Coding-Assistent die Einsortierung — die einzige Stelle, an der nexus-flow\nein Modell fragt, und bewusst außerhalb jedes Schreibpfads, damit `nxm remember` offline und\ndeterministisch bleibt. Du prüfst den Plan, dann sortiert `nxm migrate apply --plan ` die\nErinnerungen ein, schreibt die Abschnitte als Erinnerungen, nimmt sie aus ihrem Quelldokument heraus\nund erzeugt `NEXUS_MEMORY.md` neu. Ein Plan, den niemand beurteilt hat, wird abgelehnt statt in\nMasse eingesortiert; ein Arbeitsbereich, dessen Umzug schon auf einem anderen Gerät lief, sagt das\nund hält an, statt alles ein zweites Mal einzusortieren. Dokumente mit Erzeugt-Kopf werden nie\nzurückimportiert, egal wie sie heißen.\n\n`nxs prime` weist jetzt einen offenen Umzug aus, wenn dieser Arbeitsbereich noch nicht einsortierte\nErinnerungen hat, und schweigt sonst. Es fordert außerdem dazu auf, eine als falsch erkannte\nErinnerung zu korrigieren, statt sie zu überlesen — jede Erinnerung trägt die Kennung, unter der man\nsie korrigiert. `nxs self-update` nennt den neuen Befehl genau einmal." - } - }, - { - "version": "0.47.0", - "date": "2026-08-04", - "items": [ - { - "type": "added", - "en": "The project's durable memory is now a generated file. `NEXUS_MEMORY.md` at the workspace root is a\nprojection of the memories `nxm` holds — every memory write regenerates it, and so does a sync pass\nthat pulled memories from another device. The store stays the single source of truth; git supplies\nthe version history, with the diff visible in the change proposal.\n\nIt exists for the readers a session hook never reaches: the SessionStart hook is now\n`nxs prime || cat NEXUS_MEMORY.md`, so a contributor without nexus-flow installed still gets the\nproject's context, and `AGENTS.md` binds the file for agents outside Claude Code. The file is a\ntrue subset of what `nxs prime` renders — the fallback can only ever deliver less than the main\npath, never more.\n\nBecause it is generated, it must not be edited: `nxm doc` prints the projection and\n`nxm doc --check` exits non-zero when the file and the store have drifted apart — a hand edit, or a\nsync daemon that was never running in this checkout. Writing into a git worktree follows three\nrules: only when the content actually differs, never a commit, and never at all while the worktree\nis mid-rebase, mid-merge or mid-conflict.\n\n`nxs sync daemon status` also stops guessing on platforms with no way to probe a process: it\nanswers `running: null` instead of reporting a long-dead daemon as running.", - "de": "Das dauerhafte Gedächtnis eines Projekts ist jetzt eine erzeugte Datei. `NEXUS_MEMORY.md` im\nWurzelverzeichnis ist eine Projektion der Erinnerungen aus `nxm` — jeder Schreibvorgang erzeugt sie\nneu, ebenso ein Sync-Durchlauf, der Erinnerungen von einem anderen Gerät geholt hat. Der Speicher\nbleibt die einzige Quelle der Wahrheit; die Versionsgeschichte liefert Git, samt Diff im Vorschlag.\n\nSie existiert für die Leser, die ein Sitzungs-Hook nie erreicht: Der SessionStart-Hook lautet jetzt\n`nxs prime || cat NEXUS_MEMORY.md`, damit auch ein Mitwirkender ohne installiertes nexus-flow den\nProjektkontext bekommt, und `AGENTS.md` bindet die Datei für Agenten außerhalb von Claude Code ein.\nDie Datei ist eine echte Teilmenge dessen, was `nxs prime` ausgibt — der Rückfallweg liefert nie\nmehr als der Hauptweg.\n\nWeil sie erzeugt wird, darf sie nicht von Hand geändert werden: `nxm doc` gibt die Projektion aus,\n`nxm doc --check` endet mit einem Fehler, sobald Datei und Speicher auseinanderlaufen — eine\nHandänderung, oder ein Sync-Dienst, der in diesem Arbeitsbaum nie lief. Für das Schreiben in einen\nGit-Arbeitsbaum gelten drei Regeln: nur wenn sich der Inhalt wirklich unterscheidet, niemals ein\nCommit, und gar nicht, solange der Arbeitsbaum mitten in einem Rebase, Merge oder Konflikt steckt.\n\n`nxs sync daemon status` rät außerdem nicht mehr auf Plattformen ohne Prozessprüfung: Es antwortet\n`running: null`, statt einen längst toten Dienst als laufend zu melden." - } - ], - "notes": { - "en": "### Added\n- The project's durable memory is now a generated file. `NEXUS_MEMORY.md` at the workspace root is a\nprojection of the memories `nxm` holds — every memory write regenerates it, and so does a sync pass\nthat pulled memories from another device. The store stays the single source of truth; git supplies\nthe version history, with the diff visible in the change proposal.\n\nIt exists for the readers a session hook never reaches: the SessionStart hook is now\n`nxs prime || cat NEXUS_MEMORY.md`, so a contributor without nexus-flow installed still gets the\nproject's context, and `AGENTS.md` binds the file for agents outside Claude Code. The file is a\ntrue subset of what `nxs prime` renders — the fallback can only ever deliver less than the main\npath, never more.\n\nBecause it is generated, it must not be edited: `nxm doc` prints the projection and\n`nxm doc --check` exits non-zero when the file and the store have drifted apart — a hand edit, or a\nsync daemon that was never running in this checkout. Writing into a git worktree follows three\nrules: only when the content actually differs, never a commit, and never at all while the worktree\nis mid-rebase, mid-merge or mid-conflict.\n\n`nxs sync daemon status` also stops guessing on platforms with no way to probe a process: it\nanswers `running: null` instead of reporting a long-dead daemon as running.", - "de": "### Neu\n- Das dauerhafte Gedächtnis eines Projekts ist jetzt eine erzeugte Datei. `NEXUS_MEMORY.md` im\nWurzelverzeichnis ist eine Projektion der Erinnerungen aus `nxm` — jeder Schreibvorgang erzeugt sie\nneu, ebenso ein Sync-Durchlauf, der Erinnerungen von einem anderen Gerät geholt hat. Der Speicher\nbleibt die einzige Quelle der Wahrheit; die Versionsgeschichte liefert Git, samt Diff im Vorschlag.\n\nSie existiert für die Leser, die ein Sitzungs-Hook nie erreicht: Der SessionStart-Hook lautet jetzt\n`nxs prime || cat NEXUS_MEMORY.md`, damit auch ein Mitwirkender ohne installiertes nexus-flow den\nProjektkontext bekommt, und `AGENTS.md` bindet die Datei für Agenten außerhalb von Claude Code ein.\nDie Datei ist eine echte Teilmenge dessen, was `nxs prime` ausgibt — der Rückfallweg liefert nie\nmehr als der Hauptweg.\n\nWeil sie erzeugt wird, darf sie nicht von Hand geändert werden: `nxm doc` gibt die Projektion aus,\n`nxm doc --check` endet mit einem Fehler, sobald Datei und Speicher auseinanderlaufen — eine\nHandänderung, oder ein Sync-Dienst, der in diesem Arbeitsbaum nie lief. Für das Schreiben in einen\nGit-Arbeitsbaum gelten drei Regeln: nur wenn sich der Inhalt wirklich unterscheidet, niemals ein\nCommit, und gar nicht, solange der Arbeitsbaum mitten in einem Rebase, Merge oder Konflikt steckt.\n\n`nxs sync daemon status` rät außerdem nicht mehr auf Plattformen ohne Prozessprüfung: Es antwortet\n`running: null`, statt einen längst toten Dienst als laufend zu melden." - } - }, - { - "version": "0.46.0", - "date": "2026-08-04", - "items": [ - { - "type": "added", - "en": "Workflow runs can now be enumerated, and their progress arrives as events. `nxc workflow list`\nanswers \"which work orders are running here?\" with no prior knowledge at all — no run id, no\nsession, no having started anything — optionally narrowed by `--status`, `--milestone`, `--ticket`\nand `--limit`, newest first. Until now every workflow read needed a run id you already held, so a\npicture across several workspaces could not be derived at all.\n\nFor an embedding app the same list is `Engine::workflow_list`, and progress no longer has to be\npolled: `Engine::subscribe_workflow` delivers one typed event per run started and per step advanced\n— which run, which step, which outcome, and the status it reached — whoever wrote it, including\nanother process or a sync pull. `Engine::workflow_events` is the same stream addressed by cursor,\nfor catching up after a restart.", - "de": "Läufe lassen sich jetzt auflisten, und ihr Fortschritt kommt als Ereignis an. `nxc workflow list`\nbeantwortet „welche Arbeitsaufträge laufen hier?\" ganz ohne Vorwissen — ohne Lauf-Kennung, ohne\nSitzung, ohne selbst etwas gestartet zu haben — wahlweise eingegrenzt mit `--status`, `--milestone`,\n`--ticket` und `--limit`, neueste zuerst. Bisher brauchte jeder Lauf-Zugriff eine Kennung, die man\nschon besitzen musste; eine Gesamtlage über mehrere Arbeitsbereiche war damit gar nicht ableitbar.\n\nFür eine einbettende App ist dieselbe Liste `Engine::workflow_list`, und der Fortschritt muss nicht\nmehr abgefragt werden: `Engine::subscribe_workflow` liefert je gestartetem Lauf und je\nweitergeschaltetem Schritt ein typisiertes Ereignis — welcher Lauf, welcher Schritt, welches Ergebnis\nund der erreichte Status —, gleich wer es geschrieben hat, auch ein anderer Prozess oder ein\nSync-Abgleich. `Engine::workflow_events` ist derselbe Strom über einen Cursor, zum Aufholen nach\neinem Neustart.", - "unreleased": true - } - ], - "notes": { - "en": "### Added\n- Workflow runs can now be enumerated, and their progress arrives as events. `nxc workflow list`\nanswers \"which work orders are running here?\" with no prior knowledge at all — no run id, no\nsession, no having started anything — optionally narrowed by `--status`, `--milestone`, `--ticket`\nand `--limit`, newest first. Until now every workflow read needed a run id you already held, so a\npicture across several workspaces could not be derived at all.\n\nFor an embedding app the same list is `Engine::workflow_list`, and progress no longer has to be\npolled: `Engine::subscribe_workflow` delivers one typed event per run started and per step advanced\n— which run, which step, which outcome, and the status it reached — whoever wrote it, including\nanother process or a sync pull. `Engine::workflow_events` is the same stream addressed by cursor,\nfor catching up after a restart.", - "de": "### Neu\n- Läufe lassen sich jetzt auflisten, und ihr Fortschritt kommt als Ereignis an. `nxc workflow list`\nbeantwortet „welche Arbeitsaufträge laufen hier?\" ganz ohne Vorwissen — ohne Lauf-Kennung, ohne\nSitzung, ohne selbst etwas gestartet zu haben — wahlweise eingegrenzt mit `--status`, `--milestone`,\n`--ticket` und `--limit`, neueste zuerst. Bisher brauchte jeder Lauf-Zugriff eine Kennung, die man\nschon besitzen musste; eine Gesamtlage über mehrere Arbeitsbereiche war damit gar nicht ableitbar.\n\nFür eine einbettende App ist dieselbe Liste `Engine::workflow_list`, und der Fortschritt muss nicht\nmehr abgefragt werden: `Engine::subscribe_workflow` liefert je gestartetem Lauf und je\nweitergeschaltetem Schritt ein typisiertes Ereignis — welcher Lauf, welcher Schritt, welches Ergebnis\nund der erreichte Status —, gleich wer es geschrieben hat, auch ein anderer Prozess oder ein\nSync-Abgleich. `Engine::workflow_events` ist derselbe Strom über einen Cursor, zum Aufholen nach\neinem Neustart." - } - }, - { - "version": "0.45.0", - "date": "2026-08-03", - "items": [ - { - "type": "added", - "en": "A memory's reach now decides where it appears. Knowledge that holds for the whole workspace\n(`project`/`global`) is replayed at session start as before; a memory filed against board items\n(`--scope item --refs `) reads in full on `nxf show ` and shows up as a short `(2 memories)`\nmarker on `nxf next` — so the work list stays a list, and the detail sits where you are already\nlooking. This is deterministic retrieval: no embeddings, no similarity search, no network.\n\nThe MCP memory tools can file memories too: `memory_add`/`memory_update` take optional `category`,\n`scope` and `refs`, and the new `memory_classify` / `memory_reorder` tools mirror `nxm classify` /\n`nxm reorder`. Without them an agent writing over MCP could only produce memories no surface knew\nwhere to show.", - "de": "Die Reichweite einer Erinnerung entscheidet jetzt, wo sie auftaucht. Was für den ganzen\nArbeitsbereich gilt (`project`/`global`), wird wie bisher beim Sitzungsstart eingespielt; eine\nErinnerung, die zu Board-Einträgen abgelegt ist (`--scope item --refs `), steht im Volltext in\n`nxf show ` und erscheint in `nxf next` nur als kurze Markierung `(2 memories)` — die Arbeitsliste\nbleibt eine Liste, das Detail liegt dort, wo man ohnehin hinsieht. Der Abruf ist deterministisch:\nkein Einbetten, keine Ähnlichkeitssuche, keine Netzabhängigkeit.\n\nAuch die MCP-Werkzeuge können jetzt einsortieren: `memory_add`/`memory_update` nehmen optional\n`category`, `scope` und `refs`, und die neuen Werkzeuge `memory_classify` / `memory_reorder`\nentsprechen `nxm classify` / `nxm reorder`. Ohne sie konnte ein über MCP schreibender Agent nur\nErinnerungen anlegen, die keine Oberfläche einzuordnen wusste." - } - ], - "notes": { - "en": "### Added\n- A memory's reach now decides where it appears. Knowledge that holds for the whole workspace\n(`project`/`global`) is replayed at session start as before; a memory filed against board items\n(`--scope item --refs `) reads in full on `nxf show ` and shows up as a short `(2 memories)`\nmarker on `nxf next` — so the work list stays a list, and the detail sits where you are already\nlooking. This is deterministic retrieval: no embeddings, no similarity search, no network.\n\nThe MCP memory tools can file memories too: `memory_add`/`memory_update` take optional `category`,\n`scope` and `refs`, and the new `memory_classify` / `memory_reorder` tools mirror `nxm classify` /\n`nxm reorder`. Without them an agent writing over MCP could only produce memories no surface knew\nwhere to show.", - "de": "### Neu\n- Die Reichweite einer Erinnerung entscheidet jetzt, wo sie auftaucht. Was für den ganzen\nArbeitsbereich gilt (`project`/`global`), wird wie bisher beim Sitzungsstart eingespielt; eine\nErinnerung, die zu Board-Einträgen abgelegt ist (`--scope item --refs `), steht im Volltext in\n`nxf show ` und erscheint in `nxf next` nur als kurze Markierung `(2 memories)` — die Arbeitsliste\nbleibt eine Liste, das Detail liegt dort, wo man ohnehin hinsieht. Der Abruf ist deterministisch:\nkein Einbetten, keine Ähnlichkeitssuche, keine Netzabhängigkeit.\n\nAuch die MCP-Werkzeuge können jetzt einsortieren: `memory_add`/`memory_update` nehmen optional\n`category`, `scope` und `refs`, und die neuen Werkzeuge `memory_classify` / `memory_reorder`\nentsprechen `nxm classify` / `nxm reorder`. Ohne sie konnte ein über MCP schreibender Agent nur\nErinnerungen anlegen, die keine Oberfläche einzuordnen wusste." - } - }, - { - "version": "0.44.0", - "date": "2026-08-03", - "items": [ - { - "type": "added", - "en": "`nxm` memories now carry a **category**, a **reach**, the **board items they are about**, and a\n**reading order**. Set them while writing (`nxm remember \"…\" --category rules --scope global --refs\nab12.0007`) or file an existing memory afterwards with the new `nxm classify ` — filing never\ntouches the text, so `updated` keeps dating the fact itself. `nxm memories` gained `--category`,\n`--scope` and `--ordered`, and the new `nxm reorder …` stores an explicit reading sequence.\nNothing on the write path asks a model: `nxm remember` stays offline and deterministic, and ordering\nis a separate, deliberate verb.\n\nExisting memories are migrated the first time a new binary opens the workspace: they get reach\n`project` — exactly what they already did, since `nxs prime` replays them for this workspace — and\nthe reserved category `unsorted`, which makes them countable. Nothing else changes; the session\nblock a `nxs prime` hands you is byte-for-byte what it was.", - "de": "Erinnerungen in `nxm` tragen jetzt eine **Kategorie**, eine **Reichweite**, die **Board-Einträge, um\ndie es geht**, und eine **Lesereihenfolge**. Beim Schreiben mitgeben (`nxm remember \"…\" --category\nrules --scope global --refs ab12.0007`) oder eine bestehende Erinnerung nachträglich mit dem neuen\n`nxm classify ` einsortieren — das Einsortieren rührt den Text nicht an, `updated` datiert\nweiterhin die Tatsache selbst. `nxm memories` kennt nun `--category`, `--scope` und `--ordered`, und\ndas neue `nxm reorder …` legt eine ausdrückliche Lesereihenfolge ab. Auf dem Schreibpfad fragt\nnichts ein Modell: `nxm remember` bleibt offline und deterministisch, die Ordnung ist ein eigener, bewusst\nseltener Befehl.\n\nBestehende Erinnerungen werden beim ersten Öffnen durch ein neues Binary migriert: Sie bekommen die\nReichweite `project` — genau das, was sie ohnehin taten, denn `nxs prime` spielt sie für diesen\nArbeitsbereich ein — und die reservierte Kategorie `unsorted`, die sie zählbar macht. Sonst ändert\nsich nichts; der Sitzungsblock aus `nxs prime` ist Byte für Byte derselbe." - } - ], - "notes": { - "en": "### Added\n- `nxm` memories now carry a **category**, a **reach**, the **board items they are about**, and a\n**reading order**. Set them while writing (`nxm remember \"…\" --category rules --scope global --refs\nab12.0007`) or file an existing memory afterwards with the new `nxm classify ` — filing never\ntouches the text, so `updated` keeps dating the fact itself. `nxm memories` gained `--category`,\n`--scope` and `--ordered`, and the new `nxm reorder …` stores an explicit reading sequence.\nNothing on the write path asks a model: `nxm remember` stays offline and deterministic, and ordering\nis a separate, deliberate verb.\n\nExisting memories are migrated the first time a new binary opens the workspace: they get reach\n`project` — exactly what they already did, since `nxs prime` replays them for this workspace — and\nthe reserved category `unsorted`, which makes them countable. Nothing else changes; the session\nblock a `nxs prime` hands you is byte-for-byte what it was.", - "de": "### Neu\n- Erinnerungen in `nxm` tragen jetzt eine **Kategorie**, eine **Reichweite**, die **Board-Einträge, um\ndie es geht**, und eine **Lesereihenfolge**. Beim Schreiben mitgeben (`nxm remember \"…\" --category\nrules --scope global --refs ab12.0007`) oder eine bestehende Erinnerung nachträglich mit dem neuen\n`nxm classify ` einsortieren — das Einsortieren rührt den Text nicht an, `updated` datiert\nweiterhin die Tatsache selbst. `nxm memories` kennt nun `--category`, `--scope` und `--ordered`, und\ndas neue `nxm reorder …` legt eine ausdrückliche Lesereihenfolge ab. Auf dem Schreibpfad fragt\nnichts ein Modell: `nxm remember` bleibt offline und deterministisch, die Ordnung ist ein eigener, bewusst\nseltener Befehl.\n\nBestehende Erinnerungen werden beim ersten Öffnen durch ein neues Binary migriert: Sie bekommen die\nReichweite `project` — genau das, was sie ohnehin taten, denn `nxs prime` spielt sie für diesen\nArbeitsbereich ein — und die reservierte Kategorie `unsorted`, die sie zählbar macht. Sonst ändert\nsich nichts; der Sitzungsblock aus `nxs prime` ist Byte für Byte derselbe." - } - }, - { - "version": "0.43.0", - "date": "2026-08-03", - "items": [ - { - "type": "fixed", - "en": "A long-running role no longer runs out of depth just for holding a conversation. The role runtime's\ndepth guard now counts the *open* chain — work commissioned and not yet answered — instead of every\nagent-to-agent hop a session ever took part in. Previously an answer travelling back up the chain\ndeepened the session it returned to, and that counter never falls: an orchestrator talking to a\nchannel owner gained four hops per round and was refused on every verb after about seven rounds,\npermanently, with neither a session handover nor a fresh terminal able to bring it back. Commissions\n(`nxc send --role`, `nxc send --session`, a channel fan-out, a workflow step, a liveness nudge) still\ncount one hop deeper and still hit the cap, so two roles that only ever push work at each other and\nnever answer are bounded exactly as before.", - "de": "Eine langlebige Rolle geht nicht mehr allein davon k.o., dass sie ein Gespräch führt. Der\nTiefenschutz der Rollen-Laufzeit zählt jetzt die *offene* Kette — beauftragte, noch unbeantwortete\nArbeit — statt jeden Agent-zu-Agent-Sprung, an dem eine Sitzung je beteiligt war. Bisher vertiefte\neine Antwort auf dem Rückweg die Sitzung, zu der sie zurückkehrte, und dieser Zähler sinkt nie: Ein\nOrchestrator im Austausch mit einem Kanal-Eigner gewann vier Sprünge je Runde und wurde nach etwa\nsieben Runden bei jedem Verb abgewiesen — dauerhaft, und weder eine Sitzungsübergabe noch ein frisches\nTerminal holten ihn zurück. Beauftragungen (`nxc send --role`, `nxc send --session`, ein\nKanal-Fan-out, ein Workflow-Schritt, ein Lebenszeichen-Anstoß) zählen weiterhin eine Stufe tiefer und\nlaufen weiterhin in den Deckel: Zwei Rollen, die sich nur Arbeit zuschieben und nie antworten, sind\ngenau wie zuvor beschränkt." - } - ], - "notes": { - "en": "### Fixed\n- A long-running role no longer runs out of depth just for holding a conversation. The role runtime's\ndepth guard now counts the *open* chain — work commissioned and not yet answered — instead of every\nagent-to-agent hop a session ever took part in. Previously an answer travelling back up the chain\ndeepened the session it returned to, and that counter never falls: an orchestrator talking to a\nchannel owner gained four hops per round and was refused on every verb after about seven rounds,\npermanently, with neither a session handover nor a fresh terminal able to bring it back. Commissions\n(`nxc send --role`, `nxc send --session`, a channel fan-out, a workflow step, a liveness nudge) still\ncount one hop deeper and still hit the cap, so two roles that only ever push work at each other and\nnever answer are bounded exactly as before.", - "de": "### Behoben\n- Eine langlebige Rolle geht nicht mehr allein davon k.o., dass sie ein Gespräch führt. Der\nTiefenschutz der Rollen-Laufzeit zählt jetzt die *offene* Kette — beauftragte, noch unbeantwortete\nArbeit — statt jeden Agent-zu-Agent-Sprung, an dem eine Sitzung je beteiligt war. Bisher vertiefte\neine Antwort auf dem Rückweg die Sitzung, zu der sie zurückkehrte, und dieser Zähler sinkt nie: Ein\nOrchestrator im Austausch mit einem Kanal-Eigner gewann vier Sprünge je Runde und wurde nach etwa\nsieben Runden bei jedem Verb abgewiesen — dauerhaft, und weder eine Sitzungsübergabe noch ein frisches\nTerminal holten ihn zurück. Beauftragungen (`nxc send --role`, `nxc send --session`, ein\nKanal-Fan-out, ein Workflow-Schritt, ein Lebenszeichen-Anstoß) zählen weiterhin eine Stufe tiefer und\nlaufen weiterhin in den Deckel: Zwei Rollen, die sich nur Arbeit zuschieben und nie antworten, sind\ngenau wie zuvor beschränkt." - } - }, - { - "version": "0.42.0", - "date": "2026-08-03", - "items": [ - { - "type": "changed", - "en": "`nxm prime` and `nxc prime` are now assembled in the shared library layer instead of inside the\ncommand-line tool. The session-start block an app or a host shows you is the identical one the\ncommand line prints — same words, byte for byte — because both now render one and the same record\ninstead of each building its own. Until now only the command line could produce it, so an app had to\nrebuild the whole block by hand and the two could quietly drift apart.\n\nThe `--json` form of both commands grew accordingly: alongside the memories, the inbox and the\ncounts it now also carries the rule text, the context-recovery hint and the command list, so a tool\nreading it no longer has to know the wording to reproduce the block. Everything that was in there\nbefore is unchanged, and the human-readable output is unchanged down to the last space.\n\nFor chat, the \"declaration errors\" section stays exactly where it belongs: it is shown to a person\nat the keyboard and stayed hidden in an automatically started session, and that choice now sits with\nwhoever displays the block rather than being made deep inside.", - "de": "`nxm prime` und `nxc prime` werden jetzt in der geteilten Bibliotheksschicht zusammengestellt statt\nim Kommandozeilenwerkzeug. Der Sitzungsstart-Block, den eine App oder ein Host anzeigt, ist derselbe,\nden die Kommandozeile ausgibt — wortgleich, Byte für Byte —, weil beide nun ein und denselben Bericht\ndarstellen, statt ihn sich jeweils selbst zusammenzubauen. Bislang konnte ihn nur die Kommandozeile\nerzeugen, eine App musste ihn also von Hand nachbauen, und beide konnten unbemerkt auseinanderlaufen.\n\nDie `--json`-Form beider Befehle ist entsprechend gewachsen: neben den Erinnerungen, dem Posteingang\nund den Zählern trägt sie jetzt auch den Regeltext, den Wiederherstellungs-Hinweis und die\nKommandoliste, sodass ein auslesendes Werkzeug den Wortlaut nicht mehr kennen muss, um den Block\nnachzubilden. Alles, was vorher darin stand, bleibt unverändert, und die menschenlesbare Ausgabe\nbleibt es bis aufs letzte Leerzeichen.\n\nBeim Chat bleibt der Abschnitt „Declaration Errors\" genau dort, wo er hingehört: Er wird einem\nMenschen an der Tastatur gezeigt und blieb in einer automatisch gestarteten Sitzung verborgen — diese\nEntscheidung liegt jetzt bei dem, der den Block anzeigt, statt tief drinnen getroffen zu werden." - } - ], - "notes": { - "en": "### Changed\n- `nxm prime` and `nxc prime` are now assembled in the shared library layer instead of inside the\ncommand-line tool. The session-start block an app or a host shows you is the identical one the\ncommand line prints — same words, byte for byte — because both now render one and the same record\ninstead of each building its own. Until now only the command line could produce it, so an app had to\nrebuild the whole block by hand and the two could quietly drift apart.\n\nThe `--json` form of both commands grew accordingly: alongside the memories, the inbox and the\ncounts it now also carries the rule text, the context-recovery hint and the command list, so a tool\nreading it no longer has to know the wording to reproduce the block. Everything that was in there\nbefore is unchanged, and the human-readable output is unchanged down to the last space.\n\nFor chat, the \"declaration errors\" section stays exactly where it belongs: it is shown to a person\nat the keyboard and stayed hidden in an automatically started session, and that choice now sits with\nwhoever displays the block rather than being made deep inside.", - "de": "### Geändert\n- `nxm prime` und `nxc prime` werden jetzt in der geteilten Bibliotheksschicht zusammengestellt statt\nim Kommandozeilenwerkzeug. Der Sitzungsstart-Block, den eine App oder ein Host anzeigt, ist derselbe,\nden die Kommandozeile ausgibt — wortgleich, Byte für Byte —, weil beide nun ein und denselben Bericht\ndarstellen, statt ihn sich jeweils selbst zusammenzubauen. Bislang konnte ihn nur die Kommandozeile\nerzeugen, eine App musste ihn also von Hand nachbauen, und beide konnten unbemerkt auseinanderlaufen.\n\nDie `--json`-Form beider Befehle ist entsprechend gewachsen: neben den Erinnerungen, dem Posteingang\nund den Zählern trägt sie jetzt auch den Regeltext, den Wiederherstellungs-Hinweis und die\nKommandoliste, sodass ein auslesendes Werkzeug den Wortlaut nicht mehr kennen muss, um den Block\nnachzubilden. Alles, was vorher darin stand, bleibt unverändert, und die menschenlesbare Ausgabe\nbleibt es bis aufs letzte Leerzeichen.\n\nBeim Chat bleibt der Abschnitt „Declaration Errors\" genau dort, wo er hingehört: Er wird einem\nMenschen an der Tastatur gezeigt und blieb in einer automatisch gestarteten Sitzung verborgen — diese\nEntscheidung liegt jetzt bei dem, der den Block anzeigt, statt tief drinnen getroffen zu werden." - } - }, - { - "version": "0.41.0", - "date": "2026-08-03", - "items": [ - { - "type": "added", - "en": "A work order and the conversation that carried it out are no longer two unconnected artefacts.\n\nA workflow run now records the **milestone** it serves and the **set of board items** it was\ncommissioned with, as fields you can query rather than ticket ids buried in prose:\n`nxc workflow start \"…\" --milestone --ticket --ticket `. Every message the run sends\ncarries those items along, so filing a report under its subject is a lookup instead of a judgement\ncall. The set does not freeze at commissioning — `nxc workflow tickets add ` widens it while the\nrun is live — and a run that names no milestone starts exactly as before.\n\nA chat thread can now also be linked directly to a board item: `nxf thread link `.\nThe link says what it consists in — `worked_on` or `cited` — and how much the thread is about the\nitem — `bearing` or `passing` — and the two are independent. `nxf show ` lists the conversations\nabout an item, `nxf thread list ` lists what a thread is about, and `nxf next` flags only\n`bearing` conversations, so a long wandering discussion cannot flood the work list with everything\nit happened to touch. Links are n:m in both directions, can be made at any point in a thread's life,\nfirm up in place when a passing mention turns out to be the subject, and can be taken back with\n`nxf thread unlink`.", - "de": "Ein Arbeitsauftrag und das Gespräch, das ihn ausgeführt hat, sind nicht länger zwei getrennte\nArtefakte.\n\nEin Workflow-Lauf hält jetzt den **Meilenstein**, dem er dient, und die **Menge der Board-Einträge**\nfest, mit der er beauftragt wurde — als abfragbare Felder statt als Ticket-Kennungen im Fließtext:\n`nxc workflow start \"…\" --milestone --ticket --ticket `. Jede Nachricht, die der Lauf\nsendet, trägt diese Einträge mit; eine Meldung ihrem Thema zuzuordnen ist damit eine\nNachschlage-Operation und keine Ermessensfrage. Die Menge friert beim Beauftragen nicht ein —\n`nxc workflow tickets add ` erweitert sie im laufenden Auftrag — und ein Lauf ohne Meilenstein\nstartet unverändert.\n\nEin Chat-Faden lässt sich außerdem direkt an einen Board-Eintrag hängen: `nxf thread link \n`. Die Kante sagt, worin der Bezug besteht — `worked_on` oder `cited` — und wie sehr der Faden\ndavon handelt — `bearing` oder `passing`; beides ist frei kombinierbar. `nxf show ` weist die\nGespräche zu einem Eintrag aus, `nxf thread list ` zählt die Einträge eines Fadens auf, und\n`nxf next` meldet nur `bearing`-Gespräche, damit ein langes, wanderndes Gespräch die Arbeitsliste\nnicht mit allem flutet, was es beiläufig gestreift hat. Die Kanten sind n:m in beide Richtungen,\ndürfen jederzeit im Leben eines Fadens entstehen, festigen sich an Ort und Stelle, wenn aus einer\nNebenbemerkung der Gegenstand wird, und lassen sich mit `nxf thread unlink` wieder zurücknehmen.", - "facade": "changed", - "unreleased": true - } - ], - "notes": { - "en": "### Added\n- A work order and the conversation that carried it out are no longer two unconnected artefacts.\n\nA workflow run now records the **milestone** it serves and the **set of board items** it was\ncommissioned with, as fields you can query rather than ticket ids buried in prose:\n`nxc workflow start \"…\" --milestone --ticket --ticket `. Every message the run sends\ncarries those items along, so filing a report under its subject is a lookup instead of a judgement\ncall. The set does not freeze at commissioning — `nxc workflow tickets add ` widens it while the\nrun is live — and a run that names no milestone starts exactly as before.\n\nA chat thread can now also be linked directly to a board item: `nxf thread link `.\nThe link says what it consists in — `worked_on` or `cited` — and how much the thread is about the\nitem — `bearing` or `passing` — and the two are independent. `nxf show ` lists the conversations\nabout an item, `nxf thread list ` lists what a thread is about, and `nxf next` flags only\n`bearing` conversations, so a long wandering discussion cannot flood the work list with everything\nit happened to touch. Links are n:m in both directions, can be made at any point in a thread's life,\nfirm up in place when a passing mention turns out to be the subject, and can be taken back with\n`nxf thread unlink`.\n\n### Facade Contract\n- `changed` · A work order and the conversation that carried it out are no longer two unconnected artefacts.\n\nA workflow run now records the **milestone** it serves and the **set of board items** it was\ncommissioned with, as fields you can query rather than ticket ids buried in prose:\n`nxc workflow start \"…\" --milestone --ticket --ticket `. Every message the run sends\ncarries those items along, so filing a report under its subject is a lookup instead of a judgement\ncall. The set does not freeze at commissioning — `nxc workflow tickets add ` widens it while the\nrun is live — and a run that names no milestone starts exactly as before.\n\nA chat thread can now also be linked directly to a board item: `nxf thread link `.\nThe link says what it consists in — `worked_on` or `cited` — and how much the thread is about the\nitem — `bearing` or `passing` — and the two are independent. `nxf show ` lists the conversations\nabout an item, `nxf thread list ` lists what a thread is about, and `nxf next` flags only\n`bearing` conversations, so a long wandering discussion cannot flood the work list with everything\nit happened to touch. Links are n:m in both directions, can be made at any point in a thread's life,\nfirm up in place when a passing mention turns out to be the subject, and can be taken back with\n`nxf thread unlink`.", - "de": "### Neu\n- Ein Arbeitsauftrag und das Gespräch, das ihn ausgeführt hat, sind nicht länger zwei getrennte\nArtefakte.\n\nEin Workflow-Lauf hält jetzt den **Meilenstein**, dem er dient, und die **Menge der Board-Einträge**\nfest, mit der er beauftragt wurde — als abfragbare Felder statt als Ticket-Kennungen im Fließtext:\n`nxc workflow start \"…\" --milestone --ticket --ticket `. Jede Nachricht, die der Lauf\nsendet, trägt diese Einträge mit; eine Meldung ihrem Thema zuzuordnen ist damit eine\nNachschlage-Operation und keine Ermessensfrage. Die Menge friert beim Beauftragen nicht ein —\n`nxc workflow tickets add ` erweitert sie im laufenden Auftrag — und ein Lauf ohne Meilenstein\nstartet unverändert.\n\nEin Chat-Faden lässt sich außerdem direkt an einen Board-Eintrag hängen: `nxf thread link \n`. Die Kante sagt, worin der Bezug besteht — `worked_on` oder `cited` — und wie sehr der Faden\ndavon handelt — `bearing` oder `passing`; beides ist frei kombinierbar. `nxf show ` weist die\nGespräche zu einem Eintrag aus, `nxf thread list ` zählt die Einträge eines Fadens auf, und\n`nxf next` meldet nur `bearing`-Gespräche, damit ein langes, wanderndes Gespräch die Arbeitsliste\nnicht mit allem flutet, was es beiläufig gestreift hat. Die Kanten sind n:m in beide Richtungen,\ndürfen jederzeit im Leben eines Fadens entstehen, festigen sich an Ort und Stelle, wenn aus einer\nNebenbemerkung der Gegenstand wird, und lassen sich mit `nxf thread unlink` wieder zurücknehmen.\n\n### Facade-Kontrakt\n- `changed` · Ein Arbeitsauftrag und das Gespräch, das ihn ausgeführt hat, sind nicht länger zwei getrennte\nArtefakte.\n\nEin Workflow-Lauf hält jetzt den **Meilenstein**, dem er dient, und die **Menge der Board-Einträge**\nfest, mit der er beauftragt wurde — als abfragbare Felder statt als Ticket-Kennungen im Fließtext:\n`nxc workflow start \"…\" --milestone --ticket --ticket `. Jede Nachricht, die der Lauf\nsendet, trägt diese Einträge mit; eine Meldung ihrem Thema zuzuordnen ist damit eine\nNachschlage-Operation und keine Ermessensfrage. Die Menge friert beim Beauftragen nicht ein —\n`nxc workflow tickets add ` erweitert sie im laufenden Auftrag — und ein Lauf ohne Meilenstein\nstartet unverändert.\n\nEin Chat-Faden lässt sich außerdem direkt an einen Board-Eintrag hängen: `nxf thread link \n`. Die Kante sagt, worin der Bezug besteht — `worked_on` oder `cited` — und wie sehr der Faden\ndavon handelt — `bearing` oder `passing`; beides ist frei kombinierbar. `nxf show ` weist die\nGespräche zu einem Eintrag aus, `nxf thread list ` zählt die Einträge eines Fadens auf, und\n`nxf next` meldet nur `bearing`-Gespräche, damit ein langes, wanderndes Gespräch die Arbeitsliste\nnicht mit allem flutet, was es beiläufig gestreift hat. Die Kanten sind n:m in beide Richtungen,\ndürfen jederzeit im Leben eines Fadens entstehen, festigen sich an Ort und Stelle, wenn aus einer\nNebenbemerkung der Gegenstand wird, und lassen sich mit `nxf thread unlink` wieder zurücknehmen." - } - }, - { - "version": "0.40.0", - "date": "2026-08-02", - "items": [ - { - "type": "fixed", - "en": "A declared channel that finishes at the same moment its timer fires no longer starts two summarizer\nsessions for the same board on the same machine. Both completions used to see the board as \"just\nfinished\" and each start its own summarizer — two paid sessions doing the same work, and two\nsummaries posted into the thread of which only the last one counted. One completion now wins the\nright to summarize, decided by the machine holding the board, so the other steps aside and the run\nkeeps waiting for the one summary that is actually coming. (Two different machines syncing the same\nworkspace can still each start one; that case is tracked separately.)", - "de": "Ein deklarierter Kanal, der genau dann fertig wird, wenn sein Timer fällt, startet auf demselben\nRechner nicht mehr zwei Zusammenfasser-Sitzungen für dieselbe Tafel. Bisher sahen beide Abschlüsse\ndie Tafel als „gerade fertig\" und starteten je einen eigenen Zusammenfasser — zwei bezahlte\nSitzungen für dieselbe Arbeit und zwei Zusammenfassungen im Thread, von denen nur die letzte zählte.\nJetzt gewinnt genau ein Abschluss das Recht zusammenzufassen, entschieden vom Rechner, der die Tafel\nhält; der andere tritt zurück, und der Ablauf wartet weiter auf die eine Zusammenfassung, die\nwirklich kommt. (Zwei verschiedene Rechner am selben synchronisierten Arbeitsbereich können weiterhin\nje einen starten; dieser Fall wird gesondert verfolgt.)" - } - ], - "notes": { - "en": "### Fixed\n- A declared channel that finishes at the same moment its timer fires no longer starts two summarizer\nsessions for the same board on the same machine. Both completions used to see the board as \"just\nfinished\" and each start its own summarizer — two paid sessions doing the same work, and two\nsummaries posted into the thread of which only the last one counted. One completion now wins the\nright to summarize, decided by the machine holding the board, so the other steps aside and the run\nkeeps waiting for the one summary that is actually coming. (Two different machines syncing the same\nworkspace can still each start one; that case is tracked separately.)", - "de": "### Behoben\n- Ein deklarierter Kanal, der genau dann fertig wird, wenn sein Timer fällt, startet auf demselben\nRechner nicht mehr zwei Zusammenfasser-Sitzungen für dieselbe Tafel. Bisher sahen beide Abschlüsse\ndie Tafel als „gerade fertig\" und starteten je einen eigenen Zusammenfasser — zwei bezahlte\nSitzungen für dieselbe Arbeit und zwei Zusammenfassungen im Thread, von denen nur die letzte zählte.\nJetzt gewinnt genau ein Abschluss das Recht zusammenzufassen, entschieden vom Rechner, der die Tafel\nhält; der andere tritt zurück, und der Ablauf wartet weiter auf die eine Zusammenfassung, die\nwirklich kommt. (Zwei verschiedene Rechner am selben synchronisierten Arbeitsbereich können weiterhin\nje einen starten; dieser Fall wird gesondert verfolgt.)" - } - }, - { - "version": "0.39.0", - "date": "2026-08-02", - "items": [ - { - "type": "added", - "en": "An app can now bring its own agent runtime. The role runtime's worker seam was always meant to have\nan escape hatch — \"you run the sessions, we decide what a role is\" — but there was no way to actually\nhand one over. There is now, and it is shaped for a runtime that is not a local process: a session is\nnamed by an opaque id rather than a process handle, starting one may be answered later instead of\nimmediately, and \"that session no longer exists\" is its own recognisable case rather than one more\nopaque failure. Cloud runtimes retire idle sessions silently, so that last one is routine, not an\nincident.\n\nA workflow step can now say how long it may go quiet. Declare `liveness: 20m` on a step and the\norchestrator watches the session working on it; if it stops producing anything at all past that\nwindow, it is woken with a reminder to pick up where it left off — and to run long checks in the\nforeground, which is what killed the sessions this was built for. After a bounded number of\nunanswered wakes (`max_nudges`, two by default) the step is escalated instead of poked forever:\nrouted through a `when: stalled` transition if the workflow declares one, otherwise the run stops\nand says so. Steps that declare nothing are untouched and unwatched.\n\nAlso new: `nxc workflow liveness` to run that check by hand on a run that looks stuck.", - "de": "Eine Anwendung kann jetzt ihre eigene Agenten-Laufzeit mitbringen. Die Worker-Naht der\nRollen-Laufzeit sollte immer einen Notausgang haben — „ihr führt die Sitzungen aus, wir legen fest,\nwas eine Rolle ist\" —, nur ließ sich bisher keiner übergeben. Jetzt schon, und zwar in einer Form,\ndie zu einer Laufzeit passt, die kein lokaler Prozess ist: Eine Sitzung trägt eine undurchsichtige\nKennung statt eines Prozess-Handles, ihr Start darf auch später beantwortet werden statt sofort, und\n„diese Sitzung gibt es nicht mehr\" ist ein eigener, erkennbarer Fall statt eines beliebigen Fehlers.\nCloud-Laufzeiten räumen untätige Sitzungen still ab — der letzte Punkt ist also Alltag, kein Störfall.\n\nEin Arbeitsablauf-Schritt kann jetzt sagen, wie lange er still sein darf. Wer `liveness: 20m` an\neinem Schritt deklariert, lässt die Sitzung dahinter beobachten; produziert sie über dieses Fenster\nhinaus gar nichts mehr, wird sie geweckt — mit dem Hinweis, dort weiterzumachen, wo sie aufgehört\nhat, und lange Prüfläufe im Vordergrund laufen zu lassen, woran genau die Sitzungen gestorben sind,\nfür die das gebaut wurde. Nach einer begrenzten Zahl unbeantworteter Weckrufe (`max_nudges`,\nstandardmäßig zwei) wird der Schritt eskaliert statt endlos angestupst: über einen\n`when: stalled`-Übergang, falls der Ablauf einen deklariert, sonst hält der Lauf an und sagt es.\nSchritte ohne Deklaration bleiben unangetastet und unbeobachtet.\n\nEbenfalls neu: `nxc workflow liveness`, um diese Prüfung von Hand auf einem Lauf auszuführen, der\nfestzuhängen scheint." - }, - { - "type": "changed", - "en": "**For anyone embedding nexus-chat as a Rust library:** the worker seam's shape changed, and it is a\ncompile break rather than a silent one. `Worker::trigger` now answers with what happened to the\nsession — whether a runtime id came back, or the session it was asked to resume is gone — instead of\na bare success/failure, so any existing implementation of that trait needs its signature updated.\n`WorkerConfig` gained a variant (the host-supplied worker this release is about) and is\n`#[non_exhaustive]` from now on, so a `match` over it needs a wildcard arm and later variants will\ncost nothing. `WorkflowStep` gained the two new liveness fields, so a struct literal that builds one\nby hand needs them.\n\nNothing about the embedding handle itself changed: every `Engine` verb keeps its signature and stays\nsynchronous, and the new `bind_runtime_session` / `workflow_liveness` are additions beside them.\n\nWorth knowing: nexus-chat's Rust surface has no automated compatibility gate (the one that exists\ncovers the flow facade only), so a change like this reaches you through these notes rather than\nthrough a failing check. That is why it is spelled out here in full.", - "de": "**Für alle, die nexus-chat als Rust-Bibliothek einbinden:** Die Form der Worker-Naht hat sich\ngeändert, und zwar als Übersetzungsfehler, nicht stillschweigend. `Worker::trigger` meldet jetzt\nzurück, was mit der Sitzung geschehen ist — ob eine Laufzeit-Kennung vorliegt oder die Sitzung, die\nfortgesetzt werden sollte, verschwunden ist — statt bloß Erfolg oder Misserfolg; jede vorhandene\nUmsetzung dieses Traits braucht also eine angepasste Signatur. `WorkerConfig` hat eine Variante\nbekommen (den mitgebrachten Worker, um den es in dieser Veröffentlichung geht) und ist ab jetzt\n`#[non_exhaustive]`, ein `match` darüber braucht also einen Auffang-Zweig — spätere Varianten kosten\ndafür nichts mehr. `WorkflowStep` hat die zwei neuen Liveness-Felder bekommen, ein von Hand\ngebautes Struct-Literal braucht sie also.\n\nAm Einbettungs-Griff selbst ändert sich nichts: Jedes `Engine`-Verb behält seine Signatur und bleibt\nsynchron; `bind_runtime_session` und `workflow_liveness` kommen daneben hinzu.\n\nGut zu wissen: Die Rust-Oberfläche von nexus-chat hat kein automatisches Kompatibilitäts-Gate (das\nvorhandene deckt nur die Flow-Fassade ab). Eine Änderung wie diese erreicht euch also über diese\nNotizen und nicht über eine rote Prüfung — deshalb steht sie hier ausführlich." - } - ], - "notes": { - "en": "### Added\n- An app can now bring its own agent runtime. The role runtime's worker seam was always meant to have\nan escape hatch — \"you run the sessions, we decide what a role is\" — but there was no way to actually\nhand one over. There is now, and it is shaped for a runtime that is not a local process: a session is\nnamed by an opaque id rather than a process handle, starting one may be answered later instead of\nimmediately, and \"that session no longer exists\" is its own recognisable case rather than one more\nopaque failure. Cloud runtimes retire idle sessions silently, so that last one is routine, not an\nincident.\n\nA workflow step can now say how long it may go quiet. Declare `liveness: 20m` on a step and the\norchestrator watches the session working on it; if it stops producing anything at all past that\nwindow, it is woken with a reminder to pick up where it left off — and to run long checks in the\nforeground, which is what killed the sessions this was built for. After a bounded number of\nunanswered wakes (`max_nudges`, two by default) the step is escalated instead of poked forever:\nrouted through a `when: stalled` transition if the workflow declares one, otherwise the run stops\nand says so. Steps that declare nothing are untouched and unwatched.\n\nAlso new: `nxc workflow liveness` to run that check by hand on a run that looks stuck.\n\n### Changed\n- **For anyone embedding nexus-chat as a Rust library:** the worker seam's shape changed, and it is a\ncompile break rather than a silent one. `Worker::trigger` now answers with what happened to the\nsession — whether a runtime id came back, or the session it was asked to resume is gone — instead of\na bare success/failure, so any existing implementation of that trait needs its signature updated.\n`WorkerConfig` gained a variant (the host-supplied worker this release is about) and is\n`#[non_exhaustive]` from now on, so a `match` over it needs a wildcard arm and later variants will\ncost nothing. `WorkflowStep` gained the two new liveness fields, so a struct literal that builds one\nby hand needs them.\n\nNothing about the embedding handle itself changed: every `Engine` verb keeps its signature and stays\nsynchronous, and the new `bind_runtime_session` / `workflow_liveness` are additions beside them.\n\nWorth knowing: nexus-chat's Rust surface has no automated compatibility gate (the one that exists\ncovers the flow facade only), so a change like this reaches you through these notes rather than\nthrough a failing check. That is why it is spelled out here in full.", - "de": "### Neu\n- Eine Anwendung kann jetzt ihre eigene Agenten-Laufzeit mitbringen. Die Worker-Naht der\nRollen-Laufzeit sollte immer einen Notausgang haben — „ihr führt die Sitzungen aus, wir legen fest,\nwas eine Rolle ist\" —, nur ließ sich bisher keiner übergeben. Jetzt schon, und zwar in einer Form,\ndie zu einer Laufzeit passt, die kein lokaler Prozess ist: Eine Sitzung trägt eine undurchsichtige\nKennung statt eines Prozess-Handles, ihr Start darf auch später beantwortet werden statt sofort, und\n„diese Sitzung gibt es nicht mehr\" ist ein eigener, erkennbarer Fall statt eines beliebigen Fehlers.\nCloud-Laufzeiten räumen untätige Sitzungen still ab — der letzte Punkt ist also Alltag, kein Störfall.\n\nEin Arbeitsablauf-Schritt kann jetzt sagen, wie lange er still sein darf. Wer `liveness: 20m` an\neinem Schritt deklariert, lässt die Sitzung dahinter beobachten; produziert sie über dieses Fenster\nhinaus gar nichts mehr, wird sie geweckt — mit dem Hinweis, dort weiterzumachen, wo sie aufgehört\nhat, und lange Prüfläufe im Vordergrund laufen zu lassen, woran genau die Sitzungen gestorben sind,\nfür die das gebaut wurde. Nach einer begrenzten Zahl unbeantworteter Weckrufe (`max_nudges`,\nstandardmäßig zwei) wird der Schritt eskaliert statt endlos angestupst: über einen\n`when: stalled`-Übergang, falls der Ablauf einen deklariert, sonst hält der Lauf an und sagt es.\nSchritte ohne Deklaration bleiben unangetastet und unbeobachtet.\n\nEbenfalls neu: `nxc workflow liveness`, um diese Prüfung von Hand auf einem Lauf auszuführen, der\nfestzuhängen scheint.\n\n### Geändert\n- **Für alle, die nexus-chat als Rust-Bibliothek einbinden:** Die Form der Worker-Naht hat sich\ngeändert, und zwar als Übersetzungsfehler, nicht stillschweigend. `Worker::trigger` meldet jetzt\nzurück, was mit der Sitzung geschehen ist — ob eine Laufzeit-Kennung vorliegt oder die Sitzung, die\nfortgesetzt werden sollte, verschwunden ist — statt bloß Erfolg oder Misserfolg; jede vorhandene\nUmsetzung dieses Traits braucht also eine angepasste Signatur. `WorkerConfig` hat eine Variante\nbekommen (den mitgebrachten Worker, um den es in dieser Veröffentlichung geht) und ist ab jetzt\n`#[non_exhaustive]`, ein `match` darüber braucht also einen Auffang-Zweig — spätere Varianten kosten\ndafür nichts mehr. `WorkflowStep` hat die zwei neuen Liveness-Felder bekommen, ein von Hand\ngebautes Struct-Literal braucht sie also.\n\nAm Einbettungs-Griff selbst ändert sich nichts: Jedes `Engine`-Verb behält seine Signatur und bleibt\nsynchron; `bind_runtime_session` und `workflow_liveness` kommen daneben hinzu.\n\nGut zu wissen: Die Rust-Oberfläche von nexus-chat hat kein automatisches Kompatibilitäts-Gate (das\nvorhandene deckt nur die Flow-Fassade ab). Eine Änderung wie diese erreicht euch also über diese\nNotizen und nicht über eine rote Prüfung — deshalb steht sie hier ausführlich." - } - }, - { - "version": "0.38.0", - "date": "2026-08-01", - "items": [ - { - "type": "changed", - "en": "The role runtime's spawn-depth guard no longer trusts the counter a running role reports about\nitself. The chain depth is recorded per session when a role is spawned and read back from there, so a\nrole that clears its own `NXC_HOP` — or an app that passes `hop: 0` on every call — can no longer\nreset the chain and keep summoning roles past the cap of 32. A caller-supplied hop count is still\nhonoured when it is HIGHER, so a host that tracks its own chain is unaffected. Existing workspaces\ngain the new column on first open; sessions already running read as fresh chains, exactly as before.\nThe guard's message now names the hop rather than the `NXC_HOP` variable, since the variable is no\nlonger where the number comes from. For anyone embedding the `nexus-chat` orchestration module\ndirectly rather than through the `Engine` handle: the functions that spawn a session now take a\n`CheckedHop` instead of a bare number, and the only way to obtain one is `resolve_hop` — so \"has the\nguard run\" is a question the compiler answers. The `Engine` verbs and their `Caller` are unchanged.\n\nA triggered role also reads the right workflow now. Its system prompt carries a block describing its\nown steps; with more than one workflow declared, that block was always composed from the workspace's\nnominated team workflow, so a role fired as part of a `hotfix` run was shown `build-and-ship`'s steps\nalongside `hotfix`'s actual instructions. The block now follows the run — for a workflow step, a\nchannel fan-out, and a role summoned by another role that is mid-run. Work outside any run still\nreads the nominated workflow, and a run whose workflow is no longer declared gets no block at all\nrather than the wrong one.", - "de": "Der Tiefenschutz der Rollen-Laufzeit vertraut dem Zähler nicht mehr, den eine laufende Rolle über\nsich selbst meldet. Die Kettentiefe wird beim Start einer Rolle pro Sitzung festgehalten und von dort\nwieder gelesen. Eine Rolle, die ihr eigenes `NXC_HOP` löscht — oder eine App, die bei jedem Aufruf\n`hop: 0` übergibt —, kann die Kette damit nicht mehr zurücksetzen und über die Obergrenze von 32\nhinaus weitere Rollen starten. Ein höherer übergebener Wert gilt weiterhin, eine App mit eigener\nKettenzählung ist also nicht betroffen. Bestehende Arbeitsbereiche erhalten die neue Spalte beim\nersten Öffnen; bereits laufende Sitzungen gelten wie bisher als frische Kette. Die Fehlermeldung\nnennt jetzt die Sprungtiefe statt der Variable `NXC_HOP`, weil die Zahl nicht mehr von dort kommt.\nFür alle, die das Orchestrierungs-Modul von `nexus-chat` direkt einbinden statt über den\n`Engine`-Griff: die Funktionen, die eine Sitzung starten, nehmen jetzt einen `CheckedHop` statt einer\nblanken Zahl, und den gibt es nur von `resolve_hop` — ob der Schutz gelaufen ist, beantwortet damit\nder Compiler. Die `Engine`-Verben und ihr `Caller` bleiben unverändert.\n\nEine gestartete Rolle liest außerdem den richtigen Arbeitsablauf. Ihr System-Prompt enthält einen\nBlock mit ihren eigenen Schritten; bei mehreren deklarierten Abläufen stammte dieser Block immer aus\ndem nominierten Team-Ablauf des Arbeitsbereichs — eine Rolle in einem `hotfix`-Lauf bekam also die\nSchritte von `build-and-ship` neben den tatsächlichen Anweisungen aus `hotfix`. Der Block folgt jetzt\ndem Lauf: beim Arbeitsablauf-Schritt, beim Verteilen an einen Kanal und bei einer Rolle, die von\neiner anderen mitten im Lauf hinzugezogen wird. Arbeit außerhalb jedes Laufs liest weiterhin den\nnominierten Ablauf, und ein Lauf, dessen Ablauf nicht mehr deklariert ist, bekommt gar keinen Block\nstatt des falschen." - } - ], - "notes": { - "en": "### Changed\n- The role runtime's spawn-depth guard no longer trusts the counter a running role reports about\nitself. The chain depth is recorded per session when a role is spawned and read back from there, so a\nrole that clears its own `NXC_HOP` — or an app that passes `hop: 0` on every call — can no longer\nreset the chain and keep summoning roles past the cap of 32. A caller-supplied hop count is still\nhonoured when it is HIGHER, so a host that tracks its own chain is unaffected. Existing workspaces\ngain the new column on first open; sessions already running read as fresh chains, exactly as before.\nThe guard's message now names the hop rather than the `NXC_HOP` variable, since the variable is no\nlonger where the number comes from. For anyone embedding the `nexus-chat` orchestration module\ndirectly rather than through the `Engine` handle: the functions that spawn a session now take a\n`CheckedHop` instead of a bare number, and the only way to obtain one is `resolve_hop` — so \"has the\nguard run\" is a question the compiler answers. The `Engine` verbs and their `Caller` are unchanged.\n\nA triggered role also reads the right workflow now. Its system prompt carries a block describing its\nown steps; with more than one workflow declared, that block was always composed from the workspace's\nnominated team workflow, so a role fired as part of a `hotfix` run was shown `build-and-ship`'s steps\nalongside `hotfix`'s actual instructions. The block now follows the run — for a workflow step, a\nchannel fan-out, and a role summoned by another role that is mid-run. Work outside any run still\nreads the nominated workflow, and a run whose workflow is no longer declared gets no block at all\nrather than the wrong one.", - "de": "### Geändert\n- Der Tiefenschutz der Rollen-Laufzeit vertraut dem Zähler nicht mehr, den eine laufende Rolle über\nsich selbst meldet. Die Kettentiefe wird beim Start einer Rolle pro Sitzung festgehalten und von dort\nwieder gelesen. Eine Rolle, die ihr eigenes `NXC_HOP` löscht — oder eine App, die bei jedem Aufruf\n`hop: 0` übergibt —, kann die Kette damit nicht mehr zurücksetzen und über die Obergrenze von 32\nhinaus weitere Rollen starten. Ein höherer übergebener Wert gilt weiterhin, eine App mit eigener\nKettenzählung ist also nicht betroffen. Bestehende Arbeitsbereiche erhalten die neue Spalte beim\nersten Öffnen; bereits laufende Sitzungen gelten wie bisher als frische Kette. Die Fehlermeldung\nnennt jetzt die Sprungtiefe statt der Variable `NXC_HOP`, weil die Zahl nicht mehr von dort kommt.\nFür alle, die das Orchestrierungs-Modul von `nexus-chat` direkt einbinden statt über den\n`Engine`-Griff: die Funktionen, die eine Sitzung starten, nehmen jetzt einen `CheckedHop` statt einer\nblanken Zahl, und den gibt es nur von `resolve_hop` — ob der Schutz gelaufen ist, beantwortet damit\nder Compiler. Die `Engine`-Verben und ihr `Caller` bleiben unverändert.\n\nEine gestartete Rolle liest außerdem den richtigen Arbeitsablauf. Ihr System-Prompt enthält einen\nBlock mit ihren eigenen Schritten; bei mehreren deklarierten Abläufen stammte dieser Block immer aus\ndem nominierten Team-Ablauf des Arbeitsbereichs — eine Rolle in einem `hotfix`-Lauf bekam also die\nSchritte von `build-and-ship` neben den tatsächlichen Anweisungen aus `hotfix`. Der Block folgt jetzt\ndem Lauf: beim Arbeitsablauf-Schritt, beim Verteilen an einen Kanal und bei einer Rolle, die von\neiner anderen mitten im Lauf hinzugezogen wird. Arbeit außerhalb jedes Laufs liest weiterhin den\nnominierten Ablauf, und ein Lauf, dessen Ablauf nicht mehr deklariert ist, bekommt gar keinen Block\nstatt des falschen." - } - }, - { - "version": "0.37.0", - "date": "2026-08-01", - "items": [ - { - "type": "added", - "en": "The role runtime is now reachable from an application, not only from the command line. An app that\nembeds the chat engine can drive the whole thing through its library handle: start a workflow and\nadvance it step by step, hand a role a new task or continue its session, open a declared channel and\nfan it out to its members, and open a review board — the same verbs `nxc` has, with the same\nreceipts. It can also bring **its own** roles, channels and workflows instead of requiring a\n`roles/` folder inside every user's project, and replace that catalogue while the workspace stays\nopen, so an agent edited in the app takes effect on the next trigger without a restart. Nothing\nchanges for an application that only reads and posts messages — the role runtime is opt-in.\nRoles and workflow steps can now choose which Claude model they run on — `model: fable`, `opus` or\n`sonnet` in a role file, or on a single workflow step to pair a cheap reviewer with an expensive\nimplementer. `nxc send --model ` picks the model for one call and overrides both.\nA role that names no model keeps running on the default, exactly as before.", - "de": "Die Rollen-Laufzeit ist jetzt aus einer Anwendung heraus erreichbar, nicht mehr nur über die\nKommandozeile. Eine App, die die Chat-Engine einbettet, kann sie vollständig über ihr\nBibliotheks-Handle steuern: einen Workflow starten und Schritt für Schritt weiterführen, einer Rolle\neine neue Aufgabe geben oder ihre Sitzung fortsetzen, einen deklarierten Kanal öffnen und an seine\nMitglieder verteilen sowie ein Review-Board eröffnen — dieselben Verben wie `nxc`, mit denselben\nQuittungen. Sie kann außerdem **ihre eigenen** Rollen, Kanäle und Workflows mitbringen, statt in\njedem Projekt einen `roles/`-Ordner zu verlangen, und diesen Katalog austauschen, während der\nArbeitsbereich geöffnet bleibt — eine in der App bearbeitete Rolle wirkt damit beim nächsten Auslösen\nohne Neustart. Für Anwendungen, die nur Nachrichten lesen und schreiben, ändert sich nichts: die\nRollen-Laufzeit ist ausdrücklich zuzuschalten.\nRollen und Workflow-Schritte können jetzt wählen, auf welchem Claude-Modell sie laufen — `model:\nfable`, `opus` oder `sonnet` in einer Rollendatei oder an einem einzelnen Workflow-Schritt, um etwa\neine günstige Prüfung mit einer teuren Umsetzung zu kombinieren. `nxc send --model\n` wählt das Modell für einen einzelnen Aufruf und schlägt beide. Eine Rolle ohne\nModellangabe läuft unverändert auf der Voreinstellung." - }, - { - "type": "fixed", - "en": "A reply sent through the embedded engine handle now finishes what it answers. It used to\npost the message and stop — it never woke the requester when a review board completed, never\ndelivered a declared channel's result, and never advanced the workflow run waiting on that board,\nall silently behind a successful receipt. An application replying through the handle therefore left\nboards that never completed and runs that never moved on. `nxc reply` was always correct; only the\nembedded path was affected.", - "de": "Eine Antwort über das eingebettete Engine-Handle bringt jetzt zu Ende, was sie\nbeantwortet. Bisher wurde die Nachricht nur geschrieben — der Anfragende wurde beim Abschluss eines\nReview-Boards nie geweckt, das Ergebnis eines deklarierten Kanals nie zugestellt und der auf dieses\nBoard wartende Workflow-Lauf nie weitergeführt, alles stillschweigend hinter einer erfolgreichen\nQuittung. Eine Anwendung, die über das Handle antwortete, hinterließ so Boards, die nie fertig\nwurden, und Läufe, die nie weiterliefen. `nxc reply` war immer korrekt; betroffen war nur der\neingebettete Weg." - }, - { - "type": "changed", - "en": "`nxc reply` no longer fails when it cannot pass a completed review board on. Replying\nthe last outstanding answer into an ordinary channel used to report an error whenever the board's\nopener could not be started again — even though the reply itself had been written and was readable\nin the thread. It now succeeds, like the same reply into a declared channel always did, and says in\nits `--json` output that the hand-off did not happen (`wake_skipped`, naming the session and the\nreason) so nothing is quietly lost. The key is only present when there is something to report.\n`nxc workflow tick` — what a review board's timeout timer runs unattended, and what an application\ncalls to re-check a board — now reports the same thing: which session it handed the finished board\nto (`woke`), and the same `wake_skipped` when it could not hand it over at all. It used to say only\nthat it had acted, which looked exactly the same whether the hand-off arrived or was lost.", - "de": "`nxc reply` scheitert nicht mehr, wenn ein abgeschlossenes Review-Board nicht\nweitergereicht werden kann. Wer die letzte ausstehende Antwort in einen gewöhnlichen Kanal schrieb,\nbekam bisher einen Fehler gemeldet, sobald die eröffnende Rolle nicht erneut gestartet werden konnte\n— obwohl die Antwort geschrieben und im Verlauf lesbar war. Der Aufruf gelingt jetzt, so wie\ndieselbe Antwort in einem deklarierten Kanal schon immer gelang, und die `--json`-Ausgabe hält fest,\ndass die Weitergabe nicht stattgefunden hat (`wake_skipped`, mit Sitzung und Grund) — nichts geht\nalso stillschweigend verloren. Der Schlüssel erscheint nur, wenn es etwas zu melden gibt.\n`nxc workflow tick` — das, was der Fristablauf eines Review-Boards unbeaufsichtigt ausführt und was\neine Anwendung zum Nachprüfen eines Boards aufruft — meldet jetzt dasselbe: an welche Sitzung das\nfertige Board übergeben wurde (`woke`) und dasselbe `wake_skipped`, wenn die Übergabe nicht möglich\nwar. Bisher stand dort nur, dass gehandelt wurde — ununterscheidbar davon, ob die Übergabe ankam\noder verloren ging." - }, - { - "type": "changed", - "en": "**`nxc reply` returns a different exit code in one case:** it no longer fails when it\ncannot hand the reply over to the recipient it was addressed to. Replying to a message that names a\nsession to continue used to report an error whenever that session could not be started again — its\nrole no longer declared, or the worker unable to spawn it — even though the reply itself had been\nwritten and was readable in the conversation. Since this is the most common thing a reply does, this\nis the most visible part of the change: **the command now exits successfully where it used to exit\nwith an error code**, and instead says in its `--json` output that the hand-over did not happen\n(`wake_skipped`, naming the session and whether the recipient is gone or only its start-up failed).\nReplying to an ordinary message that names no session at all is unaffected and reports nothing —\nthere was nobody to hand anything over to, and that stays distinguishable from a hand-over that was\nattempted and lost. All four places that pass work on now behave the same way; there is no longer\none that fails while the others report.", - "de": "**`nxc reply` liefert in einem Fall einen anderen Exit-Code:** Der Aufruf scheitert nicht\nmehr, wenn die Antwort dem angeschriebenen Empfänger nicht übergeben werden kann. Wer auf eine\nNachricht antwortete, die eine fortzusetzende Sitzung nennt, bekam bisher einen Fehler gemeldet,\nsobald diese Sitzung nicht erneut gestartet werden konnte — ihre Rolle ist nicht mehr deklariert,\noder der Arbeitsprozess ließ sich nicht starten —, obwohl die Antwort geschrieben und im Verlauf\nlesbar war. Da dies der weitaus häufigste Fall einer Antwort ist, ist es der sichtbarste Teil dieser\nÄnderung: **der Befehl endet jetzt erfolgreich, wo er bisher mit einem Fehlercode endete**, und hält\nstattdessen in seiner `--json`-Ausgabe fest, dass die Übergabe nicht stattgefunden hat\n(`wake_skipped`, mit der Sitzung und der Unterscheidung, ob der Empfänger weg ist oder nur sein Start\nscheiterte). Eine Antwort auf eine gewöhnliche Nachricht ohne Sitzungsangabe ist nicht betroffen und\nmeldet nichts — es gab niemanden, dem etwas zu übergeben gewesen wäre, und das bleibt unterscheidbar\nvon einer Übergabe, die versucht wurde und verloren ging. Alle vier Stellen, die Arbeit weiterreichen,\nverhalten sich damit gleich; keine scheitert mehr, während die anderen melden." - }, - { - "type": "changed", - "en": "When a finished review board should move a workflow run on to its next step and that\ndoes not work out — a step with no matching outcome, a workflow that is no longer declared — `nxc\nreply` and `nxc workflow tick` now say so in their `--json` output (`advance_failed`, naming the run\nand the reason). Both calls still succeed, exactly as before: the board's result was delivered\neither way. Until now the run simply stayed where it was, with nothing to tell anyone why. The key\nis only present when there is something to report.", - "de": "Wenn ein abgeschlossenes Review-Board einen Workflow-Lauf zum nächsten Schritt bringen\nsoll und das nicht gelingt — ein Schritt ohne passendes Ergebnis, ein nicht mehr deklarierter\nWorkflow —, halten `nxc reply` und `nxc workflow tick` das jetzt in ihrer `--json`-Ausgabe fest\n(`advance_failed`, mit Lauf und Grund). Beide Aufrufe gelingen weiterhin, genau wie bisher: das\nErgebnis des Boards wurde ohnehin zugestellt. Bisher blieb der Lauf einfach stehen, ohne dass\nirgendetwas den Grund nannte. Der Schlüssel erscheint nur, wenn es etwas zu melden gibt.", - "unreleased": true - }, - { - "type": "changed", - "en": "`nxc workflow start`, `workflow step done`, `workflow tick` and `workflow status` emit\ntheir `--json` keys in the record's declared order instead of alphabetically. The output is\ndeterministic either way and no key was added, removed or renamed, so anything that parses the JSON\nis unaffected; only a consumer comparing output byte for byte will see a difference.", - "de": "`nxc workflow start`, `workflow step done`, `workflow tick` und `workflow status` geben\nihre `--json`-Schlüssel jetzt in der deklarierten Reihenfolge des Datensatzes aus statt alphabetisch.\nDie Ausgabe ist so wie zuvor deterministisch, und kein Schlüssel kam hinzu, entfiel oder wurde\numbenannt — wer das JSON parst, ist nicht betroffen; sichtbar wird der Unterschied nur beim\nbyteweisen Vergleich der Ausgabe.", - "unreleased": true - } - ], - "notes": { - "en": "### Added\n- The role runtime is now reachable from an application, not only from the command line. An app that\nembeds the chat engine can drive the whole thing through its library handle: start a workflow and\nadvance it step by step, hand a role a new task or continue its session, open a declared channel and\nfan it out to its members, and open a review board — the same verbs `nxc` has, with the same\nreceipts. It can also bring **its own** roles, channels and workflows instead of requiring a\n`roles/` folder inside every user's project, and replace that catalogue while the workspace stays\nopen, so an agent edited in the app takes effect on the next trigger without a restart. Nothing\nchanges for an application that only reads and posts messages — the role runtime is opt-in.\nRoles and workflow steps can now choose which Claude model they run on — `model: fable`, `opus` or\n`sonnet` in a role file, or on a single workflow step to pair a cheap reviewer with an expensive\nimplementer. `nxc send --model ` picks the model for one call and overrides both.\nA role that names no model keeps running on the default, exactly as before.\n\n### Changed\n- `nxc reply` no longer fails when it cannot pass a completed review board on. Replying\nthe last outstanding answer into an ordinary channel used to report an error whenever the board's\nopener could not be started again — even though the reply itself had been written and was readable\nin the thread. It now succeeds, like the same reply into a declared channel always did, and says in\nits `--json` output that the hand-off did not happen (`wake_skipped`, naming the session and the\nreason) so nothing is quietly lost. The key is only present when there is something to report.\n`nxc workflow tick` — what a review board's timeout timer runs unattended, and what an application\ncalls to re-check a board — now reports the same thing: which session it handed the finished board\nto (`woke`), and the same `wake_skipped` when it could not hand it over at all. It used to say only\nthat it had acted, which looked exactly the same whether the hand-off arrived or was lost.\n- **`nxc reply` returns a different exit code in one case:** it no longer fails when it\ncannot hand the reply over to the recipient it was addressed to. Replying to a message that names a\nsession to continue used to report an error whenever that session could not be started again — its\nrole no longer declared, or the worker unable to spawn it — even though the reply itself had been\nwritten and was readable in the conversation. Since this is the most common thing a reply does, this\nis the most visible part of the change: **the command now exits successfully where it used to exit\nwith an error code**, and instead says in its `--json` output that the hand-over did not happen\n(`wake_skipped`, naming the session and whether the recipient is gone or only its start-up failed).\nReplying to an ordinary message that names no session at all is unaffected and reports nothing —\nthere was nobody to hand anything over to, and that stays distinguishable from a hand-over that was\nattempted and lost. All four places that pass work on now behave the same way; there is no longer\none that fails while the others report.\n- When a finished review board should move a workflow run on to its next step and that\ndoes not work out — a step with no matching outcome, a workflow that is no longer declared — `nxc\nreply` and `nxc workflow tick` now say so in their `--json` output (`advance_failed`, naming the run\nand the reason). Both calls still succeed, exactly as before: the board's result was delivered\neither way. Until now the run simply stayed where it was, with nothing to tell anyone why. The key\nis only present when there is something to report.\n- `nxc workflow start`, `workflow step done`, `workflow tick` and `workflow status` emit\ntheir `--json` keys in the record's declared order instead of alphabetically. The output is\ndeterministic either way and no key was added, removed or renamed, so anything that parses the JSON\nis unaffected; only a consumer comparing output byte for byte will see a difference.\n\n### Fixed\n- A reply sent through the embedded engine handle now finishes what it answers. It used to\npost the message and stop — it never woke the requester when a review board completed, never\ndelivered a declared channel's result, and never advanced the workflow run waiting on that board,\nall silently behind a successful receipt. An application replying through the handle therefore left\nboards that never completed and runs that never moved on. `nxc reply` was always correct; only the\nembedded path was affected.", - "de": "### Neu\n- Die Rollen-Laufzeit ist jetzt aus einer Anwendung heraus erreichbar, nicht mehr nur über die\nKommandozeile. Eine App, die die Chat-Engine einbettet, kann sie vollständig über ihr\nBibliotheks-Handle steuern: einen Workflow starten und Schritt für Schritt weiterführen, einer Rolle\neine neue Aufgabe geben oder ihre Sitzung fortsetzen, einen deklarierten Kanal öffnen und an seine\nMitglieder verteilen sowie ein Review-Board eröffnen — dieselben Verben wie `nxc`, mit denselben\nQuittungen. Sie kann außerdem **ihre eigenen** Rollen, Kanäle und Workflows mitbringen, statt in\njedem Projekt einen `roles/`-Ordner zu verlangen, und diesen Katalog austauschen, während der\nArbeitsbereich geöffnet bleibt — eine in der App bearbeitete Rolle wirkt damit beim nächsten Auslösen\nohne Neustart. Für Anwendungen, die nur Nachrichten lesen und schreiben, ändert sich nichts: die\nRollen-Laufzeit ist ausdrücklich zuzuschalten.\nRollen und Workflow-Schritte können jetzt wählen, auf welchem Claude-Modell sie laufen — `model:\nfable`, `opus` oder `sonnet` in einer Rollendatei oder an einem einzelnen Workflow-Schritt, um etwa\neine günstige Prüfung mit einer teuren Umsetzung zu kombinieren. `nxc send --model\n` wählt das Modell für einen einzelnen Aufruf und schlägt beide. Eine Rolle ohne\nModellangabe läuft unverändert auf der Voreinstellung.\n\n### Geändert\n- `nxc reply` scheitert nicht mehr, wenn ein abgeschlossenes Review-Board nicht\nweitergereicht werden kann. Wer die letzte ausstehende Antwort in einen gewöhnlichen Kanal schrieb,\nbekam bisher einen Fehler gemeldet, sobald die eröffnende Rolle nicht erneut gestartet werden konnte\n— obwohl die Antwort geschrieben und im Verlauf lesbar war. Der Aufruf gelingt jetzt, so wie\ndieselbe Antwort in einem deklarierten Kanal schon immer gelang, und die `--json`-Ausgabe hält fest,\ndass die Weitergabe nicht stattgefunden hat (`wake_skipped`, mit Sitzung und Grund) — nichts geht\nalso stillschweigend verloren. Der Schlüssel erscheint nur, wenn es etwas zu melden gibt.\n`nxc workflow tick` — das, was der Fristablauf eines Review-Boards unbeaufsichtigt ausführt und was\neine Anwendung zum Nachprüfen eines Boards aufruft — meldet jetzt dasselbe: an welche Sitzung das\nfertige Board übergeben wurde (`woke`) und dasselbe `wake_skipped`, wenn die Übergabe nicht möglich\nwar. Bisher stand dort nur, dass gehandelt wurde — ununterscheidbar davon, ob die Übergabe ankam\noder verloren ging.\n- **`nxc reply` liefert in einem Fall einen anderen Exit-Code:** Der Aufruf scheitert nicht\nmehr, wenn die Antwort dem angeschriebenen Empfänger nicht übergeben werden kann. Wer auf eine\nNachricht antwortete, die eine fortzusetzende Sitzung nennt, bekam bisher einen Fehler gemeldet,\nsobald diese Sitzung nicht erneut gestartet werden konnte — ihre Rolle ist nicht mehr deklariert,\noder der Arbeitsprozess ließ sich nicht starten —, obwohl die Antwort geschrieben und im Verlauf\nlesbar war. Da dies der weitaus häufigste Fall einer Antwort ist, ist es der sichtbarste Teil dieser\nÄnderung: **der Befehl endet jetzt erfolgreich, wo er bisher mit einem Fehlercode endete**, und hält\nstattdessen in seiner `--json`-Ausgabe fest, dass die Übergabe nicht stattgefunden hat\n(`wake_skipped`, mit der Sitzung und der Unterscheidung, ob der Empfänger weg ist oder nur sein Start\nscheiterte). Eine Antwort auf eine gewöhnliche Nachricht ohne Sitzungsangabe ist nicht betroffen und\nmeldet nichts — es gab niemanden, dem etwas zu übergeben gewesen wäre, und das bleibt unterscheidbar\nvon einer Übergabe, die versucht wurde und verloren ging. Alle vier Stellen, die Arbeit weiterreichen,\nverhalten sich damit gleich; keine scheitert mehr, während die anderen melden.\n- Wenn ein abgeschlossenes Review-Board einen Workflow-Lauf zum nächsten Schritt bringen\nsoll und das nicht gelingt — ein Schritt ohne passendes Ergebnis, ein nicht mehr deklarierter\nWorkflow —, halten `nxc reply` und `nxc workflow tick` das jetzt in ihrer `--json`-Ausgabe fest\n(`advance_failed`, mit Lauf und Grund). Beide Aufrufe gelingen weiterhin, genau wie bisher: das\nErgebnis des Boards wurde ohnehin zugestellt. Bisher blieb der Lauf einfach stehen, ohne dass\nirgendetwas den Grund nannte. Der Schlüssel erscheint nur, wenn es etwas zu melden gibt.\n- `nxc workflow start`, `workflow step done`, `workflow tick` und `workflow status` geben\nihre `--json`-Schlüssel jetzt in der deklarierten Reihenfolge des Datensatzes aus statt alphabetisch.\nDie Ausgabe ist so wie zuvor deterministisch, und kein Schlüssel kam hinzu, entfiel oder wurde\numbenannt — wer das JSON parst, ist nicht betroffen; sichtbar wird der Unterschied nur beim\nbyteweisen Vergleich der Ausgabe.\n\n### Behoben\n- Eine Antwort über das eingebettete Engine-Handle bringt jetzt zu Ende, was sie\nbeantwortet. Bisher wurde die Nachricht nur geschrieben — der Anfragende wurde beim Abschluss eines\nReview-Boards nie geweckt, das Ergebnis eines deklarierten Kanals nie zugestellt und der auf dieses\nBoard wartende Workflow-Lauf nie weitergeführt, alles stillschweigend hinter einer erfolgreichen\nQuittung. Eine Anwendung, die über das Handle antwortete, hinterließ so Boards, die nie fertig\nwurden, und Läufe, die nie weiterliefen. `nxc reply` war immer korrekt; betroffen war nur der\neingebettete Weg." - } - }, - { - "version": "0.36.0", - "date": "2026-07-31", - "items": [ - { - "type": "added", - "en": "`nxs sync daemon` keeps every bound workspace in sync continuously — on a periodic safety-net\ninterval (5 minutes by default), right after the machine wakes from sleep, and shortly after a\nlocal write — so a machine you come back to is already up to date instead of being read while\nstale. Binding a workspace (`nxs sync bind`) now installs it automatically as a background launchd\nagent on macOS (pass `--no-daemon` to skip that); on other platforms `nxs sync daemon` runs in the\nforeground under your own process supervisor instead. Only one instance runs at a time per\nmachine, and it syncs every registered workspace, not just the one you're standing in.\n`nxs sync daemon status` reports whether it's running and what it last did; `nxs sync daemon\ninstall`/`uninstall` manage the launchd agent explicitly if the automatic install didn't run or you\nneed to redo it.", - "de": "`nxs sync daemon` hält jeden gebundenen Workspace dauerhaft synchron — in einem periodischen\nSicherheitsintervall (standardmäßig alle 5 Minuten), direkt nach dem Aufwachen aus dem\nRuhezustand und kurz nach einem lokalen Schreibvorgang. Ein Rechner, zu dem du zurückkehrst, ist\ndamit schon aktuell, statt veraltet gelesen zu werden. Das Binden eines Workspace (`nxs sync\nbind`) richtet den Daemon jetzt automatisch als launchd-Hintergrunddienst unter macOS ein (mit\n`--no-daemon` kannst du das überspringen); auf anderen Plattformen läuft `nxs sync daemon`\nstattdessen im Vordergrund unter deinem eigenen Prozess-Supervisor. Pro Rechner läuft immer nur\neine Instanz, und sie synchronisiert alle registrierten Workspaces, nicht nur den, in dem du\ngerade stehst. `nxs sync daemon status` zeigt, ob er läuft und was er zuletzt getan hat; `nxs sync\ndaemon install`/`uninstall` verwalten den launchd-Dienst explizit, falls die automatische\nEinrichtung nicht lief oder du sie neu aufsetzen musst." - }, - { - "type": "changed", - "en": "`nxs sync bind` no longer requires `--create` or `--join`: run with no flags and it derives the\nstream id deterministically from the repo's git `origin` remote, so every clone of a repository\nlands on the same sync stream automatically — nothing to mint or copy between machines.\n`--create` (mint a fresh stream) and `--join ` (an out-of-band shared id) still work exactly\nas before — useful when there's no remote to derive from, or you want an independent stream.\nRe-running `bind` with the same id is now a successful no-op instead of an error, so it's safe to\nrun from scripts or `nxs init`; switching to a *different* stream now requires the explicit\n`--rebind` flag, which resets both sync watermarks —\nthis is also the way to move a workspace still on an older, randomly-minted stream id onto the new\nderived one. The relay endpoint moved out of `sync run`'s command line and into configuration: set\na machine-wide default with `nxs sync endpoint `, or pin one workspace to a different relay\nwith `nxs sync bind --endpoint `. `nxs sync run --remote ` is now optional — omit it and\nthe bound/default endpoint is used; passing it overrides for that one run without changing what's\nstored. The relay has no authentication yet, so a derived stream id is only as private as your\nrepo's remote URL — treat the endpoint as trusted/private until authentication ships.", - "de": "`nxs sync bind` verlangt nicht mehr `--create` oder `--join`: ohne Flags aufgerufen, leitet es die\nStream-Id deterministisch aus dem git-`origin`-Remote des Repos ab, sodass jeder Klon eines\nRepositories automatisch auf demselben Sync-Stream landet — nichts muss mehr erzeugt oder\nzwischen Rechnern kopiert werden. `--create` (eine neue Stream-Id erzeugen) und `--join `\n(eine außerhalb geteilte Id übernehmen) funktionieren weiterhin genau wie bisher — nützlich, wenn\nkein Remote zum Ableiten vorhanden ist oder du bewusst einen eigenständigen Stream willst.\nEin erneutes `bind` mit unveränderter Id ist jetzt ein erfolgreiches No-op statt eines Fehlers,\nlässt sich also gefahrlos aus Skripten oder `nxs init` aufrufen; der Wechsel zu\neinem *anderen* Stream verlangt jetzt das explizite `--rebind`-Flag, das beide Sync-Marken\nzurücksetzt — das ist zugleich der Weg, um einen Workspace, der noch auf einer älteren, zufällig\nerzeugten Stream-Id sitzt, auf die neue abgeleitete Id umzustellen. Der Relay-Endpunkt ist aus der\nKommandozeile von `sync run` in die Konfiguration gewandert: ein rechnerweiter Standard lässt sich\nmit `nxs sync endpoint ` setzen, ein einzelner Workspace mit `nxs sync bind --endpoint `\nauf ein abweichendes Relay festlegen. `nxs sync run --remote ` ist jetzt optional — ohne das\nFlag wird der gebundene bzw. voreingestellte Endpunkt verwendet; wird es angegeben, überschreibt\nes nur diesen einen Lauf, ohne den gespeicherten Wert zu ändern. Das Relay verfügt noch über keine\nAuthentifizierung, daher ist eine abgeleitete Stream-Id nur so privat wie die Remote-URL deines\nRepos — behandle den Endpunkt bis zur Einführung der Authentifizierung als vertraulich/privat." - }, - { - "type": "fixed", - "en": "A stream id containing characters like `/` produced a 404 instead of syncing — and `#` or `?`\ncould truncate the path or inject a bogus query parameter — because the sync transport built the\nrelay URL without percent-encoding the id into a single path segment. Such ids are now refused at\n`nxs sync bind` time with a slug-safe suggestion, so the problem is caught at bind rather than\nsurfacing later as a mysterious sync failure. The transport also now percent-encodes the path\nsegment, so a request built from such an id is at least well-formed — but that's a defensive\nbackstop, not a guarantee for an id already on disk: the deployed relay sits behind infrastructure\nthat can normalize `%2F` back into a path separator before the request ever reaches the app, so an\nid with `/` bound before this fix can still fail there even though it now works fine against a\nlocal relay. If your workspace is already bound to an id containing `/`, `#`, or `?`, move it to a\nclean one with `nxs sync bind --rebind` rather than relying on the encoding alone.", - "de": "Eine Stream-Id mit Zeichen wie `/` führte zu einem 404 statt zu einer Synchronisation — und `#`\noder `?` konnten den Pfad abschneiden oder einen falschen Query-Parameter einschleusen —, weil der\nSync-Transport die Relay-URL aufbaute, ohne die Id in ein einzelnes, prozentkodiertes Pfadsegment\numzuwandeln. Solche Ids werden jetzt schon bei `nxs sync bind` mit einem slug-tauglichen Vorschlag\nabgelehnt, sodass das Problem beim Binden auffällt statt später als rätselhafter Sync-Fehler. Der\nTransport kodiert das Pfadsegment jetzt außerdem prozentweise, sodass eine daraus gebaute Anfrage\nzumindest wohlgeformt ist — das ist aber nur ein defensives Sicherheitsnetz, keine Garantie für\neine bereits gespeicherte Id: Das produktive Relay steht hinter Infrastruktur, die `%2F` vor dem\nErreichen der App wieder in einen Pfadtrenner zurückverwandeln kann. Eine vor diesem Fix gebundene\nId mit `/` kann dort also weiterhin scheitern, selbst wenn sie gegen ein lokales Relay jetzt\nfunktioniert. Ist dein Workspace bereits an eine Id mit `/`, `#` oder `?` gebunden, wechsle mit\n`nxs sync bind --rebind` auf eine saubere Id, statt dich allein auf die Kodierung zu verlassen." - } - ], - "notes": { - "en": "### Added\n- `nxs sync daemon` keeps every bound workspace in sync continuously — on a periodic safety-net\ninterval (5 minutes by default), right after the machine wakes from sleep, and shortly after a\nlocal write — so a machine you come back to is already up to date instead of being read while\nstale. Binding a workspace (`nxs sync bind`) now installs it automatically as a background launchd\nagent on macOS (pass `--no-daemon` to skip that); on other platforms `nxs sync daemon` runs in the\nforeground under your own process supervisor instead. Only one instance runs at a time per\nmachine, and it syncs every registered workspace, not just the one you're standing in.\n`nxs sync daemon status` reports whether it's running and what it last did; `nxs sync daemon\ninstall`/`uninstall` manage the launchd agent explicitly if the automatic install didn't run or you\nneed to redo it.\n\n### Changed\n- `nxs sync bind` no longer requires `--create` or `--join`: run with no flags and it derives the\nstream id deterministically from the repo's git `origin` remote, so every clone of a repository\nlands on the same sync stream automatically — nothing to mint or copy between machines.\n`--create` (mint a fresh stream) and `--join ` (an out-of-band shared id) still work exactly\nas before — useful when there's no remote to derive from, or you want an independent stream.\nRe-running `bind` with the same id is now a successful no-op instead of an error, so it's safe to\nrun from scripts or `nxs init`; switching to a *different* stream now requires the explicit\n`--rebind` flag, which resets both sync watermarks —\nthis is also the way to move a workspace still on an older, randomly-minted stream id onto the new\nderived one. The relay endpoint moved out of `sync run`'s command line and into configuration: set\na machine-wide default with `nxs sync endpoint `, or pin one workspace to a different relay\nwith `nxs sync bind --endpoint `. `nxs sync run --remote ` is now optional — omit it and\nthe bound/default endpoint is used; passing it overrides for that one run without changing what's\nstored. The relay has no authentication yet, so a derived stream id is only as private as your\nrepo's remote URL — treat the endpoint as trusted/private until authentication ships.\n\n### Fixed\n- A stream id containing characters like `/` produced a 404 instead of syncing — and `#` or `?`\ncould truncate the path or inject a bogus query parameter — because the sync transport built the\nrelay URL without percent-encoding the id into a single path segment. Such ids are now refused at\n`nxs sync bind` time with a slug-safe suggestion, so the problem is caught at bind rather than\nsurfacing later as a mysterious sync failure. The transport also now percent-encodes the path\nsegment, so a request built from such an id is at least well-formed — but that's a defensive\nbackstop, not a guarantee for an id already on disk: the deployed relay sits behind infrastructure\nthat can normalize `%2F` back into a path separator before the request ever reaches the app, so an\nid with `/` bound before this fix can still fail there even though it now works fine against a\nlocal relay. If your workspace is already bound to an id containing `/`, `#`, or `?`, move it to a\nclean one with `nxs sync bind --rebind` rather than relying on the encoding alone.", - "de": "### Neu\n- `nxs sync daemon` hält jeden gebundenen Workspace dauerhaft synchron — in einem periodischen\nSicherheitsintervall (standardmäßig alle 5 Minuten), direkt nach dem Aufwachen aus dem\nRuhezustand und kurz nach einem lokalen Schreibvorgang. Ein Rechner, zu dem du zurückkehrst, ist\ndamit schon aktuell, statt veraltet gelesen zu werden. Das Binden eines Workspace (`nxs sync\nbind`) richtet den Daemon jetzt automatisch als launchd-Hintergrunddienst unter macOS ein (mit\n`--no-daemon` kannst du das überspringen); auf anderen Plattformen läuft `nxs sync daemon`\nstattdessen im Vordergrund unter deinem eigenen Prozess-Supervisor. Pro Rechner läuft immer nur\neine Instanz, und sie synchronisiert alle registrierten Workspaces, nicht nur den, in dem du\ngerade stehst. `nxs sync daemon status` zeigt, ob er läuft und was er zuletzt getan hat; `nxs sync\ndaemon install`/`uninstall` verwalten den launchd-Dienst explizit, falls die automatische\nEinrichtung nicht lief oder du sie neu aufsetzen musst.\n\n### Geändert\n- `nxs sync bind` verlangt nicht mehr `--create` oder `--join`: ohne Flags aufgerufen, leitet es die\nStream-Id deterministisch aus dem git-`origin`-Remote des Repos ab, sodass jeder Klon eines\nRepositories automatisch auf demselben Sync-Stream landet — nichts muss mehr erzeugt oder\nzwischen Rechnern kopiert werden. `--create` (eine neue Stream-Id erzeugen) und `--join `\n(eine außerhalb geteilte Id übernehmen) funktionieren weiterhin genau wie bisher — nützlich, wenn\nkein Remote zum Ableiten vorhanden ist oder du bewusst einen eigenständigen Stream willst.\nEin erneutes `bind` mit unveränderter Id ist jetzt ein erfolgreiches No-op statt eines Fehlers,\nlässt sich also gefahrlos aus Skripten oder `nxs init` aufrufen; der Wechsel zu\neinem *anderen* Stream verlangt jetzt das explizite `--rebind`-Flag, das beide Sync-Marken\nzurücksetzt — das ist zugleich der Weg, um einen Workspace, der noch auf einer älteren, zufällig\nerzeugten Stream-Id sitzt, auf die neue abgeleitete Id umzustellen. Der Relay-Endpunkt ist aus der\nKommandozeile von `sync run` in die Konfiguration gewandert: ein rechnerweiter Standard lässt sich\nmit `nxs sync endpoint ` setzen, ein einzelner Workspace mit `nxs sync bind --endpoint `\nauf ein abweichendes Relay festlegen. `nxs sync run --remote ` ist jetzt optional — ohne das\nFlag wird der gebundene bzw. voreingestellte Endpunkt verwendet; wird es angegeben, überschreibt\nes nur diesen einen Lauf, ohne den gespeicherten Wert zu ändern. Das Relay verfügt noch über keine\nAuthentifizierung, daher ist eine abgeleitete Stream-Id nur so privat wie die Remote-URL deines\nRepos — behandle den Endpunkt bis zur Einführung der Authentifizierung als vertraulich/privat.\n\n### Behoben\n- Eine Stream-Id mit Zeichen wie `/` führte zu einem 404 statt zu einer Synchronisation — und `#`\noder `?` konnten den Pfad abschneiden oder einen falschen Query-Parameter einschleusen —, weil der\nSync-Transport die Relay-URL aufbaute, ohne die Id in ein einzelnes, prozentkodiertes Pfadsegment\numzuwandeln. Solche Ids werden jetzt schon bei `nxs sync bind` mit einem slug-tauglichen Vorschlag\nabgelehnt, sodass das Problem beim Binden auffällt statt später als rätselhafter Sync-Fehler. Der\nTransport kodiert das Pfadsegment jetzt außerdem prozentweise, sodass eine daraus gebaute Anfrage\nzumindest wohlgeformt ist — das ist aber nur ein defensives Sicherheitsnetz, keine Garantie für\neine bereits gespeicherte Id: Das produktive Relay steht hinter Infrastruktur, die `%2F` vor dem\nErreichen der App wieder in einen Pfadtrenner zurückverwandeln kann. Eine vor diesem Fix gebundene\nId mit `/` kann dort also weiterhin scheitern, selbst wenn sie gegen ein lokales Relay jetzt\nfunktioniert. Ist dein Workspace bereits an eine Id mit `/`, `#` oder `?` gebunden, wechsle mit\n`nxs sync bind --rebind` auf eine saubere Id, statt dich allein auf die Kodierung zu verlassen." - } - }, - { - "version": "0.35.0", - "date": "2026-07-29", - "items": [ - { - "type": "added", - "en": "`nxf-relay` can now be pointed at DynamoDB as a durable storage backend, alongside the\nexisting SQLite and Postgres options. Build with `--features dynamodb` and set\n`NXF_RELAY_BACKEND=dynamodb`; `NXF_RELAY_DDB_TABLE` / `NXF_RELAY_DDB_REGISTRY_TABLE` name the\n(externally provisioned) ops and prefix-registry tables — both default so nothing is\nstrictly required — and `NXF_RELAY_DDB_ENDPOINT` / `NXF_RELAY_DDB_REGION` override the\nendpoint and region, e.g. for DynamoDB Local or a self-hosted setup.", - "de": "`nxf-relay` kann jetzt auch DynamoDB als dauerhaftes Speicher-Backend nutzen, zusätzlich zu\nden bestehenden SQLite- und Postgres-Optionen. Mit `--features dynamodb` bauen und\n`NXF_RELAY_BACKEND=dynamodb` setzen; `NXF_RELAY_DDB_TABLE` / `NXF_RELAY_DDB_REGISTRY_TABLE`\nbenennen die (extern bereitgestellten) Ops- und Prefix-Registry-Tabellen — beide mit\nStandardwert, sodass nichts zwingend erforderlich ist — und `NXF_RELAY_DDB_ENDPOINT` /\n`NXF_RELAY_DDB_REGION` überschreiben Endpoint und Region, etwa für DynamoDB Local oder ein\nSelf-Hosting-Setup." - } - ], - "notes": { - "en": "### Added\n- `nxf-relay` can now be pointed at DynamoDB as a durable storage backend, alongside the\nexisting SQLite and Postgres options. Build with `--features dynamodb` and set\n`NXF_RELAY_BACKEND=dynamodb`; `NXF_RELAY_DDB_TABLE` / `NXF_RELAY_DDB_REGISTRY_TABLE` name the\n(externally provisioned) ops and prefix-registry tables — both default so nothing is\nstrictly required — and `NXF_RELAY_DDB_ENDPOINT` / `NXF_RELAY_DDB_REGION` override the\nendpoint and region, e.g. for DynamoDB Local or a self-hosted setup.", - "de": "### Neu\n- `nxf-relay` kann jetzt auch DynamoDB als dauerhaftes Speicher-Backend nutzen, zusätzlich zu\nden bestehenden SQLite- und Postgres-Optionen. Mit `--features dynamodb` bauen und\n`NXF_RELAY_BACKEND=dynamodb` setzen; `NXF_RELAY_DDB_TABLE` / `NXF_RELAY_DDB_REGISTRY_TABLE`\nbenennen die (extern bereitgestellten) Ops- und Prefix-Registry-Tabellen — beide mit\nStandardwert, sodass nichts zwingend erforderlich ist — und `NXF_RELAY_DDB_ENDPOINT` /\n`NXF_RELAY_DDB_REGION` überschreiben Endpoint und Region, etwa für DynamoDB Local oder ein\nSelf-Hosting-Setup." - } - }, - { - "version": "0.34.0", - "date": "2026-07-29", - "items": [ - { - "type": "added", - "en": "Agent transcripts: what a role session actually did is now recorded and readable. Until now only a\nrole's finished **messages** were kept — the reasoning, the tool calls and their results, and\neverything a delegated subagent did along the way were consumed and thrown away the moment the\nsession ended, so a surprising answer could never be traced back to how it came about. Every role\nsession's full agent transcript is now captured as it runs and stored durably: assistant text,\neach tool call with its result, and — the part that usually goes missing — the complete\nsub-timeline of every subagent the role spawned, nested under the step that spawned\nit. Read it back with `nxc transcript show ` as an indented timeline, or as JSON for\ntooling; the same view is available in-process to apps embedding nexus-chat. Transcripts stay on\nthe machine that produced them (they describe a local session, so they are never synced to peers\nand never bloat the shared log), and a transcript flushed mid-run still shows everything it has\nrather than hiding partial evidence. Two long-form fields are shortened on the way in so one turn\ncannot dominate the record: a thinking block is kept up to 4000 characters and a tool result up to\n8000, each ending in a marker naming how much was cut. Everything else — including a tool call's\nown input, however large — is stored whole.\n\nOne honest limitation: the model's **extended thinking** is not part of the transcript yet. The\nrecording side is built and will pick it up the moment it becomes available, but the Claude Agent\nSDK does not currently hand reasoning blocks to a program driving it the way nexus-chat does, so\ntoday a transcript shows what the agent did and said, not what it was reasoning.", - "de": "Agenten-Transkripte: Was eine Rollen-Session tatsächlich getan hat, wird jetzt aufgezeichnet und ist\nlesbar. Bisher wurden nur die fertigen **Nachrichten** einer Rolle aufbewahrt — die Überlegungen,\ndie Tool-Aufrufe samt Ergebnissen und alles, was ein beauftragter Subagent unterwegs getan hat,\nwurden verbraucht und mit dem Ende der Session verworfen; eine überraschende Antwort ließ sich\ndeshalb nie zu ihrem Zustandekommen zurückverfolgen. Das vollständige Agenten-Transkript jeder\nRollen-Session wird jetzt während des Laufs erfasst und dauerhaft gespeichert: Assistenz-Text,\njeder Tool-Aufruf mit seinem Ergebnis und — der Teil, der üblicherweise\nverloren geht — die komplette Unter-Zeitleiste jedes von der Rolle gestarteten Subagenten,\nverschachtelt unter dem Schritt, der ihn gestartet hat. Abrufbar mit\n`nxc transcript show ` als eingerückte Zeitleiste oder als JSON für Werkzeuge; dieselbe\nSicht steht Apps, die nexus-chat einbetten, direkt im Prozess zur Verfügung. Transkripte bleiben auf\ndem Rechner, der sie erzeugt hat (sie beschreiben eine lokale Session, werden also nie zu Peers\nsynchronisiert und blähen das gemeinsame Log nicht auf), und ein mitten im Lauf geschriebenes\nTranskript zeigt weiterhin alles, was es hat, statt Teilbelege zu verbergen. Zwei besonders lange\nFelder werden beim Erfassen gekürzt, damit ein einzelner Zug den Datensatz nicht dominiert: ein\nDenk-Block wird bis 4000 Zeichen, ein Tool-Ergebnis bis 8000 Zeichen aufbewahrt, jeweils mit einem\nHinweis am Ende, wie viel abgeschnitten wurde. Alles andere — auch die Eingabe eines Tool-Aufrufs,\nwie groß sie auch ist — wird vollständig gespeichert.\n\nEine ehrliche Einschränkung: Das **erweiterte Nachdenken** des Modells ist noch nicht Teil des\nTranskripts. Die Aufzeichnung dafür ist gebaut und greift, sobald es verfügbar wird — aber das\nClaude Agent SDK gibt Denk-Blöcke derzeit nicht an ein Programm heraus, das es so ansteuert wie\nnexus-chat. Ein Transkript zeigt heute also, was der Agent getan und gesagt hat, nicht, was er\ndabei überlegt hat." - }, - { - "type": "changed", - "en": "A workspace can now declare **multiple named workflows** at once: alongside the legacy single\n`roles/workflow.yaml`, any number of additional workflows may live under `roles/workflows/*.yaml`\n(each file's own `name:` must be unique across the whole set). `nxc workflow start` gains a real\n`--name` selector: with exactly one workflow declared it stays optional and defaults to it, exactly\nas before; once more than one is declared, `--name` is required, and both an omitted name and an\nunknown one fail with a validation error listing every available name. `nxc prime`/`nxs prime` now\nlists every declared workflow under the plural `workflows` JSON key (replacing the old singular\n`workflow` key) and validates each one independently — a broken workflow is excluded from the\nroster rather than taking the whole session down, same as roles and channels already worked. A\nworkspace with only the legacy `roles/workflow.yaml` and no `roles/workflows/` directory keeps\nworking completely unchanged, with no `--name` flag required — this back-compat guarantee is\ncovered by a dedicated regression test.", - "de": "Ein Workspace kann jetzt **mehrere benannte Workflows** gleichzeitig deklarieren: neben der\nbisherigen einzelnen `roles/workflow.yaml` können beliebig viele weitere Workflows unter\n`roles/workflows/*.yaml` liegen (der `name:` jeder Datei muss über die gesamte Menge hinweg\neindeutig sein). `nxc workflow start` erhält einen echten `--name`-Selektor: bei genau einem\ndeklarierten Workflow bleibt er optional und wählt ihn automatisch, wie bisher; sind mehrere\ndeklariert, wird `--name` zur Pflicht — sowohl ein fehlender als auch ein unbekannter Name\nscheitern mit einem Validierungsfehler, der alle verfügbaren Namen auflistet. `nxc prime`/\n`nxs prime` listet jetzt jeden deklarierten Workflow unter dem pluralen JSON-Schlüssel `workflows`\n(er ersetzt den bisherigen singulären Schlüssel `workflow`) und validiert jeden einzeln — ein\nfehlerhafter Workflow wird aus der Übersicht ausgeschlossen, statt die ganze Sitzung zu blockieren,\ngenau wie es bei Rollen und Channels bereits der Fall war. Ein Workspace mit ausschließlich der\nalten `roles/workflow.yaml` und ohne `roles/workflows/`-Verzeichnis funktioniert weiterhin\nunverändert, ganz ohne `--name`-Flag — diese Abwärtskompatibilität ist durch einen eigenen\nRegressionstest abgesichert." - }, - { - "type": "added", - "en": "`nxf show ` now appends a loud, verbatim notice at the very end of its output when the item\nhas parent(s): \"This item has the following parents: . URGENT RECOMMENDATION: ALSO READ\nTHESE ITEMS TO GET THE COMPLETE PICTURE!!!\" — a nudge so an agent reading a child ticket also\npulls the parent (often an epic) that carries the spec, global constraints, and design context the\nchild assumes. Computed once in the engine/facade read layer (`ShowRecord::parents` +\n`parents_notice`), so the human `nxf show`, `--json` (`parents`/`parents_notice`, sparse — present\nonly when the item has parent(s)), and every other consumer (MCP, embedders) render the identical\ntext. Scoped to belongs-to/containment `parent` edges only, not `contributes_to`.", - "de": "`nxf show ` hängt jetzt am ganz Ende der Ausgabe einen auffälligen, wörtlichen Hinweis an, wenn\ndas Item Elternteil(e) hat: \"This item has the following parents: . URGENT RECOMMENDATION:\nALSO READ THESE ITEMS TO GET THE COMPLETE PICTURE!!!\" — ein Stupser, damit ein Agent, der ein\nKind-Ticket liest, auch das Elternteil (oft ein Epic) zieht, das die Spezifikation, globale\nRandbedingungen und den Designkontext trägt, von denen das Kind ausgeht. Einmalig im Engine-/\nFacade-Lese-Layer berechnet (`ShowRecord::parents` + `parents_notice`), sodass das menschenlesbare\n`nxf show`, `--json` (`parents`/`parents_notice`, sparsam — nur vorhanden, wenn das Item\nElternteil(e) hat) und jeder andere Konsument (MCP, Embedder) denselben Text rendern. Beschränkt\nauf Zugehörigkeits-/Containment-`parent`-Kanten, nicht `contributes_to`.", - "facade": "changed" - }, - { - "type": "added", - "en": "Role Runtime v2: roles are automatically primed with correct `nxc` usage and their own job\ndescription, so a trigger message carries only the task, never the role prompt; `send --role\n` auto-opens the DM with a peer and mints a fresh session, no manual `channels dm` step;\na role's own nested `send --role` reliably reaches the sidecar across multiple hops; a\nYAML-declared team workflow with per-transition validation prompts drives a PM/coder loop with no\nhard-coded orchestration; and a 4-role review quorum (general + code quality/test quality/\nintegrity) wakes the PM only once every reviewer has replied, via the existing thread-quorum\nmachinery. Role sessions now also run with `settingSources: []` (SDK isolation hardening): a\nspawned role ignores the operator's/project's own `.claude/` plugin, skill, and hook\nconfiguration, so its behavior depends only on its own role YAML.", - "de": "Role Runtime v2: Rollen werden automatisch mit korrekter `nxc`-Nutzung und ihrer eigenen\nAufgabenbeschreibung geprimt, sodass eine Trigger-Nachricht nur noch die Aufgabe trägt, nie mehr\nden Rollen-Prompt; `send --role ` öffnet automatisch die DM mit einer Partnerrolle und\nstartet eine frische Session, kein manueller `channels dm`-Schritt mehr nötig; der verschachtelte\n`send --role`-Aufruf einer Rolle erreicht den Sidecar zuverlässig über mehrere Hops hinweg; ein\nYAML-deklarierter Team-Workflow mit Validierungs-Prompts pro Übergang steuert eine\nPM/Coder-Schleife ohne fest verdrahtete Orchestrierung; und ein 4-Rollen-Review-Quorum (General +\nCode-Qualität/Test-Qualität/Integrität) weckt den PM erst, wenn jeder Reviewer geantwortet hat —\nverdrahtet über die bestehende Thread-Quorum-Mechanik. Rollen-Sessions laufen jetzt zusätzlich mit\n`settingSources: []` (SDK-Isolationshärtung): eine gestartete Rolle ignoriert die eigene\n`.claude/`-Plugin-, Skill- und Hook-Konfiguration des Operators/Projekts, ihr Verhalten hängt also\nnur noch von der eigenen Rollen-YAML ab." - }, - { - "type": "added", - "en": "Role Runtime v3: `nxc` becomes the orchestrator. A new declared **channel** primitive is typed\ngroup communication — who's expected to reply, whether replies stay visible only to the requester\nor to all members, and a one-shot timeout — and `nxc` now actually **fans out** a channel\ninvocation to every declared member (minus the sender), closing the v2 gap where a role could only\ndeclare who should reply, never trigger them, which is what caused the \"PM opens the board and\nwaits forever\" deadlock. A channel's completion either delivers the raw replies as-is\n(`pass_through`) or spawns an ephemeral synthesizer session that reads all the replies and posts\none summarized verdict (`summarize`), so no role has to summarize a pile of replies by hand. On top\nof that sits an explicit **workflow orchestrator** (`nxc workflow start`/`step done`/`status`/\n`tick`): a declared step graph with branching and looping transitions and a hard cycle cap, so a\nteam's control flow is owned by `nxc` itself instead of being scattered as prose across role\nprompts; each step can resume a role's prior session or start a fresh one, so a role reappearing\nlater in the same run picks up where it left off when the step calls for it. The example coding\nteam (PM/coder/4-role review quorum) has been hardened with a per-reviewer merge verdict and\nanti-rubber-stamp review discipline. All of it was verified end-to-end with real (not mocked)\nClaude Agent SDK sessions.\n\nHardened after an independent pre-PR review: `nxc workflow step done` now verifies the caller is\nactually the role bound to the run's current step (a `channel:`-target step can only complete via\nits own quorum/synthesis, never a direct call), the workflow-run return address is checked against\nthe engine's own record of which thread it opened rather than trusted as a bare string, a declared\n`visibility: requester_only` channel is now genuinely enforced on `nxc threads show`/the embedding\nfacade (it used to be validated and stored but never applied), the `summarize` synthesizer's prompt\nnow delimits the untrusted collected replies and its outcome token is parsed from the last matching\nline, a stale reply can no longer re-advance an already-advanced run, and a malformed channel\ndeclaration (e.g. `summarize` with no `summary_prompt`) is now rejected at the point of use instead\nof silently degrading.", - "de": "Role Runtime v3: `nxc` wird zum Orchestrator. Eine neue deklarierte **Channel**-Primitive ist\ntypisierte Gruppenkommunikation — wer antworten soll, ob Antworten nur für den Anfragenden oder für\nalle Mitglieder sichtbar sind, und ein einmaliges Timeout — und `nxc` **verteilt** einen\nChannel-Aufruf jetzt tatsächlich an jedes deklarierte Mitglied (außer den Absender); das schließt\ndie v2-Lücke, in der eine Rolle nur deklarieren konnte, wer antworten soll, ohne je jemanden\nauszulösen — genau das verursachte das Deadlock \"PM öffnet das Board und wartet für immer\". Der\nAbschluss eines Channels liefert entweder die rohen Antworten unverändert aus (`pass_through`)\noder startet eine ephemere Synthesizer-Session, die alle Antworten liest und ein zusammengefasstes\nVerdikt postet (`summarize`), sodass keine Rolle mehr von Hand zusammenfassen muss. Darauf setzt ein\nexpliziter **Workflow-Orchestrator** auf (`nxc workflow start`/`step done`/`status`/`tick`): ein\ndeklarierter Schrittgraph mit verzweigenden und rückführenden Übergängen und einer harten\nZyklus-Obergrenze, sodass der Kontrollfluss eines Teams jetzt `nxc` selbst gehört statt als Prosa\nüber Rollen-Prompts verstreut zu sein; jeder Schritt kann die vorherige Session einer Rolle\nfortsetzen oder eine frische starten, sodass eine später im selben Lauf wiederkehrende Rolle dort\nweitermacht, wo sie aufgehört hat, wenn der Schritt das vorsieht. Das Beispiel-Coding-Team\n(PM/Coder/4-Rollen-Review-Quorum) wurde mit einem Merge-Verdikt pro Reviewer und\nAnti-Abnick-Disziplin bei Reviews gehärtet. Alles wurde Ende-zu-Ende mit echten (nicht gemockten)\nClaude-Agent-SDK-Sessions verifiziert.\n\nNach einem unabhängigen Pre-PR-Review gehärtet: `nxc workflow step done` prüft jetzt, dass der\nAufrufer tatsächlich die an den aktuellen Schritt des Laufs gebundene Rolle ist (ein\n`channel:`-Zielschritt kann nur über sein eigenes Quorum/seine eigene Synthese abgeschlossen\nwerden, nie über einen direkten Aufruf); die Rücksende-Adresse eines Workflow-Laufs wird gegen die\neigene Aufzeichnung der Engine geprüft statt als bloße Zeichenkette vertraut; eine deklarierte\n`visibility: requester_only` wird jetzt bei `nxc threads show`/der Embedding-Facade tatsächlich\ndurchgesetzt (bisher wurde sie zwar validiert und gespeichert, aber nie angewendet); der Prompt des\n`summarize`-Synthesizers grenzt die nicht vertrauenswürdigen gesammelten Antworten jetzt klar ab,\nund sein Outcome-Token wird aus der letzten passenden Zeile geparst; eine veraltete Antwort kann\neinen bereits weitergerückten Lauf nicht mehr erneut voranbringen; und eine fehlerhafte\nChannel-Deklaration (z. B. `summarize` ohne `summary_prompt`) wird jetzt am Verwendungsort\nzurückgewiesen, statt still zu degradieren." - } - ], - "notes": { - "en": "### Added\n- Agent transcripts: what a role session actually did is now recorded and readable. Until now only a\nrole's finished **messages** were kept — the reasoning, the tool calls and their results, and\neverything a delegated subagent did along the way were consumed and thrown away the moment the\nsession ended, so a surprising answer could never be traced back to how it came about. Every role\nsession's full agent transcript is now captured as it runs and stored durably: assistant text,\neach tool call with its result, and — the part that usually goes missing — the complete\nsub-timeline of every subagent the role spawned, nested under the step that spawned\nit. Read it back with `nxc transcript show ` as an indented timeline, or as JSON for\ntooling; the same view is available in-process to apps embedding nexus-chat. Transcripts stay on\nthe machine that produced them (they describe a local session, so they are never synced to peers\nand never bloat the shared log), and a transcript flushed mid-run still shows everything it has\nrather than hiding partial evidence. Two long-form fields are shortened on the way in so one turn\ncannot dominate the record: a thinking block is kept up to 4000 characters and a tool result up to\n8000, each ending in a marker naming how much was cut. Everything else — including a tool call's\nown input, however large — is stored whole.\n\nOne honest limitation: the model's **extended thinking** is not part of the transcript yet. The\nrecording side is built and will pick it up the moment it becomes available, but the Claude Agent\nSDK does not currently hand reasoning blocks to a program driving it the way nexus-chat does, so\ntoday a transcript shows what the agent did and said, not what it was reasoning.\n- `nxf show ` now appends a loud, verbatim notice at the very end of its output when the item\nhas parent(s): \"This item has the following parents: . URGENT RECOMMENDATION: ALSO READ\nTHESE ITEMS TO GET THE COMPLETE PICTURE!!!\" — a nudge so an agent reading a child ticket also\npulls the parent (often an epic) that carries the spec, global constraints, and design context the\nchild assumes. Computed once in the engine/facade read layer (`ShowRecord::parents` +\n`parents_notice`), so the human `nxf show`, `--json` (`parents`/`parents_notice`, sparse — present\nonly when the item has parent(s)), and every other consumer (MCP, embedders) render the identical\ntext. Scoped to belongs-to/containment `parent` edges only, not `contributes_to`.\n- Role Runtime v2: roles are automatically primed with correct `nxc` usage and their own job\ndescription, so a trigger message carries only the task, never the role prompt; `send --role\n` auto-opens the DM with a peer and mints a fresh session, no manual `channels dm` step;\na role's own nested `send --role` reliably reaches the sidecar across multiple hops; a\nYAML-declared team workflow with per-transition validation prompts drives a PM/coder loop with no\nhard-coded orchestration; and a 4-role review quorum (general + code quality/test quality/\nintegrity) wakes the PM only once every reviewer has replied, via the existing thread-quorum\nmachinery. Role sessions now also run with `settingSources: []` (SDK isolation hardening): a\nspawned role ignores the operator's/project's own `.claude/` plugin, skill, and hook\nconfiguration, so its behavior depends only on its own role YAML.\n- Role Runtime v3: `nxc` becomes the orchestrator. A new declared **channel** primitive is typed\ngroup communication — who's expected to reply, whether replies stay visible only to the requester\nor to all members, and a one-shot timeout — and `nxc` now actually **fans out** a channel\ninvocation to every declared member (minus the sender), closing the v2 gap where a role could only\ndeclare who should reply, never trigger them, which is what caused the \"PM opens the board and\nwaits forever\" deadlock. A channel's completion either delivers the raw replies as-is\n(`pass_through`) or spawns an ephemeral synthesizer session that reads all the replies and posts\none summarized verdict (`summarize`), so no role has to summarize a pile of replies by hand. On top\nof that sits an explicit **workflow orchestrator** (`nxc workflow start`/`step done`/`status`/\n`tick`): a declared step graph with branching and looping transitions and a hard cycle cap, so a\nteam's control flow is owned by `nxc` itself instead of being scattered as prose across role\nprompts; each step can resume a role's prior session or start a fresh one, so a role reappearing\nlater in the same run picks up where it left off when the step calls for it. The example coding\nteam (PM/coder/4-role review quorum) has been hardened with a per-reviewer merge verdict and\nanti-rubber-stamp review discipline. All of it was verified end-to-end with real (not mocked)\nClaude Agent SDK sessions.\n\nHardened after an independent pre-PR review: `nxc workflow step done` now verifies the caller is\nactually the role bound to the run's current step (a `channel:`-target step can only complete via\nits own quorum/synthesis, never a direct call), the workflow-run return address is checked against\nthe engine's own record of which thread it opened rather than trusted as a bare string, a declared\n`visibility: requester_only` channel is now genuinely enforced on `nxc threads show`/the embedding\nfacade (it used to be validated and stored but never applied), the `summarize` synthesizer's prompt\nnow delimits the untrusted collected replies and its outcome token is parsed from the last matching\nline, a stale reply can no longer re-advance an already-advanced run, and a malformed channel\ndeclaration (e.g. `summarize` with no `summary_prompt`) is now rejected at the point of use instead\nof silently degrading.\n\n### Changed\n- A workspace can now declare **multiple named workflows** at once: alongside the legacy single\n`roles/workflow.yaml`, any number of additional workflows may live under `roles/workflows/*.yaml`\n(each file's own `name:` must be unique across the whole set). `nxc workflow start` gains a real\n`--name` selector: with exactly one workflow declared it stays optional and defaults to it, exactly\nas before; once more than one is declared, `--name` is required, and both an omitted name and an\nunknown one fail with a validation error listing every available name. `nxc prime`/`nxs prime` now\nlists every declared workflow under the plural `workflows` JSON key (replacing the old singular\n`workflow` key) and validates each one independently — a broken workflow is excluded from the\nroster rather than taking the whole session down, same as roles and channels already worked. A\nworkspace with only the legacy `roles/workflow.yaml` and no `roles/workflows/` directory keeps\nworking completely unchanged, with no `--name` flag required — this back-compat guarantee is\ncovered by a dedicated regression test.\n\n### Facade Contract\n- `changed` · `nxf show ` now appends a loud, verbatim notice at the very end of its output when the item\nhas parent(s): \"This item has the following parents: . URGENT RECOMMENDATION: ALSO READ\nTHESE ITEMS TO GET THE COMPLETE PICTURE!!!\" — a nudge so an agent reading a child ticket also\npulls the parent (often an epic) that carries the spec, global constraints, and design context the\nchild assumes. Computed once in the engine/facade read layer (`ShowRecord::parents` +\n`parents_notice`), so the human `nxf show`, `--json` (`parents`/`parents_notice`, sparse — present\nonly when the item has parent(s)), and every other consumer (MCP, embedders) render the identical\ntext. Scoped to belongs-to/containment `parent` edges only, not `contributes_to`.", - "de": "### Neu\n- Agenten-Transkripte: Was eine Rollen-Session tatsächlich getan hat, wird jetzt aufgezeichnet und ist\nlesbar. Bisher wurden nur die fertigen **Nachrichten** einer Rolle aufbewahrt — die Überlegungen,\ndie Tool-Aufrufe samt Ergebnissen und alles, was ein beauftragter Subagent unterwegs getan hat,\nwurden verbraucht und mit dem Ende der Session verworfen; eine überraschende Antwort ließ sich\ndeshalb nie zu ihrem Zustandekommen zurückverfolgen. Das vollständige Agenten-Transkript jeder\nRollen-Session wird jetzt während des Laufs erfasst und dauerhaft gespeichert: Assistenz-Text,\njeder Tool-Aufruf mit seinem Ergebnis und — der Teil, der üblicherweise\nverloren geht — die komplette Unter-Zeitleiste jedes von der Rolle gestarteten Subagenten,\nverschachtelt unter dem Schritt, der ihn gestartet hat. Abrufbar mit\n`nxc transcript show ` als eingerückte Zeitleiste oder als JSON für Werkzeuge; dieselbe\nSicht steht Apps, die nexus-chat einbetten, direkt im Prozess zur Verfügung. Transkripte bleiben auf\ndem Rechner, der sie erzeugt hat (sie beschreiben eine lokale Session, werden also nie zu Peers\nsynchronisiert und blähen das gemeinsame Log nicht auf), und ein mitten im Lauf geschriebenes\nTranskript zeigt weiterhin alles, was es hat, statt Teilbelege zu verbergen. Zwei besonders lange\nFelder werden beim Erfassen gekürzt, damit ein einzelner Zug den Datensatz nicht dominiert: ein\nDenk-Block wird bis 4000 Zeichen, ein Tool-Ergebnis bis 8000 Zeichen aufbewahrt, jeweils mit einem\nHinweis am Ende, wie viel abgeschnitten wurde. Alles andere — auch die Eingabe eines Tool-Aufrufs,\nwie groß sie auch ist — wird vollständig gespeichert.\n\nEine ehrliche Einschränkung: Das **erweiterte Nachdenken** des Modells ist noch nicht Teil des\nTranskripts. Die Aufzeichnung dafür ist gebaut und greift, sobald es verfügbar wird — aber das\nClaude Agent SDK gibt Denk-Blöcke derzeit nicht an ein Programm heraus, das es so ansteuert wie\nnexus-chat. Ein Transkript zeigt heute also, was der Agent getan und gesagt hat, nicht, was er\ndabei überlegt hat.\n- `nxf show ` hängt jetzt am ganz Ende der Ausgabe einen auffälligen, wörtlichen Hinweis an, wenn\ndas Item Elternteil(e) hat: \"This item has the following parents: . URGENT RECOMMENDATION:\nALSO READ THESE ITEMS TO GET THE COMPLETE PICTURE!!!\" — ein Stupser, damit ein Agent, der ein\nKind-Ticket liest, auch das Elternteil (oft ein Epic) zieht, das die Spezifikation, globale\nRandbedingungen und den Designkontext trägt, von denen das Kind ausgeht. Einmalig im Engine-/\nFacade-Lese-Layer berechnet (`ShowRecord::parents` + `parents_notice`), sodass das menschenlesbare\n`nxf show`, `--json` (`parents`/`parents_notice`, sparsam — nur vorhanden, wenn das Item\nElternteil(e) hat) und jeder andere Konsument (MCP, Embedder) denselben Text rendern. Beschränkt\nauf Zugehörigkeits-/Containment-`parent`-Kanten, nicht `contributes_to`.\n- Role Runtime v2: Rollen werden automatisch mit korrekter `nxc`-Nutzung und ihrer eigenen\nAufgabenbeschreibung geprimt, sodass eine Trigger-Nachricht nur noch die Aufgabe trägt, nie mehr\nden Rollen-Prompt; `send --role ` öffnet automatisch die DM mit einer Partnerrolle und\nstartet eine frische Session, kein manueller `channels dm`-Schritt mehr nötig; der verschachtelte\n`send --role`-Aufruf einer Rolle erreicht den Sidecar zuverlässig über mehrere Hops hinweg; ein\nYAML-deklarierter Team-Workflow mit Validierungs-Prompts pro Übergang steuert eine\nPM/Coder-Schleife ohne fest verdrahtete Orchestrierung; und ein 4-Rollen-Review-Quorum (General +\nCode-Qualität/Test-Qualität/Integrität) weckt den PM erst, wenn jeder Reviewer geantwortet hat —\nverdrahtet über die bestehende Thread-Quorum-Mechanik. Rollen-Sessions laufen jetzt zusätzlich mit\n`settingSources: []` (SDK-Isolationshärtung): eine gestartete Rolle ignoriert die eigene\n`.claude/`-Plugin-, Skill- und Hook-Konfiguration des Operators/Projekts, ihr Verhalten hängt also\nnur noch von der eigenen Rollen-YAML ab.\n- Role Runtime v3: `nxc` wird zum Orchestrator. Eine neue deklarierte **Channel**-Primitive ist\ntypisierte Gruppenkommunikation — wer antworten soll, ob Antworten nur für den Anfragenden oder für\nalle Mitglieder sichtbar sind, und ein einmaliges Timeout — und `nxc` **verteilt** einen\nChannel-Aufruf jetzt tatsächlich an jedes deklarierte Mitglied (außer den Absender); das schließt\ndie v2-Lücke, in der eine Rolle nur deklarieren konnte, wer antworten soll, ohne je jemanden\nauszulösen — genau das verursachte das Deadlock \"PM öffnet das Board und wartet für immer\". Der\nAbschluss eines Channels liefert entweder die rohen Antworten unverändert aus (`pass_through`)\noder startet eine ephemere Synthesizer-Session, die alle Antworten liest und ein zusammengefasstes\nVerdikt postet (`summarize`), sodass keine Rolle mehr von Hand zusammenfassen muss. Darauf setzt ein\nexpliziter **Workflow-Orchestrator** auf (`nxc workflow start`/`step done`/`status`/`tick`): ein\ndeklarierter Schrittgraph mit verzweigenden und rückführenden Übergängen und einer harten\nZyklus-Obergrenze, sodass der Kontrollfluss eines Teams jetzt `nxc` selbst gehört statt als Prosa\nüber Rollen-Prompts verstreut zu sein; jeder Schritt kann die vorherige Session einer Rolle\nfortsetzen oder eine frische starten, sodass eine später im selben Lauf wiederkehrende Rolle dort\nweitermacht, wo sie aufgehört hat, wenn der Schritt das vorsieht. Das Beispiel-Coding-Team\n(PM/Coder/4-Rollen-Review-Quorum) wurde mit einem Merge-Verdikt pro Reviewer und\nAnti-Abnick-Disziplin bei Reviews gehärtet. Alles wurde Ende-zu-Ende mit echten (nicht gemockten)\nClaude-Agent-SDK-Sessions verifiziert.\n\nNach einem unabhängigen Pre-PR-Review gehärtet: `nxc workflow step done` prüft jetzt, dass der\nAufrufer tatsächlich die an den aktuellen Schritt des Laufs gebundene Rolle ist (ein\n`channel:`-Zielschritt kann nur über sein eigenes Quorum/seine eigene Synthese abgeschlossen\nwerden, nie über einen direkten Aufruf); die Rücksende-Adresse eines Workflow-Laufs wird gegen die\neigene Aufzeichnung der Engine geprüft statt als bloße Zeichenkette vertraut; eine deklarierte\n`visibility: requester_only` wird jetzt bei `nxc threads show`/der Embedding-Facade tatsächlich\ndurchgesetzt (bisher wurde sie zwar validiert und gespeichert, aber nie angewendet); der Prompt des\n`summarize`-Synthesizers grenzt die nicht vertrauenswürdigen gesammelten Antworten jetzt klar ab,\nund sein Outcome-Token wird aus der letzten passenden Zeile geparst; eine veraltete Antwort kann\neinen bereits weitergerückten Lauf nicht mehr erneut voranbringen; und eine fehlerhafte\nChannel-Deklaration (z. B. `summarize` ohne `summary_prompt`) wird jetzt am Verwendungsort\nzurückgewiesen, statt still zu degradieren.\n\n### Geändert\n- Ein Workspace kann jetzt **mehrere benannte Workflows** gleichzeitig deklarieren: neben der\nbisherigen einzelnen `roles/workflow.yaml` können beliebig viele weitere Workflows unter\n`roles/workflows/*.yaml` liegen (der `name:` jeder Datei muss über die gesamte Menge hinweg\neindeutig sein). `nxc workflow start` erhält einen echten `--name`-Selektor: bei genau einem\ndeklarierten Workflow bleibt er optional und wählt ihn automatisch, wie bisher; sind mehrere\ndeklariert, wird `--name` zur Pflicht — sowohl ein fehlender als auch ein unbekannter Name\nscheitern mit einem Validierungsfehler, der alle verfügbaren Namen auflistet. `nxc prime`/\n`nxs prime` listet jetzt jeden deklarierten Workflow unter dem pluralen JSON-Schlüssel `workflows`\n(er ersetzt den bisherigen singulären Schlüssel `workflow`) und validiert jeden einzeln — ein\nfehlerhafter Workflow wird aus der Übersicht ausgeschlossen, statt die ganze Sitzung zu blockieren,\ngenau wie es bei Rollen und Channels bereits der Fall war. Ein Workspace mit ausschließlich der\nalten `roles/workflow.yaml` und ohne `roles/workflows/`-Verzeichnis funktioniert weiterhin\nunverändert, ganz ohne `--name`-Flag — diese Abwärtskompatibilität ist durch einen eigenen\nRegressionstest abgesichert.\n\n### Facade-Kontrakt\n- `changed` · `nxf show ` hängt jetzt am ganz Ende der Ausgabe einen auffälligen, wörtlichen Hinweis an, wenn\ndas Item Elternteil(e) hat: \"This item has the following parents: . URGENT RECOMMENDATION:\nALSO READ THESE ITEMS TO GET THE COMPLETE PICTURE!!!\" — ein Stupser, damit ein Agent, der ein\nKind-Ticket liest, auch das Elternteil (oft ein Epic) zieht, das die Spezifikation, globale\nRandbedingungen und den Designkontext trägt, von denen das Kind ausgeht. Einmalig im Engine-/\nFacade-Lese-Layer berechnet (`ShowRecord::parents` + `parents_notice`), sodass das menschenlesbare\n`nxf show`, `--json` (`parents`/`parents_notice`, sparsam — nur vorhanden, wenn das Item\nElternteil(e) hat) und jeder andere Konsument (MCP, Embedder) denselben Text rendern. Beschränkt\nauf Zugehörigkeits-/Containment-`parent`-Kanten, nicht `contributes_to`." - } - }, - { - "version": "0.33.0", - "date": "2026-07-19", - "items": [ - { - "type": "added", - "en": "`nxc` gains the role runtime: `roles/*.yaml` declarations, `send --role`/`reply` that spawn a real\nClaude Agent SDK session (Node sidecar) as the consequence of a message, ambient caller\nresolution, an internal session-id map, and follow-up-question resume — so a role can ask back\nwithout the task running dead.", - "de": "`nxc` erhält die Role-Runtime: `roles/*.yaml`-Deklarationen, `send --role`/`reply`, die als Folge\neiner Nachricht eine echte Claude-Agent-SDK-Session (Node-Sidecar) starten, ambiente\nCaller-Auflösung, ein internes Session-ID-Mapping und Rückfrage-Resume — eine Rolle kann\nzurückfragen, ohne dass die Aufgabe totläuft." - } - ], - "notes": { - "en": "### Added\n- `nxc` gains the role runtime: `roles/*.yaml` declarations, `send --role`/`reply` that spawn a real\nClaude Agent SDK session (Node sidecar) as the consequence of a message, ambient caller\nresolution, an internal session-id map, and follow-up-question resume — so a role can ask back\nwithout the task running dead.", - "de": "### Neu\n- `nxc` erhält die Role-Runtime: `roles/*.yaml`-Deklarationen, `send --role`/`reply`, die als Folge\neiner Nachricht eine echte Claude-Agent-SDK-Session (Node-Sidecar) starten, ambiente\nCaller-Auflösung, ein internes Session-ID-Mapping und Rückfrage-Resume — eine Rolle kann\nzurückfragen, ohne dass die Aufgabe totläuft." - } - }, - { - "version": "0.32.0", - "date": "2026-07-17", - "items": [ - { - "type": "added", - "en": "`nxs mcp install` now recognises **Amazon Quick** (the renamed Amazon Q Developer) as an install host, writing the shared `mcpServers` entry to its global config at `~/.aws/amazonq/mcp.json` — auto-detected like the other hosts, or targeted explicitly with `--host amazon-quick`.\nA **multi-workspace registry** (`~/.nexusflow/workspaces.toml`) lets an MCP host discover boards by name instead of path: `nxs mcp install --workspace ` registers the pinned workspace, the new `list_workspaces` tool (and the `nxs mcp workspaces` command) return the deterministic `{name, path}` list, and a host feeds a listed path back through any tool's per-call `workspace` override — selecting another board without knowing where it lives.", - "de": "`nxs mcp install` erkennt jetzt **Amazon Quick** (das umbenannte Amazon Q Developer) als Install-Host und schreibt den gemeinsamen `mcpServers`-Eintrag in dessen globale Konfiguration unter `~/.aws/amazonq/mcp.json` — automatisch erkannt wie die anderen Hosts oder gezielt per `--host amazon-quick`.\nEine **Multi-Workspace-Registry** (`~/.nexusflow/workspaces.toml`) lässt einen MCP-Host Boards per Name statt per Pfad finden: `nxs mcp install --workspace ` registriert den angehefteten Workspace, das neue `list_workspaces`-Tool (und der Befehl `nxs mcp workspaces`) liefern die deterministische `{name, path}`-Liste, und ein Host reicht einen gelisteten Pfad über den Per-Call-`workspace`-Override eines beliebigen Tools weiter — Auswahl eines anderen Boards, ohne dessen Ort zu kennen." - } - ], - "notes": { - "en": "### Added\n- `nxs mcp install` now recognises **Amazon Quick** (the renamed Amazon Q Developer) as an install host, writing the shared `mcpServers` entry to its global config at `~/.aws/amazonq/mcp.json` — auto-detected like the other hosts, or targeted explicitly with `--host amazon-quick`.\nA **multi-workspace registry** (`~/.nexusflow/workspaces.toml`) lets an MCP host discover boards by name instead of path: `nxs mcp install --workspace ` registers the pinned workspace, the new `list_workspaces` tool (and the `nxs mcp workspaces` command) return the deterministic `{name, path}` list, and a host feeds a listed path back through any tool's per-call `workspace` override — selecting another board without knowing where it lives.", - "de": "### Neu\n- `nxs mcp install` erkennt jetzt **Amazon Quick** (das umbenannte Amazon Q Developer) als Install-Host und schreibt den gemeinsamen `mcpServers`-Eintrag in dessen globale Konfiguration unter `~/.aws/amazonq/mcp.json` — automatisch erkannt wie die anderen Hosts oder gezielt per `--host amazon-quick`.\nEine **Multi-Workspace-Registry** (`~/.nexusflow/workspaces.toml`) lässt einen MCP-Host Boards per Name statt per Pfad finden: `nxs mcp install --workspace ` registriert den angehefteten Workspace, das neue `list_workspaces`-Tool (und der Befehl `nxs mcp workspaces`) liefern die deterministische `{name, path}`-Liste, und ein Host reicht einen gelisteten Pfad über den Per-Call-`workspace`-Override eines beliebigen Tools weiter — Auswahl eines anderen Boards, ohne dessen Ort zu kennen." - } - }, - { - "version": "0.31.0", - "date": "2026-07-17", - "items": [ - { - "type": "added", - "en": "`nxf recap` — a recall view of what you finished most recently (newest close first, archived closes\nincluded), with `--limit` (default 10), `--since `, and `--json`. The same recency view now\nalso appears as a \"Recently Closed\" section in `nxf prime`, so a fresh session sees at a glance what\nwas just completed. Distinct from `nxf closed` (the lane, archived excluded), which is unchanged.\n\nFacade: adds `read::recap()` (the shared recency query — full, uncapped records), the shared\n`read::truncate_notes` / `read::recap_note` note helpers, and a `recently_closed` field on\n`PrimeReport` (its `--json` gains a matching `recently_closed` array). The new `PrimeReport` field is\na breaking change for exhaustive struct literals — an embedder constructing `PrimeReport` directly\nmust add the field; reading it needs no change.", - "de": "`nxf recap` — eine Rückschau auf das zuletzt Erledigte (jüngster Abschluss zuerst, inkl.\narchivierter Abschlüsse), mit `--limit` (Standard 10), `--since ` und `--json`. Dieselbe\nRecency-Sicht erscheint jetzt auch als Abschnitt „Recently Closed\" in `nxf prime`, sodass eine neue\nSession auf einen Blick sieht, was gerade fertig wurde. Getrennt von `nxf closed` (der Lane, ohne\narchivierte), die unverändert bleibt.\n\nFacade: ergänzt `read::recap()` (die geteilte Recency-Abfrage — volle, ungekappte Records), die\ngeteilten Notiz-Helfer `read::truncate_notes` / `read::recap_note` sowie ein Feld `recently_closed`\nan `PrimeReport` (dessen `--json` erhält ein gespiegeltes Array `recently_closed`). Das neue\n`PrimeReport`-Feld bricht erschöpfende Struct-Literale — wer `PrimeReport` direkt konstruiert, muss\ndas Feld ergänzen; zum Lesen ist keine Änderung nötig.", - "facade": "breaking" - } - ], - "notes": { - "en": "### Added\n- `nxf recap` — a recall view of what you finished most recently (newest close first, archived closes\nincluded), with `--limit` (default 10), `--since `, and `--json`. The same recency view now\nalso appears as a \"Recently Closed\" section in `nxf prime`, so a fresh session sees at a glance what\nwas just completed. Distinct from `nxf closed` (the lane, archived excluded), which is unchanged.\n\nFacade: adds `read::recap()` (the shared recency query — full, uncapped records), the shared\n`read::truncate_notes` / `read::recap_note` note helpers, and a `recently_closed` field on\n`PrimeReport` (its `--json` gains a matching `recently_closed` array). The new `PrimeReport` field is\na breaking change for exhaustive struct literals — an embedder constructing `PrimeReport` directly\nmust add the field; reading it needs no change.\n\n### Facade Contract\n- `breaking` · `nxf recap` — a recall view of what you finished most recently (newest close first, archived closes\nincluded), with `--limit` (default 10), `--since `, and `--json`. The same recency view now\nalso appears as a \"Recently Closed\" section in `nxf prime`, so a fresh session sees at a glance what\nwas just completed. Distinct from `nxf closed` (the lane, archived excluded), which is unchanged.\n\nFacade: adds `read::recap()` (the shared recency query — full, uncapped records), the shared\n`read::truncate_notes` / `read::recap_note` note helpers, and a `recently_closed` field on\n`PrimeReport` (its `--json` gains a matching `recently_closed` array). The new `PrimeReport` field is\na breaking change for exhaustive struct literals — an embedder constructing `PrimeReport` directly\nmust add the field; reading it needs no change.", - "de": "### Neu\n- `nxf recap` — eine Rückschau auf das zuletzt Erledigte (jüngster Abschluss zuerst, inkl.\narchivierter Abschlüsse), mit `--limit` (Standard 10), `--since ` und `--json`. Dieselbe\nRecency-Sicht erscheint jetzt auch als Abschnitt „Recently Closed\" in `nxf prime`, sodass eine neue\nSession auf einen Blick sieht, was gerade fertig wurde. Getrennt von `nxf closed` (der Lane, ohne\narchivierte), die unverändert bleibt.\n\nFacade: ergänzt `read::recap()` (die geteilte Recency-Abfrage — volle, ungekappte Records), die\ngeteilten Notiz-Helfer `read::truncate_notes` / `read::recap_note` sowie ein Feld `recently_closed`\nan `PrimeReport` (dessen `--json` erhält ein gespiegeltes Array `recently_closed`). Das neue\n`PrimeReport`-Feld bricht erschöpfende Struct-Literale — wer `PrimeReport` direkt konstruiert, muss\ndas Feld ergänzen; zum Lesen ist keine Änderung nötig.\n\n### Facade-Kontrakt\n- `breaking` · `nxf recap` — eine Rückschau auf das zuletzt Erledigte (jüngster Abschluss zuerst, inkl.\narchivierter Abschlüsse), mit `--limit` (Standard 10), `--since ` und `--json`. Dieselbe\nRecency-Sicht erscheint jetzt auch als Abschnitt „Recently Closed\" in `nxf prime`, sodass eine neue\nSession auf einen Blick sieht, was gerade fertig wurde. Getrennt von `nxf closed` (der Lane, ohne\narchivierte), die unverändert bleibt.\n\nFacade: ergänzt `read::recap()` (die geteilte Recency-Abfrage — volle, ungekappte Records), die\ngeteilten Notiz-Helfer `read::truncate_notes` / `read::recap_note` sowie ein Feld `recently_closed`\nan `PrimeReport` (dessen `--json` erhält ein gespiegeltes Array `recently_closed`). Das neue\n`PrimeReport`-Feld bricht erschöpfende Struct-Literale — wer `PrimeReport` direkt konstruiert, muss\ndas Feld ergänzen; zum Lesen ist keine Änderung nötig." - } - }, - { - "version": "0.30.0", - "date": "2026-07-17", - "items": [ - { - "type": "changed", - "en": "`next` now recommends finishing over starting. The ranked list is grouped into three tiers on top of\nthe unchanged plugin ranking: first the started work you can close right now (an in-progress item\nwhose children are all closed, or a claimed leaf), then each started epic as a header row with its\nopen children grouped beneath it, and finally the general ready backlog. Within every tier your\nplugin's `next` policy still decides the order, so priorities are unchanged — only the grouping is\nnew. Two long-standing annoyances go away with it: a finished-but-still-open epic no longer vanishes\nfrom the list, and the children of an epic you already started no longer scatter across the backlog.\n\nStarted work is now **always** part of `next`, so the `--include-in-progress` flag is gone — drop it\nfrom scripts and aliases (`nxf next --include-in-progress` → `nxf next`). Use `--sort id` (or any\nexplicit `--sort`) when you want the old flat, ungrouped order. `nxs prime` shows 15 instead of 7\nrecommendations, so a started epic with many children is not truncated mid-cluster.\n\nEmbedding apps (facade contract, breaking): `read::next`, `Engine::next` and `Engine::next_value`\ndrop their `include_in_progress` parameter — call them without it to get the same set, now tiered.\nThe MCP `flow_next` tool drops the `include_in_progress` parameter too; an older client that still\nsends the field is tolerated (the field is ignored, not rejected). Each `next` row keeps its resolved\n`parent` join, so a board can group by the epic header row or by each child's `parent`.", - "de": "`next` empfiehlt jetzt Fertigmachen vor Anfangen. Die gerankte Liste wird über dem unveränderten\nPlugin-Ranking in drei Stufen gruppiert: zuerst die begonnene Arbeit, die du sofort abschließen\nkannst (ein laufendes Item, dessen Kinder alle geschlossen sind, oder ein beanspruchtes Blatt), dann\njedes begonnene Epic als Kopfzeile mit seinen offenen Kindern darunter, zuletzt der allgemeine\nReady-Backlog. Innerhalb jeder Stufe entscheidet weiterhin die `next`-Policy deines Plugins über die\nReihenfolge — Prioritäten bleiben also unverändert, neu ist nur die Gruppierung. Damit verschwinden\nzwei alte Ärgernisse: Ein fertiges, aber noch offenes Epic fällt nicht mehr aus der Liste, und die\nKinder eines bereits begonnenen Epics verstreuen sich nicht mehr über den Backlog.\n\nBegonnene Arbeit ist jetzt **immer** Teil von `next`, deshalb entfällt die Option\n`--include-in-progress` — entferne sie aus Skripten und Aliassen (`nxf next --include-in-progress` →\n`nxf next`). Für die alte flache, ungruppierte Reihenfolge `--sort id` (oder ein beliebiges\nexplizites `--sort`) verwenden. `nxs prime` zeigt 15 statt 7 Empfehlungen, damit ein begonnenes Epic\nmit vielen Kindern nicht mitten im Cluster abgeschnitten wird.\n\nEinbettende Apps (Facade-Kontrakt, brechend): `read::next`, `Engine::next` und `Engine::next_value`\nverlieren ihren Parameter `include_in_progress` — ohne ihn aufgerufen liefern sie dieselbe Menge,\njetzt getiert. Das MCP-Tool `flow_next` verliert den Parameter `include_in_progress` ebenfalls; ein\nälterer Client, der das Feld weiterhin sendet, wird toleriert (das Feld wird ignoriert, nicht\nabgelehnt). Jede `next`-Zeile behält ihren aufgelösten `parent`-Join, sodass ein Board wahlweise über\ndie Epic-Kopfzeile oder über das `parent`-Feld jedes Kindes gruppieren kann.", - "facade": "breaking" - } - ], - "notes": { - "en": "### Changed\n- `next` now recommends finishing over starting. The ranked list is grouped into three tiers on top of\nthe unchanged plugin ranking: first the started work you can close right now (an in-progress item\nwhose children are all closed, or a claimed leaf), then each started epic as a header row with its\nopen children grouped beneath it, and finally the general ready backlog. Within every tier your\nplugin's `next` policy still decides the order, so priorities are unchanged — only the grouping is\nnew. Two long-standing annoyances go away with it: a finished-but-still-open epic no longer vanishes\nfrom the list, and the children of an epic you already started no longer scatter across the backlog.\n\nStarted work is now **always** part of `next`, so the `--include-in-progress` flag is gone — drop it\nfrom scripts and aliases (`nxf next --include-in-progress` → `nxf next`). Use `--sort id` (or any\nexplicit `--sort`) when you want the old flat, ungrouped order. `nxs prime` shows 15 instead of 7\nrecommendations, so a started epic with many children is not truncated mid-cluster.\n\nEmbedding apps (facade contract, breaking): `read::next`, `Engine::next` and `Engine::next_value`\ndrop their `include_in_progress` parameter — call them without it to get the same set, now tiered.\nThe MCP `flow_next` tool drops the `include_in_progress` parameter too; an older client that still\nsends the field is tolerated (the field is ignored, not rejected). Each `next` row keeps its resolved\n`parent` join, so a board can group by the epic header row or by each child's `parent`.\n\n### Facade Contract\n- `breaking` · `next` now recommends finishing over starting. The ranked list is grouped into three tiers on top of\nthe unchanged plugin ranking: first the started work you can close right now (an in-progress item\nwhose children are all closed, or a claimed leaf), then each started epic as a header row with its\nopen children grouped beneath it, and finally the general ready backlog. Within every tier your\nplugin's `next` policy still decides the order, so priorities are unchanged — only the grouping is\nnew. Two long-standing annoyances go away with it: a finished-but-still-open epic no longer vanishes\nfrom the list, and the children of an epic you already started no longer scatter across the backlog.\n\nStarted work is now **always** part of `next`, so the `--include-in-progress` flag is gone — drop it\nfrom scripts and aliases (`nxf next --include-in-progress` → `nxf next`). Use `--sort id` (or any\nexplicit `--sort`) when you want the old flat, ungrouped order. `nxs prime` shows 15 instead of 7\nrecommendations, so a started epic with many children is not truncated mid-cluster.\n\nEmbedding apps (facade contract, breaking): `read::next`, `Engine::next` and `Engine::next_value`\ndrop their `include_in_progress` parameter — call them without it to get the same set, now tiered.\nThe MCP `flow_next` tool drops the `include_in_progress` parameter too; an older client that still\nsends the field is tolerated (the field is ignored, not rejected). Each `next` row keeps its resolved\n`parent` join, so a board can group by the epic header row or by each child's `parent`.", - "de": "### Geändert\n- `next` empfiehlt jetzt Fertigmachen vor Anfangen. Die gerankte Liste wird über dem unveränderten\nPlugin-Ranking in drei Stufen gruppiert: zuerst die begonnene Arbeit, die du sofort abschließen\nkannst (ein laufendes Item, dessen Kinder alle geschlossen sind, oder ein beanspruchtes Blatt), dann\njedes begonnene Epic als Kopfzeile mit seinen offenen Kindern darunter, zuletzt der allgemeine\nReady-Backlog. Innerhalb jeder Stufe entscheidet weiterhin die `next`-Policy deines Plugins über die\nReihenfolge — Prioritäten bleiben also unverändert, neu ist nur die Gruppierung. Damit verschwinden\nzwei alte Ärgernisse: Ein fertiges, aber noch offenes Epic fällt nicht mehr aus der Liste, und die\nKinder eines bereits begonnenen Epics verstreuen sich nicht mehr über den Backlog.\n\nBegonnene Arbeit ist jetzt **immer** Teil von `next`, deshalb entfällt die Option\n`--include-in-progress` — entferne sie aus Skripten und Aliassen (`nxf next --include-in-progress` →\n`nxf next`). Für die alte flache, ungruppierte Reihenfolge `--sort id` (oder ein beliebiges\nexplizites `--sort`) verwenden. `nxs prime` zeigt 15 statt 7 Empfehlungen, damit ein begonnenes Epic\nmit vielen Kindern nicht mitten im Cluster abgeschnitten wird.\n\nEinbettende Apps (Facade-Kontrakt, brechend): `read::next`, `Engine::next` und `Engine::next_value`\nverlieren ihren Parameter `include_in_progress` — ohne ihn aufgerufen liefern sie dieselbe Menge,\njetzt getiert. Das MCP-Tool `flow_next` verliert den Parameter `include_in_progress` ebenfalls; ein\nälterer Client, der das Feld weiterhin sendet, wird toleriert (das Feld wird ignoriert, nicht\nabgelehnt). Jede `next`-Zeile behält ihren aufgelösten `parent`-Join, sodass ein Board wahlweise über\ndie Epic-Kopfzeile oder über das `parent`-Feld jedes Kindes gruppieren kann.\n\n### Facade-Kontrakt\n- `breaking` · `next` empfiehlt jetzt Fertigmachen vor Anfangen. Die gerankte Liste wird über dem unveränderten\nPlugin-Ranking in drei Stufen gruppiert: zuerst die begonnene Arbeit, die du sofort abschließen\nkannst (ein laufendes Item, dessen Kinder alle geschlossen sind, oder ein beanspruchtes Blatt), dann\njedes begonnene Epic als Kopfzeile mit seinen offenen Kindern darunter, zuletzt der allgemeine\nReady-Backlog. Innerhalb jeder Stufe entscheidet weiterhin die `next`-Policy deines Plugins über die\nReihenfolge — Prioritäten bleiben also unverändert, neu ist nur die Gruppierung. Damit verschwinden\nzwei alte Ärgernisse: Ein fertiges, aber noch offenes Epic fällt nicht mehr aus der Liste, und die\nKinder eines bereits begonnenen Epics verstreuen sich nicht mehr über den Backlog.\n\nBegonnene Arbeit ist jetzt **immer** Teil von `next`, deshalb entfällt die Option\n`--include-in-progress` — entferne sie aus Skripten und Aliassen (`nxf next --include-in-progress` →\n`nxf next`). Für die alte flache, ungruppierte Reihenfolge `--sort id` (oder ein beliebiges\nexplizites `--sort`) verwenden. `nxs prime` zeigt 15 statt 7 Empfehlungen, damit ein begonnenes Epic\nmit vielen Kindern nicht mitten im Cluster abgeschnitten wird.\n\nEinbettende Apps (Facade-Kontrakt, brechend): `read::next`, `Engine::next` und `Engine::next_value`\nverlieren ihren Parameter `include_in_progress` — ohne ihn aufgerufen liefern sie dieselbe Menge,\njetzt getiert. Das MCP-Tool `flow_next` verliert den Parameter `include_in_progress` ebenfalls; ein\nälterer Client, der das Feld weiterhin sendet, wird toleriert (das Feld wird ignoriert, nicht\nabgelehnt). Jede `next`-Zeile behält ihren aufgelösten `parent`-Join, sodass ein Board wahlweise über\ndie Epic-Kopfzeile oder über das `parent`-Feld jedes Kindes gruppieren kann." - } - }, - { - "version": "0.29.0", - "date": "2026-07-16", - "items": [ - { - "type": "changed", - "en": "The default install and self-update origin moved to the nxsflow.com brand domain: `install.sh`,\n`nxs self-update`, and the `@nexus-flow/mcp` npx runner now fetch signed artifacts from\n`https://nxsflow.com/nxs` (previously `https://nxf.nxsflow.com`). The `NXF_BASE_URL` override is\nunchanged, and every artifact is still verified fail-closed (sha256 + minisign) from the same\norigin it was resolved on. Nothing to do on upgrade — this release is published to both the old\nand the new delivery chains during the migration window, so already-installed clients update\nonto the new endpoint over their existing chain automatically.", - "de": "Der Standard-Ursprung für Installation und Self-Update liegt jetzt auf der Marken-Domain\nnxsflow.com: `install.sh`, `nxs self-update` und der `@nexus-flow/mcp`-npx-Runner beziehen die\nsignierten Artefakte von `https://nxsflow.com/nxs` (vorher `https://nxf.nxsflow.com`). Der\n`NXF_BASE_URL`-Override bleibt unverändert, und jedes Artefakt wird weiterhin fail-closed\n(sha256 + minisign) vom selben Ursprung verifiziert, von dem es aufgelöst wurde. Beim Upgrade\nist nichts zu tun — dieses Release wird während des Migrationsfensters in die alte und die neue\nAuslieferungskette veröffentlicht, sodass bereits installierte Clients automatisch über ihre\nbestehende Kette auf den neuen Endpunkt aktualisieren." - } - ], - "notes": { - "en": "### Changed\n- The default install and self-update origin moved to the nxsflow.com brand domain: `install.sh`,\n`nxs self-update`, and the `@nexus-flow/mcp` npx runner now fetch signed artifacts from\n`https://nxsflow.com/nxs` (previously `https://nxf.nxsflow.com`). The `NXF_BASE_URL` override is\nunchanged, and every artifact is still verified fail-closed (sha256 + minisign) from the same\norigin it was resolved on. Nothing to do on upgrade — this release is published to both the old\nand the new delivery chains during the migration window, so already-installed clients update\nonto the new endpoint over their existing chain automatically.", - "de": "### Geändert\n- Der Standard-Ursprung für Installation und Self-Update liegt jetzt auf der Marken-Domain\nnxsflow.com: `install.sh`, `nxs self-update` und der `@nexus-flow/mcp`-npx-Runner beziehen die\nsignierten Artefakte von `https://nxsflow.com/nxs` (vorher `https://nxf.nxsflow.com`). Der\n`NXF_BASE_URL`-Override bleibt unverändert, und jedes Artefakt wird weiterhin fail-closed\n(sha256 + minisign) vom selben Ursprung verifiziert, von dem es aufgelöst wurde. Beim Upgrade\nist nichts zu tun — dieses Release wird während des Migrationsfensters in die alte und die neue\nAuslieferungskette veröffentlicht, sodass bereits installierte Clients automatisch über ihre\nbestehende Kette auf den neuen Endpunkt aktualisieren." - } - }, - { - "version": "0.28.0", - "date": "2026-07-15", - "items": [ - { - "type": "added", - "en": "Embedding API: the long-lived engine handle now exposes `closed_value`, `deferred_value`,\n`archived_value`, and `blocked_value` — the `closed`/`deferred`/`archived`/`blocked` lanes as the\nsame canonical `--json` records the CLI emits, each carrying the sparse plugin-`custom` map (and\n`blocked_value` its open `blockers` list). A new `custom_fields_bulk` returns the declared `custom`\nmap for many ids in one read, for joining custom onto row sets the handle does not project itself\n(notably `prime`'s `next` list). In-process consumers can now mirror the full `--json` custom\ncoverage without an N+1 per-item read.", - "de": "Embedding-API: Das langlebige Engine-Handle stellt jetzt `closed_value`, `deferred_value`,\n`archived_value` und `blocked_value` bereit — die Lanes `closed`/`deferred`/`archived`/`blocked` als\ndieselben kanonischen `--json`-Records wie die CLI, jeweils mit der spärlichen Plugin-`custom`-Map\n(und `blocked_value` mit seiner Liste offener `blockers`). Ein neues `custom_fields_bulk` liefert die\ndeklarierte `custom`-Map für viele IDs in einem Read, um Custom-Werte an Zeilenmengen zu joinen, die\ndas Handle nicht selbst projiziert (insbesondere die `next`-Liste von `prime`). In-Process-Consumer\nkönnen damit die volle `--json`-Custom-Abdeckung ohne N+1-Read pro Item spiegeln.", - "facade": "changed" - }, - { - "type": "changed", - "en": "`nxf prime`'s \"Finding work\" command index now lists `nxf deferred` (open, unblocked work with a\nfuture defer date). The lane was documented only in the Core Rules prose, so an agent that scans the\ncommand index never saw it. The entry also surfaces the event-vs-date practice: defer needs a real\ndate — to wait on an event or delivery, use a `WAIT:` chore that dependents depend on (see `nxf guide\ndeferring-and-waiting`), not a placeholder defer date.", - "de": "Der Kommando-Index „Finding work\" von `nxf prime` listet jetzt `nxf deferred` (offene, nicht\nblockierte Arbeit mit zukünftigem Defer-Datum). Die Lane war nur in der Prosa der Core Rules\ndokumentiert, sodass ein Agent, der den Kommando-Index scannt, sie nie sah. Der Eintrag macht auch die\nEreignis-vs-Datum-Praxis sichtbar: Defer braucht ein echtes Datum — um auf ein Ereignis oder eine\nLieferung zu warten, ein `WAIT:`-Chore anlegen, hinter das Abhängige gehängt werden (siehe `nxf guide\ndeferring-and-waiting`), statt eines Platzhalter-Defer-Datums.", - "facade": "changed" - }, - { - "type": "added", - "en": "Read surface: item `--json` records (`show`, `list`, `next`, and the `blocked`/`deferred`/`closed`/\n`archived` lanes) now carry `created_at` and `updated_at`, derived from the op-log — present when the\nwrite stamped a timestamp. Worklog notes gain a `created_at` too (in `show --json` and `note list\n--json`), and the human `show`/`note list` views print each note's date. The canonical record keys are\nunchanged; the new fields ride additively, so a consumer that ignores them is unaffected.", - "de": "Read-Surface: Item-`--json`-Records (`show`, `list`, `next` sowie die Lanes `blocked`/`deferred`/\n`closed`/`archived`) tragen jetzt `created_at` und `updated_at`, aus dem Op-Log abgeleitet — vorhanden,\nwenn der Write einen Zeitstempel gesetzt hat. Worklog-Notizen erhalten ebenfalls ein `created_at` (in\n`show --json` und `note list --json`), und die Human-Ansichten von `show`/`note list` zeigen das Datum\njeder Notiz. Die kanonischen Record-Keys bleiben unverändert; die neuen Felder kommen additiv hinzu, ein\nConsumer, der sie ignoriert, ist nicht betroffen.", - "facade": "changed" - }, - { - "type": "changed", - "en": "`nxf update --set defer=` (an empty value) now CLEARS an optional field. Previously `defer=`\nand `due=` were rejected outright (the date validator refuses an empty value) and there was no unset,\nso the only way out of the deferred lane was to set a stale past date; `assignee=` was accepted but\nsilently stored an empty string rather than clearing. Now `defer=`, `due=`, and `assignee=` all unset\nthe field. Validated fields like `status`/`priority` still reject an empty value.", - "de": "`nxf update --set defer=` (ein leerer Wert) LÖSCHT jetzt ein optionales Feld. Bisher wurden\n`defer=` und `due=` rundweg abgelehnt (der Datumsvalidator verweigert einen leeren Wert) und es gab\nkein Unset, sodass der einzige Weg aus der deferred-Lane das Setzen eines veralteten vergangenen\nDatums war; `assignee=` wurde akzeptiert, speicherte aber stillschweigend eine leere Zeichenkette,\nstatt zu löschen. Jetzt entfernen `defer=`, `due=` und `assignee=` das Feld. Validierte Felder wie\n`status`/`priority` lehnen einen leeren Wert weiterhin ab.", - "facade": "changed" - } - ], - "notes": { - "en": "### Added\n- Embedding API: the long-lived engine handle now exposes `closed_value`, `deferred_value`,\n`archived_value`, and `blocked_value` — the `closed`/`deferred`/`archived`/`blocked` lanes as the\nsame canonical `--json` records the CLI emits, each carrying the sparse plugin-`custom` map (and\n`blocked_value` its open `blockers` list). A new `custom_fields_bulk` returns the declared `custom`\nmap for many ids in one read, for joining custom onto row sets the handle does not project itself\n(notably `prime`'s `next` list). In-process consumers can now mirror the full `--json` custom\ncoverage without an N+1 per-item read.\n- Read surface: item `--json` records (`show`, `list`, `next`, and the `blocked`/`deferred`/`closed`/\n`archived` lanes) now carry `created_at` and `updated_at`, derived from the op-log — present when the\nwrite stamped a timestamp. Worklog notes gain a `created_at` too (in `show --json` and `note list\n--json`), and the human `show`/`note list` views print each note's date. The canonical record keys are\nunchanged; the new fields ride additively, so a consumer that ignores them is unaffected.\n\n### Changed\n- `nxf prime`'s \"Finding work\" command index now lists `nxf deferred` (open, unblocked work with a\nfuture defer date). The lane was documented only in the Core Rules prose, so an agent that scans the\ncommand index never saw it. The entry also surfaces the event-vs-date practice: defer needs a real\ndate — to wait on an event or delivery, use a `WAIT:` chore that dependents depend on (see `nxf guide\ndeferring-and-waiting`), not a placeholder defer date.\n- `nxf update --set defer=` (an empty value) now CLEARS an optional field. Previously `defer=`\nand `due=` were rejected outright (the date validator refuses an empty value) and there was no unset,\nso the only way out of the deferred lane was to set a stale past date; `assignee=` was accepted but\nsilently stored an empty string rather than clearing. Now `defer=`, `due=`, and `assignee=` all unset\nthe field. Validated fields like `status`/`priority` still reject an empty value.\n\n### Facade Contract\n- `changed` · Embedding API: the long-lived engine handle now exposes `closed_value`, `deferred_value`,\n`archived_value`, and `blocked_value` — the `closed`/`deferred`/`archived`/`blocked` lanes as the\nsame canonical `--json` records the CLI emits, each carrying the sparse plugin-`custom` map (and\n`blocked_value` its open `blockers` list). A new `custom_fields_bulk` returns the declared `custom`\nmap for many ids in one read, for joining custom onto row sets the handle does not project itself\n(notably `prime`'s `next` list). In-process consumers can now mirror the full `--json` custom\ncoverage without an N+1 per-item read.\n- `changed` · `nxf prime`'s \"Finding work\" command index now lists `nxf deferred` (open, unblocked work with a\nfuture defer date). The lane was documented only in the Core Rules prose, so an agent that scans the\ncommand index never saw it. The entry also surfaces the event-vs-date practice: defer needs a real\ndate — to wait on an event or delivery, use a `WAIT:` chore that dependents depend on (see `nxf guide\ndeferring-and-waiting`), not a placeholder defer date.\n- `changed` · Read surface: item `--json` records (`show`, `list`, `next`, and the `blocked`/`deferred`/`closed`/\n`archived` lanes) now carry `created_at` and `updated_at`, derived from the op-log — present when the\nwrite stamped a timestamp. Worklog notes gain a `created_at` too (in `show --json` and `note list\n--json`), and the human `show`/`note list` views print each note's date. The canonical record keys are\nunchanged; the new fields ride additively, so a consumer that ignores them is unaffected.\n- `changed` · `nxf update --set defer=` (an empty value) now CLEARS an optional field. Previously `defer=`\nand `due=` were rejected outright (the date validator refuses an empty value) and there was no unset,\nso the only way out of the deferred lane was to set a stale past date; `assignee=` was accepted but\nsilently stored an empty string rather than clearing. Now `defer=`, `due=`, and `assignee=` all unset\nthe field. Validated fields like `status`/`priority` still reject an empty value.", - "de": "### Neu\n- Embedding-API: Das langlebige Engine-Handle stellt jetzt `closed_value`, `deferred_value`,\n`archived_value` und `blocked_value` bereit — die Lanes `closed`/`deferred`/`archived`/`blocked` als\ndieselben kanonischen `--json`-Records wie die CLI, jeweils mit der spärlichen Plugin-`custom`-Map\n(und `blocked_value` mit seiner Liste offener `blockers`). Ein neues `custom_fields_bulk` liefert die\ndeklarierte `custom`-Map für viele IDs in einem Read, um Custom-Werte an Zeilenmengen zu joinen, die\ndas Handle nicht selbst projiziert (insbesondere die `next`-Liste von `prime`). In-Process-Consumer\nkönnen damit die volle `--json`-Custom-Abdeckung ohne N+1-Read pro Item spiegeln.\n- Read-Surface: Item-`--json`-Records (`show`, `list`, `next` sowie die Lanes `blocked`/`deferred`/\n`closed`/`archived`) tragen jetzt `created_at` und `updated_at`, aus dem Op-Log abgeleitet — vorhanden,\nwenn der Write einen Zeitstempel gesetzt hat. Worklog-Notizen erhalten ebenfalls ein `created_at` (in\n`show --json` und `note list --json`), und die Human-Ansichten von `show`/`note list` zeigen das Datum\njeder Notiz. Die kanonischen Record-Keys bleiben unverändert; die neuen Felder kommen additiv hinzu, ein\nConsumer, der sie ignoriert, ist nicht betroffen.\n\n### Geändert\n- Der Kommando-Index „Finding work\" von `nxf prime` listet jetzt `nxf deferred` (offene, nicht\nblockierte Arbeit mit zukünftigem Defer-Datum). Die Lane war nur in der Prosa der Core Rules\ndokumentiert, sodass ein Agent, der den Kommando-Index scannt, sie nie sah. Der Eintrag macht auch die\nEreignis-vs-Datum-Praxis sichtbar: Defer braucht ein echtes Datum — um auf ein Ereignis oder eine\nLieferung zu warten, ein `WAIT:`-Chore anlegen, hinter das Abhängige gehängt werden (siehe `nxf guide\ndeferring-and-waiting`), statt eines Platzhalter-Defer-Datums.\n- `nxf update --set defer=` (ein leerer Wert) LÖSCHT jetzt ein optionales Feld. Bisher wurden\n`defer=` und `due=` rundweg abgelehnt (der Datumsvalidator verweigert einen leeren Wert) und es gab\nkein Unset, sodass der einzige Weg aus der deferred-Lane das Setzen eines veralteten vergangenen\nDatums war; `assignee=` wurde akzeptiert, speicherte aber stillschweigend eine leere Zeichenkette,\nstatt zu löschen. Jetzt entfernen `defer=`, `due=` und `assignee=` das Feld. Validierte Felder wie\n`status`/`priority` lehnen einen leeren Wert weiterhin ab.\n\n### Facade-Kontrakt\n- `changed` · Embedding-API: Das langlebige Engine-Handle stellt jetzt `closed_value`, `deferred_value`,\n`archived_value` und `blocked_value` bereit — die Lanes `closed`/`deferred`/`archived`/`blocked` als\ndieselben kanonischen `--json`-Records wie die CLI, jeweils mit der spärlichen Plugin-`custom`-Map\n(und `blocked_value` mit seiner Liste offener `blockers`). Ein neues `custom_fields_bulk` liefert die\ndeklarierte `custom`-Map für viele IDs in einem Read, um Custom-Werte an Zeilenmengen zu joinen, die\ndas Handle nicht selbst projiziert (insbesondere die `next`-Liste von `prime`). In-Process-Consumer\nkönnen damit die volle `--json`-Custom-Abdeckung ohne N+1-Read pro Item spiegeln.\n- `changed` · Der Kommando-Index „Finding work\" von `nxf prime` listet jetzt `nxf deferred` (offene, nicht\nblockierte Arbeit mit zukünftigem Defer-Datum). Die Lane war nur in der Prosa der Core Rules\ndokumentiert, sodass ein Agent, der den Kommando-Index scannt, sie nie sah. Der Eintrag macht auch die\nEreignis-vs-Datum-Praxis sichtbar: Defer braucht ein echtes Datum — um auf ein Ereignis oder eine\nLieferung zu warten, ein `WAIT:`-Chore anlegen, hinter das Abhängige gehängt werden (siehe `nxf guide\ndeferring-and-waiting`), statt eines Platzhalter-Defer-Datums.\n- `changed` · Read-Surface: Item-`--json`-Records (`show`, `list`, `next` sowie die Lanes `blocked`/`deferred`/\n`closed`/`archived`) tragen jetzt `created_at` und `updated_at`, aus dem Op-Log abgeleitet — vorhanden,\nwenn der Write einen Zeitstempel gesetzt hat. Worklog-Notizen erhalten ebenfalls ein `created_at` (in\n`show --json` und `note list --json`), und die Human-Ansichten von `show`/`note list` zeigen das Datum\njeder Notiz. Die kanonischen Record-Keys bleiben unverändert; die neuen Felder kommen additiv hinzu, ein\nConsumer, der sie ignoriert, ist nicht betroffen.\n- `changed` · `nxf update --set defer=` (ein leerer Wert) LÖSCHT jetzt ein optionales Feld. Bisher wurden\n`defer=` und `due=` rundweg abgelehnt (der Datumsvalidator verweigert einen leeren Wert) und es gab\nkein Unset, sodass der einzige Weg aus der deferred-Lane das Setzen eines veralteten vergangenen\nDatums war; `assignee=` wurde akzeptiert, speicherte aber stillschweigend eine leere Zeichenkette,\nstatt zu löschen. Jetzt entfernen `defer=`, `due=` und `assignee=` das Feld. Validierte Felder wie\n`status`/`priority` lehnen einen leeren Wert weiterhin ab." - } - }, - { - "version": "0.27.0", - "date": "2026-07-14", - "items": [ - { - "type": "changed", - "en": "`nxs mcp serve` with no `--workspace`/`--db` now defaults its auto-initialized board to a neutral,\nnxs-owned location (`com.nxsflow.nxs` under the platform data dir) instead of a specific consumer\napp's data home. The OSS server no longer assumes any consumer; a consumer that wants its own board\ninstalls its own `--workspace`-pinned MCP entry (and may import this board's data). If you relied on\nthe previous default location, point the entry at it explicitly with `--workspace`.", - "de": "`nxs mcp serve` ohne `--workspace`/`--db` legt sein automatisch initialisiertes Board jetzt an einem\nneutralen, nxs-eigenen Ort ab (`com.nxsflow.nxs` unter dem Plattform-Datenverzeichnis) statt im\nDatenverzeichnis einer bestimmten Consumer-App. Der OSS-Server nimmt keinen Consumer mehr an; ein\nConsumer mit eigenem Board richtet seinen eigenen `--workspace`-gepinnten MCP-Eintrag ein (und kann\ndieses Board importieren). Wer sich auf den bisherigen Standardort verlassen hat, verweist den\nEintrag per `--workspace` explizit darauf." - }, - { - "type": "added", - "en": "More `--json` reads now carry the same additive fields the primary reads already do, for cross-verb\nagent consistency: `blocked` and `search` gain `priority_label`/`type_label`, `next`'s nested\n`parent` join gains `type_label`, and `blocked`/`deferred`/`closed`/`archived` plus `prime` now carry\nthe sparse plugin `custom` field map (identical to `list`/`next`/`show`). Canonical fields are\nunchanged; all additions are additive and appear only where relevant.", - "de": "Weitere `--json`-Reads führen jetzt dieselben additiven Felder wie die Hauptreads — für\nverbübergreifende Agenten-Konsistenz: `blocked` und `search` erhalten `priority_label`/`type_label`,\nder verschachtelte `parent`-Join von `next` erhält `type_label`, und `blocked`/`deferred`/`closed`/\n`archived` sowie `prime` führen jetzt die sparse Plugin-`custom`-Feldkarte (identisch zu\n`list`/`next`/`show`). Kanonische Felder bleiben unverändert; alle Ergänzungen sind additiv und\nerscheinen nur dort, wo sie zutreffen.", - "facade": "changed" - } - ], - "notes": { - "en": "### Added\n- More `--json` reads now carry the same additive fields the primary reads already do, for cross-verb\nagent consistency: `blocked` and `search` gain `priority_label`/`type_label`, `next`'s nested\n`parent` join gains `type_label`, and `blocked`/`deferred`/`closed`/`archived` plus `prime` now carry\nthe sparse plugin `custom` field map (identical to `list`/`next`/`show`). Canonical fields are\nunchanged; all additions are additive and appear only where relevant.\n\n### Changed\n- `nxs mcp serve` with no `--workspace`/`--db` now defaults its auto-initialized board to a neutral,\nnxs-owned location (`com.nxsflow.nxs` under the platform data dir) instead of a specific consumer\napp's data home. The OSS server no longer assumes any consumer; a consumer that wants its own board\ninstalls its own `--workspace`-pinned MCP entry (and may import this board's data). If you relied on\nthe previous default location, point the entry at it explicitly with `--workspace`.\n\n### Facade Contract\n- `changed` · More `--json` reads now carry the same additive fields the primary reads already do, for cross-verb\nagent consistency: `blocked` and `search` gain `priority_label`/`type_label`, `next`'s nested\n`parent` join gains `type_label`, and `blocked`/`deferred`/`closed`/`archived` plus `prime` now carry\nthe sparse plugin `custom` field map (identical to `list`/`next`/`show`). Canonical fields are\nunchanged; all additions are additive and appear only where relevant.", - "de": "### Neu\n- Weitere `--json`-Reads führen jetzt dieselben additiven Felder wie die Hauptreads — für\nverbübergreifende Agenten-Konsistenz: `blocked` und `search` erhalten `priority_label`/`type_label`,\nder verschachtelte `parent`-Join von `next` erhält `type_label`, und `blocked`/`deferred`/`closed`/\n`archived` sowie `prime` führen jetzt die sparse Plugin-`custom`-Feldkarte (identisch zu\n`list`/`next`/`show`). Kanonische Felder bleiben unverändert; alle Ergänzungen sind additiv und\nerscheinen nur dort, wo sie zutreffen.\n\n### Geändert\n- `nxs mcp serve` ohne `--workspace`/`--db` legt sein automatisch initialisiertes Board jetzt an einem\nneutralen, nxs-eigenen Ort ab (`com.nxsflow.nxs` unter dem Plattform-Datenverzeichnis) statt im\nDatenverzeichnis einer bestimmten Consumer-App. Der OSS-Server nimmt keinen Consumer mehr an; ein\nConsumer mit eigenem Board richtet seinen eigenen `--workspace`-gepinnten MCP-Eintrag ein (und kann\ndieses Board importieren). Wer sich auf den bisherigen Standardort verlassen hat, verweist den\nEintrag per `--workspace` explizit darauf.\n\n### Facade-Kontrakt\n- `changed` · Weitere `--json`-Reads führen jetzt dieselben additiven Felder wie die Hauptreads — für\nverbübergreifende Agenten-Konsistenz: `blocked` und `search` erhalten `priority_label`/`type_label`,\nder verschachtelte `parent`-Join von `next` erhält `type_label`, und `blocked`/`deferred`/`closed`/\n`archived` sowie `prime` führen jetzt die sparse Plugin-`custom`-Feldkarte (identisch zu\n`list`/`next`/`show`). Kanonische Felder bleiben unverändert; alle Ergänzungen sind additiv und\nerscheinen nur dort, wo sie zutreffen." - } - }, - { - "version": "0.26.0", - "date": "2026-07-13", - "items": [ - { - "type": "fixed", - "en": "Piping CLI output into a reader that closes early (`nxf blocked | head`, `nxf list | grep -m1 …`) no longer panics with \"failed printing to stdout: Broken pipe\". The process now terminates quietly on SIGPIPE like any other Unix filter, so agent scripts that page or truncate output stay clean.", - "de": "Wird CLI-Ausgabe in einen Reader gepiped, der früh schließt (`nxf blocked | head`, `nxf list | grep -m1 …`), bricht das nicht mehr mit „failed printing to stdout: Broken pipe\" ab. Der Prozess endet jetzt still per SIGPIPE wie jeder andere Unix-Filter — Agenten-Skripte, die Ausgabe kürzen oder blättern, bleiben sauber." - }, - { - "type": "added", - "en": "`nxf show`, `list`, and `next --json` now carry additive `priority_label` and `type_label` fields — the active plugin's display labels (e.g. `P2`, `epic`) — alongside the canonical `priority`/`type` keys, so an agent no longer needs a separate `schema` lookup to render them. `create` and `update` now also accept the canonical ordinal priority **key** (e.g. `2`) in addition to the label (`P2`), so a value read back from `--json` round-trips as valid input. The canonical `priority`/`type` keys, the human (non-`--json`) output, and the embedding/MCP record are all unchanged.", - "de": "`nxf show`, `list` und `next --json` liefern jetzt zusätzlich die additiven Felder `priority_label` und `type_label` — die Anzeige-Labels des aktiven Plugins (z. B. `P2`, `epic`) — neben den kanonischen Keys `priority`/`type`, sodass ein Agent für die Anzeige keinen separaten `schema`-Lookup mehr braucht. `create` und `update` akzeptieren jetzt zusätzlich zum Label (`P2`) auch den kanonischen Ordinal-**Key** der Priorität (z. B. `2`), sodass ein aus `--json` gelesener Wert als gültige Eingabe zurückläuft. Die kanonischen Keys `priority`/`type`, die Human-Ausgabe (ohne `--json`) und der Embedding-/MCP-Record bleiben unverändert.", - "facade": "changed" - }, - { - "type": "changed", - "en": "The \"no embedded minisign public key\" error from `nxs self-update` and `nxf verify-signature` is now actionable: it explains that the binary is unsigned (a locally-built or development build) and points to the official signed-release install (`curl -fsSL https://nxf.nxsflow.com/install.sh | sh`), instead of referencing an internal spec document that isn't shipped with the binary. The fail-closed refusal itself is unchanged.", - "de": "Die Fehlermeldung „no embedded minisign public key\" von `nxs self-update` und `nxf verify-signature` ist jetzt handlungsleitend: Sie erklärt, dass das Binary unsigniert ist (ein lokal gebauter oder Entwicklungs-Build), und verweist auf die offizielle signierte Release-Installation (`curl -fsSL https://nxf.nxsflow.com/install.sh | sh`) statt auf ein internes Spec-Dokument, das mit dem Binary gar nicht ausgeliefert wird. Die fail-closed-Verweigerung selbst bleibt unverändert." - } - ], - "notes": { - "en": "### Added\n- `nxf show`, `list`, and `next --json` now carry additive `priority_label` and `type_label` fields — the active plugin's display labels (e.g. `P2`, `epic`) — alongside the canonical `priority`/`type` keys, so an agent no longer needs a separate `schema` lookup to render them. `create` and `update` now also accept the canonical ordinal priority **key** (e.g. `2`) in addition to the label (`P2`), so a value read back from `--json` round-trips as valid input. The canonical `priority`/`type` keys, the human (non-`--json`) output, and the embedding/MCP record are all unchanged.\n\n### Changed\n- The \"no embedded minisign public key\" error from `nxs self-update` and `nxf verify-signature` is now actionable: it explains that the binary is unsigned (a locally-built or development build) and points to the official signed-release install (`curl -fsSL https://nxf.nxsflow.com/install.sh | sh`), instead of referencing an internal spec document that isn't shipped with the binary. The fail-closed refusal itself is unchanged.\n\n### Fixed\n- Piping CLI output into a reader that closes early (`nxf blocked | head`, `nxf list | grep -m1 …`) no longer panics with \"failed printing to stdout: Broken pipe\". The process now terminates quietly on SIGPIPE like any other Unix filter, so agent scripts that page or truncate output stay clean.\n\n### Facade Contract\n- `changed` · `nxf show`, `list`, and `next --json` now carry additive `priority_label` and `type_label` fields — the active plugin's display labels (e.g. `P2`, `epic`) — alongside the canonical `priority`/`type` keys, so an agent no longer needs a separate `schema` lookup to render them. `create` and `update` now also accept the canonical ordinal priority **key** (e.g. `2`) in addition to the label (`P2`), so a value read back from `--json` round-trips as valid input. The canonical `priority`/`type` keys, the human (non-`--json`) output, and the embedding/MCP record are all unchanged.", - "de": "### Neu\n- `nxf show`, `list` und `next --json` liefern jetzt zusätzlich die additiven Felder `priority_label` und `type_label` — die Anzeige-Labels des aktiven Plugins (z. B. `P2`, `epic`) — neben den kanonischen Keys `priority`/`type`, sodass ein Agent für die Anzeige keinen separaten `schema`-Lookup mehr braucht. `create` und `update` akzeptieren jetzt zusätzlich zum Label (`P2`) auch den kanonischen Ordinal-**Key** der Priorität (z. B. `2`), sodass ein aus `--json` gelesener Wert als gültige Eingabe zurückläuft. Die kanonischen Keys `priority`/`type`, die Human-Ausgabe (ohne `--json`) und der Embedding-/MCP-Record bleiben unverändert.\n\n### Geändert\n- Die Fehlermeldung „no embedded minisign public key\" von `nxs self-update` und `nxf verify-signature` ist jetzt handlungsleitend: Sie erklärt, dass das Binary unsigniert ist (ein lokal gebauter oder Entwicklungs-Build), und verweist auf die offizielle signierte Release-Installation (`curl -fsSL https://nxf.nxsflow.com/install.sh | sh`) statt auf ein internes Spec-Dokument, das mit dem Binary gar nicht ausgeliefert wird. Die fail-closed-Verweigerung selbst bleibt unverändert.\n\n### Behoben\n- Wird CLI-Ausgabe in einen Reader gepiped, der früh schließt (`nxf blocked | head`, `nxf list | grep -m1 …`), bricht das nicht mehr mit „failed printing to stdout: Broken pipe\" ab. Der Prozess endet jetzt still per SIGPIPE wie jeder andere Unix-Filter — Agenten-Skripte, die Ausgabe kürzen oder blättern, bleiben sauber.\n\n### Facade-Kontrakt\n- `changed` · `nxf show`, `list` und `next --json` liefern jetzt zusätzlich die additiven Felder `priority_label` und `type_label` — die Anzeige-Labels des aktiven Plugins (z. B. `P2`, `epic`) — neben den kanonischen Keys `priority`/`type`, sodass ein Agent für die Anzeige keinen separaten `schema`-Lookup mehr braucht. `create` und `update` akzeptieren jetzt zusätzlich zum Label (`P2`) auch den kanonischen Ordinal-**Key** der Priorität (z. B. `2`), sodass ein aus `--json` gelesener Wert als gültige Eingabe zurückläuft. Die kanonischen Keys `priority`/`type`, die Human-Ausgabe (ohne `--json`) und der Embedding-/MCP-Record bleiben unverändert." - } - }, - { - "version": "0.25.1", - "date": "2026-07-13", - "items": [ - { - "type": "fixed", - "en": "Plugin custom fields follow-ups (post-v0.25.0 review): `nxf create --json -` now accepts custom\nfields via a `set` array in the JSON payload (mirroring the `--set` flag), so an item whose type has\na **required** custom field can be created from a JSON payload too — previously that path silently\ndropped custom fields. The \"custom field does not apply to this type\" error now also lists the\ntype(s) the field applies to, so an agent can self-correct without a `schema` round-trip.", - "de": "Nachbesserungen zu Plugin-Custom-Fields (Review nach v0.25.0): `nxf create --json -` akzeptiert\nCustom-Felder jetzt über ein `set`-Array in der JSON-Payload (analog zum `--set`-Flag) — damit lässt\nsich auch ein Item, dessen Typ ein **Pflicht**-Custom-Feld hat, aus einer JSON-Payload erzeugen;\nzuvor ließ dieser Pfad Custom-Felder still fallen. Die Fehlermeldung „Custom-Feld gilt nicht für\ndiesen Typ\" nennt jetzt zusätzlich die Typen, für die das Feld gilt, sodass ein Agent sich ohne\n`schema`-Abfrage selbst korrigieren kann." - } - ], - "notes": { - "en": "### Fixed\n- Plugin custom fields follow-ups (post-v0.25.0 review): `nxf create --json -` now accepts custom\nfields via a `set` array in the JSON payload (mirroring the `--set` flag), so an item whose type has\na **required** custom field can be created from a JSON payload too — previously that path silently\ndropped custom fields. The \"custom field does not apply to this type\" error now also lists the\ntype(s) the field applies to, so an agent can self-correct without a `schema` round-trip.", - "de": "### Behoben\n- Nachbesserungen zu Plugin-Custom-Fields (Review nach v0.25.0): `nxf create --json -` akzeptiert\nCustom-Felder jetzt über ein `set`-Array in der JSON-Payload (analog zum `--set`-Flag) — damit lässt\nsich auch ein Item, dessen Typ ein **Pflicht**-Custom-Feld hat, aus einer JSON-Payload erzeugen;\nzuvor ließ dieser Pfad Custom-Felder still fallen. Die Fehlermeldung „Custom-Feld gilt nicht für\ndiesen Typ\" nennt jetzt zusätzlich die Typen, für die das Feld gilt, sodass ein Agent sich ohne\n`schema`-Abfrage selbst korrigieren kann." - } - }, - { - "version": "0.25.0", - "date": "2026-07-12", - "items": [ - { - "type": "added", - "en": "Plugins can now declare their own **custom item fields** on top of the 16 built-in ones. A plugin\ndeclares them in a `[fields]` table (each with a type — `text`/`longtext`/`date`/`enum` — an\noptional `on` type scope, `required`, and a display `label`); you then set them with\n`nxf create`/`nxf update --set =` (type-validated; an empty value clears). They surface\neverywhere reads do: `nxf schema --json` lists each declared field (marked `custom`, with its\n`kind`/`on`/`required`/`label`, and `values` for an enum), and `nxf show`/`list`/`next --json`\nattach a sparse `custom` map with the item's set values (a field that is unset — or belongs to a\ndifferent plugin — is simply omitted). The MCP `flow_schema`/`flow_show`/`flow_list`/`flow_next`\ntools carry the identical shape. `nxf show`'s human view also prints declared custom fields under\ntheir label. This replaces overloading `labels`/`description` for domain data (a file's `uri`, a\nperson's `email`, a project's `stage`).", - "de": "Plugins können jetzt **eigene Feldtypen** zusätzlich zu den 16 eingebauten Feldern deklarieren.\nEin Plugin gibt sie in einer `[fields]`-Tabelle an (je mit Typ — `text`/`longtext`/`date`/`enum` —\noptionalem `on`-Typ-Geltungsbereich, `required` und einem Anzeige-`label`); gesetzt werden sie mit\n`nxf create`/`nxf update --set =` (typgeprüft; ein leerer Wert löscht). Sie erscheinen\nüberall dort, wo gelesen wird: `nxf schema --json` listet jedes deklarierte Feld auf (mit `custom`\nmarkiert, samt `kind`/`on`/`required`/`label` und `values` bei einem Enum), und\n`nxf show`/`list`/`next --json` hängen eine schlanke `custom`-Map mit den gesetzten Werten an (ein\nnicht gesetztes Feld — oder eines aus einem anderen Plugin — wird schlicht weggelassen). Die\nMCP-Tools `flow_schema`/`flow_show`/`flow_list`/`flow_next` liefern dieselbe Struktur. Die\nmenschenlesbare Ansicht von `nxf show` zeigt deklarierte Felder ebenfalls unter ihrem Label. Damit\nentfällt das Zweckentfremden von `labels`/`description` für Fachdaten (die `uri` einer Datei, die\n`email` einer Person, die `stage` eines Projekts).", - "facade": "breaking" - } - ], - "notes": { - "en": "### Added\n- Plugins can now declare their own **custom item fields** on top of the 16 built-in ones. A plugin\ndeclares them in a `[fields]` table (each with a type — `text`/`longtext`/`date`/`enum` — an\noptional `on` type scope, `required`, and a display `label`); you then set them with\n`nxf create`/`nxf update --set =` (type-validated; an empty value clears). They surface\neverywhere reads do: `nxf schema --json` lists each declared field (marked `custom`, with its\n`kind`/`on`/`required`/`label`, and `values` for an enum), and `nxf show`/`list`/`next --json`\nattach a sparse `custom` map with the item's set values (a field that is unset — or belongs to a\ndifferent plugin — is simply omitted). The MCP `flow_schema`/`flow_show`/`flow_list`/`flow_next`\ntools carry the identical shape. `nxf show`'s human view also prints declared custom fields under\ntheir label. This replaces overloading `labels`/`description` for domain data (a file's `uri`, a\nperson's `email`, a project's `stage`).\n\n### Facade Contract\n- `breaking` · Plugins can now declare their own **custom item fields** on top of the 16 built-in ones. A plugin\ndeclares them in a `[fields]` table (each with a type — `text`/`longtext`/`date`/`enum` — an\noptional `on` type scope, `required`, and a display `label`); you then set them with\n`nxf create`/`nxf update --set =` (type-validated; an empty value clears). They surface\neverywhere reads do: `nxf schema --json` lists each declared field (marked `custom`, with its\n`kind`/`on`/`required`/`label`, and `values` for an enum), and `nxf show`/`list`/`next --json`\nattach a sparse `custom` map with the item's set values (a field that is unset — or belongs to a\ndifferent plugin — is simply omitted). The MCP `flow_schema`/`flow_show`/`flow_list`/`flow_next`\ntools carry the identical shape. `nxf show`'s human view also prints declared custom fields under\ntheir label. This replaces overloading `labels`/`description` for domain data (a file's `uri`, a\nperson's `email`, a project's `stage`).", - "de": "### Neu\n- Plugins können jetzt **eigene Feldtypen** zusätzlich zu den 16 eingebauten Feldern deklarieren.\nEin Plugin gibt sie in einer `[fields]`-Tabelle an (je mit Typ — `text`/`longtext`/`date`/`enum` —\noptionalem `on`-Typ-Geltungsbereich, `required` und einem Anzeige-`label`); gesetzt werden sie mit\n`nxf create`/`nxf update --set =` (typgeprüft; ein leerer Wert löscht). Sie erscheinen\nüberall dort, wo gelesen wird: `nxf schema --json` listet jedes deklarierte Feld auf (mit `custom`\nmarkiert, samt `kind`/`on`/`required`/`label` und `values` bei einem Enum), und\n`nxf show`/`list`/`next --json` hängen eine schlanke `custom`-Map mit den gesetzten Werten an (ein\nnicht gesetztes Feld — oder eines aus einem anderen Plugin — wird schlicht weggelassen). Die\nMCP-Tools `flow_schema`/`flow_show`/`flow_list`/`flow_next` liefern dieselbe Struktur. Die\nmenschenlesbare Ansicht von `nxf show` zeigt deklarierte Felder ebenfalls unter ihrem Label. Damit\nentfällt das Zweckentfremden von `labels`/`description` für Fachdaten (die `uri` einer Datei, die\n`email` einer Person, die `stage` eines Projekts).\n\n### Facade-Kontrakt\n- `breaking` · Plugins können jetzt **eigene Feldtypen** zusätzlich zu den 16 eingebauten Feldern deklarieren.\nEin Plugin gibt sie in einer `[fields]`-Tabelle an (je mit Typ — `text`/`longtext`/`date`/`enum` —\noptionalem `on`-Typ-Geltungsbereich, `required` und einem Anzeige-`label`); gesetzt werden sie mit\n`nxf create`/`nxf update --set =` (typgeprüft; ein leerer Wert löscht). Sie erscheinen\nüberall dort, wo gelesen wird: `nxf schema --json` listet jedes deklarierte Feld auf (mit `custom`\nmarkiert, samt `kind`/`on`/`required`/`label` und `values` bei einem Enum), und\n`nxf show`/`list`/`next --json` hängen eine schlanke `custom`-Map mit den gesetzten Werten an (ein\nnicht gesetztes Feld — oder eines aus einem anderen Plugin — wird schlicht weggelassen). Die\nMCP-Tools `flow_schema`/`flow_show`/`flow_list`/`flow_next` liefern dieselbe Struktur. Die\nmenschenlesbare Ansicht von `nxf show` zeigt deklarierte Felder ebenfalls unter ihrem Label. Damit\nentfällt das Zweckentfremden von `labels`/`description` für Fachdaten (die `uri` einer Datei, die\n`email` einer Person, die `stage` eines Projekts)." - } - }, - { - "version": "0.24.2", - "date": "2026-07-12", - "items": [ - { - "type": "fixed", - "en": "Opening a freshly-created workspace database from two connections at once — e.g. `nxs init` / `nxf\ninit` over an existing workspace (or an E4 sync reset) while a long-lived app is watching it — no\nlonger intermittently fails with \"duplicate column name: domain\". The schema migration now runs\nunder a write lock, so concurrent openers serialize instead of both applying the same `ALTER`.", - "de": "Das gleichzeitige Öffnen einer frisch erstellten Workspace-Datenbank durch zwei Verbindungen — z. B.\n`nxs init` / `nxf init` über einem bestehenden Workspace (oder ein E4-Sync-Reset), während eine\nlaufende App sie beobachtet — schlägt nicht mehr sporadisch mit „duplicate column name: domain\"\nfehl. Die Schema-Migration läuft jetzt unter einem Schreib-Lock, sodass gleichzeitige Öffner\nserialisiert werden, statt beide dasselbe `ALTER` auszuführen." - }, - { - "type": "fixed", - "en": "The \"your first move\" suggestion after `nxs init` / `nxf init` now names a type the active plugin\nactually declares (e.g. `epic` for issue-tracker, `project` for personal-todo), so the copy-pasted\nfirst command works. Previously it hardcoded `--type task`, which no bundled plugin accepts — the\nvery first suggested command errored on a stock install.", - "de": "Der Vorschlag „your first move\" nach `nxs init` / `nxf init` nennt jetzt einen Typ, den das aktive\nPlugin tatsächlich deklariert (z. B. `epic` bei issue-tracker, `project` bei personal-todo) — der\nkopierte erste Befehl funktioniert also. Zuvor war `--type task` fest verdrahtet, das kein\nmitgeliefertes Plugin kennt: Der allererste vorgeschlagene Befehl schlug auf einer frischen\nInstallation fehl.", - "facade": "changed" - }, - { - "type": "fixed", - "en": "The ready lane (`next`) is dramatically faster on larger boards. Resolving the ready set no longer\nissues one query per item — each of which re-materialised the parent projection, making the lane\nO(n·edges) — it now bulk-resolves in a single query. On a ~156-item board this cuts `read::next`\nfrom ~0.7 s to well under 100 ms, speeding every board refresh and session start (`nxf next`,\n`nxs prime`).", - "de": "Die Bereit-Lane (`next`) ist auf größeren Boards drastisch schneller. Das Auflösen der Bereit-Menge\nstellt nicht mehr eine Abfrage pro Item (die je die Parent-Projektion neu materialisierte und die\nLane O(n·edges) machte), sondern löst in einer einzigen Abfrage auf. Auf einem ~156-Item-Board sinkt\n`read::next` damit von ~0,7 s auf deutlich unter 100 ms — das beschleunigt jeden Board-Refresh und\nSession-Start (`nxf next`, `nxs prime`)." - } - ], - "notes": { - "en": "### Fixed\n- Opening a freshly-created workspace database from two connections at once — e.g. `nxs init` / `nxf\ninit` over an existing workspace (or an E4 sync reset) while a long-lived app is watching it — no\nlonger intermittently fails with \"duplicate column name: domain\". The schema migration now runs\nunder a write lock, so concurrent openers serialize instead of both applying the same `ALTER`.\n- The \"your first move\" suggestion after `nxs init` / `nxf init` now names a type the active plugin\nactually declares (e.g. `epic` for issue-tracker, `project` for personal-todo), so the copy-pasted\nfirst command works. Previously it hardcoded `--type task`, which no bundled plugin accepts — the\nvery first suggested command errored on a stock install.\n- The ready lane (`next`) is dramatically faster on larger boards. Resolving the ready set no longer\nissues one query per item — each of which re-materialised the parent projection, making the lane\nO(n·edges) — it now bulk-resolves in a single query. On a ~156-item board this cuts `read::next`\nfrom ~0.7 s to well under 100 ms, speeding every board refresh and session start (`nxf next`,\n`nxs prime`).\n\n### Facade Contract\n- `changed` · The \"your first move\" suggestion after `nxs init` / `nxf init` now names a type the active plugin\nactually declares (e.g. `epic` for issue-tracker, `project` for personal-todo), so the copy-pasted\nfirst command works. Previously it hardcoded `--type task`, which no bundled plugin accepts — the\nvery first suggested command errored on a stock install.", - "de": "### Behoben\n- Das gleichzeitige Öffnen einer frisch erstellten Workspace-Datenbank durch zwei Verbindungen — z. B.\n`nxs init` / `nxf init` über einem bestehenden Workspace (oder ein E4-Sync-Reset), während eine\nlaufende App sie beobachtet — schlägt nicht mehr sporadisch mit „duplicate column name: domain\"\nfehl. Die Schema-Migration läuft jetzt unter einem Schreib-Lock, sodass gleichzeitige Öffner\nserialisiert werden, statt beide dasselbe `ALTER` auszuführen.\n- Der Vorschlag „your first move\" nach `nxs init` / `nxf init` nennt jetzt einen Typ, den das aktive\nPlugin tatsächlich deklariert (z. B. `epic` bei issue-tracker, `project` bei personal-todo) — der\nkopierte erste Befehl funktioniert also. Zuvor war `--type task` fest verdrahtet, das kein\nmitgeliefertes Plugin kennt: Der allererste vorgeschlagene Befehl schlug auf einer frischen\nInstallation fehl.\n- Die Bereit-Lane (`next`) ist auf größeren Boards drastisch schneller. Das Auflösen der Bereit-Menge\nstellt nicht mehr eine Abfrage pro Item (die je die Parent-Projektion neu materialisierte und die\nLane O(n·edges) machte), sondern löst in einer einzigen Abfrage auf. Auf einem ~156-Item-Board sinkt\n`read::next` damit von ~0,7 s auf deutlich unter 100 ms — das beschleunigt jeden Board-Refresh und\nSession-Start (`nxf next`, `nxs prime`).\n\n### Facade-Kontrakt\n- `changed` · Der Vorschlag „your first move\" nach `nxs init` / `nxf init` nennt jetzt einen Typ, den das aktive\nPlugin tatsächlich deklariert (z. B. `epic` bei issue-tracker, `project` bei personal-todo) — der\nkopierte erste Befehl funktioniert also. Zuvor war `--type task` fest verdrahtet, das kein\nmitgeliefertes Plugin kennt: Der allererste vorgeschlagene Befehl schlug auf einer frischen\nInstallation fehl." - } - }, - { - "version": "0.24.1", - "date": "2026-07-12", - "items": [ - { - "type": "fixed", - "en": "The `nxc inbox` (and the inbox section of `nxc prime`) now displays unread messages in deterministic\ncausal order. Following the message-ordering fix, the inbox's own display order was still keyed on\nthe raw ULID `message_id`, so two unread messages posted in the same millisecond could show in an\narbitrary, per-database order. It now orders by the CRDT clock `(lamport, site, message_id)` — the\nsame guarantee the channel, thread and search reads already give — while the unread cursor keeps its\nown `message_id` watermark unchanged.", - "de": "`nxc inbox` (und der Inbox-Abschnitt von `nxc prime`) zeigt ungelesene Nachrichten jetzt in\ndeterministischer, kausaler Reihenfolge an. Nach dem Fix der Nachrichten-Ordnung war die\nAnzeige-Reihenfolge der Inbox selbst noch an die rohe ULID-`message_id` gebunden, sodass zwei in\nderselben Millisekunde gepostete ungelesene Nachrichten in beliebiger, pro-Datenbank\nunterschiedlicher Reihenfolge erscheinen konnten. Sie sortiert nun nach der CRDT-Uhr\n`(lamport, site, message_id)` — dieselbe Garantie wie Kanal-, Thread- und Suchlesungen — während der\nUngelesen-Cursor seine eigene `message_id`-Wasserstandsmarke unverändert behält." - }, - { - "type": "fixed", - "en": "Chat messages now read back in a deterministic, causal order. Reads (channel history, thread\nassembly, search, and the M2 review board) are ordered by the CRDT clock `(lamport, site,\nmessage_id)` instead of the raw `message_id`. A `message_id` is `m-`+ULID (a millisecond timestamp\nplus random bits), so two messages posted in the same millisecond — an `ask` opener and its first\nreply, or a rapid send/reply burst — previously sorted by ULID randomness, i.e. in an arbitrary,\nper-database order. Lamport is the substrate's logical clock (a reply's op is emitted after the\nrequest it answers), so the read order now always matches post order and is reproducible across runs\nand across sites/merges.", - "de": "Chat-Nachrichten werden jetzt in einer deterministischen, kausalen Reihenfolge gelesen. Die Reads\n(Kanalverlauf, Thread-Zusammenbau, Suche und das M2-Review-Board) sind nach der CRDT-Uhr\n`(lamport, site, message_id)` sortiert statt nach der rohen `message_id`. Eine `message_id` ist\n`m-`+ULID (ein Millisekunden-Zeitstempel plus Zufallsbits); zwei in derselben Millisekunde gepostete\nNachrichten — ein `ask`-Opener und seine erste Antwort oder ein schneller Send/Reply-Burst —\nsortierten sich vorher nach ULID-Zufall, also in einer beliebigen, pro-Datenbank\nunterschiedlichen Reihenfolge. Lamport ist die logische Uhr des Substrats (die Op einer Antwort\nwird nach der beantworteten Anfrage emittiert), sodass die Lesereihenfolge jetzt stets der\nPost-Reihenfolge entspricht und über Läufe sowie Sites/Merges hinweg reproduzierbar ist." - } - ], - "notes": { - "en": "### Fixed\n- The `nxc inbox` (and the inbox section of `nxc prime`) now displays unread messages in deterministic\ncausal order. Following the message-ordering fix, the inbox's own display order was still keyed on\nthe raw ULID `message_id`, so two unread messages posted in the same millisecond could show in an\narbitrary, per-database order. It now orders by the CRDT clock `(lamport, site, message_id)` — the\nsame guarantee the channel, thread and search reads already give — while the unread cursor keeps its\nown `message_id` watermark unchanged.\n- Chat messages now read back in a deterministic, causal order. Reads (channel history, thread\nassembly, search, and the M2 review board) are ordered by the CRDT clock `(lamport, site,\nmessage_id)` instead of the raw `message_id`. A `message_id` is `m-`+ULID (a millisecond timestamp\nplus random bits), so two messages posted in the same millisecond — an `ask` opener and its first\nreply, or a rapid send/reply burst — previously sorted by ULID randomness, i.e. in an arbitrary,\nper-database order. Lamport is the substrate's logical clock (a reply's op is emitted after the\nrequest it answers), so the read order now always matches post order and is reproducible across runs\nand across sites/merges.", - "de": "### Behoben\n- `nxc inbox` (und der Inbox-Abschnitt von `nxc prime`) zeigt ungelesene Nachrichten jetzt in\ndeterministischer, kausaler Reihenfolge an. Nach dem Fix der Nachrichten-Ordnung war die\nAnzeige-Reihenfolge der Inbox selbst noch an die rohe ULID-`message_id` gebunden, sodass zwei in\nderselben Millisekunde gepostete ungelesene Nachrichten in beliebiger, pro-Datenbank\nunterschiedlicher Reihenfolge erscheinen konnten. Sie sortiert nun nach der CRDT-Uhr\n`(lamport, site, message_id)` — dieselbe Garantie wie Kanal-, Thread- und Suchlesungen — während der\nUngelesen-Cursor seine eigene `message_id`-Wasserstandsmarke unverändert behält.\n- Chat-Nachrichten werden jetzt in einer deterministischen, kausalen Reihenfolge gelesen. Die Reads\n(Kanalverlauf, Thread-Zusammenbau, Suche und das M2-Review-Board) sind nach der CRDT-Uhr\n`(lamport, site, message_id)` sortiert statt nach der rohen `message_id`. Eine `message_id` ist\n`m-`+ULID (ein Millisekunden-Zeitstempel plus Zufallsbits); zwei in derselben Millisekunde gepostete\nNachrichten — ein `ask`-Opener und seine erste Antwort oder ein schneller Send/Reply-Burst —\nsortierten sich vorher nach ULID-Zufall, also in einer beliebigen, pro-Datenbank\nunterschiedlichen Reihenfolge. Lamport ist die logische Uhr des Substrats (die Op einer Antwort\nwird nach der beantworteten Anfrage emittiert), sodass die Lesereihenfolge jetzt stets der\nPost-Reihenfolge entspricht und über Läufe sowie Sites/Merges hinweg reproduzierbar ist." - } - }, - { - "version": "0.24.0", - "date": "2026-07-11", - "items": [ - { - "type": "added", - "en": "Added a bulk labels read to the embedding API — `Engine::labels_bulk(ids)` (and `read::labels_bulk`,\n`Store::labels_of_bulk`) — that returns the present labels for many items in a single indexed query\ninstead of one `labels(id)` call per item. Decorating a whole lane (the app-bridge `with_labels`\nseam) now costs one query rather than N. The `list`/`next --json` label projection uses it\ninternally, so those lane reads collapse their per-row label lookups to one bulk read while the\noutput stays byte-identical. The existing `labels(id)` is unchanged; the new method is additive and\nbackward-compatible.", - "de": "Einen Bulk-Labels-Lesepfad zur Embedding-API hinzugefügt — `Engine::labels_bulk(ids)` (sowie\n`read::labels_bulk`, `Store::labels_of_bulk`) —, der die aktiven Labels vieler Items in EINER\nindexgestützten Abfrage liefert statt eines `labels(id)`-Aufrufs pro Item. Das Dekorieren einer\nganzen Lane (die App-Bridge-`with_labels`-Naht) kostet jetzt eine Abfrage statt N. Die\nLabel-Projektion von `list`/`next --json` nutzt ihn intern, sodass diese Lane-Reads ihre\nLabel-Lookups pro Zeile auf einen Bulk-Read zusammenfassen — bei bytegleicher Ausgabe. Das\nbestehende `labels(id)` bleibt unverändert; die neue Methode ist additiv und abwärtskompatibel.", - "facade": "changed" - }, - { - "type": "added", - "en": "nexus-chat now gates its `nxc` CLI/onboarding surface behind a default-on `cli` Cargo feature. An\nembedding consumer that links only `nexus_chat::engine::Engine` can build with\n`default-features = false` and drop the whole CLI/TUI dependency closure — clap, nxs-ui (and its\ninquire/crossterm), nxs-init, inventory, sha2 — leaving just the engine/facade/store/model/watch\nsurface over the shared substrate. The `nxs` binary and `nxc` are unchanged (the feature is on by\ndefault).", - "de": "nexus-chat kapselt seine `nxc`-CLI/Onboarding-Oberfläche jetzt hinter einem standardmäßig aktiven\n`cli`-Cargo-Feature. Ein Embedding-Consumer, der nur `nexus_chat::engine::Engine` einbindet, kann\nmit `default-features = false` bauen und den gesamten CLI/TUI-Abhängigkeitsbaum — clap, nxs-ui\n(samt inquire/crossterm), nxs-init, inventory, sha2 — weglassen; es bleibt die\nengine/facade/store/model/watch-Oberfläche über dem gemeinsamen Substrat. Das `nxs`-Binary und\n`nxc` bleiben unverändert (das Feature ist standardmäßig an)." - } - ], - "notes": { - "en": "### Added\n- Added a bulk labels read to the embedding API — `Engine::labels_bulk(ids)` (and `read::labels_bulk`,\n`Store::labels_of_bulk`) — that returns the present labels for many items in a single indexed query\ninstead of one `labels(id)` call per item. Decorating a whole lane (the app-bridge `with_labels`\nseam) now costs one query rather than N. The `list`/`next --json` label projection uses it\ninternally, so those lane reads collapse their per-row label lookups to one bulk read while the\noutput stays byte-identical. The existing `labels(id)` is unchanged; the new method is additive and\nbackward-compatible.\n- nexus-chat now gates its `nxc` CLI/onboarding surface behind a default-on `cli` Cargo feature. An\nembedding consumer that links only `nexus_chat::engine::Engine` can build with\n`default-features = false` and drop the whole CLI/TUI dependency closure — clap, nxs-ui (and its\ninquire/crossterm), nxs-init, inventory, sha2 — leaving just the engine/facade/store/model/watch\nsurface over the shared substrate. The `nxs` binary and `nxc` are unchanged (the feature is on by\ndefault).\n\n### Facade Contract\n- `changed` · Added a bulk labels read to the embedding API — `Engine::labels_bulk(ids)` (and `read::labels_bulk`,\n`Store::labels_of_bulk`) — that returns the present labels for many items in a single indexed query\ninstead of one `labels(id)` call per item. Decorating a whole lane (the app-bridge `with_labels`\nseam) now costs one query rather than N. The `list`/`next --json` label projection uses it\ninternally, so those lane reads collapse their per-row label lookups to one bulk read while the\noutput stays byte-identical. The existing `labels(id)` is unchanged; the new method is additive and\nbackward-compatible.", - "de": "### Neu\n- Einen Bulk-Labels-Lesepfad zur Embedding-API hinzugefügt — `Engine::labels_bulk(ids)` (sowie\n`read::labels_bulk`, `Store::labels_of_bulk`) —, der die aktiven Labels vieler Items in EINER\nindexgestützten Abfrage liefert statt eines `labels(id)`-Aufrufs pro Item. Das Dekorieren einer\nganzen Lane (die App-Bridge-`with_labels`-Naht) kostet jetzt eine Abfrage statt N. Die\nLabel-Projektion von `list`/`next --json` nutzt ihn intern, sodass diese Lane-Reads ihre\nLabel-Lookups pro Zeile auf einen Bulk-Read zusammenfassen — bei bytegleicher Ausgabe. Das\nbestehende `labels(id)` bleibt unverändert; die neue Methode ist additiv und abwärtskompatibel.\n- nexus-chat kapselt seine `nxc`-CLI/Onboarding-Oberfläche jetzt hinter einem standardmäßig aktiven\n`cli`-Cargo-Feature. Ein Embedding-Consumer, der nur `nexus_chat::engine::Engine` einbindet, kann\nmit `default-features = false` bauen und den gesamten CLI/TUI-Abhängigkeitsbaum — clap, nxs-ui\n(samt inquire/crossterm), nxs-init, inventory, sha2 — weglassen; es bleibt die\nengine/facade/store/model/watch-Oberfläche über dem gemeinsamen Substrat. Das `nxs`-Binary und\n`nxc` bleiben unverändert (das Feature ist standardmäßig an).\n\n### Facade-Kontrakt\n- `changed` · Einen Bulk-Labels-Lesepfad zur Embedding-API hinzugefügt — `Engine::labels_bulk(ids)` (sowie\n`read::labels_bulk`, `Store::labels_of_bulk`) —, der die aktiven Labels vieler Items in EINER\nindexgestützten Abfrage liefert statt eines `labels(id)`-Aufrufs pro Item. Das Dekorieren einer\nganzen Lane (die App-Bridge-`with_labels`-Naht) kostet jetzt eine Abfrage statt N. Die\nLabel-Projektion von `list`/`next --json` nutzt ihn intern, sodass diese Lane-Reads ihre\nLabel-Lookups pro Zeile auf einen Bulk-Read zusammenfassen — bei bytegleicher Ausgabe. Das\nbestehende `labels(id)` bleibt unverändert; die neue Methode ist additiv und abwärtskompatibel." - } - }, - { - "version": "0.23.1", - "date": "2026-07-11", - "items": [ - { - "type": "added", - "en": "When every requested reviewer has replied, the thread's opener is now woken: `nxc inbox` and the `nxs prime` catch-up surface a \"Threads you opened\" block with the completed board and its replies (and a note when a board is past its deadline but still waiting). Your own sent messages no longer show up in your own unread.", - "de": "Sobald alle angefragten Reviewer geantwortet haben, wird der Thread-Ersteller geweckt: `nxc inbox` und der `nxs prime`-Catch-up zeigen einen \"Threads you opened\"-Block mit dem fertigen Board und seinen Antworten (und einen Hinweis, wenn ein Board seine Frist überschritten hat, aber noch wartet). Eigene gesendete Nachrichten erscheinen nicht mehr im eigenen Ungelesen-Stand." - }, - { - "type": "added", - "en": "`nxc ask` opens a review board in one step — post a request, declare who must reply, and set an optional deadline. `nxc threads list`/`show` render the reply quorum (who has replied, who is still outstanding, whether the board is complete), and `nxc threads expect` re-declares the reviewer set to unstick a board waiting on a non-responder.", - "de": "`nxc ask` öffnet ein Review-Board in einem Schritt — Anfrage posten, festlegen wer antworten muss, optionale Frist setzen. `nxc threads list`/`show` zeigen das Antwort-Quorum (wer geantwortet hat, wer noch aussteht, ob das Board vollständig ist), und `nxc threads expect` deklariert den Reviewer-Kreis neu, um ein auf einen Nicht-Antwortenden wartendes Board flottzumachen." - }, - { - "type": "added", - "en": "Applications embedding nexus-chat can now read the reply quorum through the in-process engine, not\njust the CLI: a bulk read returns the per-reviewer state for a whole set of boards at once (for a\ncoordination-UI quorum bar, no N+1), a single board in full, and the opener's completed/overdue\nwake — all identical to what `nxc` reports. The `subscribe` change feed lets the UI re-read the\nquorum and surface \"complete\"/\"past deadline\" transitions live.", - "de": "Anwendungen, die nexus-chat einbetten, können das Antwort-Quorum jetzt über die In-Process-Engine\nlesen, nicht nur über die CLI: ein Bulk-Read liefert den Reviewer-Stand für eine ganze Menge von\nBoards auf einmal (für einen Koordinations-UI-Quorum-Balken, kein N+1), ein einzelnes Board\nvollständig, sowie die Fertig-/Überfällig-Weckliste des Anfragenden — alles identisch zu dem, was\n`nxc` meldet. Über den `subscribe`-Änderungsstrom kann die UI das Quorum neu lesen und\n\"vollständig\"/\"überfällig\"-Übergänge live anzeigen." - } - ], - "notes": { - "en": "### Added\n- When every requested reviewer has replied, the thread's opener is now woken: `nxc inbox` and the `nxs prime` catch-up surface a \"Threads you opened\" block with the completed board and its replies (and a note when a board is past its deadline but still waiting). Your own sent messages no longer show up in your own unread.\n- `nxc ask` opens a review board in one step — post a request, declare who must reply, and set an optional deadline. `nxc threads list`/`show` render the reply quorum (who has replied, who is still outstanding, whether the board is complete), and `nxc threads expect` re-declares the reviewer set to unstick a board waiting on a non-responder.\n- Applications embedding nexus-chat can now read the reply quorum through the in-process engine, not\njust the CLI: a bulk read returns the per-reviewer state for a whole set of boards at once (for a\ncoordination-UI quorum bar, no N+1), a single board in full, and the opener's completed/overdue\nwake — all identical to what `nxc` reports. The `subscribe` change feed lets the UI re-read the\nquorum and surface \"complete\"/\"past deadline\" transitions live.", - "de": "### Neu\n- Sobald alle angefragten Reviewer geantwortet haben, wird der Thread-Ersteller geweckt: `nxc inbox` und der `nxs prime`-Catch-up zeigen einen \"Threads you opened\"-Block mit dem fertigen Board und seinen Antworten (und einen Hinweis, wenn ein Board seine Frist überschritten hat, aber noch wartet). Eigene gesendete Nachrichten erscheinen nicht mehr im eigenen Ungelesen-Stand.\n- `nxc ask` öffnet ein Review-Board in einem Schritt — Anfrage posten, festlegen wer antworten muss, optionale Frist setzen. `nxc threads list`/`show` zeigen das Antwort-Quorum (wer geantwortet hat, wer noch aussteht, ob das Board vollständig ist), und `nxc threads expect` deklariert den Reviewer-Kreis neu, um ein auf einen Nicht-Antwortenden wartendes Board flottzumachen.\n- Anwendungen, die nexus-chat einbetten, können das Antwort-Quorum jetzt über die In-Process-Engine\nlesen, nicht nur über die CLI: ein Bulk-Read liefert den Reviewer-Stand für eine ganze Menge von\nBoards auf einmal (für einen Koordinations-UI-Quorum-Balken, kein N+1), ein einzelnes Board\nvollständig, sowie die Fertig-/Überfällig-Weckliste des Anfragenden — alles identisch zu dem, was\n`nxc` meldet. Über den `subscribe`-Änderungsstrom kann die UI das Quorum neu lesen und\n\"vollständig\"/\"überfällig\"-Übergänge live anzeigen." - } - }, - { - "version": "0.22.0", - "date": "2026-07-11", - "items": [ - { - "type": "fixed", - "en": "**`nxs init` now works for the chat module on a real install.** The `nxc` persona symlink was never\ncreated by the production install paths (`install.sh` and `nxs self-update`) — only by in-tree dev\nbuilds — so a fresh install would fail with \"is `nxc` installed?\" when setting up chat. All three\npersonas (`nxf`, `nxm`, `nxc`) are now linked on install and self-update, and a regression test pins\nthe full set so a future persona can't silently repeat the gap.", - "de": "**`nxs init` funktioniert jetzt für das Chat-Modul bei einer echten Installation.** Der\n`nxc`-Persona-Symlink wurde von den Produktions-Installationspfaden (`install.sh` und\n`nxs self-update`) nie angelegt — nur von In-Tree-Dev-Builds — sodass eine frische Installation beim\nEinrichten von Chat mit „is `nxc` installed?\" fehlschlug. Alle drei Personas (`nxf`, `nxm`, `nxc`)\nwerden jetzt bei Installation und Self-Update verlinkt, und ein Regressionstest fixiert das\nvollständige Set, damit eine künftige Persona die Lücke nicht stillschweigend wiederholt." - }, - { - "type": "changed", - "en": "**nexus-chat app-facade refinements.** Channel reads are now genuinely a fixed two SQLite queries\nregardless of channel count (the member-list read was collapsed from a per-channel loop into a\nsingle query). Store reads now surface database errors as a structured `io` error instead of\npanicking the embedded engine. And `send`/`reply` take named-field request structs\n(`SendRequest`/`ReplyRequest`) instead of a long positional argument list, so callers can't transpose\nsame-typed arguments.", - "de": "**Verfeinerungen der nexus-chat-App-Facade.** Channel-Abfragen sind jetzt tatsächlich fixe zwei\nSQLite-Queries, unabhängig von der Anzahl der Channels (die Mitglieder-Liste wurde von einer\nPro-Channel-Schleife auf eine einzige Query zusammengefasst). Store-Lesezugriffe liefern\nDatenbankfehler nun als strukturierten `io`-Fehler, statt die eingebettete Engine abstürzen zu\nlassen. Und `send`/`reply` nehmen benannte Request-Structs (`SendRequest`/`ReplyRequest`) statt einer\nlangen Positionsargument-Liste, sodass gleich-typisierte Argumente nicht mehr vertauscht werden\nkönnen." - }, - { - "type": "added", - "en": "**MCP hosts can now discover the active plugin's item types and their containment roles.** A new\n`flow_schema` MCP tool reports the valid item types, which of them are containers (and each type's\nallowed parents, parent cardinality, and the hierarchy depth limit), and the create/update field\nmodel — so an agent driving a board over MCP learns what to create without guessing. `nxf schema`\ngains the same information as an additive `hierarchy` block, and both surfaces now render from one\nshared record (`facade::read::schema`) so they cannot drift.", - "de": "**MCP-Hosts können jetzt die Item-Typen des aktiven Plugins und ihre Container-Rollen entdecken.** Ein\nneues `flow_schema`-MCP-Tool nennt die gültigen Item-Typen, welche davon Container sind (samt der\nerlaubten Eltern je Typ, der Eltern-Kardinalität und der Hierarchie-Tiefengrenze) sowie das\nCreate/Update-Feldmodell — damit ein Agent, der ein Board über MCP steuert, ohne Raten weiß, was er\nanlegen kann. `nxf schema` erhält dieselbe Information als additiven `hierarchy`-Block, und beide\nFlächen rendern jetzt aus einem gemeinsamen Record (`facade::read::schema`), sodass sie nicht\nauseinanderlaufen können.", - "facade": "changed" - } - ], - "notes": { - "en": "### Added\n- **MCP hosts can now discover the active plugin's item types and their containment roles.** A new\n`flow_schema` MCP tool reports the valid item types, which of them are containers (and each type's\nallowed parents, parent cardinality, and the hierarchy depth limit), and the create/update field\nmodel — so an agent driving a board over MCP learns what to create without guessing. `nxf schema`\ngains the same information as an additive `hierarchy` block, and both surfaces now render from one\nshared record (`facade::read::schema`) so they cannot drift.\n\n### Changed\n- **nexus-chat app-facade refinements.** Channel reads are now genuinely a fixed two SQLite queries\nregardless of channel count (the member-list read was collapsed from a per-channel loop into a\nsingle query). Store reads now surface database errors as a structured `io` error instead of\npanicking the embedded engine. And `send`/`reply` take named-field request structs\n(`SendRequest`/`ReplyRequest`) instead of a long positional argument list, so callers can't transpose\nsame-typed arguments.\n\n### Fixed\n- **`nxs init` now works for the chat module on a real install.** The `nxc` persona symlink was never\ncreated by the production install paths (`install.sh` and `nxs self-update`) — only by in-tree dev\nbuilds — so a fresh install would fail with \"is `nxc` installed?\" when setting up chat. All three\npersonas (`nxf`, `nxm`, `nxc`) are now linked on install and self-update, and a regression test pins\nthe full set so a future persona can't silently repeat the gap.\n\n### Facade Contract\n- `changed` · **MCP hosts can now discover the active plugin's item types and their containment roles.** A new\n`flow_schema` MCP tool reports the valid item types, which of them are containers (and each type's\nallowed parents, parent cardinality, and the hierarchy depth limit), and the create/update field\nmodel — so an agent driving a board over MCP learns what to create without guessing. `nxf schema`\ngains the same information as an additive `hierarchy` block, and both surfaces now render from one\nshared record (`facade::read::schema`) so they cannot drift.", - "de": "### Neu\n- **MCP-Hosts können jetzt die Item-Typen des aktiven Plugins und ihre Container-Rollen entdecken.** Ein\nneues `flow_schema`-MCP-Tool nennt die gültigen Item-Typen, welche davon Container sind (samt der\nerlaubten Eltern je Typ, der Eltern-Kardinalität und der Hierarchie-Tiefengrenze) sowie das\nCreate/Update-Feldmodell — damit ein Agent, der ein Board über MCP steuert, ohne Raten weiß, was er\nanlegen kann. `nxf schema` erhält dieselbe Information als additiven `hierarchy`-Block, und beide\nFlächen rendern jetzt aus einem gemeinsamen Record (`facade::read::schema`), sodass sie nicht\nauseinanderlaufen können.\n\n### Geändert\n- **Verfeinerungen der nexus-chat-App-Facade.** Channel-Abfragen sind jetzt tatsächlich fixe zwei\nSQLite-Queries, unabhängig von der Anzahl der Channels (die Mitglieder-Liste wurde von einer\nPro-Channel-Schleife auf eine einzige Query zusammengefasst). Store-Lesezugriffe liefern\nDatenbankfehler nun als strukturierten `io`-Fehler, statt die eingebettete Engine abstürzen zu\nlassen. Und `send`/`reply` nehmen benannte Request-Structs (`SendRequest`/`ReplyRequest`) statt einer\nlangen Positionsargument-Liste, sodass gleich-typisierte Argumente nicht mehr vertauscht werden\nkönnen.\n\n### Behoben\n- **`nxs init` funktioniert jetzt für das Chat-Modul bei einer echten Installation.** Der\n`nxc`-Persona-Symlink wurde von den Produktions-Installationspfaden (`install.sh` und\n`nxs self-update`) nie angelegt — nur von In-Tree-Dev-Builds — sodass eine frische Installation beim\nEinrichten von Chat mit „is `nxc` installed?\" fehlschlug. Alle drei Personas (`nxf`, `nxm`, `nxc`)\nwerden jetzt bei Installation und Self-Update verlinkt, und ein Regressionstest fixiert das\nvollständige Set, damit eine künftige Persona die Lücke nicht stillschweigend wiederholt.\n\n### Facade-Kontrakt\n- `changed` · **MCP-Hosts können jetzt die Item-Typen des aktiven Plugins und ihre Container-Rollen entdecken.** Ein\nneues `flow_schema`-MCP-Tool nennt die gültigen Item-Typen, welche davon Container sind (samt der\nerlaubten Eltern je Typ, der Eltern-Kardinalität und der Hierarchie-Tiefengrenze) sowie das\nCreate/Update-Feldmodell — damit ein Agent, der ein Board über MCP steuert, ohne Raten weiß, was er\nanlegen kann. `nxf schema` erhält dieselbe Information als additiven `hierarchy`-Block, und beide\nFlächen rendern jetzt aus einem gemeinsamen Record (`facade::read::schema`), sodass sie nicht\nauseinanderlaufen können." - } - }, - { - "version": "0.21.0", - "date": "2026-07-10", - "items": [ - { - "type": "added", - "en": "**nexus-chat now embeds in apps.** A new in-process app-facade (`nexus_chat::engine::Engine`) lets a\nfrontend read channels, inbox, messages and threads, send/reply/mark-read, and subscribe to change\nnotifications through one long-lived handle — no `nxc` subprocess. It derives identically to the\n`nxc` CLI and returns the same errors/rejections (proven by an App↔CLI parity test), and channel\nreads carry per-channel unread counts in two queries regardless of channel count.", - "de": "**nexus-chat lässt sich jetzt in Apps einbetten.** Eine neue In-Process-App-Facade\n(`nexus_chat::engine::Engine`) erlaubt einem Frontend, Channels, Inbox, Nachrichten und Threads zu\nlesen, zu senden/antworten/als-gelesen-zu-markieren und Änderungs-Benachrichtigungen über ein\nlanglebiges Handle zu abonnieren — ohne `nxc`-Subprozess. Sie leitet identisch zur `nxc`-CLI ab und\nliefert dieselben Fehler/Rejections (abgesichert durch einen App↔CLI-Paritätstest); Channel-Abfragen\nliefern Unread-Zähler pro Channel in zwei Queries, unabhängig von der Anzahl der Channels." - } - ], - "notes": { - "en": "### Added\n- **nexus-chat now embeds in apps.** A new in-process app-facade (`nexus_chat::engine::Engine`) lets a\nfrontend read channels, inbox, messages and threads, send/reply/mark-read, and subscribe to change\nnotifications through one long-lived handle — no `nxc` subprocess. It derives identically to the\n`nxc` CLI and returns the same errors/rejections (proven by an App↔CLI parity test), and channel\nreads carry per-channel unread counts in two queries regardless of channel count.", - "de": "### Neu\n- **nexus-chat lässt sich jetzt in Apps einbetten.** Eine neue In-Process-App-Facade\n(`nexus_chat::engine::Engine`) erlaubt einem Frontend, Channels, Inbox, Nachrichten und Threads zu\nlesen, zu senden/antworten/als-gelesen-zu-markieren und Änderungs-Benachrichtigungen über ein\nlanglebiges Handle zu abonnieren — ohne `nxc`-Subprozess. Sie leitet identisch zur `nxc`-CLI ab und\nliefert dieselben Fehler/Rejections (abgesichert durch einen App↔CLI-Paritätstest); Channel-Abfragen\nliefern Unread-Zähler pro Channel in zwei Queries, unabhängig von der Anzahl der Channels." - } - }, - { - "version": "0.20.0", - "date": "2026-07-10", - "items": [ - { - "type": "changed", - "en": "**Large boards read much faster.** `next`, `list`, and `show` now use covering indexes for the\nper-item labels, notes, and dependency lookups they run once per row, instead of scanning the whole\nprojection table on every call. A 159-item board lane that took ~2.0s to decorate now renders in\n~0.6s, and the gain grows with board size. Applied automatically on the next workspace open — no\nmigration step.", - "de": "**Große Boards werden deutlich schneller gelesen.** `next`, `list` und `show` nutzen jetzt\nabdeckende Indizes für die Label-, Notiz- und Abhängigkeits-Lookups, die sie einmal pro Eintrag\nausführen, statt bei jedem Aufruf die gesamte Projektionstabelle zu durchsuchen. Eine Board-Spalte\nmit 159 Einträgen, deren Aufbereitung zuvor ~2,0s dauerte, wird jetzt in ~0,6s aufgebaut, und der\nGewinn wächst mit der Board-Größe. Wird beim nächsten Öffnen des Workspace automatisch angewendet —\nohne Migrationsschritt." - } - ], - "notes": { - "en": "### Changed\n- **Large boards read much faster.** `next`, `list`, and `show` now use covering indexes for the\nper-item labels, notes, and dependency lookups they run once per row, instead of scanning the whole\nprojection table on every call. A 159-item board lane that took ~2.0s to decorate now renders in\n~0.6s, and the gain grows with board size. Applied automatically on the next workspace open — no\nmigration step.", - "de": "### Geändert\n- **Große Boards werden deutlich schneller gelesen.** `next`, `list` und `show` nutzen jetzt\nabdeckende Indizes für die Label-, Notiz- und Abhängigkeits-Lookups, die sie einmal pro Eintrag\nausführen, statt bei jedem Aufruf die gesamte Projektionstabelle zu durchsuchen. Eine Board-Spalte\nmit 159 Einträgen, deren Aufbereitung zuvor ~2,0s dauerte, wird jetzt in ~0,6s aufgebaut, und der\nGewinn wächst mit der Board-Größe. Wird beim nächsten Öffnen des Workspace automatisch angewendet —\nohne Migrationsschritt." - } - }, - { - "version": "0.19.0", - "date": "2026-07-10", - "items": [ - { - "type": "added", - "en": "**nexus-chat (`nxc`) messages now travel across your machines.** With the M1 messaging substrate\ncomplete, an agent-to-agent message posted in one workspace reaches an agent on another machine\nafter `nxs sync` — messages ride the same offline-first sync as your tasks and durable memory, no\nextra service. The three engines share one `.nxs/` workspace while staying fully independent: your\nprojects (flow) and memory are byte-for-byte unaffected by chat traffic, and every message carries a\nglobally unique id so agents keep the same reference to it on every machine.", - "de": "**nexus-chat (`nxc`)-Nachrichten wandern jetzt über deine Maschinen hinweg.** Mit dem\nabgeschlossenen M1-Messaging-Substrat erreicht eine Agent-zu-Agent-Nachricht aus einem Workspace\nnach `nxs sync` einen Agenten auf einer anderen Maschine — Nachrichten nutzen dieselbe\noffline-first-Synchronisation wie deine Aufgaben und dein dauerhaftes Gedächtnis, ganz ohne\nZusatzdienst. Die drei Engines teilen sich einen `.nxs/`-Workspace und bleiben dabei völlig\nunabhängig: deine Projekte (flow) und dein Gedächtnis bleiben von Chat-Verkehr byte-für-byte\nunberührt, und jede Nachricht trägt eine global eindeutige id, sodass Agenten auf jeder Maschine\ndieselbe Referenz auf sie behalten." - } - ], - "notes": { - "en": "### Added\n- **nexus-chat (`nxc`) messages now travel across your machines.** With the M1 messaging substrate\ncomplete, an agent-to-agent message posted in one workspace reaches an agent on another machine\nafter `nxs sync` — messages ride the same offline-first sync as your tasks and durable memory, no\nextra service. The three engines share one `.nxs/` workspace while staying fully independent: your\nprojects (flow) and memory are byte-for-byte unaffected by chat traffic, and every message carries a\nglobally unique id so agents keep the same reference to it on every machine.", - "de": "### Neu\n- **nexus-chat (`nxc`)-Nachrichten wandern jetzt über deine Maschinen hinweg.** Mit dem\nabgeschlossenen M1-Messaging-Substrat erreicht eine Agent-zu-Agent-Nachricht aus einem Workspace\nnach `nxs sync` einen Agenten auf einer anderen Maschine — Nachrichten nutzen dieselbe\noffline-first-Synchronisation wie deine Aufgaben und dein dauerhaftes Gedächtnis, ganz ohne\nZusatzdienst. Die drei Engines teilen sich einen `.nxs/`-Workspace und bleiben dabei völlig\nunabhängig: deine Projekte (flow) und dein Gedächtnis bleiben von Chat-Verkehr byte-für-byte\nunberührt, und jede Nachricht trägt eine global eindeutige id, sodass Agenten auf jeder Maschine\ndieselbe Referenz auf sie behalten." - } - }, - { - "version": "0.18.0", - "date": "2026-07-10", - "items": [ - { - "type": "added", - "en": "**nexus-chat (`nxc`) is now discoverable and set up through the suite.** `nxc` — agent-to-agent\nmessaging — joins the `nxs init` chooser and, once active, the single SessionStart fan-out: `nxs\nprime` now catches an agent up on its **entire** unread inbox (both \"act now\" and \"next session\"\nmessages) at the start of each session. `nxc init` sets chat up in a workspace and wires the one\n`nxs prime` hook (joining an existing flow/memory workspace without adding a second hook), and `nxc\nagent-manifest` exposes chat's session contract as data. The suite cross-sell now runs in every\ndirection — setting up flow or memory points you at chat, and setting up chat points you at flow and\nmemory. Coordinate agents through `nxc` instead of ad-hoc notes.", - "de": "**nexus-chat (`nxc`) ist jetzt über die Suite auffindbar und einrichtbar.** `nxc` — Messaging\nzwischen Agenten — erscheint im `nxs init`-Auswahldialog und, sobald aktiv, im einzelnen\nSessionStart-Fan-out: `nxs prime` bringt einen Agenten zu Sitzungsbeginn auf den **gesamten**\nungelesenen Posteingang (sowohl „jetzt handeln“ als auch „nächste Sitzung“). `nxc init` richtet Chat\nin einem Workspace ein und verdrahtet den einen `nxs prime`-Hook (ein bestehender flow-/memory-\nWorkspace bekommt keinen zweiten Hook), und `nxc agent-manifest` legt Chats Sitzungskontrakt als\nDaten offen. Der Suite-Querverweis läuft jetzt in alle Richtungen — wer flow oder memory einrichtet,\nwird auf chat hingewiesen, und wer chat einrichtet, auf flow und memory. Koordiniere Agenten über\n`nxc` statt über Ad-hoc-Notizen." - } - ], - "notes": { - "en": "### Added\n- **nexus-chat (`nxc`) is now discoverable and set up through the suite.** `nxc` — agent-to-agent\nmessaging — joins the `nxs init` chooser and, once active, the single SessionStart fan-out: `nxs\nprime` now catches an agent up on its **entire** unread inbox (both \"act now\" and \"next session\"\nmessages) at the start of each session. `nxc init` sets chat up in a workspace and wires the one\n`nxs prime` hook (joining an existing flow/memory workspace without adding a second hook), and `nxc\nagent-manifest` exposes chat's session contract as data. The suite cross-sell now runs in every\ndirection — setting up flow or memory points you at chat, and setting up chat points you at flow and\nmemory. Coordinate agents through `nxc` instead of ad-hoc notes.", - "de": "### Neu\n- **nexus-chat (`nxc`) ist jetzt über die Suite auffindbar und einrichtbar.** `nxc` — Messaging\nzwischen Agenten — erscheint im `nxs init`-Auswahldialog und, sobald aktiv, im einzelnen\nSessionStart-Fan-out: `nxs prime` bringt einen Agenten zu Sitzungsbeginn auf den **gesamten**\nungelesenen Posteingang (sowohl „jetzt handeln“ als auch „nächste Sitzung“). `nxc init` richtet Chat\nin einem Workspace ein und verdrahtet den einen `nxs prime`-Hook (ein bestehender flow-/memory-\nWorkspace bekommt keinen zweiten Hook), und `nxc agent-manifest` legt Chats Sitzungskontrakt als\nDaten offen. Der Suite-Querverweis läuft jetzt in alle Richtungen — wer flow oder memory einrichtet,\nwird auf chat hingewiesen, und wer chat einrichtet, auf flow und memory. Koordiniere Agenten über\n`nxc` statt über Ad-hoc-Notizen." - } - }, - { - "version": "0.17.0", - "date": "2026-07-07", - "items": [ - { - "type": "fixed", - "en": "The facade `Engine` embedding handle now exposes user-labels (h89s), which v0.16.0 shipped but wired only for the CLI/MCP/migration paths: `label_add`, `label_remove`, `labels`, a `with_label` filter, and label-bearing reads `show_value` / `list_value` / `next_value` (labels ride as a sparse `labels` key, matching the CLI/MCP JSON). The priority write contract is clarified and documented: `create`/`update` take the plugin **label** (`P0`..`P4`) — the form `schema` advertises — not the read-form ordinal reads emit; a consumer that stores the ordinal translates it to the label at its own seam.", - "de": "Der Einbettungs-Handle `Engine` der Facade stellt jetzt die User-Labels (h89s) bereit, die v0.16.0 nur für die CLI/MCP-/Migrationspfade verdrahtet hatte: `label_add`, `label_remove`, `labels`, einen `with_label`-Filter sowie label-tragende Reads `show_value` / `list_value` / `next_value` (Labels als sparses `labels`-Feld, wie im CLI/MCP-JSON). Der Prioritäts-Write-Kontrakt ist klargestellt und dokumentiert: `create`/`update` erwarten die Plugin-**Bezeichnung** (`P0`..`P4`) — die Form, die `schema` ausweist —, nicht die Lese-Ordnungszahl, die Reads ausgeben; ein Consumer, der die Ordnungszahl speichert, übersetzt sie an seiner eigenen Naht in die Bezeichnung.", - "facade": "changed" - }, - { - "type": "added", - "en": "`nxs prime` and `nxf guide` now teach the defer-vs-wait convention: defer an item only for a real calendar date, and model waiting on an external delivery as an open `WAIT:` chore your items depend on (closed, with the delivered version, to release the chain). A new guide topic `nxf guide deferring-and-waiting` walks through it with a worked example.", - "de": "`nxs prime` und `nxf guide` vermitteln jetzt die Defer-vs-Warten-Konvention: Ein Item nur für ein echtes Kalenderdatum zurückstellen und das Warten auf eine externe Lieferung als offenes `WAIT:`-Chore modellieren, auf das deine Items zeigen (geschlossen mit der gelieferten Version, um die Kette zu entsperren). Das neue Guide-Thema `nxf guide deferring-and-waiting` führt mit einem durchgespielten Beispiel hindurch." - } - ], - "notes": { - "en": "### Added\n- `nxs prime` and `nxf guide` now teach the defer-vs-wait convention: defer an item only for a real calendar date, and model waiting on an external delivery as an open `WAIT:` chore your items depend on (closed, with the delivered version, to release the chain). A new guide topic `nxf guide deferring-and-waiting` walks through it with a worked example.\n\n### Fixed\n- The facade `Engine` embedding handle now exposes user-labels (h89s), which v0.16.0 shipped but wired only for the CLI/MCP/migration paths: `label_add`, `label_remove`, `labels`, a `with_label` filter, and label-bearing reads `show_value` / `list_value` / `next_value` (labels ride as a sparse `labels` key, matching the CLI/MCP JSON). The priority write contract is clarified and documented: `create`/`update` take the plugin **label** (`P0`..`P4`) — the form `schema` advertises — not the read-form ordinal reads emit; a consumer that stores the ordinal translates it to the label at its own seam.\n\n### Facade Contract\n- `changed` · The facade `Engine` embedding handle now exposes user-labels (h89s), which v0.16.0 shipped but wired only for the CLI/MCP/migration paths: `label_add`, `label_remove`, `labels`, a `with_label` filter, and label-bearing reads `show_value` / `list_value` / `next_value` (labels ride as a sparse `labels` key, matching the CLI/MCP JSON). The priority write contract is clarified and documented: `create`/`update` take the plugin **label** (`P0`..`P4`) — the form `schema` advertises — not the read-form ordinal reads emit; a consumer that stores the ordinal translates it to the label at its own seam.", - "de": "### Neu\n- `nxs prime` und `nxf guide` vermitteln jetzt die Defer-vs-Warten-Konvention: Ein Item nur für ein echtes Kalenderdatum zurückstellen und das Warten auf eine externe Lieferung als offenes `WAIT:`-Chore modellieren, auf das deine Items zeigen (geschlossen mit der gelieferten Version, um die Kette zu entsperren). Das neue Guide-Thema `nxf guide deferring-and-waiting` führt mit einem durchgespielten Beispiel hindurch.\n\n### Behoben\n- Der Einbettungs-Handle `Engine` der Facade stellt jetzt die User-Labels (h89s) bereit, die v0.16.0 nur für die CLI/MCP-/Migrationspfade verdrahtet hatte: `label_add`, `label_remove`, `labels`, einen `with_label`-Filter sowie label-tragende Reads `show_value` / `list_value` / `next_value` (Labels als sparses `labels`-Feld, wie im CLI/MCP-JSON). Der Prioritäts-Write-Kontrakt ist klargestellt und dokumentiert: `create`/`update` erwarten die Plugin-**Bezeichnung** (`P0`..`P4`) — die Form, die `schema` ausweist —, nicht die Lese-Ordnungszahl, die Reads ausgeben; ein Consumer, der die Ordnungszahl speichert, übersetzt sie an seiner eigenen Naht in die Bezeichnung.\n\n### Facade-Kontrakt\n- `changed` · Der Einbettungs-Handle `Engine` der Facade stellt jetzt die User-Labels (h89s) bereit, die v0.16.0 nur für die CLI/MCP-/Migrationspfade verdrahtet hatte: `label_add`, `label_remove`, `labels`, einen `with_label`-Filter sowie label-tragende Reads `show_value` / `list_value` / `next_value` (Labels als sparses `labels`-Feld, wie im CLI/MCP-JSON). Der Prioritäts-Write-Kontrakt ist klargestellt und dokumentiert: `create`/`update` erwarten die Plugin-**Bezeichnung** (`P0`..`P4`) — die Form, die `schema` ausweist —, nicht die Lese-Ordnungszahl, die Reads ausgeben; ein Consumer, der die Ordnungszahl speichert, übersetzt sie an seiner eigenen Naht in die Bezeichnung." - } - }, - { - "version": "0.16.0", - "date": "2026-07-06", - "items": [ - { - "type": "fixed", - "en": "The `beads → nexus-flow` migration (`nxs init --from-beads`) no longer silently drops a ticket's\ndefer and due dates. A ticket deferred in beads (`defer_until`) now migrates into the deferred lane\nwith its date intact — `nxf deferred` lists it and it stays out of `nxf next` until then — and a due\ndate (`due_at`) is carried over as well. Previously both fields were discarded during import, so a\ndeferred ticket landed as plain open work with no date.", - "de": "Die Migration `beads → nexus-flow` (`nxs init --from-beads`) verwirft die Zurückstell- und\nFälligkeitsdaten eines Tickets nicht mehr stillschweigend. Ein in beads zurückgestelltes Ticket\n(`defer_until`) wandert jetzt mit erhaltenem Datum in die Deferred-Lane — `nxf deferred` listet es,\nund es bleibt bis dahin aus `nxf next` heraus — und ein Fälligkeitsdatum (`due_at`) wird ebenfalls\nübernommen. Zuvor gingen beide Felder beim Import verloren, sodass ein zurückgestelltes Ticket als\ngewöhnliche offene Arbeit ohne Datum landete." - }, - { - "type": "added", - "en": "Items can now carry **user labels** — free-text tags, distinct from the plugin's display type —\nacross every layer. Attach and detach them with `nxf label add/remove