diff --git a/notes.txt b/notes.txt index c7fab3747df..11abc565f39 100644 --- a/notes.txt +++ b/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@" — 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. diff --git a/run_agent.py b/run_agent.py index f3f5b53f540..658e714fcb2 100644 --- a/run_agent.py +++ b/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.]"