diff --git a/context/system.md b/context/system.md index a9aac8a..e8b3c62 100644 --- a/context/system.md +++ b/context/system.md @@ -3,6 +3,10 @@ local instructions before changing files. Preserve unrelated work. Use the tools actually mounted by the host, and distinguish proposed, attempted and verified outcomes. Provide concise public progress while substantial work runs. +Choose a mounted capability that directly supports the task and follow its +advertised interface. Keep the working directory, targets, permitted effects, +and output destinations explicit. + Use `apply_patch` for contextual file edits and `write_file` for complete small files. Follow each tool's advertised schema; providers may expose patch operations in different forms. A write denial is a configured boundary: report the blocked @@ -20,6 +24,9 @@ Use bounded delegation when the user's policy permits it and a concrete subtask can run independently. The delegate tool supports self-delegation when no named agent is needed: pass `agent: self`. Children inherit tools and providers subject to host policy. The profile excludes recursive delegation from children by default. +Pass the selected interface, authorized scope, constraints, required evidence, +and reporting destination. Keep actions requiring tools or authority unavailable +to the delegate with the parent. Async receipts identify pending work; inspect the actual result before reporting success. New corrections and side questions steer the current objective unless the user explicitly replaces or cancels it. diff --git a/context/work-execution.md b/context/work-execution.md index b227a62..4f6f241 100644 --- a/context/work-execution.md +++ b/context/work-execution.md @@ -1,5 +1,8 @@ # Managed work +Follow each mounted tool's supported input forms. Direct calls and `tool_exec` +use the same configured permissions; programmatic dispatch adds no authority. + For a command that should outlive one tool response, use the managed actions in the mounted `bash` schema. Save its returned process ID; read by the returned cursor, wait for actual changes, and report the real exit or cancellation state. diff --git a/presets/anchors-work.md b/presets/anchors-work.md index a18b9fa..c1dc3c3 100644 --- a/presets/anchors-work.md +++ b/presets/anchors-work.md @@ -6,7 +6,7 @@ bundle: description: Anchors tools and expertise with Work live execution and managed context. includes: - - bundle: git+https://github.com/microsoft/amplifier-foundation@main#subdirectory=bundles/anchors.md + - bundle: git+https://github.com/microsoft/amplifier-foundation@main#subdirectory=bundles/anchors/bundle.md - bundle: work:behaviors/work-skills.yaml - bundle: work:bundles/work-session.yaml - bundle: work:behaviors/work-execution.yaml diff --git a/tests/conftest.py b/tests/conftest.py index 41a3d38..fc31f07 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -62,3 +62,33 @@ def resolve(source): original_init(registry, *args, **kwargs) monkeypatch.setattr(BundleRegistry, "__init__", init) + + +@pytest.fixture(autouse=True) +def candidate_tester_include(monkeypatch): + """Use an explicit unpublished tester candidate in direct API composition.""" + candidate = os.environ.get("WORK_TESTER_BUNDLE") + if not candidate: + return + root = Path(candidate).expanduser().resolve(strict=True) + assert (root / "bundle.md").is_file() + canonical = "git+https://github.com/microsoft/amplifier-bundle-amplifier-tester@main" + original_init = BundleRegistry.__init__ + + def init(registry, *args, **kwargs): + prior = kwargs.get("include_source_resolver") + + def resolve(source): + if source == canonical: + return str(root / "bundle.md") + prefix = canonical + "#subdirectory=" + if source.startswith(prefix): + path = (root / source[len(prefix):]).resolve() + assert path.is_relative_to(root) + return str(path) + return prior(source) if prior else None + + kwargs["include_source_resolver"] = resolve + original_init(registry, *args, **kwargs) + + monkeypatch.setattr(BundleRegistry, "__init__", init) diff --git a/tests/test_amp_dev_composition.py b/tests/test_amp_dev_composition.py index 9caca21..453213e 100644 --- a/tests/test_amp_dev_composition.py +++ b/tests/test_amp_dev_composition.py @@ -1,4 +1,5 @@ """Work plus amp-dev preserves Work, not an Anchors-derived host.""" +import os from pathlib import Path from unittest.mock import AsyncMock, MagicMock @@ -48,12 +49,13 @@ async def test_work_amp_dev_preserves_work_and_resolves_portable_expert(tmp_path @pytest.mark.asyncio -async def test_actual_root_and_delegated_prompts_preserve_instruction_once(tmp_path, monkeypatch): +@pytest.mark.parametrize("source", ["bundle.md", "bundles/work-amp-dev.md", "presets/anchors-work.md"]) +async def test_actual_root_and_delegated_prompts_preserve_instruction_once(tmp_path, monkeypatch, source): """Exercise production prompt construction; module activation/model calls are separate DTU checks.""" monkeypatch.setenv("AMPLIFIER_HOME", str(tmp_path / "shared")) monkeypatch.chdir(tmp_path) (tmp_path / "AGENTS.md").write_text("host conventions sentinel") - bundle = await load_bundle(str(ROOT / "bundles/work-amp-dev.md"), strict=True) + bundle = await load_bundle(str(ROOT / source), strict=True) bundle.load_agent_metadata() monkeypatch.setattr(ModuleActivator, "activate_all", AsyncMock(return_value={})) monkeypatch.setattr(ModuleActivator, "finalize", lambda self: None) @@ -63,18 +65,38 @@ async def test_actual_root_and_delegated_prompts_preserve_instruction_once(tmp_p session = MagicMock() session.coordinator.hooks.emit = AsyncMock() prompt = await prepared.create_system_prompt_factory(session, session_cwd=tmp_path)() - assert prompt.count((ROOT / "context/system.md").read_text().strip()) == 1 - assert prompt.count("# Amplifier Ecosystem") == 1 - assert "Principles governing every action" not in prompt - expert = prepared.mount_plan["agents"]["amp-dev:amplifier-dev-expert"] - child = bundle.compose(Bundle(name="amp-dev:amplifier-dev-expert", - instruction=expert["instruction"])) + work_root = source != "presets/anchors-work.md" + assert prompt.count((ROOT / "context/system.md").read_text().strip()) == int(work_root) + assert ("Principles governing every action" in prompt) is not work_root + normalized = " ".join(prompt.split()) + assert normalized.count("Choose a mounted capability that directly supports the task") == 1 + assert "selected interface, authorized scope, constraints, required evidence" in normalized + assert "reporting destination" in normalized + assert prompt.count((ROOT / "context/work-execution.md").read_text().strip()) == 1 + assert "# Agent Baseline" not in prompt + if source == "bundle.md": + assert not prepared.mount_plan.get("agents") + return + name = ("amp-dev:amplifier-dev-expert" if source == "bundles/work-amp-dev.md" + else "anchors:builder") + expert = prepared.mount_plan["agents"][name] + candidate_tester = os.environ.get("WORK_TESTER_BUNDLE") + if source == "bundles/work-amp-dev.md" and candidate_tester: + assert bundle.source_base_paths["amplifier-tester"] == Path(candidate_tester).resolve() + tester = prepared.mount_plan["agents"]["amplifier-tester:validator"] + assert "inspect the home and mounts from inside the environment" in " ".join( + tester["instruction"].split() + ) + child = bundle.compose(Bundle(name=name, instruction=expert["instruction"])) child_prepared = await child.prepare(install_deps=False) child_prepared.bundle.resolve_pending_context() child_prompt = await child_prepared.create_system_prompt_factory(session, session_cwd=tmp_path)() - assert "# Amplifier Ecosystem Map" in child_prompt - assert "# Amplifier Development Workflows" in child_prompt - assert "# Amplifier Testing Patterns" in child_prompt + if source == "bundles/work-amp-dev.md": + assert "# Amplifier Ecosystem Map" in child_prompt + assert "# Amplifier Development Workflows" in child_prompt + assert "# Amplifier Testing Patterns" in child_prompt assert child_prompt.count("# Agent Baseline") == 1 + assert "within both task authorization and session permissions" in child_prompt + assert "selected interface, authorized scope, constraints, required evidence" not in " ".join(child_prompt.split()) assert (ROOT / "context/system.md").read_text().strip() not in child_prompt assert "host conventions sentinel" not in child_prompt \ No newline at end of file