All guides
Commands
The complete nxc surface: fourteen commands, in the order a session tends to reach for them. Run
nxc <command> --help for the generated reference (flags, types, defaults); this guide adds the
narrative and the --json shapes. Two flags are global — --json on every command, and --db <path> (also NXC_DB) to point at a workspace database instead of discovering .nxs/ from the
working directory.
The surface is small on purpose. There are exactly two ways to say something — send starts a
conversation, reply answers one — plus withdraw, which takes a commission back before it
starts, and release, which hands the working copy on when the chain holding it is over.
Everything else reads. If you are looking for a verb that creates a channel, registers an agent, or
advances a workflow step, see what is deliberately absent at the
end.
Set up
nxc init
Activate chat in the current directory: ensure a .nxs/ workspace, register the chat module, then
hand the shared agent file and the SessionStart hooks to the nxs assembler,
which re-assembles them for every active module. --quiet does the same and prints no banner —
that is the seam the umbrella drives, and the non-interactive entry for an agent.
nxc init # human, with the banner
nxc init --json # the machine-readable receipt--quiet is silent success — it prints nothing at all, which is what makes it drivable:
$ nxc init --quiet
nxc agent-manifest
What chat declares it contributes to the shared agent file — the prime command and the hook — as data rather than as prose:
$ nxc agent-manifest
nexus-chat agent manifest
prime command: nxc prime
hook: SessionStart → nxc prime
Run with --json for the machine contract the `nxs` umbrella assembles from.
--json is that contract; the umbrella reads it from all three blocks and assembles one
AGENTS.md and one hook, so activating a second module never overwrites the first one's block.
Who is here
nxc list
The directory: who can be addressed in this workspace, and what for. It is a read over
.nxs-personas/ — nothing else — so it shows exactly what is declared.
$ NXC_ACTOR=alice nxc list
## Who you can address
Address any of them the same way: `nxc send --to <handle> "<your message>"`.
**Coder** (handle: `coder`) — Implements a work order on a branch and merges it.
**Build-And-Ship** (handle: `build-and-ship`, members: coder, review) — the declared order a work order runs through
**Review** (handle: `review`, members: general, integrity) — the review quorum — one round asks both reviewers and hands back one verdict
Without --persona a human sees the whole declared team, while a persona running under its own
session sees its own address book, recognised from that session. --persona <handle> projects that
persona's view whoever is asking — useful for checking what an agent will actually see before you
start it.
--json carries personas (handle, job_title, job_description, direct), channels
(name, members, description), a public_channels array of the front doors it can see
(omitted when there are none, and absent from the human rendering on purpose — a door no
declaration here names is discoverable, not addressable), and a declarations block naming the
folder it read and how many files were in it. That last block is the one to check when the list is
emptier than you expected.
Say something
nxc send --to <persona|channel> <BODY>
Open a conversation. --to is required, and its target must be declared: a persona handle or a
channel name from .nxs-personas/. The declaration decides what happens next — a persona is started
on a fresh session, a channel fans out under its own policy — and the call returns the thread id
either way.
$ NXC_ACTOR=alice nxc send --to coder --ref nxf_ids=ab12.0007 "Add a --since flag to the export command."
opened thread m-00000000000000000000000001 in dm:9445dbc93dfdad401585c42b — reply with `nxc reply --thread m-00000000000000000000000001 "…"`
Flags:
--ref k=v(repeatable) — what this conversation is about:nxf_ids(a flow item; repeatable because a first message may name several),branch,pr,session_id.--no-ref— the explicit answer that there is genuinely no subject. Saying "I looked" where saying nothing says nothing.--stream— watch the thread and the personas' transcripts until somebody answers, instead of returning immediately. For a human at a keyboard; refused inside a running persona.
Naming neither --ref nor --no-ref still sends, and warns — as a line on stderr for a reader
and as a refs_warning field for an app, so the reminder cannot be lost between the two:
$ NXC_ACTOR=alice nxc send --to coder "One more thing." --json
{"thread_id":"m-00000000000000000000000002","message_id":"m-00000000000000000000000003","to":"coder","target":"persona","channel":"dm:9445dbc93dfdad401585c42b","session":"m-00000000000000000000000002","expects":["ab12/coder"],"spawned":true,"warnings":[],"refs_warning":"nothing was named as the subject of this conversation. Say what it is about — `--ref nxf_ids=<id>`, repeatable — or `--no-ref` if there really is nothing."}
warning: nothing was named as the subject of this conversation. Say what it is about — `--ref nxf_ids=<id>`, repeatable — or `--no-ref` if there really is nothing.
Sending to a channel returns the same receipt with target: "channel", the declared channel's id
(decl:<name>), and the channel supervisor as the expected replier:
$ NXC_ACTOR=alice nxc send --to build-and-ship --ref nxf_ids=ab12.0008 "Ship the export change." --json
{"thread_id":"m-00000000000000000000000003","message_id":"m-00000000000000000000000004","to":"build-and-ship","target":"channel","channel":"decl:build-and-ship","expects":["ab12/__channel__"],"spawned":true,"warnings":[],"refs_warning":null}
The receipt's fields, in the order they are emitted: thread_id, message_id, to, target
(persona or channel), channel, session (the internal session id, when a persona was
started), queued_behind / queue_position (present only when the trigger is parked behind a
working-copy lease), expects, deadline (when the channel declares a timeout:), spawned,
warnings, refs_warning. warnings and refs_warning are always present — even empty and even
null — so a reader never has to tell "no key" from "nothing to report".
A target no declaration names is refused, and the refusal says what was searched:
$ NXC_ACTOR=alice nxc send --to nobody --ref nxf_ids=ab12.0008 "Anyone there?" --json
? 1
{"error":{"kind":"not_found","msg":"no such target: nobody (not a declared channel, not a declared persona, not a channel in this workspace) — `nxc list` shows what can be addressed"}}
So is a persona that declared it is reachable only through a channel — for everyone alike, human or agent, named or anonymous:
$ NXC_ACTOR=alice nxc send --to general --ref nxf_ids=ab12.0008 "Have a look?" --json
? 1
{"error":{"kind":"validation","msg":"general is addressable only through review — send --to review instead"}}
nxc reply --thread <THREAD> <BODY>
Answer in a thread. --thread is required: a thread id identifies a conversation on its own, with
no channel context, and the reply is routed back to whoever is on the other side of it.
$ NXC_ACTOR=coder nxc reply --thread m-00000000000000000000000001 "Added the flag and a test; branch feat/export-since." --json
{"posted":true,"message_id":"m-00000000000000000000000002","thread_id":"m-00000000000000000000000001","resumed":false,"warnings":[]}
resumed is true only when the reply woke the target's own return-address session — the direct
one-to-one path. false covers two different things (there was no session to resume, or there was
and the resume did not land), and wake_skipped beside it is what separates them. A quorum board's
completion wake is not a "resume" in this sense and never sets it. posted is false only in the
--if-unanswered no-op below.
--escalate— "I cannot carry out this task." One of the three things an agent may say withreply; the others are "I am finished" (a bare reply) and--needs-reworkbelow. It is a declared signal, which is what makes it usable: a channel's supervisor branches on a bit whose meaning is written down, not on a phrase it has to interpret. The turn is discharged either way — what differs is the outcome.On an ordered channel the outcome is that the chain ends. The step after yours is not started, the round goes up as it stands, and whoever commissioned it decides. That is the point of the flag: a task that could not be carried out must not commission the work that was to follow it.
It is not a way to say "not yet". There is no spelling for that on
reply— every answer it has ends the turn, and a caller reads this one as work that will not arrive. If your step has commissioned a round of its own and is waiting for it, wait: answer once you have its result.--needs-rework— "what you handed me does not meet the standard." The third answer, and the one about somebody else's work rather than your own. Name what must be put right in the body: those words are handed to the party that produced the work, and they are its next task.It is offered only where the channel declares somewhere for it to go (
steps:→on_needs_rework:) — that is what makes it a signal rather than a token you invent. A step without that edge is told about two endings, not three; set on such a step the bit falls away, with averdict_droppedwarning on the reply's own receipt. It may not be combined with--escalate: the two are statements about different work with opposite consequences. Seenxc guide channels.--if-unanswered— post only if the caller still owes a reply on this thread; otherwise a deliberate no-op (exit 0,posted: false, nothing written), never an error. It exists for one caller: the agent sidecar's teardown, which says something when an SDK session ends without ever answering the thread it owed, so the thread does not go quiet forever. You do not need it.It is the fallback, not the first move. Before speaking in the agent's name, the teardown gives the session its turn back once and tells it — naming the thread, both ways to end a turn, and any round it commissioned that is still open. Most sessions answer then. One that is reminded and ends silent again is handed back as an escalation: at that point no result exists and somebody has to decide what happens to the round, which is a different fact from a turn that simply produced nothing, and
escalated: trueonnxc statusis where the caller reads it.Whatever it posts is marked as the runtime's. Any reply written through this door carries
substituted: trueonnxc status, so an app never has to read a message body to tell an answer the agent wrote from one written in its name. See limits and safety for the pairing withescalated.--stream— as onsend: watch the answer arrive.
There is no --kind, no --priority, no --disposition and no --ref on reply. A reply
inherits its subject: the thread already says what the conversation is about, which is why the
--ref obligation sits on send and nowhere else.
nxc withdraw --thread <THREAD>
Take back a commission that is still waiting for the working copy — one that never started. It
prints one line per commission it took back (withdrew <role> on thread <id> (never started)), and
--json carries the same as withdrawn / started_meanwhile.
A persona or channel that declares working_tree: exclusive runs one chain at a time; a commission
that arrives while another chain is holding is parked in a queue and starts when the holder lets
go. Until then nothing has happened: no session, no transcript, no model call. That is the whole of
what this verb takes back, and the reason it is safe to.
Name the thread send handed you. Every parked commission under it is withdrawn, and each waiting
thread is discharged with a message saying who took it back — so it is a record in the conversation,
not an absence.
If every commission of the round was taken back, the thread you named is discharged the same way
and stops counting as an open operation in nxc status. That is not a consolidation — nothing is
delivered, because there is no answer — it is the register saying there is no round left. Without
it, a send --to <channel> you withdrew went on being reported as running, since the channel thread
was still waiting for a supervisor whose round had just been taken away.
What it does not do: stop a round that has started. That one has work in flight and a session
mid-turn, and it is a different question with different answers. If a commission starts between this
command reading the queue and writing to it, it is reported as started_meanwhile and left strictly
alone — there is no window in which this can stop something that had begun.
A thread with nothing parked under it is a not_found saying so.
nxc release --thread <THREAD>
Give this workspace's working copy back when the chain holding it is over and nothing is going
to give it back on its own. The other end of withdraw: that one takes back the commission that is
waiting, this one moves the one that is in the way.
An escalation holds the working copy on purpose — the task is mid-flight and the answer is still travelling upward. When every session in that chain has ended, nothing is left that could ever say it is finished, and whatever is queued behind it waits forever. (Answering the escalation clears the hold inside a channel; on a direct thread it does not — see limits and safety, which used to claim otherwise.)
Name a thread inside the holding chain — nxc status marks the operation that holds one with
holds working tree, and nxc threads show <id> says working tree: holding on the thread itself.
Naming the thread that is waiting is refused by name: that is the chain which wants the copy, not
the one that has it.
It refuses while any session in the holding chain still has a live process, and says which. Taking the checkout from a chain that is still working is the collision the lease exists to prevent; this verb is for the other case. It asks nothing about whether the register looks tidy — a dead chain's register can say anything at all, and "nothing is running" is the fact that decides.
Whoever was parked starts, and the receipt says so (started, plus failed for a promotion that
could not be started — its message is persisted and its session is minted, so it names what to go
and look at). expired: true means the lease was already past its two-hour bound, so what this call
actually did was drain the queue behind it.
Nothing is written to the conversation, unlike withdraw. The lease is a claim on files on one
machine's disk — one row in this workspace's own database, never synced — so a message about it
would sync to replicas where the fact is not true. What is visible afterwards is the consequence:
the operation stops reporting holds working tree, and the chain that was waiting is running.
Read
None of these write anything, and none of them consume a message. --consumer <handle> reads as
another qualified handle; without it, a command reads as the caller's own.
nxc inboxandnxc readwere here, and both are gone (nxf 6j6v.1gm9). There is no verb for asking after your own messages, and none for acking them, because people pull and agents get pushed: an agent's message arrives in the prompt that starts its session, or in the turn that resumes it, so a pull verb was the duplicate of what had already been delivered — measured at 0 uses in 66 role sessions for each of the two. What a person reads instead is the CONVERSATION, which is what the two verbs below are for. The unread record itself is untouched on the app seam (prime --jsoncarries it); its own future is nxf 6j6v.4d2z.
nxc threads list / nxc threads show <THREAD>
The quorum boards. list is every board the caller is a member of, each with its bulk state;
--channel narrows it.
$ NXC_ACTOR=alice nxc threads list
m-00000000000000000000000003 decl:build-and-ship 0/1 in [waiting] · waiting for working tree (#1)
m-00000000000000000000000004 decl:build-and-ship 0/1 in [waiting] · waiting for working tree (#1)
m-00000000000000000000000001 dm:9445dbc93dfdad401585c42b 1/1 in [complete]
m-00000000000000000000000002 dm:9445dbc93dfdad401585c42b 0/1 in [waiting] · holding working tree
show is one board in full — the quorum state plus the replies in order:
$ NXC_ACTOR=alice nxc threads show m-00000000000000000000000001
thread m-00000000000000000000000001 in dm:9445dbc93dfdad401585c42b
expects: ab12/coder
replied:
outstanding: ab12/coder
complete: false
working tree: holding
· ab12/alice Add a --since flag to the export command.
The --json records carry thread_id, channel_id, opener, expects, replied,
outstanding, complete, stale, working_tree and working_tree_queue_position. Reading many
boards costs the same number of queries as reading two — that property is measured by a test, not
merely intended, because a coordination UI reads all of them at once.
nxc status
Where an operation stands: the whole thread tree from its root down, across channel borders. A
board tells you about one conversation; status tells you about the whole piece of work it started.
$ NXC_ACTOR=alice nxc status
operation m-00000000000000000000000001 dm:9445dbc93dfdad401585c42b 1 thread(s), 0 open
m-00000000000000000000000001 dm:9445dbc93dfdad401585c42b awaiting you (answered by ab12/coder)
$ NXC_ACTOR=alice nxc status --json
{"operations":[{"root":"m-00000000000000000000000001","channel_id":"dm:9445dbc93dfdad401585c42b","live":true,"open":0,"needs_decision":false,"holds_working_tree":false,"threads":[{"thread_id":"m-00000000000000000000000001","channel_id":"dm:9445dbc93dfdad401585c42b","depth":0,"state":"answered","awaiting_human":true,"escalated":false,"substituted":false,"opener":"ab12/alice","expects":["ab12/coder"],"stale":false}]}]}
--thread <id>— the one operation that thread belongs to, shown from its root, whether it is still running or already finished.--channel <name>— the live operations whose root sits in that channel; a declared channel name or a raw channel id. An entry point, not an anchor: an operation crosses channels, and the tree follows it.- neither — everything still going on here.
Each thread carries a state of open / answered / stale, plus awaiting_human, which is
true only at the root of a finished operation. That distinction is the point of the field: a
stalled chain and a finished one that is waiting for a person look identical to a counter, and they
mean opposite things.
Two flags sit on the operation rather than on a thread, and both answer a question the per-thread rows can only answer by being read one at a time:
needs_decision— somewhere under this root a task was handed back and nobody has taken it up. Read it next toawaiting_human: both say "the human is up", about situations of completely different urgency. A finished operation waiting to be read is the normal end; one whose root looks exactly the same while an unanswered escalation sits under it is a chain that has stopped — and an escalation holds the working copy, so it stops the machine too. It clears itself: re-commission the round and the flag goes.holds_working_tree— somewhere under this root the working copy is held. It tells you whether to go looking; the per-threadworking_treefield still says which thread.
nxc search <QUERY>
Case-insensitive substring over message bodies in the caller's channels, deterministically ordered.
$ NXC_ACTOR=alice nxc search "since flag" --json
[{"message_id":"m-00000000000000000000000001","channel_id":"dm:9445dbc93dfdad401585c42b","sender":"ab12/alice","body":"Add a --since flag to the export command."}]
It searches bodies only, and only in channels the consumer is a member of. To find what a session did rather than what it said, read its transcript.
Sessions and transcripts
These are the role runtime's own plumbing. The agent sidecar writes them; one of them is a read you will want by name.
nxc session bind <INTERNAL> <REAL>
Bind an internal (nxc-minted) session id to the real Claude Agent SDK session id the SDK handed
back. The sidecar calls it once a session starts. An unknown internal id is reported as not_found.
nxc session state <SESSION> / --thread <ID>
Is that session still alive, or is it dead? The read opposite of the two writes the sidecar
makes on this seam — session bind above and the session ended it calls last of all.
nxc session state m-01M0… # one session
nxc session state --thread m-01M0… # every session that ran on a thread, ended ones includedThree answers, and the third is not a hedge:
running— a live process stands behind it.ended <instant>— the session announced its own end, and this is when. That is the fact a channel declaredworking_tree: exclusiveopens its next step on.unknown— nothing announced an end and no live process answers for it: a session killed hard, or one this machine never ran. Reportingendedthere would state a fact nobody established.
unknown has a third reading, and the report tells you when you are looking at it. Whether a
session is running is a question the worker answers, and not every worker can: one that starts no
process has none to look at. So the answer carries worker_answers_liveness — false means nothing
here was ever asked, and no unknown under it says anything about a session. The shipped sidecar
answers it, so on the normal command line the flag is true and an unknown really is a session
that died without a word.
Use the session form at a thread that has gone quiet, before you conclude it failed. Use
--thread before you touch a working copy a previous step may still have hands on: it lists what
ran there and what became of each, which is the question nxc status cannot answer, because it
reads a message board and this reads a process. An unknown session is not_found; a thread nothing
ran on is an empty answer, not an error.
nxc transcript show <SESSION>
A session's normalized stream, rendered as a timeline: assistant text, thinking, tool calls and
their results, with each Task-spawned subagent's entries nested under the tool_use that spawned
them. The session id is the internal one — what send --to <persona> returns as session.
$ NXC_ACTOR=alice nxc transcript show m-00000000000000000000000001
transcript m-00000000000000000000000001 role=coder (0 entries)
--from-seq <n> and --limit <n> page a long session: pass the largest seq the last chunk
showed, and a chunk shorter than --limit is the end. An unknown session renders an empty
transcript rather than failing — a session whose sidecar never flushed is indistinguishable from one
that had nothing to say.
Two things to know before you paste one anywhere. A transcript contains raw tool inputs and
results — whatever the agent read, wrote, or ran — so treat a dump like the workspace database,
not like a message log. And it is deliberately not membership-gated: a transcript has no channel to
gate on, and the table is device-local and never synced, so a gate would buy nothing that anyone
holding the file cannot already do with sqlite3.
nxc transcript append --session <ID>
Append normalized entries, read as JSON-lines from STDIN. This is the sidecar's callback contract —
the flag name, the stdin framing and the --json record are all load-bearing because an
already-shipped producer depends on them. A line that does not parse is a loud validation error
naming the line, never a silent skip.
nxc transcript prune
Retire the transcripts of sessions nobody has written to for a while, and say what went. Whole sessions, aged on their last recorded entry: a session still being written to is never a candidate however long it has been running, and a long one is never cut in half.
nxc transcript prune --dry-run # report, remove nothing, take no write lock
nxc transcript prune --keep-days 7 # tighter than the configured windowYou do not have to run it to keep the table bounded — the same retention rides the first flush of
every new role session. Reach for it to clear a workspace that has gone quiet, or to apply a tighter
window once. It stops the database growing; it does not make the file smaller (that needs a
VACUUM, which is not run here because it rewrites the whole shared workspace under an exclusive
lock). Sessions carrying no readable timestamp are of unknown age, so they are kept and reported
separately — a visible gap rather than a silent one.
Documentation
nxc guide [TOPIC]
The guides you are reading, compiled into the binary. No workspace is needed and no network:
nxc guide # list chat's topics
nxc guide core-concepts # print one
nxc guide --json # [{topic, summary}, …] — the agent contractnxs guide fans out over the active modules and lists all three blocks' topics at once. Where a
topic name exists in more than one block — getting-started and commands do — it refuses to pick
for you and names the per-tool commands instead.
Not verbs you type
Two subcommands exist and are hidden from --help, because nobody should type them:
nxc prime— the session bootstrap. Chat's ownSessionStarthook runs it directly, andnxs primefans out to it when you ask the umbrella by hand.nxc tick --thread <id>— a clock's hand. The one-shot job a channel's declaredtimeout:schedules runs it to re-check a thread and, if due, route it through the channel'son_completepolicy. It is idempotent: a thread whose completion was already handled is a clean no-op, never a second wake.
They are named here so that finding them in a process list or a log is not a mystery. Do not build on them.
What is deliberately absent
Half of a trustworthy reference is what it says is not there. These verbs existed and were removed; each entry says what to do instead.
nxc ask— folded intosend --to. A channel is addressed exactly like a persona, and one verb that mints a thread beats two that disagree about whether they do.nxc channels create/dm/join/leave— gone with the raw channels. A channel is a declaration in.nxs-personas/channels.yaml; membership is itsmembers:list. A direct conversation is minted for you the moment yousend --toa persona, under an id derived from the two handles, so there is nothing to create. Cross-project discovery of public channels left the agent surface entirely and is an app's job —nxc list --jsonstill carries the front doors it can see.nxc agents register/list/search/show— a team is declared, not registered.nxc listis the read, and it searches the samejob_title/job_descriptionfieldsagents searchdid. A profile row registered at runtime lived in one machine's database, nothing reviewed it, and it did not survive the run.nxc workflow start/step/status/tick/done/bind/append/show/expect— the declarative run engine is gone, along with the run record. A channel declares its own order (flow: sequential),send --to <channel>starts it, andnxc statusis where an operation's position is read. See channels.send --role/--session—--tois the one way to name a target.--rolecollapsed into it;--sessionis a recorded gap rather than a collapse, and is named as such in the source.send --kind/--priority/--disposition/--model/--deadline, andreply --kind— removed together. The first three let a caller answer a question nobody had asked it; the last two are declared at the persona and at the channel, and a per-call override beside a declared value is two answers to one question. Say how much thinking a job is worth in the persona'sstage:ormodel:, and how long a board may wait in the channel'stimeout:.
Next
- core-concepts — what a thread, a channel and a handle actually are.
- channels — the declaration these verbs address.
- limits-and-safety — the caps and gates around all of the above.