Implements Wave 3 of the cleveractors-core integration (ADR-2026, ADR-2028).
## AgentFactory
- Added optional credentials: dict[str, dict[str, str]] | None parameter to
__init__. Stored as self.credentials.
- When creating LLMAgent instances, the full credentials dict is forwarded via
a new credentials constructor kwarg, threading per-request API keys without
touching the stored actor config_dict.
- Added explicit type annotation ReactiveAgentFactory: type[AgentFactory].
- _create_agent_instance raises ConfigurationError when credentials is not None
but does not contain an entry for the requested provider (AC4 compliance).
## LLMAgent
- Added optional credentials: dict[str, str] | None parameter to __init__.
Stored as self._credentials; config_dict is never modified.
- LangChain client construction deferred to first access (lazy init) via a
chat_model property backed by _ensure_chat_model(). The property setter
allows test-injection of mock models without going through the lazy-init path.
- _create_chat_model() dispatches to credential-injection / standalone paths.
- Extracted to llm_client.py and llm_providers.py for modularity.
- Defined shared module-level constants DEFAULT_MODEL, DEFAULT_TEMPERATURE,
DEFAULT_MAX_TOKENS to eliminate duplicated magic numbers across llm.py,
runtime.py, and factory.py.
- Added public credentials property exposing self._credentials.
- Added type narrowing guard in chat_model property to satisfy static analysis.
- Added temperature_override type validation in process_message.
- Guarded cleanup() null-assignment with _chat_model_lock (TOCTOU fix).
## SSRF Prevention (ADR-2028)
- _validate_base_url(): https-only, userinfo rejection, localhost rejection,
raw IP literal rejection, numeric-hostname (dotted-hex/octal/compact) rejection,
percent-decode loop with iteration cap (10), trailing-dot FQDN strip.
- DNS resolution intentionally omitted (blocking call in async path).
- Canonical URL reconstruction uses lowercased scheme.
## Credential Leak Prevention
- All logger.exception(...) replaced with logger.debug(..., exc_info=True).
- Error messages use type(e).__name__ instead of str(e).
- process_message exception chains broken with from None to prevent LangChain
authentication errors from leaking via __cause__.
## Runtime fixes
- Executor._execute_llm: shallow copy for setdefault mutations.
- Executor._execute_graph: restructured try/finally for resource cleanup.
- Uses DEFAULT_MODEL, DEFAULT_TEMPERATURE, DEFAULT_MAX_TOKENS from llm.py.
- Defensive copy of credentials dict in Executor.__init__ to prevent caller
mutation after construction.
## Test improvements
- Added BDD scenarios for llm_imports.py ImportError fallback branches using
sys.modules manipulation with a custom import hook, exercised via subprocess.
- Added BDD scenario for AgentFactory.create_agents_from_config() credential
threading to all created agents.
- Removed tautological dataclass test scenarios (NodeUsage, ActorResult) from
runtime_coverage.feature.
- Fixed misleading scenario title in runtime_coverage.feature (exception chain
suppressed, not chained) and updated step to assert __cause__ is None.
- Added CHANGELOG entry for ReactiveAgentFactory backward-compatibility alias.
- Removed duplicate log emission in _check_known_provider_domain.
ISSUES CLOSED: #12
- Fix bare excepts (replace with except Exception:)
- Remove wildcard star imports from test step files
- Add explicit imports for ConfigurationError, AgentCreationError
- Add 'from err' to raise statements in except blocks
- Fix loop variable binding with default arguments (B023)
- Organize imports, fix trailing whitespace and blank line whitespace
- Bump Python to 3.13 and update tool configurations