Skip to content

feat(queue): per-(tool, model) concurrency mode for solve queue (closes #1474)#1797

Open
konard wants to merge 9 commits into
mainfrom
issue-1474-a2f95662f0b0
Open

feat(queue): per-(tool, model) concurrency mode for solve queue (closes #1474)#1797
konard wants to merge 9 commits into
mainfrom
issue-1474-a2f95662f0b0

Conversation

@konard

@konard konard commented May 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #1474.

Implements per-tool concurrency gating for the solve queue so the --tool agent queue can run one task at a time per free model (e.g. one minimax-m2.5-free task at a time, in parallel with one gpt-5-nano-free task). The agent queue defaults to global one-at-a-time today; operators flip individual queues (claude, agent, codex, qwen, gemini) to a different mode via env vars or the existing HIVE_MIND_QUEUE_CONFIG links notation.

See docs/case-studies/issue-1474/README.md for the full design, R1–R7 requirements, and acceptance criteria.

Concurrency modes

Mode Behavior
off No extra cap from the queue layer (default for claude/codex/qwen/gemini).
global-one-at-a-time At most 1 in-flight item across the tool's entire queue. Default for agent.
per-free-model-one-at-a-time For free models, at most 1 in-flight item per (tool, model). Non-free models bypass the gate.
per-model-one-at-a-time At most 1 in-flight item per (tool, model) for every model.

Configuration surface

  1. Env var per toolHIVE_MIND_AGENT_CONCURRENCY=per-free-model-one-at-a-time (same for CLAUDE, CODEX, QWEN, GEMINI).
  2. Links notation — extend HIVE_MIND_QUEUE_CONFIG:
    (
      (agent-concurrency per-free-model-one-at-a-time)
      (claude-concurrency global-one-at-a-time)
    )
    
  3. Built-in defaultsagent: global-one-at-a-time, all others off.

Resolution priority: HIVE_MIND_QUEUE_CONFIG > HIVE_MIND_<TOOL>_CONCURRENCY > built-in default.

Requirements traceability

  • R1--tool agent defaults to globally and unconditionally 1-at-a-time. Verified by R1: agent default (global-one-at-a-time) makes only 1 of 2 startable.
  • R2 — Each specific free agent model has its own 1-at-a-time slot. Verified by R2: per-free-model mode lets two different free models start in parallel + R2: per-free-model mode blocks two items with the SAME free model.
  • R3 — Every queue's mode is configurable via env vars and HIVE_MIND_QUEUE_CONFIG. Verified by parseQueueConfig — *-concurrency lino entries (R3) (4 cases).
  • R4 — Free-model detection covers both providers (OpenCode Zen opencode/...-free and Kilo Gateway kilo/...-free) and aliases. Verified by isFreeAgentModel unit cases.
  • R5 — Other tools (claude/codex/qwen/gemini) are unaffected unless explicitly configured. Verified by R5: claude default (off) does not gate concurrent same-model items + cross-tool isolation: agent gate does NOT block claude/codex.
  • R6 — Behavior covered by automated tests (28 new tests in tests/solve-queue-concurrency.test.mjs) and documented in docs/case-studies/issue-1474/README.md.
  • R7 — No regressions: full npm test green (207/207 test files), lint clean, all line caps respected.

Test plan

  • node tests/solve-queue-concurrency.test.mjs → 28/28 pass
  • npm test (default suite) → 207/207 test files pass
  • npm run lint → clean
  • bash scripts/check-file-line-limits.sh → all files ≤ 1500 lines

Reproduction (manual)

# Default agent queue: global-one-at-a-time
HIVE_MIND_QUEUE_CONFIG="" node -e "
import('./src/queue-config.lib.mjs').then(m => console.log(m.QUEUE_CONFIG.concurrency))
"
# → { claude: 'off', agent: 'global-one-at-a-time', codex: 'off', qwen: 'off', gemini: 'off' }

# Switch agent to per-free-model mode
HIVE_MIND_AGENT_CONCURRENCY=per-free-model-one-at-a-time node -e "
import('./src/queue-config.lib.mjs').then(m => console.log(m.QUEUE_CONFIG.concurrency.agent))
"
# → 'per-free-model-one-at-a-time'

Files changed

Path Purpose
src/models/index.mjs Add isFreeAgentModel, normalizeAgentModelKey helpers.
src/queue-config.lib.mjs Add CONCURRENCY_MODES, (<tool>-concurrency <mode>) lino parsing, HIVE_MIND_<TOOL>_CONCURRENCY env var, QUEUE_CONFIG.concurrency, getConcurrencyMode.
src/telegram-solve-queue.lib.mjs SolveQueueItem.model + gate in findStartableItems.
src/telegram-solve-queue.concurrency.lib.mjs (new) Pure helpers: countProcessingByTool, countProcessingByToolAndModel, canStartUnderConcurrencyMode.
src/telegram-bot.mjs Parse --model/-m/--model= at the queue boundary; pass to enqueue.
tests/solve-queue-concurrency.test.mjs (new) 28 tests covering R1–R5.
docs/case-studies/issue-1474/README.md (new) Full design + R1–R7 requirements.
.changeset/issue-1474-agent-concurrency.md (new) Patch-bump changeset.
experiments/{debug-r1,debug-concurrency-second-pass,reproduce-r2-failing,verify-finally-bug}.mjs (new) Reproducible debug scripts for future investigation.

Atomic commits

  1. feat(models): add isFreeAgentModel and normalizeAgentModelKey helpers
  2. feat(queue): add per-tool concurrency mode to QUEUE_CONFIG
  3. feat(queue): gate findStartableItems on per-(tool, model) concurrency
  4. feat(telegram-bot): forward --model alias into the solve queue
  5. test(queue): cover per-(tool, model) concurrency gating
  6. docs(case-studies): add issue-1474 case study + changeset
  7. chore(experiments): add debug scripts for issue-1474 investigation

Adding .gitkeep for PR creation (default mode).
This file will be removed when the task is complete.

Issue: #1474
@konard konard self-assigned this May 13, 2026
konard added 7 commits May 13, 2026 08:10
Lift the free-agent-model classification out of agent.lib.mjs so the
solve queue can call it without depending on the runner module. Pair it
with normalizeAgentModelKey to give the queue a stable per-(provider,
model) gating key.

Issue #1474.
Extend QUEUE_CONFIG with a per-tool concurrency map and add the four
modes off / global-one-at-a-time / per-free-model-one-at-a-time /
per-model-one-at-a-time. Resolution priority is HIVE_MIND_QUEUE_CONFIG
(via new (<tool>-concurrency <mode>) lino entries) >
HIVE_MIND_<TOOL>_CONCURRENCY env var > built-in default. Defaults:
agent = global-one-at-a-time, all others = off.

Issue #1474.
Add a pure helper module (telegram-solve-queue.concurrency.lib.mjs) that
decides whether the head of a tool queue can start under the configured
concurrency mode, and call it from findStartableItems(). The
SolveQueueItem now carries a normalized model alias used as the per-
model gating key. Extracted to a separate file to keep the main queue
module under the 1500-line cap.

Issue #1474.
Parse --model / -m / --model= at the queue boundary the same way --tool
is parsed and pass it to solveQueue.enqueue() so the queue can gate per-
(free-)model concurrency. Mirrors validateModelInArgs() but only needs
the literal string value because equality is enough for gating.

Issue #1474.
Add 28 tests in tests/solve-queue-concurrency.test.mjs covering:
- HIVE_MIND_QUEUE_CONFIG lino parsing of (<tool>-concurrency <mode>)
- SolveQueueItem.model plumbing through enqueue (alias normalization)
- getProcessingCountByToolAndModel counter
- canStartUnderConcurrencyMode under all four modes
- findStartableItems gating: R1 (agent default), R2 (per-free-model
  parallel + same-model block), R5 (claude unchanged), cross-tool
  isolation

Issue #1474.
Document the per-(tool, model) concurrency design, requirements R1-R7,
acceptance criteria, and atomic commit plan.

Issue #1474.
Keep the debugging scripts in experiments/ for future reproduction:
- debug-r1.mjs / debug-concurrency-second-pass.mjs: walk the queue
  through two enqueue + start cycles to inspect findStartableItems()
  gating behavior.
- reproduce-r2-failing.mjs: minimum reproducible case for the original
  R2 failure.
- verify-finally-bug.mjs: standalone proof that try/finally restores
  state before an async body resolves, which was the root cause of the
  earlier test flakiness in withConcurrency().

Issue #1474.
@konard konard changed the title [WIP] For all free models in queue of --tool agent please use mode 1 at time for each specific free model. feat(queue): per-(tool, model) concurrency mode for solve queue (closes #1474) May 13, 2026
@konard konard marked this pull request as ready for review May 13, 2026 08:11
@konard

konard commented May 13, 2026

Copy link
Copy Markdown
Contributor Author

Working session summary

Issue #1474 is solved. PR #1797 is ready for review with all CI green:

PR: #1797

What was done

  • Added per-tool concurrency mode to the solve queue with 4 modes: off, global-one-at-a-time, per-free-model-one-at-a-time, per-model-one-at-a-time.
  • The --tool agent queue defaults to global-one-at-a-time (R1). Operators flip individual queues to per-free-model-one-at-a-time via HIVE_MIND_AGENT_CONCURRENCY env var or (agent-concurrency per-free-model-one-at-a-time) in HIVE_MIND_QUEUE_CONFIG.
  • Other tools (claude/codex/qwen/gemini) default to off so they're unaffected (R5).
  • New SolveQueueItem.model field carries the normalized alias as the per-model gating key.
  • Extracted concurrency helpers into src/telegram-solve-queue.concurrency.lib.mjs to keep the main queue module under the 1500-line cap.
  • 28 new tests in tests/solve-queue-concurrency.test.mjs; full npm test green (207/207 files).
  • Case study at docs/case-studies/issue-1474/README.md with R1–R7 traceability.

Commits (7 atomic): models helpers → queue-config → queue gating → telegram-bot wire-up → tests → docs/changeset → debug experiments.


This summary was automatically extracted from the AI working session output.

@konard

konard commented May 13, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Solution Draft Log

This log file contains the complete execution trace of the AI solution draft process.

💰 Cost: $14.707635

📊 Context and tokens usage:

Claude Opus 4.7: (5 sub-sessions)

  1. 118.6K / 1M (12%) input tokens, 2.9K / 128K (2%) output tokens
  2. 115.6K / 1M (12%) input tokens, 14.0K / 128K (11%) output tokens
  3. 116.4K / 1M (12%) input tokens, 23.4K / 128K (18%) output tokens
  4. 116.6K / 1M (12%) input tokens, 21.6K / 128K (17%) output tokens
  5. 102.0K / 1M (10%) input tokens, 21.0K / 128K (16%) output tokens

Total: (9.4K new + 508.5K cache writes + 17.4M cache reads) input tokens, 111.4K output tokens, $14.707635 cost

🤖 Models used:

  • Tool: Anthropic Claude Code
  • Requested: opus
  • Model: Claude Opus 4.7 (claude-opus-4-7)

📎 Log file uploaded as Gist (9684KB)


Now working session is ended, feel free to review and add any feedback on the solution draft.

@konard

konard commented May 13, 2026

Copy link
Copy Markdown
Contributor Author

✅ Ready to merge

This pull request is now ready to be merged:

  • All CI checks have passed
  • No merge conflicts
  • No pending changes

Monitored by hive-mind with --auto-restart-until-mergeable flag

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.

For all free models in queue of --tool agent please use mode 1 at time for each specific free model.

1 participant