Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
a20a1b6
Only display file adding warnings when more than 50% of the compactio…
Jul 11, 2026
ca5f44a
Programmatic tool calling base implementation
Jul 11, 2026
6b168db
Base 1024 Corpus
Jul 12, 2026
b6f397e
JSON encode all tool calls
Jul 12, 2026
1a6a2c6
Update orchestration prompts and example file format
Jul 12, 2026
3190d81
Raise default sub agent size for PTC delegations
Jul 12, 2026
ddb2e9e
Use prime number base adjacency hash
Jul 12, 2026
16cfd9c
Update Read file to have my contextual position read facilities
Jul 12, 2026
0fcdb7b
Fix bug in generating file context ranges since we need surrounding c…
Jul 12, 2026
0282b67
Update file format section of agent mode system prompts
Jul 12, 2026
6f77950
Tool results are and should be dicts for delegation
Jul 12, 2026
aa8b3d3
Normalize tool results in orchestration tool
Jul 12, 2026
e71e817
Fix tests to account for ToolResponse
Jul 12, 2026
17f76c3
Adjust which modules are lazy vs eagerly imported
Jul 14, 2026
4c73926
Update tests for web browser and numpy import moves
Jul 14, 2026
3940255
Remove numpy dependency
Jul 14, 2026
f45ddde
Revert some changes to io.py
Jul 15, 2026
8087eba
Merge branch 'main' into v1.0.0
Jul 15, 2026
f92688b
Fix start up behavior of pre initialization confirmations
Jul 16, 2026
1354cf5
Translate file strings to hashline ids before normalizing
Jul 16, 2026
eb5e824
Let the LLM triage it's own harness misunderstandings
Jul 16, 2026
f84febe
Numerous updates to support ptc
Jul 17, 2026
27428e1
Resolve agent pain points
Jul 18, 2026
0e144ab
ExploreCode should return strings like other ToolResponses
Jul 18, 2026
1aab5fc
Allow delegation to run inline for orchestration
Jul 18, 2026
00d7cb0
Updates per agent feedback on ease of use
Jul 18, 2026
d7b257e
Update requirements for later versions of ltiellm
Jul 18, 2026
afc2cce
Change get_shape() to peek
Jul 18, 2026
5038ebd
Don't restrict based on adjacency, add notes on state persistence
Jul 18, 2026
f5a4b7d
Genericize _stringify and exempt orchestrate from invocation tracking
Jul 18, 2026
abaa961
Return session for http based servers
Jul 18, 2026
bdd3ad7
Update hashpos to be more conservative on when it identifies a line w…
Jul 19, 2026
0b53c42
Fix edit_file guidance
Jul 19, 2026
5d48272
Make region resolver work with tilde based hashpos
Jul 19, 2026
4aad939
Allow peak to recurse in to classes
Jul 19, 2026
afebc0d
Update quote detection in parser
Jul 19, 2026
dce0e9e
Instead of crashing on statements, replace them with __security_raise…
Jul 19, 2026
8dddb68
Add additional mehods to sandbox
Jul 19, 2026
7bb299a
Add more specific error message including match content for region re…
Jul 19, 2026
07c2a23
Add pathlib wrapper
Jul 19, 2026
9bef725
state.get() can check both local and global stored items
Jul 19, 2026
06e703d
Centralize Agent utilities in orchestrate, allow brackets in nested g…
Jul 19, 2026
37cd1f2
Use ToolResult classes for joing property/attribute access for results
Jul 19, 2026
8ee4955
Allow line numbers to also be used to resolve locations in file
Jul 19, 2026
abb7c74
Give the LLM nicer error messages
Jul 19, 2026
a7e91b6
Use json escaping for safe string construction
Jul 19, 2026
cc34dfc
Simplify edit_text range parsing
Jul 19, 2026
ec1eac2
Allow before/after {second pattern} specification
Jul 19, 2026
c4dab5d
Allow patterns that start with @ to also recieve line hints
Jul 19, 2026
9cefd5d
Unify methods for parsing file reads
Jul 19, 2026
15d5d95
Add clearer error messages to the hashpos system
Jul 19, 2026
38e0b5d
Fix small grep errors
Jul 19, 2026
d42884d
Specify that no other modules can be imported
Jul 19, 2026
4c0ead2
Update orcehstration system prompt
Jul 19, 2026
e810465
Unify command_interactive into a parameter inside the command tool
Jul 19, 2026
5575d33
Cache bust file reads on edit
Jul 20, 2026
fcbbcc3
Revert orchestrate context block to more verbose one, update grep tool
Jul 20, 2026
ddead55
Update hashpos to lead with stable identifiers
Jul 20, 2026
7153f38
Merge branch 'main' into v1.0.0
Jul 21, 2026
98591f5
Allow configuration of the orchestration sandbox to open it up for mo…
Jul 21, 2026
95cfc8b
Fix background command pagination
Jul 21, 2026
00a1a43
Fix tests
Jul 21, 2026
b3fa8a6
Change delegate format_output
Jul 21, 2026
b6b2883
Remov duplicate print
Jul 21, 2026
898d1fa
GitPython is not threadsafe so force it to be
Jul 21, 2026
c18a53f
Update keep alive tests
Jul 21, 2026
45f98c5
Give LLM a measure of control over timeouts so it can work with longe…
Jul 21, 2026
f38818e
Integer cutoff for output limit
Jul 21, 2026
f2fbda8
Patch the most low hanging sandbox escape vectors
Jul 22, 2026
9f297cd
Rewrap sub module exports to prevent improper access, disable match s…
Jul 22, 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
47 changes: 41 additions & 6 deletions cecli/coders/agent_coder.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ def __init__(self, *args, **kwargs):
self.max_tool_vector_history = 20
self.read_tools = {
"command",
"commandinteractive",
"explorecode",
"ls",
"readfile",
Expand All @@ -72,7 +71,6 @@ def __init__(self, *args, **kwargs):
}
self.write_tools = {
"command",
"commandinteractive",
"editfile",
"undochange",
}
Expand All @@ -83,6 +81,7 @@ def __init__(self, *args, **kwargs):
self.change_tracker = ChangeTracker()
self.args = kwargs.get("args")
self.files_added_in_exploration = set()
self.file_read_cache = set()
self.tool_call_count = 0
self.turn_count = 0
self.max_reflections = 15
Expand All @@ -98,7 +97,7 @@ def __init__(self, *args, **kwargs):
self.skip_cli_confirmations = False
self.agent_finished = False
self.agent_config = self._get_agent_config()
self.max_sub_agents = self.agent_config.get("max_sub_agents", 3)
self.max_sub_agents = self.agent_config.get("max_sub_agents", 30)
self.sub_agent_paths = self.agent_config.get("subagent_paths", [])
self._setup_agent()

Expand Down Expand Up @@ -167,6 +166,11 @@ def _get_agent_config(self):
config["hot_reload"] = nested.getter(config, "hot_reload", False)
config["diff_colors"] = nested.getter(config, "diff_colors", True)
config["allow_nested_delegation"] = nested.getter(config, "allow_nested_delegation", False)
config["allow_orchestration"] = nested.getter(config, "allow_orchestration", True)
config["max_sub_agents"] = nested.getter(config, "max_sub_agents", 30)

if config["max_sub_agents"] == -1:
config["max_sub_agents"] = 2 ^ 31 - 1

config["tools_paths"] = nested.getter(config, ["tools_paths", "tool_paths"], [])
config["tools_includelist"] = nested.getter(
Expand All @@ -184,6 +188,7 @@ def _get_agent_config(self):
config["servers_excludelist"] = nested.getter(
config, ["servers_excludelist", "servers_blacklist"], []
)

config["include_context_blocks"] = set(
nested.getter(
config,
Expand All @@ -203,6 +208,10 @@ def _get_agent_config(self):
)
config["exclude_context_blocks"] = set(nested.getter(config, "exclude_context_blocks", []))

if config["allow_orchestration"]:
config["include_context_blocks"].add("orchestration")
config["exclude_context_blocks"].discard("orchestration")

self.large_file_token_threshold = config["large_file_token_threshold"]
self.skip_cli_confirmations = config["skip_cli_confirmations"]
self.hot_reload_enabled = config["hot_reload"]
Expand Down Expand Up @@ -292,6 +301,7 @@ async def initialize_mcp_tools(self):

async def _execute_mcp_tool(self, server, tool_name, params):
"""Helper to execute a single MCP tool call, created from legacy format."""
from cecli.tools.utils.responses import ToolResponse

async def _exec_async():
function_dict = {"name": tool_name, "arguments": json.dumps(params)}
Expand Down Expand Up @@ -350,9 +360,14 @@ async def _exec_async():

result, interrupted = await interruptible(_exec_async(), self.interrupt_event)

response = ToolResponse(tool_name)
if interrupted:
return "Tool execution interrupted by user."
return result
response.append_error("Tool execution interrupted by user.")
elif isinstance(result, str) and result.startswith("Error executing tool call"):
response.append_error(result)
else:
response.append_result(result)
return response

def _calculate_context_block_tokens(self, force=False):
"""
Expand Down Expand Up @@ -383,6 +398,7 @@ def _calculate_context_block_tokens(self, force=False):
"servers",
"sub_agents",
"loaded_skills",
"orchestration",
]
for block_type in block_types:
if block_type in self.allowed_context_blocks:
Expand Down Expand Up @@ -419,6 +435,8 @@ def _generate_context_block(self, block_name):
content = self.get_servers_context()
elif block_name == "loaded_skills":
content = self.get_skills_content()
elif block_name == "orchestration":
content = self.get_orchestration_context()
elif block_name == "sub_agents" and (
not self.parent_uuid or self.agent_config.get("allow_nested_delegation", False)
):
Expand Down Expand Up @@ -897,7 +915,7 @@ async def _execute_mcp_tools(self, server, tool_calls):
{
"role": "tool",
"tool_call_id": tool_call.id,
"content": result,
"content": str(result),
}
)
return tool_responses
Expand Down Expand Up @@ -1601,6 +1619,23 @@ def get_servers_context(self):
self.io.tool_error(f"Error generating servers context: {str(e)}")
return None

def get_orchestration_context(self):
"""
Generate a context block for the Orchestrate tool if allowed.

Only returns content if ``allow_orchestration`` is enabled in the agent config.
"""
if not self.use_enhanced_context:
return None

try:
from cecli.helpers.orchestration import build_orchestration_context_block

return build_orchestration_context_block(self.agent_config)
except Exception as e:
self.io.tool_error(f"Error generating orchestration context: {str(e)}")
return None

def get_sub_agents_context(self):
"""
Generate a context block for registered sub-agents.
Expand Down
23 changes: 14 additions & 9 deletions cecli/coders/base_coder.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
remove_reasoning_content,
replace_reasoning_tags,
)
from cecli.repo import ANY_GIT_ERROR, GitRepo
from cecli.repo import ANY_GIT_ERROR, GitRepo, GitRepoProxy
from cecli.repomap import RepoMap
from cecli.report import update_error_prefix
from cecli.run_cmd import run_cmd_async
Expand Down Expand Up @@ -571,11 +571,13 @@ def __init__(
self.repo = repo
if use_git and self.repo is None:
try:
self.repo = GitRepo(
self.io,
fnames,
None,
models=main_model.commit_message_models(),
self.repo = GitRepoProxy(
GitRepo(
self.io,
fnames,
None,
models=main_model.commit_message_models(),
)
)
except FileNotFoundError:
pass
Expand Down Expand Up @@ -4266,9 +4268,12 @@ def check_added_files(self):
if tokens < warn_number_of_tokens:
return

self.io.tool_warning("Warning: it's best to only add files that need changes to the chat.")
self.io.tool_warning(urls.edit_errors)
self.warning_given = True
if self.context_compaction_current_ratio > 0.5:
self.io.tool_warning(
"Warning: it's best to only add files that need changes to the chat."
)
self.io.tool_warning(urls.edit_errors)
self.warning_given = True

async def prepare_to_edit(self, edits):
res = []
Expand Down
4 changes: 3 additions & 1 deletion cecli/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
BaseCommand pattern for modular, testable command execution.
"""

from cecli.signals import ReloadProgramSignal, SwitchCoderSignal

from .add import AddCommand
from .agent import AgentCommand
from .agent_model import AgentModelCommand
Expand All @@ -21,7 +23,7 @@
from .context_management import ContextManagementCommand
from .copy import CopyCommand
from .copy_context import CopyContextCommand
from .core import Commands, ReloadProgramSignal, SwitchCoderSignal
from .core import Commands
from .diff import DiffCommand
from .drop import DropCommand
from .editor import EditCommand, EditorCommand
Expand Down
46 changes: 3 additions & 43 deletions cecli/commands/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,49 +8,7 @@
from cecli.helpers import nested, plugin_manager
from cecli.helpers.file_searcher import handle_core_files
from cecli.helpers.threading import ThreadSafeEvent
from cecli.repo import ANY_GIT_ERROR


class SwitchCoderSignal(BaseException):
"""
Signal to switch the current Coder instance to a new configuration.

This is NOT an error - it's a control flow signal used to propagate
coder switching requests up through the async call stack. It carries
the kwargs needed to create a new Coder instance.

Note: Inherits from BaseException (like KeyboardInterrupt and SystemExit)
to avoid being caught by generic `except Exception` handlers, making the
non-error nature of this signal explicit.

Attributes:
kwargs: Configuration dict passed to Coder.create() for the new instance
placeholder: Optional placeholder text for the input prompt
"""

def __init__(self, placeholder=None, **kwargs):
self.kwargs = kwargs
self.placeholder = placeholder
super().__init__()


class ReloadProgramSignal(BaseException):
"""
Signal to reload the entire program configuration.

This is NOT an error - it's a control flow signal used to trigger
a full program reload, re-parsing config files and re-initializing
all components. Useful for hot-reloading when configuration files
change.

Note: Inherits from BaseException (like KeyboardInterrupt and SystemExit)
to avoid being caught by generic `except Exception` handlers.
"""

def __init__(self, message="Reloading program configuration...", **kwargs):
self.kwargs = kwargs
self.message = message
super().__init__(self.message)
from cecli.signals import SwitchCoderSignal


class Commands:
Expand Down Expand Up @@ -215,6 +173,8 @@ def get_commands(self):
return sorted(commands)

async def execute(self, cmd_name, args, coder=None, **kwargs):
from cecli.repo import ANY_GIT_ERROR

active_coder = coder or self.coder
command_class = CommandRegistry.get_command(cmd_name)

Expand Down
2 changes: 1 addition & 1 deletion cecli/commands/hot_reload.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from typing import List

from cecli.commands.core import ReloadProgramSignal
from cecli.commands.utils.base_command import BaseCommand
from cecli.signals import ReloadProgramSignal


class HotReloadCommand(BaseCommand):
Expand Down
15 changes: 11 additions & 4 deletions cecli/commands/undo.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,12 @@ async def _raw_cmd_undo(cls, io, coder, args):
)
return format_command_result(io, "undo", "Commit has multiple parents")

prev_commit = last_commit.parents[0]
changed_files_last_commit = [item.a_path for item in last_commit.diff(prev_commit)]
prev_sha = last_commit.parents[0]
# Get list of files changed between parent and current commit
diff_output = coder.repo.repo.git.diff("--name-only", prev_sha, last_commit.hexsha)
changed_files_last_commit = (
[f for f in diff_output.splitlines() if f] if diff_output else []
)

for fname in changed_files_last_commit:
if coder.repo.repo.is_dirty(path=fname):
Expand All @@ -64,9 +68,12 @@ async def _raw_cmd_undo(cls, io, coder, args):
return format_command_result(io, "undo", f"File {fname} has uncommitted changes")

# Check if the file was in the repo in the previous commit
# git ls-tree returns empty string if the file does not exist in that commit
try:
prev_commit.tree[fname]
except KeyError:
tree_output = coder.repo.repo.git.ls_tree(prev_sha, "--", fname)
if not tree_output:
raise KeyError
except (KeyError, ANY_GIT_ERROR):
io.tool_error(
f"The file {fname} was not in the repository in the previous commit. Cannot"
" undo safely."
Expand Down
6 changes: 3 additions & 3 deletions cecli/helpers/agents/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ def __init__(self, coder) -> None:
@property
def max_sub_agents(self) -> int:
"""Return the max number of sub-agents allowed for this coder."""
return getattr(self.coder, "max_sub_agents", 3)
return getattr(self.coder, "max_sub_agents", 30)

# ------------------------------------------------------------------ #
# Internal helpers
Expand Down Expand Up @@ -495,7 +495,7 @@ async def _create_sub_agent_coder(
)

new_coder = await Coder.create(**kwargs)
new_coder.max_sub_agents = getattr(self.coder, "max_sub_agents", 3)
new_coder.max_sub_agents = getattr(self.coder, "max_sub_agents", 30)
# IOProxy wrapping is handled by base_coder.py's Coder.__init__

# Re-acquire the lock to register — we must re-check max agents since
Expand Down Expand Up @@ -575,7 +575,7 @@ def start_generate_task(self, info: SubAgentInfo, user_message: str) -> asyncio.

Args:

.. note::
.. note:

**Ordering dependency with mark_sub_agent_finished()**

Expand Down
4 changes: 3 additions & 1 deletion cecli/helpers/background_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -767,14 +767,16 @@ def save_paginated_output(
Returns:
Tuple of (folder path, rel file paths list, alias paths list), or None if output fits in one page.
"""
import math

if not output or len(output) <= page_size:
return None

folder_path = local_agent_folder_func(command_key)
abs_folder = abs_root_path_func(folder_path)
os.makedirs(abs_folder, exist_ok=True)

num_pages = (len(output) + page_size - 1) // page_size
num_pages = math.ceil((len(output) + page_size - 1) // page_size)

for i in range(num_pages):
page_num = i + 1
Expand Down
Loading
Loading