nxsflow
chat (nxc)

Personas

Eine Persona ist ein Agent, deklariert in einer Datei: .nxs-personas/<handle>.yaml. nxc liest diesen Ordner; niemand außer Ihnen schreibt hinein. Es gibt kein register-Verb, keine mitgelieferten Personas und keinen Weg, zur Laufzeit eine herbeizuzaubern — und das ist der Punkt. Ein deklarierter Agent ist einer, den Sie lesen, diffen, prüfen und einchecken können, und einmal eingecheckt ist er morgen noch da. Checken Sie ihn ein — siehe der Ordner gehört in die Versionsverwaltung; das ist kein Ordnungshinweis.

Das Minimum sind zwei Zeilen:

handle: coder
system_prompt: |
  You are the coder. Do what the trigger message asks, then answer on your own thread.

Alles andere hat einen Vorgabewert, der bedeutet, was er bedeutete, bevor es das Feld gab — eine Deklaration geht also nie dadurch kaputt, dass sie stehen bleibt.

Wer sie ist

handle: coder
job_title: Coder
job_description: Implements a work order on a branch and merges it.
expected_output: A short report of what changed, and the branch it is on.

handle ist die Adresse — das, was Sie hinter send --to tippen. Konventionsgemäß ist es auch der Dateiname ohne Endung, verbindlich ist aber das YAML-Feld.

job_title und job_description sind keine Zierde. Sie sind das, was nxc list einem Menschen zeigt, der entscheidet, wen er anspricht, und das, was die Persona selbst zum Sitzungsstart erfährt. Ein Handle ohne Beschreibung ist ein Name in einem Verzeichnis, aus dem niemand auswählen kann — der Einzeiler leistet, was die Frontmatter eines Skills leistet, und ist einen Satz Nachdenken wert.

expected_output ist die Form der Antwort, die Sie zurück haben wollen. Sie erreicht den Identitätsblock der Persona und ist damit der billigste Weg, die Antworten mehrerer Agenten vergleichbar zu machen.

Ein Handle darf nicht mit __ beginnen. Dieses Präfix ist den engineeigenen Identitäten vorbehalten (der Betreuer eines Kanals ist __channel__), und genau das macht sie durch eine Deklaration unfälschbar.

Wofür sie da ist

system_prompt ist die Aufgabe der Persona, in Ihren eigenen Worten, und das Letzte, was das Modell vor dem eigentlichen Gespräch liest. Der zusammengesetzte Prompt ist geschichtet, in dieser Reihenfolge:

  1. Der prime-Block — das, was nxs prime --persona <handle> zusammensetzt; Sie können ihn ausgeben und selbst lesen. Es ist der Sitzungsstart der Suite, in Modulreihenfolge zusammengesetzt: das Brett (nxf prime), die Erinnerungen des Projekts (nxm prime), dann der eigene Block von chat — die Identität dieser Persona (Titel, Aufgabe, erwartete Ausgabe, Erfahrungsstufe, wie sie erreichbar ist), wen sie ansprechen darf und wofür, die nxc-Befehlsliste und ihre Antwortregeln. Er kommt zuerst, damit „wer bin ich und wie antworte ich" alles Folgende verankert.
  2. Die CLAUDE.md des Projekts, wenn die claude_md:-Politik der Persona danach verlangt.
  3. Der eigene system_prompt der Persona — am nächsten am tatsächlichen Gespräch.

Die Antwortregeln aus Schicht 1 sollte man wörtlich nehmen, denn es gibt genau zwei davon und keine dritte: antworte in dem Faden, den man dir gegeben hat — oder sag, im selben Faden, dass du es nicht kannst. Beides ist nxc reply --thread <id>, und beides beendet den Zug. Es gibt keinen Weg, zu sagen, was fehlt, und danach weiterzuarbeiten: man antwortet, der Zug endet, und wenn die Antwort kommt, wird man fortgesetzt — mit allem, was man schon weiß. send --to beginnt etwas Neues mit jemand anderem; nxc list zeigt, wer da ist.

Drei Schalter formen diese Schichten:

prime: true            # Vorgabe. false steigt aus Schicht 1 aus, wenn der Prompt sie selbst abdeckt
claude_md: inherit     # Vorgabe | ignore (Projektkonventionen nicht zeigen) | override (reserviert)
base_prompt: claude_code   # Vorgabe. `none` läuft ohne das Claude-Code-Preset darunter

prime: nimmt auch eine Abbildung, wenn eine Persona einen Teil der Suite bekommen soll und den Rest nicht:

prime:
  flow: false          # kein Brett für DIESE Persona — ein reiner Prüfer braucht keines
  memory: true         # die Erinnerungen des Projekts (die Vorgabe)
  chat: true           # Identität, Adressbuch und Antwortregeln (die Vorgabe)

Was Sie weglassen, bleibt an — die Abbildung oben sagt also eine Sache und ändert eine Sache. Der Filter gilt je Persona: das Brett hier auszuschließen entfernt den Abschnitt aus dem Block dieser Persona und aus keinem anderen.

memory: false kann mehr kosten als Erinnerungen. Wenn dieses Projekt nxm migrate gelaufen ist, sind seine Konventionen AUS der CLAUDE.md heraus in den Speicher gewandert — genau das tut die Migration —, die Projektregeln kommen also aus nxm prime, und claude_md: inherit trägt nichts mehr bei. Eine Persona, die in einem solchen Projekt die Erinnerungen ausschließt, hat die Regeln von nirgendwoher. Die Engine kann ein migriertes Projekt nicht von einem nicht migrierten unterscheiden und weist die Deklaration deshalb nicht ab; sie sagt es stattdessen der Sitzung, in einer Zeile ihres eigenen Prompts: sie hat beides nicht und sollte nxm prime lesen, bevor sie etwas ändert, bei dem sie sich nicht sicher ist.

Weil Schicht 1 die Antwortschleife bereits beibringt, kann eine Anstoßnachricht nur die Aufgabe tragen. Genau deshalb ist das Feld standardmäßig an.

Einen Satz können Sie nicht abschalten. Sobald der Anstoß, der eine Sitzung startet, erklärt hat, dass ein Faden auf ihre Antwort wartet, trägt der zusammengesetzte Prompt das erzwungene Ende — und zwar auch unter prime: false:

Obligation: thread <id> is waiting on your reply, and your turn may not end without one.
There are exactly two ways to end it:
- `nxc reply --thread <id> "<your result>"` — you are finished.
- `nxc reply --thread <id> --escalate "<what is in the way>"` — you cannot reach the result
  and need help or a decision.

Diese Formen sind die ganze Menge, und „ich warte und antworte später" ist keine davon. Die Regel steht mit Absicht in der Engine und nicht in Ihrer Deklaration — ein Schritt eines deklarierten Ablaufs endet, wenn seine Sitzung antwortet, und eine Deklaration, die das zu sagen vergisst, ließe den Ablauf ohne Signal zurück.

Einem Schritt, der Arbeit zurückschicken kann, wird eine dritte Form genannt, und nur einem solchen: wo der Kanal für ihn on_needs_rework: erklärt (nxc guide channels), bietet derselbe Absatz zusätzlich nxc reply --thread <id> --needs-rework "<was in Ordnung zu bringen ist>" an. Das Angebot hängt am erzwungenen Ende und nicht am Nutzungsblock — aus dem Grund darüber: eine Rolle mit prime: false ist weiterhin zur Antwort verpflichtet, und eine Verpflichtung, deren Mittel niemand sicherstellt, ist genau der Fehler, den diese Regel verhindert.

Womit sie läuft

stage: senior          # junior | senior | principal
model: opus            # fable | opus | sonnet — schlägt `stage`, wenn beides dasteht
tools: [Bash, Read, Write]
permissions: acceptEdits

stage ist ein Vokabular, das kein Modellname ist. Sie entscheiden, wie viel Denken eine Aufgabe wert ist, ohne wissen zu müssen, welche Modelle es diesen Monat gibt: junior → Sonnet, senior → Opus, principal → Fable. Eine Tabelle verbindet beides, damit die Schreibweise der Deklaration und die Wahl der Engine nicht auseinanderlaufen können. Greifen Sie zu model: nur, wenn Sie etwas meinen, das die Stufe nicht ausdrücken kann — und beachten Sie die bewusste Vorrangregel: die Stufe ist die grobe, besprechbare Wahl, ein benanntes Modell ist Ihre Übersteuerung.

tools hat drei Zustände, und der Unterschied zählt. Lassen Sie das Feld ganz weg, gilt der volle Standardwerkzeugsatz der Agenten-Laufzeit. Schreiben Sie tools: [], hat die Persona ausdrücklich keine — eine enge, nicht-agentische Rolle. Schreiben Sie eine Liste, bekommt sie genau diese. Ein weggelassener Schlüssel und eine leere Liste sind nicht dasselbe.

Eine Persona, die überhaupt nxc ausführen soll, braucht Bash, denn so antwortet sie — und wo die Engine eine Antwort verlangt, gibt sie das selbst dazu. Jede Beauftragung sagt der Persona in ihrem eigenen Systemprompt, sie solle ihren Zug mit nxc reply --thread <id> beenden; wer so verpflichtet, muss also sicherstellen, dass es ausführbar ist. Der Trigger nimmt Bash zusätzlich zu dem auf, was Sie deklariert haben — ohne den Werkzeugsatz zu verengen, den ein weggelassenes tools: gewährt. Für eine Persona, die die Shell für ihre Arbeit braucht, deklarieren Sie Bash weiterhin selbst; was Sie nicht mehr mitdenken müssen, ist, dass Antworten auch Arbeit ist.

Wer sie ansprechen darf

addressable: general           # die Vorgabe: jeder darf direkt ein Gespräch eröffnen
addressable: [review]          # nur über diese Kanäle; eine Direktnachricht wird abgelehnt

Das ist eine Eigenschaft des Ziels, keine aus dem Aufrufer abgeleitete Schranke. Genau das macht sie durchsetzbar: eine nur über Kanäle erreichbare Persona lehnt eine Direktnachricht für alle gleichermaßen ab — Mensch oder Agent, benannt oder anonym. Eine Einschränkung, die daraus abgeleitet wäre, wer Sie zu sein behaupten, hieße, dass das Ablegen der Identität mehr erlaubt — schlimmer als gar keine Einschränkung.

Sie zu deklarieren hält die Persona außerdem als eigenen Eintrag aus der menschlichen Darstellung von nxc list heraus, und das ist meist gewollt: wenn der Weg zu vier Reviewern darin besteht, die Runde anzusprechen, in der sie sitzen, lädt eine Einzelauflistung genau zu dem Aufruf ein, den Sie nicht wollen.

Wen sie ansprechen darf

address_book:
  - to: review
    why: to get a change judged before merging
  - to: pm
    why: to report that a work order is done

Das why ist die tragende Hälfte — es wirkt wie die einzeilige Beschreibung eines Skills, ein Satz, aus dem ein Modell auswählt, und deshalb wird es überall gerendert, wo auch das Ziel steht.

Es hat drei Zustände, genau wie tools:, und der Unterschied zählt.

# Schlüssel fehlt        -> „nicht aufgeschrieben": die Persona sieht das ganze deklarierte Team
address_book: []         # -> „beauftragt nichts": die Persona sieht niemanden
address_book: [{to: pm}] # -> genau dieses Buch, in der Reihenfolge des Autors

address_book: [] ist die Deklaration für eine Rolle am Blatt des Baums: ein reiner Prüfer, ein Zusammenfasser, alles, dessen einziger ausgehender Ruf die Antwort auf dem eigenen Faden ist. Dass es in der Datei steht, ist der Punkt — vorher blieb dafür nur ein Satz im system_prompt („du beauftragst nichts; ignoriere die Liste"), also ein Prompt, der gegen eine Deklaration anredet.

Ein fehlender Schlüssel bleibt „nicht aufgeschrieben" und zeigt das ganze Team; jede Persona, die vor dieser Änderung deklariert wurde, bedeutet also unverändert das, was sie immer bedeutet hat.

Eines lässt die abgeleitete Liste weg: die Kanäle, in denen die Persona selbst Mitglied ist. Einem Prüfer die review-Runde anzubieten, in der er sitzt, hilft in keiner Lesart — und den eigenen Kanal zu beauftragen ist keine Deklarations-Zyklizität, die wird beim Laden abgewiesen, also fiele es sonst durch jede Prüfung. Ein Buch, das Sie selbst schreiben, wird Eintrag für Eintrag befolgt, diesen Kanal eingeschlossen: die Datei ist die Autorität.

Beachten Sie, was das Adressbuch in dieser Stufe bewusst nicht tut: Es schränkt nicht ein. Es ist Orientierung, die eine Persona liest — aus demselben Grund wie oben: eine Schranke aus einer ablegbaren Identität abzuleiten, wäre schlimmer als nutzlos.

Was sie zum Arbeiten braucht

working_tree: exclusive        # shared (Vorgabe) | exclusive

exclusive sagt, dass die Sitzungen dieser Persona die Arbeitskopie und das Build-Verzeichnis des Repositories allein brauchen. Eine zweite Kette, die kollidieren würde, wartet stattdessen. Das wird deklariert und nicht erschlossen, denn eine Persona darf heißen, wie sie will, und die Laufzeit kann einer Aufgabe nicht ansehen, dass gleich ein Checkout verzweigt wird. Die vollen Mechanik — was ein Anspruch umfasst, was ihn freigibt und wovor er nicht schützt — steht unter Grenzen und Sicherheit.

Ein ausgearbeitetes Beispiel

handle: coder
job_title: Coder
job_description: Implements a work order on a branch and merges it.
working_tree: exclusive
system_prompt: |
  You are the coder. You are the first step of the declared `build-and-ship` channel, which means
  somebody sent a work order to that channel and its supervisor opened a thread for you with the
  work on it.

  ## How you finish — read this before you do anything else

  You finish by ANSWERING ON YOUR OWN THREAD:
  `nxc reply --thread <the thread id in your trigger message> "<what you did>"`.

  That reply is what moves the operation on: the channel's supervisor is waiting for it, and it
  starts the next step the moment it lands. There is no second call and nothing else to remember.
  If you end your session without replying, the operation stands still until the channel's declared
  `timeout` strikes.

  If you genuinely cannot carry the task out, say so with `nxc reply --escalate "<why>"` on the same
  thread. That is the ONE other thing you may say, and it is a declared signal the supervisor
  branches on — not a way to hand back an unfinished task quietly.
tools: [Bash, Read, Write]
permissions: acceptEdits

Zwei Dinge in diesem Prompt lohnt es sich zu übernehmen. Er nennt den Abschlusszug zuerst, vor der Arbeit, denn eine Sitzung, die die Arbeit tut und das Antworten vergisst, hat nichts erreicht, was irgendwer sehen kann. Und er benennt Eskalation als deklariertes Signal statt als Papierkorb — ein Agent, dem man sagt „sag --escalate, wenn du nicht kannst", erfindet dafür keine Formulierung, die nichts lesen kann.

Deklariert, aber noch nicht gelesen

Vertrauenswürdige Dokumentation sagt, welche Felder wirkungslos sind. Diese werden geparst, zurückgeschrieben und validiert, und nichts in der Engine handelt heute danach:

  • session: fresh | continue — wirkungslos, weil nicht die Persona es entscheidet. nxc send --to <persona> prägt eine frische Sitzung; eine nxc reply --thread in den eigenen Faden dieser Persona setzt die Sitzung fort, die sie bereits hat — mit allem, was sie bereits weiß; und in einem Kanal mit steps: entscheidet der betretene Schritt es mit seinem eigenen resume: (nxc guide channels). Eine Politik auf der Persona hat nichts mehr zu entscheiden, was das Verb oder der Schritt nicht schon entschieden hat.
  • sub_agents: true | false.
  • reports_to: <handle>.

Sie zu deklarieren kostet nichts und hält die Absicht fest; bauen Sie keinen Prozess darauf, dass sie etwas tun.

Bei claude_md: override ist Vorsicht geboten, denn es ist nur ZUR HÄLFTE ungelesen. Das damit gemeinte Ersatzdokument je Persona ist noch nicht spezifiziert, also setzt nichts eines zusammen — untätig ist die Angabe deswegen aber nicht: sie ist schlicht nicht inherit, also wird eine Persona, die sie deklariert, OHNE die CLAUDE.md des Projekts zusammengesetzt, genau wie bei ignore. Schreiben Sie ignore, wenn Sie das meinen. inherit (die Vorgabe) und ignore sind beide wirksam.

Der Ordner gehört in die Versionsverwaltung

.nxs-personas/ ist keine Dokumentation darüber, wie Ihre Agenten arbeiten — er ist, wie sie arbeiten, bei jedem Start frisch gelesen. Und er liegt in derselben Arbeitskopie, in der die Agenten selbst arbeiten sollen. Das ist eine Rückkopplung, die keine andere Konfiguration in diesem System hat: ein git switch, ein git stash oder ein git checkout -- . einer Persona ändert, wie die nächste Persona denkt.

Eine nicht eingecheckte Änderung an einer Deklaration ist deshalb gar keine richtige Änderung. Sie ist etwas, das ein Zweig hat und jeder andere nicht — in einem Ordner, dessen Inhalt die Regeln festlegt. Und was sie zurückdreht, ist gewöhnliche, korrekte Zweighygiene von jemandem, der nicht wissen kann, dass Ihre Datei wichtig war. Das ist keine Vermutung, sondern das, was hier passiert ist: drei Deklarationen wurden genau auf diesem Weg zurückgedreht, darunter eine Regel, die einer Persona sagte, sie solle jeden Zug mit einer Antwort beenden. Die Persona lief danach ohne sie, und auf vier Fäden antwortete die Laufzeit an ihrer Stelle, bevor es jemandem auffiel. Gefunden wurde es von Hand, durch einen Vergleich der gespeicherten Sitzungs-Spec mit der Datei auf der Platte.

Checken Sie den Ordner ein und prüfen Sie Änderungen daran wie Code. Zwei Dinge helfen Ihnen zu bemerken, wenn es doch passiert ist, und keines davon ist eine Sperre — die Dateien bleiben jederzeit editierbar, denn stabil sein muss ein laufender Vorgang, nicht das Verzeichnis:

  • Jede gestartete Sitzung hält fest, aus welcher Fassung der Deklaration ihr Prompt gebaut wurde: als declarationHash in .nxs/agent-logs/<session>.spec.json. „Lief diese Sitzung unter der Regel, die ich geschrieben habe?" ist damit ein Vergleich und keine Textsuche in einem Prompt.
  • Wird eine Persona unter einer anderen Deklaration gestartet als beim letzten Mal, sagt das der Empfangsschein des Aufrufs, der sie gestartet hat — ein declaration_changed-Eintrag in warnings, der beide Fassungen nennt. Es ist eine Warnung und keine Verweigerung: eine Persona zu ändern und sie dann anzusprechen ist die gewöhnliche Arbeitsweise, und der Exit-Code bleibt 0. Eine Änderung ist meistens gewollt. Eine unbemerkte nie.

Beides beobachtet die Datei der Persona selbst und sonst nichts in diesem Ordner. Eine channels.yaml, die auf demselben Weg zurückgedreht wurde — andere Mitglieder, ein anderes working_tree:, ein anderes timeout: — steuert Ihre Agenten genauso und wird von beidem nicht gemeldet. Der Rat oben heißt deshalb nicht „das Werkzeug passt schon auf": es ist die Versionsverwaltung, die aufpasst, und dies ist ein zweites Paar Augen auf der Hälfte des Ordners, die es sehen kann.

Wenn eine Deklaration falsch ist

Eine fehlerhafte Datei ist ein lauter validation-Fehler, der den Pfad nennt — nie eine still übersprungene Persona. Ein fehlender Ordner ist kein Fehler: ein Workspace ohne Deklarationen löst sauber auf und hat schlicht niemanden anzusprechen, und nxc list und nxs prime sagen genau das.

Referenzielle Probleme — ein Kanal, der eine nicht existierende Persona nennt; eine Persona, die sich über einen Kanal erreichbar erklärt, in dem sie kein Mitglied ist — erscheinen in nxs prime, und zwar nur im interaktiven Kontext. Einer gestarteten Persona werden die Fehler ihres Autors nicht vorgehalten; einem Menschen an der Tastatur schon.

Weiter

Tippen, um zu suchen. Gefunden wird nach Wortlaut und nach Bedeutung — ein Abschnitt kann also eine Frage beantworten, die er nicht ausspricht.