Compare commits

...
Author SHA1 Message Date
ethernet 7acd6c902c fix(types): fix ty python env resolution + triage discord adapter
The .venv directory (created by uv run) contained Python 3.13 with no
deps installed. ty auto-discovers .venv for module resolution, so it
could not find discord.py, aiohttp, etc., producing ~1000 false
"Module has no member" errors.

Removing the stray .venv makes ty fall back to the nix env (Python 3.12
with all deps installed). Also set python-version = "3.12" in
[tool.ty.environment] with a comment explaining why.

ty diagnostics: 4,422 -> 3,385 (-1,037)
Tests: 496 passed, 0 failed

fix(nix): use python311 in dev shell (matches requires-python floor)

Production venv still uses python312 (nixos-unstable default). The dev
editable venv now uses python311 — the requires-python floor (>=3.11) —
so ty type checking and local dev catch 3.12+-only syntax that wouldnt
2026-07-17 15:35:38 -04:00
ethernet 0cb42c10a4 fix(types): declare AIAgent instance attributes as class-level annotations
init_agent() in agent/agent_init.py sets ~176 instance attributes on the
AIAgent instance, but ty cannot track cross-module attribute assignment
through a function that receives the instance as a plain `agent` parameter.
This caused 186 unresolved-attribute errors in run_agent.py alone.

Declaring the attributes as class-level annotations (PEP 526) tells ty
they exist, clearing 182 of 190 errors (190→8). The remaining 8 are:
- 2 duck-typed object.function accesses (hasattr-guarded, safe)
- 2 missing attrs (_current_tool, _api_call_count — added)
- 2 None-narrowing on iteration_budget (.used, .max_total)
- 2 None-narrowing on client (.close)

Overall ty diagnostics: 4,648 → 4,422 (−226)
Tests: 496 passed, 0 failed
2026-07-17 15:05:55 -04:00
ethernet 169bfe20e4 fix(types): sweep invalid-parameter-default across core modules
Add `| None` to ~250 params across 30 files that were annotated as bare
`str`, `list`, `dict`, `int`, `Callable`, etc. but defaulted to None.

This is a big typechecking fix. each one cascades, making ty stop
narrowing those vars to None and clearing downstream
unresolved-attribute / not-subscriptable / invalid-argument-type errors.

Two type bugs surfaced and fixed during the sweep:

1. Lowercase `callable` (builtin function) used as type annotation in
   28 callback params in agent_init.py + run_agent.py. `callable | None`
   isn't valid. Fixed to `Callable` (typing).

2. String forward-ref with `| None` (`"IterationBudget" | None`) is
    wrong because `|` can't OR a str with NoneType. Fixed to
   `Optional["IterationBudget"]`.

Also:
- Configure ty to exclude tests/ via [tool.ty.src] in pyproject.toml
  (tests are ~57% of diagnostics, lowest-value typing target)

ty diagnostics: 13,290 -> 4,648 (core only, tests excluded)
Tests: 496 passed, 0 failed
2026-07-17 15:04:17 -04:00
34 changed files with 861 additions and 332 deletions
+46 -46
View File
@@ -275,71 +275,71 @@ def _merge_custom_provider_extra_body(agent, custom_providers: List[Dict[str, An
def init_agent(
agent,
base_url: str = None,
api_key: str = None,
provider: str = None,
api_mode: str = None,
acp_command: str = None,
base_url: str | None = None,
api_key: str | None = None,
provider: str | None = None,
api_mode: str | None = None,
acp_command: str | None = None,
acp_args: list[str] | None = None,
command: str = None,
command: str | None = None,
args: list[str] | None = None,
model: str = "",
max_iterations: int = 90, # Default tool-calling iterations (shared with subagents)
tool_delay: float = 1.0,
enabled_toolsets: List[str] = None,
disabled_toolsets: List[str] = None,
enabled_toolsets: List[str] | None = None,
disabled_toolsets: List[str] | None = None,
save_trajectories: bool = False,
verbose_logging: bool = False,
quiet_mode: bool = False,
tool_progress_mode: str = "all",
ephemeral_system_prompt: str = None,
ephemeral_system_prompt: str | None = None,
log_prefix_chars: int = 100,
log_prefix: str = "",
providers_allowed: List[str] = None,
providers_ignored: List[str] = None,
providers_order: List[str] = None,
provider_sort: str = None,
providers_allowed: List[str] | None = None,
providers_ignored: List[str] | None = None,
providers_order: List[str] | None = None,
provider_sort: str | None = None,
provider_require_parameters: bool = False,
provider_data_collection: str = None,
provider_data_collection: str | None = None,
openrouter_min_coding_score: Optional[float] = None,
session_id: str = None,
tool_progress_callback: callable = None,
tool_start_callback: callable = None,
tool_complete_callback: callable = None,
thinking_callback: callable = None,
reasoning_callback: callable = None,
clarify_callback: callable = None,
read_terminal_callback: callable = None,
step_callback: callable = None,
stream_delta_callback: callable = None,
interim_assistant_callback: callable = None,
tool_gen_callback: callable = None,
status_callback: callable = None,
notice_callback: callable = None,
notice_clear_callback: callable = None,
session_id: str | None = None,
tool_progress_callback: Callable | None = None,
tool_start_callback: Callable | None = None,
tool_complete_callback: Callable | None = None,
thinking_callback: Callable | None = None,
reasoning_callback: Callable | None = None,
clarify_callback: Callable | None = None,
read_terminal_callback: Callable | None = None,
step_callback: Callable | None = None,
stream_delta_callback: Callable | None = None,
interim_assistant_callback: Callable | None = None,
tool_gen_callback: Callable | None = None,
status_callback: Callable | None = None,
notice_callback: Callable | None = None,
notice_clear_callback: Callable | None = None,
event_callback: Optional[Callable[[str, dict], None]] = None,
reaction_callback: Optional[Callable[[str], None]] = None,
max_tokens: int = None,
reasoning_config: Dict[str, Any] = None,
service_tier: str = None,
request_overrides: Dict[str, Any] = None,
prefill_messages: List[Dict[str, Any]] = None,
platform: str = None,
user_id: str = None,
user_id_alt: str = None,
user_name: str = None,
chat_id: str = None,
chat_name: str = None,
chat_type: str = None,
thread_id: str = None,
gateway_session_key: str = None,
max_tokens: int | None = None,
reasoning_config: Dict[str, Any] | None = None,
service_tier: str | None = None,
request_overrides: Dict[str, Any] | None = None,
prefill_messages: List[Dict[str, Any]] | None = None,
platform: str | None = None,
user_id: str | None = None,
user_id_alt: str | None = None,
user_name: str | None = None,
chat_id: str | None = None,
chat_name: str | None = None,
chat_type: str | None = None,
thread_id: str | None = None,
gateway_session_key: str | None = None,
skip_context_files: bool = False,
load_soul_identity: bool = False,
skip_memory: bool = False,
session_db=None,
parent_session_id: str = None,
iteration_budget: "IterationBudget" = None,
fallback_model: Dict[str, Any] = None,
parent_session_id: str | None = None,
iteration_budget: Optional["IterationBudget"] = None,
fallback_model: Dict[str, Any] | None = None,
credential_pool=None,
checkpoints_enabled: bool = False,
checkpoint_max_snapshots: int = 20,
+1 -1
View File
@@ -246,7 +246,7 @@ def sanitize_tool_call_arguments(
messages: list,
*,
logger=None,
session_id: str = None,
session_id: str | None = None,
) -> int:
"""Repair corrupted assistant tool-call argument JSON in-place."""
log = logger or logging.getLogger(__name__)
+4 -4
View File
@@ -633,8 +633,8 @@ def _common_betas_for_base_url(
def _build_anthropic_client_with_bearer_hook(
token_provider,
base_url: str = None,
timeout: float = None,
base_url: str | None = None,
timeout: float | None = None,
*,
drop_context_1m_beta: bool = False,
):
@@ -709,8 +709,8 @@ def _build_anthropic_client_with_bearer_hook(
def build_anthropic_client(
api_key,
base_url: str = None,
timeout: float = None,
base_url: str | None = None,
timeout: float | None = None,
*,
drop_context_1m_beta: bool = False,
):
+35 -35
View File
@@ -3972,7 +3972,7 @@ async def _call_fallback_candidate_async(
def _try_payment_fallback(
failed_provider: str,
task: str = None,
task: str | None = None,
reason: str = "payment error",
) -> Tuple[Optional[Any], Optional[str], str]:
"""Try alternative providers after a payment/credit or connection error.
@@ -4023,7 +4023,7 @@ def _try_payment_fallback(
def _try_main_agent_model_fallback(
failed_provider: str,
task: str = None,
task: str | None = None,
reason: str = "error",
) -> Tuple[Optional[Any], Optional[str], str]:
"""Last-resort fallback to the user's main agent provider + model.
@@ -4665,12 +4665,12 @@ def _normalize_resolved_model(model_name: Optional[str], provider: str) -> Optio
def resolve_provider_client(
provider: str,
model: str = None,
model: str | None = None,
async_mode: bool = False,
raw_codex: bool = False,
explicit_base_url: str = None,
explicit_api_key: str = None,
api_mode: str = None,
explicit_base_url: str | None = None,
explicit_api_key: str | None = None,
api_mode: str | None = None,
main_runtime: Optional[Dict[str, Any]] = None,
is_vision: bool = False,
task: Optional[str] = None,
@@ -6086,11 +6086,11 @@ def _compat_model(client: Any, model: Optional[str], cached_default: Optional[st
def _get_cached_client(
provider: str,
model: str = None,
model: str | None = None,
async_mode: bool = False,
base_url: str = None,
api_key: str = None,
api_mode: str = None,
base_url: str | None = None,
api_key: str | None = None,
api_mode: str | None = None,
main_runtime: Optional[Dict[str, Any]] = None,
is_vision: bool = False,
task: Optional[str] = None,
@@ -6222,11 +6222,11 @@ _AUX_DIRECT_API_BASE_URLS: Dict[str, str] = {
def _resolve_task_provider_model(
task: str = None,
provider: str = None,
model: str = None,
base_url: str = None,
api_key: str = None,
task: str | None = None,
provider: str | None = None,
model: str | None = None,
base_url: str | None = None,
api_key: str | None = None,
) -> Tuple[str, Optional[str], Optional[str], Optional[str], Optional[str]]:
"""Determine provider + model for a call.
@@ -6900,23 +6900,23 @@ def _obj_get(obj: Any, key: str, default: Any = None) -> Any:
def call_llm(
task: str = None,
task: str | None = None,
*,
provider: str = None,
model: str = None,
base_url: str = None,
api_key: str = None,
provider: str | None = None,
model: str | None = None,
base_url: str | None = None,
api_key: str | None = None,
main_runtime: Optional[Dict[str, Any]] = None,
messages: list,
temperature: Optional[float] = None,
max_tokens: int = None,
tools: list = None,
timeout: float = None,
extra_body: dict = None,
max_tokens: int | None = None,
tools: list | None = None,
timeout: float | None = None,
extra_body: dict | None = None,
reasoning_config: Optional[dict] = None,
api_mode: str = None,
api_mode: str | None = None,
stream: bool = False,
stream_options: dict = None,
stream_options: dict | None = None,
) -> Any:
"""Centralized synchronous LLM call.
@@ -7567,19 +7567,19 @@ def extract_content_or_reasoning(response) -> str:
async def async_call_llm(
task: str = None,
task: str | None = None,
*,
provider: str = None,
model: str = None,
base_url: str = None,
api_key: str = None,
provider: str | None = None,
model: str | None = None,
base_url: str | None = None,
api_key: str | None = None,
main_runtime: Optional[Dict[str, Any]] = None,
messages: list,
temperature: Optional[float] = None,
max_tokens: int = None,
tools: list = None,
timeout: float = None,
extra_body: dict = None,
max_tokens: int | None = None,
tools: list | None = None,
timeout: float | None = None,
extra_body: dict | None = None,
reasoning_config: Optional[dict] = None,
) -> Any:
"""Centralized asynchronous LLM call.
+24 -24
View File
@@ -536,22 +536,22 @@ class BatchRunner:
run_name: str,
distribution: str = "default",
max_iterations: int = 10,
base_url: str = None,
api_key: str = None,
base_url: str | None = None,
api_key: str | None = None,
model: str = "claude-opus-4-20250514",
num_workers: int = 4,
verbose: bool = False,
ephemeral_system_prompt: str = None,
ephemeral_system_prompt: str | None = None,
log_prefix_chars: int = 100,
providers_allowed: List[str] = None,
providers_ignored: List[str] = None,
providers_order: List[str] = None,
provider_sort: str = None,
providers_allowed: List[str] | None = None,
providers_ignored: List[str] | None = None,
providers_order: List[str] | None = None,
provider_sort: str | None = None,
openrouter_min_coding_score: Optional[float] = None,
max_tokens: int = None,
reasoning_config: Dict[str, Any] = None,
prefill_messages: List[Dict[str, Any]] = None,
max_samples: int = None,
max_tokens: int | None = None,
reasoning_config: Dict[str, Any] | None = None,
prefill_messages: List[Dict[str, Any]] | None = None,
max_samples: int | None = None,
):
"""
Initialize the batch runner.
@@ -1145,29 +1145,29 @@ class BatchRunner:
def main(
dataset_file: str = None,
batch_size: int = None,
run_name: str = None,
dataset_file: str | None = None,
batch_size: int | None = None,
run_name: str | None = None,
distribution: str = "default",
model: str = "anthropic/claude-sonnet-4.6",
api_key: str = None,
api_key: str | None = None,
base_url: str = "https://openrouter.ai/api/v1",
max_turns: int = 10,
num_workers: int = 4,
resume: bool = False,
verbose: bool = False,
list_distributions: bool = False,
ephemeral_system_prompt: str = None,
ephemeral_system_prompt: str | None = None,
log_prefix_chars: int = 100,
providers_allowed: str = None,
providers_ignored: str = None,
providers_order: str = None,
provider_sort: str = None,
max_tokens: int = None,
reasoning_effort: str = None,
providers_allowed: str | None = None,
providers_ignored: str | None = None,
providers_order: str | None = None,
provider_sort: str | None = None,
max_tokens: int | None = None,
reasoning_effort: str | None = None,
reasoning_disabled: bool = False,
prefill_messages_file: str = None,
max_samples: int = None,
prefill_messages_file: str | None = None,
max_samples: int | None = None,
):
"""
Run batch processing of agent prompts from a dataset.
+18 -18
View File
@@ -3708,15 +3708,15 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin):
def __init__(
self,
model: str = None,
toolsets: List[str] = None,
provider: str = None,
api_key: str = None,
base_url: str = None,
max_turns: int = None,
model: str | None = None,
toolsets: List[str] | None = None,
provider: str | None = None,
api_key: str | None = None,
base_url: str | None = None,
max_turns: int | None = None,
verbose: Optional[bool] = None,
compact: bool = False,
resume: str = None,
resume: str | None = None,
checkpoints: bool = False,
pass_session_id: bool = False,
ignore_rules: bool = False,
@@ -15999,23 +15999,23 @@ def _run_kanban_goal_loop_q(cli: "HermesCLI", first_response: str) -> None:
def main(
query: str = None,
q: str = None,
image: str = None,
toolsets: str = None,
skills: str | list[str] | tuple[str, ...] = None,
model: str = None,
provider: str = None,
api_key: str = None,
base_url: str = None,
max_turns: int = None,
query: str | None = None,
q: str | None = None,
image: str | None = None,
toolsets: str | None = None,
skills: str | list[str] | tuple[str, ...] | None = None,
model: str | None = None,
provider: str | None = None,
api_key: str | None = None,
base_url: str | None = None,
max_turns: int | None = None,
verbose: Optional[bool] = None,
quiet: bool = False,
compact: bool = False,
list_tools: bool = False,
list_toolsets: bool = False,
gateway: bool = False,
resume: str = None,
resume: str | None = None,
worktree: bool = False,
w: bool = False,
checkpoints: bool = False,
+2 -2
View File
@@ -2891,8 +2891,8 @@ class APIServerAdapter(BasePlatformAdapter):
async def _write_sse_chat_completion(
self, request: "web.Request", completion_id: str, model: str,
created: int, stream_q, agent_task, agent_ref=None, session_id: str = None,
gateway_session_key: str = None,
created: int, stream_q, agent_task, agent_ref | None = None, session_id: str = None,
gateway_session_key: str | None = None,
) -> "web.StreamResponse":
"""Write real streaming SSE from agent's stream_delta_callback queue.
+5 -5
View File
@@ -219,7 +219,7 @@ def _gateway_platform_value(platform: Any) -> str:
def _non_conversational_metadata(
metadata: Optional[Dict[str, Any]] = None,
*,
platform: Any = None,
platform: Any | None = None,
) -> Optional[Dict[str, Any]]:
"""Mark Discord lifecycle/status sends without changing other platforms."""
if _gateway_platform_value(platform) != "discord":
@@ -542,7 +542,7 @@ def _resolve_gateway_display_bool(
setting: str,
*,
default: bool = False,
platform: Any = None,
platform: Any | None = None,
require_platform_override_for: set[Any] | None = None,
) -> bool:
"""Resolve a boolean display setting with optional platform-only opt-in.
@@ -17311,7 +17311,7 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
history: List[Dict[str, Any]],
source: "SessionSource",
session_id: str,
session_key: str = None,
session_key: str | None = None,
run_generation: Optional[int] = None,
event_message_id: Optional[str] = None,
) -> Dict[str, Any]:
@@ -17610,7 +17610,7 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
history: List[Dict[str, Any]],
source: SessionSource,
session_id: str,
session_key: str = None,
session_key: str | None = None,
run_generation: Optional[int] = None,
_interrupt_depth: int = 0,
event_message_id: Optional[str] = None,
@@ -17760,7 +17760,7 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
history: List[Dict[str, Any]],
source: SessionSource,
session_id: str,
session_key: str = None,
session_key: str | None = None,
run_generation: Optional[int] = None,
_interrupt_depth: int = 0,
event_message_id: Optional[str] = None,
+6 -6
View File
@@ -2132,8 +2132,8 @@ def _scope_values(raw_scope: Any) -> set[str]:
def _nous_invoke_jwt_status(
token: Any,
*,
scope: Any = None,
expires_at: Any = None,
scope: Any | None = None,
expires_at: Any | None = None,
min_ttl_seconds: int = NOUS_INVOKE_JWT_MIN_TTL_SECONDS,
) -> Optional[str]:
"""Return None when the token can be used for inference, else a reason."""
@@ -2161,8 +2161,8 @@ def _nous_invoke_jwt_status(
def _nous_invoke_jwt_is_usable(
token: Any,
*,
scope: Any = None,
expires_at: Any = None,
scope: Any | None = None,
expires_at: Any | None = None,
min_ttl_seconds: int = NOUS_INVOKE_JWT_MIN_TTL_SECONDS,
) -> bool:
return (
@@ -2179,7 +2179,7 @@ def _nous_invoke_jwt_is_usable(
def _assert_nous_inference_jwt_usable(
state: Dict[str, Any],
*,
access_token: Any = None,
access_token: Any | None = None,
) -> None:
token = state.get("access_token") if access_token is None else access_token
reason = _nous_invoke_jwt_status(
@@ -2263,7 +2263,7 @@ def _set_nous_agent_key_from_invoke_jwt(
def _select_nous_invoke_jwt(
state: Dict[str, Any],
*,
access_token: Any = None,
access_token: Any | None = None,
sequence_id: Optional[str] = None,
) -> None:
if isinstance(access_token, str) and access_token.strip():
+5 -5
View File
@@ -578,12 +578,12 @@ def _display_toolset_name(toolset_name: str) -> str:
def build_welcome_banner(console: "Console", model: str, cwd: str,
tools: List[dict] = None,
enabled_toolsets: List[str] = None,
session_id: str = None,
tools: List[dict] | None = None,
enabled_toolsets: List[str] | None = None,
session_id: str | None = None,
get_toolset_for_tool=None,
context_length: int = None,
provider: str = None):
context_length: int | None = None,
provider: str | None = None):
"""Build and print a welcome banner with caduceus on left and info on right.
Args:
+3 -3
View File
@@ -3391,7 +3391,7 @@ def _has_sticky_block(conn: sqlite3.Connection, task_id: str) -> bool:
def recompute_ready(
conn: sqlite3.Connection, failure_limit: int = None,
conn: sqlite3.Connection, failure_limit: int | None = None,
) -> int:
"""Promote ``todo`` tasks to ``ready`` when all parents are ``done`` or ``archived``.
@@ -7025,7 +7025,7 @@ def _record_task_failure(
error: str,
*,
outcome: str,
failure_limit: int = None,
failure_limit: int | None = None,
force_trip: bool = False,
release_claim: bool = False,
end_run: bool = False,
@@ -7190,7 +7190,7 @@ def _record_spawn_failure(
task_id: str,
error: str,
*,
failure_limit: int = None,
failure_limit: int | None = None,
) -> bool:
return _record_task_failure(
conn, task_id, error,
+2 -2
View File
@@ -2008,8 +2008,8 @@ def _launch_tui(
tui_dev: bool = False,
model: Optional[str] = None,
provider: Optional[str] = None,
toolsets: object = None,
skills: object = None,
toolsets: object | None = None,
skills: object | None = None,
verbose: Optional[bool] = None,
quiet: bool = False,
query: Optional[str] = None,
+4 -4
View File
@@ -638,7 +638,7 @@ def resolve_alias(
def get_authenticated_provider_slugs(
current_provider: str = "",
user_providers: dict = None,
user_providers: dict | None = None,
custom_providers: list | None = None,
) -> list[str]:
"""Return slugs of providers that have credentials.
@@ -801,7 +801,7 @@ def switch_model(
current_api_key: str = "",
is_global: bool = False,
explicit_provider: str = "",
user_providers: dict = None,
user_providers: dict | None = None,
custom_providers: list | None = None,
) -> ModelSwitchResult:
"""Core model-switching pipeline shared between CLI and gateway.
@@ -1471,7 +1471,7 @@ def prewarm_picker_cache_async() -> Optional["_threading.Thread"]:
def list_authenticated_providers(
current_provider: str = "",
current_base_url: str = "",
user_providers: dict = None,
user_providers: dict | None = None,
custom_providers: list | None = None,
*,
force_fresh_nous_tier: bool = False,
@@ -2438,7 +2438,7 @@ def _prepend_moa_picker_provider(providers: List[dict], current_provider: str =
def list_picker_providers(
current_provider: str = "",
current_base_url: str = "",
user_providers: dict = None,
user_providers: dict | None = None,
custom_providers: list | None = None,
max_models: int | None = None,
current_model: str = "",
+5 -5
View File
@@ -4042,9 +4042,9 @@ def get_sessions(
min_messages: int = 0,
archived: str = "exclude",
order: str = "created",
source: str = None,
exclude_sources: str = None,
cwd_prefix: str = None,
source: str | None = None,
exclude_sources: str | None = None,
cwd_prefix: str | None = None,
full: bool = False,
profile: Optional[str] = None,
):
@@ -4142,8 +4142,8 @@ def get_profiles_sessions(
archived: str = "exclude",
order: str = "recent",
profile: str = "all",
source: str = None,
exclude_sources: str = None,
source: str | None = None,
exclude_sources: str | None = None,
full: bool = False,
):
"""Unified, read-only session list aggregated across ALL profiles.
+49 -49
View File
@@ -1928,17 +1928,17 @@ class SessionDB:
self,
session_id: str,
source: str,
model: str = None,
model_config: Dict[str, Any] = None,
system_prompt: str = None,
user_id: str = None,
session_key: str = None,
chat_id: str = None,
chat_type: str = None,
thread_id: str = None,
parent_session_id: str = None,
cwd: str = None,
profile_name: str = None,
model: str | None = None,
model_config: Dict[str, Any] | None = None,
system_prompt: str | None = None,
user_id: str | None = None,
session_key: str | None = None,
chat_id: str | None = None,
chat_type: str | None = None,
thread_id: str | None = None,
parent_session_id: str | None = None,
cwd: str | None = None,
profile_name: str | None = None,
) -> None:
"""Insert a session row, enriching NULL metadata on conflict.
@@ -2005,13 +2005,13 @@ class SessionDB:
session_id: str,
*,
source: str,
user_id: str = None,
session_key: str = None,
chat_id: str = None,
chat_type: str = None,
thread_id: str = None,
display_name: str = None,
origin_json: str = None,
user_id: str | None = None,
session_key: str | None = None,
chat_id: str | None = None,
chat_type: str | None = None,
thread_id: str | None = None,
display_name: str | None = None,
origin_json: str | None = None,
) -> None:
"""Persist the gateway routing peer for an existing session row.
@@ -2830,7 +2830,7 @@ class SessionDB:
session_id: str,
input_tokens: int = 0,
output_tokens: int = 0,
model: str = None,
model: str | None = None,
cache_read_tokens: int = 0,
cache_write_tokens: int = 0,
reasoning_tokens: int = 0,
@@ -3100,7 +3100,7 @@ class SessionDB:
self,
session_id: str,
source: str = "unknown",
model: str = None,
model: str | None = None,
**kwargs,
) -> str:
"""Ensure a session row exists (INSERT OR IGNORE). Accepts optional kwargs."""
@@ -3678,9 +3678,9 @@ class SessionDB:
def list_sessions_rich(
self,
source: str = None,
exclude_sources: List[str] = None,
cwd_prefix: str = None,
source: str | None = None,
exclude_sources: List[str] | None = None,
cwd_prefix: str | None = None,
limit: int = 20,
offset: int = 0,
include_children: bool = False,
@@ -3689,8 +3689,8 @@ class SessionDB:
order_by_last_active: bool = False,
include_archived: bool = False,
archived_only: bool = False,
id_query: str = None,
search_query: str = None,
id_query: str | None = None,
search_query: str | None = None,
compact_rows: bool = False,
) -> List[Dict[str, Any]]:
"""List sessions with preview (first user message) and last active timestamp.
@@ -4123,21 +4123,21 @@ class SessionDB:
self,
session_id: str,
role: str,
content: str = None,
tool_name: str = None,
tool_calls: Any = None,
tool_call_id: str = None,
token_count: int = None,
finish_reason: str = None,
reasoning: str = None,
reasoning_content: str = None,
reasoning_details: Any = None,
codex_reasoning_items: Any = None,
codex_message_items: Any = None,
platform_message_id: str = None,
content: str | None = None,
tool_name: str | None = None,
tool_calls: Any | None = None,
tool_call_id: str | None = None,
token_count: int | None = None,
finish_reason: str | None = None,
reasoning: str | None = None,
reasoning_content: str | None = None,
reasoning_details: Any | None = None,
codex_reasoning_items: Any | None = None,
codex_message_items: Any | None = None,
platform_message_id: str | None = None,
observed: bool = False,
effect_disposition: Optional[str] = None,
timestamp: Any = None,
timestamp: Any | None = None,
) -> int:
"""
Append a message to a session. Returns the message row ID.
@@ -5238,12 +5238,12 @@ class SessionDB:
def search_messages(
self,
query: str,
source_filter: List[str] = None,
exclude_sources: List[str] = None,
role_filter: List[str] = None,
source_filter: List[str] | None = None,
exclude_sources: List[str] | None = None,
role_filter: List[str] | None = None,
limit: int = 20,
offset: int = 0,
sort: str = None,
sort: str | None = None,
include_inactive: bool = False,
) -> List[Dict[str, Any]]:
"""
@@ -5605,7 +5605,7 @@ class SessionDB:
def search_sessions(
self,
source: str = None,
source: str | None = None,
limit: int = 20,
offset: int = 0,
) -> List[Dict[str, Any]]:
@@ -5645,13 +5645,13 @@ class SessionDB:
def session_count(
self,
source: str = None,
cwd_prefix: str = None,
source: str | None = None,
cwd_prefix: str | None = None,
min_message_count: int = 0,
include_archived: bool = False,
archived_only: bool = False,
exclude_children: bool = False,
exclude_sources: List[str] = None,
exclude_sources: List[str] | None = None,
) -> int:
"""Count sessions, optionally filtered by source.
@@ -6653,7 +6653,7 @@ class SessionDB:
def list_prune_candidates(
self,
older_than_days: Optional[float] = None,
source: str = None,
source: str | None = None,
**filters,
) -> List[Dict[str, Any]]:
"""Return the sessions a matching :meth:`prune_sessions` /
@@ -6681,7 +6681,7 @@ class SessionDB:
def archive_sessions(
self,
older_than_days: Optional[float] = None,
source: str = None,
source: str | None = None,
**filters,
) -> int:
"""Bulk-archive (soft-hide) every session matching the filters.
@@ -6707,7 +6707,7 @@ class SessionDB:
def prune_sessions(
self,
older_than_days: Optional[float] = 90,
source: str = None,
source: str | None = None,
sessions_dir: Optional[Path] = None,
**filters,
) -> int:
+6 -6
View File
@@ -163,8 +163,8 @@ class MiniSWERunner:
def __init__(
self,
model: str = "anthropic/claude-sonnet-4.6",
base_url: str = None,
api_key: str = None,
base_url: str | None = None,
api_key: str | None = None,
env_type: str = "local",
image: str = "python:3.11-slim",
cwd: str = "/tmp",
@@ -628,12 +628,12 @@ Complete the user's task step by step."""
# ============================================================================
def main(
task: str = None,
prompts_file: str = None,
task: str | None = None,
prompts_file: str | None = None,
output_file: str = "swe-runner-test1.jsonl",
model: str = "claude-sonnet-4-20250514",
base_url: str = None,
api_key: str = None,
base_url: str | None = None,
api_key: str | None = None,
env: str = "local",
image: str = "python:3.11-slim",
cwd: str = "/tmp",
+25 -8
View File
@@ -9,6 +9,7 @@
stdenv,
makeWrapper,
callPackage,
python311,
python312,
nodejs_22,
electron,
@@ -37,15 +38,28 @@
}:
let
nodejs = nodejs_22;
mkHermesVenv =
extraDependencyGroups:
{
extraDependencyGroups,
python ? null,
}:
callPackage ./python.nix {
inherit uv2nix pyproject-nix pyproject-build-systems;
inherit
uv2nix
pyproject-nix
pyproject-build-systems
python
;
pythonSrc = hermesNpmLib.pythonSrc;
dependency-groups = [ "all" ] ++ extraDependencyGroups;
};
hermesVenv = (mkHermesVenv extraDependencyGroups).venv;
hermesVenv =
(mkHermesVenv {
inherit extraDependencyGroups;
python = python312;
}).venv;
hermesNpmLib = callPackage ./lib.nix {
inherit npm-lockfile-fix nodejs;
@@ -61,8 +75,7 @@ let
bundledSkills = lib.cleanSourceWith {
src = ../skills;
filter =
path: _type: !(lib.hasInfix "/index-cache/" path) && !(lib.hasInfix "/__pycache__/" path);
filter = path: _type: !(lib.hasInfix "/index-cache/" path) && !(lib.hasInfix "/__pycache__/" path);
};
# Optional skills are NOT in the wheel (pythonSrc excludes them, see
@@ -70,8 +83,7 @@ let
# same mechanism Homebrew packaging uses.
bundledOptionalSkills = lib.cleanSourceWith {
src = ../optional-skills;
filter =
path: _type: !(lib.hasInfix "/index-cache/" path) && !(lib.hasInfix "/__pycache__/" path);
filter = path: _type: !(lib.hasInfix "/index-cache/" path) && !(lib.hasInfix "/__pycache__/" path);
};
# Import bundled plugins (memory, context_engine, platforms/*). Keeping
@@ -224,8 +236,13 @@ stdenv.mkDerivation (finalAttrs: {
'';
passthru =
python:
let
devPython = (mkHermesVenv (extraDependencyGroups ++ [ "dev" ])).editableVenv;
devPython =
(mkHermesVenv ({
extraDependencyGroups = extraDependencyGroups ++ [ "dev" ];
python = python311;
})).editableVenv;
in
{
inherit
+10 -10
View File
@@ -1,6 +1,6 @@
# nix/python.nix — uv2nix virtual environment builder
{
python312,
python,
lib,
callPackage,
uv2nix,
@@ -65,30 +65,30 @@ let
final: _prev:
if isAarch64Darwin then
{
numpy = mkPrebuiltOverride final python312.pkgs.numpy { };
numpy = mkPrebuiltOverride final python.pkgs.numpy { };
pyarrow = mkPrebuiltOverride final python312.pkgs.pyarrow { };
pyarrow = mkPrebuiltOverride final python.pkgs.pyarrow { };
av = mkPrebuiltOverride final python312.pkgs.av { };
av = mkPrebuiltOverride final python.pkgs.av { };
humanfriendly = mkPrebuiltOverride final python312.pkgs.humanfriendly { };
humanfriendly = mkPrebuiltOverride final python.pkgs.humanfriendly { };
coloredlogs = mkPrebuiltOverride final python312.pkgs.coloredlogs {
coloredlogs = mkPrebuiltOverride final python.pkgs.coloredlogs {
humanfriendly = [ ];
};
onnxruntime = mkPrebuiltOverride final python312.pkgs.onnxruntime {
onnxruntime = mkPrebuiltOverride final python.pkgs.onnxruntime {
coloredlogs = [ ];
numpy = [ ];
packaging = [ ];
};
ctranslate2 = mkPrebuiltOverride final python312.pkgs.ctranslate2 {
ctranslate2 = mkPrebuiltOverride final python.pkgs.ctranslate2 {
numpy = [ ];
pyyaml = [ ];
};
faster-whisper = mkPrebuiltOverride final python312.pkgs.faster-whisper {
faster-whisper = mkPrebuiltOverride final python.pkgs.faster-whisper {
av = [ ];
ctranslate2 = [ ];
huggingface-hub = [ ];
@@ -102,7 +102,7 @@ let
pythonSet =
(callPackage pyproject-nix.build.packages {
python = python312;
python = python;
}).overrideScope
(
lib.composeManyExtensions [
+312
View File
@@ -0,0 +1,312 @@
# ty type-checking notes — antipatterns & refactors spotted
working through the codebase root-to-tip with astral.sh `ty`.
logging antipatterns, clean refactors, and observations as we go.
---
## tools/registry.py (dependency root, 810 lines)
### invalid-parameter-default (FIXED)
three params in `ToolEntry.register()` were annotated as bare `Callable` / `list`
but defaulted to `None`. this is the #1 pattern ty flags across the whole codebase
(~423 occurrences). the fix is always the same: add `| None` to the annotation.
```python
# BEFORE
check_fn: Callable = None,
requires_env: list = None,
dynamic_schema_overrides: Callable = None,
# AFTER
check_fn: Callable | None = None,
requires_env: list | None = None,
dynamic_schema_overrides: Callable | None = None,
```
this is the single highest-leverage fix across the codebase. each one of these
cascades: when ty sees `None` as a possible value, every downstream `d["key"]`,
`d.get(...)`, `d.pop()` on that variable becomes an `unresolved-attribute` or
`not-subscriptable` or `invalid-argument-type` error. fixing the parameter
default upstream makes all those downstream errors vanish.
### antipattern: ToolEntry.__init__ has zero annotations
```python
def __init__(self, name, toolset, schema, handler, check_fn,
requires_env, is_async, description, emoji,
max_result_size_chars=None, dynamic_schema_overrides=None):
```
this is a core data class (every tool in the system passes through it) but has
no type annotations on any parameter. `__slots__` is used so the shape is
well-defined — adding annotations would be straightforward and high-value.
ty can't infer much here because everything comes through as `Unknown`.
not fixed yet — would benefit from a proper pass:
```python
def __init__(
self,
name: str,
toolset: str,
schema: dict,
handler: Callable,
check_fn: Callable | None,
requires_env: list[str],
is_async: bool,
description: str,
emoji: str,
max_result_size_chars: int | float | None = None,
dynamic_schema_overrides: Callable | None = None,
):
```
### antipattern: tool_error / tool_result helpers lack annotations
```python
def tool_error(message, **extra) -> str: # message: str, **extra: Any
def tool_result(data=None, **kwargs) -> str: # data: dict | None, **kwargs: Any
```
these are the canonical serialization helpers used by hundreds of tool handlers.
low-effort to annotate, high-value since they're the return path for everything.
### clean pattern: _check_fn_cached TTL + grace window
this is well-done code. the TTL cache with transient-failure suppression is
a correct implementation of a flaky-external-check absorption pattern. the
docstrings explain WHY (issue #21658 / #5304 — flaky docker probes stripping
tools mid-session). nothing to change here, just noting it as a positive
example of defensive caching done right.
### clean pattern: _snapshot_state() for thread safety
using `_lock` + snapshot copies for reads is the right pattern for a registry
that can be mutated by MCP dynamic refresh while other threads read. the
generation counter for cache invalidation is also clean.
### observation: `from typing import` vs PEP 604 `X | None`
the file imports `Optional`, `Callable`, `Dict`, `List`, `Set` from `typing`
but also uses `int | float | None` (PEP 604) in the same signatures. the
codebase targets python >=3.11 so PEP 604 is always available. there's a
mix of `Optional[X]` and `X | None` styles across files — not a bug, but
worth standardizing on `X | None` (PEP 604) as we type-sweep since it's
shorter and the modern idiom.
---
## agent/agent_init.py + run_agent.py (AIAgent constructor, 60+ params)
### invalid-parameter-default (FIXED — 31 + 54 params)
same pattern as registry.py but at massive scale. the AIAgent.__init__ in
`agent_init.py` and the class `AIAgent.__init__` in `run_agent.py` each take
~60 params, nearly all defaulting to None but annotated as bare `str`, `list`,
`dict`, `int`, etc.
### antipattern: lowercase `callable` used as a type annotation
**this is a real runtime bug, not just a type error.** 14 callback params in
both `agent_init.py` and `run_agent.py` were annotated as `callable` (the
builtin *function*) instead of `Callable` (from `typing`).
```python
# BEFORE — runtime crash if you try `X | None`
tool_progress_callback: callable = None, # callable is the builtin function
# AFTER
tool_progress_callback: Callable | None = None, # Callable is the type
```
when the automated sweep script added `| None` to these, it produced
`callable | None` which crashes at *import time* with:
```
TypeError: unsupported operand type(s) for |: 'builtin_function_or_method' and 'NoneType'
```
this was lurking silently as long as nobody tried to make the annotation
nullable — the `callable` builtin is truthy so `callable = None` didn't
crash, it just stored a nonsensical annotation. the fix is `Callable` (capital C).
also spotted in `agent/transports/chat_completions.py` in docstring-like
comments (lines 290, 314-315) — not executable code but worth standardizing.
### antipattern: string forward-ref with `| None` doesn't work
```python
# WRONG — crashes at class-def time
iteration_budget: "IterationBudget" | None = None,
# TypeError: unsupported operand type(s) for |: 'str' and 'NoneType'
# RIGHT — wrap the whole thing in Optional
iteration_budget: Optional["IterationBudget"] = None,
```
when a type is a forward reference (string-quoted because it's not yet defined),
you CANNOT use PEP 604 `| None` on it directly — the `|` operator tries to
OR a `str` with `NoneType` and crashes. must use `Optional["ForwardRef"]`.
this affected both `agent_init.py` and `run_agent.py`.
---
## hermes_state.py (49 params fixed)
same `X = None` → `X | None = None` sweep. this is the SQLite session store
module — heavily imported by cli.py, run_agent.py, gateway/, etc.
## batch_runner.py (24 params fixed)
parallel batch processing entry point.
## cli.py (18 params fixed)
the HermesCLI class constructor + helper methods.
## tools/*.py (20+ params fixed across file_tools, file_operations,
skills_tool, skills_hub, terminal_tool, browser_tool, memory_tool,
delegate_tool, process_registry, skill_manager_tool, session_search_tool)
---
## summary of the invalid-parameter-default sweep
total params fixed: ~250 across ~25 core files
the pattern is always identical: `param: Type = None` → `param: Type | None = None`
two runtime-breaking gotchas found during the sweep:
1. lowercase `callable` (builtin function) → must be `Callable` (typing)
2. string forward-refs can't use `| None` → must use `Optional["Ref"]`
---
## run_agent.py — unresolved-attribute analysis (190 errors)
### category 1: "Self@<method>" — init_agent() cross-module init (186/190)
these are ALL the same root cause: `AIAgent.__init__` in `run_agent.py` is a
thin forwarder that calls `init_agent(self, ...)` from `agent/agent_init.py`.
that function sets `self.model`, `self.provider`, `self.session_id`, etc. on
the instance — but ty can't see across the module boundary that these
attributes are being set. so every method that accesses `self.model`,
`self.provider`, etc. gets flagged.
**these are NOT bugs.** the attributes are correctly set at runtime by
`init_agent()`. this is a ty limitation — it can't track attribute assignments
made in a function defined in a different module that receives `self` as a
regular parameter (not as a method on the class).
example:
```python
# run_agent.py
class AIAgent:
def __init__(self, model: str = "", ...):
from agent.agent_init import init_agent
init_agent(self, model=model, ...) # sets self.model, self.provider, etc.
def _resolved_api_call_timeout(self):
return self.provider # ty: "Self@_resolved_api_call_timeout has no attribute provider"
```
**fix approach:** this is the single biggest cluster of ty errors in the
codebase (186 in run_agent.py alone, plus similar in other files). the cleanest
fix would be to declare the attributes as class-level annotations in the
`AIAgent` class body so ty knows they exist:
```python
class AIAgent:
# Instance attributes — set by init_agent() in agent/agent_init.py
model: str
provider: str | None
session_id: str | None
# ... etc
```
this is a one-time declaration that would clear ~186 errors instantly. it's
also good documentation — currently there's no single place that lists all
instance attributes; they're scattered across the 60-param init_agent() body.
### category 2: object.function — duck-typed tool_calls access (2 errors)
line 1968: `tc.function.name` and `tc.function.arguments` on objects typed as
`object`. this is because `msg.tool_calls` is checked via `hasattr` +
`isinstance(list)` but the list elements are `object` to ty.
```python
if hasattr(msg, "tool_calls") and isinstance(msg.tool_calls, list) and msg.tool_calls:
tool_calls_data = [
{"name": tc.function.name, "arguments": tc.function.arguments}
for tc in msg.tool_calls
]
```
**not a bug** — this is duck-typed access to OpenAI SDK objects. the `hasattr`
guard makes it safe at runtime. ty just can't infer the element type of a
list accessed via `hasattr`. could be fixed with a `type: ignore` or by
narrowing `msg` to a proper type.
### category 3: ~AlwaysFalsy.on_session_end — duck-typed context_compressor (2 errors)
lines 3416, 3441: `self.context_compressor.on_session_end(...)` after a
`hasattr(self, "context_compressor") and self.context_compressor` guard.
```python
if hasattr(self, "context_compressor") and self.context_compressor:
self.context_compressor.on_session_end(self.session_id or "", messages or [])
```
ty narrows `self.context_compressor` to `~AlwaysFalsy` (the truthy branch of
the `and`) but doesn't know it has `on_session_end`. **not a bug** — the
`hasattr` guard makes this safe. again a type-system limitation with
duck-typed access.
### verdict for run_agent.py: 0 real bugs, 190 type-system limitations
all 190 are ty being unable to track either cross-module attribute init
(186) or duck-typed hasattr-guarded access (4). no logic bugs found.
### remaining 6 unresolved-attribute (post class-level annotation fix)
1. **line 2162** (`object.function` ×2): duck-typed access to OpenAI SDK
`ChatCompletionMessageToolCall` objects. `hasattr` guard makes it safe.
ty can't infer the list element type through `hasattr`. not a bug.
2. **lines 3586-3587** (`iteration_budget.used` / `.max_total`): `iteration_budget`
is `Optional[IterationBudget]` but `init_agent` always sets it to a non-None
value (`iteration_budget or IterationBudget(max_iterations)`). the annotation
says `| None` but the code guarantees non-None at runtime. not a bug — but
the annotation could be tightened to just `IterationBudget` (without Optional).
3. **lines 4677 + 4799** (`_anthropic_client.close()`): `_anthropic_client` is
`Any | None` — could be None if the anthropic path was never taken. but both
call sites are wrapped in `try/except Exception: pass`, so even if it IS None,
the AttributeError is caught. safe.
---
## plugins/platforms/discord/adapter.py — unresolved-attribute analysis (269 errors)
### root cause: ty resolving the wrong Python environment
the `.venv` directory (created by `uv run` earlier) contained Python 3.13 with
NO dependencies installed. ty auto-discovers `.venv` and uses it for module
resolution — so it couldn't find `discord.py`, `aiohttp`, etc., producing 254
`Module 'discord' has no member X` errors.
the actual dependencies are installed in the nix develop environment
(Python 3.12, all deps in site-packages). removing the empty `.venv` made ty
fall back to the system/nix python, resolving all 254 import errors instantly.
**fix:** `rm -rf .venv` (it was a stray artifact, not a real venv).
also set `python-version = "3.12"` in `[tool.ty.environment]` to match the
nix env, with a comment explaining why.
**impact:** 4,422 → 3,385 diagnostics (1,037) from this single fix.
### remaining 122 discord errors (post-fix)
after ty could resolve discord.py:
- 17 `not defined on None` — `self._client` (Optional[commands.Bot]) accessed
before ty can prove it's non-None. all are in event handlers called after
the bot is ready. not bugs, but could benefit from `assert self._client is not None`
guards for type narrowing.
- ~100 remaining are real type-checking errors from ty now being able to
see discord.py's actual types (mismatched args, wrong attribute access, etc.)
these need individual triage.
@@ -92,12 +92,12 @@ class EvidenceStore:
source: str,
content: str,
evidence_type: str,
actor: str = None,
url: str = None,
timestamp: str = None,
ioc_type: str = None,
actor: str | None = None,
url: str | None = None,
timestamp: str | None = None,
ioc_type: str | None = None,
verification: str = "unverified",
notes: str = None,
notes: str | None = None,
) -> str:
evidence_id = self._next_id()
entry = {
+7 -1
View File
@@ -366,12 +366,18 @@ markers = [
addopts = "-m 'not integration'"
[tool.ty.environment]
python-version = "3.13"
# Match requires-python floor (>=3.11).
python-version = "3.11"
[tool.ty.rules]
unknown-argument = "warn"
redundant-cast = "ignore"
# Exclude tests from type-checking — they're the lowest-value typing target
# (~57% of diagnostics) and we want to focus ty on the core codebase.
[tool.ty.src]
exclude = ["tests/"]
[tool.ruff]
preview = true # required for PLW1514 (unspecified-encoding) — preview rule
+248 -54
View File
@@ -400,6 +400,200 @@ class AIAgent:
for AI models that support function calling.
"""
# -----------------------------------------------------------------------
# Instance attributes — set by init_agent() in agent/agent_init.py.
# Ideally, we could refactor this class into smaller parts so that
# we don't need to split its __init__ into a separate file.
# Declared here so type checkers know they exist (init_agent receives
# the instance as a plain `agent` parameter, not as a method receiver,
# so cross-module attribute assignment is invisible to static analysis).
# -----------------------------------------------------------------------
# scalars
_anthropic_api_key: str
_anthropic_base_url: str | None
_budget_exhausted_injected: bool
_budget_grace_call: bool
_cache_ttl: str
_cached_system_prompt: str | None
_chat_id: str | None
_chat_name: str | None
_chat_type: str | None
_client_kwargs: dict
_codex_reasoning_replay_enabled: bool
_compression_feasibility_checked: bool
_credits_latch: dict
_current_streamed_assistant_text: str
_delegate_depth: int
_end_session_on_close: bool
_executing_tools: bool
_fallback_activated: bool
_fallback_chain: list
_fallback_index: int
_force_ascii_payload: bool
_interrupt_requested: bool
_interrupt_thread_signal_pending: bool
_is_anthropic_oauth: bool
_is_user_initiated_turn: bool
_iters_since_skill: int
_last_flushed_db_idx: int
_memory_enabled: bool
_memory_nudge_interval: int
_memory_write_context: str
_memory_write_origin: str
_parent_session_id: str | None
_persist_disabled: bool
_primary_runtime: dict
_session_db_created: bool
_session_init_model_config: dict
_session_json_enabled: bool
_skill_nudge_interval: int
_skip_mcp_refresh: bool
_stream_needs_break: bool
_stream_writer_dropped: int
_stream_writer_token: int
_thread_id: str | None
_tool_snapshot_generation: int
_turns_since_memory: int
_user_name: str | None
_user_profile_enabled: bool
_user_turn_count: int
acp_command: str | None
api_key: str | None
api_mode: str | None
ephemeral_system_prompt: str | None
lmstudio_load_mode: str
load_soul_identity: bool
log_prefix: str
log_prefix_chars: int
max_iterations: int
max_tokens: int | None
memory_notifications: str
model: str
pass_session_id: bool
platform: str | None
provider: str | None
provider_data_collection: str | None
provider_require_parameters: bool
provider_sort: str | None
quiet_mode: bool
save_trajectories: bool
service_tier: str | None
session_api_calls: int
session_cache_read_tokens: int
session_cache_write_tokens: int
session_completion_tokens: int
session_cost_source: str
session_cost_status: str
session_estimated_cost_usd: float
session_id: str | None
session_input_tokens: int
session_output_tokens: int
session_prompt_tokens: int
session_reasoning_tokens: int
session_total_tokens: int
show_commentary: bool
skip_context_files: bool
suppress_status_output: bool
tool_delay: float
tool_progress_mode: str
verbose_logging: bool
# callbacks
clarify_callback: Callable | None
event_callback: Optional[Callable[[str, dict], None]]
interim_assistant_callback: Callable | None
notice_callback: Callable | None
notice_clear_callback: Callable | None
reaction_callback: Optional[Callable[[str], None]]
read_terminal_callback: Callable | None
reasoning_callback: Callable | None
status_callback: Callable | None
step_callback: Callable | None
stream_delta_callback: Callable | None
thinking_callback: Callable | None
tool_complete_callback: Callable | None
tool_gen_callback: Callable | None
tool_progress_callback: Callable | None
tool_start_callback: Callable | None
# collections
acp_args: list[str] | None
disabled_toolsets: List[str] | None
enabled_toolsets: List[str] | None
prefill_messages: List[Dict[str, Any]] | None
providers_allowed: List[str] | None
providers_ignored: List[str] | None
providers_order: List[str] | None
reasoning_config: Dict[str, Any] | None
request_overrides: Dict[str, Any] | None
# internal / runtime state
_active_children: list
_active_children_lock: Any
_anthropic_client: Any | None
_anthropic_image_fallback_cache: Any
_api_max_retries: Any
_aux_compression_context_length_config: Any | None
_base_url_hostname: Any
_bedrock_guardrail_config: Any | None
_bedrock_region: Any
_checkpoint_mgr: Any
_client_lock: Any
_compression_threshold_autoraised: Any | None
_compression_warning: Any | None
_config_context_length: Any
_credential_pool: Any
_credits_session_start_micros: Any | None
_credits_state: Any | None
_custom_providers: Any
_environment_probe: Any
_execution_thread_id: Any
_fallback_model: Any
_gateway_session_key: Any
_intent_ack_continuation: Any
_interrupt_message: Any
_kanban_worker_guidance: Any
_memory_manager: Any | None
_memory_store: Any | None
_ollama_num_ctx: Any
_parallel_tool_call_guidance: Any
_pending_cli_user_message: Any | None
_pending_steer_lock: Any
_persist_user_message_idx: Any | None
_persist_user_message_override: Any | None
_persist_user_message_timestamp: Any | None
_platform_hint_overrides: Any
_print_fn: Any | None
_session_db: Any
_session_persist_lock: Any
_stream_callback: Any | None
_stream_context_scrubber: Any
_stream_think_scrubber: Any
_stream_writer_lock: Any
_stream_writer_tls: Any
_subdirectory_hints: Any
_task_completion_guidance: Any
_todo_store: Any
_tool_guardrails: Any
_tool_use_enforcement: Any
_tool_worker_threads_lock: Any
_user_id: Any
_user_id_alt: Any
background_review_callback: Any
client: Any | None
codex_app_server_auto_compaction: Any
compression_enabled: Any
compression_in_place: Any
context_compressor: Any
iteration_budget: Optional[IterationBudget]
logs_dir: Any
openrouter_min_coding_score: Optional[float]
session_start: Any
tools: Any
valid_tool_names: Any
_current_tool: Any
_api_call_count: int
_TOOL_CALL_ARGUMENTS_CORRUPTION_MARKER = (
"[hermes-agent: tool call arguments were corrupted in this session and "
"have been dropped to keep the conversation alive. See issue #15236.]"
@@ -417,71 +611,71 @@ class AIAgent:
def __init__(
self,
base_url: str = None,
api_key: str = None,
provider: str = None,
api_mode: str = None,
acp_command: str = None,
base_url: str | None = None,
api_key: str | None = None,
provider: str | None = None,
api_mode: str | None = None,
acp_command: str | None = None,
acp_args: list[str] | None = None,
command: str = None,
command: str | None = None,
args: list[str] | None = None,
model: str = "",
max_iterations: int = 90, # Default tool-calling iterations (shared with subagents)
tool_delay: float = 1.0,
enabled_toolsets: List[str] = None,
disabled_toolsets: List[str] = None,
enabled_toolsets: List[str] | None = None,
disabled_toolsets: List[str] | None = None,
save_trajectories: bool = False,
verbose_logging: bool = False,
quiet_mode: bool = False,
tool_progress_mode: str = "all",
ephemeral_system_prompt: str = None,
ephemeral_system_prompt: str | None = None,
log_prefix_chars: int = 100,
log_prefix: str = "",
providers_allowed: List[str] = None,
providers_ignored: List[str] = None,
providers_order: List[str] = None,
provider_sort: str = None,
providers_allowed: List[str] | None = None,
providers_ignored: List[str] | None = None,
providers_order: List[str] | None = None,
provider_sort: str | None = None,
provider_require_parameters: bool = False,
provider_data_collection: str = None,
provider_data_collection: str | None = None,
openrouter_min_coding_score: Optional[float] = None,
session_id: str = None,
tool_progress_callback: callable = None,
tool_start_callback: callable = None,
tool_complete_callback: callable = None,
thinking_callback: callable = None,
reasoning_callback: callable = None,
clarify_callback: callable = None,
read_terminal_callback: callable = None,
step_callback: callable = None,
stream_delta_callback: callable = None,
interim_assistant_callback: callable = None,
tool_gen_callback: callable = None,
status_callback: callable = None,
notice_callback: callable = None,
notice_clear_callback: callable = None,
session_id: str | None = None,
tool_progress_callback: Callable | None = None,
tool_start_callback: Callable | None = None,
tool_complete_callback: Callable | None = None,
thinking_callback: Callable | None = None,
reasoning_callback: Callable | None = None,
clarify_callback: Callable | None = None,
read_terminal_callback: Callable | None = None,
step_callback: Callable | None = None,
stream_delta_callback: Callable | None = None,
interim_assistant_callback: Callable | None = None,
tool_gen_callback: Callable | None = None,
status_callback: Callable | None = None,
notice_callback: Callable | None = None,
notice_clear_callback: Callable | None = None,
event_callback: Optional[Callable[[str, dict], None]] = None,
reaction_callback: Optional[Callable[[str], None]] = None,
max_tokens: int = None,
reasoning_config: Dict[str, Any] = None,
service_tier: str = None,
request_overrides: Dict[str, Any] = None,
prefill_messages: List[Dict[str, Any]] = None,
platform: str = None,
user_id: str = None,
user_id_alt: str = None,
user_name: str = None,
chat_id: str = None,
chat_name: str = None,
chat_type: str = None,
thread_id: str = None,
gateway_session_key: str = None,
max_tokens: int | None = None,
reasoning_config: Dict[str, Any] | None = None,
service_tier: str | None = None,
request_overrides: Dict[str, Any] | None = None,
prefill_messages: List[Dict[str, Any]] | None = None,
platform: str | None = None,
user_id: str | None = None,
user_id_alt: str | None = None,
user_name: str | None = None,
chat_id: str | None = None,
chat_name: str | None = None,
chat_type: str | None = None,
thread_id: str | None = None,
gateway_session_key: str | None = None,
skip_context_files: bool = False,
load_soul_identity: bool = False,
skip_memory: bool = False,
session_db=None,
parent_session_id: str = None,
iteration_budget: "IterationBudget" = None,
fallback_model: Dict[str, Any] = None,
parent_session_id: str | None = None,
iteration_budget: Optional["IterationBudget"] = None,
fallback_model: Dict[str, Any] | None = None,
credential_pool=None,
checkpoints_enabled: bool = False,
checkpoint_max_snapshots: int = 20,
@@ -5932,7 +6126,7 @@ class AIAgent:
messages: list,
*,
logger=None,
session_id: str = None,
session_id: str | None = None,
) -> int:
"""Forwarder — see ``agent.agent_runtime_helpers.sanitize_tool_call_arguments``."""
from agent.agent_runtime_helpers import sanitize_tool_call_arguments
@@ -6171,9 +6365,9 @@ class AIAgent:
def run_conversation(
self,
user_message: Any,
system_message: str = None,
conversation_history: List[Dict[str, Any]] = None,
task_id: str = None,
system_message: str | None = None,
conversation_history: List[Dict[str, Any]] | None = None,
task_id: str | None = None,
stream_callback: Optional[callable] = None,
persist_user_message: Optional[Any] = None,
persist_user_timestamp: Optional[float] = None,
@@ -6253,13 +6447,13 @@ class AIAgent:
return run_codex_app_server_turn(self, user_message=user_message, original_user_message=original_user_message, messages=messages, effective_task_id=effective_task_id, should_review_memory=should_review_memory)
def main(
query: str = None,
query: str | None = None,
model: str = "",
api_key: str = None,
api_key: str | None = None,
base_url: str = "",
max_turns: int = 10,
enabled_toolsets: str = None,
disabled_toolsets: str = None,
enabled_toolsets: str | None = None,
disabled_toolsets: str | None = None,
list_tools: bool = False,
save_trajectories: bool = False,
save_sample: bool = False,
+1 -1
View File
@@ -2266,7 +2266,7 @@ def _extract_screenshot_path_from_text(text: str) -> Optional[str]:
def _run_browser_command(
task_id: str,
command: str,
args: List[str] = None,
args: List[str] | None = None,
timeout: Optional[int] = None,
_engine_override: Optional[str] = None,
) -> Dict[str, Any]:
+1 -1
View File
@@ -869,7 +869,7 @@ def _build_child_progress_callback(
return kw
def _relay(
event_type: str, tool_name: str = None, preview: str = None, args=None, **kwargs
event_type: str, tool_name: str | None = None, preview: str = None, args=None, **kwargs
):
if not parent_cb:
return
+1 -1
View File
@@ -834,7 +834,7 @@ class ShellFileOperations(FileOperations):
self._command_cache: Dict[str, bool] = {}
def _exec(self, command: str, cwd: str = None, timeout: int = None,
stdin_data: str = None) -> ExecuteResult:
stdin_data: str | None = None) -> ExecuteResult:
"""Execute command via terminal backend.
Args:
+2 -2
View File
@@ -1653,7 +1653,7 @@ def write_file_tool(path: str, content: str, task_id: str = "default",
def patch_tool(mode: str = "replace", path: str = None, old_string: str = None,
new_string: str = None, replace_all: bool = False, patch: str = None,
new_string: str | None = None, replace_all: bool = False, patch: str = None,
task_id: str = "default", cross_profile: bool = False,
session_id: str | None = None) -> str:
"""Patch a file using replace mode or V4A patch format.
@@ -1847,7 +1847,7 @@ def patch_tool(mode: str = "replace", path: str = None, old_string: str = None,
def search_tool(pattern: str, target: str = "content", path: str = ".",
file_glob: str = None, limit: int = 50, offset: int = 0,
file_glob: str | None = None, limit: int = 50, offset: int = 0,
output_mode: str = "content", context: int = 0,
task_id: str = "default") -> str:
"""Search for content or files."""
+3 -3
View File
@@ -957,10 +957,10 @@ def _missing_old_text_error(store: "MemoryStore", target: str, action: str) -> s
def memory_tool(
action: str = None,
action: str | None = None,
target: str = "memory",
content: str = None,
old_text: str = None,
content: str | None = None,
old_text: str | None = None,
operations: Optional[List[Dict[str, Any]]] = None,
store: Optional[MemoryStore] = None,
) -> str:
+3 -3
View File
@@ -689,10 +689,10 @@ class ProcessRegistry:
def spawn_local(
self,
command: str,
cwd: str = None,
cwd: str | None = None,
task_id: str = "",
session_key: str = "",
env_vars: dict = None,
env_vars: dict | None = None,
use_pty: bool = False,
) -> ProcessSession:
"""
@@ -829,7 +829,7 @@ class ProcessRegistry:
self,
env: Any,
command: str,
cwd: str = None,
cwd: str | None = None,
task_id: str = "",
session_key: str = "",
timeout: int = 10,
+3 -3
View File
@@ -368,13 +368,13 @@ class ToolRegistry:
toolset: str,
schema: dict,
handler: Callable,
check_fn: Callable = None,
requires_env: list = None,
check_fn: Callable | None = None,
requires_env: list | None = None,
is_async: bool = False,
description: str = "",
emoji: str = "",
max_result_size_chars: int | float | None = None,
dynamic_schema_overrides: Callable = None,
dynamic_schema_overrides: Callable | None = None,
override: bool = False,
):
"""Register a tool. Called at module-import time by each tool file.
+8 -8
View File
@@ -305,7 +305,7 @@ def _scroll(
session_id: str,
around_message_id: int,
window: int = 5,
current_session_id: str = None,
current_session_id: str | None = None,
) -> str:
"""Scroll shape: return a window of messages centered on an anchor.
@@ -502,7 +502,7 @@ def _discover(
role_filter: Optional[List[str]],
limit: int,
sort: Optional[str],
current_session_id: str = None,
current_session_id: str | None = None,
) -> str:
"""Discovery shape: FTS5 + anchored window + bookends per hit. Single call."""
role_list = role_filter if role_filter else ["user", "assistant"]
@@ -618,18 +618,18 @@ def _discover(
def session_search(
query: str = "",
role_filter: str = None,
role_filter: str | None = None,
limit: int = 3,
db=None,
current_session_id: str = None,
current_session_id: str | None = None,
# Scroll shape
session_id: str = None,
around_message_id: int = None,
session_id: str | None = None,
around_message_id: int | None = None,
window: int = 5,
# Discovery shape
sort: str = None,
sort: str | None = None,
# Cross-profile (any shape)
profile: str = None,
profile: str | None = None,
) -> str:
"""Single-shape tool. Mode inferred from which args are set.
+8 -8
View File
@@ -918,7 +918,7 @@ def _patch_skill(
name: str,
old_string: str,
new_string: str,
file_path: str = None,
file_path: str | None = None,
replace_all: bool = False,
) -> Dict[str, Any]:
"""Targeted find-and-replace within a skill file.
@@ -1323,14 +1323,14 @@ def apply_skill_pending(payload: Dict[str, Any]) -> str:
def skill_manage(
action: str,
name: str,
content: str = None,
category: str = None,
file_path: str = None,
file_content: str = None,
old_string: str = None,
new_string: str = None,
content: str | None = None,
category: str | None = None,
file_path: str | None = None,
file_content: str | None = None,
old_string: str | None = None,
new_string: str | None = None,
replace_all: bool = False,
absorbed_into: str = None,
absorbed_into: str | None = None,
) -> str:
"""
Manage user-created skills. Dispatches to the appropriate action handler.
+2 -2
View File
@@ -960,8 +960,8 @@ def _serve_plugin_skill(
def skill_view(
name: str,
file_path: str = None,
task_id: str = None,
file_path: str | None = None,
task_id: str | None = None,
preprocess: bool = True,
) -> str:
"""
+3 -3
View File
@@ -1482,10 +1482,10 @@ def _get_modal_backend_state(modal_mode: object | None) -> Dict[str, Any]:
def _create_environment(env_type: str, image: str, cwd: str, timeout: int,
ssh_config: dict = None, container_config: dict = None,
local_config: dict = None,
ssh_config: dict | None = None, container_config: dict = None,
local_config: dict | None = None,
task_id: str = "default",
host_cwd: str = None):
host_cwd: str | None = None):
"""
Create an execution environment for sandboxed command execution.
+4 -4
View File
@@ -1355,11 +1355,11 @@ Write only the summary, starting with "[CONTEXT SUMMARY]:" prefix."""
def main(
input: str,
output: str = None,
output: str | None = None,
config: str = "configs/trajectory_compression.yaml",
target_max_tokens: int = None,
tokenizer: str = None,
sample_percent: float = None,
target_max_tokens: int | None = None,
tokenizer: str | None = None,
sample_percent: float | None = None,
seed: int = 42,
dry_run: bool = False,
):