Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ wheels/
# Chainlit file storage
.files/
.codex
.langgraph_api/

# Local RAG index
.rag/

# Local test/browser artifacts
.pytest_cache/
.playwright-mcp/
Binary file removed .langgraph_api/.langgraph_checkpoint.1.pckl
Binary file not shown.
Binary file removed .langgraph_api/.langgraph_checkpoint.2.pckl
Binary file not shown.
Binary file removed .langgraph_api/.langgraph_checkpoint.3.pckl
Binary file not shown.
Binary file removed .langgraph_api/.langgraph_ops.pckl
Binary file not shown.
Binary file removed .langgraph_api/.langgraph_retry_counter.pckl
Binary file not shown.
Binary file removed .langgraph_api/store.pckl
Binary file not shown.
Binary file removed .langgraph_api/store.vectors.pckl
Binary file not shown.
72 changes: 41 additions & 31 deletions deepagent.toml
Original file line number Diff line number Diff line change
@@ -1,46 +1,54 @@
#[model]
#provider = "ollama"
#base_url = "http://127.0.0.1:11434"
#temperature = 0.3
#repeat_penalty = 1.1
[model]
provider = "ollama"
base_url = "http://10.0.1.152:11434"
temperature = 0.3
repeat_penalty = 1.1

#name = "gemma4:26b"
name = "qwen3.6:35b-a3b"
#models = ["gpt-oss:20b", "gemma4:26b", "qwen3.6:27b"]

#reasoning_effort = "medium"
reasoning_effort = "medium"

# For LM Studio or another OpenAI-compatible server, switch to:
[model]
provider = "openai_compatible"
base_url = "http://10.0.1.152:1234/v1"
temperature = 0
name = "qwen3.6-35b-a3b-mtp"
reasoning_effort = "medium"
api_key = "optional"
#[model]
#provider = "openai_compatible"
#base_url = "http://10.0.1.152:1234/v1"
#temperature = 0
#name = "qwen3.6-35b-a3b-mtp"
#reasoning_effort = "medium"
#api_key = "optional"



[mcp]
tool_name_prefix = false
stateful = true

[mcp.servers.repo]
#[mcp.servers.repo]
#transport = "stdio"
#command = "npx"
#args = ["-y", "@modelcontextprotocol/server-filesystem@2025.8.21", "."]
#cwd = "."

[mcp.servers.playwright]
transport = "stdio"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem@2025.8.21", "."]
args = ["-y", "@playwright/mcp@latest", "--headless"]
# Set true to run Playwright MCP without a visible browser window.
#headless = true
cwd = "."

[agent]
recursion_limit = 200
skills = ["skills"]
mcp_servers = ["repo"]
mcp_servers = [ "playwright"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep default agent bound to repo MCP server

Switching [agent].mcp_servers to "playwright" while the repo server block is commented out removes the default repo_* tool surface that this project’s documented flows rely on (for example /repo-readme and the repo-researcher path), so a fresh checkout now loses core repository tooling unless users manually reconfigure deepagent.toml. Keep repo enabled by default (or migrate all dependent flows/docs in the same change) to avoid a runtime regression in the default setup.

Useful? React with 👍 / 👎.

summarization_middleware_enabled = true
# Optional token thresholds used when the installed LangChain middleware supports them.
summarization_trigger_tokens = 150000
summarization_keep_tokens = 10000

[rag]
enabled = true
enabled = false
persist_directory = ".rag"
include_globs = ["README.md", "chainlit.md", "prompts/**/*.md", "skills/**/*.md"]
exclude_globs = ["AGENTS.md", "AGENT.md"]
Expand All @@ -55,23 +63,23 @@ provider = "auto"
# base_url = "http://127.0.0.1:11434"

# Async subagent backed by the local LangGraph Agent Protocol server.
[[async_subagents]]
name = "async-researcher"
description = "Runs longer research jobs in the background, such as reading documentation or running code analysis tools."
graph_id = "async-researcher"
url = "http://127.0.0.1:2024"
#[[async_subagents]]
#name = "async-researcher"
#description = "Runs longer research jobs in the background, such as reading documentation or running code analysis tools."
#graph_id = "async-researcher"
#url = "http://127.0.0.1:2024"

# If you run only the co-deployed langgraph.json setup, omit url for ASGI transport.
# In Chainlit, set url here or use CHAINLIT_ASYNC_SUBAGENT_URL for HTTP transport.
#url = "https://researcher-deployment.langsmith.dev"
#headers = { Authorization = "Bearer ${RESEARCHER_TOKEN}" }

[[subagents]]
name = "repo-researcher"
description = "Researches the current repository, explains architecture, and identifies the right files to change."
system_prompt_file = "prompts/repo-researcher.md"
skills = ["skills"]
mcp_servers = ["repo"]
#[[subagents]]
#name = "repo-researcher"
#description = "Researches the current repository, explains architecture, and identifies the right files to change."
#system_prompt_file = "prompts/repo-researcher.md"
#skills = ["skills"]
#mcp_servers = ["repo"]

[chainlit]
# Set false to hide model selection in chat settings and Modes.
Expand All @@ -82,7 +90,9 @@ reasoning_mode_enabled = true
startup_status_enabled = false
chronological_ui_enabled = false
commands = [
{ name = "ask-researcher", description = "Delegate to repo-researcher.", target = "subagent", value = "repo-researcher", template = "{input}" },
{ name = "repo-readme", description = "Run an MCP tool directly.", target = "mcp_tool", value = "repo_read_file", mcp_server = "repo", template = "{\"path\":\"README.md\"}" },
# { name = "ask-researcher", description = "Delegate to repo-researcher.", target = "subagent", value = "repo-researcher", template = "{input}" },

# { name = "repo-readme", description = "Run an MCP tool directly.", target = "mcp_tool", value = "repo_read_file", mcp_server = "repo", template = "{\"path\":\"README.md\"}" },

{ name = "summarize", description = "Apply a prompt template.", target = "prompt", value = "Summarize the input", template = "Summarize this:\n{input}" }
]
132 changes: 124 additions & 8 deletions deepagent_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
DEFAULT_RECURSION_LIMIT = 100
PROJECT_ROOT = Path(__file__).resolve().parent
DEEPAGENT_ARTIFACTS_DIRECTORY = Path(".files/deepagent")
LARGE_TOOL_RESULTS_PREFIX = "/large_tool_results"
AGENTS_MD_FILENAME = "AGENTS.md"
logger = logging.getLogger(__name__)
OPENAI_CHAT_COMPLETIONS_PATH_SUFFIX = "/chat/completions"
Expand Down Expand Up @@ -522,6 +523,98 @@ def deepagent_artifacts_route_prefix(project_root: Path | None = None) -> str:
return f"{deepagent_artifacts_root(project_root).as_posix().rstrip('/')}/"


def large_tool_result_path_to_local(
path_value: str,
project_root: Path | None = None,
) -> str:
"""Convert a large tool result path into a local artifact path.

Args:
path_value: The path value to convert.
project_root: Project root used to resolve local paths.

Returns:
The local artifact path, or the original value when it is not a large
tool result path.
"""
normalized = path_value.strip().replace("\\", "/")
if normalized != LARGE_TOOL_RESULTS_PREFIX and not normalized.startswith(
f"{LARGE_TOOL_RESULTS_PREFIX}/"
):
return path_value

relative = PurePosixPath(normalized.removeprefix("/"))
if ".." in relative.parts:
return path_value

return str((deepagent_artifacts_root(project_root) / Path(*relative.parts)).resolve())


def state_file_text_content(file_data: Any) -> str | None:
"""Return text content from a DeepAgents state file entry.

Args:
file_data: State-backed file data.

Returns:
Text content when the state entry is text-like, otherwise None.
"""
if not isinstance(file_data, dict):
return None

encoding = file_data.get("encoding")
if encoding not in {None, "utf-8"}:
return None

content = file_data.get("content")
if isinstance(content, str):
return content
if isinstance(content, list):
return "\n".join(str(line) for line in content)
return None


def materialize_state_large_tool_result(
path_value: str,
state: Any,
project_root: Path,
) -> str:
"""Materialize a state-backed large tool result into the local artifacts dir.

Args:
path_value: The requested large tool result path.
state: Current agent state, potentially including state-backed files.
project_root: Project root used to resolve local paths.

Returns:
The local artifact path for large tool result paths, otherwise the
original path.
"""
local_path_value = large_tool_result_path_to_local(path_value, project_root)
if local_path_value == path_value:
return path_value

local_path = Path(local_path_value)
local_path.parent.mkdir(parents=True, exist_ok=True)
if local_path.exists():
return local_path_value

if not isinstance(state, dict):
return local_path_value

files = state.get("files")
if not isinstance(files, dict):
return local_path_value

normalized = path_value.strip().replace("\\", "/")
content = state_file_text_content(files.get(normalized))
if content is None:
return local_path_value

local_path.write_text(content, encoding="utf-8")
return local_path_value


def summarize_tool_exception(exc: Exception, *, limit: int = 400) -> str:
"""Summarize tool exception.

Expand Down Expand Up @@ -554,31 +647,49 @@ def summarize_tool_exception(exc: Exception, *, limit: int = 400) -> str:
}


def _map_workspace_tool_path_value(value: Any, project_root: Path) -> Any:
def _map_workspace_tool_path_value(
value: Any,
project_root: Path,
state: Any = None,
) -> Any:
"""Map one virtual workspace path value to a local path.

Args:
value: Value to normalize, convert, or serialize.
project_root: Project root used to resolve local paths.
state: Current agent state used to materialize state-backed artifacts.

Returns:
The mapped value.
"""
if isinstance(value, str):
return virtual_workspace_path_to_local(value, project_root)
return materialize_state_large_tool_result(
virtual_workspace_path_to_local(value, project_root),
state,
project_root,
)
if isinstance(value, list):
return [_map_workspace_tool_path_value(item, project_root) for item in value]
return [
_map_workspace_tool_path_value(item, project_root, state) for item in value
]
if isinstance(value, tuple):
return tuple(_map_workspace_tool_path_value(item, project_root) for item in value)
return tuple(
_map_workspace_tool_path_value(item, project_root, state) for item in value
)
return value


def map_workspace_paths_in_tool_args(args: Any, project_root: Path | None = None) -> Any:
def map_workspace_paths_in_tool_args(
args: Any,
project_root: Path | None = None,
state: Any = None,
) -> Any:
"""Map workspace paths in tool args.

Args:
args: Parsed command-line arguments.
project_root: Project root used to resolve local paths.
state: Current agent state used to materialize state-backed artifacts.

Returns:
The mapped value.
Expand All @@ -590,7 +701,7 @@ def map_workspace_paths_in_tool_args(args: Any, project_root: Path | None = None
mapped = dict(args)
for key, value in args.items():
if str(key).lower() in WORKSPACE_PATH_TOOL_ARG_KEYS:
mapped[key] = _map_workspace_tool_path_value(value, root)
mapped[key] = _map_workspace_tool_path_value(value, root, state)
return mapped


Expand All @@ -612,7 +723,11 @@ def _map_workspace_path_args(self, request: ToolCallRequest) -> None:
request: The request value.
"""
args = request.tool_call.get("args")
mapped_args = map_workspace_paths_in_tool_args(args, self.project_root)
mapped_args = map_workspace_paths_in_tool_args(
args,
self.project_root,
request.state,
)
if mapped_args is not args:
request.tool_call["args"] = mapped_args

Expand Down Expand Up @@ -2805,6 +2920,7 @@ async def get_agent(
)
for subagent in self.config.extensions.async_subagents
)
backend = build_deepagent_backend(project_root=self.project_root)
agent = create_deep_agent(
model=model,
tools=main_tools or None,
Expand All @@ -2817,7 +2933,7 @@ async def get_agent(
rag_enabled=rag_tool_enabled,
),
middleware=middleware,
backend=self._build_backend,
backend=backend,
store=self.store,
checkpointer=self.checkpointer,
skills=list(self.config.extensions.skills) or None,
Expand Down
Loading
Loading