feat(goose): add experimental Goose (Block) harness#1833
Conversation
b03dfb7 to
e92b6da
Compare
There was a problem hiding this comment.
Pull request overview
Adds a new experimental goose target (Block Goose) to APM, including Goose-specific compilation artifacts (.goosehints, recipes) and a YAML-backed MCP config adapter shared with Hermes.
Changes:
- Introduces
GooseClientAdapterand a sharedYamlMcpClientAdapterbase to manage YAML MCP config documents (Hermes + Goose). - Adds Goose agent recipe generation (
.goose/recipes/*.yaml) and.goosehintsstub compilation that importsAGENTS.md. - Wires the new target through detection, install help text, docs, and tests.
Reviewed changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/integration/test_targets_registry_completeness.py | Registers Goose adapter + adds extensions to known MCP keys. |
| tests/unit/integration/test_goose_target.py | End-to-end integration tests for Goose target surfaces and gating. |
| tests/unit/integration/test_data_driven_dispatch.py | Adds agents_goose bucket to dispatch parity test. |
| tests/unit/core/test_target_detection.py | Updates experimental targets membership to include goose. |
| tests/unit/core/test_scope.py | Adds goose to the known targets set assertion. |
| src/apm_cli/utils/yaml_io.py | Adds optional multiline block-scalar YAML rendering for readability. |
| src/apm_cli/integration/targets.py | Defines the goose TargetProfile and primitive mappings. |
| src/apm_cli/integration/agent_integrator.py | Adds Goose recipe writer and factors shared agent-frontmatter parsing. |
| src/apm_cli/factory.py | Registers goose adapter in the client factory. |
| src/apm_cli/core/target_detection.py | Routes explicit/config goose, adds .goosehints compile predicate, and updates descriptions/experimental set. |
| src/apm_cli/core/experimental.py | Adds goose experimental flag metadata and hint text. |
| src/apm_cli/compilation/goose_formatter.py | New formatter subclass for .goosehints stub generation. |
| src/apm_cli/compilation/gemini_formatter.py | Generalizes stub behavior via overridable class attributes. |
| src/apm_cli/compilation/agents_compiler.py | Factors a shared import-stub compiler and adds Goose stub compilation. |
| src/apm_cli/commands/install.py | Extends --target help to describe experimental Goose behavior. |
| src/apm_cli/adapters/client/hermes.py | Refactors Hermes adapter to use the shared YAML base class. |
| src/apm_cli/adapters/client/goose.py | New Goose YAML extensions: MCP adapter and transform logic. |
| src/apm_cli/adapters/client/_yaml_config.py | New shared YAML round-trip + atomic write base for YAML MCP adapters. |
| docs/src/content/docs/reference/experimental.md | Documents the goose experimental flag and its surfaces. |
| docs/src/content/docs/producer/compile.md | Documents Goose compile behavior (.goosehints + AGENTS.md). |
| docs/src/content/docs/integrations/goose.md | New Goose integration page. |
| CHANGELOG.md | Adds an entry describing the Goose experimental target. |
|
@microsoft-github-policy-service agree |
c7e7b53 to
54c858c
Compare
54c858c to
546f52a
Compare
Hermes was the only MCP client adapter writing a YAML config document (a single top-level servers mapping) rather than the JSON mcpServers schema. Lift its YAML round-trip / sibling preservation / atomic 0o600 write / malformed-file refusal / configure_mcp_server flow into a new YamlMcpClientAdapter base so a second YAML-backed adapter can reuse it without duplicating those blocks (the R0801 duplication guard would otherwise reject the copy). HermesClientAdapter now only declares its config path, mcp_servers_key, display name, and the per-server schema transform; behaviour and the _to_hermes_format static surface are unchanged.
Adds Goose (https://goose-docs.ai) as a first-class, experimental APM target gated behind the `goose` flag (`apm experimental enable goose`). Like the other frontier targets it is hidden from auto-detection and excluded from `--target all`. Goose differs structurally from every existing target, so the mapping follows its native surfaces: - MCP servers: GooseClientAdapter writes the YAML `extensions:` block of ~/.config/goose/config.yaml (honouring $XDG_CONFIG_HOME) in Goose's own per-server schema (type: stdio / cmd / args / envs / enabled / timeout; remotes -> streamable_http / uri). Reuses the shared YamlMcpClientAdapter base. User-scope only (Goose has no project config), with the new `extensions` key wired into the conflict detector's generic path. - agents -> recipes: each .apm/agents/<name>.md compiles to .goose/recipes/<name>.yaml, the native packaged-agent unit Goose runs via `goose run --recipe` (title/description/instructions, plus settings.goose_model when the agent pins a model). A shared _parse_agent_frontmatter helper is factored out of the Codex writer to avoid duplicating the frontmatter preamble. MCP extensions are not embedded per recipe -- an APM agent declares no MCP servers; those stay in config.yaml, which Goose reads globally at run time. - skills -> the cross-tool .agents/skills/ standard Goose reads natively (.agents/skills/ project, ~/.agents/skills/ with --global). - instructions: compile_family="agents" emits AGENTS.md and a thin .goosehints stub that imports it via Goose's `@./AGENTS.md` preprocessor (GeminiFormatter is parameterised by overridable stub attributes so GooseFormatter reuses it; _compile_gemini_md is generalised into a shared _compile_import_stub). Includes path-fidelity acceptance tests, registry/dispatch invariant updates, the integration docs page, and the experimental-flag reference.
…mat (closes Mode B gate for microsoft#1833) Add two normative requirements to spec section 8.5 (Deploy directory contract): - req-tg-005: A consumer writing MCP config for a YAML-based target MUST use the target's registered config key and per-server schema, preserving sibling keys not managed by APM; JSON-format mcpServers MUST NOT be written to YAML-config target files. - req-tg-006: A consumer supporting the agents primitive for a target MUST compile each agent to the target's registered native format and deploy under the target's registered root; a different target's format MUST NOT be emitted. Both requirements generalize the Goose and Hermes YAML-config pattern and the recipe/agent compilation surface. Spec count: 95 -> 97 (92 MUST, 5 SHOULD). Manifest + Appendix C + conformance tests updated. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rements) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
8aa175d to
8294013
Compare
APM Review Panel:
|
| Persona | B | R | N | Takeaway |
|---|---|---|---|---|
| Python Architect | 0 | 1 | 2 | Clean DRY extraction; GooseFormatter/GeminiFormatter coupling is a naming smell worth tracking; abstract methods should use @abstractmethod. |
| CLI Logging Expert | 0 | 1 | 2 | CLI output is ASCII-clean and uses correct helpers; bare 'Error configuring MCP server' catch-all needs a follow-up line; flag description scope mismatch affects apm experimental list. |
| DevX UX Expert | 0 | 2 | 3 | Two recommended fixes: flag description and post-enable hint misrepresent feature scope, steering users away from recipes (the primary capability). |
| Supply Chain Security Expert | 0 | 1 | 2 | Security posture is solid: 0o600 atomic writes, malformed-YAML refusal, fail-closed transforms; XDG_CONFIG_HOME path lacks bounds check (pre-existing Hermes pattern). |
| OSS Growth Hacker | 0 | 1 | 1 | Adding Block/Goose strengthens the multi-harness story; cross-tool .agents/skills/ standard is a standout narrative hook; CHANGELOG buries the headline recipe feature. |
| Auth Expert | 0 | 1 | 1 | APM auth tokens untouched; literal-env-embedding choice is documented; no user-facing warning for ${VAR} passthrough. |
| Doc Writer | 0 | 2 | 1 | goose.md is comprehensive and well-structured; two gaps: goose omitted from compile.md recommended-targets note, GOOSE_RECIPE_PATH example uses a relative path. |
| Test Coverage Expert | 0 | 2 | 1 | Two recommended gaps: no CliRunner integration test (Hermes has one), _goose_runtime_opted_in binary branch untested. Unit/fixture coverage is otherwise thorough. |
| Performance Expert | 0 | 0 | 1 | No hot-path regressions; read-all/write-all config pattern is acceptable for small YAML files; minor: _goose_runtime_opted_in instantiates GooseClientAdapter on every call. |
B = blocking-severity findings, R = recommended, N = nits.
Counts are signal strength, not gates. The maintainer ships.
Top 5 follow-ups
-
[DevX UX Expert + CLI Logging Expert] Fix
ExperimentalFlagdescription and post-enable hint to surface the recipe feature, not just MCP servers -- Two panelists independently flagged this from different angles. The current description ("Configure MCP servers for the Goose agent (Block).") and hint (pointing exclusively to--global) steer every user who enables the goose flag away from the headline capability. Recipe compilation is only reachable via project-scopeapm install --target goose, which the hint never mentions. Suggested fix forsrc/apm_cli/core/experimental.pyline 144:description="Deploy APM agents as Goose recipes (.goose/recipes/), skills, MCP extensions, and a .goosehints stub."and line 147 hint:"Run 'apm install --target goose' to install package agents as Goose recipes (.goose/recipes/) and skills (.agents/skills/). Add '--global' to also write MCP servers to ~/.config/goose/config.yaml. Run 'apm compile -t goose' to emit AGENTS.md + .goosehints at the project root." -
[Test Coverage Expert] Add CliRunner integration test suite for
apm install --target goose(Hermes parity gap) -- The CLI parser contract --TargetParamTypeacceptinggoose, flag-off hint emission, flag-on recipe deployment -- is unverified at the command surface.tests/integration/test_hermes_target.pyhasTestHermesParserE2EandTestHermesDeployE2E; Goose has no equivalent. Atests/integration/test_goose_target.pyshould cover: (1) flag-off parser acceptsgooseand emits enable hint, (2) flag-on + real bundle deploys.goose/recipes/and.goosehints. Principle: devx, multi-harness-support. -
[Test Coverage Expert] Add unit test for
_goose_runtime_opted_inbinary-on-PATH branch (Hermes parity gap) -- Theconfig_dirbranch is tested; the "binary on PATH, config dir absent" branch is not.tests/integration/test_hermes_target.pyhastest_flag_on_with_binary_only_opts_infor this exact case. Without this test, the opt-in path silently regresses if the binary-detection branch is ever refactored. One-liner fix: monkeypatchXDG_CONFIG_HOMEto an absent dir andfind_runtime_binaryto return a path. -
[Supply Chain Security Expert] Add bounds check on
XDG_CONFIG_HOMEbefore using it as a config write target --GooseClientAdapter._config_path()usesXDG_CONFIG_HOMEwithout validating it resolves inside an expected directory. A malicious dependency's post-install hook could set it to/etcor an attacker-controlled path. The same gap exists in the pre-existing$HERMES_HOMEsurface; worth closing both adapters together with a simple absolute-path + home-dir anchor check. -
[OSS Growth Hacker] Reorder CHANGELOG entry to lead with the Goose recipe feature, not MCP config details -- The current entry front-loads subordinate MCP config details and buries the headline in clause three. Release-note aggregators stop at the first clause. Suggested lead: "APM agents compile to Goose recipes at
.goose/recipes/<name>.yamland run natively viagoose run --recipe <name>."
Architecture
classDiagram
class CopilotClientAdapter {
+configure_mcp_server()
+_fetch_server_info()
+_format_server_config()
}
class YamlMcpClientAdapter {
+mcp_servers_key: str
+_display_name: str
+_supports_runtime_env_substitution: bool
+_config_path() Path
+_to_native_format() dict
+_load_document() dict
+update_config() bool
}
class GooseClientAdapter {
+target_name = goose
+mcp_servers_key = extensions
+_config_path() Path
+_to_native_format() dict
}
class HermesClientAdapter {
+target_name = hermes
+mcp_servers_key = mcp_servers
+_config_path() Path
+_to_native_format() dict
}
class GeminiFormatter {
+_stub_filename: str
+_stub_title: str
+_generate_stub() str
}
class GooseFormatter {
+_stub_filename = .goosehints
+_stub_title = Goose hints
}
CopilotClientAdapter <|-- YamlMcpClientAdapter : extends
YamlMcpClientAdapter <|-- GooseClientAdapter : extends
YamlMcpClientAdapter <|-- HermesClientAdapter : extends
GeminiFormatter <|-- GooseFormatter : extends
flowchart TD
CMD["apm install --target goose"] --> EXP["_goose_runtime_opted_in()"]
EXP -->|"flag off"| HINT["Enable hint to user"]
EXP -->|"flag on + runtime present"| INST["Install pipeline"]
INST --> AGI["AgentIntegrator._write_goose_recipe()"]
INST --> SKI["SkillIntegrator SKILL.md"]
INST --> MCI["GooseClientAdapter.update_config()"]
AGI --> RECIPE[".goose/recipes/name.yaml"]
SKI --> SKILL[".agents/skills/name/SKILL.md"]
MCI --> CONFIG["~/.config/goose/config.yaml (0o600)"]
CMD2["apm compile -t goose"] --> COMP["AgentsCompiler"]
COMP --> AMD["AGENTS.md"]
COMP --> GH[".goosehints (@./AGENTS.md)"]
sequenceDiagram
participant U as User
participant CLI as CLI
participant EXP as ExperimentalGate
participant AGI as AgentIntegrator
participant GCA as GooseClientAdapter
U->>CLI: apm install --target goose
CLI->>EXP: _goose_runtime_opted_in()
EXP-->>CLI: True (flag on + runtime present)
CLI->>AGI: integrate_agents_for_target(goose)
AGI->>AGI: _write_goose_recipe(agent.md to .yaml)
AGI-->>CLI: IntegrationResult
CLI->>GCA: update_config(mcp_servers)
GCA->>GCA: _load_document() then atomic_write 0o600
GCA-->>CLI: True
CLI-->>U: success output
Recommendation
The implementation is ready to ship: zero findings above the recommended tier across nine panelists, strong security posture, and a test suite that covers new surfaces at fixture and unit depth. The single highest-priority in-PR action is the ExperimentalFlag description and hint text fix in src/apm_cli/core/experimental.py lines 144-147 -- a two-line change that prevents the initial Goose adopter cohort from missing recipe compilation entirely; if it lands before merge, the PR ships cleanly. If it slips, track it as the first post-merge patch alongside the CHANGELOG reorder. The Hermes-parity CliRunner test suite (tests/integration/test_goose_target.py) is the most important post-merge addition and should target the next patch cycle.
Full per-persona findings
Python Architect
- [recommended] GooseFormatter subclasses GeminiFormatter -- wrong inheritance axis; prefer shared ImportStubFormatter base at
src/apm_cli/compilation/goose_formatter.py
GooseFormatter inherits from GeminiFormatter solely to reuse two class attribute overrides. If GeminiFormatter gains Gemini-specific behavior, GooseFormatter silently inherits it. The correct shape is a shared ImportStubFormatter base that both extend.
Suggested: Extractclass ImportStubFormatterwith_stub_filename,_stub_title,_generate_stub(); let both GeminiFormatter and GooseFormatter subclass it. - [nit]
_config_pathand_to_native_formatonYamlMcpClientAdaptershould use@abstractmethodatsrc/apm_cli/adapters/client/_yaml_config.py:64
Without@abstractmethod, the base can be instantiated directly and missing-override errors surface at call time rather than construction. - [nit]
_write_goose_recipehardcodesversion: 1.0.0-- extract as a module-level constant atsrc/apm_cli/integration/agent_integrator.py:404
CLI Logging Expert
- [recommended] Bare
_rich_error('Error configuring MCP server')catch-all gives users no next step atsrc/apm_cli/adapters/client/_yaml_config.py:181
The suppression is correct (avoids credential leakage) but the message is not actionable. A follow-up_rich_info('Check registry connectivity and server name, then retry.')costs nothing. - [nit] Experimental flag description "Configure MCP servers for the Goose agent (Block)." understates feature scope in
apm experimental listatsrc/apm_cli/core/experimental.py:144 - [nit] Goose target description in
RUNTIME_DESCRIPTIONSomits recipes and next-step hint atsrc/apm_cli/core/target_detection.py
DevX UX Expert
- [recommended] Flag description in registry says only "Configure MCP servers" -- hides recipes, skills, and instructions at
src/apm_cli/core/experimental.py:144
Suggested:description="Deploy APM agents as Goose recipes (.goose/recipes/), skills, MCP extensions, and a .goosehints stub.", - [recommended] Post-enable hint steers users exclusively to
--global, causing them to miss recipes (unsupported at user scope) atsrc/apm_cli/core/experimental.py:147
Runningapm install --target goose --globalskips recipes entirely (unsupported_user_primitives=("agents",)). The hint never mentions project-scope install. - [nit]
--globalhelp text lists global-capable MCP runtimes but omits Goose atsrc/apm_cli/commands/install.py:964 - [nit]
GOOSE_RECIPE_PATHdoc example uses a relative path that silently fails outside project root atdocs/src/content/docs/integrations/goose.md
Suggested:export GOOSE_RECIPE_PATH=$(pwd)/.goose/recipes - [nit] Stale "no runtimes support user-scope MCP" warning omits Goose at
src/apm_cli/integration/mcp_integrator_install.py
Supply Chain Security Expert
- [recommended]
XDG_CONFIG_HOMEpath is used without bounds check atsrc/apm_cli/adapters/client/goose.py:55
Path(os.environ.get('XDG_CONFIG_HOME')) / 'goose' / 'config.yaml'with no validation; same pre-existing pattern as Hermes$HERMES_HOME. In a CI environment where env vars may be attacker-influenced, this is a write-anywhere primitive bounded only by user permissions. - [nit]
os.chmodafteratomic_write_texthas a narrow TOCTOU window between rename and chmod atsrc/apm_cli/adapters/client/_yaml_config.py:141
Microsecond window, accepted trade-off;contextlib.suppressis correct for cross-platform compat. - [nit]
_supports_runtime_env_substitutionclass attribute could be inadvertently overridden by a future subclass without enforcement atsrc/apm_cli/adapters/client/_yaml_config.py:58
OSS Growth Hacker
- [recommended] CHANGELOG entry leads with MCP config details, burying the headline feature (Goose recipes) in clause three at
CHANGELOG.md
Suggested lead: "APM agents compile to Goose recipes at.goose/recipes/<name>.yamland run natively viagoose run --recipe <name>." - [nit] goose.md does not link to the Goose GitHub repo (github.com/block/goose) -- missed contributor funnel at
docs/src/content/docs/integrations/goose.md
Auth Expert
- [recommended] No user-visible warning when an MCP server config uses
${VAR}syntax -- values embedded literally atsrc/apm_cli/adapters/client/_yaml_config.py:55
_supports_runtime_env_substitution = Falsemeans${MY_API_KEY}in an MCP env block is written as the literal string to~/.config/goose/config.yaml. Security rationale is documented in comments but nothing warns the user at install time. - [nit]
ValueErrormessage in_to_native_formatinterpolatesname-- confirmed non-sensitive (registry server key) atsrc/apm_cli/adapters/client/goose.py:74
Doc Writer
- [recommended] compile.md intro note box lists recommended targets but omits goose at
docs/src/content/docs/producer/compile.md:20
Goose requiresapm compile -t gooseto emit.goosehints-- the primary instruction mechanism. Without it in the "recommended for every other target" note, users skip compile and get no instruction context. - [recommended]
GOOSE_RECIPE_PATHexample uses a relative path that silently fails outside the project root atdocs/src/content/docs/integrations/goose.md:92
export GOOSE_RECIPE_PATH=.goose/recipesresolves against the cwd at goose-run time, not the project root. Change to$(pwd)/.goose/recipes. - [nit] goose.md sidebar order 10 -- verify consistent with other integration pages at
docs/src/content/docs/integrations/goose.md:5
Test Coverage Expert
- [recommended] No CliRunner integration test for
apm install --target goose-- Hermes has a full parity suite attests/integration/test_hermes_target.py
tests/integration/test_hermes_target.pyhasTestHermesParserE2EandTestHermesDeployE2E; Goose has no equivalent. The CLI parser contract is unverified.
Proof (missing at integration-with-fixtures):tests/integration/test_goose_target.py::TestGooseParserE2E::test_flag_off_parser_accepts_and_emits_hint-- proves: Runningapm install --target goose(flag off) is accepted by the CLI parser and prints the experimental enable hint [devx, multi-harness-support] - [recommended]
_goose_runtime_opted_inbinary-on-PATH branch not tested; Hermes hastest_flag_on_with_binary_only_opts_inattests/unit/integration/test_goose_target.py:145
Proof (missing at unit):tests/unit/integration/test_goose_target.py::test_goose_runtime_opts_in_via_binary_only-- proves: Goose MCP opt-in fires when the goose binary is on PATH even if ~/.config/goose does not exist [multi-harness-support, devx] - [nit]
yaml_to_str(multiline_block=True)has no direct unit test intests/unit/test_yaml_io.py
Exercised indirectly viatest_goose_recipe_renders_multiline_instructions_as_block; a direct unit test intest_yaml_io.pywould isolate the_BlockStringDumperregression surface.
Performance Expert
- [nit]
_goose_runtime_opted_in()constructs aGooseClientAdapter()instance on every call to get the config path atsrc/apm_cli/integration/mcp_integrator_install.py:222
Path computation is pure; expose a module-level_goose_config_path()function and call it directly. Not a measurable regression at current call frequency.
This panel is advisory. It does not block merge. Re-apply the panel-review label after addressing feedback to re-run.
Generated by PR Review Panel for issue #1833 · 758.7 AIC · ⌖ 8.23 AIC · ⊞ 5.5K · ◷
feat(goose): add experimental Goose (Block) harness
TL;DR
Adds Goose (by Block) as a new experimental APM target, gated behind the
gooseflag. APM agents compile to Goose recipes that are headless-runnable (goose run --recipe) and parameterizable ({{ }}variables), skills land in the cross-tool.agents/skills/standard Goose reads natively, MCP servers write to Goose's YAMLextensions:block in~/.config/goose/config.yaml, and instructions reach Goose through a.goosehintsstub that imports the generatedAGENTS.md. Like the other frontier targets,gooseis never auto-detected and is excluded from--target all.Problem (WHY)
APM's promise is one manifest deployed across every agent harness, yet Goose — a widely-used on-machine agent — had no target. Goose also does not fit the existing target mould on several axes, so a naive copy of another adapter would have been wrong:
~/.config/goose/config.yaml, in YAML, under anextensions:key with a Goose-native per-server schema (type: stdio/cmd/args/envs/timeout) — not the JSONmcpServersschema every other adapter writes.goose run --recipe), which is the natural target for APM'sagentsprimitive — no other harness maps agents this way..goosehints, which supports an@./pathimport preprocessor, so the right move is a stub that importsAGENTS.mdrather than a second on-disk copy — context pulled at load time, anchor: PROSE, "Context arrives just-in-time, not just-in-case."..agents/skills/standard, so that surface is reused as-is.Approach (WHAT)
Each APM primitive maps onto the native Goose surface it belongs to:
goose run --recipe).goose/recipes/<name>.yaml(project scope)SKILL.md).agents/skills/<name>/(project) ·~/.agents/skills/<name>/(--global).goosehintsimportingAGENTS.md.goosehints+AGENTS.mdat project rootextensions:block~/.config/goose/config.yaml(user scope)Gating is experimental: enable with
apm experimental enable goose. Until then the target is inert — hidden from detection, excluded from--target all, and an explicit--target gooseexits with an enable hint.Implementation (HOW)
adapters/client/_yaml_config.py(new)YamlMcpClientAdapterbase — YAML round-trip, sibling-key preservation, atomic0o600write, malformed-file refusal,configure_mcp_serverflow.adapters/client/hermes.py_to_hermes_formatsurface unchanged).adapters/client/goose.py(new)GooseClientAdapter—~/.config/goose/config.yamlpath (honours$XDG_CONFIG_HOME),extensionskey, stdio/streamable_http per-server transform.integration/agent_integrator.py_write_goose_recipe(agent.md→ recipe.yaml:instructions+ apromptfor headless, verbatimparameters:+ preserved{{ }},settings.goose_model) + shared_parse_agent_frontmatterfactored out of the Codex writer.core/target_detection.pydetect_targetechoes an explicit/configgoose(added toTargetType) soapm compile -t gooseroutes the.goosehintsstub instead of falling back tominimal;_validate_canonical_v2(the MCP-install--targetresolver) now accepts experimental + explicit-only targets, fixing a pre-existing crash whereapm install --target <goose|antigravity|hermes>aborted whenever the package declared anmcp:dependency.integration/mcp_integrator_install.py~/.config/gooseorgooseon PATH), soapm install --target goosewrites the package'smcp:deps to~/.config/goose/config.yamlinstead of silently skipping them.utils/yaml_io.pyyaml_to_str(multiline_block=True)rendersinstructions/promptas readable literal blocks; default path (Hermes) unchanged.compilation/{gemini_formatter,goose_formatter,agents_compiler}.pyGeminiFormatterparameterised by stub attributes soGooseFormatterreuses it;_compile_gemini_mdgeneralised into_compile_import_stub.integration/targets.py,factory.py,core/experimental.py,core/target_detection.py,commands/install.pygooseTargetProfile, adapter registration, experimental flag, detection/description wiring, help text.docs/,CHANGELOG.mdDiagrams
Legend: how one
apm install/apm compile -t goosefans each APM primitive out to its native Goose surface.Legend: the YAML-backed adapters now share one base, so the new target adds no duplicated config-I/O code.
classDiagram CopilotClientAdapter <|-- YamlMcpClientAdapter YamlMcpClientAdapter <|-- HermesClientAdapter YamlMcpClientAdapter <|-- GooseClientAdapter class YamlMcpClientAdapter { update_config() get_current_config() configure_mcp_server() to_native_format() }Trade-offs
config.yaml, not embedded in recipes (Option A). An APM agent declares no MCP servers (onlymodel+ a tool-name whitelist), so per-recipeextensionswould have to source the package's resolved MCP deps and thread them through the agent integrator — a cross-phase change. Goose readsconfig.yamlglobally at run time, so recipes still work. Embedding remains a clean follow-up.$GOOSE_RECIPE_PATHwith no canonical user-scope home, so theagentsprimitive is excluded at--global(skills + MCP still deploy at user scope)._compile_import_stub, and_parse_agent_frontmatterwere extracted rather than duplicated — both to satisfy the R0801 duplication gate and on principle, anchor: PROSE, "Favor small, chainable primitives over monolithic frameworks.".hermes/openclaw) so it stays out of default flows until proven.Benefits
apm.yml.min-similarity-lines=10).config.yamlare written atomically with0o600and never interpolated into errors/logs.config.yamlis refused, never clobbered.Validation
Test suite, lint, duplication gate
Also verified end-to-end against the real
gooseCLI:apm compile -t goosewritesAGENTS.md+.goosehints,apm install --target goosewrites.goose/recipes/<name>.yaml+.agents/skills/, and a recipe with noextensions:block inherits the MCP server from the global~/.config/goose/config.yamlat run time (so MCP is not embedded per-recipe).A supply-chain-security review pass (per the repo's
supply-chain-security-expertpersona) returned APPROVE with no required findings:config.yamlwrite is0o600+ atomic, path containment viaensure_path_withinruns before the recipe dispatch, symlink sources are refused, and no new install-time network/code-execution path is introduced.Scenario Evidence
test_goose_update_config_writes_extensions_block_0600test_goose_update_config_refuses_malformed_yamltest_goose_agent_compiles_to_recipe_yamlprompt)test_goose_recipe_falls_back_to_default_prompt,test_goose_recipe_uses_authored_prompt_verbatim{{ }}templating pass throughtest_goose_recipe_passes_parameters_and_preserves_templatingapm compile -t gooseactually emits the.goosehintsstubtest_goose_detect_target_echoes_explicit_and_configapm install --target goosewith anmcp:dep configures Goose (no crash)test_goose_accepted_by_resolve_targets,test_goose_runtime_discovered_when_opted_intest_goose_skills_deploy_to_agents_skills.goosehintstest_goose_compile_writes_agents_md_and_goosehints--target alltest_goose_excluded_from_target_all,test_goose_resolves_only_when_named_with_flag_enabledHow to test
apm experimental enable goose.apm/agents/<name>.md, runapm compile -t goose→ confirmAGENTS.md,.goosehints(contains@./AGENTS.md), and.goose/recipes/<name>.yaml(valid recipe).apm install --target goose→ confirm skills under.agents/skills/.apm install --target goose --globalwith an MCP dep → confirm anextensions:entry in~/.config/goose/config.yamlwith mode0o600.apm compile --all→ confirm.goosehintsis not generated (goose is excluded fromall).Spec conformance note
apm-spec-waiver: This PR introduces Goose-target critical-path behavior before spec text lands; we will follow with a dedicated spec citation/update PR to formally map these semantics.