Skip to content

[Bug]: Unify task tracking and same-session concurrency semantics #6273

Description

@rayrayraykk

QwenPaw Version

QwenPaw 2.0.1b1, reproduced from source near commit 8c2cceff.

Description

Task tracking and same-session concurrency behave differently depending on
the execution entry point. Some paths serialize work, some attach to an
existing run while silently ignoring the new payload, and others allow
concurrent execution without registering the real task in TaskTracker.

This makes chat status, cancellation, graceful workspace reload, and session
state safety depend on how the run was started rather than on a documented
policy.

Examples:

  • External channel messages with the same (channel, session, priority) are
    serialized by UnifiedQueueManager, while different priorities may run
    concurrently for the same session.
  • A second /api/console/chat request for an active chat attaches to the
    existing tracked run. Its new payload is not executed, and the API does not
    report a conflict or queued status.
  • /api/console/chat/task bypasses both UnifiedQueueManager and
    TaskTracker, so multiple background tasks using the same session can run
    concurrently.
  • Cron agent runs create a ChatSpec, but max_concurrency can permit
    multiple executions that reuse the same job session.
  • Heartbeat runs use the shared main session and can be triggered manually
    in addition to scheduled execution, but they do not explicitly create a
    ChatSpec or register the real execution in TaskTracker.
  • ACP maintains its own session registry and cancellation events. Concurrent
    prompts for one ACP session are not explicitly rejected or serialized, and
    a later prompt can replace the earlier _cancel_events[session_id] entry.

Related PR(s): #6272

Security considerations: No direct data-exposure issue is known. However,
concurrent writes and incomplete cancellation can make tool execution and
session state harder to reason about.

Component(s) Affected

  • Core / Backend (app, agents, config, providers, utils, local_models)
  • Console (frontend web UI)
  • Channels (DingTalk, Feishu, QQ, Discord, iMessage, etc.)
  • Skills
  • CLI
  • Documentation (website)
  • Tests
  • CI/CD
  • Scripts / Deploy

Environment

  • QwenPaw version: 2.0.1b1 / source commit 8c2cceff
  • OS: macOS
  • Install method: From source
  • Python version (if applicable): Python 3.12, conda environment

Steps to Reproduce

  1. Submit two POST /api/console/chat/task requests with the same
    session_id before either task completes.
  2. Observe that both tasks receive distinct task IDs and execute concurrently.
  3. Query the chat list while they run. The chat can exist, but its status is
    not backed by the actual background tasks in TaskTracker.
  4. Submit two regular POST /api/console/chat requests for the same active
    chat.
  5. Observe that the second request attaches to the first run instead of
    executing, queueing, or returning an explicit conflict.
  6. Trigger /api/config/heartbeat/run while another main session run is
    active, or trigger it repeatedly, and observe that no common per-session
    concurrency policy is applied.

Actual vs Expected

  • Actual: Execution identity is sometimes a chat ID, sometimes an opaque
    task ID, and sometimes absent from TaskTracker. Same-session work may be
    serialized, merged, silently ignored, attached, or run concurrently based
    on the entry point. request_stop() cannot cancel tasks that were never
    registered with their actual asyncio.Task.
  • Expected: All execution paths should follow an explicit and testable
    same-session concurrency policy. Every running operation should have a
    unique execution ID, an optional chat association, accurate aggregate chat
    status, and cancellation tied to the real task.

Logs / Screenshots

Relevant code paths:

src/qwenpaw/app/channels/unified_queue_manager.py
  QueueKey = (channel_id, session_id, priority_level)

src/qwenpaw/app/channels/base.py
  TaskTracker.attach_or_start(chat.id, ...)
  Logs "Message ignored (task already running)" for an existing run.

src/qwenpaw/app/routers/console.py
  /console/chat uses TaskTracker.attach_or_start(chat.id, ...)
  /console/chat/task creates an independent asyncio task outside TaskTracker.

src/qwenpaw/app/crons/executor.py
  Cron agent runs create or reuse a ChatSpec.

src/qwenpaw/app/crons/heartbeat.py
  Heartbeat runs use session_id="main" and call workspace.stream_query().

src/qwenpaw/agents/acp/server.py
  ACP prompt stores one cancel event per session ID and calls
  workspace.stream_query() directly.

Additional Notes

Suggested design direction:

  1. Give every execution a unique run_id or task_id.
  2. Associate executions with an optional chat_id instead of using
    chat_id as the only TaskTracker key.
  3. Allow TaskTracker to aggregate multiple running executions for one chat.
  4. Register the real asyncio.Task so stop and shutdown operations cancel or
    await the actual execution.
  5. Define one policy for duplicate same-session work: queue, reject with an
    explicit conflict, merge under documented rules, or allow controlled
    concurrency. Avoid silently dropping the new payload.
  6. Apply the policy consistently to Console streaming, background chat tasks,
    external channels, cron, heartbeat, and ACP.

Suggested acceptance criteria:

  • Two runs for the same session have deterministic behavior documented by the
    selected policy.
  • Chat status remains running until all associated executions finish.
  • Stop/cancel targets real executions and does not only update proxy state.
  • Workspace reload waits for or cancels all registered executions.
  • Integration tests cover regular Console chat, background chat tasks,
    cross-priority channel messages, heartbeat, cron concurrency, and ACP
    prompts.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    Status
    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions