HKUDS/DeepTutor · warning · HTTPException

{exc}

Error message

{exc}

What it means

Raised as HTTP 409 when starting a memory consolidation run fails because the run manager rejects the request with RunBusyError — typically another consolidation run is already active for the same memory state. The router maps the domain exception to a Conflict status with the exception's message as the detail.

Source

Thrown at deeptutor/api/routers/memory.py:349

        if req.llm_selection
        else None
    )
    try:
        run = await manager.start(
            layer=lyr,
            key=req.key,
            mode=req.mode,
            runner=runner,
            params={
                "budget": req.budget,
                "iterations": req.iterations,
                "language": req.language,
                "llm_selection": selection,
            },
            language=req.language,
        )
    except RunBusyError as exc:
        raise HTTPException(status_code=409, detail=str(exc))
    return run.to_dict()


@router.get("/runs/{run_id}")
async def get_run(run_id: str):
    from deeptutor.services.memory.consolidator.runs import get_run_manager

    run = get_run_manager().get(run_id)
    if run is None:
        raise HTTPException(status_code=404, detail="unknown run_id")
    return run.to_dict()


@router.post("/runs/{run_id}/cancel")
async def cancel_run(run_id: str):
    from deeptutor.services.memory.consolidator.runs import get_run_manager

    ok = await get_run_manager().cancel(run_id)

View on GitHub (pinned to 3e82f13042)

Solutions

  1. Poll GET /runs/{run_id} (or the active-run listing) and wait for the current run to finish before starting a new one
  2. Add client-side debounce/lock on the trigger action so concurrent POSTs cannot overlap
  3. Cancel the active run via POST /runs/{run_id}/cancel, then retry the start
  4. If overlap is expected in your workflow, serialize runs through a queue instead of firing concurrent requests

Example fix

// before
const res = await fetch('/memory/runs', {method:'POST'});
if (res.status === 409) throw new Error('start failed');
// after
const res = await fetch('/memory/runs', {method:'POST'});
if (res.status === 409) {
  await waitForActiveRunToFinish(); // poll GET /runs/{id}
  return fetch('/memory/runs', {method:'POST'});
}
Defensive patterns

Strategy: retry

Validate before calling

const active = await (await fetch('/memory/runs?active=true')).json();
if (active.length > 0) await waitForRun(active[0].run_id);

Try / catch

try { await startRun(payload); } catch (e) { if (e.status === 409) { await pollUntilIdle(); await startRun(payload); } else throw e; }

Prevention

When it happens

Trigger: POST to the memory runs endpoint (start_run) while another consolidation run is still in-flight; the underlying get_run_manager().start (or equivalent) raises RunBusyError and the handler converts it to 409.

Common situations: Clients double-clicking a 'Consolidate now' button, retry logic that re-POSTs before polling run status, or background schedules overlapping with a user-triggered run.

Related errors


AI-assisted analysis of HKUDS/DeepTutor@3e82f13042 (2026-08-27). Data as JSON: /api/errors/d1b28a618ff38284. Report an issue: GitHub.