Repository navigation
feat: decouple worker concurrency from coordinator uniqueness - #229
Conversation
|
@richardfogaca is attempting to deploy a commit to the Compozy Team on Vercel. A member of the Team first needs to authorize it. |
WalkthroughReplaces coordinator config field MaxActivePerWorkspace with MaxActiveSessionsPerWorkspace (default 5). Adds DefaultCoordinatorMaxActiveSessionsPerWorkspace, requires the value be positive during validation, updates TOML overlay handling, API payloads, runtime wiring into SpawnBudget/Lineage, and relevant tests. ChangesCoordinator Session Cap Configuration
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
a9d0b73 to
f091edd
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@internal/config/autonomy_test.go`:
- Around line 49-69: Restructure TestCoordinatorConfigValidatesMaxActiveSessions
into three t.Run subtests (e.g., "Should validate default", "Should error when
max_active_sessions_per_workspace too low", "Should validate higher") and call
t.Parallel() inside each subtest; locate the logic around
DefaultCoordinatorConfig(), modifications to MaxActiveSessionsPerWorkspace and
Validate("autonomy.coordinator", nil). For the invalid case assert the error is
non-nil AND that the error message contains the expected text (e.g.,
"max_active_sessions_per_workspace" or the specific validation phrase) using an
ErrorContains-style check rather than only err != nil; keep the valid-case
checks identical but wrapped in their own subtests.
In `@internal/config/autonomy.go`:
- Around line 25-29: Replace the hardcoded literal 5 used by
DefaultCoordinatorMaxActiveSessionsPerWorkspace with a value retrieved from the
project's configuration/environment system: read the int option (e.g., via the
existing config package or environment variable like
COORDINATOR_MAX_ACTIVE_SESSIONS_PER_WORKSPACE) and fall back to 5 if unset;
update the initializer for DefaultCoordinatorMaxActiveSessionsPerWorkspace to
call that config lookup instead of embedding the literal so the default is
driven by the config layer.
🪄 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: 5bfa7d76-6f27-4cf1-9c4c-cd0ed3e4803f
⛔ Files ignored due to path filters (1)
packages/site/content/runtime/core/configuration/config-toml.mdxis excluded by!**/*.mdx
📒 Files selected for processing (4)
internal/config/autonomy.gointernal/config/autonomy_test.gointernal/config/merge.gointernal/coordinator/coordinator.go
f091edd to
6d360f4
Compare
pedronauck
left a comment
There was a problem hiding this comment.
there are some conflicts to solve @richardfogaca
The coordinator's spawn budget MaxActivePerWorkspace was seeded from autonomy.coordinator.max_active_per_workspace, which is pinned to 1 to preserve coordinator uniqueness. That made the coordinator consume the workspace's only active-session slot, so it could never spawn a worker, blocking autonomous worker execution. Add autonomy.coordinator.max_active_sessions_per_workspace with default 5, and feed the spawn budget from it. max_active_per_workspace stays pinned at 1; coordinator uniqueness remains enforced by the daemon singleton check.
6d360f4 to
50e3134
Compare
|
conflicts solved @pedronauck PTAL when you get a chance, thanks |
pedronauck
left a comment
There was a problem hiding this comment.
The fix is functionally correct and solves a real, confirmed blocker — the coordinator counts itself in validateSpawnCaps (internal/session/spawn.go:284-307, since its normalized lineage sets RootSessionID = coordinator.ID via store/session_lineage.go:71-73), so with the budget pinned to 1 no worker can ever spawn. Repro and root cause check out, and the fix is targeted at the right line (the budget flows through spawnLineage → effectiveSpawnBudget, which doesn't touch MaxActivePerWorkspace).
That said, I don't think this is the right shape for the change, and there are gaps. Requesting changes on the following:
1. The core rationale is inaccurate
The PR argues:
Simply allowing
max_active_per_workspace > 1would instead permit multiple coordinators, which is why a separate field is the correct shape.
This isn't true. Coordinator uniqueness is enforced solely by the daemon singleton check — activeCoordinator + coordinator.HealthySession under a mutex (internal/daemon/coordinator_runtime.go:296-307,404-416). That check never consults the spawn budget. The coordinator is created via Create (startCoordinatorSession), not via Spawn, so max_active_per_workspace has never gated coordinator creation. Bumping it would only ever allow more workers — which is exactly the goal here. The premise that a second field is needed to protect uniqueness doesn't hold.
2. This leaves max_active_per_workspace as a vestigial field
After this change, max_active_per_workspace is still validated to be exactly 1 (internal/config/autonomy.go:161) and still exposed over the API, but it no longer feeds any runtime enforcement. That's a validated-but-no-op knob. Per the repo's "Greenfield Alpha — Zero Legacy Tolerance" rules (no dual fields, hard cuts, delete obsolete code instead of bridging), the cleaner fix is to repurpose the existing field to mean the session cap (default 5, drop the "must be 1" pin) and lean on the daemon singleton for uniqueness — rather than adding a parallel field and advertising "Fully backward compatible."
3. Near-duplicate naming is a footgun
max_active_per_workspace (coordinators, =1) vs max_active_sessions_per_workspace (all managed sessions, =5) differ by a single word. This is very easy to confuse for both operators and agents. If we do keep two fields, they need names that don't collide on read.
4. Partial-surface gap (agent-manageability)
The new knob materially governs worker concurrency, but it's not plumbed into CoordinatorConfigPayload (internal/api/core/agent_contracts.go:16-26), which is served at GET /api/agent/coordinator/config over both HTTP and UDS (httpapi/routes.go:143, udsapi/routes.go:166) and is part of the agent Situation Surface. As-is, an agent inspecting its coordinator config sees the now-meaningless max_active_per_workspace but not the field that actually controls its worker budget. This violates "No partial-surface completions" / "Agent-manageable by default." Please plumb the new field through the contract payload (and regenerate the OpenAPI/TS if the contract DTO changes).
5. Missing AGH Impact Audit
The PR body is missing the required four-row AGH Impact Audit (native tools / extensibility & hooks / workspace data isolation / official AGH skill). Please add it.
Suggested direction
I'd prefer the hard-cut version: rename/repurpose to a single max_active_sessions_per_workspace (default 5), delete the must be 1 validation, document that coordinator uniqueness is owned by the daemon singleton, and expose the field in the agent-facing payload. If you want to keep an explicit uniqueness invariant as defense-in-depth, that's fine — but then fix the rationale, keep it out of the spawn-budget seed entirely, and resolve the naming collision.
Also: Verify (fmt, lint, unit, build) was still pending at review time — let's get that green before merge.
|
thanks for the review @pedronauck, I went with the hard-cut direction you suggested:
Validation passed locally: focused config/coordinator/API/daemon/workspace tests, |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
internal/api/spec/spec_test.go (1)
1025-1033: ⚡ Quick winAssert the legacy coordinator property is absent.
This checks that
max_active_sessions_per_workspaceis required, but it would still pass if the generated schema accidentally kept the oldmax_active_per_workspacealongside it. Adding an explicit absence check will catch partial rename regressions in the OpenAPI contract.Suggested test hardening
assertRequired( t, configSchema, "enabled", "agent_name", "default_ttl_seconds", "max_children", "max_active_sessions_per_workspace", "source", ) + assertPropertyAbsent(t, configSchema, "max_active_per_workspace") assertEnumValues(t, propertySchema(t, configSchema, "source"), "workspace", "global", "default")🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/api/spec/spec_test.go` around lines 1025 - 1033, The test currently asserts required fields via assertRequired but doesn't ensure the old property name is removed; add an explicit absence assertion for the legacy "max_active_per_workspace" property (e.g. use assert.NotContains or require.NotContains) immediately after the assertRequired call to verify configSchema does NOT include "max_active_per_workspace" so a partial rename won't slip through.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@internal/api/spec/spec_test.go`:
- Around line 1025-1033: The test currently asserts required fields via
assertRequired but doesn't ensure the old property name is removed; add an
explicit absence assertion for the legacy "max_active_per_workspace" property
(e.g. use assert.NotContains or require.NotContains) immediately after the
assertRequired call to verify configSchema does NOT include
"max_active_per_workspace" so a partial rename won't slip through.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: a6882724-e82f-4df5-9f36-d294e6001b1d
⛔ Files ignored due to path filters (5)
openapi/agh.jsonis excluded by!**/*.jsonpackages/site/content/runtime/core/autonomy/coordinator.mdxis excluded by!**/*.mdxpackages/site/content/runtime/core/autonomy/execution-profiles.mdxis excluded by!**/*.mdxpackages/site/content/runtime/core/configuration/config-toml.mdxis excluded by!**/*.mdxweb/src/generated/agh-openapi.d.tsis excluded by!**/generated/**
📒 Files selected for processing (12)
internal/api/contract/agents.gointernal/api/core/agent_channels_internal_test.gointernal/api/core/agent_contracts.gointernal/api/core/agent_contracts_test.gointernal/api/spec/spec_test.gointernal/api/udsapi/agent_channels_test.gointernal/config/autonomy.gointernal/config/autonomy_test.gointernal/config/merge.gointernal/coordinator/coordinator.gointernal/daemon/coordinator_runtime_test.gointernal/workspace/resolver_test.go
💤 Files with no reviewable changes (1)
- internal/config/merge.go
✅ Files skipped from review due to trivial changes (2)
- internal/coordinator/coordinator.go
- internal/workspace/resolver_test.go
TL;DR - Make the coordinator workspace session budget configurable with one hard-cut setting:
max_active_sessions_per_workspacedefaults to5, so a coordinator can run workers alongside itself while coordinator uniqueness remains owned by the daemon singleton.The problem
A coordinator can never spawn a worker. Every
agh spawnfrom a live coordinator fails:Root cause: the coordinator's spawn budget capped active managed sessions in the workspace at
1. The coordinator itself fills that slot, so no worker can start.The fix
Use one public coordinator setting for the active managed session budget:
max_active_sessions_per_workspace5Coordinator uniqueness is not budget-driven. It is enforced by the daemon runtime singleton check for a healthy active coordinator in the workspace. The coordinator is still created through the daemon coordinator path, not through worker spawn.
Changes
internal/config/autonomy.goMaxActiveSessionsPerWorkspace, default5, validate it as positiveinternal/config/merge.gomax_active_per_workspacecoordinator overlayinternal/coordinator/coordinator.goMaxActiveSessionsPerWorkspaceinternal/api/core/agent_contracts.go/internal/api/contract/agents.goopenapi/agh.json/web/src/generated/agh-openapi.d.tsAGH Impact Audit
Testing
go test ./internal/config/... ./internal/coordinator/... ./internal/api/... ./internal/daemon/... ./internal/workspace/...go test ./...go run ./cmd/agh-codegen checkgo build ./...go run github.com/golangci/golangci-lint/v2/cmd/[email protected] run --allow-parallel-runners --timeout 10m ./...Summary by CodeRabbit
Release Notes
Configuration Updates
max_active_per_workspacetomax_active_sessions_per_workspaceAPI Updates
max_active_sessions_per_workspaceinstead ofmax_active_per_workspaceImprovements