Skip to content

Conversation

@code-yeongyu
Copy link
Owner

@code-yeongyu code-yeongyu commented Jan 6, 2026

Summary

  • Add ConcurrencyManager for model-based rate limiting
  • Support per-model, per-provider, and default concurrency limits
  • 0 means unlimited concurrency
  • Default concurrency set to 5
  • Comprehensive test coverage (350+ lines)

Configuration

{
  "background_task": {
    "defaultConcurrency": 5,
    "providerConcurrency": { "anthropic": 3 },
    "modelConcurrency": { "anthropic/claude-sonnet-4-5": 10 }
  }
}

Priority: modelConcurrency > providerConcurrency > defaultConcurrency


Summary by cubic

Adds model-based concurrency control to the background agent to cap parallel tasks and reduce provider throttling. Default limit is 5; setting a limit to 0 makes it unlimited.

  • New Features
    • ConcurrencyManager with per-model, per-provider, and default limits.
    • Priority order: model > provider > default.
    • BackgroundManager acquires before launch and releases on completion/error/timeout.
    • New config: background_task.defaultConcurrency, providerConcurrency, modelConcurrency.
    • Exported ConcurrencyManager and added comprehensive tests.

Written for commit ca4e1a7. Summary will update on new commits.

Setting concurrency to 0 means unlimited (Infinity).
Works for defaultConcurrency, providerConcurrency, and modelConcurrency.

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
@code-yeongyu code-yeongyu merged commit f25f7ed into dev Jan 6, 2026
4 checks passed
@code-yeongyu code-yeongyu deleted the feat/background-agent-concurrency branch January 6, 2026 16:24
@greptile-apps
Copy link

greptile-apps bot commented Jan 6, 2026

Greptile Summary

Added model-based concurrency management for background tasks with configurable limits per model, provider, and default fallback. The ConcurrencyManager implements a priority system (model > provider > default) with queueing and supports 0 as unlimited concurrency.

Major Changes:

  • New ConcurrencyManager class with acquire/release semaphore pattern for rate limiting
  • Configuration schema for background_task.defaultConcurrency, background_task.providerConcurrency, and background_task.modelConcurrency
  • Integration into BackgroundManager.launch() to enforce limits before creating sessions
  • Added model field to BackgroundTask type for tracking which model to release
  • Comprehensive test coverage (350+ lines) following TDD approach

Critical Issues Found:

  • Schema validation prevents 0 values, but implementation treats 0 as unlimited (contradicts PR description)
  • Config not passed to BackgroundManager in src/index.ts:177, so limits won't work
  • Concurrency slot leak in launch() error path when session creation fails after acquiring

Confidence Score: 2/5

  • Not safe to merge - critical bugs prevent the feature from working
  • Three blocking issues: (1) schema rejects 0 values despite 0=unlimited being core feature, (2) config not wired up so concurrency limits won't activate, (3) resource leak when session creation fails. Tests pass in isolation but integration will fail.
  • Pay close attention to src/config/schema.ts (schema validation), src/index.ts (missing config passthrough), and src/features/background-agent/manager.ts (error handling leak)

Important Files Changed

Filename Overview
src/config/schema.ts Added BackgroundTaskConfigSchema with concurrency settings, but .min(1) prevents 0 (unlimited) despite code/tests supporting it
assets/oh-my-opencode.schema.json Added background_task config with minimum: 1 constraint, inconsistent with 0=unlimited feature
src/features/background-agent/concurrency.ts New ConcurrencyManager class implements model/provider/default priority correctly with 0=unlimited support
src/features/background-agent/manager.ts Integrated concurrency manager but missing config passthrough in index.ts and release in launch() error path causes slot leaks

Sequence Diagram

sequenceDiagram
    participant User
    participant BackgroundManager
    participant ConcurrencyManager
    participant OpenCodeClient
    participant BackgroundTask

    User->>BackgroundManager: launch(input)
    BackgroundManager->>BackgroundManager: validate agent
    BackgroundManager->>ConcurrencyManager: acquire(model)
    
    alt limit not reached
        ConcurrencyManager-->>BackgroundManager: resolve immediately
    else limit reached
        ConcurrencyManager->>ConcurrencyManager: queue promise
        Note over ConcurrencyManager: waits in queue
    end

    BackgroundManager->>OpenCodeClient: session.create()
    OpenCodeClient-->>BackgroundManager: sessionID
    BackgroundManager->>BackgroundManager: create BackgroundTask
    BackgroundManager->>OpenCodeClient: session.promptAsync()
    BackgroundManager-->>User: return task

    par Task Execution
        OpenCodeClient->>BackgroundTask: execute prompt
        BackgroundTask-->>OpenCodeClient: completion/error
    and Polling
        BackgroundManager->>BackgroundManager: poll status
    end

    alt on completion/error/timeout
        BackgroundManager->>BackgroundTask: mark complete
        BackgroundManager->>ConcurrencyManager: release(model)
        
        alt queue not empty
            ConcurrencyManager->>ConcurrencyManager: resolve next queued promise
        else queue empty
            ConcurrencyManager->>ConcurrencyManager: decrement count
        end
    end
Loading

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional Comments (2)

  1. src/features/background-agent/manager.ts, line 82-92 (link)

    logic: If client.session.create throws or returns error after acquiring concurrency, the slot is never released (leaks).

    Wrap in try-catch:

    await this.concurrencyManager.acquire(model)
    
    try {
      const createResult = await this.client.session.create({
        body: {
          parentID: input.parentSessionID,
          title: `Background: ${input.description}`,
        },
      })
    
      if (createResult.error) {
        throw new Error(`Failed to create background session: ${createResult.error}`)
      }
      // ... rest of code
    } catch (error) {
      this.concurrencyManager.release(model)
      throw error
    }
  2. src/index.ts, line 177 (link)

    logic: BackgroundManager accepts a config parameter, but it's not being passed here. Concurrency limits won't work.

7 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +236 to +238
defaultConcurrency: z.number().min(1).optional(),
providerConcurrency: z.record(z.string(), z.number().min(1)).optional(),
modelConcurrency: z.record(z.string(), z.number().min(1)).optional(),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Schema doesn't allow 0 as value, but implementation treats 0 as unlimited. According to PR description and tests, 0 means unlimited concurrency.

Suggested change
defaultConcurrency: z.number().min(1).optional(),
providerConcurrency: z.record(z.string(), z.number().min(1)).optional(),
modelConcurrency: z.record(z.string(), z.number().min(1)).optional(),
defaultConcurrency: z.number().min(0).optional(),
providerConcurrency: z.record(z.string(), z.number().min(0)).optional(),
modelConcurrency: z.record(z.string(), z.number().min(0)).optional(),
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/config/schema.ts
Line: 236:238

Comment:
**logic:** Schema doesn't allow 0 as value, but implementation treats 0 as unlimited. According to PR description and tests, 0 means unlimited concurrency.

```suggestion
  defaultConcurrency: z.number().min(0).optional(),
  providerConcurrency: z.record(z.string(), z.number().min(0)).optional(),
  modelConcurrency: z.record(z.string(), z.number().min(0)).optional(),
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 7 files

Confidence score: 2/5

  • Double releasing the concurrency slot in src/features/background-agent/manager.ts (catch block plus notifyParentSession() finally) risks racey state and blocked tasks, so the current behavior isn’t safe to ship.
  • If session.create() fails in src/features/background-agent/manager.ts, the slot is never released, leading to a leak that could eventually halt all queued work.
  • Both assets/oh-my-opencode.schema.json and src/config/schema.ts reject the documented “0 = unlimited concurrency”, so user configs that rely on unlimited agents would break immediately.
  • Pay close attention to src/features/background-agent/manager.ts, assets/oh-my-opencode.schema.json, src/config/schema.ts - concurrency slot handling and schema limits must match documented behavior.
Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="src/features/background-agent/manager.ts">

<violation number="1" location="src/features/background-agent/manager.ts:82">
P1: Missing concurrency release on session creation failure. If `session.create()` throws or returns an error, the acquired concurrency slot is never released, causing a resource leak that could eventually block all tasks for this model.</violation>

<violation number="2" location="src/features/background-agent/manager.ts:144">
P1: Double release of concurrency slot. This release in the catch block is followed by a call to `notifyParentSession()` which also releases in its finally block. Remove this release since `notifyParentSession()` handles cleanup.</violation>
</file>

<file name="assets/oh-my-opencode.schema.json">

<violation number="1" location="assets/oh-my-opencode.schema.json:1665">
P1: Schema `minimum: 1` contradicts the documented behavior that &quot;0 means unlimited concurrency&quot;. Change to `minimum: 0` to allow users to configure unlimited concurrency as described in the PR.</violation>
</file>

<file name="src/config/schema.ts">

<violation number="1" location="src/config/schema.ts:236">
P1: Schema validation contradicts documented behavior. The PR states &quot;0 means unlimited concurrency&quot;, but `.min(1)` rejects 0 as invalid. Change to `.min(0)` to allow unlimited concurrency.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

existingTask.error = errorMessage
}
existingTask.completedAt = new Date()
if (existingTask.model) {
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Double release of concurrency slot. This release in the catch block is followed by a call to notifyParentSession() which also releases in its finally block. Remove this release since notifyParentSession() handles cleanup.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/features/background-agent/manager.ts, line 144:

<comment>Double release of concurrency slot. This release in the catch block is followed by a call to `notifyParentSession()` which also releases in its finally block. Remove this release since `notifyParentSession()` handles cleanup.</comment>

<file context>
@@ -132,6 +141,9 @@ export class BackgroundManager {
           existingTask.error = errorMessage
         }
         existingTask.completedAt = new Date()
+        if (existingTask.model) {
+          this.concurrencyManager.release(existingTask.model)
+        }
</file context>
Fix with Cubic


const model = input.agent

await this.concurrencyManager.acquire(model)
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Missing concurrency release on session creation failure. If session.create() throws or returns an error, the acquired concurrency slot is never released, causing a resource leak that could eventually block all tasks for this model.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/features/background-agent/manager.ts, line 82:

<comment>Missing concurrency release on session creation failure. If `session.create()` throws or returns an error, the acquired concurrency slot is never released, causing a resource leak that could eventually block all tasks for this model.</comment>

<file context>
@@ -60,19 +62,25 @@ export class BackgroundManager {
 
+    const model = input.agent
+
+    await this.concurrencyManager.acquire(model)
+
     const createResult = await this.client.session.create({
</file context>
Fix with Cubic

"maximum": 1000
},
"state_dir": {
"type": "string"
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Schema minimum: 1 contradicts the documented behavior that "0 means unlimited concurrency". Change to minimum: 0 to allow users to configure unlimited concurrency as described in the PR.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At assets/oh-my-opencode.schema.json, line 1665:

<comment>Schema `minimum: 1` contradicts the documented behavior that &quot;0 means unlimited concurrency&quot;. Change to `minimum: 0` to allow users to configure unlimited concurrency as described in the PR.</comment>

<file context>
@@ -1658,6 +1658,35 @@
+    &quot;background_task&quot;: {
+      &quot;type&quot;: &quot;object&quot;,
+      &quot;properties&quot;: {
+        &quot;defaultConcurrency&quot;: {
+          &quot;type&quot;: &quot;number&quot;,
+          &quot;minimum&quot;: 1
</file context>
Suggested change
"type": "string"
"defaultConcurrency": {
"type": "number",
"minimum": 0
},
"providerConcurrency": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "number",
"minimum": 0
}
},
"modelConcurrency": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "number",
"minimum": 0
}
}
Fix with Cubic

Comment on lines +236 to +238
defaultConcurrency: z.number().min(1).optional(),
providerConcurrency: z.record(z.string(), z.number().min(1)).optional(),
modelConcurrency: z.record(z.string(), z.number().min(1)).optional(),
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Schema validation contradicts documented behavior. The PR states "0 means unlimited concurrency", but .min(1) rejects 0 as invalid. Change to .min(0) to allow unlimited concurrency.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/config/schema.ts, line 236:

<comment>Schema validation contradicts documented behavior. The PR states &quot;0 means unlimited concurrency&quot;, but `.min(1)` rejects 0 as invalid. Change to `.min(0)` to allow unlimited concurrency.</comment>

<file context>
@@ -232,6 +232,12 @@ export const RalphLoopConfigSchema = z.object({
 })
 
+export const BackgroundTaskConfigSchema = z.object({
+  defaultConcurrency: z.number().min(1).optional(),
+  providerConcurrency: z.record(z.string(), z.number().min(1)).optional(),
+  modelConcurrency: z.record(z.string(), z.number().min(1)).optional(),
</file context>
Suggested change
defaultConcurrency: z.number().min(1).optional(),
providerConcurrency: z.record(z.string(), z.number().min(1)).optional(),
modelConcurrency: z.record(z.string(), z.number().min(1)).optional(),
defaultConcurrency: z.number().min(0).optional(),
providerConcurrency: z.record(z.string(), z.number().min(0)).optional(),
modelConcurrency: z.record(z.string(), z.number().min(0)).optional(),
Fix with Cubic

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.

2 participants