Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
ab4d3f8
feat: add workspace archive and recovery for Pod deletion
Mar 25, 2026
d4f8860
fix(backend): add missing minio dependency
Mar 25, 2026
44fa8d4
feat(admin): add created_at field to device monitor list (#846)
FicoHu Mar 25, 2026
6f782a7
perf: use async heartbeat (#849)
qdaxb Mar 25, 2026
862506c
fix(chat_shell): prevent orphaned tool call IDs in group chat history…
moqimoqidea Mar 26, 2026
fdae4bc
fix: 在里面智能体的创建人和机器人的创建人不是一个人时会报错 (#853)
Micro66 Mar 26, 2026
8e04857
fix: fail fast on invalid tool call IDs across chat protocols (#854)
moqimoqidea Mar 26, 2026
ae1ba75
fix: 订阅任务执行间隔调整 & trace http接口的匹配规则调整 (#855)
Micro66 Mar 26, 2026
f089d1d
fix: sanitize PyInstaller LD_LIBRARY_PATH for child processes (#859)
icycrystal4 Mar 26, 2026
1909a61
refactor(rag): move internal retrieve routing into backend (#847)
kissghosts Mar 26, 2026
0ff7699
frontend: show 'indexing' status for documents pending indexing (#858)
parabala Mar 26, 2026
69fafa6
fix: 修复订阅器中脏数据导致的无法展示的问题 (#860)
Micro66 Mar 26, 2026
920fec8
feat(backend): add knowledge base RAG support to /v1/responses API (#…
parabala Mar 26, 2026
a6a2eb6
fix(chat_shell): use model_type for provider detection instead of mod…
moqimoqidea Mar 26, 2026
c112d02
ix: claude的子代理模式不可用修复 (#862)
Micro66 Mar 26, 2026
79a2646
feat(inbox): implement work queue and message forwarding system (#851)
qdaxb Mar 26, 2026
3cca7a9
Wegent/work queue and message forwarding (#863)
qdaxb Mar 26, 2026
704cb10
fix: fix column default value
qdaxb Mar 26, 2026
e40bc18
fix: fix forward message click
qdaxb Mar 26, 2026
e4fb230
feat: remove unused area
qdaxb Mar 26, 2026
0a1657c
feat: add inbox default message
qdaxb Mar 26, 2026
0b8b5e2
fix: 修复脏数据导致的订阅任务无法执行的问题 (#865)
Micro66 Mar 27, 2026
47410fc
fix(browser): harden browser-tool linux recovery (#867)
icycrystal4 Mar 27, 2026
751fd04
fix: 修复无法使用自定义镜像以及自定义镜像无法校验的问题 (#866)
Micro66 Mar 27, 2026
58e97d7
fix: fix welcome timezone (#868)
qdaxb Mar 27, 2026
ab7fad3
docs: update inbox docs (#869)
qdaxb Mar 27, 2026
d243063
fix: 修复单测 (#870)
Micro66 Mar 27, 2026
88f2687
fix: fix page error (#871)
qdaxb Mar 27, 2026
c4536ff
fix: data-roleid="api-key-create-button" not in html attr (#873)
flyhope Mar 27, 2026
3d22ff7
fix: 修复停止按钮偶发变成发送的问题 & 修复失败的消息会丢失的问题 (#876)
Micro66 Mar 30, 2026
5315a10
fix: 修复“skill not found” (#877)
Micro66 Mar 30, 2026
a2c6669
chore(executor): upgrade claude-agent-sdk from 0.1.31 to 0.1.51 (#880)
icycrystal4 Mar 30, 2026
5215598
feat(frontend): add HTML attachment preview and share link generation…
johnny0120 Mar 30, 2026
cdfb5f5
fix(knowledge): harden document indexing state (#875)
kissghosts Mar 30, 2026
f56c024
docs: update contributors list (zh) (#879)
github-actions[bot] Mar 30, 2026
dc8a089
docs: update contributors list (#878)
github-actions[bot] Mar 30, 2026
75a9280
feat: 支持skill认证 (#882)
Apr 1, 2026
17b1b99
Feature/knowledge document read (#888)
kissghosts Mar 31, 2026
7bd3b52
refactor(web-scraper): add configuration for specific sites that requ…
parabala Mar 31, 2026
25298c2
Merge branch 'main' into feat/workspace-archive-recovery
Apr 1, 2026
facb938
refactor(backend): replace auto-cleanup with archive expiration check
Apr 1, 2026
102abbc
feat: 添加恢复行为测试和脚本路径修正
Apr 1, 2026
5edb79d
fix: 使用 timezone.utc 替换 UTC 以确保时区正确
Apr 1, 2026
2dbaffc
test(backend): fix workspace recovery test patches
Apr 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions backend/app/api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
skills_router,
subscriptions_router,
tables_router,
workspace_archives_router,
)

# RAG internal router is conditionally imported based on STANDALONE_MODE
Expand Down Expand Up @@ -207,6 +208,11 @@
api_router.include_router(
services_router, prefix="/internal", tags=["internal-services"]
)
api_router.include_router(
workspace_archives_router,
prefix="/internal",
tags=["internal-workspace-archives"],
)
api_router.include_router(
subscriptions_router, prefix="/internal", tags=["internal-subscriptions"]
)
Expand Down
2 changes: 2 additions & 0 deletions backend/app/api/endpoints/internal/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from .skills import router as skills_router
from .subscriptions import router as subscriptions_router
from .tables import router as tables_router
from .workspace_archives import router as workspace_archives_router

# RAG router is conditionally imported based on STANDALONE_MODE
# RAG module is heavy (llama_index, scipy, pandas, grpc) - skip in standalone mode
Expand All @@ -27,6 +28,7 @@
"skills_router",
"subscriptions_router",
"tables_router",
"workspace_archives_router",
]

# Conditionally add rag_router to __all__
Expand Down
87 changes: 87 additions & 0 deletions backend/app/api/endpoints/internal/workspace_archives.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# SPDX-FileCopyrightText: 2026 Weibo, Inc.
#
# SPDX-License-Identifier: Apache-2.0

"""Internal workspace archive endpoints."""

import logging

from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from sqlalchemy.orm import Session

from app.api.dependencies import get_db
from app.models.subtask import Subtask
from app.models.task import TaskResource
from app.schemas.kind import ArchiveInfo
from app.services.workspace_archive import archive_service

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/workspace-archives", tags=["internal-workspace-archives"])


class ManualArchiveResponse(BaseModel):
"""Response model for manually triggered workspace archive."""

success: bool = True
task_id: int
archive: ArchiveInfo


@router.post("/{task_id}/archive", response_model=ManualArchiveResponse)
async def archive_task_workspace(
task_id: int,
db: Session = Depends(get_db),
):
Comment on lines +32 to +36

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Trace this async internal endpoint.

This new archive control-plane path is missing the repo's async OTel wrapper, so request-level visibility depends on logs only.

As per coding guidelines, "Use @trace_async(span_name, tracer_name, extract_attributes) decorator to trace entire async functions in OpenTelemetry".

🧰 Tools
🪛 Ruff (0.15.7)

[warning] 35-35: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/api/endpoints/internal/workspace_archives.py` around lines 32 -
36, The async endpoint archive_task_workspace is not instrumented with the OTel
async tracer; wrap it with the `@trace_async` decorator so request-level traces
are captured (e.g., `@trace_async`("archive_task_workspace", "internal",
extract_attributes=...) above the function), import trace_async from your
tracing utilities, and provide an extract_attributes callable that returns
relevant attributes such as {"task_id": task_id} (use kwargs.get("task_id") or
inspect args) to ensure the task_id is recorded on the span.

"""Archive the current task workspace and persist archive metadata."""
task = (
db.query(TaskResource)
.filter(
TaskResource.id == task_id,
TaskResource.kind == "Task",
TaskResource.is_active == TaskResource.STATE_ACTIVE,
)
.first()
)

if not task:
raise HTTPException(status_code=404, detail="Task not found")

subtask = (
db.query(Subtask)
.filter(
Subtask.task_id == task_id,
Subtask.executor_name.isnot(None),
Subtask.executor_name != "",
Subtask.executor_deleted_at == False,
)
.order_by(Subtask.id.desc())
.first()
)

if not subtask:
raise HTTPException(status_code=404, detail="No active executor found for task")

archive_info = await archive_service.archive_workspace(
db=db,
subtask=subtask,
task=task,
executor_name=subtask.executor_name,
executor_namespace=subtask.executor_namespace or "",
)

if not archive_info:
raise HTTPException(status_code=500, detail="Failed to archive workspace")

db.commit()
db.refresh(task)

logger.info(
"Manually archived workspace for task %s via executor %s/%s",
task_id,
subtask.executor_namespace or "",
subtask.executor_name,
)

return ManualArchiveResponse(task_id=task_id, archive=archive_info)
31 changes: 31 additions & 0 deletions backend/app/schemas/kind.py
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,32 @@ class TaskApp(BaseModel):
previewUrl: str


class ArchiveInfo(BaseModel):
"""Workspace archive information for Pod recovery after deletion.

Stores metadata about archived workspace files that can be restored
when a user sends a message after the executor Pod has been deleted.
"""

storageKey: Optional[str] = Field(
None,
description="MinIO storage key (e.g., 'workspace-archives/12345/archive.tar.gz')",
)
archivedAt: Optional[datetime] = Field(
None, description="Timestamp when the workspace was archived"
)
expiresAt: Optional[datetime] = Field(
None, description="Timestamp when the archive expires (30 days after archiving)"
)
sizeBytes: Optional[int] = Field(None, description="Archive file size in bytes")
sessionFileIncluded: bool = Field(
False, description="Whether .claude_session_id file is included in the archive"
)
gitIncluded: bool = Field(
False, description="Whether .git directory is included in the archive"
)


class TaskStatus(Status):
"""Task status"""

Expand All @@ -548,6 +574,11 @@ class TaskStatus(Status):
completedAt: Optional[datetime] = None
subTasks: Optional[List[Dict[str, Any]]] = None
app: Optional[TaskApp] = None # App preview information
archive: Optional[ArchiveInfo] = Field(
None,
description="Workspace archive information for Pod recovery after deletion. "
"Set when executor Pod is deleted after task completion.",
)


class Task(BaseModel):
Expand Down
110 changes: 102 additions & 8 deletions backend/app/services/adapters/executor_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import asyncio
import logging
from datetime import datetime, timedelta, timezone
from typing import List, Set
from typing import Dict, List, Set, Tuple

from sqlalchemy import and_
from sqlalchemy.orm import Session
Expand Down Expand Up @@ -131,18 +131,58 @@ def cleanup_stale_executors(self, db: Session) -> None:
)
return

# Deduplicate by (namespace, name)
unique_executor_keys: Set[tuple[str, str]] = set()
for s in valid_candidates:
if s.executor_name:
unique_executor_keys.add((s.executor_namespace, s.executor_name))
# Deduplicate by (namespace, name) and collect task info for archiving
# Map: (namespace, name) -> (subtask, task) for archive operation
executor_task_map: Dict[Tuple[str, str], Tuple[Subtask, TaskResource]] = {}
for subtask in valid_candidates:
if subtask.executor_name:
key = (subtask.executor_namespace, subtask.executor_name)
if key not in executor_task_map:
# Get task for this subtask
task = (
db.query(TaskResource)
.filter(
TaskResource.id == subtask.task_id,
TaskResource.kind == "Task",
TaskResource.is_active == TaskResource.STATE_ACTIVE,
)
.first()
)
if task:
executor_task_map[key] = (subtask, task)

if not unique_executor_keys:
if not executor_task_map:
return

# Use sync version to avoid event loop issues
for ns, name in unique_executor_keys:
for (ns, name), (subtask, task) in executor_task_map.items():
try:
# Archive workspace before deletion (for code tasks)
task_crd = Task.model_validate(task.json)
task_type = (
task_crd.metadata.labels
and task_crd.metadata.labels.get("taskType")
or "chat"
)

if task_type == "code":
# Try to archive workspace before deletion
try:
self._archive_workspace_sync(
db=db,
subtask=subtask,
task=task,
executor_name=name,
executor_namespace=ns,
)
except Exception as archive_error:
# Log but continue with deletion
# Recovery will fall back to git clone if archive failed
logger.warning(
f"[executor_job] Failed to archive workspace for task {task.id} "
f"ns={ns} name={name}: {archive_error}"
)
Comment on lines +168 to +184

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Do not delete code-task executors unless archiving actually succeeded.

_archive_workspace_sync() only logs success/skip/fail, and this call site still deletes the executor after archive exceptions. For code tasks, that can discard the only copy of uncommitted workspace state and session metadata; cleanup should leave the executor in place and retry later whenever no archive was produced.

Also applies to: 240-258

🧰 Tools
🪛 Ruff (0.15.7)

[warning] 178-178: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/services/adapters/executor_job.py` around lines 168 - 184, The
current code calls _archive_workspace_sync(...) for code tasks but proceeds to
delete the executor even if archiving failed or raised an exception; change
_archive_workspace_sync to return a clear status (e.g., True on archive
produced, False otherwise) or expose whether an archive was created, then update
the call sites (the try/except around _archive_workspace_sync in executor_job.py
and the similar block at the other location referenced) to: call
_archive_workspace_sync, capture its boolean result, and only proceed with
deletion when it returns True; on False or on exception, log the failure with
context (task.id, ns, name) and skip deletion so the executor stays in place for
retry later. Ensure both the call at the shown block and the analogous block
(lines ~240-258) use the same pattern and reference the _archive_workspace_sync
return value.


logger.info(
f"[executor_job] Scheduled deleting executor task ns={ns} name={name}"
)
Expand All @@ -166,5 +206,59 @@ def cleanup_stale_executors(self, db: Session) -> None:
except Exception as e:
logger.error(f"[executor_job] cleanup_stale_executors error: {e}")

def _archive_workspace_sync(
self,
db: Session,
subtask: Subtask,
task: TaskResource,
executor_name: str,
executor_namespace: str,
) -> None:
"""Archive workspace files before Pod deletion (synchronous wrapper).

This method wraps the async archive_service.archive_workspace() for use
in the synchronous cleanup_stale_executors job.

Args:
db: Database session
subtask: Subtask with executor info
task: Task resource
executor_name: Executor name
executor_namespace: Executor namespace
"""
from app.services.workspace_archive import archive_service

logger.info(
f"[executor_job] Archiving workspace for task {task.id}, "
f"executor={executor_namespace}/{executor_name}"
)

# Run async archive in new event loop
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
archive_info = loop.run_until_complete(
archive_service.archive_workspace(
db=db,
subtask=subtask,
task=task,
executor_name=executor_name,
executor_namespace=executor_namespace,
)
)

if archive_info:
logger.info(
f"[executor_job] Workspace archived for task {task.id}, "
f"size={archive_info.sizeBytes} bytes"
)
else:
logger.info(
f"[executor_job] Workspace archiving skipped or failed for task {task.id}"
)
finally:
loop.run_until_complete(loop.shutdown_asyncgens())
loop.close()
Comment on lines +237 to +261

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

head -n 300 backend/app/services/adapters/executor_job.py | tail -n 100

Repository: wecode-ai/Wegent

Length of output: 3985


🏁 Script executed:

cd backend && find . -name "*.py" -type f | xargs rg "class JobService" -A 10

Repository: wecode-ai/Wegent

Length of output: 939


🏁 Script executed:

cd backend && rg "job_service\." -B 2 -A 2 | head -40

Repository: wecode-ai/Wegent

Length of output: 1972


🏁 Script executed:

cd backend && cat -n app/services/jobs.py | head -80

Repository: wecode-ai/Wegent

Length of output: 3029


🏁 Script executed:

cd backend && cat -n app/services/jobs.py | sed -n '75,150p'

Repository: wecode-ai/Wegent

Length of output: 3379


🏁 Script executed:

cd backend && cat -n app/services/jobs.py | sed -n '140,160p'

Repository: wecode-ai/Wegent

Length of output: 1038


🏁 Script executed:

cd backend && cat -n app/services/jobs.py | sed -n '159,180p'

Repository: wecode-ai/Wegent

Length of output: 900


🏁 Script executed:

cd backend && sed -n '171,280p' app/services/adapters/executor_job.py

Repository: wecode-ai/Wegent

Length of output: 3788


🌐 Web query:

asyncio event loop management background threads Python best practices

💡 Result:

Best practices for asyncio event loop management, especially with background threads in Python: 1. Use high-level entry points: Prefer asyncio.run(coro) or asyncio.Runner for managing the event loop lifecycle. These handle creation, running, cleanup (including shutdown_default_executor and close), and should be used as the main entry point. Avoid manual loop management unless necessary for low-level code. 2. Inside coroutines, use asyncio.get_running_loop instead of deprecated asyncio.get_event_loop. Do not use get_event_loop in async code. 3. For blocking synchronous code (CPU/IO-bound): Offload to thread pool using: - Python 3.9+: asyncio.to_thread(func, *args) for IO-bound (uses default ThreadPoolExecutor). - Or loop.run_in_executor(None, func, *args) or with custom concurrent.futures.ThreadPoolExecutor for more control. Use ProcessPoolExecutor for CPU-bound to bypass GIL. - asyncio.run handles default executor shutdown automatically. Example: import asyncio import concurrent.futures def blocking_io: ... async def main: loop = asyncio.get_running_loop result = await loop.run_in_executor(None, blocking_io) asyncio.run(main) 4. Background threads interacting with asyncio: - Event loop runs in one thread (usually main). asyncio objects are not thread-safe. - From other threads: Use loop.call_soon_threadsafe(callback) for callbacks, asyncio.run_coroutine_threadsafe(coro, loop) to schedule coroutines (returns concurrent.futures.Future). - To run asyncio in background thread: Create new loop with asyncio.new_event_loop, set with asyncio.set_event_loop(loop), then loop.run_forever. Submit tasks via thread-safe methods from main loop thread. Example for per-thread loops: import threading def thread_worker: loop = asyncio.new_event_loop asyncio.set_event_loop(loop) loop.run_forever # Start threads, submit from main via run_coroutine_threadsafe 5. Policies (deprecated in 3.14+, removal 3.16): Avoid set_event_loop_policy. Use loop_factory in asyncio.run(loop_factory=...) for custom loops (e.g., uvloop). 6. Multithreading best practices: - One event loop per thread. - Main thread for signals/subprocesses. - Enable debug mode (asyncio.run(debug=True)) for thread-safety checks, slow callbacks detection. - Close loops explicitly if not using asyncio.run: Call shutdown_asyncgens then close. - No direct coroutine scheduling from other processes (use multiprocessing separately). 7. General: - Avoid blocking the loop: Never run sync blocking code directly in coroutines. - Use create_task(coro) for concurrent async tasks, asyncio.gather for waiting. - For fire-and-forget: Track tasks in set to prevent GC. - Production: Consider uvloop for performance. These practices ensure responsiveness, thread-safety, and proper resource management per official Python 3.14+ docs.

Citations:


🏁 Script executed:

cd backend && python3 << 'EOF'
# Simulate the proposed fix behavior in a background thread context
import asyncio

def simulate_worker_thread_context():
    """Simulate what happens in cleanup_worker thread"""
    print("1. Starting in background thread (no event loop set)")
    
    # Try the proposed fix logic
    try:
        previous_loop = asyncio.get_event_loop_policy().get_event_loop()
        print(f"2. Got previous loop: {previous_loop}")
    except RuntimeError as e:
        previous_loop = None
        print(f"2. RuntimeError caught (expected in worker thread): {e}")
        print("   Set previous_loop = None")
    
    # Create new loop (as in _archive_workspace_sync)
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    print(f"3. Created and set new loop: {loop}")
    
    # Use and close it
    loop.close()
    print("4. Closed new loop")
    
    # Try to get event loop after closure
    try:
        current = asyncio.get_event_loop()
        print(f"5. After closure, get_event_loop() returned: {current}")
        if current.is_closed():
            print("   Loop is closed!")
    except RuntimeError as e:
        print(f"5. After closure, RuntimeError: {e}")
    
    # Restoration attempt (as in proposed fix)
    asyncio.set_event_loop(previous_loop)
    print(f"6. Restored previous_loop: {previous_loop}")

simulate_worker_thread_context()
EOF

Repository: wecode-ai/Wegent

Length of output: 528


🏁 Script executed:

cd backend && sed -n '75,95p' app/services/jobs.py

Repository: wecode-ai/Wegent

Length of output: 768


Restore the prior event loop after closing it in finally.

asyncio.set_event_loop(loop) makes this temporary loop current for the worker thread, but the finally block closes it without restoring the prior loop. Although the current code path contains only synchronous code after, this leaves a closed loop as the thread's current event loop, creating a latent bug if any async code is added later or if this method is reused elsewhere.

Proposed fix
-        loop = asyncio.new_event_loop()
-        asyncio.set_event_loop(loop)
+        try:
+            previous_loop = asyncio.get_event_loop_policy().get_event_loop()
+        except RuntimeError:
+            previous_loop = None
+
+        loop = asyncio.new_event_loop()
+        asyncio.set_event_loop(loop)
         try:
             archive_info = loop.run_until_complete(
                 archive_service.archive_workspace(
                     db=db,
                     subtask=subtask,
@@
         finally:
             loop.run_until_complete(loop.shutdown_asyncgens())
             loop.close()
+            asyncio.set_event_loop(previous_loop)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/services/adapters/executor_job.py` around lines 237 - 261, Before
calling asyncio.set_event_loop(loop) capture the previous event loop (e.g.,
prev_loop = asyncio.get_event_loop()), and in the finally block restore it with
asyncio.set_event_loop(prev_loop) before shutting down and closing the temporary
loop used to run archive_service.archive_workspace; update the block around the
temporary loop creation in executor_job (the variables loop and
archive_service.archive_workspace call) so the thread's original event loop is
reinstated to avoid leaving a closed loop as the current loop.



job_service = JobService(Kind)
9 changes: 8 additions & 1 deletion backend/app/services/chat/storage/task_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -493,8 +493,13 @@ def create_assistant_subtask(
# Resolve executor_name from previous subtasks for container reuse
executor_name = ""
executor_namespace = ""
executor_deleted_at = False
previous = (
db.query(Subtask.executor_name, Subtask.executor_namespace)
db.query(
Subtask.executor_name,
Subtask.executor_namespace,
Subtask.executor_deleted_at,
)
.filter(
Subtask.task_id == task_id,
Subtask.role == SubtaskRole.ASSISTANT,
Expand All @@ -507,6 +512,7 @@ def create_assistant_subtask(
if previous:
executor_name = previous.executor_name or ""
executor_namespace = previous.executor_namespace or ""
executor_deleted_at = bool(previous.executor_deleted_at)

# Note: completed_at is set to a placeholder value because the DB column doesn't allow NULL
# It will be updated when the stream completes
Expand All @@ -519,6 +525,7 @@ def create_assistant_subtask(
role=SubtaskRole.ASSISTANT,
executor_namespace=executor_namespace,
executor_name=executor_name,
executor_deleted_at=executor_deleted_at,
prompt="",
status=SubtaskStatus.PENDING,
progress=0,
Expand Down
Loading
Loading