Skip to content

Seed built-ins from fixed-UUID tables with Markdown bodies, order agent guidance, and link skills to their schemas #3418

Closed

Description

@malibio

Overview

Every built-in seeded node (agent-guidance, skills, tools, core plays, bundled playbook plays) gets a fixed literal UUID and is defined in one Rust seed table per kind. Long text bodies move out of Rust string literals into plain .md files (no frontmatter). The local agent's resident system prompt is assembled from the agent-guidance seeds in table order, instead of in the order the database happens to return them.

Problem Statement

  1. The resident prompt is assembled in reverse.
    • PromptAssembler::fetch_prompt_overrides() (packages/agent/src/prompt_assembler.rs) gets the guidance nodes through node_ops::query_nodes, which always sorts OrderBy::CreatedDesc (packages/core/src/ops/node_ops.rs, filter.with_order_by(OrderBy::CreatedDesc)).
    • The five seeds are created in list order about 1 ms apart. So the live prompt starts with "Tool Call Formatting" and ends with "Core Identity", the reverse of the order the seed list defines.
    • The order is also fragile:
      • a timestamp tie falls back to id ASC, and the ids are random UUIDs;
      • a replaced or user-created root node jumps to the front;
      • the 50-node cap drops the oldest node first.
  2. The golden test doesn't catch this. packages/agent/tests/it/prompt_assembly_snapshot.rs (assemble_resident_system_prompt) iterates seed_agent_guidance_nodes() in its defined order, so the golden pins an order production never produces.
  3. The guidance set is open. Any root agent-guidance node a user creates is silently added to the system prompt.
  4. Seeded nodes have random ids. They're matched on re-seed by _seed.key (their title), not by id. Two devices that each seed the same built-in under different random UUIDs produce two distinct nodes.
  5. Plays use slug ids, breaking the UUID rule. Examples: play-core-parent-task-completion (packages/core/src/playbook/core_plays.rs), and spec-driven-plan-approval-gate and its siblings from the bundled spec-driven playbook. create_node requires a UUID for every type except date and schema (packages/core/src/services/node_service/crud.rs, "Production nodes must use UUID format"). Mentions only resolve UUID and date ids (is_valid_node_id).
  6. Seed content is Rust string literals. That includes markdown_content in seed_agent_guidance_nodes(), seed_skill_nodes() and seed_tool_nodes() (packages/agent/src/{prompt_assembler,skill_pipeline}.rs). The bundled playbook skills already use .md files via include_str!, but with a hand-parsed frontmatter header (packages/core/src/methodology/skills.rs). The result is two seed formats.

Proposed Solution

Seed tables

  • One const table per seed kind, holding the node's fixed literal UUID, its title and any metadata, plus body: include_str!("…/name.md") where the node has a body. A comment names each id. Array order is the defined order.
  • Bodies are plain Markdown: no frontmatter and no Rust string assembly. What the file says is what the node holds.
  • Shared rule text. The constants in packages/agent/src/skill_rules.rs become .md fragments. The guidance bodies, skill bodies, tool descriptions (packages/agent/src/local_agent/tools.rs) and the generated SKILL.md (packages/cli/examples/gen_skill_md.rs) all include the same fragment files, so each rule still has one source. Fragments are resolved when the seeds are built, through a small include marker.
  • The bundled playbook skills (packages/core/src/methodology/skills/*) drop their frontmatter and move into tables too, so the repo has one seed format.
  • Tools stay defined in Rust, because each is bound to a handler with a typed parameter schema. Their table only gains the fixed UUID, and their description text comes from the shared fragments.

Identity and reconciliation

  • Seeding matches existing nodes by id. _seed keeps the ADR-072 durability fields: tier, the version hashes, config_modified and guidance_modified.
  • _seed.key stays as the human-readable handle for nodespace skill reset <key>, so the CLI surface doesn't change, but it is no longer identity.
  • A deleted seed is recreated on the next launch (ADR-072's "absent → create" row). A user turns a built-in off by archiving it.
  • No migration: change the seeding and reset the database (CLAUDE.md).

Agent guidance

  • The assembler reads the agent-guidance seed table and fetches each node by its fixed id, in table order. It no longer queries by type.
  • Only the built-in sections are used. A user's own standing instructions belong in skills, which the agent finds with search_skills.
  • An archived guidance node is skipped. The emergency fallback stays for the case where no section resolves.

Plays

  • Core plays and bundled playbook plays get fixed literal UUIDs in their tables.
  • ADR-060 §5 orders rules across plays by play node id. Fixed UUIDs keep the order the same on every device, but the order itself changes from slug order to UUID order. Check that no play relies on running before another. If one does, raise it on this issue rather than encoding it in UUID order.

Skills link to the schemas they are about (applies_to)

  • Today: SkillNode.node_types (packages/nodespace-types/src/skill.rs) is an undeclared string[] of schema ids. find_skills (packages/core/src/ops/skill_ops.rs) reads it to choose which schemas' schema_metadata a matched skill carries. No seeded or bundled skill sets it, and the decoder also reads it from a flat key (a compatibility fallback).
  • Replace it with a relationship declared on the skill schema: applies_to, from skill to schema node, Many/Many, optional, with reverse name skills. Declare it the same way task's blocks and project's tasks are declared in core_schemas.rs. It applies to every skill: built-in, playbook-installed, or authored by a user or agent.
  • find_skills scope:
    • A matched skill's schema scope is its applies_to targets plus their subtypes, found by following extends (subtypes inherit their base type).
    • Linked core schemas such as task are honoured too; the unlinked fallback stays custom-only.
    • Read the edges for all candidates in one batched query, not one query per skill.
    • With no links, keep today's fallback: the type the query names, otherwise the first five custom schemas.
  • Delete SkillNode.node_types, with_node_types, and the flat-key fallback in from_properties.
  • The bundled playbook skills create their applies_to edges at install. For example, spec-driven's "Writing a spec" links to spec, and Linear's "Creating an issue" links to issue.
  • Skill guidance: the skill-authoring guidance (SKILL.md and the CLI references) tells agents to link a new skill to the schemas it is about, using the existing relationship commands.

Architecture Impact

  • ADR-072 (decisions/072-per-aspect-seed-durability-with-explicit-reset.md) says reconciliation is "keyed by a per-node _seed block (key, …)". This issue extends it: identity moves to the fixed node id, and the per-aspect guard and explicit reset are unchanged. Companion docs change: amend ADR-072's reconciliation keying.
  • ADR-060 §5 and architecture/playbook-system.md: cross-play ordering keys on play node id. Unchanged in principle; the ids become UUIDs.
  • architecture/prompt-skill-system.md is stale. It still calls the type prompt and describes priority, template_syntax and source fields that ADR-057 says never shipped. Rewrite it to describe agent-guidance as a closed, table-ordered set and the seed-table pattern. This is a companion docs change; nodespace-docs is committed straight to main.
  • ADR-057 / ADR-030: consistent. Skill bodies remain Markdown children under the skill root.
  • ADR-081 §8: the new .md files sit under packages/, so scripts/check-pro-boundary.ts scans them. They must add no Pro markers. The Pro-only skill guidance leaves core in [Core release blocker] Strip all Pro code, edition branching and sync/ACL state from core in one pass (ADR-081 steps 0 and 2) #3412.

Acceptance Criteria

  • agent-guidance, skills, tools, core plays and bundled playbook plays are each defined by one const seed table with fixed literal UUIDs, and a test asserts every id is a valid UUID and unique across all tables
  • Every seeded node's id passes is_valid_node_id; no play id is a slug
  • Guidance and skill bodies, and shared rule fragments, are plain .md files with no frontmatter; no seed body is a Rust string literal
  • Each skill_rules constant is replaced by one .md fragment, used by guidance, skills, tool descriptions and gen_skill_md; the generated SKILL.md is unchanged unless a rule's text intentionally changes
  • The bundled playbook skill files drop their frontmatter, and the frontmatter parser in methodology/skills.rs is deleted
  • The skill schema declares applies_to (skill → schema, Many/Many, reverse skills); a test creates the edge from a skill to a schema node and resolves both directions
  • find_skills scopes schema_metadata to a skill's applies_to targets plus their extends subtypes, including linked core schemas, read in one batched query; tests cover a linked custom type, a linked core type, a subtype of a linked type, and the unlinked fallback
  • SkillNode.node_types, with_node_types and the flat-key fallback are deleted
  • The bundled playbook skills create applies_to edges at install (test per playbook)
  • Skill-authoring guidance teaches linking a skill to its schemas; the generated SKILL.md and its drift check are updated
  • Seeding reconciles by id: re-seeding an unchanged database creates no nodes, and a changed template replaces the node with the same id, preserving ADR-072's per-aspect guard
  • A deleted seeded node is recreated on the next seed run; an archived one stays archived and is not re-activated
  • The resident prompt is assembled from the guidance table in table order: a test seeds the nodes in reverse creation order and asserts the prompt's section order matches the table
  • A root agent-guidance node not in the table is not included in the prompt (test)
  • An archived guidance section is omitted from the prompt (test)
  • prompt_assembly_snapshot.rs builds the resident prompt through the same path as production, and the golden is regenerated (UPDATE_GOLDEN=1) with the diff reviewed
  • ADR-060 §5 cross-play ordering checked against the new ids; any order dependency is raised on this issue
  • Companion nodespace-docs changes committed: ADR-072 amendment, and architecture/prompt-skill-system.md rewritten
  • bun run test:changed passes; bun run quality:fix clean; Pro-boundary ratchet unchanged

Technical Specifications

Reference Files

  • Guidance seeds: packages/agent/src/prompt_assembler.rs (seed_agent_guidance_nodes, fetch_prompt_overrides, fetch_prompt_body, assemble_static)
  • Skill and tool seeds: packages/agent/src/skill_pipeline.rs (seed_skill_nodes, seed_tool_nodes)
  • Shared rules: packages/agent/src/skill_rules.rs, packages/agent/src/agent_guidance.rs, packages/agent/src/local_agent/tools.rs, packages/cli/examples/gen_skill_md.rs, packages/cli/tests/it/skill_md_generation.rs
  • Seeding and reconciliation: packages/daemon/src/services/assembly.rs (seed_agent_nodes), packages/core/src/services/node_service/mod.rs (seed_nodes_from_templates), packages/core/src/markdown/mod.rs (NodeTemplate, prepare_nodes_from_template)
  • Plays: packages/core/src/playbook/core_plays.rs, packages/core/src/methodology/{spec_driven,jira,linear,skills,install}.rs
  • Golden test: packages/agent/tests/it/prompt_assembly_snapshot.rs
  • UUID rule: packages/core/src/services/node_service/crud.rs (provided-id validation), is_valid_node_id in node_service/mod.rs

Implementation Notes

  • Order is the array order of the guidance table. There is no separate order field.
  • Keep NodeTemplate, or replace it with the table row types, whichever deletes more code. Don't keep both.
  • Model-facing text edits fail prompt_assembly_snapshot; regenerate with UPDATE_GOLDEN=1 and review the diff.
  • Run bun run build:sidecars --debug and bun run build:skill in a fresh worktree before workspace-wide cargo commands.

Dependencies

Non-Goals

  • Typed wire shapes for skill, tool or play (separate type-architecture work)
  • Play's enabled switch
  • Making core schemas reject undeclared properties such as _seed

Activity

  1. changed the title [-]Seed built-in nodes from fixed-UUID tables with plain-Markdown bodies, and assemble agent guidance in table order[/-] [+]Seed built-ins from fixed-UUID tables with Markdown bodies, order agent guidance, and link skills to their schemas[/+] on Oct 1, 2026
  2. malibio commented on Oct 2, 2026

    @malibio
    CollaboratorAuthor

    Baseline — Frontend: 309 test files passed (5606 tests)

  3. self-assigned this
    on Oct 2, 2026
  4. malibio commented on Oct 2, 2026

    @malibio
    CollaboratorAuthor

    ADR-060 §5 check against the fixed play ids: no play relies on running before another. Every shipped invariant rule's only action is reject, so order among them only decides which rejection message is reported. The three rules that write (the core parent-task rollup, Linear's scheduled cycle rollover, Jira's sprint completion stamp) each have a trigger no other writing rule shares. Nothing to encode in id order. PR: #3495

  5. added 2 commits that reference this issue on Oct 2, 2026
    a6a3332
    d9f8974
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions