GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
GeezerVison is showing github.com — feat: discover ACP harnesses configured in the workspace by hugodutka · Pull Request #30557 · coder/coder Conversation
|
|
/coder-agents-review |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
Chat: Review posted | View chat Review history
deep-review v0.13.0 | Round 1 | Last posted: Round 1, 11 findings (1 P0, 1 P2, 3 P3, 4 P4, 2 Nit), COMMENT. Review Finding inventoryFinding inventory: PR #30557Findings
Contested and acknowledgedNone. Law analysis
Round logRound 1Law mandatory split. Panel skipped. Netero: 1 P0, 1 P2, 3 P3, 4 P4, 2 Nit, 1 Note. Law: 1 P1 (merged into CRF-1), 1 Note. CRF-1 severity kept at P0 over Law's P1 because the defect deterministically breaks all context sync for affected workspaces (reviewer-prompt P0 "will break"); orchestrator verified coderd/agentapi/context.go marshalContextResourceBody has no AcpHarness case and the WorkspaceAgentContextBodyKind enum has no acp_harness value. Posted as COMMENT: deep-review post rejects REQUEST_CHANGES on rounds without panel reviewers. Reviewed against f8e053e..fadd3a5. About deep-reviewCRF = Coder Review Finding (P0-P4, Nit, Note)
|
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
This PR adds ACP harness discovery to the agent, an agent HTTP catalog with an SDK client, and harness metadata in workspace context pushes. Findings: 1 P0, 1 P2, 3 P3, 4 P4, 2 Nit. This round ran only mechanical checks and a split analysis; the full review panel will review after the split.
Please split this PR. Slice A: the discovery engine, GET /api/v0/acp/harnesses, ListACPHarnesses, and agent wiring without ACPResources: and SetOnChange(a.contextManager.Trigger). #30558 needs only slice A. Slice B: the acp_harness context resource (proto variant, KindACPHarness, resolver changes, ContextResources) plus the coderd half it requires: a marshalContextResourceBody case, an acp_harness enum value, and an Agent API version bump with agent-side gating. Slice A is subprocess execution in the agent; slice B changes the agent to coderd contract, and that contract is incomplete in this PR.
Notes:
agent/x/agentacp/manager.go:153: the grouped-options branch of configOptions has no test; the fake harness returns only ungrouped options.agent/agentcontext/resolve.go:718: the applyMCPCaps to applyProviderCaps change exists only for ACP resources and belongs in slice B.🤖 This review was automatically generated with Coder Agents.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| }, | ||
| } | ||
|
|
||
| case KindACPHarness: |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
P0 [CRF-1] coderd rejects every PushContextState that contains an acp_harness body, so a workspace with any file in ~/.coder/acp/ stops syncing all context: instruction files, skills, and MCP servers. (Netero, Law)
marshalContextResourceBody in coderd/agentapi/context.go has no ContextResource_AcpHarness case and returns unsupported body variant, and validateAndConvertContextResources fails the whole request on that error; a scratch test in coderd/agentapi returned unsupported body variant *proto.ContextResource_AcpHarness. The push loop retries with the same payload, so context stays stale until the config is removed. TestAgent_ACPInterface passes because agenttest.Client records pushes without coderd validation. Fix: move the context integration to a separate PR that adds the coderd case, an acp_harness value in the workspace_agent_context_body_kind enum, and an Agent API minor version bump, and have the agent send the variant only to coderd versions that accept it.
🤖
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
This can only happen during a multi-replica rollout deployment, and it'll automatically recover once the rollout completes. We don't officially support rollout deployments. Not fixing.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| _, _ = hash.Write(configHash[:]) | ||
| _, _ = hash.Write(payload) | ||
| _, _ = hash.Write([]byte(m.configDir)) | ||
| r := agentcontext.Resource{ID: "acp_harness:" + h.Slug, Kind: agentcontext.KindACPHarness, Source: h.Slug, SourcePath: m.configDir, SizeBytes: uint64(len(payload)), Payload: payload, Status: agentcontext.StatusOK, ACPHarness: &h} |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
P2 [CRF-2] ACP resources use the bare harness slug as Source, which is the same value MCP servers use for their name, so an ACP harness and an MCP server with the same name make coderd reject the push as duplicate source. (Netero)
MCP resources set Source: s.Name (agent/agentcontext/mcp.go:68,86) and coderd rejects any request with a repeated Source (coderd/agentapi/context.go:305). A user with a .mcp.json server claude and ~/.coder/acp/claude.json loses all context sync. Use a source that cannot collide with MCP server names, for example the config file path.
🤖
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
This is a valid concern, but I think the fix should be to stop enforcing the uniqueness of Source across all resource Kinds. We can enforce that the tuple of (Kind, Source) is unique instead. We should do this in a follow up PR.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
fadd3a5 to
82f2681
Compare
| "github.com/coder/coder/v2/codersdk/workspacesdk" | ||
| ) | ||
|
|
||
| const discoveryInterval = 30 * time.Second |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Feels like we have too many timeouts spread out across the workspace agent submodules. I think we need a redesign soon on how chatd and workspace agent communicate and have clearly defined behavior and allowances controlled on a different level.
For now, though, this seems to control multiple knobs (polling interval and retry delay).
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
I changed it to only control the polling interval.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
| h.Error = err.Error() | ||
| return h | ||
| } | ||
| ctx, cancel := m.timeout(ctx, setupTimeout) |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Why is this timeout defined in manager.go but used here? Echoing my previous disdain about the amount of timeouts we have.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
The timeout function is there just to make it easier to construct a context with the quartz clock. We depend on that in some tests.
It's there because discovery.go implements methods on the Manager.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| } | ||
| defer func() { | ||
| // Drain stderr before logging. Raw diagnostics may contain launch | ||
| // credentials, so keep them out of the catalog and workspace context. |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
If true, should we be logging it without it having been explicitly enabled? I believe Warn makes it through even for the regular log level? Not sure if Debug is always written somewhere, but if there's any output on stderr, we'll essentially be logging it.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
if there's any output on stderr, we'll essentially be logging it
Yeah, but only to workspace agent logs which usually stay on the workspace. We need to log them somewhere even if they contain sensitive info so the admin can debug why the workspace agent is failing to probe the harness they configured. The alternative would be to leave them in h.Error but that gets saved in Postgres later.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| }() | ||
| init, err := p.conn.Initialize(ctx, acp.InitializeRequest{ProtocolVersion: acp.ProtocolVersionNumber, ClientInfo: &acp.Implementation{Name: "coder-agent", Version: buildinfo.Version()}}) | ||
| if err != nil { | ||
| h.Error = "cannot initialize harness; see workspace agent logs for details" |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Are we sure something will be printed? [I may find the answer later, but jotting down the question for now]
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Yes, the defer statement does it.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
82f2681 to
e097492
Compare
71719e6 to
d19cd6a
Compare
d19cd6a to
5ae95cf
Compare
5ae95cf to
a0c0b2f
Compare
a0c0b2f to
4304830
Compare
00ec052 to
c970a16
Compare
|
|
||
| func (m *Manager) pollDiscovery() { | ||
| reload := func() { | ||
| if err := m.Reload(m.ctx); err != nil { |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Considering we're using m.ctx here, we have no poll-related bound for reload. It could hang? Considering we have an interval, should the interval duration be an upper bound, or perhaps some multiplier of it?
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Yeah. I now gave Reload its own context that inherits from m.ctx with its own timeout.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| m.wg.Add(1) | ||
| m.mu.Unlock() | ||
| defer m.wg.Done() | ||
| lifetime, cancel := context.WithCancel(m.ctx) |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Looking at poll below, we're using 2x m.ctx here, both in lifetime and stop.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Got rid of lifetime and stop and made m.discover use the ctx directly.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
The polling context could be changed, but otherwise LGTM.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
c970a16 to
00accb1
Compare
00accb1 to
9ac5344
Compare
There was an error while loading. Please reload this page.
Probes configured ACP harnesses to discover session load and resume capabilities and available configuration options. Caches successful probes until the configuration, workspace directory, or environment changes, and exposes probe errors through the agent API and workspace SDK. Depends on #30557. Generated with Codex, carefully reviewed by Hugo.
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
There was an error while loading. Please reload this page.