Skip to content

A link or path token in a rulebook body resolves to nothing #1107

Description

@williamthorsen

Problem

A rulebook body ships verbatim. sync applies no transform to it, so a Markdown link or a {harness_home_dir} token authored in a rulebook body reaches the deployed artifact unconverted and resolves to nothing.

Relative paths are also unreliable for an agent to resolve on its own. Claude Code has standing reports that a relative path in a SKILL.md is not resolved against the skill's directory, because the model cannot know its install directory (#17741, #56325, skills#1153). Absolute paths produced by the delivery pipeline rather than resolved by the model are the reliable form, and that is already what rewriteMarkdownPaths emits for skills and subagents.

No rulebook contains a link today, so this is enabling work rather than corrective. Its value is that it makes the destination-tier doctrine in content/guidance/README.md actionable: a harness-neutral host cannot carry a harness-scoped path, and the prescribed remedy is to move such content into an ambient rulebook, which helps only once a rulebook can carry a path at all. content/guidance/shared/AGENTS.md cites four _data/ files as prose ("in the agents skills tree") for exactly that reason. Those citations stay prose until their content migrates, which this change unblocks and does not itself perform.

Context

In scope: Render rulebook bodies per harness with the two transforms that concern paths, namely Markdown link rewriting (rewriteMarkdownPaths) and template variables (rewriteTemplateVariables, covering {harness_home_dir} and {harness_id}), at both delivery points: the rulebook-skill write loop and ambient region injection.

Out of scope:

  • {tool:} and {skill:}/{subagent:} invocation tokens. They carry dependency-edge semantics and would require extending edge extraction to rulebooks.
  • Links from one rulebook to another. An ambient-only target has no file to address, and dependencies: already expresses that relationship.
  • content/guidance/shared/, which installs both to the harness-neutral ~/.agents/AGENTS.md and, through include expansion, into each harness guidance file. Its neutral copy is why a link authored there cannot resolve.

Design context: the refined plan 20260729-023321Z_ambient-rulebooks-per-harness_plan-v2.md under this project's tickets/1088/ artifact directory (Tasks 2 and 4). That plan covers a larger scope that has since been split; this ticket is the rendering slice only.

Proposed solution

Rulebook bodies pass through a per-harness render before delivery, validated in the same pre-write pass that fails a dry run with nothing written.

Anchoring at the content root. The rewrite passes guidance/rulebooks/<slug>.md as the file path and the target harness's homeDir as the prefix. A source-tree link such as ../../skills/_data/concision.md then normalizes to skills/_data/concision.md and emits the harness's absolute path. No mapping layer is needed, because content/skills/ and content/scripts/ are laid out congruently with their deployed locations.

Allowed roots are skills/ and scripts/, not subagents/. Those two are the roots whose source layout matches their deployed layout under every harness. subagents/ is excluded because a subagent definition is dispatched rather than read as a document, so there is no link worth authoring into one; its deployed directory name also diverges by harness, but the renderer could map that. _partials/, collections/, and guidance/ never deploy as files. Widening later is easier than narrowing, and silently emitting a path nothing creates is the failure this work exists to prevent.

Validation splits by what each mechanism can know. The renderer enforces the root allowlist and content-root containment, both lexical and always correct. Existence and anchor-fragment checking stays in content-link-resolution.test.ts, extended to cover guidance/rulebooks/. The renderer deliberately does not check existence: it would resolve a target against the rulebook's own content root, which would reject a project-source or machine-local rulebook's link to library content, since no single content root models the deployed tree's union of library and source content.

Rulebook resolution moves to src/lib/ beside the renderer, matching where skill and subagent resolution already live.

Acceptance criteria

Must have

  • A link target under an allowed root emits the target harness's absolute path, with anchor fragments preserved.
  • A target in a rejected root, or one escaping the content root, fails the run with an error naming the rulebook, the authored target, and the reason.
  • ~-prefixed, absolute, http(s), and anchor-only targets pass through untouched.
  • {harness_home_dir} and {harness_id} expand per target harness.
  • A link target beginning with a template token expands to the harness path rather than being mangled into a relative path. Applies to skills and subagents as well as rulebooks.
  • A rulebook delivered to two harnesses yields the correct absolute path in each, in both skill delivery and ambient delivery.
  • Validation runs in the same pre-write pass as assertDeclaredSkillsRender, so a bad link fails a dry run with nothing written.
  • content-link-resolution.test.ts covers guidance/rulebooks/, and its claim that guidance/ is copied verbatim with no link rewriting is corrected.
  • codeassembly-content-specification.md documents the link convention, the allowed roots, and what a rejected target reports. Its claim that rulebook bodies bypass the render pass is made precise rather than deleted: rulebooks receive link and template rewriting but not invocation tokens, which is why dependencies: remains their edge mechanism.
  • New and modified behavior is covered by tests.

Metadata

Metadata

Labels

featureAdded or improved external functionalityscope:agents

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions