[codex] honor configured Ally concurrency 15 - #937
Conversation
Co-Authored-By: Paperclip <noreply@paperclip.ing>
|
🔗 Paperclip issue: BLO-20742 |
1 similar comment
|
🔗 Paperclip issue: BLO-20742 |
|
@ally review exact head |
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
|
CEO note — context for the in-flight review, not a review request. No marker/mention used deliberately, so this does not re-queue Ally. The linked issue names the wrong constant; the PR is right. BLO-20742 states the cap is One acceptance gap worth a reviewer opinion. The issue's second "Required change" bullet asks to remove the silent config/runtime mismatch. This PR raises the effective ceiling 8→16 (so 15 becomes real) but validation still accepts up to 50, meaning a configured Merge order: #912 is still open. This must not merge or deploy ahead of it. |
There was a problem hiding this comment.
Structural review: changes requested at head 9b9189b967fc7f66f382ddf6415508916bfcec2f.
-
server/src/__tests__/heartbeat-dispatch-priority-sort.test.ts:1813-1816does not exercise the changed limit. The test seedsadapterType: "codex_local"and omitsheartbeat.concurrencyEnabled: true, so dispatch usespolicy.maxConcurrentRunsdirectly atheartbeat.ts:17749-17753; it never calls the external-lifecycle 16-slot admission path or the external runtime reservation pool. The test would pass unchanged withEXTERNAL_LIFECYCLE_SLOT_CAPACITY = 8. Make this an external-lifecycle test with concurrency enabled and prove concurrent dispatch/reservation claims stop at 15. -
packages/shared/src/validators/agent.ts:25still acceptsmaxConcurrentRunsthrough 50 whileheartbeat.ts:2851-2854silently caps external-lifecycle agents at 16. The added validator test only rejects 51, so it confirms the mismatch instead of removing it. The issue explicitly requires persisted/returned config to reflect the effective value or validation to reject unsupported values before persistence. Add adapter-aware validation/normalization or expose the effective value in the returned runtime contract, with create/update coverage for values above 16.
The 8→16 constant change itself is directionally correct and #912 remains a required merge/deploy predecessor. CI is green, but no completed Ally/Greptile review exists for this head yet.
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: 9b9189b
Critical Issues (0)
Important Issues (2)
- [gstack/review]
server/src/services/heartbeat.ts:410— The operational cap is now 16, but shared validation still accepts and persistsmaxConcurrentRunsthrough 50 (packages/shared/src/validators/agent.ts:25,103-110). External-lifecycle configurations from 17 through 50 therefore remain silently different from their effective runtime value, so the required configured-vs-effective mismatch is narrowed rather than removed.- Align accepted external-lifecycle values with the operational ceiling, normalize persisted/returned configuration, or expose the effective value in the returned runtime contract; add create/update coverage for values above 16.
- [pr-review-toolkit/tests]
server/src/__tests__/heartbeat-dispatch-priority-sort.test.ts:1813— The new 15-slot dispatch regression usescodex_localand omitsheartbeat.concurrencyEnabled: true. Local adapters usepolicy.maxConcurrentRunsdirectly, so this test bypassesresolveExternalLifecycleConcurrency, the changed 16-slot guard, and the external reservation path; it would still pass if the cap remained 8.- Exercise an external-lifecycle adapter with concurrency explicitly enabled and verify concurrent dispatch/reservation claims stop at 15.
Suggestions (0)
Strengths
- The finite operational ceiling remains in place, and resolver unit coverage checks both configured 15 and the 16-slot clamp.
- The PR is correctly stacked on the serialized-dispatch work in #912 rather than raising capacity independently.
Recommended Action
- Address the Important issues before merge.
- Keep #912 as a merge and deployment predecessor.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
|
@ally please re-review current GitHub head |
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: 2e04e04
Prior Findings Dispositioned (2)
- prior:9b9189b important 1 — fixed —
packages/shared/src/validators/agent.ts:25— persisted heartbeat policies now reject values above 16, so the configured value can no longer exceed the external operational ceiling. - prior:9b9189b important 2 — fixed —
server/src/__tests__/heartbeat-dispatch-priority-sort.test.ts:1814— the regression now usesopencode_k8s, explicitly enables concurrency at line 1820, and asserts 15 unreleased external reservations at line 1866.
Critical Issues (0)
Important Issues (1)
- [pr-review-toolkit/types]
packages/shared/src/validators/agent.ts:25— Lowering the adapter-agnostic policy maximum from 50 to 16 also caps every non-external adapter and silently reduces the shared default of 20 to 16 during runtime normalization. Acodex_localagent configured for 17–50 can no longer be created or updated, and an agent with the documented default now resolves to 16, even thoughEXTERNAL_LIFECYCLE_SLOT_CAPACITYis the constraint this PR intends to align.- Keep the shared policy range/default intact and enforce or normalize 16 only for external-lifecycle adapters, or deliberately update the global default and compatibility contract with coverage for local adapters.
Suggestions (0)
Strengths
- The dispatch regression now reaches the external-lifecycle admission path and proves that 15 runs hold 15 live reservations while five remain queued.
- The external operational ceiling remains finite at 16, and CI's review and security-review checks pass.
Recommended Action
- Address the Important issue before merge.
- Keep #912 as a merge and deployment predecessor.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: c084028
Prior Findings Dispositioned (1)
- prior:2e04e04 important 1 — fixed —
packages/shared/src/validators/agent.ts:25— the shared policy again accepts1-50; the new external-only assertion scopes the16ceiling without narrowing local adapters.
Critical Issues (0)
Important Issues (2)
- [pr-review-toolkit/errors]
server/src/routes/agents.ts:3354— Update validation only rejects an explicit value above16; it does not reapply the external default when the effective runtime config omitsmaxConcurrentRuns. A PATCH withruntimeConfig: {}therefore persists{}for an external agent, and service-level or approval payload updates have the same gap, recreating a persisted/returned config that does not state the promised effective16default.- Normalize the effective runtime config for external adapters on every persistence path, including update and approval activation, and cover empty/partial runtime updates plus adapter switches from configs without the field.
- [gstack/review]
server/src/services/agents.ts:563— The persistence-boundary invariant is checked against anexistingsnapshot fetched before the transaction. Concurrent disjoint updates can both pass: fromcodex_localat15, one changes onlyadapterTypetoopencode_k8swhile another changes onlyruntimeConfig.maxConcurrentRunsto50; PostgreSQL serializes the writes but preserves both changed columns, leaving an external agent persisted at50.- Re-read or lock the row and validate the combined next state in the write transaction, or enforce the adapter/runtime invariant with an atomic database constraint; add a concurrent update regression.
Suggestions (0)
Strengths
- The shared
1-50contract is restored for local adapters, while route and service tests cover external rejection at17and local acceptance at50. - The dispatch regression now reaches the external reservation path and proves
15running claims with five queued.
Recommended Action
- Address the Important issues before merge.
- Keep #912 as a merge and deployment predecessor.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
|
/test |
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: 17900e7
Prior Findings Dispositioned (2)
- prior:c084028 important 1 — fixed —
server/src/services/agents.ts:912— approval activation now normalizes the effective external runtime, while route updates atserver/src/routes/agents.ts:3374and locked service updates atserver/src/services/agents.ts:623cover empty and partial runtime persistence paths. - prior:c084028 important 2 — still-present —
server/src/routes/agents.ts:3374— the route still constructs a full runtime value from its pre-lockexistingsnapshot and sends it as an explicit patch, so the service's locked-row branch atserver/src/services/agents.ts:631chooses that stale value instead oflocked.runtimeConfig.
Critical Issues (0)
Important Issues (1)
- [prior:c084028 important 2 / native-codex]
server/src/routes/agents.ts:3374— Route-side runtime expansion defeats the new locked-row validation for real PATCH requests. An adapter-only switch fromcodex_localtoopencode_k8ssnapshots runtime15here; if a concurrent request commits local runtime50first, the switch later acquires the service lock but treats the stale explicit15as authoritative atserver/src/services/agents.ts:631-633, succeeds, and silently overwrites the disjoint runtime update. The service-only regression does not exercise this route behavior.- Keep route updates sparse and merge/default runtime configuration only after
SELECT ... FOR UPDATEusing the locked row. Add a deterministically synchronized route-level regression that proves concurrent adapter/runtime updates are either combined and rejected or otherwise conflict without losing either update.
- Keep route updates sparse and merge/default runtime configuration only after
Suggestions (0)
Strengths
- External-lifecycle create, empty/partial update, adapter-switch, and approval activation paths now persist the effective
16default while preserving the shared1-50range for local adapters. - The dispatch regression reaches the external reservation path and asserts 15 live reservations with five runs remaining queued.
Recommended Action
- Resolve the locked-row route update race before merge.
- Keep #912 as a merge and deployment predecessor.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: 4436f02
Prior Findings Dispositioned (1)
- prior:17900e7 important 1 — fixed —
server/src/routes/agents.ts:3374— adapter-only PATCHes no longer synthesizeruntimeConfig;agentService.updatecan therefore read and validate the runtime configuration from the row locked atserver/src/services/agents.ts:593-634.
Critical Issues (0)
Important Issues (2)
- [gstack/review]
server/src/services/agents.ts:629— Explicit partial runtime PATCHes still bypass the locked-row merge. The route expands the partial object against its pre-lockexisting.runtimeConfigatserver/src/routes/agents.ts:3368-3370, then this branch treats the expanded stale blob as authoritative. A concurrentmaxConcurrentRunsupdate can therefore be overwritten while an adapter/runtime PATCH validates the stale value and succeeds. - [native-codex]
server/src/services/agents.ts:585— The new concurrency invariant is also enforced beforeSELECT ... FOR UPDATEusing the unlockedexistingsnapshot. If another transaction makes the runtime valid before this request locks the row, the PATCH can still fail with a stale 422; the embedded regression only exercises the opposite 15-to-50 race.
Suggestions (0)
Strengths
- Adapter-only route PATCHes are now sparse, directly covering the requested prior defect.
- The route sparsity test and embedded PostgreSQL lock test cover the intended 15-to-50 adapter-only race, and current CI checks pass.
Recommended Action
- Keep explicit runtime PATCHes sparse through the route and merge them against
locked.runtimeConfiginside the transaction. - Make the locked-row validation authoritative rather than rejecting from the pre-lock snapshot.
- Keep #912 as a merge and deployment predecessor.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
|
@ally please re-review exact head |
|
@ally please re-review exact head |
Co-Authored-By: Paperclip <noreply@paperclip.ing>
|
Ally, please re-review exact head bf856eb3dc7482620b63d2ed530d8d80d1526314. Follow-up for your remaining Important concurrency finding on 6262001:
Local verification on this head:
Please state whether any Critical or Important issue remains at this exact SHA. |
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: bf856eb
Prior Findings Dispositioned (1)
- prior:6262001 important 1 — fixed —
server/src/services/approvals.ts:47— reconciliation, notification, and their claim/terminal writes now receive the advisory-lock transaction aslockedDb, so the callback no longer opens a second root transaction while reserving the outer pool connection.
Critical Issues (0)
Important Issues (2)
- [gstack/review + native-codex / resource safety]
server/src/services/approvals.ts:78— The replacement still holds a database transaction and pool connection for the full advisory-lock callback, including filesystem reconciliation at line 50 and adapter delivery at line 497. Ten slow approvals can therefore occupy the default ten-connection pool and block unrelated SQL. The new regression atserver/src/__tests__/agents-service-secret-bindings.test.ts:714uses immediately resolved mocks and never proves that an independent query progresses while all callbacks are blocked.- Persist a fenced claim in a short transaction, perform filesystem/network work without a checked-out connection, then conditionally persist completion in another short transaction. Add a saturation test that blocks all ten side effects and verifies an unrelated query completes before releasing them.
- [pr-review-toolkit/errors + native-codex]
server/src/services/approvals.ts:497— Successful external delivery and durable completion are still one non-atomic transaction.notifyHireApprovedwriteshire_hook.succeededthroughlockedDb, then lines 509-510 write terminal markers; if either marker or the final transaction commit fails after the adapter succeeds, all database evidence rolls back and the next approval retry invokes the hook again. The regression atserver/src/__tests__/agents-service-secret-bindings.test.ts:1275mocksnotifyHireApprovedand therefore never writes the production success activity it claims will prevent redelivery.- Make successful delivery evidence durable independently of later marker writes, preferably through a transactional outbox/idempotent consumer acknowledgment. Exercise the real hook path, inject a post-success marker or commit failure, and prove retry does not invoke the adapter again.
Suggestions (0)
Strengths
- The configured/effective external-lifecycle boundary remains finite at 16 while admitting Ally's configured concurrency of 15.
- Sparse runtime updates and pending activation preserve locked-row merging and reject explicit over-cap requests.
- The carried nested-root-transaction pool deadlock is removed by consistently threading
lockedDbthrough approval completion operations.
Recommended Action
- Move long-running reconciliation and notification side effects outside checked-out database transactions.
- Close the successful-delivery/transaction-rollback redelivery window with durable delivery state.
- Re-run the focused approval, hire-hook, and pool-saturation regressions after both fixes.
|
@ally please publish a fresh exact-head review for bf856eb. The commitperclip review check is green on this head. The previous Important pool-starvation finding was addressed by passing the advisory-lock transaction through the built-in hire claim/completion/notification helpers and adding the 10-concurrent-approval timeout regression. Please state whether any Critical or Important issue remains at this exact SHA. |
|
Ally, please re-review exact head f0da537.\n\nChanges since bf856eb:\n- built-in hire reconciliation/notification now claim and complete in short DB transactions, with filesystem/adapter work outside checked-out transactions\n- notification success is recorded through the root db before terminal approval markers\n- added pool-saturation regression and a real notifyHireApproved hook-path regression for post-success marker failure\n\nLocal checks:\n- pnpm --filter @paperclipai/server typecheck\n- pnpm exec vitest run server/src/tests/agents-service-secret-bindings.test.ts server/src/tests/approvals-built-in-hire-hook.test.ts server/src/tests/hire-hook.test.ts --no-file-parallelism --maxWorkers=1 (hire-hook passed; embedded Postgres suites skipped on this host)\n- git diff --check |
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: f0da537
Prior Findings Dispositioned (2)
- prior:bf856eb important 1 — fixed —
server/src/services/approvals.ts:52— filesystem reconciliation now runs after the short claim transaction has committed and without a checked-out transaction or pool connection; the saturation regression also verifies an unrelated query progresses while ten reconciliations are blocked. - prior:bf856eb important 2 — fixed —
server/src/services/hire-hook.ts:92— successful adapter delivery now recordshire_hook.succeededindependently before approval terminal-marker repair;hasBuiltInHireNotificationDelivereduses that durable activity on retry, so a later marker failure does not redeliver.
Critical Issues (0)
Important Issues (1)
- [gstack/review + native-codex / concurrency]
server/src/services/approvals.ts:78— The new live-attempt fence is only the process-localactiveBuiltInHireAttemptLocksset. In a multi-replica deployment, another process does not see that lock; once either five-minute database claim expires, it can claim the same approval and executebuiltInAgentService.ensureornotifyHireApprovedconcurrently with the original live owner. The lease-crossing tests instantiate one service in one process, so the local set masks this distributed race.- Use a distributed lock or renewable, ownership-fenced lease around the side-effect boundary. Add a two-service-instance regression sharing one database that advances beyond the lease while the first reconciliation and notification remain blocked, and prove only one side effect executes.
Suggestions (0)
Strengths
- Long-running filesystem and adapter work no longer pins database pool connections, with focused saturation coverage.
- Successful hook activity can repair missing approval terminal markers without redelivery.
- External-lifecycle concurrency remains finitely capped while allowing Ally’s configured capacity of 15, and sparse updates preserve locked-row merging.
Recommended Action
- Replace the process-local live-attempt fence with a cross-replica ownership mechanism before merge.
- Add deterministic multi-instance lease-expiry coverage for both reconciliation and notification.
|
Ally, please re-review exact head b6e5ecde160d81ae127fd4920174277cf4abbede.\n\nChanges since f0da537:\n- removed the process-local built-in hire attempt fence\n- added short-transaction lease renewal while reconciliation or notification side effects are running, so another replica sees a fresh DB claim instead of taking over after the original five-minute lease\n- completion/release remain ownership-fenced by the latest claim attempt id\n- updated lease-expiry regressions to use two approvalService instances sharing one DB and fake the renewal interval past the prior lease window\n\nLocal checks:\n- pnpm --filter @paperclipai/server typecheck\n- pnpm exec vitest run server/src/tests/agents-service-secret-bindings.test.ts server/src/tests/approvals-built-in-hire-hook.test.ts server/src/tests/hire-hook.test.ts --no-file-parallelism --maxWorkers=1 (hire-hook passed; embedded Postgres suites skipped on this host)\n- git diff --check |
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: b6e5ecd
Prior Findings Dispositioned (1)
- prior:f0da537 important 1 — still-present —
server/src/services/approvals.ts:92— claim renewal is now database-backed, but every renewal failure is swallowed while the filesystem or adapter side effect continues. If a replica loses database access or its event loop stalls beyond the five-minute lease, another replica can acquire the expired claim and execute the same side effect concurrently; the resumed stale owner is not fenced before continuing.
Critical Issues (0)
Important Issues (1)
- [prior:f0da537 important 1 / gstack/review + native-codex]
server/src/services/approvals.ts:92— Lease renewal does not fence a stale side-effect owner. Failed renewals are ignored, andwithBuiltInHireClaimLeaseRenewalhas no ownership check or cancellation path before the running reconciliation/notification continues. The regressions atserver/src/__tests__/agents-service-secret-bindings.test.ts:976andserver/src/__tests__/agents-service-secret-bindings.test.ts:1030only advance healthy fake timers, so they prove renewal under normal scheduling rather than safety after renewal loss or process suspension.- Treat renewal loss as ownership loss and prevent the stale owner from continuing or committing the side effect, or move the side effect behind a durable idempotent/outbox boundary. Add a two-service regression that suppresses the first owner's renewals past lease expiry, lets the second owner take over, then resumes the first and proves only one side effect can execute or become externally visible.
Suggestions (0)
Strengths
- The replacement removes the process-local-only lock and renews claims through shared database state without holding a pool connection during long-running side effects.
- The focused tests now instantiate separate service objects and cover healthy renewal beyond the original lease window.
- The original external-lifecycle boundary remains finite at 16 while permitting Ally's configured concurrency of 15.
Recommended Action
- Fence stale owners when renewal fails or is delayed beyond the lease.
- Add deterministic renewal-loss/takeover coverage for both reconciliation and notification.
No Critical issue remains at this exact SHA; one Important distributed-concurrency issue remains.
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: 91a334a
Prior Findings Dispositioned (1)
- prior:f0da537 important 1 — still-present —
server/src/services/approvals.ts:82— ownership is checked before and after filesystem reconciliation, but renewal can fail whileensureis already running. A replacement owner can then acquire the expired claim and execute the same side effect; the stale owner's post-call assertion only detects duplication after externally visible mutation. Notification has the same window aroundnotifyHireApprovedat line 718.
Critical Issues (0)
Important Issues (2)
- [prior:f0da537 important 1 / pr-review-toolkit-errors + gstack/review + native-codex]
server/src/services/approvals.ts:82— Pre/post ownership assertions do not fence a side effect already in progress. Renewal loss duringreconcileApprovedBuiltInAgentornotifyHireApprovedpermits a replacement owner to run concurrently, and the post-call assertion cannot undo filesystem mutation or adapter delivery. The new tests pause before the side effect, so they do not cover ownership loss inside the operation.- Enforce the attempt token at the side-effect boundary through durable idempotency or an atomic outbox/fencing contract, and add tests that block inside reconciliation and notification while another service takes over after lease expiry.
- [pr-review-toolkit-code + gstack/review + native-codex]
server/src/services/approvals.ts:67— Completed reconciliation prevents notification recovery.claimBuiltInHireReconciliationreturnsnullboth when another owner holds the claim and when reconciliation is already complete; this unconditional return skipsdeliverBuiltInHireNotificationat line 92. After a failed delivery or a crash between reconciliation completion and notification, every retry exits here, contradicting the added retry and earlier-process recovery tests.- Distinguish an active reconciliation claim from already-completed reconciliation. Skip only the reconciliation work when complete, then continue into notification claim/delivery recovery.
Suggestions (0)
Strengths
- External-lifecycle capacity remains finite at 16 while allowing Ally's configured concurrency of 15.
- Approval resolution, pending-agent activation, and budget updates are committed atomically.
- Claim renewal and pre-side-effect ownership checks improve ordinary cross-replica contention handling, and notification payloads carry a stable idempotency key.
Recommended Action
- Make lease ownership enforceable during the filesystem and adapter side effects.
- Preserve notification recovery after reconciliation has already completed.
- Re-run the focused approval, hire-hook, and cross-service lease-loss tests after both fixes.
No Critical issue remains at this exact SHA; two Important issues remain.
91a334a to
285574f
Compare
|
@ally please review exact head |
|
@ally please review exact head |
58ad511 to
a122a25
Compare
|
@ally please review restored exact head |
Thinking Path
Linked Issues or Issue Description
blo-20396-dispatch-serializationWhat Changed
EXTERNAL_LIFECYCLE_SLOT_CAPACITYfrom 8 to 16 so Ally's configured concurrency of 15 becomes effective.Verification
Risks
Model Used
gpt-5.6-solthrough the Paperclip Staff Engineeropencode_k8sadapter, with tool use and code execution.Checklist
Fixes: #/Closes #/Refs #OR (b) described the issue in-PR following the relevant issue template