Skip to content

Skill-relative file resolution: read referenced markdown and execute referenced scripts (sh/py/js) from a skill's own directory #251

Description

@initializ-mk

Problem

A SKILL.md body routinely refers to sibling files by relative path — e.g. "run owl/scripts/check.py", "read owl.md", "apply templates/patch.yaml". Those files ship inside the skill's own directory (skills/<skill>/...) and reach the running agent via COPY . ., but the runtime has no notion of a skill-relative working directory, so those references don't resolve:

  • CWD is the agent root, not the skill dir. Both cli_execute (cmd.Dir = t.workDir, forge-cli/tools/cli_execute.go:222) and the skill-script executor (forge-cli/tools/exec.go:94) run with CWD = agent working directory. So owl/scripts/check.py (meant relative to skills/owl/) resolves against the agent root and is not found; the real path is skills/owl/scripts/check.py.
  • Only .sh skill tools are executable. registerSkillTools (forge-cli/runtime/runner.go:3071) resolves only scripts/<name>.sh; a referenced .py/.js/.ts script is neither registered as a tool nor runnable via the bash <script> path.
  • References are inline in the SKILL.md prose, not declared in frontmatter — so this can't be solved with a metadata field; the harness has to resolve a path mentioned in the body against the skill's directory.

Net effect: an author writes a perfectly reasonable skill ("read owl.md, then run owl/scripts/check.py") and it silently fails at runtime.

Desired behavior

When the agent is following a skill loaded via read_skill, the harness should resolve a relative reference in that skill's instructions against the skill's own directory and act on it:

  • Read a referenced file — read owl.md / read reference/runbook.md → return the file's contents from skills/<skill>/....
  • Execute a referenced script — run owl/scripts/check.py → resolve to skills/<skill>/owl/scripts/check.py, pick the interpreter from the extension (.sh→bash, .py→python3, .js→node, .ts→…; forge-core/tools/discovery.go already has langFromExt), run it with CWD set to the skill dir, and honor the skill's declared requires.bins / egress / denied_tools.

Path resolution MUST be confined to the skill's directory (reject .. / absolute escapes), same posture as read_skill's traversal guard and cli_execute's workdir confinement.

Candidate approaches (to decide in design)

  1. Skill-scoped toolsread_skill_file(skill, relpath) and run_skill_script(skill, relpath, args) that resolve against skills/<skill>/, path-guarded, interpreter chosen by extension. Deterministic and explicit about which skill is active.
  2. Skill-relative execution context — carry the "active skill dir" (from the last read_skill) so a plain cli_execute of a relative path resolves against it. Less explicit; needs a clear rule for which skill is active.
  3. Reference rewriting — have read_skill rewrite recognizable relative references in the returned body to agent-root-relative paths. Fragile (prose parsing) — noted for completeness, not preferred.

Whatever the mechanism, it should build on #250, which already (a) makes read_skill resolve a skill by its loadable name and (b) appends a ## Skill files listing of the skill's directory contents (scripts annotated by language, reference files). That listing gives the model the agent-root-relative paths today; this issue closes the loop so skill-relative execution/reading actually works.

Acceptance criteria

  • A relative file reference in a SKILL.md resolves and reads from the skill's directory (read owl.md, read reference/*.md).
  • A relative script reference executes with the correct interpreter by extension (.sh/.py/.js/.ts), CWD = skill dir, honoring requires.bins / egress / denied_tools.
  • Path resolution is confined to the skill directory (traversal / absolute-escape rejected), with a test.
  • skillEntryHasScript / the catalog provides: classification is broadened in lockstep so multi-language tools no longer fall between "not callable" and "in provides" (see the note left on that helper in fix(skills): align skill catalog names with read_skill resolution #250).
  • Docs: the Skill Designer prompt (forge-ui/skill_builder_context.go) and skill docs explain that references are skill-relative and which languages are runnable.
  • Tests covering read + execute for shell/python/javascript and the traversal guard.

Context / related

  • Runtime resolution + .sh-only registration: forge-cli/runtime/runner.go:3071, forge-cli/tools/cli_execute.go:222, forge-cli/tools/exec.go:94
  • Interpreter mapping already present: forge-core/tools/discovery.go (langFromExt)
  • Builds on fix(skills): align skill catalog names with read_skill resolution #250 (skill-name resolution + read_skill directory listing)

Metadata

Metadata

Assignees

Labels

enhancementNew feature or requestforge-cliAffects the forge-cli command-line tool (init, run, build, mcp commands)forge-coreAffects the forge-core library (runtime, security, types, llm, mcp, auth)toolsAffects builtin or adapter tools (cli_execute, http_request, browser, etc.)

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions