Repository navigation
Make the graph the source of truth for the NodeSpace skill, and decide how guidance reaches external agents #1958
Description
Principle
Everything an external agent is told about NodeSpace should come from one source of truth, not be maintained as a hand-written second copy. Where a fact exists authoritatively — a skill guidance rule, a CLI flag, an enum value, a schema — the skill should render or fetch it, never restate it.
The graph is that source of truth. Guidance is authored and edited in NodeSpace, by the user or the team, not by editing files inside an installed package. How that guidance is delivered to an external agent is an open question this issue must answer — see "Open question: delivery channel" below.
Scope: user-launched external agents
This issue covers the path where the user starts their own agent (Claude Code, Codex, Gemini CLI, OpenCode, Cursor, …) in their own repo, and NodeSpace is available as an installed Agent Skill.
Explicitly out of scope: the PTY path where NodeSpace launches the agent (nodespace session launch, GraphContextAssembler, ADR-032). That is a different setup with its own context-assembly mechanism.
On the in-scope path the skill folder is the only channel. There is no per-session context assembly and no injected CLAUDE.md. If the skill does not activate, NodeSpace never enters the session at all.
Goals for the skill
The skill must cover four things:
- What NodeSpace is — context infrastructure for AI-native development; the repo holds what was built, NodeSpace holds why it was built and how it should be built. (judgment prose)
- How to operate it — the operations available via the CLI. (derivable from clap)
- When to reach for it — use NodeSpace for architecture, design, ADRs, specs, tasks, planning. (judgment prose — see NodeSpace skill instructions don't establish it as the source of truth for docs/specs — only teaches how to call functions #2272)
- How to install it if NodeSpace isn't present. (judgment prose)
Plus the live surface: which schemas/types exist right now, which is user data and must be fetched at runtime, never baked in.
Current state (verified 2026-08-24/25)
packages/skill/SKILL.mdis 534 lines / ~31KB / ~7.8k tokens, exceeding both Agent Skills recommendations (≤500 lines; body <5000 tokens). Some restructuring is warranted regardless of which delivery model is chosen.- One generated region exists:
packages/agent/src/bin/gen_skill_md.rssplices the block between<!-- BEGIN GENERATED: schema-rules -->/<!-- END GENERATED: schema-rules -->(SKILL.md:349-371) from the 13SchemaRule.prosevalues inskill_rules.rs.--writeregenerates,--checkexits 1 on drift. - That block is gated pre-push today — not via
--checkin a workflow, but via the unit testchecked_in_skill_md_generated_block_is_up_to_date(gen_skill_md.rs:159-170), which runs undercargo testinsidetest:all. seed_skill_nodes()(skill_pipeline.rs:228-461) defines eight seeded skills, not six. Six are built from functions interpolatingskill_rules.rs. Two are inline raw strings with no builder fn: Research & Search (:242-281, the largest body, and the un-generated source of SKILL.md's Tool Decision Guide) and Node Creation (:325-339). Any completeness test keyed on function names would silently skip both.skill_updater.rs:27-32already rewrites the Node Creation description at runtime with live custom type names — precedent that live data injection is both wanted and workable.
CLI drift already present
SKILL.md's CLI Reference is a hand-maintained restatement of the clap definitions and has fallen out of date:
| Drift | Evidence |
|---|---|
nodespace session (launch/attach/list/kill) |
0 occurrences in SKILL.md |
nodespace uninstall |
0 occurrences |
nodespace model (list/load/recommended/status) |
mentioned once in passing (SKILL.md:108), never documented |
import dir flags |
--exclude, --include-agent-files, --include-hidden, --no-recursive, --replace, --collection, --use-filename-as-title all omitted |
node query --id |
omitted |
The clap doc comments are already written at the fidelity the skill needs. gen_skill_md.rs's module doc currently asserts the CLI reference "has no analog in the seed skill nodes and stays hand-written" — true as to seed nodes, but clap is its analog. That comment should be corrected as part of this work.
Settled
- The graph is the source of truth for guidance. Users and teams control how the skill instructs by editing in NodeSpace, not by editing files in an installed package.
- Descriptions are hand-tuned, never generated. A skill's
descriptionis the entire discovery surface — under the standard's progressive disclosure, agents load only name+description at startup. Its job is to match a user's phrasing, which is measurement-and-judgment work with no upstream source to render from. Generating it would produce worse text than tuning it. This applies to both the external skill description and the internal seeded skill descriptions, and they should be tuned independently — they face different retrievers. - Schemas and node state are fetched live via the CLI, never baked in. They are user data: per-database, changing as users work.
nodespace schema list --json/nodespace schema get <type> --json. Baking them in would be wrong the moment a user adds a type. This is a fact lookup (ADR-064 rule 4), carries no procedure risk, and is partly done already. - CLI reference content is derived from the clap definitions, not hand-restated — whatever the delivery channel. This closes the drift table above.
Decision: delivery channel is (b) fetched, hybrid-shaped
Decided by @malibio. Recording the reasoning so it is not re-litigated, and so the harness test is run for the right purpose.
What was decided
Procedural guidance is fetched from the graph at activation, not rendered to disk. Hybrid in shape, per the structure already described above:
- Static body carries identity, mental model, when-to-use, and the error path — so a failed fetch degrades to incomplete, never to wrong.
- Facts (schemas, node state) are always fetched live. Unchanged; partly done already.
- Procedural/convention guidance is the part that is fetched. This is where (a) vs (b) actually applied.
How the decision was reached
The choice had two independent gates, and they are not symmetric:
Gate 1 — is fetched guidance reliable across harnesses? Genuinely unknowable without measurement: whether a harness honors fetched text as instructions or paraphrases it first, whether a failed fetch degrades or hangs the turn, whether the content survives context compression. This gate can only rule fetched out, never rule it in.
Gate 2 — is the trust boundary acceptable? Answerable without any test. Under (b), graph content becomes literal agent instructions at skill-level trust inside a third-party harness, and in a shared workspace a teammate can author that content.
Gate 2 is decisive on its own, and it resolved: this is part of the product vision. If it turns out to be wrong, users will tell us, and that signal guides the correction. The propagation argument for (b) stands unanswered under (a) — an org or team changing a convention reaches every developer and every installed harness on next activation, where rendering must push to N developers × M harnesses with stale copies invisible until someone acts on one.
Consequence: the harness test is rescoped
It no longer decides the outcome. It validates the decision and locates where it breaks. Same matrix as specified above — Claude Code and Codex at minimum, with a rendered control arm — but the question changes from "which channel do we pick?" to "where does fetched fail, and what must the static body cover as a result?"
This means the test is still required before the fetch path ships, but it is no longer a blocker on the decision. #2283 blocks the test, not the design.
One implementation constraint that follows from the rationale
"Users will tell us" holds unevenly across the two risks, and the fetch path must account for that:
- Reliability failures are self-reporting. A paraphrased instruction or a hung turn produces a visibly broken session. The feedback loop closes on its own.
- Injection failures are not. Graph content that steers an agent produces an agent that did something subtly wrong — indistinguishable, to the user, from the agent simply being wrong. Nobody files that bug, so absence of complaints is not evidence of absence.
Therefore: fetched guidance must be visible, not gated. Mark its provenance where it lands and let a user see what changed. This adds no gate and does not slow propagation — it converts a silent failure mode into one that can produce the signal the correction strategy depends on. Without it, the feedback loop does not close for that risk.
This is an implementation requirement for the fetch path, not a re-opening of the decision.
Superseded: the original open question and the arguments on both sides (kept for context)
Open question: delivery channel
How does graph-authored guidance reach an external agent? Two candidate models:
(a) Rendered — NodeSpace renders the skill folder (SKILL.md + references/) to disk when guidance changes. The agent reads a static, reviewable, spec-compliant file. Users still author in NodeSpace and never touch the file.
(b) Fetched — SKILL.md carries identity, mental model, and an instruction to fetch procedural guidance from the graph via the CLI at activation. Guidance is scoped to the task rather than delivered whole.
Arguments for (b) — fetched
- Only what's required. A fetch can be scoped to the actual task ("persist an ADR" → the ADR procedure + that schema's real fields), where static files can only be sliced along boundaries guessed at build time.
- Org/team behavior changes propagate without redistribution. If a team changes a convention, pull reaches every developer and every installed harness on next activation. Render must push to N developers × M harnesses, and stale copies are invisible until someone acts on one. This is the strongest argument for (b) and there is no clean answer for it under (a).
- Nothing in the Agent Skills spec forbids it. Body content has "no format restrictions";
scripts/and on-demand resource loading are part of the standard.
Arguments for (a) — rendered
- Reviewability. A rendered artifact is diffable and inspectable before it acts. NodeSpace can show a user what their edit produced.
- Trust boundary. Under (b), graph content — editable by a user, or by a teammate in a shared workspace — becomes literal agent instructions at skill-level trust inside third-party harnesses. This is an architectural concern independent of any measurement, and it is the mechanism prompt injection uses. ADR-038 already treats the internal registry as an attack surface and gates registration; a bare topic-name lookup would bypass even that. If (b) is chosen, it needs an answer: provenance marking, signing, or a user-visible diff on change.
- No runtime dependency at activation. Under (b), a daemon that is down or bound to the wrong database means the agent has a skill it cannot read, at exactly the moment it has decided the skill is relevant.
- Spec alignment. The standard defines activation as reading the body. Fetch-at-activation is permitted but is not something the ~40 adopting harnesses have committed to supporting uniformly.
What we do NOT know, and must not pretend to
No measurement exists for how external harnesses handle this. Specifically unknown:
- Does fetched text get honored as instructions, or summarized/paraphrased before re-entering context (some harnesses compress long tool output)?
- Does a failed fetch degrade gracefully, or hang/error the turn?
- Does behavior differ across Claude Code, Codex, Gemini CLI, OpenCode, Cursor?
⚠️ Do not cite NodeSpace's internal agent measurements as evidence here. Prior results about instruction delivery and skill retrieval (e.g. #1814, #2265, and the matrix runs) were produced by NodeSpace's own in-process agent loop — its inference path, its seeded skill nodes, its Stage-1/Stage-2 routing — and several of those runs had known harness defects of their own (see #2243, #2282). They are evidence about that system. They say nothing about how Claude Code, Codex, or any other external harness behaves when reading a skill, and must not be used to justify a decision on this axis. This mistake has already been made twice in discussion of this issue.
Required before the fetch path ships: manual harness test
This must be tested by hand before the fetch path ships. It no longer decides the channel — that is settled above — it validates the decision and locates where fetched guidance breaks, so the static body can cover those cases. Automated testing is not realistic — it would require driving each harness as a subprocess and inspecting what actually entered its context, which these tools do not expose. A human running the scenario once and reading the transcript is the appropriate instrument.
Minimum test matrix — Claude Code and Codex at minimum, ideally one more (Gemini CLI or Cursor):
- Install a spec-compliant
nodespaceskill folder whose body instructs the agent to fetch guidance via the CLI. - Give the agent a task that should activate the skill (e.g. "write an ADR about X and save it").
- Record, per harness:
- Did the skill activate at all (does the description match)?
- Did the agent actually run the fetch command?
- Did it follow the fetched instructions, or summarize/paraphrase them and proceed on its own?
- With the daemon stopped: does the turn degrade gracefully, or hang/error?
- Was the fetched content visibly present in context, or compressed away?
- Repeat with a rendered static body as the control arm, same task, same harnesses.
Record the outcome on this issue. Findings feed the shape of the hybrid, not the choice: whatever fetched guidance a harness handles badly is content the static body must carry instead.
A hybrid is likely correct regardless — the two models are not mutually exclusive:
- Identity, mental model, when-to-use, and the error path stay in the static body so a failed fetch degrades to incomplete rather than wrong.
- Facts (schemas, node state) are always fetched live.
- Procedural/convention guidance is where (a) vs (b) actually applies.
Fix direction (channel-independent)
- Generalize the generator from one hardcoded region to a registry of
(marker_id, render_fn)pairs, keeping--write/--check. - Cover both source families: guidance from
skill_rules.rsfor all eight seeded skills — lifting Research & Search and Node Creation into the shared pattern or registering them as explicit, justified exceptions; and CLI content by walkingCli::command()from the clap derive. - Enforce completeness structurally. Copy the existing precedent at
agent_guidance.rs:189-199(guidance_corpus()), which iteratesseed_skill_nodes()dynamically so a new skill is auto-covered. A new skill, command, or flag with no corresponding section must fail a test. - Protect judgment prose. Goals 1, 3, 4 have no derivable source and must survive regeneration untouched. The completeness test asserts every source has a rendered section — never that every section has a source.
- Bring SKILL.md within spec recommendations (≤500 lines, <5000 token body) by moving detail into
references/or the graph, per the chosen channel.
Orchestration: bun entry point
Generation is driven through bun scripts, per the repo's Bun-only tooling standard:
bun run skill:gen— regenerate (wraps--write)bun run skill:check— fail on drift (wraps--check), called fromscripts/test-gate.tsbun run build:skill— runs the check, then stages, so a stale skill cannot be bundled
Rendering stays in Rust: both sources of truth (skill_rules.rs, the clap definitions) are Rust, and reading them from TypeScript would mean re-encoding them, recreating the drift this issue exists to kill. Bun is the interface; Rust is the implementation behind it.
Correction: which direction "source of truth" runs
An earlier reading of this issue implied the Rust constants should be assigned from the graph. They must not be — the arrow points the other way, and cannot be reversed:
skill_rules.rs ──> seed_skill_nodes() ──> graph skill nodes (created on first run)
└─> gen_skill_md ──> SKILL.md + references/
Two hard blockers:
- Bootstrap.
seed_skill_nodes()is what creates the graph's skill nodes on a fresh install. If the constants read from the graph, seeding has nothing to seed from — the graph is empty until it runs. - Build time. Generation runs with no daemon and no database, which is exactly why the drift check can live in the pre-push gate. It cannot read a graph that does not exist yet.
So the principle splits in two, and conflating them is what produced the confusion:
- Shipped defaults must originate in code. Something has to exist before the graph does.
skill_rules.rsis the seed, not a second copy of the graph. - Runtime guidance — what a user or team edits in the app afterwards — legitimately lives in the graph. This is the part still unbuilt.
The real gap is not the constants. It is that guidance does not flow in either direction after install:
- a user's graph edit never reaches the shipped
SKILL.mdor an external agent, and - a
skill_rules.rschange never reaches an existing install, because seeding skips by node type (see Related, below).
Neither is fixed by making a Rust constant read from a database. Both are downstream of the delivery-channel decision.
Acceptance criteria
Checked items were delivered by PR #2284 (the channel-independent half) and #2296 (publishing, closing #2283).
#2283 is closed and the skill is published at NodeSpaceAI/nodespace-skill, pushed by an automated release sync. Claude Code activation is verified on a real machine. What remains is the rest of the harness matrix and the fetch path itself — see "Handoff" at the end.
- Delivery channel decided: fetched, hybrid-shaped — see "Decision" above.
- Manual test run against at least Claude Code and Codex, covering activation, fetch-follow-through, daemon-down degradation, and a static control arm. (Validates the decision and shapes the hybrid; no longer decides the channel.)
- Fetched guidance is visible, not gated: provenance marked where it lands, and a user can see what changed. Required so injection-class failures produce signal rather than passing silently — see the Decision rationale.
- Shipped default guidance has exactly one source, and everything else renders from it.
skill_rules.rsis that source for schema rules; the clap definitions are that source for the CLI surface;BUILTIN_RELATIONSHIP_NAMESfor relationship names. Nothing restates them by hand. - A user's or team's own graph edit reaches an external agent. The runtime half — now unblocked by the channel decision above. Note the prerequisite in Related: seeding skips by node type, so guidance changes never reach an existing install.
- Skill descriptions remain hand-tuned and are explicitly excluded from generation.
- The frontmatter
descriptioncarries docs/specs/architecture/planning vocabulary. - The description's effect on activation is verified by hand in at least one real harness. Claude Code: PASS — verified on Publish the NodeSpace skill so users can actually install it: public import repo + automated release #2283 (2026-08-25) in a real session on a real machine: the skill loaded from the task description alone, with no explicit naming, then self-bootstrapped the CLI via its own preflight and drove real searches against a 321-node database. The remaining harnesses are covered by the test matrix below.
- Spec-compliant frontmatter ships for all four installer targets, not Claude Code only (
agents.ts:31-54currently prepends it for Claude Code alone, leaving three targets spec-invalid). - SKILL.md's opening definition uses the vision framing (repo holds what; NodeSpace holds why and how).
- Schemas/types are obtained live via the CLI, never baked into shipped content.
- CLI reference content derives from the clap definitions;
model,session,uninstall, the fullimport dirflag set, andnode query --idall become reachable as a result. (All five drift rows verified closed.) - All eight seeded skills are covered, not six.
- Adding a new seeded skill, CLI command, or flag without corresponding coverage fails a test, via dynamic enumeration rather than a hand-maintained list.
- Judgment prose (goals 1, 3, 4) survives regeneration untouched.
-
SKILL.mdbody is within spec recommendations (≤500 lines, <5000 tokens). - The drift check runs via
bun run skill:checkinscripts/test-gate.ts. -
gen_skill_md.rs's module doc no longer claims the CLI reference has no generatable analog.
Absorbed from #2272: description and definition
#2272 ("skill instructions don't establish NodeSpace as the source of truth for docs/specs") is folded into this issue, because its fix and this issue's design are the same artifact. Its substance:
The frontmatter description is the fix, not the body. Under progressive disclosure the body loads only after the skill activates. Instructions added to the body change what an agent understands once loaded, not whether it loads. On the user-launched path the skill is the only channel — if the description doesn't match, NodeSpace never enters the session.
The current description (packages/skill/src/agents.ts:16-27) is entirely personal-memory framing — "remember something for later", "check nodespace", "what did we save" — with no spec, ADR, architecture, design, requirements, or planning vocabulary.
The definition also under-sells the product. SKILL.md's opening line ("a local-first knowledge graph that stores notes, tasks, and structured data") would fit Obsidian. strategy/vision.md says: "context infrastructure for AI-native development... Repositories contain what was built. NodeSpace contains why it was built, how it should be built." Principle 1: "Code is artifact. Context is infrastructure." An agent told NodeSpace holds "why it was built" needs far less separate instruction to check it before writing a spec.
Starting point for the description (tune, don't merge on aesthetics — max 1024 chars):
name: nodespace
description: >
Context infrastructure for AI-native development. Read and write the
NodeSpace knowledge graph — the durable record of why a system was built
and how it should be built: specs, architecture decisions, ADRs, designs,
plans, standards, tasks, and findings. Use before writing or changing a
spec, ADR, design doc, or plan; when you need the reasoning or constraints
behind existing code; when recording a decision or discovery that should
outlive this session; or when asked to "check nodespace".
allowed-tools: Bash(nodespace:*)Frontmatter portability. The spec allows exactly six fields: name, description, license, compatibility, metadata, allowed-tools. Claude Code accepts more (argument-hint, disable-model-invocation, user-invocable, disallowed-tools), but other distribution paths hard-error on unknown keys. name must match the parent directory name (skills/nodespace/ → name: nodespace).
Follow-on noted in #2272: once the skill states this, teams' own CLAUDE.md files likely need to point agents at NodeSpace — a repo whose CLAUDE.md never mentions it won't have agents reach for the skill even after this lands. Out of scope here, but a real dependency.
Related
- Markdown list bullets are lossy through the skill-guidance seed/render round-trip #1974 — markdown bullets are lossy through the skill-guidance seed/render round-trip. Same pipeline; a renderer will hit the same round-trip.
- Seeded skill guidance teaches parameter name relation_type but the tool schema and executor require relationship_type #2200 — seeded guidance teaches
relation_typewhile the tool schema requires otherwise: a live instance of the drift class this issue prevents. Also, the universal relation names (member_of,has_child,mentions,has_role) atskill_pipeline.rs:149appear nowhere in SKILL.md. - Seeded guidance never updates on existing installs — seeding skips by node type, so guidance changes never reach an existing install. This is a prerequisite for any model in which users control guidance through NodeSpace; without it, "the user controls it in the app" is only true for fresh installs.
- Distribution — the skill is intended to be published to a public repo users import into their agent.
@nodespaceai/skillcurrently 404s on npm (Skill auto-install fails: npx 404, @nodespaceai/skill not found on npm registry #2271; Publish @nodespaceai/skill to npm: distributable skill package for PTY agent marketplaces #1184 was closed as completed without the package ever being published), so today a user cannot install this at all. That repo must be generated output, not hand-editable, or it becomes another representation. File separately. - Lower-priority: the
NO_NAME_TITLE_FIELDrule never explains why (title derives fromcontent, or fromtitle_templateinterpolation) on either surface.
Handoff — state as of PR #2284
PR #2284 is open and approved, implementing the channel-independent half. It is a complete, self-consistent change: nothing in it presumes either delivery channel, and it can merge before the channel question is settled. Merging is the maintainer's call.
Branch: issue-1958-skill-source-of-truth (7 commits, all pushed). The commit messages carry the reasoning — read them before the diff; several record why an obvious approach does not work, which is not recoverable from the code alone.
What is already built
| Capability | Where |
|---|---|
Multi-region generator (marker_id, file, render_fn) |
packages/cli/examples/gen_skill_md.rs |
| Coverage + drift guards (8 tests) | packages/cli/tests/skill_md_generation.rs |
| Shipped skill body (270 lines, ~3.9k tokens) | packages/skill/SKILL.md |
| On-demand tier (full CLI reference) | packages/skill/references/cli.md |
| Shared spec-valid frontmatter, all four targets | packages/skill/src/agents.ts |
bun run skill:gen / skill:check; build:skill runs the check first |
root package.json |
Three generated regions today: schema-rules, cli-surface, builtin-relationships — all in references/cli.md.
Why the generator lives in packages/cli/examples/
It must read both nodespace_agent::skill_rules and the clap definitions. The dependency graph is nodespace-cli → nodespace-daemon → nodespace-agent, so an equivalent binary in nodespace-agent (its original home) cannot reach clap without a cycle. nodespace-cli is downstream of both. It is an example rather than a [[bin]] so cargo build --release does not ship a dev-time tool. Do not "tidy" this back into nodespace-agent — it will not compile.
The one-way contract (do not invert it)
Every source must have a rendered section. It is never asserted that every section has a source. Judgment prose — what NodeSpace is, when to reach for it, how to install it — has no upstream and must survive regeneration untouched. A test demanding the reverse would delete exactly the content a generator cannot produce.
Corollary, learned the hard way: assertions about hand-written prose must not read generated text. every_seeded_skill_is_represented uses skill_prose_only(), which strips every generated region. An earlier version read the full corpus and could not fail — replacing the entire body with a 3-line stub left it green, because the generated CLI surface contains every command string by construction. That weak assertion was also concealing a real gap (nodespace node delete appeared zero times in hand-written prose).
Verification standard used here
Every guard in this PR was checked by reverting the fix and confirming the test fails. Three review rounds found four defects, and all four were green tests passing for the wrong reason — none was visible by reading the diff. If you add a guard, mutate it before trusting it.
What remains, and the order
1. #2283 first — publish the skill. @nodespaceai/skill 404s on npm, so today a user cannot install this at all. Nothing below can start until it can.
2. Run the manual harness test. The matrix is specified above under "Required before merging: manual harness test". Claude Code and Codex at minimum. Automation is not realistic — it would require driving each harness as a subprocess and inspecting what actually entered its context, which these tools do not expose.
⚠️ Do not cite NodeSpace's internal agent measurements as evidence here. See the warning above. This mistake has been made repeatedly on this issue.
3. Decide the delivery channel and record the outcome here. If harness behavior varies materially, (a) rendered is the only option that works everywhere and the decision is made for us. A hybrid is likely correct regardless.
4. Then the runtime half becomes reachable: a user's or team's graph edit reaching an external agent. Note the prerequisite in Related — seeding skips by node type, so guidance changes never reach an existing install. Until that is fixed, "the user controls it in the app" is only true for fresh installs.
Two open judgment calls for the maintainer
- The
descriptiontext is product positioning. PR Make the graph the source of truth for the NodeSpace skill (channel-independent half) #2284 uses this issue's own proposed wording verbatim rather than inventing new copy. Worth a review pass on the words themselves. - One latent limitation is documented, not fixed: directory pruning in
uninstall()is one level deep. Correct for every shim today; a futurereferences/api/cli.mdwould need leaf-upward pruning. The comment atpackages/skill/src/installer.tssays so.
Activity
[-]Structurally enforce parity between internal skill guidance and external SKILL.md[/-][+]Generate SKILL.md from its sources of truth: skill guidance + CLI definitions, structurally enforced[/+][-]Generate SKILL.md from its sources of truth: skill guidance + CLI definitions, structurally enforced[/-][+]Make the graph the source of truth for the NodeSpace skill, and decide how guidance reaches external agents[/+]malibio commentedon Aug 24, 2026
Baseline (worktree issue-1958-skill-source-of-truth): Frontend 227 test files passed, 4794 tests passed, 0 failed.
malibio commentedon Aug 25, 2026
Status: PR #2284 open and approved; 13 of 17 criteria met
Issue body updated — criteria checked off, and criterion 3 corrected (see "Correction: which direction 'source of truth' runs").
The correction matters for whoever picks this up. An earlier reading implied the Rust constants in
skill_rules.rsshould be assigned from the graph. They must not be, and it is not a preference:seed_skill_nodes()is what creates the graph's skill nodes on a fresh install. If the constants read from the graph, seeding has nothing to seed from.The principle splits in two: shipped defaults must originate in code (something has to exist before the graph does), while runtime guidance — what a user edits in the app afterwards — legitimately lives in the graph. The second is the unbuilt half, and it is the delivery-channel question, not a source-of-truth question.
The real gap is that guidance flows in neither direction after install: a user's graph edit never reaches the shipped SKILL.md, and a
skill_rules.rschange never reaches an existing install (seeding skips by node type). Neither is fixed by pointing a Rust constant at a database.Remaining work is blocked on #2283, which is now in progress
All four unchecked criteria trace to one root cause — the delivery channel cannot be decided without a manual harness test, and that needs an installable skill.
@nodespaceai/skillstill returns HTTP 404 on npm as of today.#2283 is now assigned and in progress, so the two tracks are running in parallel as intended:
PR #2284 does not need to wait. It presumes neither delivery channel and is self-consistent either way — the generator publishes a skill folder regardless of whether the body ends up full or a stub. Holding it open only risks conflicts.
A Handoff section at the end of the issue body carries what a fresh developer needs: what is built and where, why the generator lives in
packages/cli/examples/(a dependency cycle makes the obvious placement impossible — do not "tidy" it back), the one-way contract and why it must not be inverted, and the verification standard. The seven commits onissue-1958-skill-source-of-truthcarry the reasoning; read them before the diff.malibio commentedon Aug 25, 2026
Decision made, and handing off to @mstomar125
@mstomar125 — you have #2283 (publish the skill), and this issue depends on it, so it makes sense for both to sit with you. Reassigned.
The channel decision is settled: fetched, hybrid-shaped
The issue body now records this under Decision (the old "Open question" section is preserved collapsed underneath, so the arguments on both sides are still there).
Short version of how it resolved — worth reading, because it changes what the harness test is for:
The choice had two gates. Gate 1 (is fetched guidance reliable across harnesses?) genuinely needs measurement — but it can only rule fetched out, never rule it in. Gate 2 (is the trust boundary acceptable?) needed no test at all, and it decided: this is part of the product vision, and if it's wrong, user signal will guide the correction. The org-propagation argument for fetched stands unanswered under rendered.
So the manual harness test is rescoped. It no longer picks the channel. It validates the decision and locates where fetched guidance breaks — and whatever a harness handles badly becomes content the static body must carry instead. Still required before the fetch path ships; no longer a blocker on the design.
One constraint that falls out of the rationale
Added as an acceptance criterion: fetched guidance must be visible, not gated — provenance marked where it lands, and a user able to see what changed.
The reason is that "users will tell us" holds unevenly. Reliability failures are self-reporting: a paraphrased instruction or a hung turn produces a visibly broken session. Injection-class failures are not — graph content that steers an agent looks, to the user, like the agent simply being wrong. Nobody files that bug. Visibility isn't a gate and doesn't slow propagation; it just makes that failure mode capable of producing the signal the correction strategy depends on.
State: 13 of 18 criteria met
PR #2284 is open and approved — the channel-independent half. It presumes neither delivery model and can merge independently; @malibio's call on timing.
What's left, in order:
@nodespaceai/skillresolves (currently HTTP 404).Before you touch the code
Read the Handoff section at the end of the issue body, and the seven commit messages on
issue-1958-skill-source-of-truthbefore the diff. Two things in particular that aren't recoverable from the code:packages/cli/examples/because of a dependency cycle (cli → daemon → agent). It cannot move back tonodespace-agent— it won't compile.Also: don't cite NodeSpace's internal agent measurements as evidence about external harnesses. That warning is in the issue body and the mistake has been made repeatedly here.
Happy to walk through any of it.
malibio commentedon Aug 25, 2026
PR #2284 merged —
5f29ee32Squash-merged to main; branch deleted. Verified on merged main: 8/8 skill-generation tests pass and the generator reports no drift.
This issue stays open. #2284 was the channel-independent half only — 14 of 18 criteria. Status set back to In Progress, assigned to @mstomar125.
What's now on main
packages/cli/examples/gen_skill_md.rs, rendering three regions from three sources of truth (skill_rules.rs, the clap definitions,BUILTIN_RELATIONSHIP_NAMES)packages/cli/tests/skill_md_generation.rs— 8 coverage/drift guards, dynamically enumeratedpackages/skill/references/cli.md— the on-demand tier; body is now 270 lines / ~3.9k tokens, inside specbun run skill:gen/skill:check;build:skillruns the check first, and it's already in the pre-push gateRemaining, in order
@nodespaceai/skillstill 404s, and nothing below can start until it resolves.@mstomar125 — the Handoff section at the end of the issue body is the thing to read first, along with the merged commit's message. The two non-obvious constraints: the generator can't move back to
nodespace-agent(dependency cycle — it won't compile), and the one-way contract must not be inverted (every source needs a rendered section, never the reverse — judgment prose has no upstream and must survive regeneration).mstomar125 commentedon Aug 25, 2026
First real result against the required manual harness test matrix (Claude Code leg), run on a real machine now that #2283 has made the skill actually installable:
nodespaceCLI commands (not from memory)Note on scope: the currently-published skill body is still the static/rendered content from #2284 — this run validates basic activation + CLI-driving, not yet the fetch-guidance behavior the decided channel (fetched, hybrid-shaped) will eventually need, since that runtime mechanism isn't built yet. Still useful signal: the description-driven activation and real tool-use both worked cleanly on the first real harness available.
Separately, an interesting adjacent finding surfaced during this test: the agent encountered a node in a shared database phrased like injected system instructions and correctly refused to act on it — good independent evidence of injection resistance, though whether that specific node is malicious or legitimate seeded "Agent Guidance" content is still being checked separately (authorship/write-access, not a #1958 concern) — noting here only because it happened during this test run, not as a finding about the skill itself.
mstomar125 commentedon Aug 27, 2026
Status check, for accuracy: this is not done yet — 15/18 criteria checked, 3 remain, and confirming that here so it doesn't get closed prematurely.
Remaining:
Not starting the fetch-path implementation yet, per the issue's own sequencing — the harness matrix result is supposed to shape what the static body needs to cover, and that matrix is only 1/3+ done. Will pick up the seeding-skips-by-node-type prerequisite separately since it's real, self-contained work that doesn't depend on the channel decision either way.
mstomar125 commentedon Sep 1, 2026
Status check (2026-09-01): assessed what's actually left to close this issue.
Genuinely blocked, not attemptable from this environment: the acceptance criterion "manual test run against at least Claude Code and Codex" needs the Codex CLI, which isn't installed/available here (
which codex→ not found). Only the Claude Code leg of the harness matrix can run from this session, and that's already done (verified 2026-08-25, recorded on this issue).Genuinely substantial, not yet built: the fetch-at-activation mechanism itself. Checked directly —
SKILL.mdcurrently has no instruction telling an agent to fetch procedural guidance from the graph at activation (grepped for it, nothing there), and there's nofetch_guidance-shaped code anywhere inpackages/skill/packages/cli. The channel decision (fetched, hybrid-shaped) is made and recorded, but implementing it — plus the "visible, not gated" provenance-marking requirement, plus the runtime half (a user's/team's graph edit reaching an external agent) — is real, unbuilt feature work gated on a separate prerequisite (seeding currently skips by node type, so guidance changes never reach an existing install even once the fetch mechanism exists). This isn't a quick add-on to something already there; it's the epic's actual remaining body of work.Leaving this "In Progress" rather than force a rushed partial implementation of the fetch mechanism alongside other work today — that's real design+build work deserving its own focused pass, not something to bolt on in the margins. Recommend picking it up as its own dedicated task when there's room to do it properly, and revisiting whether Codex access can be arranged for the harness-testing acceptance item.
mstomar125 commentedon Sep 9, 2026
Closing — 15 of 18 acceptance criteria are genuinely done (verified across 2026-08-24/25/27, 2026-09-01 status checks on this issue). The remaining 3 aren't a small residual: one is a testing-tooling gap (Codex/Gemini CLI/OpenCode harness legs, needs a machine with those installed), and two are real unbuilt feature work (the fetch-at-activation mechanism itself, plus its runtime half and seeding prerequisite) — not something to leave this mostly-done issue open indefinitely for. Split out as #2532, which tracks all 3 accurately (not glossed as "just needs more testing" — the fetch mechanism doesn't exist yet, that's real design+build). Superseded by #2532.
Add fetch-at-activation guidance mechanism for external agent skill s…