Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions amplifier_foundation/bundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,10 @@ def resolve_with_overrides(module_id: str, source: str) -> str:
"""
from amplifier_foundation.modules.activator import ModuleActivator

# Load full agent metadata (including system instructions from .md files)
# Must happen before to_mount_plan() so agent configs include system.instruction
self.load_agent_metadata()

# Get mount plan
mount_plan = self.to_mount_plan()

Expand Down Expand Up @@ -526,12 +530,12 @@ def _load_agent_file_metadata(path: Path, fallback_name: str) -> dict[str, Any]:
fallback_name: Name to use if not specified in file

Returns:
Dict with name, description, and any other meta fields
Dict with name, description, system instruction, and any other meta fields
"""
from amplifier_foundation.io.frontmatter import parse_frontmatter

text = path.read_text(encoding="utf-8")
frontmatter, _body = parse_frontmatter(text)
frontmatter, body = parse_frontmatter(text)

# Agents use meta: section (not bundle:)
meta = frontmatter.get("meta", {})
Expand All @@ -542,12 +546,20 @@ def _load_agent_file_metadata(path: Path, fallback_name: str) -> dict[str, Any]:
else:
meta = {}

return {
result = {
"name": meta.get("name", fallback_name),
"description": meta.get("description", ""),
**{k: v for k, v in meta.items() if k not in ("name", "description")},
}

# Extract markdown body as system instruction
# The body contains the agent's behavioral instructions that should be
# injected as the system prompt when the agent is spawned
if body and body.strip():
result["system"] = {"instruction": body.strip()}

return result


def _parse_context(
context_config: dict[str, Any], base_path: Path | None
Expand Down
100 changes: 100 additions & 0 deletions tests/test_bundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,3 +256,103 @@ def test_pending_context_resolved_after_compose(self) -> None:
assert "ns2:context/b.md" in result.context
assert result.context["ns1:context/a.md"] == Path("/ns1/root/context/a.md")
assert result.context["ns2:context/b.md"] == Path("/ns2/root/context/b.md")


class TestAgentMetadataLoading:
"""Tests for agent metadata loading from .md files."""

def test_load_agent_metadata_extracts_system_instruction(self) -> None:
"""Agent markdown body becomes system.instruction."""
with TemporaryDirectory() as tmpdir:
# Create agent .md file with frontmatter and body
agents_dir = Path(tmpdir) / "agents"
agents_dir.mkdir()
agent_file = agents_dir / "test-agent.md"
agent_file.write_text("""---
meta:
name: test-agent
description: A test agent
---

# Test Agent Instructions

You are a test agent. Follow these rules:
1. Do something
2. Do something else
""")

# Create bundle with agent stub
bundle = Bundle(
name="test",
base_path=Path(tmpdir),
agents={"test-agent": {"name": "test-agent"}},
)

# Load metadata
bundle.load_agent_metadata()

# Verify system instruction was extracted from body
agent_config = bundle.agents["test-agent"]
assert "system" in agent_config
assert "instruction" in agent_config["system"]
assert "Test Agent Instructions" in agent_config["system"]["instruction"]
assert "Follow these rules" in agent_config["system"]["instruction"]

def test_load_agent_metadata_preserves_existing_system(self) -> None:
"""Existing system.instruction is not overwritten by file metadata."""
with TemporaryDirectory() as tmpdir:
# Create agent .md file
agents_dir = Path(tmpdir) / "agents"
agents_dir.mkdir()
agent_file = agents_dir / "test-agent.md"
agent_file.write_text("""---
meta:
name: test-agent
---

File body instructions.
""")

# Create bundle with agent that already has system.instruction
bundle = Bundle(
name="test",
base_path=Path(tmpdir),
agents={"test-agent": {
"name": "test-agent",
"system": {"instruction": "Explicit inline instruction"}
}},
)

# Load metadata
bundle.load_agent_metadata()

# Explicit instruction should be preserved (not overwritten)
agent_config = bundle.agents["test-agent"]
assert agent_config["system"]["instruction"] == "Explicit inline instruction"

def test_load_agent_metadata_empty_body_no_system(self) -> None:
"""Agent with empty body doesn't get system.instruction."""
with TemporaryDirectory() as tmpdir:
# Create agent .md file with only frontmatter, no body
agents_dir = Path(tmpdir) / "agents"
agents_dir.mkdir()
agent_file = agents_dir / "test-agent.md"
agent_file.write_text("""---
meta:
name: test-agent
description: Agent with no body
---
""")

bundle = Bundle(
name="test",
base_path=Path(tmpdir),
agents={"test-agent": {"name": "test-agent"}},
)

bundle.load_agent_metadata()

agent_config = bundle.agents["test-agent"]
# Should have description but no system (empty body)
assert agent_config.get("description") == "Agent with no body"
assert "system" not in agent_config or not agent_config.get("system", {}).get("instruction")