fix(goose): wire lifecycle hooks, AGENTS.md context, session support - #13
Conversation
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughGoose now supports lifecycle hooks, ordered ChangesGoose lifecycle support
Estimated code review effort: 3 (Moderate) | ~20 minutes Suggested reviewers: Sequence Diagram(s)sequenceDiagram
participant HookPayload
participant GooseAgent
participant EventPhaseMap
HookPayload->>GooseAgent: provide native lifecycle event
GooseAgent->>EventPhaseMap: resolve event through goose alias
EventPhaseMap-->>GooseAgent: return mapped agent phase
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
| func (a *GooseAgent) Capabilities() agentx.Capabilities { | ||
| return agentx.Capabilities{ | ||
| Hooks: false, // CLI-based | ||
| Hooks: true, // lifecycle hooks via plugins (goose >= 1.38) |
There was a problem hiding this comment.
Goose hook capability has no installation backend
Capabilities() now advertises Hooks: true, but the fully wired setup registry registers a bare GooseAgent and never assigns a HookManager. Callers that select Goose based on this capability receive nil from HookManager() and cannot install, remove, or validate the Goose plugin hook configuration. Implement and register a Goose hook manager for Goose's plugin hooks contract, or leave Hooks disabled until that operation is supported.
Artifacts
Goose lifecycle exported API validation script
- A Go program imported agentx setup and exercised Goose registration, lifecycle mappings, session support, context metadata, and hook-manager availability; it demonstrates the unsupported Hooks capability contract.
Goose lifecycle behavior before PR 13
- The same validation program compiled against HEAD^ and showed Goose had no hook capability, lifecycle mapper, alias registration, or session support before the PR.
Goose lifecycle behavior at PR 13 HEAD
- The validation program at HEAD showed all six lifecycle events and metadata were registered but failed because the exported setup path leaves Goose HookManager nil, proving the advertised hook capability is unusable.
Repository test suite after Goose lifecycle validation
- The executed `go test ./... -count=1` suite passed across all repository packages, showing existing tests do not cover the exported HookManager capability mismatch.
Greptile SummaryThis change adds Goose lifecycle event mappings, hook/session metadata, and updated context-file ordering. The exported registry correctly exposes Goose’s six mapped lifecycle events, but it now reports hook support without providing a hook manager capable of installing or managing Goose hook configuration. Consumers that rely on the capability flag cannot complete hook setup, so the change should not merge until that capability is backed by an implementation or is no longer advertised. Confidence Score: 3/5Not safe to merge until T-Rex findings are addressed. The exported setup path reproducibly reports Goose hook support while returning a nil hook manager. Lifecycle event resolution, context metadata, and session metadata work as expected, but the unavailable hook-management operation breaks consumers that configure hooks from agent capabilities. T-Rex reproduced 2 failing behaviors at runtime in agents/goose.go; the change needs fixes before it is safe to merge. Files Needing Attention: agents/goose.go needs a concrete Goose hook manager or must stop advertising hook support; setup/setup.go must wire that manager into the registered Goose agent.
What T-Rex did
|
What broke
GooseAgentwas written when Goose had no hooks and no addressable sessions. Both facts changed, and the agent definition didn't.Goose shipped a full lifecycle hooks system in v1.38 (May 2026), built on the Open Plugins hooks specification. It also loads
AGENTS.mdas its primary context file — ahead of.goosehints— and puts asession_idon every hook payload.Meanwhile
agents/goose.gostill declared:Hooks: false— "CLI-based"ContextFiles() == [".goose/config.yaml", ".goosehints"]— a config file listed as an instruction file, andAGENTS.mdmissing entirelySupportsSession() == falseLifecycleEventMapper— so"goose"was absent fromBuildEventPhaseMap()That last one is the load-bearing bug.
Why the missing mapper matters
BuildEventPhaseMap()keys onAGENT_ENValias. With no Goose entry, a consumer resolving a Goose hook event had nothing to look up and fell through to scanning every other registered agent's map.flowchart LR EV["Goose fires SessionStart"] --> LOOK{"alias goose in<br/>BuildEventPhaseMap"} LOOK -->|"before: absent"| SCAN["scan every other agent map<br/>until a string matches"] LOOK -->|"after: present"| DIRECT["PhaseStart"] SCAN --> LUCK["PhaseStart, by coincidence"]It happened to produce the right answer, because Goose's event names are string-identical to Claude Code's and Droid's and all three map them to the same phases. That is a coincidence, not a contract — the first agent to map
StoporSessionEnddifferently silently breaks Goose. This PR makes the mapping explicit.What this PR ships
EventPhases()covering the six Goose events with a canonical phase equivalent:SessionStartPhaseStartSessionEndPhaseEndPreToolUsePhaseBeforeToolPostToolUsePhaseAfterToolUserPromptSubmitPhasePromptStopPhaseStopAgentENVAliases() == ["goose"]ContextFiles() == ["AGENTS.md", ".goosehints"]— in Goose's own load order, so a caller injecting a context marker writes to the file Goose reads firstCapabilities():Hooks: true,MinVersion: "1.38.0"SupportsSession() == true, following the OpenCode precedent —SessionID(env)still returns empty, because Goose exposes no session-ID environment variable and callers must read it from the hook payloadDeliberate omissions
PhaseCompactis not mapped. Goose fires no compaction event. Mapping one would promise a re-prime that never happens — better to leave the gap visible than to paper over it. This is an upstream limitation no consumer can work around.PostToolUseFailure,BeforeReadFile,AfterFileEdit,BeforeShellExecution,AfterShellExecution— have no canonical phase equivalent and are omitted rather than force-fit.CustomCommandsstaysfalse. Goose has recipes and skills; neither is a slash command.Stale test assertions refreshed
Three tests encoded the old beliefs and had to move, exactly as the Pi refresh did in
0.1.10:TestNonLifecycleAgents_DoNotImplementMapperTestAgentSessionSupportallAgentSpecs()Goose entrycontextFiles,capabilities,supportsSession, plus newisLifecycleAgent/envAliases/eventPhaseCountNew
agents/goose_test.goadds five focused tests. Two are worth calling out because they pin intent, not current behavior:TestGooseContextFilesasserts the order, not just membership —AGENTS.mdmust be index 0. A caller pickingfiles[0]as its injection target depends on that.TestGooseEventPhasesasserts no phase in the map isPhaseCompact, with a comment explaining why adding one would be a silent regression rather than a feature.Test Plan
make check(fmt + lint + test) — 1134 tests pass, 2 pre-existing skips, 0 lint issuesgo test ./agents/... -run Goose— new file green~/.config/goose,~/.local/share/goose/sessions/sessions.db) and the upstream docs atblock/goose@main:documentation/docs/guides/context-engineering/hooks.mdFollow-up
Cut
v0.1.12after merge.sageox/oxwill bump to it so its Goose adapter work doesn't need a locallocalEventPhasesoverride — the same migration Gemini is still waiting on (Gemini and Codex remain non-lifecycle agents here).Summary by CodeRabbit
New Features
gooseenvironment alias.AGENTS.mdand.goosehints.Documentation