mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
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
This commit is contained in:
parent
169bfe20e4
commit
0cb42c10a4
2 changed files with 281 additions and 0 deletions
87
notes.txt
87
notes.txt
|
|
@ -174,3 +174,90 @@ the pattern is always identical: `param: Type = None` → `param: Type | 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.
|
||||
|
|
|
|||
194
run_agent.py
194
run_agent.py
|
|
@ -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.]"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue