The PM of one repo commissions the PM of another, and the answer comes back (nxf 6j6v.70dy, first slice) - #14
Merged
Merged
Conversation
…`, not the shell (nxf 6j6v.ewbj) A trigger that carries a reply obligation granted the bare `Bash`, which the sidecar auto-approves, so a persona declared with `tools: []` held an unscoped shell whenever it owed an answer. The grant is now decided from the declaration: - `tools` absent: the bare `Bash`, as before; - a list naming `Bash` or `Bash(nxc reply:*)`: nothing more; - any other list, `[]` included: `Bash(nxc reply:*)`. The sidecar puts the tool a granted or declared rule names into the SDK's base `tools` list and the rule itself into `allowedTools`. Measured against the pinned SDK 0.3.215: the heredoc and one-line reply forms run; `nxc send`, writes, `gh`, `curl`, chains, pipes, redirections and `$(...)` are refused. A live smoke shows a `tools: []` persona answering and failing to touch a file; it goes red when the rule is widened again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Sipejn8YQhKgngxeHpMhT
…ine (nxf 6j6v.q32p, 6j6v.4gp2) The thinnest path through every layer, proved between two real repositories with the real sidecar and the service running: the PM of one repo asks the PM of another and passes the answer on to its owner. - A workspace has a stored name (`[workspace] name` in config.toml, derived from the git origin on first use, `nxs name`); `<owner>/<repo>/<persona>` is an address. - `nxc send --to <address>` records the commission in the caller's own log and hands the border thread over; `nxc handover` (hidden, run by `send`/`reply` and by the service on its pass) copies only that thread's ops both ways and acts in its own workspace: the receiver admits and starts its persona, the caller is woken. - `addressable.external` admits callers from other workspaces by workspace and role; `nxs sync trust add --workspace` trusts a neighbour by name. Three existing defects on that path, fixed here because the scenario cannot pass with them: spawned sessions did not inherit NXS_SERVICE_INSTANCE; a wake on the way back granted no `nxc reply` to a persona without `tools:`; the CLI timer booked a held delivery as a channel tick on a session id, so the service never delivered it. And a sub-round whose answer is held for a busy commissioner now still counts as in flight, so its sidecar no longer reminds it into a substituted reply. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Sipejn8YQhKgngxeHpMhT
…ides (nxf 6j6v.t5xb, 6j6v.4gp2, 6j6v.szc5) - A border thread follows the four A2A states in both workspaces, derived from its newest message, and `nxc status` / `Engine::status` show it with the other side's name on the row; in the receiver the border thread is the root of its operation. - Two hours without a sign of life (a message on the thread, or the receiver's persona transcript) cancel a working border thread: the caller is woken with the reason, the receiver stops its persona. - Withdrawing an operation takes its border threads back in the other workspace too, and an open border thread counts as work to withdraw. - Tests at the engine seam between two real workspaces: the scenario, the coder's "role not admitted", "not trusted" on either side, a foreign pm against `personas: [pm]`, a question routed to the commissioner, the depth cap across both workspaces, only the border thread crossing, no echo, the deadline and withdrawal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Sipejn8YQhKgngxeHpMhT
…(nxf 6j6v.70dy) - Personas guide (EN/DE): "From another workspace" — the address, `external`, trust on both sides, the refusal reasons, the states, the deadline, withdrawal, and the roll-out caveat for engines older than `external`. limits-and-safety says what outside text means for a persona's declaration. - E5c section 1 and 6: the owner lifted the cross-repo clause on 2026-10-03. - Changelog: the slice (facade: breaking — new pub fields on constructible structs) and the held-delivery fixes. - A withdrawal cancels border threads without waking anybody; the deadline wakes the commissioner through a call of its own, so the write-surface gate sees what each can start. A question asked across the border is not NEEDS DECISION in the receiving workspace. Border tables are declared machine-local. - Tests: the nxs binary with two real git repositories (name, trust by name, the handover as a child process, status on both sides); persona_chat's border guard; the held-answer rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Sipejn8YQhKgngxeHpMhT
…6v.4gp2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Sipejn8YQhKgngxeHpMhT
…ins (nxf 6j6v.4gp2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Sipejn8YQhKgngxeHpMhT
…ests (nxf 6j6v.70dy) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Sipejn8YQhKgngxeHpMhT
…6v.4gp2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Sipejn8YQhKgngxeHpMhT
This was referenced Oct 4, 2026
Merged
cabcookie
added a commit
that referenced
this pull request
Oct 4, 2026
…s (nxf 6j6v.7k58, 6j6v.dcpd) (#17) * One persona definition for every repository: a user-level declaration folder, merged per name (nxf 6j6v.7k58) Beside a workspace's own `.nxs-personas/`, every workspace reads the user-level folder `<service home>/personas` — `~/.nexusflow/personas` for the installed suite and an embedding app, `~/.nexusflow-<name>/personas` for a development build. A persona or channel the repository declares hides the user-level one of the same name; everything else is added. - The merge lives in `Definitions::resolve` alone, so the CLI, every `Engine` verb, `nxs prime` and a spawned persona's own `nxc` see one team. Duplicates are judged per folder (naming it); handle form and flow cycles on the merged team. - `DeclarationSource` carries `user_path`, `from_user` and `shadowed`, all off the wire when there is no user-level declaration, so such a machine reads exactly as before (point 8). - `nxc list` / `Engine::directory` stamp `origin: user` per entry and print the account whenever the user-level folder takes part; `nxs prime` says it under "Declarations". - A user-level persona's session start says where its declaration lives: a relative path in a prompt means the repository the session runs in, never the declaration's folder. - The freeze projects the merged catalogue, so it holds for user-level hurdles and prompts; proved by an edit mid-operation. - The readers that loaded the folder directly (thread/search channel policy, list, prime) go through the merged catalogue now. Proved through the real binary: two repositories without a pm file both run the user-level pm, the commission scenario runs on it, a repository file hides it out loud; the live test with real sessions now uses one user-level pm for both repositories (22 s, green). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Sipejn8YQhKgngxeHpMhT * Border follow-ups from the review of PR #14: revoked trust is named, a stuck handover is stopped, the cooperative depth is written down (nxf 6j6v.dcpd) 1. When the caller's workspace stops trusting the receiver while a border thread is open, the receiver's newest message is unvouched and steers nothing. The caller's coordinator now cancels at once with "the answer arrived from <peer>, a workspace no longer trusted here" and wakes the commissioner, instead of "no sign of life" two hours later. 2. The depth across the border rests on the trusted coordinator's stamp — within the trust model (spec section 4); said where `external` and the depth guard are documented. 3. A peer handover past HANDOVER_WAIT is killed and reaped, so the long-lived service no longer accumulates stuck children. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Sipejn8YQhKgngxeHpMhT * Pin the in-process home through pinned_home_env, and mark the dcpd fragment's facade impact The two single-test binaries set HOME by hand, which the source gate every_pinned_home_pins_the_instance refuses: a pinned home without the instance resolves another directory under this repo's .envrc. They now apply nxs_test_support::pinned_home_env() to their own process. The dcpd fragment says `facade: changed` (a behaviour change behind unchanged signatures: an unvouched answer now cancels the border thread). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Sipejn8YQhKgngxeHpMhT * Mitigate the review of PR #17: hermetic user-level folder, channel-only policy reads, named revoked-trust cause, group stop - A build reads a user-level folder only under a NAMED development instance, never the production one, and an unresolvable home or instance means no folder rather than an error (Code #2, #3; Integrity #3, #4). Guard test: a build under the production instance does not see personas planted in the production folder. - An existing but unreadable user-level folder is an error, not a silence (Integrity #5). - `threads show`, `search`, `Engine::thread` and `Engine::search` read the merged channels only (`merged_channels`), so a malformed persona file cannot make a thread unreadable (Code #1). - `nxc prime`, `Engine::prime_as` and `nxs prime --persona` resolve the catalogue once (`prime_with`) (Code #5). - The revoked-trust cancel fires only on an answer not yet served, so a question delivered before the revocation does not cancel; two negative controls, mutation-checked (Test #1, Integrity #7). - A stuck peer handover runs in its own process group, which is killed as a whole and then reaped by polling; the sidecar it started has a group of its own and survives; start-idempotence rests on the admission claim (Code #7, Integrity #6). Test drops the wall-clock assertion and gates `true` on unix (Test #3, #4). - The freeze test now edits a user-level hurdle too (Code #6); loader failing cases for channels, a missing and an unreadable folder, a dangling member (Test #2). - A user-level `external` admits only from workspaces the running repository trusts — tested through the binary and written in the guide (Integrity #2); the guide no longer calls a hiding "never silent" but says where it is shown (Integrity #1, the lock question filed as 6j6v.3mgy). - Stale prose and wraps (Code #4, #9). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Sipejn8YQhKgngxeHpMhT --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this does
On one machine, a persona of one workspace commissions a persona of another workspace and gets the answer back. Both repos stay what they are: the receiver runs its persona in its own working copy, with its own board and memory, and only the border thread crosses.
Tickets:
6j6v.ewbj(first, it is the precondition),6j6v.q32p,6j6v.t5xb,6j6v.4gp2,6j6v.szc5. Epic6j6v.70dy. Specification and direction: the coordinator workspace's2026-10-03-schnitt-1-pm-spricht-mit-pm-design.mdand…-standards-und-foederation-richtung.md.The pieces
Bash, sotools: []meant a full shell. It is nowBash(nxc reply:*)for any declared list withoutBash.toolsabsent andtools: [Bash]behave as before. Measured against the pinned SDK 0.3.215: the heredoc and one-line reply forms run;nxc send, writes,gh,curl, chains, pipes, redirections and$(…)are refused. A live smoke shows atools: []persona answering and failing totoucha file, and it goes red when the rule is widened.<owner>/<repo>([workspace] namein.nxs/config.toml), derived from the git origin without the host on first use.nxs name [<owner>/<repo>]prints or sets it.normalize_remotemoved tonxs_foundation::workspace_name, so the stream id and the name are one parse.<name>/<persona>is an address.addressable: { external: ["*/pm", "nxsflow/manufakt-io/pm"] }is a new variantAddressable::OnlyWithExternal, added as a variant rather than a field soOnly { .. }matches in embedding code don't break.nxs sync trust add --workspace <name>trusts a neighbour's replica key by name. Refusals name their reason: workspace unknown, not on this machine, not trusted, role not admitted, no such persona, depth limit reached.crate::border. The hidden verbnxc handover(Engine::handover) copies only the border thread's ops between the two logs viaStore::apply: signatures are verified, and op ids make it idempotent, so there is no echo. Each run acts only in its own workspace.send/replyon a border thread run it at once and then the peer's handover as a child process in the peer's root. The service runs it every 5 s where.nxs/border-openis present. Depth travels in the signedrefs.border. Two hours without a sign of life cancel the thread.nxc withdrawtakes it back on both sides.StatusThread.border {peer, direction, state, reason}with the four A2A states, in both workspaces and onEngine::status. The CLI shows— across the border to test/beta: completed.Decisions the specification does not make (also noted on the epic)
sendrefuses up front, with the command to run, when the caller's workspace does not trust the receiver.addressablestays strict (deny_unknown_fields). An older engine fails onexternal:. This is stated in the personas guide (EN/DE) and in the changelog. No declaration in this repo carriesexternal:.PARENT_WITHIN_THE_BORDER), so the operation does not hang under a phantom. A question asked across the border is not shown as NEEDS DECISION there.Existing defects this path ran into, fixed here
NXS_SERVICE_INSTANCE, so a dev build's persona read the production service home.ChainMove::Unwind) never carried the obligation the woken session still has. A persona withouttools:was woken with an answer and then refused its ownnxc reply. This applies in-house as much as across the border.schedule_delivery/schedule_resume. The trait default booked a held delivery as a channel tick on a session id ("no such thread"), so on the command line the service never delivered a held answer.waiting_on_sub_round).Evidence
Live, two real git repos (
test/alpha,test/beta), real sidecar and model, a dev-instance service (2026-10-03):send/replyand needed no service.--escalate. It woke alpha's pm, which escalated to its owner. Nothing in beta was woken.NXC_HOP=28: alpha 29 → beta 30 → alpha 31 → beta 32, then "hop 33 exceeds the cap of 32", and the answers unwound back to the owner.tools: []persona answered and could nottouch.Deterministic:
crates/chat/tests/a_persona_commissions_across_the_border.rs: 16 tests at the engine seam between two real workspaces. They cover every acceptance point of the specification's section 9 except 7 and 9, plus no echo, the deadline, withdrawal, and a held answer.crates/nxs/tests/two_workspaces_on_one_machine.rs: the real binary. It coversnxs name, trust by name, the peer's handover as a child process, and status in both directories.Local gates run:
cargo fmt --check.cargo checkfor chat and nxs.Not in this PR
6j6v.7k58(one definition for every repo; acceptance point 7) and the manufakt.io desktop app (point 9).6j6v.v0pj, which asks the owner whether a fallback is wanted. With the service it works; it is measured above.🤖 Generated with Claude Code
https://claude.ai/code/session_018Sipejn8YQhKgngxeHpMhT