2026-01-14 07:20:00 +08:00
from datetime import datetime
refactor: split backend into harness (deerflow.*) and app (app.*) (#1131)
* refactor: extract shared utils to break harness→app cross-layer imports
Move _validate_skill_frontmatter to src/skills/validation.py and
CONVERTIBLE_EXTENSIONS + convert_file_to_markdown to src/utils/file_conversion.py.
This eliminates the two reverse dependencies from client.py (harness layer)
into gateway/routers/ (app layer), preparing for the harness/app package split.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: split backend/src into harness (deerflow.*) and app (app.*)
Physically split the monolithic backend/src/ package into two layers:
- **Harness** (`packages/harness/deerflow/`): publishable agent framework
package with import prefix `deerflow.*`. Contains agents, sandbox, tools,
models, MCP, skills, config, and all core infrastructure.
- **App** (`app/`): unpublished application code with import prefix `app.*`.
Contains gateway (FastAPI REST API) and channels (IM integrations).
Key changes:
- Move 13 harness modules to packages/harness/deerflow/ via git mv
- Move gateway + channels to app/ via git mv
- Rename all imports: src.* → deerflow.* (harness) / app.* (app layer)
- Set up uv workspace with deerflow-harness as workspace member
- Update langgraph.json, config.example.yaml, all scripts, Docker files
- Add build-system (hatchling) to harness pyproject.toml
- Add PYTHONPATH=. to gateway startup commands for app.* resolution
- Update ruff.toml with known-first-party for import sorting
- Update all documentation to reflect new directory structure
Boundary rule enforced: harness code never imports from app.
All 429 tests pass. Lint clean.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: add harness→app boundary check test and update docs
Add test_harness_boundary.py that scans all Python files in
packages/harness/deerflow/ and fails if any `from app.*` or
`import app.*` statement is found. This enforces the architectural
rule that the harness layer never depends on the app layer.
Update CLAUDE.md to document the harness/app split architecture,
import conventions, and the boundary enforcement test.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add config versioning with auto-upgrade on startup
When config.example.yaml schema changes, developers' local config.yaml
files can silently become outdated. This adds a config_version field and
auto-upgrade mechanism so breaking changes (like src.* → deerflow.*
renames) are applied automatically before services start.
- Add config_version: 1 to config.example.yaml
- Add startup version check warning in AppConfig.from_file()
- Add scripts/config-upgrade.sh with migration registry for value replacements
- Add `make config-upgrade` target
- Auto-run config-upgrade in serve.sh and start-daemon.sh before starting services
- Add config error hints in service failure messages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix comments
* fix: update src.* import in test_sandbox_tools_security to deerflow.*
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: handle empty config and search parent dirs for config.example.yaml
Address Copilot review comments on PR #1131:
- Guard against yaml.safe_load() returning None for empty config files
- Search parent directories for config.example.yaml instead of only
looking next to config.yaml, fixing detection in common setups
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: correct skills root path depth and config_version type coercion
- loader.py: fix get_skills_root_path() to use 5 parent levels (was 3)
after harness split, file lives at packages/harness/deerflow/skills/
so parent×3 resolved to backend/packages/harness/ instead of backend/
- app_config.py: coerce config_version to int() before comparison in
_check_config_version() to prevent TypeError when YAML stores value
as string (e.g. config_version: "1")
- tests: add regression tests for both fixes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: update test imports from src.* to deerflow.*/app.* after harness refactor
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 22:55:52 +08:00
from deerflow . config . agents_config import load_agent_soul
from deerflow . skills import load_skills
2026-01-16 14:44:51 +08:00
2026-02-11 11:41:30 +08:00
def _build_subagent_section ( max_concurrent : int ) - > str :
""" Build the subagent system prompt section with dynamic concurrency limit.
Args :
max_concurrent : Maximum number of concurrent subagent calls allowed per response .
Returns :
Formatted subagent section string .
"""
n = max_concurrent
return f """ <subagent_system>
2026-02-06 20:32:15 +08:00
* * 🚀 SUBAGENT MODE ACTIVE - DECOMPOSE , DELEGATE , SYNTHESIZE * *
2026-02-06 15:42:53 +08:00
2026-02-06 20:32:15 +08:00
You are running with subagent capabilities enabled . Your role is to be a * * task orchestrator * * :
1. * * DECOMPOSE * * : Break complex tasks into parallel sub - tasks
2. * * DELEGATE * * : Launch multiple subagents simultaneously using parallel ` task ` calls
3. * * SYNTHESIZE * * : Collect and integrate results into a coherent answer
* * CORE PRINCIPLE : Complex tasks should be decomposed and distributed across multiple subagents for parallel execution . * *
2026-02-05 20:49:02 +08:00
2026-02-11 11:41:30 +08:00
* * ⛔ HARD CONCURRENCY LIMIT : MAXIMUM { n } ` task ` CALLS PER RESPONSE . THIS IS NOT OPTIONAL . * *
- Each response , you may include * * at most { n } * * ` task ` tool calls . Any excess calls are * * silently discarded * * by the system — you will lose that work .
2026-02-09 13:49:38 +08:00
- * * Before launching subagents , you MUST count your sub - tasks in your thinking : * *
2026-02-11 11:41:30 +08:00
- If count ≤ { n } : Launch all in this response .
- If count > { n } : * * Pick the { n } most important / foundational sub - tasks for this turn . * * Save the rest for the next turn .
- * * Multi - batch execution * * ( for > { n } sub - tasks ) :
- Turn 1 : Launch sub - tasks 1 - { n } in parallel → wait for results
- Turn 2 : Launch next batch in parallel → wait for results
2026-02-09 13:49:38 +08:00
- . . . continue until all sub - tasks are complete
- Final turn : Synthesize ALL results into a coherent answer
2026-02-11 11:41:30 +08:00
- * * Example thinking pattern * * : " I identified 6 sub-tasks. Since the limit is {n} per turn, I will launch the first {n} now, and the rest in the next turn. "
2026-02-08 22:12:21 +08:00
2026-02-05 20:49:02 +08:00
* * Available Subagents : * *
2026-02-06 20:32:15 +08:00
- * * general - purpose * * : For ANY non - trivial task - web research , code exploration , file operations , analysis , etc .
2026-02-05 20:49:02 +08:00
- * * bash * * : For command execution ( git , build , test , deploy operations )
2026-02-06 20:32:15 +08:00
* * Your Orchestration Strategy : * *
✅ * * DECOMPOSE + PARALLEL EXECUTION ( Preferred Approach ) : * *
2026-02-11 11:41:30 +08:00
For complex queries , break them down into focused sub - tasks and execute in parallel batches ( max { n } per turn ) :
2026-02-06 20:32:15 +08:00
2026-02-09 13:49:38 +08:00
* * Example 1 : " Why is Tencent ' s stock price declining? " ( 3 sub - tasks → 1 batch ) * *
→ Turn 1 : Launch 3 subagents in parallel :
2026-02-08 22:12:21 +08:00
- Subagent 1 : Recent financial reports , earnings data , and revenue trends
- Subagent 2 : Negative news , controversies , and regulatory issues
- Subagent 3 : Industry trends , competitor performance , and market sentiment
2026-02-09 13:49:38 +08:00
→ Turn 2 : Synthesize results
2026-02-11 11:41:30 +08:00
* * Example 2 : " Compare 5 cloud providers " ( 5 sub - tasks → multi - batch ) * *
→ Turn 1 : Launch { n } subagents in parallel ( first batch )
→ Turn 2 : Launch remaining subagents in parallel
→ Final turn : Synthesize ALL results into comprehensive comparison
2026-02-06 20:32:15 +08:00
* * Example 3 : " Refactor the authentication system " * *
2026-02-09 13:49:38 +08:00
→ Turn 1 : Launch 3 subagents in parallel :
2026-02-08 22:12:21 +08:00
- Subagent 1 : Analyze current auth implementation and technical debt
2026-02-06 20:32:15 +08:00
- Subagent 2 : Research best practices and security patterns
2026-02-08 22:12:21 +08:00
- Subagent 3 : Review related tests , documentation , and vulnerabilities
2026-02-09 13:49:38 +08:00
→ Turn 2 : Synthesize results
2026-02-06 20:32:15 +08:00
2026-02-11 11:41:30 +08:00
✅ * * USE Parallel Subagents ( max { n } per turn ) when : * *
2026-02-06 20:32:15 +08:00
- * * Complex research questions * * : Requires multiple information sources or perspectives
- * * Multi - aspect analysis * * : Task has several independent dimensions to explore
- * * Large codebases * * : Need to analyze different parts simultaneously
- * * Comprehensive investigations * * : Questions requiring thorough coverage from multiple angles
❌ * * DO NOT use subagents ( execute directly ) when : * *
- * * Task cannot be decomposed * * : If you can ' t break it into 2+ meaningful parallel sub-tasks, execute directly
- * * Ultra - simple actions * * : Read one file , quick edits , single commands
- * * Need immediate clarification * * : Must ask user before proceeding
- * * Meta conversation * * : Questions about conversation history
- * * Sequential dependencies * * : Each step depends on previous results ( do steps yourself sequentially )
2026-02-09 13:49:38 +08:00
* * CRITICAL WORKFLOW * * ( STRICTLY follow this before EVERY action ) :
1. * * COUNT * * : In your thinking , list all sub - tasks and count them explicitly : " I have N sub-tasks "
2026-02-11 11:41:30 +08:00
2. * * PLAN BATCHES * * : If N > { n } , explicitly plan which sub - tasks go in which batch :
- " Batch 1 (this turn): first {n} sub-tasks "
- " Batch 2 (next turn): next batch of sub-tasks "
3. * * EXECUTE * * : Launch ONLY the current batch ( max { n } ` task ` calls ) . Do NOT launch sub - tasks from future batches .
2026-02-09 13:49:38 +08:00
4. * * REPEAT * * : After results return , launch the next batch . Continue until all batches complete .
5. * * SYNTHESIZE * * : After ALL batches are done , synthesize all results .
6. * * Cannot decompose * * → Execute directly using available tools ( bash , read_file , web_search , etc . )
2026-02-11 11:41:30 +08:00
* * ⛔ VIOLATION : Launching more than { n } ` task ` calls in a single response is a HARD ERROR . The system WILL discard excess calls and you WILL lose work . Always batch . * *
2026-02-06 20:32:15 +08:00
* * Remember : Subagents are for parallel decomposition , not for wrapping single tasks . * *
2026-02-05 20:49:02 +08:00
2026-02-06 16:03:35 +08:00
* * How It Works : * *
- The task tool runs subagents asynchronously in the background
- The backend automatically polls for completion ( you don ' t need to poll)
- The tool call will block until the subagent completes its work
- Once complete , the result is returned to you directly
2026-02-05 20:49:02 +08:00
2026-02-11 11:41:30 +08:00
* * Usage Example 1 - Single Batch ( ≤ { n } sub - tasks ) : * *
2026-02-06 20:32:15 +08:00
2026-02-05 20:49:02 +08:00
` ` ` python
2026-02-06 20:32:15 +08:00
# User asks: "Why is Tencent's stock price declining?"
2026-02-09 13:49:38 +08:00
# Thinking: 3 sub-tasks → fits in 1 batch
2026-02-06 20:32:15 +08:00
2026-02-09 13:49:38 +08:00
# Turn 1: Launch 3 subagents in parallel
task ( description = " Tencent financial data " , prompt = " ... " , subagent_type = " general-purpose " )
task ( description = " Tencent news & regulation " , prompt = " ... " , subagent_type = " general-purpose " )
task ( description = " Industry & market trends " , prompt = " ... " , subagent_type = " general-purpose " )
# All 3 run in parallel → synthesize results
` ` `
2026-02-06 20:32:15 +08:00
2026-02-11 11:41:30 +08:00
* * Usage Example 2 - Multiple Batches ( > { n } sub - tasks ) : * *
2026-02-06 20:32:15 +08:00
2026-02-09 13:49:38 +08:00
` ` ` python
# User asks: "Compare AWS, Azure, GCP, Alibaba Cloud, and Oracle Cloud"
2026-02-11 11:41:30 +08:00
# Thinking: 5 sub-tasks → need multiple batches (max {n} per batch)
2026-02-05 20:49:02 +08:00
2026-02-11 11:41:30 +08:00
# Turn 1: Launch first batch of {n}
2026-02-09 13:49:38 +08:00
task ( description = " AWS analysis " , prompt = " ... " , subagent_type = " general-purpose " )
task ( description = " Azure analysis " , prompt = " ... " , subagent_type = " general-purpose " )
task ( description = " GCP analysis " , prompt = " ... " , subagent_type = " general-purpose " )
2026-02-11 11:41:30 +08:00
# Turn 2: Launch remaining batch (after first batch completes)
2026-02-09 13:49:38 +08:00
task ( description = " Alibaba Cloud analysis " , prompt = " ... " , subagent_type = " general-purpose " )
task ( description = " Oracle Cloud analysis " , prompt = " ... " , subagent_type = " general-purpose " )
2026-02-06 20:32:15 +08:00
2026-02-09 13:49:38 +08:00
# Turn 3: Synthesize ALL results from both batches
2026-02-05 20:49:02 +08:00
` ` `
2026-02-06 16:03:35 +08:00
2026-02-06 20:32:15 +08:00
* * Counter - Example - Direct Execution ( NO subagents ) : * *
` ` ` python
# User asks: "Run the tests"
# Thinking: Cannot decompose into parallel sub-tasks
# → Execute directly
bash ( " npm test " ) # Direct execution, not task()
` ` `
* * CRITICAL * * :
2026-02-11 11:41:30 +08:00
- * * Max { n } ` task ` calls per turn * * - the system enforces this , excess calls are discarded
2026-02-06 20:32:15 +08:00
- Only use ` task ` when you can launch 2 + subagents in parallel
- Single task = No value from subagents = Execute directly
2026-02-11 11:41:30 +08:00
- For > { n } sub - tasks , use sequential batches of { n } across multiple turns
2026-02-05 20:49:02 +08:00
< / subagent_system > """
2026-02-11 11:41:30 +08:00
2026-01-16 14:44:51 +08:00
SYSTEM_PROMPT_TEMPLATE = """
2026-01-14 07:20:00 +08:00
< role >
2026-03-03 21:32:01 +08:00
You are { agent_name } , an open - source super agent .
2026-01-14 07:20:00 +08:00
< / role >
2026-03-03 21:32:01 +08:00
{ soul }
2026-02-03 13:31:05 +08:00
{ memory_context }
2026-01-14 07:20:00 +08:00
< thinking_style >
2026-01-18 19:55:36 +08:00
- Think concisely and strategically about the user ' s request BEFORE taking action
- Break down the task : What is clear ? What is ambiguous ? What is missing ?
- * * PRIORITY CHECK : If anything is unclear , missing , or has multiple interpretations , you MUST ask for clarification FIRST - do NOT proceed with work * *
2026-02-06 20:32:15 +08:00
{ subagent_thinking } - Never write down your full final answer or report in thinking process , but only outline
2026-01-18 14:21:40 +08:00
- CRITICAL : After thinking , you MUST provide your actual response to the user . Thinking is for planning , the response is for delivery .
- Your response must contain the actual answer , not just a reference to what you thought about
2026-01-14 07:20:00 +08:00
< / thinking_style >
2026-01-18 19:55:36 +08:00
< clarification_system >
* * WORKFLOW PRIORITY : CLARIFY → PLAN → ACT * *
1. * * FIRST * * : Analyze the request in your thinking - identify what ' s unclear, missing, or ambiguous
2. * * SECOND * * : If clarification is needed , call ` ask_clarification ` tool IMMEDIATELY - do NOT start working
3. * * THIRD * * : Only after all clarifications are resolved , proceed with planning and execution
* * CRITICAL RULE : Clarification ALWAYS comes BEFORE action . Never start working and clarify mid - execution . * *
* * MANDATORY Clarification Scenarios - You MUST call ask_clarification BEFORE starting work when : * *
1. * * Missing Information * * ( ` missing_info ` ) : Required details not provided
- Example : User says " create a web scraper " but doesn ' t specify the target website
- Example : " Deploy the app " without specifying environment
- * * REQUIRED ACTION * * : Call ask_clarification to get the missing information
2. * * Ambiguous Requirements * * ( ` ambiguous_requirement ` ) : Multiple valid interpretations exist
- Example : " Optimize the code " could mean performance , readability , or memory usage
- Example : " Make it better " is unclear what aspect to improve
- * * REQUIRED ACTION * * : Call ask_clarification to clarify the exact requirement
3. * * Approach Choices * * ( ` approach_choice ` ) : Several valid approaches exist
- Example : " Add authentication " could use JWT , OAuth , session - based , or API keys
- Example : " Store data " could use database , files , cache , etc .
- * * REQUIRED ACTION * * : Call ask_clarification to let user choose the approach
4. * * Risky Operations * * ( ` risk_confirmation ` ) : Destructive actions need confirmation
- Example : Deleting files , modifying production configs , database operations
- Example : Overwriting existing code or data
- * * REQUIRED ACTION * * : Call ask_clarification to get explicit confirmation
5. * * Suggestions * * ( ` suggestion ` ) : You have a recommendation but want approval
- Example : " I recommend refactoring this code. Should I proceed? "
- * * REQUIRED ACTION * * : Call ask_clarification to get approval
* * STRICT ENFORCEMENT : * *
- ❌ DO NOT start working and then ask for clarification mid - execution - clarify FIRST
- ❌ DO NOT skip clarification for " efficiency " - accuracy matters more than speed
- ❌ DO NOT make assumptions when information is missing - ALWAYS ask
- ❌ DO NOT proceed with guesses - STOP and call ask_clarification first
- ✅ Analyze the request in thinking → Identify unclear aspects → Ask BEFORE any action
- ✅ If you identify the need for clarification in your thinking , you MUST call the tool IMMEDIATELY
- ✅ After calling ask_clarification , execution will be interrupted automatically
- ✅ Wait for user response - do NOT continue with assumptions
* * How to Use : * *
` ` ` python
ask_clarification (
question = " Your specific question here? " ,
clarification_type = " missing_info " , # or other type
context = " Why you need this information " , # optional but recommended
options = [ " option1 " , " option2 " ] # optional, for choices
)
` ` `
* * Example : * *
User : " Deploy the application "
You ( thinking ) : Missing environment info - I MUST ask for clarification
You ( action ) : ask_clarification (
question = " Which environment should I deploy to? " ,
clarification_type = " approach_choice " ,
context = " I need to know the target environment for proper configuration " ,
options = [ " development " , " staging " , " production " ]
)
[ Execution stops - wait for user response ]
User : " staging "
You : " Deploying to staging... " [ proceed ]
< / clarification_system >
2026-02-11 11:04:50 +08:00
{ skills_section }
2026-01-14 07:20:00 +08:00
2026-03-17 20:43:55 +08:00
{ deferred_tools_section }
2026-02-05 20:49:02 +08:00
{ subagent_section }
2026-02-05 19:59:25 +08:00
2026-01-14 07:20:00 +08:00
< working_directory existed = " true " >
2026-01-23 18:47:39 +08:00
- User uploads : ` / mnt / user - data / uploads ` - Files uploaded by the user ( automatically listed in context )
- User workspace : ` / mnt / user - data / workspace ` - Working directory for temporary files
- Output files : ` / mnt / user - data / outputs ` - Final deliverables must be saved here
* * File Management : * *
- Uploaded files are automatically listed in the < uploaded_files > section before each request
- Use ` read_file ` tool to read uploaded files using their paths from the list
- For PDF , PPT , Excel , and Word files , converted Markdown versions ( * . md ) are available alongside originals
- All temporary work happens in ` / mnt / user - data / workspace `
- Final deliverables must be copied to ` / mnt / user - data / outputs ` and presented using ` present_file ` tool
feat(harness): integration ACP agent tool (#1344)
* refactor: extract shared utils to break harness→app cross-layer imports
Move _validate_skill_frontmatter to src/skills/validation.py and
CONVERTIBLE_EXTENSIONS + convert_file_to_markdown to src/utils/file_conversion.py.
This eliminates the two reverse dependencies from client.py (harness layer)
into gateway/routers/ (app layer), preparing for the harness/app package split.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: split backend/src into harness (deerflow.*) and app (app.*)
Physically split the monolithic backend/src/ package into two layers:
- **Harness** (`packages/harness/deerflow/`): publishable agent framework
package with import prefix `deerflow.*`. Contains agents, sandbox, tools,
models, MCP, skills, config, and all core infrastructure.
- **App** (`app/`): unpublished application code with import prefix `app.*`.
Contains gateway (FastAPI REST API) and channels (IM integrations).
Key changes:
- Move 13 harness modules to packages/harness/deerflow/ via git mv
- Move gateway + channels to app/ via git mv
- Rename all imports: src.* → deerflow.* (harness) / app.* (app layer)
- Set up uv workspace with deerflow-harness as workspace member
- Update langgraph.json, config.example.yaml, all scripts, Docker files
- Add build-system (hatchling) to harness pyproject.toml
- Add PYTHONPATH=. to gateway startup commands for app.* resolution
- Update ruff.toml with known-first-party for import sorting
- Update all documentation to reflect new directory structure
Boundary rule enforced: harness code never imports from app.
All 429 tests pass. Lint clean.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: add harness→app boundary check test and update docs
Add test_harness_boundary.py that scans all Python files in
packages/harness/deerflow/ and fails if any `from app.*` or
`import app.*` statement is found. This enforces the architectural
rule that the harness layer never depends on the app layer.
Update CLAUDE.md to document the harness/app split architecture,
import conventions, and the boundary enforcement test.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add config versioning with auto-upgrade on startup
When config.example.yaml schema changes, developers' local config.yaml
files can silently become outdated. This adds a config_version field and
auto-upgrade mechanism so breaking changes (like src.* → deerflow.*
renames) are applied automatically before services start.
- Add config_version: 1 to config.example.yaml
- Add startup version check warning in AppConfig.from_file()
- Add scripts/config-upgrade.sh with migration registry for value replacements
- Add `make config-upgrade` target
- Auto-run config-upgrade in serve.sh and start-daemon.sh before starting services
- Add config error hints in service failure messages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix comments
* fix: update src.* import in test_sandbox_tools_security to deerflow.*
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: handle empty config and search parent dirs for config.example.yaml
Address Copilot review comments on PR #1131:
- Guard against yaml.safe_load() returning None for empty config files
- Search parent directories for config.example.yaml instead of only
looking next to config.yaml, fixing detection in common setups
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: correct skills root path depth and config_version type coercion
- loader.py: fix get_skills_root_path() to use 5 parent levels (was 3)
after harness split, file lives at packages/harness/deerflow/skills/
so parent×3 resolved to backend/packages/harness/ instead of backend/
- app_config.py: coerce config_version to int() before comparison in
_check_config_version() to prevent TypeError when YAML stores value
as string (e.g. config_version: "1")
- tests: add regression tests for both fixes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: update test imports from src.* to deerflow.*/app.* after harness refactor
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(harness): add tool-first ACP agent invocation (#37)
* feat(harness): add tool-first ACP agent invocation
* build(harness): make ACP dependency required
* fix(harness): address ACP review feedback
* feat(harness): decouple ACP agent workspace from thread data
ACP agents (codex, claude-code) previously used per-thread workspace
directories, causing path resolution complexity and coupling task
execution to DeerFlow's internal thread data layout. This change:
- Replace _resolve_cwd() with a fixed _get_work_dir() that always uses
{base_dir}/acp-workspace/, eliminating virtual path translation and
thread_id lookups
- Introduce /mnt/acp-workspace virtual path for lead agent read-only
access to ACP agent output files (same pattern as /mnt/skills)
- Add security guards: read-only validation, path traversal prevention,
command path allowlisting, and output masking for acp-workspace
- Update system prompt and tool description to guide LLM: send
self-contained tasks to ACP agents, copy results via /mnt/acp-workspace
- Add 11 new security tests for ACP workspace path handling
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(prompt): inject ACP section only when ACP agents are configured
The ACP agent guidance in the system prompt is now conditionally built
by _build_acp_section(), which checks get_acp_agents() and returns an
empty string when no ACP agents are configured. This avoids polluting
the prompt with irrelevant instructions for users who don't use ACP.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix lint
* fix(harness): address Copilot review comments on sandbox path handling and ACP tool
- local_sandbox: fix path-segment boundary bug in _resolve_path (== or startswith +"/")
and add lookahead in _resolve_paths_in_command regex to prevent /mnt/skills matching
inside /mnt/skills-extra
- local_sandbox_provider: replace print() with logger.warning(..., exc_info=True)
- invoke_acp_agent_tool: guard getattr(option, "optionId") with None default + continue;
move full prompt from INFO to DEBUG level (truncated to 200 chars)
- sandbox/tools: fix _get_acp_workspace_host_path docstring to match implementation;
remove misleading "read-only" language from validate_local_bash_command_paths
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(acp): thread-isolated workspaces, permission guardrail, and ContextVar registry
P1.1 – ACP workspace thread isolation
- Add `Paths.acp_workspace_dir(thread_id)` for per-thread paths
- `_get_work_dir(thread_id)` in invoke_acp_agent_tool now uses
`{base_dir}/threads/{thread_id}/acp-workspace/`; falls back to
global workspace when thread_id is absent or invalid
- `_invoke` extracts thread_id from `RunnableConfig` via
`Annotated[RunnableConfig, InjectedToolArg]`
- `sandbox/tools.py`: `_get_acp_workspace_host_path(thread_id)`,
`_resolve_acp_workspace_path(path, thread_id)`, and all callers
(`replace_virtual_paths_in_command`, `mask_local_paths_in_output`,
`ls_tool`, `read_file_tool`) now resolve ACP paths per-thread
P1.2 – ACP permission guardrail
- New `auto_approve_permissions: bool = False` field in `ACPAgentConfig`
- `_build_permission_response(options, *, auto_approve: bool)` now
defaults to deny; only approves when `auto_approve=True`
- Document field in `config.example.yaml`
P2 – Deferred tool registry race condition
- Replace module-level `_registry` global with `contextvars.ContextVar`
- Each asyncio request context gets its own registry; worker threads
inherit the context automatically via `loop.run_in_executor`
- Expose `get_deferred_registry` / `set_deferred_registry` /
`reset_deferred_registry` helpers
Tests: 831 pass (57 for affected modules, 3 new tests)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(sandbox): mount /mnt/acp-workspace in docker sandbox container
The AioSandboxProvider was not mounting the ACP workspace into the
sandbox container, so /mnt/acp-workspace was inaccessible when the lead
agent tried to read ACP results in docker mode.
Changes:
- `ensure_thread_dirs`: also create `acp-workspace/` (chmod 0o777) so
the directory exists before the sandbox container starts — required
for Docker volume mounts
- `_get_thread_mounts`: add read-only `/mnt/acp-workspace` mount using
the per-thread host path (`host_paths.acp_workspace_dir(thread_id)`)
- Update stale CLAUDE.md description (was "fixed global workspace")
Tests: `test_aio_sandbox_provider.py` (4 new tests)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(lint): remove unused imports in test_aio_sandbox_provider
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix config
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-26 14:20:18 +08:00
{ acp_section }
2026-01-14 07:20:00 +08:00
< / working_directory >
< response_style >
- Clear and Concise : Avoid over - formatting unless requested
- Natural Tone : Use paragraphs and prose , not bullet points by default
- Action - Oriented : Focus on delivering results , not explaining processes
< / response_style >
2026-02-10 00:18:33 +08:00
< citations >
2026-03-17 09:51:08 +08:00
* * CRITICAL : Always include citations when using web search results * *
- * * When to Use * * : MANDATORY after web_search , web_fetch , or any external information source
- * * Format * * : Use Markdown link format ` [ citation : TITLE ] ( URL ) ` immediately after the claim
- * * Placement * * : Inline citations should appear right after the sentence or claim they support
- * * Sources Section * * : Also collect all citations in a " Sources " section at the end of reports
* * Example - Inline Citations : * *
2026-02-10 00:18:33 +08:00
` ` ` markdown
2026-02-11 11:04:50 +08:00
The key AI trends for 2026 include enhanced reasoning capabilities and multimodal integration
[ citation : AI Trends 2026 ] ( https : / / techcrunch . com / ai - trends ) .
Recent breakthroughs in language models have also accelerated progress
[ citation : OpenAI Research ] ( https : / / openai . com / research ) .
2026-02-10 00:18:33 +08:00
` ` `
2026-03-17 09:51:08 +08:00
* * Example - Deep Research Report with Citations : * *
` ` ` markdown
## Executive Summary
DeerFlow is an open - source AI agent framework that gained significant traction in early 2026
[ citation : GitHub Repository ] ( https : / / github . com / bytedance / deer - flow ) . The project focuses on
providing a production - ready agent system with sandbox execution and memory management
[ citation : DeerFlow Documentation ] ( https : / / deer - flow . dev / docs ) .
## Key Analysis
### Architecture Design
The system uses LangGraph for workflow orchestration [ citation : LangGraph Docs ] ( https : / / langchain . com / langgraph ) ,
combined with a FastAPI gateway for REST API access [ citation : FastAPI ] ( https : / / fastapi . tiangolo . com ) .
## Sources
### Primary Sources
- [ GitHub Repository ] ( https : / / github . com / bytedance / deer - flow ) - Official source code and documentation
- [ DeerFlow Documentation ] ( https : / / deer - flow . dev / docs ) - Technical specifications
### Media Coverage
- [ AI Trends 2026 ] ( https : / / techcrunch . com / ai - trends ) - Industry analysis
` ` `
* * CRITICAL : Sources section format : * *
- Every item in the Sources section MUST be a clickable markdown link with URL
- Use standard markdown link ` [ Title ] ( URL ) - Description ` format ( NOT ` [ citation : . . . ] ` format )
- The ` [ citation : Title ] ( URL ) ` format is ONLY for inline citations within the report body
- ❌ WRONG : ` GitHub 仓库 - 官方源代码和文档 ` ( no URL ! )
- ❌ WRONG in Sources : ` [ citation : GitHub Repository ] ( url ) ` ( citation prefix is for inline only ! )
- ✅ RIGHT in Sources : ` [ GitHub Repository ] ( https : / / github . com / bytedance / deer - flow ) - 官方源代码和文档 `
* * WORKFLOW for Research Tasks : * *
1. Use web_search to find sources → Extract { { title , url , snippet } } from results
2. Write content with inline citations : ` claim [ citation : Title ] ( url ) `
3. Collect all citations in a " Sources " section at the end
4. NEVER write claims without citations when sources are available
* * CRITICAL RULES : * *
- ❌ DO NOT write research content without citations
- ❌ DO NOT forget to extract URLs from search results
- ✅ ALWAYS add ` [ citation : Title ] ( URL ) ` after claims from external sources
- ✅ ALWAYS include a " Sources " section listing all references
2026-02-10 00:18:33 +08:00
< / citations >
2026-01-14 07:20:00 +08:00
< critical_reminders >
2026-01-18 19:55:36 +08:00
- * * Clarification First * * : ALWAYS clarify unclear / missing / ambiguous requirements BEFORE starting work - never assume or guess
2026-02-06 20:32:15 +08:00
{ subagent_reminder } - Skill First : Always load the relevant skill before starting * * complex * * tasks .
2026-01-14 07:20:00 +08:00
- Progressive Loading : Load resources incrementally as referenced in skills
2026-01-15 14:37:00 +08:00
- Output Files : Final deliverables must be in ` / mnt / user - data / outputs `
2026-01-14 07:20:00 +08:00
- Clarity : Be direct and helpful , avoid unnecessary meta - commentary
2026-01-29 12:51:04 +08:00
- Including Images and Mermaid : Images and Mermaid diagrams are always welcomed in the Markdown format , and you ' re encouraged to use ` \n \n ` or " ```mermaid " to display images in response or Markdown files
2026-01-14 07:20:00 +08:00
- Multi - task : Better utilize parallel tool calling to call multiple tools at one time for better performance
- Language Consistency : Keep using the same language as user ' s
2026-01-18 14:21:40 +08:00
- Always Respond : Your thinking is internal . You MUST always provide a visible response to the user after thinking .
2026-01-14 07:20:00 +08:00
< / critical_reminders >
"""
2026-03-03 21:32:01 +08:00
def _get_memory_context ( agent_name : str | None = None ) - > str :
2026-02-03 13:31:05 +08:00
""" Get memory context for injection into system prompt.
2026-03-03 21:32:01 +08:00
Args :
agent_name : If provided , loads per - agent memory . If None , loads global memory .
2026-02-03 13:31:05 +08:00
Returns :
Formatted memory context string wrapped in XML tags , or empty string if disabled .
"""
try :
refactor: split backend into harness (deerflow.*) and app (app.*) (#1131)
* refactor: extract shared utils to break harness→app cross-layer imports
Move _validate_skill_frontmatter to src/skills/validation.py and
CONVERTIBLE_EXTENSIONS + convert_file_to_markdown to src/utils/file_conversion.py.
This eliminates the two reverse dependencies from client.py (harness layer)
into gateway/routers/ (app layer), preparing for the harness/app package split.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: split backend/src into harness (deerflow.*) and app (app.*)
Physically split the monolithic backend/src/ package into two layers:
- **Harness** (`packages/harness/deerflow/`): publishable agent framework
package with import prefix `deerflow.*`. Contains agents, sandbox, tools,
models, MCP, skills, config, and all core infrastructure.
- **App** (`app/`): unpublished application code with import prefix `app.*`.
Contains gateway (FastAPI REST API) and channels (IM integrations).
Key changes:
- Move 13 harness modules to packages/harness/deerflow/ via git mv
- Move gateway + channels to app/ via git mv
- Rename all imports: src.* → deerflow.* (harness) / app.* (app layer)
- Set up uv workspace with deerflow-harness as workspace member
- Update langgraph.json, config.example.yaml, all scripts, Docker files
- Add build-system (hatchling) to harness pyproject.toml
- Add PYTHONPATH=. to gateway startup commands for app.* resolution
- Update ruff.toml with known-first-party for import sorting
- Update all documentation to reflect new directory structure
Boundary rule enforced: harness code never imports from app.
All 429 tests pass. Lint clean.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: add harness→app boundary check test and update docs
Add test_harness_boundary.py that scans all Python files in
packages/harness/deerflow/ and fails if any `from app.*` or
`import app.*` statement is found. This enforces the architectural
rule that the harness layer never depends on the app layer.
Update CLAUDE.md to document the harness/app split architecture,
import conventions, and the boundary enforcement test.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add config versioning with auto-upgrade on startup
When config.example.yaml schema changes, developers' local config.yaml
files can silently become outdated. This adds a config_version field and
auto-upgrade mechanism so breaking changes (like src.* → deerflow.*
renames) are applied automatically before services start.
- Add config_version: 1 to config.example.yaml
- Add startup version check warning in AppConfig.from_file()
- Add scripts/config-upgrade.sh with migration registry for value replacements
- Add `make config-upgrade` target
- Auto-run config-upgrade in serve.sh and start-daemon.sh before starting services
- Add config error hints in service failure messages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix comments
* fix: update src.* import in test_sandbox_tools_security to deerflow.*
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: handle empty config and search parent dirs for config.example.yaml
Address Copilot review comments on PR #1131:
- Guard against yaml.safe_load() returning None for empty config files
- Search parent directories for config.example.yaml instead of only
looking next to config.yaml, fixing detection in common setups
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: correct skills root path depth and config_version type coercion
- loader.py: fix get_skills_root_path() to use 5 parent levels (was 3)
after harness split, file lives at packages/harness/deerflow/skills/
so parent×3 resolved to backend/packages/harness/ instead of backend/
- app_config.py: coerce config_version to int() before comparison in
_check_config_version() to prevent TypeError when YAML stores value
as string (e.g. config_version: "1")
- tests: add regression tests for both fixes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: update test imports from src.* to deerflow.*/app.* after harness refactor
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 22:55:52 +08:00
from deerflow . agents . memory import format_memory_for_injection , get_memory_data
from deerflow . config . memory_config import get_memory_config
2026-02-03 13:31:05 +08:00
config = get_memory_config ( )
if not config . enabled or not config . injection_enabled :
return " "
2026-03-03 21:32:01 +08:00
memory_data = get_memory_data ( agent_name )
2026-02-05 19:59:25 +08:00
memory_content = format_memory_for_injection ( memory_data , max_tokens = config . max_injection_tokens )
2026-02-03 13:31:05 +08:00
if not memory_content . strip ( ) :
return " "
return f """ <memory>
{ memory_content }
< / memory >
"""
except Exception as e :
print ( f " Failed to load memory context: { e } " )
return " "
2026-03-03 21:32:01 +08:00
def get_skills_prompt_section ( available_skills : set [ str ] | None = None ) - > str :
2026-02-11 11:04:50 +08:00
""" Generate the skills prompt section with available skills list.
Returns the < skill_system > . . . < / skill_system > block listing all enabled skills ,
suitable for injection into any agent ' s system prompt.
"""
2026-01-20 13:57:36 +08:00
skills = load_skills ( enabled_only = True )
2026-01-16 14:44:51 +08:00
try :
refactor: split backend into harness (deerflow.*) and app (app.*) (#1131)
* refactor: extract shared utils to break harness→app cross-layer imports
Move _validate_skill_frontmatter to src/skills/validation.py and
CONVERTIBLE_EXTENSIONS + convert_file_to_markdown to src/utils/file_conversion.py.
This eliminates the two reverse dependencies from client.py (harness layer)
into gateway/routers/ (app layer), preparing for the harness/app package split.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: split backend/src into harness (deerflow.*) and app (app.*)
Physically split the monolithic backend/src/ package into two layers:
- **Harness** (`packages/harness/deerflow/`): publishable agent framework
package with import prefix `deerflow.*`. Contains agents, sandbox, tools,
models, MCP, skills, config, and all core infrastructure.
- **App** (`app/`): unpublished application code with import prefix `app.*`.
Contains gateway (FastAPI REST API) and channels (IM integrations).
Key changes:
- Move 13 harness modules to packages/harness/deerflow/ via git mv
- Move gateway + channels to app/ via git mv
- Rename all imports: src.* → deerflow.* (harness) / app.* (app layer)
- Set up uv workspace with deerflow-harness as workspace member
- Update langgraph.json, config.example.yaml, all scripts, Docker files
- Add build-system (hatchling) to harness pyproject.toml
- Add PYTHONPATH=. to gateway startup commands for app.* resolution
- Update ruff.toml with known-first-party for import sorting
- Update all documentation to reflect new directory structure
Boundary rule enforced: harness code never imports from app.
All 429 tests pass. Lint clean.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: add harness→app boundary check test and update docs
Add test_harness_boundary.py that scans all Python files in
packages/harness/deerflow/ and fails if any `from app.*` or
`import app.*` statement is found. This enforces the architectural
rule that the harness layer never depends on the app layer.
Update CLAUDE.md to document the harness/app split architecture,
import conventions, and the boundary enforcement test.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add config versioning with auto-upgrade on startup
When config.example.yaml schema changes, developers' local config.yaml
files can silently become outdated. This adds a config_version field and
auto-upgrade mechanism so breaking changes (like src.* → deerflow.*
renames) are applied automatically before services start.
- Add config_version: 1 to config.example.yaml
- Add startup version check warning in AppConfig.from_file()
- Add scripts/config-upgrade.sh with migration registry for value replacements
- Add `make config-upgrade` target
- Auto-run config-upgrade in serve.sh and start-daemon.sh before starting services
- Add config error hints in service failure messages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix comments
* fix: update src.* import in test_sandbox_tools_security to deerflow.*
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: handle empty config and search parent dirs for config.example.yaml
Address Copilot review comments on PR #1131:
- Guard against yaml.safe_load() returning None for empty config files
- Search parent directories for config.example.yaml instead of only
looking next to config.yaml, fixing detection in common setups
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: correct skills root path depth and config_version type coercion
- loader.py: fix get_skills_root_path() to use 5 parent levels (was 3)
after harness split, file lives at packages/harness/deerflow/skills/
so parent×3 resolved to backend/packages/harness/ instead of backend/
- app_config.py: coerce config_version to int() before comparison in
_check_config_version() to prevent TypeError when YAML stores value
as string (e.g. config_version: "1")
- tests: add regression tests for both fixes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: update test imports from src.* to deerflow.*/app.* after harness refactor
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 22:55:52 +08:00
from deerflow . config import get_app_config
2026-01-16 14:44:51 +08:00
config = get_app_config ( )
container_base_path = config . skills . container_path
except Exception :
container_base_path = " /mnt/skills "
2026-02-11 11:04:50 +08:00
if not skills :
return " "
2026-03-03 21:32:01 +08:00
if available_skills is not None :
skills = [ skill for skill in skills if skill . name in available_skills ]
2026-02-11 11:04:50 +08:00
skill_items = " \n " . join (
f " <skill> \n <name> { skill . name } </name> \n <description> { skill . description } </description> \n <location> { skill . get_container_file_path ( container_base_path ) } </location> \n </skill> " for skill in skills
)
skills_list = f " <available_skills> \n { skill_items } \n </available_skills> "
return f """ <skill_system>
You have access to skills that provide optimized workflows for specific tasks . Each skill contains best practices , frameworks , and references to additional resources .
2026-01-16 14:44:51 +08:00
2026-02-11 11:04:50 +08:00
* * Progressive Loading Pattern : * *
1. When a user query matches a skill ' s use case, immediately call `read_file` on the skill ' s main file using the path attribute provided in the skill tag below
2. Read and understand the skill ' s workflow and instructions
3. The skill file contains references to external resources under the same folder
4. Load referenced resources only when needed during execution
5. Follow the skill ' s instructions precisely
* * Skills are located at : * * { container_base_path }
{ skills_list }
< / skill_system > """
2026-03-03 21:32:01 +08:00
def get_agent_soul ( agent_name : str | None ) - > str :
# Append SOUL.md (agent personality) if present
soul = load_agent_soul ( agent_name )
if soul :
return f " <soul> \n { soul } \n </soul> \n " if soul else " "
return " "
2026-03-17 20:43:55 +08:00
def get_deferred_tools_prompt_section ( ) - > str :
""" Generate <available-deferred-tools> block for the system prompt.
Lists only deferred tool names so the agent knows what exists
and can use tool_search to load them .
Returns empty string when tool_search is disabled or no tools are deferred .
"""
from deerflow . tools . builtins . tool_search import get_deferred_registry
try :
from deerflow . config import get_app_config
if not get_app_config ( ) . tool_search . enabled :
return " "
except FileNotFoundError :
return " "
registry = get_deferred_registry ( )
if not registry :
return " "
names = " \n " . join ( e . name for e in registry . entries )
return f " <available-deferred-tools> \n { names } \n </available-deferred-tools> "
feat(harness): integration ACP agent tool (#1344)
* refactor: extract shared utils to break harness→app cross-layer imports
Move _validate_skill_frontmatter to src/skills/validation.py and
CONVERTIBLE_EXTENSIONS + convert_file_to_markdown to src/utils/file_conversion.py.
This eliminates the two reverse dependencies from client.py (harness layer)
into gateway/routers/ (app layer), preparing for the harness/app package split.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: split backend/src into harness (deerflow.*) and app (app.*)
Physically split the monolithic backend/src/ package into two layers:
- **Harness** (`packages/harness/deerflow/`): publishable agent framework
package with import prefix `deerflow.*`. Contains agents, sandbox, tools,
models, MCP, skills, config, and all core infrastructure.
- **App** (`app/`): unpublished application code with import prefix `app.*`.
Contains gateway (FastAPI REST API) and channels (IM integrations).
Key changes:
- Move 13 harness modules to packages/harness/deerflow/ via git mv
- Move gateway + channels to app/ via git mv
- Rename all imports: src.* → deerflow.* (harness) / app.* (app layer)
- Set up uv workspace with deerflow-harness as workspace member
- Update langgraph.json, config.example.yaml, all scripts, Docker files
- Add build-system (hatchling) to harness pyproject.toml
- Add PYTHONPATH=. to gateway startup commands for app.* resolution
- Update ruff.toml with known-first-party for import sorting
- Update all documentation to reflect new directory structure
Boundary rule enforced: harness code never imports from app.
All 429 tests pass. Lint clean.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: add harness→app boundary check test and update docs
Add test_harness_boundary.py that scans all Python files in
packages/harness/deerflow/ and fails if any `from app.*` or
`import app.*` statement is found. This enforces the architectural
rule that the harness layer never depends on the app layer.
Update CLAUDE.md to document the harness/app split architecture,
import conventions, and the boundary enforcement test.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add config versioning with auto-upgrade on startup
When config.example.yaml schema changes, developers' local config.yaml
files can silently become outdated. This adds a config_version field and
auto-upgrade mechanism so breaking changes (like src.* → deerflow.*
renames) are applied automatically before services start.
- Add config_version: 1 to config.example.yaml
- Add startup version check warning in AppConfig.from_file()
- Add scripts/config-upgrade.sh with migration registry for value replacements
- Add `make config-upgrade` target
- Auto-run config-upgrade in serve.sh and start-daemon.sh before starting services
- Add config error hints in service failure messages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix comments
* fix: update src.* import in test_sandbox_tools_security to deerflow.*
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: handle empty config and search parent dirs for config.example.yaml
Address Copilot review comments on PR #1131:
- Guard against yaml.safe_load() returning None for empty config files
- Search parent directories for config.example.yaml instead of only
looking next to config.yaml, fixing detection in common setups
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: correct skills root path depth and config_version type coercion
- loader.py: fix get_skills_root_path() to use 5 parent levels (was 3)
after harness split, file lives at packages/harness/deerflow/skills/
so parent×3 resolved to backend/packages/harness/ instead of backend/
- app_config.py: coerce config_version to int() before comparison in
_check_config_version() to prevent TypeError when YAML stores value
as string (e.g. config_version: "1")
- tests: add regression tests for both fixes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: update test imports from src.* to deerflow.*/app.* after harness refactor
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(harness): add tool-first ACP agent invocation (#37)
* feat(harness): add tool-first ACP agent invocation
* build(harness): make ACP dependency required
* fix(harness): address ACP review feedback
* feat(harness): decouple ACP agent workspace from thread data
ACP agents (codex, claude-code) previously used per-thread workspace
directories, causing path resolution complexity and coupling task
execution to DeerFlow's internal thread data layout. This change:
- Replace _resolve_cwd() with a fixed _get_work_dir() that always uses
{base_dir}/acp-workspace/, eliminating virtual path translation and
thread_id lookups
- Introduce /mnt/acp-workspace virtual path for lead agent read-only
access to ACP agent output files (same pattern as /mnt/skills)
- Add security guards: read-only validation, path traversal prevention,
command path allowlisting, and output masking for acp-workspace
- Update system prompt and tool description to guide LLM: send
self-contained tasks to ACP agents, copy results via /mnt/acp-workspace
- Add 11 new security tests for ACP workspace path handling
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(prompt): inject ACP section only when ACP agents are configured
The ACP agent guidance in the system prompt is now conditionally built
by _build_acp_section(), which checks get_acp_agents() and returns an
empty string when no ACP agents are configured. This avoids polluting
the prompt with irrelevant instructions for users who don't use ACP.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix lint
* fix(harness): address Copilot review comments on sandbox path handling and ACP tool
- local_sandbox: fix path-segment boundary bug in _resolve_path (== or startswith +"/")
and add lookahead in _resolve_paths_in_command regex to prevent /mnt/skills matching
inside /mnt/skills-extra
- local_sandbox_provider: replace print() with logger.warning(..., exc_info=True)
- invoke_acp_agent_tool: guard getattr(option, "optionId") with None default + continue;
move full prompt from INFO to DEBUG level (truncated to 200 chars)
- sandbox/tools: fix _get_acp_workspace_host_path docstring to match implementation;
remove misleading "read-only" language from validate_local_bash_command_paths
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(acp): thread-isolated workspaces, permission guardrail, and ContextVar registry
P1.1 – ACP workspace thread isolation
- Add `Paths.acp_workspace_dir(thread_id)` for per-thread paths
- `_get_work_dir(thread_id)` in invoke_acp_agent_tool now uses
`{base_dir}/threads/{thread_id}/acp-workspace/`; falls back to
global workspace when thread_id is absent or invalid
- `_invoke` extracts thread_id from `RunnableConfig` via
`Annotated[RunnableConfig, InjectedToolArg]`
- `sandbox/tools.py`: `_get_acp_workspace_host_path(thread_id)`,
`_resolve_acp_workspace_path(path, thread_id)`, and all callers
(`replace_virtual_paths_in_command`, `mask_local_paths_in_output`,
`ls_tool`, `read_file_tool`) now resolve ACP paths per-thread
P1.2 – ACP permission guardrail
- New `auto_approve_permissions: bool = False` field in `ACPAgentConfig`
- `_build_permission_response(options, *, auto_approve: bool)` now
defaults to deny; only approves when `auto_approve=True`
- Document field in `config.example.yaml`
P2 – Deferred tool registry race condition
- Replace module-level `_registry` global with `contextvars.ContextVar`
- Each asyncio request context gets its own registry; worker threads
inherit the context automatically via `loop.run_in_executor`
- Expose `get_deferred_registry` / `set_deferred_registry` /
`reset_deferred_registry` helpers
Tests: 831 pass (57 for affected modules, 3 new tests)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(sandbox): mount /mnt/acp-workspace in docker sandbox container
The AioSandboxProvider was not mounting the ACP workspace into the
sandbox container, so /mnt/acp-workspace was inaccessible when the lead
agent tried to read ACP results in docker mode.
Changes:
- `ensure_thread_dirs`: also create `acp-workspace/` (chmod 0o777) so
the directory exists before the sandbox container starts — required
for Docker volume mounts
- `_get_thread_mounts`: add read-only `/mnt/acp-workspace` mount using
the per-thread host path (`host_paths.acp_workspace_dir(thread_id)`)
- Update stale CLAUDE.md description (was "fixed global workspace")
Tests: `test_aio_sandbox_provider.py` (4 new tests)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(lint): remove unused imports in test_aio_sandbox_provider
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix config
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-26 14:20:18 +08:00
def _build_acp_section ( ) - > str :
""" Build the ACP agent prompt section, only if ACP agents are configured. """
try :
from deerflow . config . acp_config import get_acp_agents
agents = get_acp_agents ( )
if not agents :
return " "
except Exception :
return " "
return (
" \n **ACP Agent Tasks (invoke_acp_agent):** \n "
" - ACP agents (e.g. codex, claude_code) run in their own independent workspace — NOT in `/mnt/user-data/` \n "
" - When writing prompts for ACP agents, describe the task only — do NOT reference `/mnt/user-data` paths \n "
" - ACP agent results are accessible at `/mnt/acp-workspace/` (read-only) — use `ls`, `read_file`, or `bash cp` to retrieve output files \n "
" - To deliver ACP output to the user: copy from `/mnt/acp-workspace/<file>` to `/mnt/user-data/outputs/<file>`, then use `present_file` "
)
2026-03-03 21:32:01 +08:00
def apply_prompt_template ( subagent_enabled : bool = False , max_concurrent_subagents : int = 3 , * , agent_name : str | None = None , available_skills : set [ str ] | None = None ) - > str :
2026-02-03 13:31:05 +08:00
# Get memory context
2026-03-03 21:32:01 +08:00
memory_context = _get_memory_context ( agent_name )
2026-02-03 13:31:05 +08:00
2026-02-06 15:42:53 +08:00
# Include subagent section only if enabled (from runtime parameter)
2026-02-11 11:41:30 +08:00
n = max_concurrent_subagents
subagent_section = _build_subagent_section ( n ) if subagent_enabled else " "
2026-02-05 20:49:02 +08:00
2026-02-06 20:32:15 +08:00
# Add subagent reminder to critical_reminders if enabled
subagent_reminder = (
2026-02-09 13:49:38 +08:00
" - **Orchestrator Mode**: You are a task orchestrator - decompose complex tasks into parallel sub-tasks. "
2026-02-11 11:41:30 +08:00
f " **HARD LIMIT: max { n } `task` calls per response.** "
f " If > { n } sub-tasks, split into sequential batches of ≤ { n } . Synthesize after ALL batches complete. \n "
2026-02-06 20:32:15 +08:00
if subagent_enabled
else " "
)
# Add subagent thinking guidance if enabled
subagent_thinking = (
2026-02-09 13:49:38 +08:00
" - **DECOMPOSITION CHECK: Can this task be broken into 2+ parallel sub-tasks? If YES, COUNT them. "
2026-02-11 11:41:30 +08:00
f " If count > { n } , you MUST plan batches of ≤ { n } and only launch the FIRST batch now. "
f " NEVER launch more than { n } `task` calls in one response.** \n "
2026-02-06 20:32:15 +08:00
if subagent_enabled
else " "
)
2026-02-11 11:04:50 +08:00
# Get skills section
2026-03-03 21:32:01 +08:00
skills_section = get_skills_prompt_section ( available_skills )
2026-02-11 11:04:50 +08:00
2026-03-17 20:43:55 +08:00
# Get deferred tools section (tool_search)
deferred_tools_section = get_deferred_tools_prompt_section ( )
feat(harness): integration ACP agent tool (#1344)
* refactor: extract shared utils to break harness→app cross-layer imports
Move _validate_skill_frontmatter to src/skills/validation.py and
CONVERTIBLE_EXTENSIONS + convert_file_to_markdown to src/utils/file_conversion.py.
This eliminates the two reverse dependencies from client.py (harness layer)
into gateway/routers/ (app layer), preparing for the harness/app package split.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: split backend/src into harness (deerflow.*) and app (app.*)
Physically split the monolithic backend/src/ package into two layers:
- **Harness** (`packages/harness/deerflow/`): publishable agent framework
package with import prefix `deerflow.*`. Contains agents, sandbox, tools,
models, MCP, skills, config, and all core infrastructure.
- **App** (`app/`): unpublished application code with import prefix `app.*`.
Contains gateway (FastAPI REST API) and channels (IM integrations).
Key changes:
- Move 13 harness modules to packages/harness/deerflow/ via git mv
- Move gateway + channels to app/ via git mv
- Rename all imports: src.* → deerflow.* (harness) / app.* (app layer)
- Set up uv workspace with deerflow-harness as workspace member
- Update langgraph.json, config.example.yaml, all scripts, Docker files
- Add build-system (hatchling) to harness pyproject.toml
- Add PYTHONPATH=. to gateway startup commands for app.* resolution
- Update ruff.toml with known-first-party for import sorting
- Update all documentation to reflect new directory structure
Boundary rule enforced: harness code never imports from app.
All 429 tests pass. Lint clean.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: add harness→app boundary check test and update docs
Add test_harness_boundary.py that scans all Python files in
packages/harness/deerflow/ and fails if any `from app.*` or
`import app.*` statement is found. This enforces the architectural
rule that the harness layer never depends on the app layer.
Update CLAUDE.md to document the harness/app split architecture,
import conventions, and the boundary enforcement test.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add config versioning with auto-upgrade on startup
When config.example.yaml schema changes, developers' local config.yaml
files can silently become outdated. This adds a config_version field and
auto-upgrade mechanism so breaking changes (like src.* → deerflow.*
renames) are applied automatically before services start.
- Add config_version: 1 to config.example.yaml
- Add startup version check warning in AppConfig.from_file()
- Add scripts/config-upgrade.sh with migration registry for value replacements
- Add `make config-upgrade` target
- Auto-run config-upgrade in serve.sh and start-daemon.sh before starting services
- Add config error hints in service failure messages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix comments
* fix: update src.* import in test_sandbox_tools_security to deerflow.*
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: handle empty config and search parent dirs for config.example.yaml
Address Copilot review comments on PR #1131:
- Guard against yaml.safe_load() returning None for empty config files
- Search parent directories for config.example.yaml instead of only
looking next to config.yaml, fixing detection in common setups
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: correct skills root path depth and config_version type coercion
- loader.py: fix get_skills_root_path() to use 5 parent levels (was 3)
after harness split, file lives at packages/harness/deerflow/skills/
so parent×3 resolved to backend/packages/harness/ instead of backend/
- app_config.py: coerce config_version to int() before comparison in
_check_config_version() to prevent TypeError when YAML stores value
as string (e.g. config_version: "1")
- tests: add regression tests for both fixes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: update test imports from src.* to deerflow.*/app.* after harness refactor
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(harness): add tool-first ACP agent invocation (#37)
* feat(harness): add tool-first ACP agent invocation
* build(harness): make ACP dependency required
* fix(harness): address ACP review feedback
* feat(harness): decouple ACP agent workspace from thread data
ACP agents (codex, claude-code) previously used per-thread workspace
directories, causing path resolution complexity and coupling task
execution to DeerFlow's internal thread data layout. This change:
- Replace _resolve_cwd() with a fixed _get_work_dir() that always uses
{base_dir}/acp-workspace/, eliminating virtual path translation and
thread_id lookups
- Introduce /mnt/acp-workspace virtual path for lead agent read-only
access to ACP agent output files (same pattern as /mnt/skills)
- Add security guards: read-only validation, path traversal prevention,
command path allowlisting, and output masking for acp-workspace
- Update system prompt and tool description to guide LLM: send
self-contained tasks to ACP agents, copy results via /mnt/acp-workspace
- Add 11 new security tests for ACP workspace path handling
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(prompt): inject ACP section only when ACP agents are configured
The ACP agent guidance in the system prompt is now conditionally built
by _build_acp_section(), which checks get_acp_agents() and returns an
empty string when no ACP agents are configured. This avoids polluting
the prompt with irrelevant instructions for users who don't use ACP.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix lint
* fix(harness): address Copilot review comments on sandbox path handling and ACP tool
- local_sandbox: fix path-segment boundary bug in _resolve_path (== or startswith +"/")
and add lookahead in _resolve_paths_in_command regex to prevent /mnt/skills matching
inside /mnt/skills-extra
- local_sandbox_provider: replace print() with logger.warning(..., exc_info=True)
- invoke_acp_agent_tool: guard getattr(option, "optionId") with None default + continue;
move full prompt from INFO to DEBUG level (truncated to 200 chars)
- sandbox/tools: fix _get_acp_workspace_host_path docstring to match implementation;
remove misleading "read-only" language from validate_local_bash_command_paths
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(acp): thread-isolated workspaces, permission guardrail, and ContextVar registry
P1.1 – ACP workspace thread isolation
- Add `Paths.acp_workspace_dir(thread_id)` for per-thread paths
- `_get_work_dir(thread_id)` in invoke_acp_agent_tool now uses
`{base_dir}/threads/{thread_id}/acp-workspace/`; falls back to
global workspace when thread_id is absent or invalid
- `_invoke` extracts thread_id from `RunnableConfig` via
`Annotated[RunnableConfig, InjectedToolArg]`
- `sandbox/tools.py`: `_get_acp_workspace_host_path(thread_id)`,
`_resolve_acp_workspace_path(path, thread_id)`, and all callers
(`replace_virtual_paths_in_command`, `mask_local_paths_in_output`,
`ls_tool`, `read_file_tool`) now resolve ACP paths per-thread
P1.2 – ACP permission guardrail
- New `auto_approve_permissions: bool = False` field in `ACPAgentConfig`
- `_build_permission_response(options, *, auto_approve: bool)` now
defaults to deny; only approves when `auto_approve=True`
- Document field in `config.example.yaml`
P2 – Deferred tool registry race condition
- Replace module-level `_registry` global with `contextvars.ContextVar`
- Each asyncio request context gets its own registry; worker threads
inherit the context automatically via `loop.run_in_executor`
- Expose `get_deferred_registry` / `set_deferred_registry` /
`reset_deferred_registry` helpers
Tests: 831 pass (57 for affected modules, 3 new tests)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(sandbox): mount /mnt/acp-workspace in docker sandbox container
The AioSandboxProvider was not mounting the ACP workspace into the
sandbox container, so /mnt/acp-workspace was inaccessible when the lead
agent tried to read ACP results in docker mode.
Changes:
- `ensure_thread_dirs`: also create `acp-workspace/` (chmod 0o777) so
the directory exists before the sandbox container starts — required
for Docker volume mounts
- `_get_thread_mounts`: add read-only `/mnt/acp-workspace` mount using
the per-thread host path (`host_paths.acp_workspace_dir(thread_id)`)
- Update stale CLAUDE.md description (was "fixed global workspace")
Tests: `test_aio_sandbox_provider.py` (4 new tests)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(lint): remove unused imports in test_aio_sandbox_provider
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix config
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-26 14:20:18 +08:00
# Build ACP agent section only if ACP agents are configured
acp_section = _build_acp_section ( )
2026-02-03 13:31:05 +08:00
# Format the prompt with dynamic skills and memory
prompt = SYSTEM_PROMPT_TEMPLATE . format (
2026-03-03 21:32:01 +08:00
agent_name = agent_name or " DeerFlow 2.0 " ,
soul = get_agent_soul ( agent_name ) ,
2026-02-11 11:04:50 +08:00
skills_section = skills_section ,
2026-03-17 20:43:55 +08:00
deferred_tools_section = deferred_tools_section ,
2026-02-03 13:31:05 +08:00
memory_context = memory_context ,
2026-02-05 20:49:02 +08:00
subagent_section = subagent_section ,
2026-02-06 20:32:15 +08:00
subagent_reminder = subagent_reminder ,
subagent_thinking = subagent_thinking ,
feat(harness): integration ACP agent tool (#1344)
* refactor: extract shared utils to break harness→app cross-layer imports
Move _validate_skill_frontmatter to src/skills/validation.py and
CONVERTIBLE_EXTENSIONS + convert_file_to_markdown to src/utils/file_conversion.py.
This eliminates the two reverse dependencies from client.py (harness layer)
into gateway/routers/ (app layer), preparing for the harness/app package split.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: split backend/src into harness (deerflow.*) and app (app.*)
Physically split the monolithic backend/src/ package into two layers:
- **Harness** (`packages/harness/deerflow/`): publishable agent framework
package with import prefix `deerflow.*`. Contains agents, sandbox, tools,
models, MCP, skills, config, and all core infrastructure.
- **App** (`app/`): unpublished application code with import prefix `app.*`.
Contains gateway (FastAPI REST API) and channels (IM integrations).
Key changes:
- Move 13 harness modules to packages/harness/deerflow/ via git mv
- Move gateway + channels to app/ via git mv
- Rename all imports: src.* → deerflow.* (harness) / app.* (app layer)
- Set up uv workspace with deerflow-harness as workspace member
- Update langgraph.json, config.example.yaml, all scripts, Docker files
- Add build-system (hatchling) to harness pyproject.toml
- Add PYTHONPATH=. to gateway startup commands for app.* resolution
- Update ruff.toml with known-first-party for import sorting
- Update all documentation to reflect new directory structure
Boundary rule enforced: harness code never imports from app.
All 429 tests pass. Lint clean.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: add harness→app boundary check test and update docs
Add test_harness_boundary.py that scans all Python files in
packages/harness/deerflow/ and fails if any `from app.*` or
`import app.*` statement is found. This enforces the architectural
rule that the harness layer never depends on the app layer.
Update CLAUDE.md to document the harness/app split architecture,
import conventions, and the boundary enforcement test.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add config versioning with auto-upgrade on startup
When config.example.yaml schema changes, developers' local config.yaml
files can silently become outdated. This adds a config_version field and
auto-upgrade mechanism so breaking changes (like src.* → deerflow.*
renames) are applied automatically before services start.
- Add config_version: 1 to config.example.yaml
- Add startup version check warning in AppConfig.from_file()
- Add scripts/config-upgrade.sh with migration registry for value replacements
- Add `make config-upgrade` target
- Auto-run config-upgrade in serve.sh and start-daemon.sh before starting services
- Add config error hints in service failure messages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix comments
* fix: update src.* import in test_sandbox_tools_security to deerflow.*
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: handle empty config and search parent dirs for config.example.yaml
Address Copilot review comments on PR #1131:
- Guard against yaml.safe_load() returning None for empty config files
- Search parent directories for config.example.yaml instead of only
looking next to config.yaml, fixing detection in common setups
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: correct skills root path depth and config_version type coercion
- loader.py: fix get_skills_root_path() to use 5 parent levels (was 3)
after harness split, file lives at packages/harness/deerflow/skills/
so parent×3 resolved to backend/packages/harness/ instead of backend/
- app_config.py: coerce config_version to int() before comparison in
_check_config_version() to prevent TypeError when YAML stores value
as string (e.g. config_version: "1")
- tests: add regression tests for both fixes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: update test imports from src.* to deerflow.*/app.* after harness refactor
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(harness): add tool-first ACP agent invocation (#37)
* feat(harness): add tool-first ACP agent invocation
* build(harness): make ACP dependency required
* fix(harness): address ACP review feedback
* feat(harness): decouple ACP agent workspace from thread data
ACP agents (codex, claude-code) previously used per-thread workspace
directories, causing path resolution complexity and coupling task
execution to DeerFlow's internal thread data layout. This change:
- Replace _resolve_cwd() with a fixed _get_work_dir() that always uses
{base_dir}/acp-workspace/, eliminating virtual path translation and
thread_id lookups
- Introduce /mnt/acp-workspace virtual path for lead agent read-only
access to ACP agent output files (same pattern as /mnt/skills)
- Add security guards: read-only validation, path traversal prevention,
command path allowlisting, and output masking for acp-workspace
- Update system prompt and tool description to guide LLM: send
self-contained tasks to ACP agents, copy results via /mnt/acp-workspace
- Add 11 new security tests for ACP workspace path handling
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(prompt): inject ACP section only when ACP agents are configured
The ACP agent guidance in the system prompt is now conditionally built
by _build_acp_section(), which checks get_acp_agents() and returns an
empty string when no ACP agents are configured. This avoids polluting
the prompt with irrelevant instructions for users who don't use ACP.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix lint
* fix(harness): address Copilot review comments on sandbox path handling and ACP tool
- local_sandbox: fix path-segment boundary bug in _resolve_path (== or startswith +"/")
and add lookahead in _resolve_paths_in_command regex to prevent /mnt/skills matching
inside /mnt/skills-extra
- local_sandbox_provider: replace print() with logger.warning(..., exc_info=True)
- invoke_acp_agent_tool: guard getattr(option, "optionId") with None default + continue;
move full prompt from INFO to DEBUG level (truncated to 200 chars)
- sandbox/tools: fix _get_acp_workspace_host_path docstring to match implementation;
remove misleading "read-only" language from validate_local_bash_command_paths
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(acp): thread-isolated workspaces, permission guardrail, and ContextVar registry
P1.1 – ACP workspace thread isolation
- Add `Paths.acp_workspace_dir(thread_id)` for per-thread paths
- `_get_work_dir(thread_id)` in invoke_acp_agent_tool now uses
`{base_dir}/threads/{thread_id}/acp-workspace/`; falls back to
global workspace when thread_id is absent or invalid
- `_invoke` extracts thread_id from `RunnableConfig` via
`Annotated[RunnableConfig, InjectedToolArg]`
- `sandbox/tools.py`: `_get_acp_workspace_host_path(thread_id)`,
`_resolve_acp_workspace_path(path, thread_id)`, and all callers
(`replace_virtual_paths_in_command`, `mask_local_paths_in_output`,
`ls_tool`, `read_file_tool`) now resolve ACP paths per-thread
P1.2 – ACP permission guardrail
- New `auto_approve_permissions: bool = False` field in `ACPAgentConfig`
- `_build_permission_response(options, *, auto_approve: bool)` now
defaults to deny; only approves when `auto_approve=True`
- Document field in `config.example.yaml`
P2 – Deferred tool registry race condition
- Replace module-level `_registry` global with `contextvars.ContextVar`
- Each asyncio request context gets its own registry; worker threads
inherit the context automatically via `loop.run_in_executor`
- Expose `get_deferred_registry` / `set_deferred_registry` /
`reset_deferred_registry` helpers
Tests: 831 pass (57 for affected modules, 3 new tests)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(sandbox): mount /mnt/acp-workspace in docker sandbox container
The AioSandboxProvider was not mounting the ACP workspace into the
sandbox container, so /mnt/acp-workspace was inaccessible when the lead
agent tried to read ACP results in docker mode.
Changes:
- `ensure_thread_dirs`: also create `acp-workspace/` (chmod 0o777) so
the directory exists before the sandbox container starts — required
for Docker volume mounts
- `_get_thread_mounts`: add read-only `/mnt/acp-workspace` mount using
the per-thread host path (`host_paths.acp_workspace_dir(thread_id)`)
- Update stale CLAUDE.md description (was "fixed global workspace")
Tests: `test_aio_sandbox_provider.py` (4 new tests)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(lint): remove unused imports in test_aio_sandbox_provider
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix config
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-26 14:20:18 +08:00
acp_section = acp_section ,
2026-02-03 13:31:05 +08:00
)
2026-01-16 14:44:51 +08:00
return prompt + f " \n <current_date> { datetime . now ( ) . strftime ( ' % Y- % m- %d , % A ' ) } </current_date> "