Skip to content

feat(config): support external instruction files via instruction_file#3272

Merged
dgageot merged 2 commits into
mainfrom
feat/instruction-file
Jun 26, 2026
Merged

feat(config): support external instruction files via instruction_file#3272
dgageot merged 2 commits into
mainfrom
feat/instruction-file

Conversation

@Sayt-0

@Sayt-0 Sayt-0 commented Jun 26, 2026

Copy link
Copy Markdown
Member

Summary

Adds an optional instruction_file field on agent config so an agent's instruction can live in its own file (relative to the config file's directory) instead of being inlined in the YAML. This separates infrastructure configuration from behavioral content, which keeps version-control diffs focused, reduces merge conflicts on shared configs, and avoids YAML syntax risk when editing long prompts.

Scope is Option 1 (local file references) from the issue. The inline instruction field stays fully supported and is not deprecated.

agents:
  coordinator:
    model: openai/gpt-5-mini
    description: Routes work between specialist agents
    instruction_file: instructions/coordinator.md

Acceptance criteria

Criterion Status Notes
instruction_file parameter supported yes New field on the latest AgentConfig
Relative paths (relative to config file) yes Resolved against Source.ParentDir()
File content loaded at startup yes Resolved in config.Load, between migration and validation
Clear error messages if file not found yes Errors name the agent and the path
Backward compatible with inline instruction yes Both fields supported; setting both is rejected
Documentation updated with examples yes New docs section plus a runnable example
Tests cover file loading scenarios yes See test list below

Design decisions

  • Resolution point. config.Load is the single choke point for every consumer (run, teamloader, OCI push, sandbox kit), so resolution happens there. The whole pipeline only ever sees the inlined Instruction; no downstream change is needed.
  • Self-containment. After reading the file into Instruction, InstructionFile is cleared. This keeps the in-memory config self-contained and is required for the marshalling round-trip test (TestParseExamplesAfterMarshalling reloads via a parentless bytes source).
  • Path safety. The reference must be a local relative path inside the config directory. Absolute paths and .. traversal are rejected via filepath.IsLocal, and reads are confined with os.OpenRoot (TOCTOU-safe, blocks symlink escapes). This mirrors the existing HCL file() helper and fileSource.Read.
  • Mutual exclusivity. instruction and instruction_file cannot both be set.
  • Parentless sources. OCI/URL/bytes sources have no directory to resolve against, so instruction_file is rejected there with a clear message. share push re-serializes the resolved config when instruction_file is used, so pushed artifacts inline the contents and stay self-contained.

Files

File Change
pkg/config/latest/types.go New InstructionFile field (latest config only; v0..v10 frozen)
pkg/config/config.go resolveInstructionFiles called from Load
agent-schema.json instruction_file added to the AgentConfig definition
pkg/oci/package.go Re-serialize resolved config when instruction_file is used
examples/instruction_file.yaml, examples/instructions/*.md Runnable coordinator/specialist example
pkg/config/instruction_file_test.go, pkg/oci/package_test.go Tests
docs/configuration/agents/index.md External Instruction Files section plus reference row

Test coverage: resolution from a relative path, file in a subdirectory, missing file, .. traversal rejected, absolute path rejected, symlink escape rejected, mutual exclusivity, parentless source rejected, empty-string treated as unset, and OCI push inlining (loads from the pushed bytes without the local file).

Validation

  • golangci-lint run ./pkg/config/... ./pkg/oci/... reports no issues.
  • go build ./... passes (the lint/ tooling module needs a network fetch unavailable in the sandbox and is unrelated).
  • pkg/config and pkg/oci suites pass. The only failures in the local run are TestURLSource_Read_RejectsLocalAddresses, which are network-dependent SSRF tests unrelated to this change (they time out instead of rejecting because outbound network is blocked in the sandbox).

Possible follow-up

Option 2 from the issue (instruction_ref for OCI artifact references) is not included here and can be added later if maintainers want remote versioning.

Closes #1666

Add an optional instruction_file field on agent config that loads an agent's
instruction from a file relative to the config file's directory, resolved at
load time. This keeps long behavioral prompts out of the YAML (cleaner diffs,
fewer merge conflicts, no YAML syntax risk when editing prompts) while staying
backward compatible with the inline instruction field.

The reference must be a local relative path inside the config directory:
absolute paths and traversal are rejected, and reads are confined to the
directory with os.OpenRoot, mirroring the HCL file() helper and fileSource.
Resolution happens once in config.Load (between migration and validation), so
the rest of the pipeline only ever sees the inlined Instruction; the field is
cleared afterwards to keep the in-memory config self-contained. share push
re-serializes the resolved config when instruction_file is used so pushed OCI
artifacts stay self-contained.

Closes #1666
@Sayt-0 Sayt-0 requested a review from a team as a code owner June 26, 2026 14:39

@docker-agent docker-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟢 APPROVE

The instruction_file feature is well-implemented. The security design is sound:

  • filepath.IsLocal provides a first-pass rejection of .. traversal and absolute paths
  • os.OpenRoot confines reads to the config directory, blocking symlink escapes at the OS level (TOCTOU-safe)
  • Parentless sources (OCI/URL/bytes) are correctly rejected with clear error messages
  • Mutual exclusivity between instruction and instruction_file is correctly enforced
  • The OCI push re-serialization path correctly inlines resolved instructions before pushing

Two hypotheses about the OCI re-serialization path were investigated and dismissed:

  1. HCL + instruction_file: An HCL source fails at the initial yaml.Unmarshal version-probe (line 43–45), returning an explicit error before configUsesInstructionFile is ever called — no silent bypass.
  2. goccy/go-yaml type assertion: goccy/go-yaml v1.19.2 decodes string-keyed YAML mappings as map[string]interface{}, so the probe["agents"].(map[string]any) assertion succeeds correctly for valid YAML configs.

One low-severity observation (not a bug): os.OpenRoot(parentDir) is called inside the per-agent loop. Opening the directory root once before the loop and reusing it for all agents would be slightly more efficient, but this is a style/performance note rather than a correctness issue and does not warrant blocking the PR.

TestLoadExamples loaded every example through a bytes source (ParentDir
empty), but instruction_file.yaml references sibling files resolved
relative to the config directory, which requires a real parent dir.
Switch to a file source so the example loads, keeping the temp
WorkingDir to redirect disk-writing toolsets away from examples/.
@aheritier aheritier added area/agent For work that has to do with the general agent loop/agentic features of the app area/config For configuration parsing, YAML, environment variables area/distribution Agent registry, packaging, distribution, sharing area/docs Documentation changes kind/feat PR adds a new feature (maps to feat:). Use on PRs only. labels Jun 26, 2026
@dgageot dgageot merged commit b0f0669 into main Jun 26, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/agent For work that has to do with the general agent loop/agentic features of the app area/config For configuration parsing, YAML, environment variables area/distribution Agent registry, packaging, distribution, sharing area/docs Documentation changes kind/feat PR adds a new feature (maps to feat:). Use on PRs only.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support external instruction files for better configuration maintainability

4 participants