feat: auto-dispatch mode with Claude Agent Runner#121
Open
black-pwq wants to merge 8 commits into
Open
Conversation
Add automatic task execution mode alongside the existing manual
(human-in-the-loop) dispatch flow.
Backend:
- services/auto_dispatch.py: get_ready_auto_tasks (query-only),
dispatch_auto_tasks (BackgroundTasks bridge), run_auto_task
(marks running → calls Agent API → chains downstream via DAG)
- services/agent_runner/: AgentRunner base class, ClaudeRunner
(Anthropic Messages API), registry (sdk_type → runner)
- services/agent_credentials.py: decrypt api_key from AgentTypeConfig
- services/git_service.py: two-repo layout (collab/ + code/),
per-task git worktree lifecycle (create/delete), ensure_code_repo_sync,
delete_project_repo, _migrate_legacy_repo
- models.py: Task.dispatch_mode, Agent.is_auto,
AgentTypeConfig.{sdk_type,api_base_url,api_key_encrypted}
- routers/agent_settings.py: CRUD for AgentTypeConfig API credentials
- routers/plans.py, tasks.py: call dispatch_auto_tasks after finalize /
mark-complete / abandon; remove manual asyncio.gather wiring
- routers/agents.py: fix stale comment (copilot → claude)
- services/polling_service.py: skip auto-mode running tasks
- Remove github-copilot-sdk dependency; VALID_SDK_TYPES = {"claude"}
- tests/test_auto_dispatch.py: 787-line test suite (286 tests pass)
Frontend:
- AgentSettingsPage: sdk_type / api_base_url / api_key credential form
- AgentsPage: surface is_auto badge
- ProjectNewPage: code repo URL field (project_repo_url)
- TaskDetailPanel: show dispatch_mode indicator
- types/index.ts: new fields; styles/index.css: credential form styles
- Remove Copilot option from agent type select; display always "Claude"
Docs:
- architecture.md: v0.3.0, dual-mode system description, §3.5 scoped
to manual mode, §3.6 auto-dispatch design (worktree, DAG chain,
dispatch_mode semantics), data model entity updates
- task-lifecycle.md: v0.3.0, §3.1 status table covers auto semantics,
§3.6 auto-dispatch execution flow, polling/auto separation
- project-structure.md: v0.3.0, new services (auto_dispatch, agent_runner/),
updated git_service description, module responsibility table
There was a problem hiding this comment.
Pull request overview
This PR introduces an auto-dispatch execution mode for projects/agents, enabling the backend to automatically run ready tasks (DAG-aware) via a Claude Agent SDK runner, while preserving the existing manual workflow.
Changes:
- Add project-level
is_automode and agent-type-level SDK credentials (sdk_type,api_base_url, encrypted API key) with UI surfaces and backend validation. - Implement backend auto-dispatch orchestration (
dispatch_auto_tasks/run_auto_task) plus per-task isolated git worktrees for auto execution. - Update task lifecycle behavior (dispatch mode tracking, polling behavior) and add acceptance-criteria-focused backend tests.
Reviewed changes
Copilot reviewed 34 out of 35 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/frontend/src/types/index.ts | Extend frontend types to include SDK config and project auto-mode fields. |
| src/frontend/src/styles/index.css | Add styling for SDK/mode badges and project mode selector UI. |
| src/frontend/src/pages/ProjectNewPage.tsx | Add project manual/auto selector; filter selectable agents by mode; submit is_auto. |
| src/frontend/src/pages/ProjectDetailPage.tsx | Display execution mode badge for selected agents. |
| src/frontend/src/pages/AgentsPage.tsx | Show “auto” badge for agents whose types have SDK enabled. |
| src/frontend/src/pages/AgentSettingsPage.tsx | Add per-agent-type SDK config editor (base URL + key + sdk_type). |
| src/frontend/src/components/TaskDetailPanel.tsx | Display assignee execution mode (auto/manual) in task detail panel. |
| src/backend/uv.lock | Lock new backend dependencies (Claude Agent SDK, cryptography, etc.). |
| src/backend/tests/test_task_predecessor_status.py | Update tests for new BackgroundTasks signature / auto-dispatch hooks. |
| src/backend/tests/test_plan_finalize_validation.py | Update tests for finalize endpoint signature change (BackgroundTasks). |
| src/backend/tests/test_git_service.py | Adjust tests for new repo directory layout (collab/). |
| src/backend/tests/test_auto_dispatch.py | Add end-to-end/unit coverage for auto-dispatch acceptance criteria. |
| src/backend/services/prompt_service.py | Enhance prompts with worktree-aware git push instructions. |
| src/backend/services/polling_service.py | Skip polling/timeouts for dispatch_mode=auto running tasks. |
| src/backend/services/git_service.py | Introduce collab/code/tasks repo layout; add worktree workspace helpers and cleanup. |
| src/backend/services/auto_dispatch.py | Implement ready-task selection, background scheduling, and async task execution chaining. |
| src/backend/services/agent_runner/registry.py | Add runner registry that syncs repos, creates worktrees, runs SDK runner, and cleans up. |
| src/backend/services/agent_runner/claude_runner.py | Add Claude Agent SDK runner implementation. |
| src/backend/services/agent_runner/base.py | Define runner base class + execution context. |
| src/backend/services/agent_runner/init.py | Export runner/registry entrypoints. |
| src/backend/services/agent_credentials.py | Add API key encryption/decryption helpers (Fernet-based). |
| src/backend/routers/tasks.py | Track dispatch_mode; block manual dispatch for auto tasks; trigger auto-dispatch after task completion/abandon. |
| src/backend/routers/projects.py | Add is_auto; validate agent/project mode compatibility; delete on-disk repos on project delete. |
| src/backend/routers/plans.py | Trigger auto-dispatch after plan finalize. |
| src/backend/routers/agents.py | Include sdk_type and has_api_key in agent responses. |
| src/backend/routers/agent_settings.py | Add SDK config fields, validation, and encrypted key handling for agent types. |
| src/backend/pyproject.toml | Add required dependencies for SDK runner + encryption. |
| src/backend/models.py | Add Project.is_auto, Task.dispatch_mode, and agent-type SDK credential columns. |
| src/backend/main.py | Add schema migration entries and optional debug logging via env var. |
| src/backend/Dockerfile | Install sandbox/runtime tools needed by SDK runner (bubblewrap, socat). |
| src/backend/config.py | Add HALF_AGENT_API_KEY_ENCRYPTION_SECRET setting. |
| docs/task-lifecycle.md | Document auto-dispatch flow and dispatch_mode semantics. |
| docs/project-structure.md | Update architecture map for auto-dispatch and runner modules. |
| docs/prd/auto-dispatch-agents.md | Add PRD document for auto-dispatch mode. |
| docs/architecture.md | Update system description to include auto-dispatch mode and related components. |
Comments suppressed due to low confidence (1)
src/backend/services/git_service.py:242
ensure_repo_synccalls_migrate_legacy_repo(project_id)before acquiring the per-project lock. If multiple threads hitensure_repo_syncconcurrently, they can race during migration (renames intocollab/), leading to intermittent filesystem errors. Consider moving the migration inside thewith lock:block (or adding a dedicated migration lock) so the operation is serialized per project.
def ensure_repo_sync(project_id: int, git_repo_url: str) -> RepoSyncStatus:
_migrate_legacy_repo(project_id)
repo_dir = _collab_dir(project_id)
now = time.monotonic()
lock = _project_lock(project_id)
with lock:
last = _ensure_repo_last_run.get(project_id)
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| now = datetime.now(timezone.utc) | ||
| prev_status = task.status | ||
| prev_error = task.last_error | ||
| task.status = "running" |
Comment on lines
+252
to
+263
| if ( | ||
| body.sdk_type is not None | ||
| or body.api_base_url is not None | ||
| or body.api_key is not None | ||
| ): | ||
| _apply_sdk_config( | ||
| agent_type, | ||
| sdk_type=body.sdk_type if body.sdk_type is not None else agent_type.sdk_type, | ||
| api_base_url=body.api_base_url, | ||
| api_key=body.api_key, | ||
| partial=True, | ||
| ) |
Comment on lines
+693
to
+708
| _run_git(base_repo, ["worktree", "add", code_wt, "-b", branch]) | ||
| logger.info("Created worktree for task %s at %s (branch=%s)", task_id, code_wt, branch) | ||
|
|
||
| default_branch = _get_default_branch(base_repo) | ||
|
|
||
| # Symlink collab repo into task workspace so agent sees both dirs | ||
| collab_target = os.path.relpath(_collab_dir(project_id), task_ws) | ||
| os.symlink(collab_target, collab_link) | ||
|
|
||
| return TaskWorkspace( | ||
| workspace_dir=task_ws, | ||
| task_branch=branch, | ||
| default_branch=default_branch, | ||
| ) | ||
|
|
||
|
|
Comment on lines
+122
to
+170
| self._client = None | ||
|
|
||
| # ------------ Local Test -------------- | ||
| async def main(): | ||
| from unittest.mock import MagicMock | ||
| from models import Project, Task | ||
| from services.prompt_service import generate_task_prompt | ||
|
|
||
| fake_db = MagicMock() | ||
| fake_db.query.return_value.filter.return_value.first.return_value = None | ||
| fake_db.query.return_value.filter.return_value.all.return_value = [] | ||
|
|
||
| project = Project( | ||
| id=2, | ||
| name="测试项目", | ||
| goal="验证 ClaudeRunner 本地执行流程", | ||
| collaboration_dir="outputs/proj-2-9400f8", | ||
| git_repo_url="git@gitee.com:black-pwq/colab.git", | ||
| project_repo_url="git@gitee.com:black-pwq/coding.git", | ||
| ) | ||
| task = Task( | ||
| id=7, | ||
| project_id=2, | ||
| plan_id=0, | ||
| task_code="test", | ||
| task_name="本地测试任务", | ||
| description="在项目代码仓库(git@gitee.com:black-pwq/coding.git)中创建文件 a.py,内容为:print('hello a!')。创建后使用 python a.py 验证输出为 hello a!,然后提交并推送到仓库。", | ||
| depends_on_json=None, | ||
| assignee_agent_id=0, | ||
| dispatch_mode="auto", | ||
| status="running", | ||
| ) | ||
|
|
||
| prompt = generate_task_prompt(fake_db, project, task) | ||
| runner = ClaudeRunner(model="glm-5") | ||
| await runner.run(AgentRunContext( | ||
| task=task, | ||
| project=project, | ||
| agent=None, | ||
| prompt=prompt, | ||
| )) | ||
|
|
||
| if __name__ == "__main__": | ||
| import asyncio | ||
| logging.basicConfig( | ||
| level=logging.DEBUG, | ||
| format="%(asctime)s %(levelname)s %(name)s %(message)s", | ||
| ) | ||
| asyncio.run(main()) No newline at end of file |
| ├── config.py # Settings 类 + validate_security_config(启动期弱密钥/弱密码拒启) | ||
| ├── database.py # SQLAlchemy engine / SessionLocal / Base | ||
| ├── models.py # 12 个 ORM 模型(User / Agent / GlobalSetting / Project / ProjectPlan / ProcessTemplate / Task / AgentTypeConfig / ModelDefinition / AgentTypeModelMap / TaskEvent / AuditLog) | ||
| ├── models.py # 12 个 ORM 模型(User / Agent / GlobalSetting / Project / ProjectPlan / ProcessTemplate / Task / AgentTypeConfig / ModelDefinition / AgentTypeModelMap / TaskEvent / AuditLog);Task 含 dispatch_mode 字段;AgentTypeConfig 含 sdk_type / api_base_url / api_key_encrypted 自动模式配置字段;Agent 含 is_auto 字段 |
| | `User` | 系统用户(admin / user);`status` 为 `active` 或 `frozen` | | ||
| | `Agent` | 用户登记的 AI agent,含多模型配置(`models_json`)、订阅到期、短期/长期重置时间与间隔 | | ||
| | `AgentTypeConfig` + `ModelDefinition` + `AgentTypeModelMap` | Agent 类型目录与模型定义的全局配置,由管理员维护 | | ||
| | `AgentTypeConfig` + `ModelDefinition` + `AgentTypeModelMap` | Agent 类型目录与模型定义的全局配置,由管理员维护;`AgentTypeConfig` 含自动模式配置字段:`sdk_type`(目前仅 `claude`)、`api_base_url`、`api_key_encrypted`(AES-256-GCM 加密存储) | |
| id = Column(Integer, primary_key=True, index=True) | ||
| name = Column(Text, unique=True, nullable=False) | ||
| description = Column(Text, nullable=True) | ||
| # sdk_type: "copilot" | "claude" — which SDK runner to use (auto mode only) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
实现任务的自动派发与执行,目前支持 Claude Agent SDK。
另见 #109
Closes #110