Skip to content

feat: agents spec - #78

Merged
pedronauck merged 11 commits into
mainfrom
agents
Apr 10, 2026
Merged

pedronauck merged 11 commits into
mainfrom
agents

Conversation

@pedronauck

@pedronauck pedronauck commented Apr 10, 2026 •

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features

    • Reusable agents: discover/inspect/list CLI (including hidden MCP serve) and --agent flag for exec; install bundled global agents via setup.
    • Nested agent execution: reserved run_agent tool over stdio, automatic MCP wiring, and agent-aware system prompts attached to jobs.
    • Runtime emits reusable-agent lifecycle events.
  • Tests

    • Extensive CLI, registry, execution, MCP server, and integration test coverage added.
  • Chores

    • Dependency manifest updates.

@pedronauck pedronauck self-assigned this Apr 10, 2026
@coderabbitai

coderabbitai Bot commented Apr 10, 2026 •

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7f1dd021-560b-433c-900e-d5a182579576

📥 Commits

Reviewing files that changed from the base of the PR and between 39d3ddf and 5d67dd3.

⛔ Files ignored due to path filters (6)
  • .compozy/tasks/agents/reviews-003/_meta.md is excluded by !**/*.md
  • .compozy/tasks/agents/reviews-003/issue_001.md is excluded by !**/*.md
  • .compozy/tasks/agents/reviews-003/issue_002.md is excluded by !**/*.md
  • .compozy/tasks/agents/reviews-003/issue_003.md is excluded by !**/*.md
  • .compozy/tasks/agents/reviews-003/issue_004.md is excluded by !**/*.md
  • .compozy/tasks/agents/reviews-003/issue_005.md is excluded by !**/*.md
📒 Files selected for processing (2)
  • internal/core/run/exec/exec_test.go
  • internal/core/run/exec/prompt_exec.go

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.


Walkthrough

Adds a reusable-agents subsystem: embedded bundled AGENT.md fixtures, discovery/validation of AGENT.md and mcp.json across workspace/global scopes, execution-context resolution and composite system-prompt assembly, reserved MCP server construction and stdio-serving, run_agent engine with nesting/depth/cycle checks, CLI commands/flags, install flows, lifecycle events, model/ACP plumbing for MCP servers, and extensive tests.

Changes

Cohort / File(s) Summary
Bundled agents embed
bundledagents/embed.go
Adds embedded FS exposing */AGENT.md as bundledagents.FS.
Bundled setup & install
internal/setup/reusable_agents.go, internal/setup/bundle.go, internal/setup/types.go, internal/setup/*_test.go
List/preview/install bundled reusable agents from embedded FS into ~/.compozy/agents; atomic staging/rename semantics; exposes bundled root helper; tests.
Agent discovery & registry
internal/core/agents/agents.go, internal/core/agents/*, internal/core/agents/*_test.go
New Registry: discover workspace/global agents, parse AGENT.md frontmatter and runtime defaults, validate/expand mcp.json, produce Catalog/ResolvedAgent/Problem types and exported sentinel errors; Resolve API.
Execution context & prompt assembly
internal/core/agents/execution.go, internal/core/agents/execution_test.go
Resolve ExecutionContext applying runtime precedence, capture base runtime, and produce composite SystemPrompt with escaped <agent_metadata> and <available_agents> blocks.
Session MCP & reserved server context
internal/core/agents/session_mcp.go, internal/core/agents/session_mcp_test.go
Build session MCP server lists with reserved compozy stdio server carrying serialized nested runtime context; defaults, cloning, and reserved-name collision erroring.
Run-agent engine & stdio MCP server
internal/core/agents/mcpserver/engine.go, internal/core/agents/mcpserver/server.go, internal/core/agents/mcpserver/*_test.go
Engine to run nested agents (depth/cycle checks, access-mode capping), stdio MCP server exposing run_agent, host context loader from env, and tests.
Agent error classification
internal/core/agents/reasons.go
Map agent/MCP errors to ReusableAgentBlockedReason and expose IsValidationError helper.
Plan/prepare/exec integration
internal/core/plan/prepare.go, internal/core/run/exec/*, internal/core/run/exec/prompt_exec.go, internal/core/run/exec/*_test.go
Thread ExecutionContext into prepare/exec flows, add Job.SystemPrompt and Job.MCPServers, add ExecutePreparedPrompt API with SessionMCPBuilder callback, and update exec job building and tests.
ACP client & model changes
internal/core/agent/client.go, internal/core/agent/client_test.go, internal/core/model/*.go
Add MCPServers to SessionRequest/ResumeSessionRequest and conversion to ACP servers; add model.MCPServer/MCPServerStdio and CloneMCPServers; tests for forwarding and unsupported transports.
Session update lifecycle & events
internal/core/run/internal/acpshared/reusable_agent_lifecycle.go, internal/core/run/internal/acpshared/*
Emit reusable-agent lifecycle events (resolved, prompt-assembled, mcp-merged, nested-started/completed/blocked), track nested run_agent tool calls, decode run_agent inputs/results, and forward MCP servers into session creation; tests added.
Executor / runtime wiring
internal/core/run/internal/runshared/*, internal/core/run/executor/*, internal/core/run/test_hooks.go
Attach ReusableAgentExecution metadata to run jobs, clone MCPServers into run jobs, add SwapNewAgentClientForTest hook, and update executor tests for nested-block recovery.
CLI commands, flags & UX
internal/cli/agents_commands.go, internal/cli/commands.go, internal/cli/root.go, internal/cli/run.go, internal/cli/state.go, internal/cli/*_test.go
Add compozy agents (list/inspect/hidden mcp-serve), --agent exec flag, explicit runtime-flag capture, error decoration for reusable-agent errors, formatting helpers; many CLI tests added/updated.
Docs/golden/help & examples
internal/cli/testdata/exec_help.golden, internal/cli/reusable_agents_doc_examples_test.go, docs/examples/*
Update exec help golden to include --agent example; doc-example tests copy fixtures from docs/examples and validate behavior.
Events & kinds
pkg/compozy/events/*, pkg/compozy/events/kinds/reusable_agent.go
Add EventKindReusableAgentLifecycle and kinds.ReusableAgentLifecyclePayload with lifecycle stage and blocked-reason enums; JSON tests and docs updated.
Module & core config
go.mod, internal/core/api.go, internal/core/model/runtime_config.go
Promote/add dependencies in go.mod; add AgentName and ExplicitRuntime to core.Config/RuntimeConfig and wire through conversion; add ExplicitRuntimeFlags type.
Misc. tests & aliases
various *_test.go, alias files
Numerous test additions/updates and small aliases to propagate reusable-agent execution types across packages.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as User/CLI
    participant Registry as Agent Registry
    participant ExecCtx as ExecutionContext
    participant Prepare as Job Prep
    participant MCP as Session MCP Builder
    participant Client as Agent Client

    CLI->>Registry: Discover(workspaceRoot)
    Registry-->>CLI: Catalog + Problems
    CLI->>ExecCtx: ResolveExecutionContext(cfg)
    ExecCtx->>Registry: Resolve(agentName)
    Registry-->>ExecCtx: ResolvedAgent
    ExecCtx-->>Prepare: SystemPrompt, runtime defaults
    Prepare->>MCP: BuildSessionMCPServers(execution, ctx)
    MCP-->>Prepare: [reserved stdio, agent-local servers]
    Prepare->>Client: CreateSession(prompt + MCP servers)
    Client-->>Prepare: session ready / runID
Loading
sequenceDiagram
    participant Parent as Parent Session
    participant MCPsrv as MCP run_agent tool
    participant Engine as RunAgent Engine
    participant Registry as Agent Registry
    participant ChildClient as Child Agent Client

    Parent->>MCPsrv: call run_agent(name, input)
    MCPsrv->>Engine: RunAgent(host, req)
    Engine->>Registry: resolve child agent
    alt resolved
        Engine->>Engine: build child MCP servers (reserved + local)
        Engine->>ChildClient: ExecutePreparedPrompt(child cfg, prompt, MCP list)
        ChildClient-->>Engine: output + runID
        Engine-->>MCPsrv: RunAgentResult{success, output, runID}
    else blocked/invalid
        Engine-->>MCPsrv: RunAgentResult{blocked, reason, error}
    end
    MCPsrv-->>Parent: tool result
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related issues

  • compozy/agh#2 — Implements AGENT.md-backed agent directories, discovery and MCP handling as described by that RFC; this PR's agent-directory/AGENT.md work appears to address that issue.

Possibly related PRs

  • feat: agents spec #78 — Shares the same bundledagents/embed.go addition and broad agents feature surface; likely a direct duplicate or sibling PR.
  • feat: kernel refactoring #68 — Touches agent client/session MCP forwarding and relates to the added MCPServers handling in agent client code.
  • feat: exec command #60 — Overlaps exec command and execution-flow changes (ExecutionContext, --agent flag, and prepare/exec plumbing).
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: agents spec' accurately summarizes the primary change: implementing a comprehensive agents feature specification with discovery, inspection, execution, and lifecycle management.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agents

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 20

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
pkg/compozy/events/docs_test.go (1)

11-56: ⚠️ Potential issue | 🟠 Major

Wrap each event-kind assertion in t.Run("Should...") subtests.

This test violates the mandatory guideline requiring subtests for all test cases in *_test.go files. Currently, if any assertion fails with t.Fatalf(), the remaining 33 kinds are not tested. Each kind should be isolated in its own subtest with t.Parallel():

Proposed refactor
 func TestEventsDocumentationEnumeratesAllPublicKinds(t *testing.T) {
 	t.Parallel()

 	content := readEventsDocumentation(t)
 	for _, kind := range []EventKind{
 		EventKindRunQueued,
@@
 		EventKindShutdownRequested,
 		EventKindShutdownDraining,
 		EventKindShutdownTerminated,
 	} {
-		want := "`" + string(kind) + "`"
-		if !strings.Contains(content, want) {
-			t.Fatalf("expected docs/events.md to mention %s", kind)
-		}
+		kind := kind
+		t.Run("Should document "+string(kind), func(t *testing.T) {
+			t.Parallel()
+			want := "`" + string(kind) + "`"
+			if !strings.Contains(content, want) {
+				t.Fatalf("expected docs/events.md to mention %s", kind)
+			}
+		})
 	}
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/compozy/events/docs_test.go` around lines 11 - 56, The test
TestEventsDocumentationEnumeratesAllPublicKinds currently iterates over multiple
EventKind values in a single test so a failure aborts the rest; convert the loop
so each EventKind is validated inside its own subtest using t.Run with a
descriptive name (e.g., the kind string), call t.Parallel() at the start of each
subtest, and perform the strings.Contains check inside that subtest (keeping the
same failure assertion but scoped to the subtest) to ensure each kind is tested
independently and in parallel.
internal/core/run/exec/exec.go (1)

237-263: ⚠️ Potential issue | 🟠 Major

Persisted exec resume does not lock reusable-agent identity.

This now resolves the current --agent and feeds its system prompt/MCP servers into the resumed job, but persisted exec compatibility still only checks IDE/model/access settings. Resuming the same run-id with a different agent will silently attach a different reusable-agent configuration to the existing ACP session.

The fix should live in the persisted exec contract: store the resolved agent identity/config when the run starts, then reject resume when it changes. Based on learnings: Prioritize system boundaries and ownership - ensure clear ownership and API contracts between system components.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/run/exec/exec.go` around lines 237 - 263, The persisted exec
resume flow currently doesn't bind the resolved reusable-agent identity to the
run, allowing resumes with a different agent; fix this by persisting the
resolved agent identity/config at run start and validating it on resume: when
creating a new run, capture the resolved agent info returned by
reusableagents.ResolveExecutionContext (agentExecution) and write a stable
identifier/config snapshot into the run record (e.g., in
prepareExecRunState/state.writeStarted where runArtifacts/record are persisted);
then update validateExecResumeCompatibility to load and compare that persisted
agent identity/config from state.record against the current
ResolveExecutionContext result and return an error if they differ so
newExecRuntimeJob/resume cannot attach a different agent to an existing run.
internal/setup/install.go (1)

268-305: ⚠️ Potential issue | 🟠 Major

Preserve source file permissions when copying bundled assets.

This helper now backs generic bundle installs, but every copied file is recreated as 0644. Any executable bundled asset will lose its execute bit in copy mode and in the symlink fallback path, which breaks post-install execution.

Proposed fix
 func copyBundleDirectory(bundle fs.FS, rootDir, dest string, subject string) error {
 	return fs.WalkDir(bundle, rootDir, func(current string, entry fs.DirEntry, err error) error {
 		if err != nil {
 			return err
 		}
@@
 		source, err := bundle.Open(current)
 		if err != nil {
 			return fmt.Errorf("copy %s %s: %w", subject, rootDir, err)
 		}
 		defer source.Close()
 
-		file, err := os.OpenFile(target, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644)
+		mode := fs.FileMode(0o644)
+		if info, statErr := fs.Stat(bundle, current); statErr == nil {
+			mode = info.Mode().Perm()
+		}
+		file, err := os.OpenFile(target, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, mode)
 		if err != nil {
 			return fmt.Errorf("copy %s %s: %w", subject, rootDir, err)
 		}
 		defer file.Close()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/setup/install.go` around lines 268 - 305, copyBundleDirectory
currently recreates files with fixed 0644 and directories with 0755, losing
execute bits; fix by reading the source file/directory mode via Stat on the
opened bundle entry (use the existing bundle.Open(current) / source.Stat() to
get FileInfo.Mode()), then use that mode when creating directories
(os.MkdirAll(target, fi.Mode().Perm())) and when creating files
(os.OpenFile(target, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, fi.Mode().Perm()));
also after copying, ensure the target file mode is set
(file.Chmod(fi.Mode().Perm()) if needed) so executable bits on source entries
are preserved (apply changes around the existing variables current, entry,
source, file inside copyBundleDirectory).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/cli/agents_commands_test.go`:
- Around line 24-30: The tests calling withWorkingDir (e.g.,
TestExecCommandPassesSelectedAgentIntoWorkflowConfig) mutate the process-wide
working directory while marked t.Parallel(), causing racey failures; fix by
either removing t.Parallel() from any test that calls withWorkingDir (and the
other affected tests at the indicated ranges) or, better, change the helper and
command invocations to avoid os.Chdir: update withWorkingDir and any command
execution to set the working directory locally (use exec.Command.Dir or
equivalent) so tests can remain parallel-safe and no longer rely on process-wide
cwd.

In `@internal/cli/agents_commands.go`:
- Around line 62-65: The agents command group never registers the hidden
mcp-serve subcommand, so newMCPServeCommand() is defined but unreachable; add
newMCPServeCommand() to the cmd.AddCommand call in the same scope where
newAgentsListCommand() and newAgentsInspectCommand() are registered (i.e.,
include newMCPServeCommand() alongside the existing commands), ensuring any
required hidden flag/state set by newMCPServeCommand() remains unchanged.
- Around line 442-449: The helper optionalExistingPath currently returns "" for
any os.Stat error, hiding useful paths on permission errors or broken symlinks;
change it so it only returns "" when the path is blank or when os.Stat returns
an IsNotExist error (os.IsNotExist(err)); for any other non-nil error return the
original path so callers (e.g., agents inspect) can show the actual path and
surface the underlying failure; update function optionalExistingPath to use
os.IsNotExist(err) to distinguish the ENOENT case.

In `@internal/cli/reusable_agents_doc_examples_test.go`:
- Around line 17-20: Set HOME and XDG_CONFIG_HOME to isolated temp dirs before
running the CLI fixture so global agent discovery cannot influence resolution;
specifically, in the test(s) that create workspaceRoot and call
writeCLIWorkspaceConfig, copyCLIDocumentedAgentFixture("reviewer") and
withWorkingDir, create two temp dirs, set os.Setenv("HOME", tmpHome) and
os.Setenv("XDG_CONFIG_HOME", tmpXdg) before invoking the CLI actions, and
restore the original env values (defer reset) after the test; apply the same
change to the other similar test that uses the same sequence of
workspaceRoot/writeCLIWorkspaceConfig/copyCLIDocumentedAgentFixture/withWorkingDir
calls.

In `@internal/core/agent/client_test.go`:
- Around line 845-846: The test is flaky because req.McpServers vs
a.scenario.ExpectedNewSessionMCPServers can differ only by ordering of Env
slices; before the DeepEqual checks (for req.McpServers and the similar check at
871-872), normalize ordering deterministically by sorting each server's Env
slice and then sorting the server slice itself (e.g., by a stable key like
Host/Port or Name) so comparisons are order-insensitive; update the assertions
to compare these normalized copies of req.McpServers and
a.scenario.ExpectedNewSessionMCPServers (and the other pair) instead of the raw
values.

In `@internal/core/agent/client.go`:
- Around line 765-789: toACPMCPServers currently drops MCPServer entries with
nil Stdio, allowing invalid/partial MCP configurations to proceed; change it to
fail fast by returning an error when any src[i].Stdio == nil (include
identifying info such as index or server name from model.MCPServer in the error
message). Update the function signature from toACPMCPServers(src
[]model.MCPServer) []acp.McpServer to return ([]acp.McpServer, error) (or
perform equivalent upstream validation before calling it), and ensure callers
handle and propagate the error so create/resume rejects unsupported transports
instead of silently omitting them.

In `@internal/core/agents/agents_test.go`:
- Around line 14-506: Multiple standalone tests (e.g.,
TestDiscoverParsesValidAgentDefinition,
TestDiscoverRejectsReservedAndInvalidAgentNames,
TestDiscoverRejectsUnsupportedDeferredMetadataFields, etc.) must be converted to
a single table-driven test using t.Run("Should ...") subtests; refactor by
creating a TestDiscover (or consolidate within an existing TestDiscover*) that
defines a slice of cases (name, setup parameters like homeDir/workspaceRoot,
agent files via writeWorkspaceAgent/writeGlobalAgent, expected
catalog/Problems/assertions) and loops over them calling t.Run("Should
<behavior>", func(t *testing.T){ /* run per-case setup using newTestRegistry,
call registry.Discover, and perform the existing assertions */ }); reuse helpers
newTestRegistry, writeWorkspaceAgent, writeGlobalAgent, validAgentMarkdown, and
problemsByName to remove duplicated setup and preserve each original case's
assertions inside the corresponding subtest.

In `@internal/core/agents/agents.go`:
- Around line 212-219: The discovery currently aborts when r.globalAgentsRoot()
(called from Discover) fails, making workspace agent discovery unavailable;
change the logic so Discover treats failure to resolve the global root as
non-fatal: if r.globalAgentsRoot() returns an error, set globalCandidates and
globalProblems to empty (or nil) and proceed to call scanScope only when a valid
root is present, rather than returning the error; update the handling around
r.globalAgentsRoot(), scanScope, and the variables
globalCandidates/globalProblems so the Discover function continues discovering
workspace-local agents even when global lookup fails.

In `@internal/core/agents/execution.go`:
- Around line 109-145: The agent metadata is injected raw into the prompt tags
and can break framing or allow prompt injection; update buildAgentMetadataBlock,
buildAvailableAgentsBlock and formatDiscoveryCatalogEntry to escape/serialize
agent.Name, agent.Metadata.Title and agent.Metadata.Description before
concatenating into the XML-like blocks (e.g., JSON-serialize the strings or
escape control characters and angle-brackets/newlines), and use the escaped
values in the returned strings so tags like </agent_metadata> or newlines in
fields cannot break the host-owned prompt structure.

In `@internal/core/agents/mcpserver/server.go`:
- Around line 121-129: The loader function loadHostContextFromEnv currently
returns a zero HostContext when the reserved env var
reusableagents.RunAgentContextEnvVar is missing, causing fail-open behavior;
change loadHostContextFromEnv so that if lookupEnv reports the var absent or
blank it returns a non-nil error (with a clear message referencing the missing
COMPOZY_RUN_AGENT_CONTEXT) instead of HostContext{}, and ensure callers (e.g.,
server startup that may call RunStdio) propagate or handle that error (tests
should call RunStdio with an explicit HostContext to continue exercising the
server).

In `@internal/core/plan/prepare_test.go`:
- Around line 387-389: The test currently sets workspaceRoot and homeDir but
only overrides HOME, so ResolveExecutionContext may still read a pre-existing
XDG_CONFIG_HOME; create a separate temp dir (e.g., xdgConfigDir := t.TempDir())
and call t.Setenv("XDG_CONFIG_HOME", xdgConfigDir) alongside t.Setenv("HOME",
homeDir) (references: workspaceRoot, homeDir) to isolate config discovery and
prevent flaky catalog assertions.

In `@internal/core/run/exec/prompt_exec.go`:
- Around line 51-60: After writeStarted succeeds but buildMCPServers fails,
route the error through the same terminal-completion path by invoking
completeTurn (using state and cfg) with a failure result before returning;
specifically, in the buildMCPServers error branch (the call to
buildMCPServers(state.runArtifacts.RunID)), call completeTurn to mark the run as
completed/failed and persist the terminal state, then return
PreparedPromptResult{} with the original error — ensuring the branch mirrors the
execution-error handling rather than returning early and leaving a started run
hanging.

In `@internal/core/run/internal/acpshared/command_io.go`:
- Around line 125-136: The current call to emitReusableAgentSetupLifecycle (with
req.Context, req.RunJournal, req.Config.RunArtifacts.RunID, req.Job) aborts
setup on error by closing outFile/errFile, closing client, calling
releaseClient(), and returning the error; change this to best-effort: invoke
emitReusableAgentSetupLifecycle and if it returns an error, log a warning with
the error details but do NOT close outFile/errFile, do NOT close or release the
client, and do NOT return; allow the ACP session to continue (optionally spawn a
non-blocking retry/report task), so failures in telemetry emission degrade to
warnings instead of failing session startup.

In `@internal/core/run/internal/acpshared/reusable_agent_lifecycle.go`:
- Around line 159-177: The code is mutating h.nestedToolCalls before calling
submitReusableAgentLifecycle, which can leave the map out-of-sync if
runJournal.Submit fails; update handleNestedReusableAgentToolUse so it calls
submitReusableAgentLifecycle first and only inserts the nestedToolCalls entry
after the submit succeeds (or, alternatively, perform the insert then roll it
back if submit returns an error), and apply the same change to the analogous
block referenced at lines 219-223 (handleNestedReusableAgentToolResult deletion)
so deletions are performed only after a successful submit or are reverted on
submit failure; use the nestedToolCalls map, submitReusableAgentLifecycle,
handleNestedReusableAgentToolUse and handleNestedReusableAgentToolResult
identifiers to locate and update the logic.

In `@internal/core/run/internal/acpshared/session_handler.go`:
- Around line 109-112: The call to h.emitReusableAgentLifecycleFromUpdate after
h.applySessionUpdate must not abort the parent session on failure; instead,
catch the error from emitReusableAgentLifecycleFromUpdate, log a warning that
includes concrete failure modes (e.g., "decoding error", "submission error",
"journal hiccup") and relevant context (session id, update id), and continue
normal execution so the parent session stays healthy; update the code around
applySessionUpdate/emitReusableAgentLifecycleFromUpdate to handle the error
non-fatally (log + continue) rather than returning it.

In `@internal/core/run/internal/runshared/config.go`:
- Line 48: The Job.ReusableAgent field is declared but not propagated in
NewJobs, so Job.ReusableAgent remains nil for cloned jobs; update the
NewJobs/job cloning code that maps from the source "item" (the constructor
creating new Job instances) to copy item.ReusableAgent into the new
Job.ReusableAgent, performing a deep copy if ReusableAgent is a mutable struct
or contains pointers/slices (e.g., call a Clone/DeepCopy method or manually copy
nested fields) to avoid shared mutable state; ensure the new mapping uses the
exact symbol names Job.ReusableAgent, item.ReusableAgent and the
NewJobs/job-constructor function so downstream consumers receive an independent
copy.

In `@internal/setup/bundle.go`:
- Around line 35-38: The current code returns nil, err after calling
InstallBundledReusableAgents (successes, failures, err), discarding the
accumulated result of successful installs; instead populate or update the
existing result variable with the successes/failures and return that result
along with a wrapped error (preserving err) so partial phase-one results are
returned on phase-two failure; locate InstallBundledReusableAgents, the
successes/failures variables, and the result variable and change the return from
nil, err to return result, wrappedErr (using the project's error-wrapping
convention) so callers receive both the partial result and the error.

In `@internal/setup/install_test.go`:
- Around line 145-207: Both tests hardcode the expected number of bundled
reusable agents (6) and duplicate setup/assertion logic; change them to a single
table-driven test with two subtests ("preview" and "install") that share setup
and derive the expected roster dynamically from the source of truth for bundled
agents (e.g., call whichever function or variable your package exposes that
lists bundled reusable agents — reference PreviewBundledReusableAgentInstall and
InstallBundledReusableAgents to run the respective flows), assert len(results)
== len(bundledRoster) rather than == 6, and keep the existing per-item path
assertions (TargetPath and success.Path) inside the subtests. Ensure you replace
the two test functions
TestPreviewBundledReusableAgentInstallUsesGlobalCompozyAgentsDir and
TestInstallBundledReusableAgentsCopiesCouncilRosterIntoGlobalCompozyAgentsDir
with the consolidated table-driven t.Run structure and derive expected counts
from the bundled roster.

In `@internal/setup/reusable_agents.go`:
- Around line 121-141: The current install flow deletes/cleans the final target
(cleanAndCreateDirectory on targetPath) before copying, which can leave a
partial or missing agent if copyBundleDirectory fails; change the flow to create
a sibling temp directory (e.g., tempTarget := targetPath + ".tmp" or use
ioutil.TempDir in the same parent), call cleanAndCreateDirectory on that
tempTarget, then call copyBundleDirectory into tempTarget; if copy succeeds,
atomically replace the old installation by renaming tempTarget to targetPath
(os.Rename) and if it fails remove tempTarget and append the
ReusableAgentFailureItem with Error set to the copy error; ensure you still use
reusableAgent.Directory and bundledagents.FS for the copy and preserve existing
failure handling for cleanup and continue.

In `@pkg/compozy/events/kinds/reusable_agent.go`:
- Around line 48-53: The JSON tags on the ReusableAgent event struct currently
use `omitempty` for fields whose zero values are meaningful; remove `omitempty`
from NestedDepth, MaxNestedDepth, Success, and Blocked (fields named
NestedDepth, MaxNestedDepth, Success, Blocked in reusable_agent.go) so
zero/false values are preserved in serialized payloads; update the struct tags
to plain `json:"nested_depth"`, `json:"max_nested_depth"`, `json:"success"`, and
`json:"blocked"` and run tests/serialization checks to ensure downstream
consumers receive explicit zero/false values.

---

Outside diff comments:
In `@internal/core/run/exec/exec.go`:
- Around line 237-263: The persisted exec resume flow currently doesn't bind the
resolved reusable-agent identity to the run, allowing resumes with a different
agent; fix this by persisting the resolved agent identity/config at run start
and validating it on resume: when creating a new run, capture the resolved agent
info returned by reusableagents.ResolveExecutionContext (agentExecution) and
write a stable identifier/config snapshot into the run record (e.g., in
prepareExecRunState/state.writeStarted where runArtifacts/record are persisted);
then update validateExecResumeCompatibility to load and compare that persisted
agent identity/config from state.record against the current
ResolveExecutionContext result and return an error if they differ so
newExecRuntimeJob/resume cannot attach a different agent to an existing run.

In `@internal/setup/install.go`:
- Around line 268-305: copyBundleDirectory currently recreates files with fixed
0644 and directories with 0755, losing execute bits; fix by reading the source
file/directory mode via Stat on the opened bundle entry (use the existing
bundle.Open(current) / source.Stat() to get FileInfo.Mode()), then use that mode
when creating directories (os.MkdirAll(target, fi.Mode().Perm())) and when
creating files (os.OpenFile(target, os.O_CREATE|os.O_TRUNC|os.O_WRONLY,
fi.Mode().Perm())); also after copying, ensure the target file mode is set
(file.Chmod(fi.Mode().Perm()) if needed) so executable bits on source entries
are preserved (apply changes around the existing variables current, entry,
source, file inside copyBundleDirectory).

In `@pkg/compozy/events/docs_test.go`:
- Around line 11-56: The test TestEventsDocumentationEnumeratesAllPublicKinds
currently iterates over multiple EventKind values in a single test so a failure
aborts the rest; convert the loop so each EventKind is validated inside its own
subtest using t.Run with a descriptive name (e.g., the kind string), call
t.Parallel() at the start of each subtest, and perform the strings.Contains
check inside that subtest (keeping the same failure assertion but scoped to the
subtest) to ensure each kind is tested independently and in parallel.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b4e764f3-cb0c-465c-8fa5-fd0b13bf3b47

📥 Commits

Reviewing files that changed from the base of the PR and between 8d85ea6 and 7074dd7.

⛔ Files ignored due to path filters (35)
  • .agents/skills/council/SKILL.md is excluded by !**/*.md, !.agents/**
  • .agents/skills/council/references/archetypes.md is excluded by !**/*.md, !.agents/**
  • .codex/ledger/2026-04-10-MEMORY-council-agent-setup.md is excluded by !**/*.md
  • .compozy/tasks/agents/_meta.md is excluded by !**/*.md
  • .compozy/tasks/agents/_tasks.md is excluded by !**/*.md
  • .compozy/tasks/agents/_techspec.md is excluded by !**/*.md
  • .compozy/tasks/agents/adrs/adr-003.md is excluded by !**/*.md
  • .compozy/tasks/agents/memory/MEMORY.md is excluded by !**/*.md
  • .compozy/tasks/agents/memory/task_01.md is excluded by !**/*.md
  • .compozy/tasks/agents/memory/task_02.md is excluded by !**/*.md
  • .compozy/tasks/agents/memory/task_03.md is excluded by !**/*.md
  • .compozy/tasks/agents/memory/task_04.md is excluded by !**/*.md
  • .compozy/tasks/agents/memory/task_05.md is excluded by !**/*.md
  • .compozy/tasks/agents/memory/task_06.md is excluded by !**/*.md
  • .compozy/tasks/agents/task_01.md is excluded by !**/*.md
  • .compozy/tasks/agents/task_02.md is excluded by !**/*.md
  • .compozy/tasks/agents/task_03.md is excluded by !**/*.md
  • .compozy/tasks/agents/task_04.md is excluded by !**/*.md
  • .compozy/tasks/agents/task_05.md is excluded by !**/*.md
  • .compozy/tasks/agents/task_06.md is excluded by !**/*.md
  • README.md is excluded by !**/*.md
  • bundledagents/architect-advisor/AGENT.md is excluded by !**/*.md
  • bundledagents/devils-advocate/AGENT.md is excluded by !**/*.md
  • bundledagents/pragmatic-engineer/AGENT.md is excluded by !**/*.md
  • bundledagents/product-mind/AGENT.md is excluded by !**/*.md
  • bundledagents/security-advocate/AGENT.md is excluded by !**/*.md
  • bundledagents/the-thinker/AGENT.md is excluded by !**/*.md
  • docs/events.md is excluded by !**/*.md
  • docs/examples/agents/repo-copilot/AGENT.md is excluded by !**/*.md
  • docs/examples/agents/repo-copilot/mcp.json is excluded by !**/*.json
  • docs/examples/agents/reviewer/AGENT.md is excluded by !**/*.md
  • docs/reusable-agents.md is excluded by !**/*.md
  • go.sum is excluded by !**/*.sum
  • skills/cy-idea-factory/SKILL.md is excluded by !**/*.md
  • skills/cy-idea-factory/references/council.md is excluded by !**/*.md
📒 Files selected for processing (62)
  • bundledagents/embed.go
  • go.mod
  • internal/cli/agents_commands.go
  • internal/cli/agents_commands_test.go
  • internal/cli/commands.go
  • internal/cli/reusable_agents_doc_examples_test.go
  • internal/cli/root.go
  • internal/cli/root_command_execution_test.go
  • internal/cli/root_test.go
  • internal/cli/run.go
  • internal/cli/setup.go
  • internal/cli/setup_test.go
  • internal/cli/state.go
  • internal/cli/testdata/exec_help.golden
  • internal/core/agent/client.go
  • internal/core/agent/client_test.go
  • internal/core/agents/agents.go
  • internal/core/agents/agents_test.go
  • internal/core/agents/doc_examples_test.go
  • internal/core/agents/execution.go
  • internal/core/agents/execution_test.go
  • internal/core/agents/mcpserver/engine.go
  • internal/core/agents/mcpserver/engine_test.go
  • internal/core/agents/mcpserver/server.go
  • internal/core/agents/mcpserver/server_test.go
  • internal/core/agents/reasons.go
  • internal/core/agents/session_mcp.go
  • internal/core/agents/session_mcp_test.go
  • internal/core/api.go
  • internal/core/kernel/commands/commands_test.go
  • internal/core/model/mcp.go
  • internal/core/model/preparation.go
  • internal/core/model/runtime_config.go
  • internal/core/plan/prepare.go
  • internal/core/plan/prepare_test.go
  • internal/core/run/exec/aliases.go
  • internal/core/run/exec/exec.go
  • internal/core/run/exec/exec_integration_test.go
  • internal/core/run/exec/exec_test.go
  • internal/core/run/exec/prompt_exec.go
  • internal/core/run/exec/run_agent_engine_integration_test.go
  • internal/core/run/exec/test_helpers_test.go
  • internal/core/run/executor/execution_acp_integration_test.go
  • internal/core/run/internal/acpshared/aliases.go
  • internal/core/run/internal/acpshared/command_io.go
  • internal/core/run/internal/acpshared/command_io_test.go
  • internal/core/run/internal/acpshared/reusable_agent_lifecycle.go
  • internal/core/run/internal/acpshared/session_handler.go
  • internal/core/run/internal/acpshared/session_handler_test.go
  • internal/core/run/internal/runshared/config.go
  • internal/core/run/internal/runshared/types.go
  • internal/core/run/test_hooks.go
  • internal/setup/bundle.go
  • internal/setup/bundle_test.go
  • internal/setup/install.go
  • internal/setup/install_test.go
  • internal/setup/reusable_agents.go
  • internal/setup/types.go
  • pkg/compozy/events/docs_test.go
  • pkg/compozy/events/event.go
  • pkg/compozy/events/event_test.go
  • pkg/compozy/events/kinds/reusable_agent.go

Comment thread internal/cli/agents_commands_test.go
Comment thread internal/cli/agents_commands.go
Comment thread internal/cli/agents_commands.go
Comment thread internal/cli/reusable_agents_doc_examples_test.go
Comment thread internal/core/agent/client_test.go
Comment thread internal/core/run/internal/runshared/config.go
Comment thread internal/setup/bundle.go Outdated
Comment thread internal/setup/install_test.go Outdated
Comment thread internal/setup/reusable_agents.go
Comment thread pkg/compozy/events/kinds/reusable_agent.go Outdated

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 7

🧹 Nitpick comments (3)
internal/setup/reusable_agents.go (1)

221-225: Consider logging instead of failing when backup cleanup fails.

After line 220, the new agent is successfully installed in targetPath. If removeReusableAgentPath(backupPath) fails here, the function returns an error, causing the install to be reported as failed even though the new content is in place.

This leaves orphaned backup directories and misleads the caller about the install status. Consider either:

  1. Logging the cleanup error and returning nil (install succeeded)
  2. Collecting cleanup errors separately from install errors
♻️ Proposed fix
 	if backupPath != "" {
 		if err := removeReusableAgentPath(backupPath); err != nil {
-			return fmt.Errorf("cleanup reusable agent backup %s: %w", backupPath, err)
+			// Log but don't fail - the install succeeded, only cleanup failed
+			// The orphaned backup will be cleaned up on next install
+			_ = err // TODO: consider structured logging for cleanup failures
 		}
 	}
 	return nil
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/setup/reusable_agents.go` around lines 221 - 225, The cleanup call
to removeReusableAgentPath(backupPath) can fail after the new agent is already
installed to targetPath, causing the function to return an error and incorrectly
signal install failure; change this to log the cleanup failure instead of
returning it (or aggregate cleanup errors separately) so the function returns
nil on successful install—specifically, in the block that calls
removeReusableAgentPath(backupPath) avoid returning fmt.Errorf on error and
instead call the appropriate logger with context about backupPath and continue,
or append the cleanup error to a separate variable that does not affect the
install success return.
internal/core/run/internal/acpshared/reusable_agent_lifecycle.go (2)

248-264: JSON decode errors are silently swallowed.

Both decodeRunAgentToolInput and decodeRunAgentToolResult discard JSON unmarshal errors. This is likely intentional for resilience against malformed tool payloads, but consider logging at debug level to aid troubleshooting when tool results don't emit expected lifecycle events.

Also applies to: 266-286

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/run/internal/acpshared/reusable_agent_lifecycle.go` around
lines 248 - 264, decodeRunAgentToolInput and decodeRunAgentToolResult currently
swallow json.Unmarshal errors; update both functions to log the unmarshal error
at debug level when json.Unmarshal(raw, &payload) fails (while keeping the
current continue behavior) so malformed block.Input or block.RawInput are
recorded for troubleshooting; reference the functions
decodeRunAgentToolInput/decodeRunAgentToolResult and the variables block.Input,
block.RawInput and payload when adding a debug log call that includes the error
and some context (e.g., which raw field failed).

96-98: Context fallback to context.Background() for nil input.

While the coding guidelines recommend avoiding context.Background() outside main and focused tests, this fallback is a defensive measure to prevent panics if a caller passes nil. Consider whether callers should be required to provide a valid context instead, which would surface bugs earlier.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/run/internal/acpshared/reusable_agent_lifecycle.go` around
lines 96 - 98, Remove the defensive fallback that sets ctx =
context.Background() when ctx is nil; instead validate the incoming ctx variable
and fail fast by returning an explicit error (e.g., return fmt.Errorf("nil
context passed to <functionName>") ) or by propagating an error up the call
chain from the function that currently contains the check (replace the current
block that uses ctx and context.Background()). Update the function signature to
return an error if needed and update its callers to provide a non-nil context
(or handle the returned error), so nil contexts surface as bugs rather than
being silently replaced by context.Background().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/core/agents/agents.go`:
- Around line 157-159: The current loop returns fmt.Errorf("resolve agent %q:
%w", normalized, problem.Err) which unwraps only the inner error and loses the
richer Problem diagnostics; change the wrap to use the whole Problem value
(e.g., fmt.Errorf("resolve agent %q: %w", normalized, problem)) so callers
retain Problem.Error() context and unwrapping still works; update the return in
the loop that iterates c.Problems (where problem.Name == normalized) to wrap
problem instead of problem.Err.

In `@internal/core/agents/execution_test.go`:
- Around line 11-385: These tests use flat top-level test functions instead of
the repository standard table-driven subtests; refactor each
TestResolveExecutionContextAppliesRuntimePrecedence,
TestResolveExecutionContextAppliesAgentModelWhenModelFlagIsUnset,
TestResolveExecutionContextPublicWrapperCapturesBaseRuntimeBeforeOverrides,
TestExecutionContextSystemPromptUsesCanonicalOrder,
TestExecutionContextSystemPromptKeepsDiscoveryCatalogCompact,
TestExecutionContextSystemPromptEscapesHostOwnedMetadataBlocks,
TestExecutionContextSystemPromptFallsBackToBasePromptWhenNoAgentSelected, and
TestResolveExecutionContextPreservesExistingRuntimeWhenAgentOmitsField to wrap
their assertions inside t.Run("Should <behaviour>", func(t *testing.T){ ... })
(or a table of named cases with t.Run for multiple scenarios), preserving
existing setup and assertions but moving them into appropriately named subtests
so failures report the descriptive subtest name and conform to the repository
testing pattern.

In `@internal/core/agents/mcpserver/server_test.go`:
- Around line 78-99: The test uses NewServer() and HostContext{} which can pick
up real-agent files; make the failure-path hermetic by creating a temp workspace
and config dirs and wiring them into the server/runtime before invoking handler:
set the server's BaseRuntime.WorkspaceRoot to t.TempDir() (or otherwise point
BaseRuntime on the created temp dir) and set the environment variables HOME and
XDG_CONFIG_HOME to temp dirs (via t.TempDir()) before calling
server.runAgentTool(HostContext{}) and executing handler(context.Background(),
...), ensuring the real runner workspace/config cannot resolve a
"missing-agent".
- Around line 21-187: Convert the top-level tests into t.Run subtests with
"Should..." names: create a single Test... wrapper (e.g., TestServerBehaviors)
and move the bodies of TestLoadHostContextFromEnvParsesReservedPayload,
TestLoadHostContextFromEnvRejectsMissingPayload,
TestRunAgentToolMarksStructuredFailuresAsToolErrors,
TestNewServerAppliesOptionsAndDefaults,
TestLoadHostContextFromEnvUsesProcessEnvironment, and
TestServeStdioReturnsWhenContextIsCanceled into individual t.Run("Should ...",
func(t *testing.T){ ... }) blocks (keep t.Parallel() where used), rename each
subtest description to start with "Should" (e.g., "Should parse reserved payload
from env"), and remove the original top-level TestXxx functions so the file uses
the required subtest pattern while preserving all assertions and helper setup
(e.g., stdioSwapMu, ServeStdio, LoadHostContextFromEnv, NewServer,
runAgentTool).

In `@internal/core/run/exec/prompt_exec.go`:
- Around line 81-86: The current logic calls executeExecJob and then
state.completeTurn(result) but if both completeTurn and result.err are non-nil
it returns only result.err, hiding the persistence failure; change the control
flow in the block around executeExecJob / state.completeTurn so you capture the
error returned by state.completeTurn (e.g., errComplete :=
state.completeTurn(result)) and if errComplete != nil return
buildPreparedPromptResult(state, result), errComplete (before returning
result.err), otherwise proceed to return result.err if present or the successful
value; this ensures state.completeTurn failures (persistence/terminal-state
errors) are surfaced instead of being masked by result.err.

In `@pkg/compozy/events/kinds/reusable_agent_test.go`:
- Around line 9-36: The test
TestReusableAgentLifecyclePayloadJSONPreservesMeaningfulZeroValues should be
converted to a table-driven test with subtests using t.Run("Should ...") and
t.Parallel() called inside each subtest: create a slice of cases (with name and
payload), iterate cases and call t.Run(caseName, func(t *testing.T){
t.Parallel(); marshal the case.payload, assert the JSON contains the expected
fields like `"nested_depth":0`, `"max_nested_depth":0`, `"success":false`,
`"blocked":false` and use t.Fatalf on errors }), keeping the same assertions and
referencing ReusableAgentLifecyclePayload and
ReusableAgentLifecycleStageNestedCompleted to locate the payload construction.

---

Nitpick comments:
In `@internal/core/run/internal/acpshared/reusable_agent_lifecycle.go`:
- Around line 248-264: decodeRunAgentToolInput and decodeRunAgentToolResult
currently swallow json.Unmarshal errors; update both functions to log the
unmarshal error at debug level when json.Unmarshal(raw, &payload) fails (while
keeping the current continue behavior) so malformed block.Input or
block.RawInput are recorded for troubleshooting; reference the functions
decodeRunAgentToolInput/decodeRunAgentToolResult and the variables block.Input,
block.RawInput and payload when adding a debug log call that includes the error
and some context (e.g., which raw field failed).
- Around line 96-98: Remove the defensive fallback that sets ctx =
context.Background() when ctx is nil; instead validate the incoming ctx variable
and fail fast by returning an explicit error (e.g., return fmt.Errorf("nil
context passed to <functionName>") ) or by propagating an error up the call
chain from the function that currently contains the check (replace the current
block that uses ctx and context.Background()). Update the function signature to
return an error if needed and update its callers to provide a non-nil context
(or handle the returned error), so nil contexts surface as bugs rather than
being silently replaced by context.Background().

In `@internal/setup/reusable_agents.go`:
- Around line 221-225: The cleanup call to removeReusableAgentPath(backupPath)
can fail after the new agent is already installed to targetPath, causing the
function to return an error and incorrectly signal install failure; change this
to log the cleanup failure instead of returning it (or aggregate cleanup errors
separately) so the function returns nil on successful install—specifically, in
the block that calls removeReusableAgentPath(backupPath) avoid returning
fmt.Errorf on error and instead call the appropriate logger with context about
backupPath and continue, or append the cleanup error to a separate variable that
does not affect the install success return.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a4243844-84ed-4c76-a425-d44003e1bc8b

📥 Commits

Reviewing files that changed from the base of the PR and between 7074dd7 and 98e3462.

⛔ Files ignored due to path filters (21)
  • .compozy/tasks/agents/reviews-001/_meta.md is excluded by !**/*.md
  • .compozy/tasks/agents/reviews-001/issue_001.md is excluded by !**/*.md
  • .compozy/tasks/agents/reviews-001/issue_002.md is excluded by !**/*.md
  • .compozy/tasks/agents/reviews-001/issue_003.md is excluded by !**/*.md
  • .compozy/tasks/agents/reviews-001/issue_004.md is excluded by !**/*.md
  • .compozy/tasks/agents/reviews-001/issue_005.md is excluded by !**/*.md
  • .compozy/tasks/agents/reviews-001/issue_006.md is excluded by !**/*.md
  • .compozy/tasks/agents/reviews-001/issue_007.md is excluded by !**/*.md
  • .compozy/tasks/agents/reviews-001/issue_008.md is excluded by !**/*.md
  • .compozy/tasks/agents/reviews-001/issue_009.md is excluded by !**/*.md
  • .compozy/tasks/agents/reviews-001/issue_010.md is excluded by !**/*.md
  • .compozy/tasks/agents/reviews-001/issue_011.md is excluded by !**/*.md
  • .compozy/tasks/agents/reviews-001/issue_012.md is excluded by !**/*.md
  • .compozy/tasks/agents/reviews-001/issue_013.md is excluded by !**/*.md
  • .compozy/tasks/agents/reviews-001/issue_014.md is excluded by !**/*.md
  • .compozy/tasks/agents/reviews-001/issue_015.md is excluded by !**/*.md
  • .compozy/tasks/agents/reviews-001/issue_016.md is excluded by !**/*.md
  • .compozy/tasks/agents/reviews-001/issue_017.md is excluded by !**/*.md
  • .compozy/tasks/agents/reviews-001/issue_018.md is excluded by !**/*.md
  • .compozy/tasks/agents/reviews-001/issue_019.md is excluded by !**/*.md
  • .compozy/tasks/agents/reviews-001/issue_020.md is excluded by !**/*.md
📒 Files selected for processing (24)
  • internal/cli/agents_commands.go
  • internal/cli/agents_commands_test.go
  • internal/cli/reusable_agents_doc_examples_test.go
  • internal/core/agent/client.go
  • internal/core/agent/client_test.go
  • internal/core/agents/agents.go
  • internal/core/agents/agents_test.go
  • internal/core/agents/execution.go
  • internal/core/agents/execution_test.go
  • internal/core/agents/mcpserver/server.go
  • internal/core/agents/mcpserver/server_test.go
  • internal/core/plan/prepare_test.go
  • internal/core/run/exec/exec_test.go
  • internal/core/run/exec/prompt_exec.go
  • internal/core/run/internal/acpshared/command_io.go
  • internal/core/run/internal/acpshared/command_io_test.go
  • internal/core/run/internal/acpshared/reusable_agent_lifecycle.go
  • internal/core/run/internal/acpshared/session_handler.go
  • internal/core/run/internal/acpshared/session_handler_test.go
  • internal/setup/bundle.go
  • internal/setup/install_test.go
  • internal/setup/reusable_agents.go
  • pkg/compozy/events/kinds/reusable_agent.go
  • pkg/compozy/events/kinds/reusable_agent_test.go
✅ Files skipped from review due to trivial changes (6)
  • internal/core/run/exec/exec_test.go
  • internal/cli/reusable_agents_doc_examples_test.go
  • internal/core/run/internal/acpshared/command_io_test.go
  • internal/cli/agents_commands.go
  • internal/core/agents/agents_test.go
  • pkg/compozy/events/kinds/reusable_agent.go
🚧 Files skipped from review as they are similar to previous changes (7)
  • internal/core/run/internal/acpshared/session_handler_test.go
  • internal/core/agent/client.go
  • internal/setup/bundle.go
  • internal/core/agent/client_test.go
  • internal/core/agents/execution.go
  • internal/cli/agents_commands_test.go
  • internal/core/run/internal/acpshared/command_io.go

Comment thread internal/core/agents/agents.go Outdated
Comment thread internal/core/agents/execution_test.go
Comment thread internal/core/agents/mcpserver/server_test.go
Comment thread internal/core/agents/mcpserver/server_test.go
Comment thread internal/core/run/exec/prompt_exec.go Outdated
Comment thread internal/core/run/internal/acpshared/reusable_agent_lifecycle.go
Comment thread pkg/compozy/events/kinds/reusable_agent_test.go

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

♻️ Duplicate comments (1)
internal/core/agents/mcpserver/server_test.go (1)

21-192: ⚠️ Potential issue | 🟠 Major

Convert these top-level tests into t.Run("Should ...") subtests.

This file still uses top-level Test... functions instead of the required subtest pattern.

#!/bin/bash
# Verify current test shape in this file.
rg -n --type=go '^func Test' internal/core/agents/mcpserver/server_test.go
rg -n --type=go 't\.Run\("Should' internal/core/agents/mcpserver/server_test.go

As per coding guidelines "**/*_test.go: MUST use t.Run("Should...") pattern for ALL test cases" and "**/*_test.go: Use table-driven tests with subtests (t.Run) as the default pattern for Go tests".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/agents/mcpserver/server_test.go` around lines 21 - 192, The
tests are top-level functions and must be converted to subtests using the
t.Run("Should ...") pattern; replace each Test... function
(TestLoadHostContextFromEnvParsesReservedPayload,
TestLoadHostContextFromEnvRejectsMissingPayload,
TestRunAgentToolMarksStructuredFailuresAsToolErrors,
TestNewServerAppliesOptionsAndDefaults,
TestLoadHostContextFromEnvUsesProcessEnvironment,
TestServeStdioReturnsWhenContextIsCanceled) with a single top-level TestServer
(or similar) that contains t.Run("Should parse reserved payload", ...),
t.Run("Should reject missing payload", ...), t.Run("Should mark structured
failures as tool errors", ...), etc., moving each function body into its
corresponding subtest and preserving behavior including t.Parallel() inside each
subtest where applicable and keeping references to functions/types like
loadHostContextFromEnv, LoadHostContextFromEnv, NewServer, server.runAgentTool,
ServeStdio, reusableagents.RunAgentContextEnvVar, and others so tests still call
the same code paths.
🧹 Nitpick comments (2)
internal/core/run/exec/exec_test.go (1)

459-466: Avoid panic in this test helper; use t.Helper() and fail through testing.T.

Line 465 panics on marshal failure, which gives poorer failure attribution than a helper-aware test failure.

Proposed fix
-func preparedPromptTextContentBlock(text string) model.ContentBlock {
+func preparedPromptTextContentBlock(t *testing.T, text string) model.ContentBlock {
+	t.Helper()
 	payload, err := json.Marshal(model.TextBlock{
 		Type: model.BlockText,
 		Text: text,
 	})
 	if err != nil {
-		panic(err)
+		t.Fatalf("marshal text block: %v", err)
 	}
 	return model.ContentBlock{
 		Type: model.BlockText,
 		Data: payload,
 	}
 }

And update call sites accordingly:

-Blocks: []model.ContentBlock{preparedPromptTextContentBlock("nested reply")},
+Blocks: []model.ContentBlock{preparedPromptTextContentBlock(t, "nested reply")},

As per coding guidelines: Mark test helper functions with t.Helper() so stack traces point to the caller.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/run/exec/exec_test.go` around lines 459 - 466,
preparedPromptTextContentBlock currently panics on json.Marshal errors; change
it into a test helper that accepts t *testing.T, call t.Helper() at the top, and
use t.Fatalf (or t.Fatalf with the marshal error) instead of panic to fail the
test with proper attribution; update all call sites that invoked
preparedPromptTextContentBlock(text) to pass the testing.T (e.g.,
preparedPromptTextContentBlock(t, text)) and adjust imports if needed.
internal/core/run/exec/prompt_exec.go (1)

42-43: Wrap propagated errors with operation context.

Several branches return raw errors (for example on Line 43, Line 48, Line 53, Line 79), which makes nested-failure diagnosis harder in callers and logs.

Proposed fix
 	if err := agent.EnsureAvailable(ctx, cfg); err != nil {
-		return PreparedPromptResult{}, err
+		return PreparedPromptResult{}, fmt.Errorf("execute prepared prompt: ensure runtime availability: %w", err)
 	}
 
 	state, err := prepareExecRunState(ctx, cfg)
 	if err != nil {
-		return PreparedPromptResult{}, err
+		return PreparedPromptResult{}, fmt.Errorf("execute prepared prompt: prepare exec state: %w", err)
 	}
@@
 	if err := state.writeStarted(cfg); err != nil {
-		return PreparedPromptResult{}, err
+		return PreparedPromptResult{}, fmt.Errorf("execute prepared prompt: persist started turn: %w", err)
 	}
@@
 	execJob, err := newExecRuntimeJobWithMCP(promptText, state, agentExecution, mcpServers)
 	if err != nil {
-		return PreparedPromptResult{}, err
+		return PreparedPromptResult{}, fmt.Errorf("execute prepared prompt: build exec job: %w", err)
 	}

As per coding guidelines: Prefer explicit error returns with wrapped context using fmt.Errorf("context: %w", err).

Also applies to: 46-49, 52-53, 77-80

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/run/exec/prompt_exec.go` around lines 42 - 43, The code
currently returns raw errors from calls like agent.EnsureAvailable(ctx, cfg) and
other branches returning PreparedPromptResult{}, which makes diagnostics hard;
update each return that directly forwards err (including the EnsureAvailable
call and the other branches around the Prepare/execute logic referenced in the
diff) to wrap the error with operation context using fmt.Errorf("failed to
ensure agent available: %w", err) (and similar contextual messages for the other
operations), e.g., replace returns of the form `return PreparedPromptResult{},
err` with `return PreparedPromptResult{}, fmt.Errorf("<operation description>:
%w", err)` so callers get wrapped errors with clear context.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/core/agents/mcpserver/server_test.go`:
- Around line 158-190: The test currently redirects stdout to a pipe but never
consumes stdoutRead, which can cause ServeStdio to block on writes; spawn a
background goroutine that calls io.ReadAll(stdoutRead) (or otherwise
continuously drains stdoutRead) immediately after creating the pipe and before
calling ServeStdio so the pipe buffer can't fill and block the server; reference
stdoutRead/stdoutWrite and ServeStdio(ctx, HostContext{}) when adding the drain
goroutine and ensure it runs until stdoutWrite is closed.

In `@internal/core/run/exec/exec_test.go`:
- Around line 20-205: Combine the related validation cases in
TestExecutePreparedPromptValidatesInputs into a table-driven set of subtests
using t.Run("Should ...") (e.g., "Should error when config is nil", "Should
error when prompt is empty") that call ExecutePreparedPrompt and assert specific
errors using ErrorContains/ErrorAs or errors.Is/strings.Contains rather than
only checking err == nil; update other tests that currently do weak assertions
to similarly assert the expected error type/message. Also fix the helper
preparedPromptTextContentBlock to call t.Helper() and replace any panic(err)
with t.Fatalf to surface failures via the testing.T; use the function names
ExecutePreparedPrompt, TestExecutePreparedPromptValidatesInputs, and
preparedPromptTextContentBlock to locate the changes. Ensure each subtest uses
t.Parallel appropriately and preserves existing setup/cleanup semantics.

In `@internal/core/run/exec/prompt_exec.go`:
- Around line 64-67: The current branch in prompt_exec.go returns only
completeErr when both err (the MCP-builder failure) and completeErr (the
completion failure from state.completeTurn) exist and differ, which masks the
original err; change the return to preserve both errors using errors.Join(err,
completeErr) (or errors.Join(completeErr, err)) so callers see both failures,
keeping the same returned prepared result from buildPreparedPromptResult(state,
failure) and referencing state.completeTurn, buildPreparedPromptResult, err and
completeErr to locate the code to update.

---

Duplicate comments:
In `@internal/core/agents/mcpserver/server_test.go`:
- Around line 21-192: The tests are top-level functions and must be converted to
subtests using the t.Run("Should ...") pattern; replace each Test... function
(TestLoadHostContextFromEnvParsesReservedPayload,
TestLoadHostContextFromEnvRejectsMissingPayload,
TestRunAgentToolMarksStructuredFailuresAsToolErrors,
TestNewServerAppliesOptionsAndDefaults,
TestLoadHostContextFromEnvUsesProcessEnvironment,
TestServeStdioReturnsWhenContextIsCanceled) with a single top-level TestServer
(or similar) that contains t.Run("Should parse reserved payload", ...),
t.Run("Should reject missing payload", ...), t.Run("Should mark structured
failures as tool errors", ...), etc., moving each function body into its
corresponding subtest and preserving behavior including t.Parallel() inside each
subtest where applicable and keeping references to functions/types like
loadHostContextFromEnv, LoadHostContextFromEnv, NewServer, server.runAgentTool,
ServeStdio, reusableagents.RunAgentContextEnvVar, and others so tests still call
the same code paths.

---

Nitpick comments:
In `@internal/core/run/exec/exec_test.go`:
- Around line 459-466: preparedPromptTextContentBlock currently panics on
json.Marshal errors; change it into a test helper that accepts t *testing.T,
call t.Helper() at the top, and use t.Fatalf (or t.Fatalf with the marshal
error) instead of panic to fail the test with proper attribution; update all
call sites that invoked preparedPromptTextContentBlock(text) to pass the
testing.T (e.g., preparedPromptTextContentBlock(t, text)) and adjust imports if
needed.

In `@internal/core/run/exec/prompt_exec.go`:
- Around line 42-43: The code currently returns raw errors from calls like
agent.EnsureAvailable(ctx, cfg) and other branches returning
PreparedPromptResult{}, which makes diagnostics hard; update each return that
directly forwards err (including the EnsureAvailable call and the other branches
around the Prepare/execute logic referenced in the diff) to wrap the error with
operation context using fmt.Errorf("failed to ensure agent available: %w", err)
(and similar contextual messages for the other operations), e.g., replace
returns of the form `return PreparedPromptResult{}, err` with `return
PreparedPromptResult{}, fmt.Errorf("<operation description>: %w", err)` so
callers get wrapped errors with clear context.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cc1b96dc-5bb4-4d0b-8a16-e9e21427c353

📥 Commits

Reviewing files that changed from the base of the PR and between 98e3462 and 39d3ddf.

⛔ Files ignored due to path filters (11)
  • .compozy/tasks/agents/reviews-002/_meta.md is excluded by !**/*.md
  • .compozy/tasks/agents/reviews-002/issue_001.md is excluded by !**/*.md
  • .compozy/tasks/agents/reviews-002/issue_002.md is excluded by !**/*.md
  • .compozy/tasks/agents/reviews-002/issue_003.md is excluded by !**/*.md
  • .compozy/tasks/agents/reviews-002/issue_004.md is excluded by !**/*.md
  • .compozy/tasks/agents/reviews-002/issue_005.md is excluded by !**/*.md
  • .compozy/tasks/agents/reviews-002/issue_006.md is excluded by !**/*.md
  • .compozy/tasks/agents/reviews-002/issue_007.md is excluded by !**/*.md
  • .compozy/tasks/agents/reviews-002/issue_008.md is excluded by !**/*.md
  • .compozy/tasks/agents/reviews-002/issue_009.md is excluded by !**/*.md
  • .compozy/tasks/agents/reviews-002/issue_010.md is excluded by !**/*.md
📒 Files selected for processing (5)
  • internal/core/agents/agents.go
  • internal/core/agents/agents_test.go
  • internal/core/agents/mcpserver/server_test.go
  • internal/core/run/exec/exec_test.go
  • internal/core/run/exec/prompt_exec.go
✅ Files skipped from review due to trivial changes (1)
  • internal/core/agents/agents_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/core/agents/agents.go

Comment thread internal/core/agents/mcpserver/server_test.go
Comment thread internal/core/run/exec/exec_test.go
Comment thread internal/core/run/exec/prompt_exec.go Outdated
@pedronauck
pedronauck merged commit ab93fca into main Apr 10, 2026
4 of 5 checks passed
@pedronauck
pedronauck deleted the agents branch April 10, 2026 23:26
@coderabbitai coderabbitai Bot mentioned this pull request Apr 11, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Apr 11, 2026
This was referenced Apr 11, 2026
This was referenced Apr 12, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Jun 19, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Aug 5, 2026
4 of 6 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant