Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b9332e0
Bump Version
dwash96 Aug 24, 2026
a4db2d8
#657: Memorizer should only load the local server
dwash96 Aug 25, 2026
77c3553
Fix auto-update script to bypass daily version check
dwash96 Aug 25, 2026
f936226
#657: Refactor MCP server loop session handling to stay pinned to the…
dwash96 Aug 28, 2026
94b951e
Keep sub agents from crashing system in cli mode by letting them operate
dwash96 Aug 28, 2026
c34cd04
Add --max-tool-calls argument
dwash96 Aug 28, 2026
42f3ace
Update file edit structure
dwash96 Aug 28, 2026
e6ad527
#660: Catch timeout errors outside of git repo, add max scan time tha…
dwash96 Aug 28, 2026
b52f1a0
Add chromadb as a main dependency so interactive help can just work
dwash96 Aug 29, 2026
fec3d0e
Strip first 8 leading chars to prevent corruption
dwash96 Aug 30, 2026
db05efd
Workspaces As Subagents
dwash96 Aug 30, 2026
0a3d25e
Add `/open` command to open a new workspace subagent to local folder
dwash96 Aug 30, 2026
7ae33bd
Only await dependent sub agents
dwash96 Aug 30, 2026
b8d3080
Add "wait" parameter to yield tool
dwash96 Aug 30, 2026
b9c716a
Add powershell support for Grep tool
Aug 30, 2026
418c93a
Merge branch 'v1.3.2' of https://github.com/dwash96/cecli into v1.3.2
dwash96 Aug 30, 2026
c017df9
Update Orchestrate tool call guidelines
dwash96 Aug 30, 2026
a905416
feat(gemini): authenticate using x-goog-api-key header
DinoChiesa Aug 30, 2026
96aae65
Merge pull request #661 from DinoChiesa/gemini-x-goog-api-key
dwash96 Aug 31, 2026
0ae5a81
Fix `--yes-always-commands`
dwash96 Aug 31, 2026
2f68df9
Add `BroadCast` tool to enable active, bi-directional inter-agent com…
dwash96 Aug 31, 2026
a865d2b
Fix formatting
dwash96 Aug 31, 2026
4ddcc84
Fix git repo tests on windows
Aug 31, 2026
e2f9c56
Fix formatting
dwash96 Aug 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cecli/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from packaging import version

__version__ = "1.3.1.dev"
__version__ = "1.3.2.dev"
safe_version = __version__

try:
Expand Down
6 changes: 6 additions & 0 deletions cecli/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,12 @@ def get_parser(default_config_files, git_root):
default=3,
help="Maximum number of retries a model gets on malformed outputs (default: 3)",
)
group.add_argument(
"--max-tool-calls",
type=int,
default=25,
help="Maximum number of tool calls allowed per message (default: 25)",
)
group.add_argument(
"--cost-limit",
type=float,
Expand Down
152 changes: 112 additions & 40 deletions cecli/coders/agent_coder.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,27 @@ def __init__(self, *args, **kwargs):
self.sub_agent_paths = self.agent_config.get("subagent_paths", [])
self._setup_agent()

AgentService.build_registry(self.sub_agent_paths)
ToolRegistry.build_registry(agent_config=self.agent_config)
# primary_root is needed early so the skills and tool registries can
# resolve relative paths against the primary workspace root rather than
# this coder's (possibly overridden) local root. base_coder re-affirms
# and keeps this value in super().__init__().
self.primary_root = kwargs.get("primary_root") or getattr(self, "primary_root", None)

# Allow a sub-agent to target a different working root (multi-project workspace).
if not kwargs.get("root"):
configured_root = self.agent_config.get("root")
if configured_root:
kwargs["root"] = configured_root

self._initialize_skills_manager(
self.agent_config, root=kwargs.get("root", self.primary_root)
)
AgentService.build_registry(
self.sub_agent_paths, root=kwargs.get("root", self.primary_root)
)
ToolRegistry.build_registry(
agent_config=self.agent_config, root=kwargs.get("root", self.primary_root)
)

self.loaded_custom_tools = ToolRegistry.loaded_custom_tools
super().__init__(*args, **kwargs)
Expand All @@ -133,6 +152,8 @@ def post_init(self):
self.io.tool_warning(err)

self.start_up_errors = []
# Preserve the original 10000 setting now that max tool calls is configurable
self.max_tool_calls = self.max_tool_calls * 400

def _setup_agent(self):
os.makedirs(".cecli/temp", exist_ok=True)
Expand Down Expand Up @@ -290,21 +311,19 @@ def _get_agent_config(self, config=None):
config["tools_excludelist"].append("loadskill")
config["tools_excludelist"].append("removeskill")

self._initialize_skills_manager(config)
return config

def _initialize_skills_manager(self, config):
def _initialize_skills_manager(self, config, root=None):
"""
Initialize the skills manager with the configured directory paths and filters.
"""
try:
git_root = str(self.repo.root) if self.repo else None
self.skills_manager = SkillsManager(
directory_paths=config.get("skills_paths", []),
include_list=config.get("skills_includelist", []),
exclude_list=config.get("skills_excludelist", []),
initialize_list=config.get("skills_init", []),
git_root=git_root,
root=root or self.root or self.primary_root,
coder=self,
)
except Exception as e:
Expand Down Expand Up @@ -731,7 +750,8 @@ def get_context_summary(self):
def get_environment_info(self):
"""
Generate an environment information context block with key system details.
Returns formatted string with working directory, platform, date, and other relevant environment details.
Returns formatted string with working directory, platform, date, and other relevant environment details,
including the agent's own identity and the primary agent's UUID.
"""
if not self.use_enhanced_context:
return None
Expand All @@ -745,6 +765,17 @@ def get_environment_info(self):
result += f"- Current date: {current_date}\n"
result += f"- Platform: {platform_info}\n"
result += f"- Language preference: {language}\n"

# Agent identity so the model can recognise itself and the primary
# agent. Kept here (a static context block) rather than in the
# sub-agent states block to avoid re-injecting an agent's own
# identity whenever sub-agent state changes mid-conversation.
service = AgentService.get_instance(self)
self_uuid = str(self.uuid)
primary_uuid = str(service.coder.uuid)
self_name = service.get_agent_name(self) or "primary"
result += f"- Agent: {self_name} ({self_uuid}) [self]\n"
result += f"- Primary agent: primary ({primary_uuid})\n"
if self.repo:
try:
rel_repo_dir = self.repo.get_rel_repo_dir()
Expand Down Expand Up @@ -1221,15 +1252,6 @@ def _generate_tool_context(self, repetitive_tools):
for i, tool in enumerate(recent_history, 1):
context_parts.append(f"{i}. {tool}")

if not self.edit_allowed:
context_parts.append("\n\n")
context_parts.append("## File Editing Tools Disabled")
context_parts.append(
"File editing tools are currently disabled. Use `ReadFile` to determine the"
" current content ID prefixes needed to perform an edit and activate them when"
" you are ready to edit a file."
)

context_parts.append("\n\n")
repetition_warning = None

Expand Down Expand Up @@ -1706,7 +1728,7 @@ def get_sub_agents_context(self):

result = '<context name="sub_agents" from="agent">\n'
result += "## Available Sub-Agents\n\n"
result += f"Found {len(registry)} registered sub-agent(s):\n\n"
result += f"Found {len(registry)} registered sub-agent(s) types:\n\n"

for name, config in sorted(registry.items()):
result += f"**{name}**:\n"
Expand All @@ -1715,57 +1737,107 @@ def get_sub_agents_context(self):
result += f"{desc}\n"
result += "\n"

result += "Use the `Delegate` tool with the sub-agent name to delegate tasks.\n"
result += "Use the `Yield` tool to wait for responses for all active sub agents.\n"
result += "Use the `Delegate` tool with the sub-agent type to delegate tasks.\n"
result += "Use the `Yield` tool to wait for responses for all child sub agents.\n"
result += "</context>"
return result
except Exception as e:
self.io.tool_error(f"Error generating sub-agents context: {str(e)}")
return None

def get_child_agent_states(self):
"""Get the state of all active child sub-agents.
def get_sub_agent_states(self):
"""Get the state of all active sub-agents.

Returns a formatted context block listing each active sub-agent as
``{name} ({uuid}, {status})`` bullets, for both dependent children and
independent sub-agents. The independent ones are those reachable via
the ``Broadcast`` tool.

Finished/errored sub-agents are excluded. Returns None if there are no
active sub-agents, if enhanced context is disabled, or if the caller is
a sub-agent without nested delegation enabled.

The primary agent and calling agent's own identity are deliberately not
included here — they live in ``get_environment_info()`` (a static block)
so they don't re-inject and churn the conversation when sub-agent state
changes.

Returns a formatted context block with each child sub-agent's name,
UUID, and current status, or None if no children exist.
This is used by ConversationChunks.add_sub_agent_states() to provide
the model with visibility into active sub-agent states.
"""
if not self.use_enhanced_context:
return None

# Sub-agents should only see child states when nested delegation is enabled
# Sub-agents should only see sub-agent states when nested delegation is enabled
if hasattr(self, "parent_uuid") and self.parent_uuid:
if not self.agent_config.get("allow_nested_delegation", False):
return None

try:
service = AgentService.get_instance(self)

from cecli.helpers.agents.service import SubAgentStatus

originator_uuid = str(self.uuid)

# Dependent children are the coder's direct children.
children = service.get_children(self)
if not children:
return None
dependent_children = [
info
for info in children
if not info.independent
and info.status not in (SubAgentStatus.FINISHED, SubAgentStatus.ERROR)
]

# Filter to non-independent children only
dependent_children = [info for info in children if not info.independent]
# Independent agents aren't all direct children, so iterate over
# every active sub-agent reachable via the Broadcast tool and dedupe
# against the dependent children already captured above.
dependent_uuids = {info.coder.uuid for info in dependent_children}
independent_agents = []
seen = set()
for info in service.sub_agents.values():
if (
info.independent
and str(info.coder.uuid) != originator_uuid
and info.status not in (SubAgentStatus.ERROR,)
and info.coder.uuid not in dependent_uuids
and info.coder.uuid not in seen
):
seen.add(info.coder.uuid)
independent_agents.append(info)

if not dependent_children:
if not dependent_children and not independent_agents:
return None

result = '<context name="sub_agent_states" from="agent">\n'
result += "## Active Sub-Agent States\n\n"
result += f"Found {len(dependent_children)} active child sub-agent(s):\n\n"

for info in dependent_children:
result += f"**{info.name}**:\n"
result += f" - UUID: `{info.coder.uuid}`\n"
result += f" - Status: {info.status.value}\n"
if info.error:
result += f" - Error: {info.error}\n"
result += "\n"
result += "## Active Sub-Agent Instances\n\n"
result += f"Found {len(dependent_children) + len(independent_agents)} active sub-agent(s):\n\n"

if dependent_children:
for info in dependent_children:
line = f"- {info.name} ({info.coder.uuid}, {info.status.value})"
if info.error:
line += f" - Error: {info.error}"
result += line + "\n"

if independent_agents:
result += "## Independent Sub-Agent Instances\n"
result += (
"Communicate bi-directionally with these sub-agents using the "
"`Broadcast` tool:\n\n"
)

for info in independent_agents:
line = f"- {info.name} ({info.coder.uuid}, {info.status.value})"
if info.error:
line += f" - Error: {info.error}"
result += line + "\n"

result += "</context>"
return result

except Exception as e:
self.io.tool_error(f"Error generating child agent states: {str(e)}")
self.io.tool_error(f"Error generating sub-agent states: {str(e)}")
return None

def get_background_command_output(self):
Expand Down
Loading
Loading