master
68 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4fe14c5b77 |
feat(agents): add offset-based pagination to file_read
CI / lint (pull_request) Successful in 1m40s
CI / quality (pull_request) Successful in 2m7s
CI / typecheck (pull_request) Successful in 3m23s
CI / build (pull_request) Successful in 1m6s
CI / security (pull_request) Successful in 4m51s
CI / integration_tests (pull_request) Successful in 3m14s
CI / unit_tests (pull_request) Successful in 5m24s
CI / coverage (pull_request) Successful in 6m51s
CI / status-check (pull_request) Successful in 7s
CI / benchmark (pull_request) Failing after 17m20s
CI / lint (push) Successful in 59s
CI / typecheck (push) Successful in 2m21s
CI / security (push) Successful in 1m16s
CI / quality (push) Successful in 51s
CI / build (push) Successful in 1m29s
CI / integration_tests (push) Successful in 3m28s
CI / unit_tests (push) Successful in 4m37s
CI / coverage (push) Successful in 4m58s
CI / status-check (push) Successful in 7s
CI / benchmark (push) Failing after 18m9s
`file_read` always read from position 0; `max_chars` (ADR-2030 D-3) only bounded where the returned content stopped, so once a large file was truncated there was no way to retrieve the next chunk short of falling back to the `shell` tool (`tail -c +N`, `sed -n`, `dd skip=...`) — the same class of awkward, error-prone detour ADR-2030 D-4 already eliminated for file paths mistaken for shell commands. Adds an optional `offset` character-position argument (default 0, byte-for-byte identical output when omitted) to both the tool schema (`llm_tools._BUILTIN_TOOL_SCHEMAS["file_read"]`) and the implementation (`ToolAgent._file_read_tool`). Combined with `max_chars` it returns the window `[offset, offset + max_chars)`, and the `[FILE_READ_SUCCESS]` header now signals continuation: `MORE_CONTENT_AT_OFFSET: N` when more content remains (N is the exact next offset, so callers never compute `offset + max_chars` themselves), or `NO_MORE_CONTENT_AT_OFFSET: N` when the requested offset is at or beyond end-of-file — a success response with empty content, not an error, since that is the expected terminal state of a correctly-paginating caller. Negative or non-integer offsets raise `ExecutionError`, mirroring the existing `max_chars` validation pattern. See docs/adr/ADR-2033-file-read-offset-pagination.md for the full set of design decisions, including why directory-listing pagination and byte-level seeking are deferred to a future issue. ISSUES CLOSED: #83 |
||
|
|
4b354f5b2d
|
feat(agents): surface timeout budget in tool schemas and errors
CI / lint (pull_request) Successful in 50s
CI / quality (pull_request) Successful in 55s
CI / security (pull_request) Successful in 1m25s
CI / typecheck (pull_request) Successful in 1m34s
CI / build (pull_request) Successful in 1m34s
CI / integration_tests (pull_request) Successful in 5m18s
CI / unit_tests (pull_request) Successful in 6m38s
CI / benchmark (pull_request) Failing after 42m6s
CI / lint (push) Successful in 47s
CI / typecheck (push) Successful in 1m26s
CI / security (push) Successful in 1m9s
CI / quality (push) Successful in 50s
CI / build (push) Successful in 1m36s
CI / integration_tests (push) Successful in 3m45s
CI / unit_tests (push) Successful in 5m42s
CI / coverage (push) Successful in 4m44s
CI / status-check (push) Successful in 6s
CI / benchmark (push) Successful in 44m16s
CI / coverage (pull_request) Successful in 4m57s
CI / status-check (pull_request) Successful in 7s
The `shell` and `http_request` built-in tools are the only two whose execution actually honors the tool agent's `timeout` config field (ToolAgent._execute_shell_command, ToolAgent._http_request_tool), but their LLM-facing schemas (_BUILTIN_TOOL_SCHEMAS in llm_tools.py) never told the model a timeout applied, and neither tool's timeout error named the config field that controls it. - _BUILTIN_TOOL_SCHEMAS["shell"] and ["http_request"] descriptions now state that execution is bounded by the agent's configured `timeout` (seconds), matching the existing precedent of documenting `file_read`'s `max_chars` behavior in its schema. - _execute_shell_command's timeout ExecutionError now names `timeout` as the adjustable config field, in addition to the elapsed seconds it already reported. - _http_request_tool now catches asyncio.TimeoutError distinctly from the generic `except Exception`, so a request timeout produces a message naming the config field instead of the previous generic "HTTP request failed" text (which gave no indication a timeout was the cause). Non-timeout HTTP failures are unaffected — same exception handling, same message shape as before. No change to enforcement mechanics: `self.timeout` remains the sole config field (default 1s, Actor Configuration Standard §4.5). This is schema/message content only, within the extension precedent already established by ADR-2030 (D-1 schema registry, D-4/D-6 error-quality improvements) — no new ADR was needed for this change. Tests: extended features/tool_agent.feature (updated the two existing shell/http_request timeout scenarios to also assert the new config-field hint) and features/llm_tools_coverage.feature (new scenarios asserting both schema descriptions mention the timeout budget). Full suite: 141 features / 2782 scenarios / 13172 steps passed; coverage 96.8% (>=96.5% threshold). Robot integration_tests: 27/27 suites passed. ASV benchmark_regression: no significant change, as expected for a messaging-only change. ISSUES CLOSED: #82 |
||
|
|
a8f68b8262
|
fix(graph): enforce USD budget at each agent invocation with pre-flight gate
CI / lint (pull_request) Successful in 50s
CI / typecheck (pull_request) Successful in 55s
CI / security (pull_request) Successful in 53s
CI / quality (pull_request) Successful in 38s
CI / unit_tests (pull_request) Successful in 3m15s
CI / integration_tests (pull_request) Successful in 1m14s
CI / build (pull_request) Successful in 39s
CI / coverage (pull_request) Successful in 3m16s
CI / status-check (pull_request) Successful in 3s
CI / lint (push) Successful in 37s
CI / typecheck (push) Successful in 56s
CI / security (push) Successful in 54s
CI / quality (push) Successful in 37s
CI / unit_tests (push) Successful in 3m15s
CI / integration_tests (push) Successful in 1m14s
CI / build (push) Successful in 39s
CI / coverage (push) Successful in 3m16s
CI / status-check (push) Successful in 3s
Enforces the USD budget (max_cost_usd) as a hard pre-flight gate before every node execution and at each main-agent ainvoke round inside the multi-turn tool loop, with pruning passes silently skipped when the budget is exhausted. Pre-flight gate (pure_graph.py): _check_budget_pre_flight() raises ExecutionError(kind='cost', reason='budget_exhausted') immediately before node execution in both _execute_from_node and all three branches of _stream_from_node. In-node budget gating (llm.py): _check_budget_before_invoke() gates every _retry_ainvoke() call inside _execute_tool_loop, process_message, and stream_message. _should_skip_pruning() skips pruning passes when budget exhausted. _accumulate_cost_after_invoke() computes USD cost from token counts using the pricing table and advances current_accumulated_cost so subsequent budget checks see the updated cost. ContextVar plumbing (retry.py, pure_graph.py): current_accumulated_cost, current_max_cost_usd, and current_pricing ContextVars carry per-node budget state from PureLangGraph into LLMAgent. Set before node.execute() via _set_budget_context_for_node, reset in finally blocks via _reset_budget_context. Reviewer fixes (rui.hu #80): - Move cost accumulation inside try block in terminal streaming branch to prevent ContextVar leak (was set after finally reset). - Standardize log precision to $%.6f across all budget messages. - Remove orphaned 'already exhausted' step (criterion unreachable). - Add dedicated streaming budget enforcement scenario. - Remove redundant = None default on _reset_budget_context pricing_token. ISSUES CLOSED: #76 |
||
|
|
2072bc3ad1 |
feat(langgraph): implement real type:tool graph node execution via ToolAgent dispatch
CI / lint (pull_request) Successful in 51s
CI / typecheck (pull_request) Successful in 58s
CI / security (pull_request) Successful in 55s
CI / quality (pull_request) Successful in 38s
CI / unit_tests (pull_request) Successful in 3m14s
CI / integration_tests (pull_request) Successful in 1m13s
CI / build (pull_request) Successful in 39s
CI / coverage (pull_request) Successful in 3m16s
CI / status-check (pull_request) Successful in 3s
CI / lint (push) Successful in 38s
CI / typecheck (push) Successful in 53s
CI / security (push) Successful in 52s
CI / quality (push) Successful in 36s
CI / unit_tests (push) Successful in 3m12s
CI / integration_tests (push) Successful in 1m9s
CI / build (push) Successful in 37s
CI / coverage (push) Successful in 3m12s
CI / status-check (push) Successful in 4s
Wires NodeConfig.tool and NodeConfig.static_config through to ToolAgent.invoke() so type: tool graph nodes execute real tool calls instead of returning no-ops. - NodeConfig: adds tool (str | None) and static_config (dict) - Node._execute_tool: creates ToolAgent, invokes with static config merged with threaded previous output, returns ToolMessage - runtime_dispatch: auto-creates ToolAgent for type: tool nodes without pre-registered agents - pure_graph: parses tool:/config: YAML keys into NodeConfig - BDD: 11 scenarios, 33 steps, all passing Closes #75 |
||
|
|
9ec578f229 |
feat(agents): add tool_agent_class parameter to AgentFactory and create_executor
CI / lint (pull_request) Successful in 44s
CI / typecheck (pull_request) Successful in 49s
CI / security (pull_request) Successful in 47s
CI / quality (pull_request) Successful in 33s
CI / unit_tests (pull_request) Successful in 3m4s
CI / integration_tests (pull_request) Successful in 1m2s
CI / build (pull_request) Successful in 33s
CI / coverage (pull_request) Successful in 3m5s
CI / status-check (pull_request) Successful in 2s
CI / lint (push) Successful in 31s
CI / typecheck (push) Successful in 46s
CI / security (push) Successful in 46s
CI / quality (push) Successful in 31s
CI / unit_tests (push) Successful in 3m2s
CI / integration_tests (push) Successful in 1m2s
CI / build (push) Successful in 31s
CI / coverage (push) Successful in 3m4s
CI / status-check (push) Successful in 6s
Extends AgentFactory.__init__ and create_executor() with a keyword-only
tool_agent_class: type[ToolAgent] = ToolAgent parameter so callers can
inject a custom ToolAgent subclass at runtime without monkey-patching
module globals (resolves the five-module setattr loop in cleveragents-webapp).
**AgentFactory** (agents/factory.py):
- Accepts tool_agent_class keyword argument; validates it is a ToolAgent
subclass at construction time (fail-fast, CONTRIBUTING.md §Argument
Validation).
- Stores as self._tool_agent_class and uses it to populate
self.agent_types['tool'], so every create_agent('tool') call
instantiates the supplied subclass.
- Forwards tool_agent_class to LLMAgent when creating 'llm'-typed agents
so the LLM tool-calling loop honours the injected subclass.
- Default ToolAgent is preserved, so all existing callers are unaffected.
**LLMAgent** (agents/llm.py):
- Accepts a keyword-only tool_agent_class: type[ToolAgent] = ToolAgent
argument, validated as a ToolAgent subclass at construction time.
- The multi-turn tool-call loop (_execute_tool_loop) constructs its
ephemeral tool executors via self._tool_agent_class(...) across all
three dispatch sites (regular loop, budget-exhaustion synthesis round,
and stuck-model synthesis round), so the LLM tool-calling path no
longer falls back to the module-level ToolAgent. This closes the gap
that previously prevented removing the webapp monkey-patch for the LLM
tool-calling path.
**Executor / create_executor** (runtime.py):
- Executor.__init__ accepts and validates tool_agent_class; stores as
self.tool_agent_class.
- create_executor() accepts tool_agent_class and forwards it to Executor.
**Dispatch paths** (runtime_dispatch.py):
- _execute_tool: uses executor.tool_agent_class(...) instead of the
module-level ToolAgent(...) so the direct-construction path (bypassing
AgentFactory) also honours the injected subclass.
- _execute_graph and _execute_graph_stream: pass
tool_agent_class=executor.tool_agent_class to AgentFactory so graph
nodes using tool-typed agents pick up the subclass.
- _execute_llm and _execute_llm_stream: pass
tool_agent_class=executor.tool_agent_class to AgentFactory so the
single-LLM actor's internally-created LLMAgent (and its tool-calling
loop) honours the injected subclass. Without this, a single LLM actor
that makes tool calls silently fell back to the base ToolAgent.
- _execute_multi_actor: passes tool_agent_class=executor.tool_agent_class
to the sub-Executor so every actor inside a multi-actor bundle (tool,
graph, or LLM) honours the injected subclass. Without this, all
dispatch paths reachable through a multi-actor bundle fell back to the
base ToolAgent.
- Removed the now-unused module-level ToolAgent import; the _execute_tool
docstring cross-reference is fully qualified so it still resolves.
All six dispatch paths that construct or dispatch tool agents now
forward the injected subclass.
**ReactiveCleverAgentsApp** (core/application.py):
- Removed the redundant register_agent_type('tool', ToolAgent) call from
load_configuration and the dead type_map re-registration block in
_create_agents; AgentFactory.__init__ already owns the 'tool'
registration. This drops the last hardcoded module-level ToolAgent
references flagged in the issue #73 audit.
**Tests** (features/tool_agent_class_injection.feature + steps):
- 17 BDD scenarios covering: default class when parameter omitted;
custom subclass stored on factory and executor; invalid class rejected
with ConfigurationError (factory, executor, and LLMAgent); factory
create_agent returns custom instance; _execute_tool dispatch
instantiates custom subclass; the LLM tool loop instantiates the
injected subclass; isinstance LSP compatibility through the factory;
the _execute_llm dispatch path forwarding tool_agent_class to
AgentFactory (single-LLM actor making a tool call); and the
_execute_multi_actor dispatch path forwarding tool_agent_class to the
sub-Executor. The two new dispatch-path scenarios were verified to
fail without the source fixes (instantiation_count=0), confirming
they guard the injection contract for the single-LLM and multi-actor
paths.
**Updated tests** (features/steps/runtime_coverage_steps.py,
features/steps/runtime_extended_coverage_steps.py):
- Removed the now-dead patch('cleveractors.runtime_dispatch.ToolAgent')
mocks (and their mock instances) that no longer intercept _execute_tool
after it switched to executor.tool_agent_class. The tool-actor scenario
continues to pass via the real ToolAgent; the normal-coverage branch
uses patch.object(ToolAgent, 'process_message', ...) to stub tool
execution.
**Updated test** (features/steps/credential_executor_paths_steps.py):
- The _execute_multi_actor credentials-forwarding scenario intercepts
Executor.__init__ with a capturing_init to assert the credentials
dict reaches the sub-Executor. Now that _execute_multi_actor forwards
tool_agent_class to the sub-Executor, capturing_init accepts and
forwards that keyword so the assertion continues to pass.
**Removed obsolete test** (features/application_coverage_gaps.feature):
- Dropped the '_create_agents registers built-in agent types' scenario
and its step definitions; the behaviour it asserted (redundant
re-registration) was removed from _create_agents.
ISSUES CLOSED: #73
|
||
|
|
ceb577e405
|
fix(nodes): propagate ExecutionError from retry mechanism instead of swallowing it
CI / lint (pull_request) Successful in 46s
CI / typecheck (pull_request) Successful in 51s
CI / security (pull_request) Successful in 50s
CI / quality (pull_request) Successful in 34s
CI / unit_tests (pull_request) Successful in 3m7s
CI / integration_tests (pull_request) Successful in 1m8s
CI / build (pull_request) Successful in 34s
CI / coverage (pull_request) Successful in 3m8s
CI / status-check (pull_request) Successful in 3s
CI / lint (push) Successful in 46s
CI / typecheck (push) Successful in 51s
CI / security (push) Successful in 51s
CI / quality (push) Successful in 34s
CI / unit_tests (push) Successful in 3m7s
CI / integration_tests (push) Successful in 1m8s
CI / build (push) Successful in 35s
CI / coverage (push) Successful in 3m9s
CI / status-check (push) Successful in 3s
When all LLM communication retries are exhausted, call_with_retry() raises ExecutionError(kind=\"timeout\"). Two broad except Exception handlers in Node._execute_agent() and Node.execute() systematically caught this, converting the timeout into an error string in graph state or — in the streaming path — an empty response. Added targeted except ExecutionError as e handlers that check e.kind and re-raise. Non-timeout ExecutionError instances (tool errors, etc.) continue to be caught and converted to error strings for graceful graph continuation. Extend _is_http_comms_error() in retry.py to recognize provider SDK connection errors (APIConnectionError, APITimeoutError) by class name, so they are retried with exponential backoff instead of propagating immediately. Add safety net in process_message() to wrap non-retried connection errors as ExecutionError(kind='timeout') for correct propagation through the node layer (issue #71). Two new BDD scenarios verify: - ExecutionError(kind=\"timeout\") propagates with correct kind/reason - Non-timeout ExecutionError is still caught gracefully ISSUES CLOSED: #71 |
||
|
|
52451b2550
|
test(agents): disable retry in error-path tests to avoid unnecessary delays
CI / lint (pull_request) Successful in 36s
CI / typecheck (pull_request) Successful in 52s
CI / security (pull_request) Successful in 50s
CI / quality (pull_request) Successful in 34s
CI / unit_tests (pull_request) Successful in 3m9s
CI / integration_tests (pull_request) Successful in 1m3s
CI / build (pull_request) Successful in 35s
CI / coverage (pull_request) Successful in 3m9s
CI / status-check (pull_request) Successful in 4s
CI / lint (push) Successful in 46s
CI / typecheck (push) Successful in 51s
CI / security (push) Successful in 50s
CI / quality (push) Successful in 34s
CI / unit_tests (push) Successful in 3m8s
CI / build (push) Successful in 37s
CI / coverage (push) Successful in 3m8s
CI / integration_tests (push) Successful in 1m12s
CI / status-check (push) Successful in 2s
Pre-existing tests that inject error-raising mock chat models (raising LangChainException or RuntimeError) were triggering the full exponential-backoff retry loop (~63.5s per test). With 6 such tests, this added minutes to the suite. Set max_retries=0 in each error-path step definition so the retry loop fails fast on the first attempt, preserving the test contract while avoiding unnecessary sleep delays. |
||
|
|
55734cd512
|
feat(agents): add LLM agent retry mechanisms with exponential backoff
- Documents the retry feature implementation (issue #69, ADR-2032) Introduce configurable retry mechanisms (ADR-2032) for all LLM agent communications (main agent and pruning agent) with exponential backoff. - New config fields: max_retries (default 7), max_retry_time (default 60s) -1 sentinel disables the corresponding guard (infinite retries/time) - Exponential backoff: initial 0.5s, doubled each retry - Termination: whichever comes first (max_retries or max_retry_time), with -1 disabling the respective check - Counter and accumulated wait time reset on success - Timeout ExecutionError includes URL, actor graph name, and retry stats - Uniform scope: applies to both main agent and pruning agent - Timeout raises ExecutionError(kind="timeout") with URL, actor graph name, retry count, and accumulated wait time in the message (D-7) - Same client/connection reused across retries, no re-establishment (D-6) - Uniform scope: applies to both main agent and pruning agent (D-5) - Graph name threaded through state metadata and ContextVar for error reporting - BDD tests (7 scenarios) for retry behaviour including -1 sentinel cases - Reject max_retries/max_retry_time values < -1 with ConfigurationError - Reset current_graph_name ContextVar after node execution - Wrap no-tools astream() with per-chunk granular retry via call_with_retry - Update ADR D-7 with symmetrical disabled-guard example - Update CHANGELOG to reflect full feature scope ISSUES CLOSED: #69 |
||
|
|
306f114b8a |
feat(agents): add tool-call support to LLMAgent.stream_message
CI / lint (pull_request) Successful in 47s
CI / typecheck (pull_request) Successful in 51s
CI / security (pull_request) Successful in 50s
CI / quality (pull_request) Successful in 33s
CI / unit_tests (pull_request) Successful in 3m7s
CI / integration_tests (pull_request) Successful in 1m10s
CI / build (pull_request) Successful in 35s
CI / coverage (pull_request) Successful in 3m9s
CI / status-check (pull_request) Successful in 3s
CI / lint (push) Successful in 34s
CI / typecheck (push) Successful in 51s
CI / security (push) Successful in 50s
CI / quality (push) Successful in 33s
CI / unit_tests (push) Successful in 3m6s
CI / integration_tests (push) Successful in 1m7s
CI / build (push) Successful in 33s
CI / coverage (push) Successful in 3m7s
CI / status-check (push) Successful in 3s
Extract the entire multi-turn tool-call orchestration from the inline loop inside process_message() into a new shared private method _execute_tool_loop(). Both process_message() and stream_message() now delegate to this single implementation, eliminating the previous capability gap where stream_message() had no tool-calling support. Design: - _ToolLoopResult dataclass: carries final_response, accumulated_prompt, accumulated_completion, budget_exhausted, and synthesis_was_run. Both callers read token counts from this object instead of local sentinels. - _ToolLoopError exception: wraps any exception raised inside the helper and carries partial accumulated token counts + any_invocation_made flag. Callers catch this, set their _captured_prompt/_captured_completion sentinels (billing integrity), then re-raise the original cause. - _execute_tool_loop(): extracted verbatim from process_message(). Contains the full ainvoke() loop, per-tool ToolAgent dispatch, token accumulation across rounds, token-budget exhaustion + synthesis path (§4.4.7 D-4), stuck-model synthesis prompt, and tool-output pruning pass (§4.4.8). On exception, wraps in _ToolLoopError for the caller. process_message() refactor: - Replaces ~490 lines of inline tool loop with a 15-line delegation. For tool-configured actors: awaits _execute_tool_loop(), extracts response text and token counts from the result. For no-tool actors: single plain ainvoke() (unchanged behaviour). Billing-integrity sentinels (_captured_prompt/_captured_completion) are set from _ToolLoopError on exception and from the result on success. All existing Behave scenarios continue to pass unmodified. stream_message() integration: - When tools are configured: awaits _execute_tool_loop() and yields the final response as a single content chunk. Token counts come from the result's accumulated_prompt/accumulated_completion fields (spanning all tool rounds + pruning passes). Memory update is performed before the generator returns. Billing sentinels are updated from _ToolLoopError on exception. - When no tools are configured: the existing astream() token-by-token path runs completely unchanged. Single-chunk trade-off: when tools are involved, the user already waits for tool execution before the final answer begins, so yielding that answer as one chunk vs. token-by-token has minimal UX impact. The alternative (re-calling astream() after ainvoke() produced the final response) would double LLM cost for every tool-using request. Webapp workaround: the actor_config_has_tools fallback in cleveragents/cleveragents-webapp#328 (stream_fallback_to_non_stream in _execute_via_cleveractors_stream / _generate_actor_sse) can now be removed. Tests added: - features/llm_agent_tool_loop.feature + steps: 10 Behave scenarios covering _execute_tool_loop() internals independently (single round, multi-round, max_rounds exhaustion, token accumulation, budget exhaustion, stuck-model synthesis, pruning pass, tool dispatch error, exception path with partial counts, pre-invocation ConfigurationError). - features/llm_agent_stream_tool_calls.feature + steps: 7 Behave scenarios for stream_message() with tool-call support (single chunk output, two-round token sum, final text, no-tools astream unchanged, no-tools token counts, exception billing integrity, memory update). - robot/llm_tool_calling.robot: two new Robot integration tests: 'Streaming Path Exercises Tool Loop Via Execute Stream' and 'Streaming Path Accumulates Tokens Across Two Tool Rounds'. - robot/ToolCallingTestLib.py: new keywords and create_executor_with_multi_round_tool_calling_agent factory. Quality gates: format ✓ lint ✓ typecheck ✓ security_scan ✓ unit_tests ✓ integration_tests ✓ coverage_report ✓ (97.0%) ISSUES CLOSED: #67 |
||
|
|
8a7e353767
|
fix(actors): llm token consumption undercounted
CI / lint (pull_request) Successful in 35s
CI / typecheck (pull_request) Successful in 50s
CI / security (pull_request) Successful in 49s
CI / quality (pull_request) Successful in 33s
CI / unit_tests (pull_request) Successful in 3m5s
CI / integration_tests (pull_request) Successful in 1m19s
CI / build (pull_request) Successful in 35s
CI / coverage (pull_request) Successful in 3m7s
CI / status-check (pull_request) Successful in 3s
CI / lint (push) Successful in 33s
CI / typecheck (push) Successful in 50s
CI / security (push) Successful in 50s
CI / quality (push) Successful in 33s
CI / integration_tests (push) Successful in 1m11s
CI / build (push) Successful in 34s
CI / unit_tests (push) Failing after 14m51s
CI / coverage (push) Has been cancelled
CI / status-check (push) Has been cancelled
- enforces pruning model token-tracking metadata availability for cost tracking - also temporarily enforces pruning-model match ISSUES CLOSED: #65 |
||
|
|
bcce5dc42a
|
fix(agents): validate tool name against declared tools list before dispatch
CI / lint (pull_request) Successful in 43s
CI / typecheck (pull_request) Successful in 47s
CI / security (pull_request) Successful in 46s
CI / quality (pull_request) Successful in 31s
CI / unit_tests (pull_request) Successful in 3m1s
CI / integration_tests (pull_request) Successful in 1m1s
CI / build (pull_request) Successful in 31s
CI / coverage (pull_request) Successful in 3m2s
CI / status-check (pull_request) Successful in 2s
CI / lint (push) Successful in 31s
CI / typecheck (push) Successful in 46s
CI / security (push) Successful in 47s
CI / quality (push) Successful in 30s
CI / unit_tests (push) Successful in 3m9s
CI / integration_tests (push) Successful in 1m7s
CI / build (push) Successful in 37s
CI / coverage (push) Successful in 3m11s
CI / status-check (push) Successful in 3s
ISSUES CLOSED: #63 |
||
|
|
964e87cc0f
|
feat(tool-loop): add token-budget awareness and tool output pruning
CI / lint (pull_request) Successful in 1m31s
CI / typecheck (pull_request) Successful in 50s
CI / security (pull_request) Successful in 50s
CI / quality (pull_request) Successful in 33s
CI / unit_tests (pull_request) Successful in 3m5s
CI / integration_tests (pull_request) Successful in 1m7s
CI / build (pull_request) Successful in 34s
CI / coverage (pull_request) Successful in 3m7s
CI / status-check (pull_request) Successful in 3s
CI / lint (push) Successful in 33s
CI / typecheck (push) Successful in 50s
CI / security (push) Successful in 50s
CI / quality (push) Successful in 33s
CI / integration_tests (push) Successful in 1m12s
CI / build (push) Successful in 33s
CI / unit_tests (push) Successful in 3m5s
CI / coverage (push) Successful in 3m6s
CI / status-check (push) Successful in 6s
Implement token-budget awareness (§4.4.7) and tool output pruning (§4.4.8-9) for the multi-turn tool-call loop to prevent context-window exhaustion. Token budget: tracks estimated token count before each ainvoke(), emits warning at 75% budget, injects synthesis prompt at exhaustion with one final tool-call round. Estimation via chat_model.get_num_tokens() with len(content)//4 heuristic fallback. Config key: token_budget_percent. Tool output pruning: implicit LLM extraction pass scoped to file_read calls. Schema augmentation injects output_prune (bool) and output_prune_context (string) meta-arguments. Pruning model responds with [PRUNE_INFO_START/END] and [PRUNE_OUTPUT_START/END] markers. Config keys: allow_tool_output_pruning, pruning_model. - Capture output_prune value in budget synthesis flow with proper guard - Fix ADR D-4: synthesis prompt is HumanMessage not SystemMessage - Fix Robot tests: replace /dev/null with temp file path ADR-2031 documents all specification extensions. 21 new BDD scenarios, 5 Robot integration tests, 16 ASV benchmarks. ISSUES CLOSED: #61 |
||
|
|
227e2feece
|
feat(agents): implement multi-turn tool-call loop and sandbox improvements
CI / build (pull_request) Successful in 45s
CI / quality (pull_request) Successful in 58s
CI / lint (pull_request) Successful in 1m15s
CI / typecheck (pull_request) Successful in 1m15s
CI / security (pull_request) Successful in 1m15s
CI / integration_tests (pull_request) Successful in 1m24s
CI / unit_tests (pull_request) Successful in 3m25s
CI / coverage (pull_request) Successful in 3m10s
CI / status-check (pull_request) Successful in 6s
CI / lint (push) Successful in 42s
CI / quality (push) Successful in 48s
CI / typecheck (push) Successful in 51s
CI / security (push) Successful in 49s
CI / build (push) Successful in 47s
CI / integration_tests (push) Successful in 1m10s
CI / unit_tests (push) Successful in 3m9s
CI / coverage (push) Successful in 3m7s
CI / status-check (push) Successful in 3s
Implements the complete LLM agent tool-calling pipeline — the tool-calling path was broken in multiple ways: tools from agent config were not passed to the LLM, tool execution was single-pass (the model could not see tool results or make follow-up calls), tool errors were discarded, and shell/ python_exec tools were never registered. Key changes: - Multi-turn tool-call loop: passes tools to the LLM via ainvoke(tools=...) and re-invokes after each batch of ToolResults, with a configurable round limit (tool_max_rounds config / TOOL_MAX_ROUNDS env var, default 20). - Tool schema module (llm_tools.py): normalize_tool_entry() converts string tool names and config dicts to OpenAI function-calling format with full parameter schemas and LLM-facing guidance (max_chars for file_read, sandbox builtins for python_exec, etc.). - file_read enhancements: directory listing with file sizes and type indicators, max_chars truncation to prevent context overflow, shell command detection with redirect to shell tool, file-not-found recovery hints with parent directory listing. - python_exec sandbox: stdout capture so print() produces visible output, NameError guidance directing LLM to file_read/file_write instead of using open(). - shell/python_exec tool registration in builtin_tools when allow_shell / exec_python is enabled. - create_subprocess_shell for shell commands (was create_subprocess_exec, which blocks pipes/redirections/chains). - Tool error propagation: ExecutionError/ConfigurationError details included in ToolMessages for LLM self-correction. - Stuck-model recovery: injects synthesizing prompt when model exhausts tool rounds without producing content. - LLM provider error details preserved in ExecutionError messages instead of generic "LLM processing failed". - Empty message filtering in _prepare_conversation_history() prevents whitespace-only messages from polluting downstream conversation history in multi-agent pipelines. Closes #59 |
||
|
|
8f986c1e31
|
test(coverage): add coverage scenarios for pre-existing code paths
Additional BDD scenarios covering registry resolver errors, cache TTL expiry, runtime dispatch normalization, template base edge cases, validation actor coverage gaps, and YAML Jinja loader deferred rendering. |
||
|
|
ed9c4dc95d |
fix(llmagent): do not close shared cached httpx clients in cleanup()
CI / quality (pull_request) Successful in 34s
CI / build (pull_request) Successful in 57s
CI / integration_tests (pull_request) Successful in 1m26s
CI / unit_tests (pull_request) Successful in 3m28s
CI / lint (pull_request) Successful in 1m1s
CI / typecheck (pull_request) Successful in 1m0s
CI / security (pull_request) Successful in 59s
CI / coverage (pull_request) Successful in 3m7s
CI / status-check (pull_request) Successful in 3s
CI / lint (push) Successful in 40s
CI / quality (push) Successful in 40s
CI / build (push) Successful in 40s
CI / typecheck (push) Successful in 1m5s
CI / security (push) Successful in 1m5s
CI / integration_tests (push) Successful in 1m20s
CI / unit_tests (push) Successful in 3m14s
CI / coverage (push) Successful in 3m2s
CI / status-check (push) Successful in 3s
LLMAgent.cleanup() previously iterated over hard-coded provider SDK client attributes (root_async_client, root_client, _async_client, _client) and called close() on each. Recent langchain-anthropic and langchain-openai versions cache their default httpx clients via module-level lru_cache functions. Closing those clients poisoned the cache: every subsequent ChatAnthropic/ChatOpenAI instance in the same process received the same closed httpx client and failed with a connection error. Fix: cleanup() now only releases the agent's own reference to the chat model (self._chat_model = None). The removed _KNOWN_CLIENT_ATTRS class variable has been deleted and ClassVar removed from the typing import. The concurrent-idempotency guarantee is preserved: the lock is still acquired before nulling _chat_model, so two concurrent cleanup() calls cannot both see a non-None model and attempt conflicting operations. The four provider SDK client-closing scenarios in credential_injection.feature and llm_missing_coverage.feature are removed as they tested the old (buggy) behaviour. Their step definitions are removed from credential_cleanup_steps.py (now only carries the resolve_class_ref patch step) and llm_missing_coverage_steps.py is updated with the corrected assertions. Six new regression BDD scenarios tagged @tdd_issue @tdd_issue_57 are added in features/llm_cleanup_shared_client.feature, covering all four provider SDK client attribute paths (Anthropic _async_client/_client, OpenAI root_async_client/root_client) and two end-to-end two-agent scenarios that prove a second agent can run successfully after the first is cleaned up. ISSUES CLOSED: #57 |
||
|
|
57b1a2e70e
|
test(unit): increase BDD coverage to 97% for registry, LLM, YAML template error branches
Adds 4 Behave BDD feature files with 15 scenarios covering previously untested error-path branches: - registry_resolver_errors.feature (7 scenarios): Invalid semver, empty version lists, alias mismatches, ID/local/unknown reference error handling - llm_agent_config_validation.feature (4 scenarios): Empty name, non-dict config, non-TemplateRenderer, non-str credential key validation - llm_provider_url_canonicalization.feature (2 scenarios): URL canonicalization with explicit port and scheme lowercasing - yaml_template_non_dict_errors.feature (2 scenarios): Non-dict YAML parse results in template engine and inline Jinja handler Coverage: 96.8% -> 97.1% |
||
|
|
517dfb4cce
|
feat(registry): implement local namespace reference resolution
CI / lint (pull_request) Successful in 35s
CI / quality (pull_request) Successful in 45s
CI / integration_tests (pull_request) Successful in 1m2s
CI / typecheck (pull_request) Successful in 1m10s
CI / security (pull_request) Successful in 1m10s
CI / build (pull_request) Successful in 42s
CI / unit_tests (pull_request) Successful in 3m21s
CI / coverage (pull_request) Successful in 3m7s
CI / status-check (pull_request) Successful in 3s
CI / quality (push) Successful in 50s
CI / security (push) Successful in 51s
CI / lint (push) Successful in 54s
CI / typecheck (push) Successful in 58s
CI / integration_tests (push) Successful in 1m0s
CI / build (push) Successful in 43s
CI / unit_tests (push) Successful in 3m23s
CI / coverage (push) Successful in 3m14s
CI / status-check (push) Successful in 3s
Implements the local:<path> reference scheme per Package Registry Standard v1.0.0 §5.3. Closes #46 |
||
|
|
d93c32ccee |
feat(streaming): add Executor.execute_stream() returning AsyncIterator[str] for token-by-token delivery
CI / lint (pull_request) Successful in 43s
CI / quality (pull_request) Successful in 49s
CI / build (pull_request) Successful in 47s
CI / typecheck (pull_request) Successful in 1m9s
CI / security (pull_request) Successful in 1m9s
CI / integration_tests (pull_request) Successful in 1m8s
CI / unit_tests (pull_request) Successful in 3m9s
CI / coverage (pull_request) Successful in 3m7s
CI / status-check (pull_request) Successful in 3s
CI / lint (push) Successful in 55s
CI / typecheck (push) Successful in 56s
CI / security (push) Successful in 57s
CI / quality (push) Successful in 48s
CI / build (push) Successful in 48s
CI / integration_tests (push) Successful in 1m10s
CI / unit_tests (push) Successful in 3m26s
CI / coverage (push) Failing after 13m5s
CI / status-check (push) Has been cancelled
Implements the full streaming execution path for the CleverThis router: - **LLMAgent.stream_message(message, context)** (agents/llm.py): Async generator calling self.chat_model.astream(messages) and yielding str(chunk.content) per chunk. Captures token counts from the final chunk's usage_metadata using the same _safe_int() fallback chain as process_message(). Sets _last_token_usage and last_token_usage_var after exhaustion. - **Node.stream_agent(state)** (langgraph/nodes.py): Async generator that mirrors _execute_agent() but uses agent.stream_message() for LLMAgent instances and falls back to process_message() for non-LLM agents. Captures per-node token usage into self._last_stream_usage after stream exhaustion. - **PureLangGraph.execute_stream()** (langgraph/pure_graph.py): Async generator mirroring execute(). Calls _stream_from_node() which uses astream() for all AGENT nodes (buffering tokens for intermediate nodes, yielding only from the terminal node) and ainvoke() for non-AGENT nodes. Stores final state and node usages in _last_stream_state / _last_stream_node_usages for post-stream access. Same limit enforcement (timeout_ms, max_depth, max_model_calls, max_tool_calls) as _execute_from_node(). - **_execute_llm_stream() / _execute_graph_stream()** (runtime_dispatch.py): Async generator dispatch functions mirroring _execute_llm() / _execute_graph(). Set executor.last_result after stream exhaustion using collected token usage. - **Executor.execute_stream(message)** (runtime.py): Public async generator that dispatches to _execute_llm_stream() for llm actors and _execute_graph_stream() for graph actors. Raises ConfigurationError for tool/multi_actor types. Resets last_result to None at entry; callers must exhaust the iterator. - **Executor.last_result** (runtime.py): ActorResult | None attribute set by execute_stream() after the iterator is exhausted. - Intermediate AGENT nodes use astream() internally (tokens buffered, not yielded to caller); terminal AGENT nodes yield buffered tokens. This avoids double- running the terminal LLM while satisfying AC2. - Non-AGENT (FUNCTION/TOOL) nodes always use ainvoke() via node.execute(). - timeout_ms in execute_stream() buffers all tokens via asyncio.wait_for() around _collect_stream_tokens(), then yields them — this is simpler than wrapping an async generator directly. - Tool and multi_actor actor types raise ConfigurationError (no streaming path). - 60 new BDD scenarios in features/execute_stream.feature covering all acceptance criteria: token delivery, last_result population, token count extraction, limit enforcement (timeout, model_calls, tool_calls), error paths, graph streaming, non-AGENT node paths, parallel execution paths, and edge cases. - All 2395 existing unit tests continue to pass. - Coverage: 96.5% (meets threshold). ISSUES CLOSED: #16 |
||
|
|
e8bd348c77
|
feat(registry): implement RegistryError hierarchy with typed exceptions
CI / lint (pull_request) Successful in 41s
CI / typecheck (pull_request) Successful in 56s
CI / security (pull_request) Successful in 51s
CI / quality (pull_request) Successful in 53s
CI / integration_tests (pull_request) Successful in 1m10s
CI / build (pull_request) Successful in 43s
CI / unit_tests (pull_request) Successful in 3m30s
CI / coverage (pull_request) Successful in 3m22s
CI / status-check (pull_request) Successful in 4s
CI / quality (push) Successful in 45s
CI / security (push) Successful in 48s
CI / lint (push) Successful in 49s
CI / typecheck (push) Successful in 50s
CI / build (push) Successful in 49s
CI / integration_tests (push) Successful in 1m9s
CI / unit_tests (push) Successful in 3m13s
CI / coverage (push) Successful in 3m7s
CI / status-check (push) Successful in 3s
Add RegistryError base with message, details, and original_reference fields. Implement all 9 typed exception types per Package Registry Standard §13.2: PackageNotFoundError, InvalidPackageIdError, InvalidPackageReferenceError, VersionNotFoundError, ValidationError, AuthenticationRequiredError, AccessDeniedError, ConflictError, and RegistryNetworkError. RegistryNetworkError carries status_code and url for transport failures. __str__ includes original_reference when available. exception_for_status() maps HTTP codes to typed exceptions. _ERROR_TYPE_MAP enables error-type parsing from JSON error bodies. 23 Behave BDD scenarios in features/registry_exceptions.feature. 12 Robot Framework integration tests in robot/exceptions.robot. Existing registry_http_client tests updated for InternalServerError removal. ISSUES CLOSED: #29 |
||
|
|
054b432197
|
feat(registry): implement RegistryCache with LRU eviction and TTL
CI / lint (pull_request) Successful in 56s
CI / typecheck (pull_request) Successful in 56s
CI / security (pull_request) Successful in 1m2s
CI / quality (pull_request) Successful in 41s
CI / integration_tests (pull_request) Successful in 1m11s
CI / build (pull_request) Successful in 45s
CI / unit_tests (pull_request) Successful in 3m29s
CI / coverage (pull_request) Successful in 3m14s
CI / status-check (pull_request) Successful in 4s
CI / lint (push) Successful in 48s
CI / typecheck (push) Successful in 58s
CI / security (push) Successful in 1m1s
CI / quality (push) Successful in 39s
CI / integration_tests (push) Successful in 1m15s
CI / build (push) Successful in 41s
CI / unit_tests (push) Successful in 3m31s
CI / coverage (push) Successful in 3m27s
CI / status-check (push) Successful in 3s
- Add RegistryCache with LRU eviction, TTL expiry, SHA-1 content validation - Add CacheFactory for centralised cache configuration (Factory Method pattern) - Add CacheStats dataclass for hit/miss/eviction observability - Implement singleflight coalescing for concurrent-miss protection - Use tuple keys for resolve store to prevent separator collision - Fix cancellation-safe singleflight via plain Future + asyncio.shield - Independent max_size budgets per store (content + resolve) - Concurrent test assertions strengthened - All test imports moved to module level per CONTRIBUTING.md - Unused dead code removed from tests ISSUES CLOSED: #28 |
||
|
|
17d99abce7 |
feat(execution-limits): add structured ExecutionError kind/reason fields; enforce all 5 execution limits in PureLangGraph
CI / quality (pull_request) Successful in 36s
CI / lint (pull_request) Successful in 39s
CI / security (pull_request) Successful in 58s
CI / build (pull_request) Successful in 58s
CI / typecheck (pull_request) Successful in 1m2s
CI / integration_tests (pull_request) Successful in 1m14s
CI / unit_tests (pull_request) Successful in 3m35s
CI / coverage (pull_request) Successful in 3m34s
CI / status-check (pull_request) Successful in 3s
CI / lint (push) Successful in 47s
CI / typecheck (push) Successful in 46s
CI / build (push) Successful in 53s
CI / quality (push) Successful in 1m4s
CI / security (push) Successful in 1m7s
CI / integration_tests (push) Successful in 1m13s
CI / unit_tests (push) Successful in 3m43s
CI / coverage (push) Successful in 3m42s
CI / status-check (push) Successful in 3s
AC1: ExecutionError gains kind (categorical: depth, model_calls, tool_calls,
timeout, cost) and reason (sub-code: budget_exhausted or
missing_pricing_entry) fields, both defaulting to empty string. All existing
raise ExecutionError(msg) call sites are backward-compatible.
AC2: PureLangGraph.__init__ accepts limits: dict[str, Any] and
pricing: dict[str, Any] (both default to {}). When limits['max_depth'] is
supplied, a depth breach raises ExecutionError(kind='depth') instead of
silently returning the current message (the old behaviour was a silent
data-loss bug). When no max_depth limit is supplied, the legacy heuristic
(max(2000, len(nodes)*50)) is used with a silent cap for backward
compatibility with existing callers that do not pass limits.
AC3: max_model_calls is checked before each NodeType.AGENT execution;
counter increments after the check so the limit is enforced before the
(limit+1)-th invocation. Breach raises ExecutionError(kind='model_calls').
AC4: max_tool_calls is checked before each NodeType.TOOL execution; same
pattern as model_calls. Breach raises ExecutionError(kind='tool_calls').
AC5: execute() wraps _execute_from_node() in asyncio.wait_for() when
limits['timeout_ms'] is set. asyncio.TimeoutError is caught and re-raised
as ExecutionError(kind='timeout') so the router can map it to HTTP 429.
AC6+AC7: After each LLM node, cost is computed from the pricing table
(rates are USD per million tokens, matching ADR-2029 example values such
as gpt-4.1-mini prompt=/bin/zsh.15/1M). Cost breach raises
ExecutionError(kind='cost', reason='budget_exhausted'). A missing
provider or model entry raises ExecutionError(kind='cost',
reason='missing_pricing_entry') — proceeding with assumed zero cost is
forbidden per ADR-2029. An empty pricing dict ({}) disables all cost
checking (cost tracking was not requested).
AC8: ExecutionError re-exported from cleveractors/__init__.py and
added to __all__.
Wire: runtime_dispatch._execute_graph() now passes executor.limits and
executor.pricing to PureLangGraph so the limits reach the enforcement
layer.
ExecutionError is also re-raised (not swallowed) in _execute_from_node()'s
broad except block so limit errors always propagate to the caller.
New BDD tests (features/execution_limits.feature, 19 scenarios):
- ExecutionError defaults kind and reason to empty strings
- ExecutionError accepts kind and reason keyword arguments
- ExecutionError exported from cleveractors top-level package
- Graph exceeding max_depth raises ExecutionError(kind='depth')
- Two AGENT nodes with max_model_calls=1 raises on 2nd call
- Two TOOL nodes with max_tool_calls=1 raises on 2nd call
- Slow node with timeout_ms=50 raises ExecutionError(kind='timeout')
- LLM node exceeding max_cost_usd raises budget_exhausted
- Unknown provider/model in pricing raises missing_pricing_entry
- Empty pricing table skips cost calculation entirely
- Executor limits/pricing propagation verified
Quality gates: lint pass, typecheck 0 errors,
unit_tests 2317/2317 pass, integration_tests pass,
coverage 97.17% (threshold 96.5%).
ISSUES CLOSED: #15
|
||
|
|
2664ebfd75 |
feat(ActorResult): implement ActorResult and NodeUsage types; capture per-node token counts from LangChain responses
CI / lint (pull_request) Successful in 1m9s
CI / typecheck (pull_request) Successful in 1m10s
CI / security (pull_request) Successful in 1m8s
CI / quality (pull_request) Successful in 44s
CI / build (pull_request) Successful in 45s
CI / integration_tests (pull_request) Successful in 1m15s
CI / unit_tests (pull_request) Successful in 3m48s
CI / coverage (pull_request) Successful in 3m43s
CI / status-check (pull_request) Successful in 3s
CI / quality (push) Successful in 44s
CI / lint (push) Successful in 49s
CI / security (push) Successful in 47s
CI / typecheck (push) Successful in 48s
CI / build (push) Successful in 50s
CI / integration_tests (push) Successful in 1m16s
CI / unit_tests (push) Successful in 3m50s
CI / coverage (push) Successful in 3m35s
CI / status-check (push) Successful in 3s
AC1: Move NodeUsage and ActorResult from runtime.py into the spec-mandated
cleveractors/result.py module. runtime.py now imports and re-exports both
types for backward compatibility. result.py defines __all__, uses
dict[str, Any] | None (PEP 585) for the state field, and makes nodes a
required positional field (no default) so the contract that nodes is always
non-empty is statically enforced.
AC2: LLMAgent.process_message() extracts real token usage from
response.usage_metadata (primary) with isinstance(dict) guard, falling back
to response.response_metadata['token_usage'] with its own isinstance(dict)
guard. If neither is available, logs a WARNING with a cause parameter that
distinguishes the three failure conditions (empty usage_metadata, missing
response_metadata, or missing token_usage key) so operators can triage billing
discrepancies. Non-numeric token values are coerced via _safe_int() with
fallback to 0 and a warning log.
AC3: Token counts are available alongside the response string via the
side-channel attribute LLMAgent._last_token_usage, keeping process_message()
return type as str (preserving the Agent base-class contract unchanged).
AC4: Node._execute_agent() reads _last_token_usage, provider, and model from
any LLMAgent after execution and includes a _node_token_usage dict in the
state-updates return. PureLangGraph._node_usages accumulates one
(node_id, provider, model, prompt_tokens, completion_tokens) 5-tuple per LLM
node invocation during _execute_from_node(). PureLangGraph.execute() now
returns a 3-tuple: (response, final_state, node_usages). process_message()
unpacks and discards the extra elements.
AC5: Executor._execute_graph() uses the node_usages list from execute() to
build NodeUsage objects. Executor._execute_llm() reads _last_token_usage
directly from the LLMAgent instance instead of calling _estimate_tokens().
State isolation verified by a new test that calls execute() twice on the same
Executor with different mocked token counts and asserts independent results.
AC6: Aggregation invariant enforced in both _execute_llm() and _execute_graph()
via sum() over nodes list.
AC7: ActorResult and NodeUsage re-exported from cleveractors/__init__.py via
cleveractors.result (path updated from cleveractors.runtime).
Cleanup: Delete runtime_tokens.py (estimate_tokens, estimate_graph_tokens) and
the private _estimate_tokens() function in runtime.py — both are superseded by
real LangChain usage_metadata extraction. Remove unused import logging and
logger = logging.getLogger(__name__) from runtime.py (dead code after dispatch
logic was moved to runtime_dispatch.py). Move NodeUsageTuple type alias in
pure_graph.py to after the import blocks. Use tuple unpacking for last_usage
in runtime_dispatch._execute_llm().
New/updated BDD tests (actor_result_token_counting.feature, 25 scenarios):
- AC2: usage_metadata primary path (input_tokens/output_tokens)
- AC2: truthy non-dict usage_metadata treated as absent (isinstance guard)
- AC2: truthy non-dict response_metadata treated as absent (isinstance guard)
- AC2: response_metadata={} (empty dict) logs warning, counts 0
- AC2: response_metadata fallback path (prompt_tokens/completion_tokens)
- AC2: no usage data -> warning logged, counts 0
- AC2: response_metadata attribute present but None
- AC2: stale token reset before each call
- AC5: state isolation — execute() twice produces independent results
- AC6: aggregation invariant for single-node and multi-node actors
- AC6: placeholder node_id exact format assertion for _execute_graph
- AC6: placeholder node_id exact format assertion for _execute_multi_actor
- AC7: import correctness from cleveractors and cleveractors.result
CHANGELOG.md updated with Changed (real token extraction, result module move)
and Removed (runtime_tokens.py) entries under [Unreleased].
Quality gates: lint pass, typecheck 0 errors, unit_tests 2157/2157,
integration_tests 86/86, coverage 97.2% (threshold 97%).
ISSUES CLOSED: #14
|
||
|
|
284d2a9f00
|
feat(registry): extend TemplateType and integrate PackageReference into template system
CI / quality (pull_request) Successful in 37s
CI / lint (pull_request) Successful in 49s
CI / typecheck (pull_request) Successful in 50s
CI / build (pull_request) Successful in 50s
CI / security (pull_request) Successful in 55s
CI / integration_tests (pull_request) Successful in 59s
CI / unit_tests (pull_request) Successful in 3m38s
CI / coverage (pull_request) Successful in 3m35s
CI / status-check (pull_request) Successful in 4s
CI / lint (push) Successful in 33s
CI / quality (push) Successful in 58s
CI / typecheck (push) Successful in 1m1s
CI / security (push) Successful in 1m1s
CI / build (push) Successful in 52s
CI / integration_tests (push) Successful in 1m12s
CI / unit_tests (push) Successful in 3m41s
CI / coverage (push) Successful in 3m35s
CI / status-check (push) Successful in 3s
Extend TemplateType enum with TEMPLATE, SKILL, ACTOR, MCP, LSP. Add optional package_ref field to ComponentReference for registry references. Create ReferenceResolver with multi-server RegistryClient pool and caching. Update InstantiationContext for registry-aware resolution with _original_reference. Extend TemplateRegistry, EnhancedTemplateRegistry, and TemplateStore for 8 types. Add registry reference detection in instantiate_from_config. Add GenericTemplate for new package types without specialized template classes. ISSUES CLOSED: #27 |
||
|
|
4e12902492
|
feat(registry): implement ReferenceResolver with version alias resolution
CI / quality (pull_request) Successful in 35s
CI / security (pull_request) Successful in 54s
CI / lint (pull_request) Successful in 58s
CI / build (pull_request) Successful in 42s
CI / typecheck (pull_request) Successful in 59s
CI / integration_tests (pull_request) Successful in 1m14s
CI / unit_tests (pull_request) Successful in 3m48s
CI / coverage (pull_request) Successful in 3m34s
CI / status-check (pull_request) Successful in 3s
CI / quality (push) Successful in 33s
CI / lint (push) Successful in 38s
CI / typecheck (push) Successful in 48s
CI / security (push) Successful in 48s
CI / build (push) Successful in 41s
CI / integration_tests (push) Successful in 1m15s
CI / unit_tests (push) Successful in 3m37s
CI / coverage (push) Successful in 3m34s
CI / status-check (push) Successful in 3s
Introduce ReferenceResolver implementing the Package Registry Standard v1.0.0 §5.3 and §4.2: ReferenceResolver: - parse(ref_str) → PackageReference handles all three reference schemes (registry, ID, local), wrapping ValueError into InvalidPackageReferenceError - resolve(ref_str) → PackageId resolves references to concrete IDs: ID refs directly, registry refs via RegistryClient, local refs reserved - Integrates with RegistryClient for remote HTTP resolution Version resolution (§4.2): - resolve_version(version, available_versions) resolves concrete (vX.Y.Z) and mutable aliases (latest, vx, x, vX.x, vX.Y.x) - is_concrete_version() / is_version_alias() classification helpers Integration tests: 35 Robot Framework test cases against fake registry server with version alias resolution support Benchmarks: 7 ASV benchmark classes covering parse, resolve, and version resolution across small/medium/large version lists ISSUES CLOSED: #26 |
||
|
|
8fa9e7652d
|
feat(registry): implement Canonicalizer with NFC normalization and SHA-1 hashing
CI / quality (pull_request) Successful in 42s
CI / lint (pull_request) Successful in 46s
CI / security (pull_request) Successful in 47s
CI / build (pull_request) Successful in 50s
CI / typecheck (pull_request) Successful in 1m4s
CI / integration_tests (pull_request) Successful in 1m9s
CI / unit_tests (pull_request) Successful in 3m43s
CI / coverage (pull_request) Successful in 3m42s
CI / status-check (pull_request) Successful in 3s
CI / lint (push) Successful in 59s
CI / typecheck (push) Successful in 58s
CI / quality (push) Successful in 1m5s
CI / security (push) Successful in 1m9s
CI / build (push) Successful in 51s
CI / integration_tests (push) Successful in 1m40s
CI / unit_tests (push) Successful in 3m53s
CI / coverage (push) Successful in 3m35s
CI / status-check (push) Successful in 3s
Create Canonicalizer class implementing Package Registry Standard v1.0.0 §6: - NFC normalization of all string values via unicodedata.normalize - Lexicographic key sorting for deterministic output - Lifecycle field stripping (version, release_date) - RFC-8785 canonical JSON serialization (no whitespace, sorted keys) - SHA-1 content-addressed hashing via compute_package_id() - Internal reference resolution with configurable resolver callback 10 integration test cases covering: - Complex content canonicalization with lifecycle stripping - Deterministic output across multiple runs - Key sorting enforcement - Unicode NFC normalization - PackageId computation for all 8 package types - Different content produces different output ISSUES CLOSED: #25 |
||
|
|
76c4c74201 |
feat(create_executor): implement create_executor() factory and Executor.execute() returning ActorResult
CI / lint (pull_request) Successful in 36s
CI / quality (pull_request) Successful in 40s
CI / build (pull_request) Successful in 44s
CI / security (pull_request) Successful in 51s
CI / typecheck (pull_request) Successful in 52s
CI / integration_tests (pull_request) Successful in 1m3s
CI / unit_tests (pull_request) Successful in 3m38s
CI / coverage (pull_request) Successful in 3m36s
CI / status-check (pull_request) Successful in 3s
CI / lint (push) Successful in 1m2s
CI / typecheck (push) Successful in 1m2s
CI / security (push) Successful in 1m2s
CI / quality (push) Successful in 40s
CI / integration_tests (push) Successful in 1m8s
CI / build (push) Successful in 49s
CI / unit_tests (push) Successful in 3m40s
CI / coverage (push) Successful in 3m36s
CI / status-check (push) Successful in 3s
- **`src/cleveractors/runtime_types.py`** — `ActorResult` and `NodeUsage` dataclasses
extracted to break circular imports (CONTRIBUTING.md §Import Guidelines). 100% coverage.
- **`src/cleveractors/runtime.py`** — `create_executor()` factory, `Executor` class
with `execute()` dispatching to module-level functions in `runtime_dispatch`.
Imports `_execute_*` at module level (no function-local imports). 100% coverage.
- **`src/cleveractors/runtime_dispatch.py`** — Four dispatch functions: `_execute_llm`,
`_execute_graph`, `_execute_tool`, `_execute_multi_actor`. All imports at module
level except `from cleveractors.runtime import Executor` inside `_execute_multi_actor`
(the only remaining circular dep — Executor calls _execute_multi_actor which creates
a new Executor; cannot be resolved without further restructuring). 99.69% coverage
(only the `if TYPE_CHECKING:` guard line is uncovered — not a real gap).
- **`src/cleveractors/runtime_tokens.py`** — Token estimation via tiktoken with
heuristic fallback. 100% coverage.
Key design decisions and fixes:
- **Circular import resolution**: `ActorResult`/`NodeUsage` moved to `runtime_types.py`;
both `runtime.py` and `runtime_dispatch.py` import from it at module level.
- **`messages` forwarded to `_execute_llm`**: builds `context={"conversation_history":[...]}`
passed to `LLMAgent.process_message` for multi-turn support.
- **`parallel_execution` aligned with `PureGraphConfig` default (True)**:
reads from legacy `route` dict or v2.0 `routes.main` dict; defaults to True.
- **Factory normalization**: always merges `actors` into `agents` (actors take
precedence) so configs using both keys work correctly.
- **Generic exception in agent creation re-raised as `ConfigurationError`**:
no double-logging; exception message preserved in `ConfigurationError`.
- **`cleveragents_block` guarded against `None`**: uses `or {}` coercion.
- **Type annotations added**: `top_provider`, `top_model`, `top_sp`,
`temperature_raw`, `max_tokens_raw`, `config_block`, `tools` in dispatch functions.
- **No `finally` in `_execute_tool`**: documented with comment (ToolAgent has no cleanup).
- **Dead step removed**: `step_rxe_invalid_node_type` was unreferenced and incorrect.
- **BDD coverage**: `parallel_execution=False` override verified via `PureGraphConfig`
constructor args; conversation history forwarding verified; AC7 immutability for
multi-actor path verified.
- ✅ `nox -e lint` — passes
- ✅ `nox -e format` — passes
- ✅ `nox -e typecheck` — passes (0 errors, 1 expected warning)
- ✅ `nox -e unit_tests` — 2113 scenarios pass, 0 failures, 0 skipped
- ✅ `nox -e integration_tests` — 76 tests pass
- ✅ `nox -e coverage_report` — 97.21% (9870 covered, 283 missing, 10153 total)
PR-modified files: `runtime_types.py` 100%, `runtime.py` 100%,
`runtime_dispatch.py` 99.69% (1 uncoverable TYPE_CHECKING guard), `runtime_tokens.py` 100%
ISSUES CLOSED: #13
|
||
|
|
3fc0a3fa57
|
test(coverage): add BDD unit tests to close coverage gap toward 97%
CI / lint (pull_request) Successful in 35s
CI / quality (pull_request) Successful in 44s
CI / typecheck (pull_request) Successful in 47s
CI / security (pull_request) Successful in 56s
CI / unit_tests (pull_request) Successful in 3m33s
CI / integration_tests (pull_request) Successful in 56s
CI / build (pull_request) Successful in 34s
CI / coverage (pull_request) Successful in 3m34s
CI / status-check (pull_request) Successful in 3s
CI / quality (push) Successful in 49s
CI / lint (push) Successful in 51s
CI / typecheck (push) Successful in 52s
CI / build (push) Successful in 41s
CI / security (push) Successful in 58s
CI / integration_tests (push) Successful in 1m0s
CI / unit_tests (push) Successful in 3m37s
CI / coverage (push) Successful in 3m34s
CI / status-check (push) Successful in 5s
Add comprehensive BDD Behave unit tests across six modules to improve line coverage. New feature files and step definitions for: - validation/_actor: graph/llm/tool/multi_actor config validation - runtime_tokens: token estimation with tiktoken and fallback heuristics - dynamic_router: ContentBasedCondition, RouterNode, graph extension - runtime: v2 route format, config blocks, conversation history, multi-actor - progress: ProgressBarManager remaining count resolution - route: MESSAGE_ROUTER rules to metadata in to_graph_config Also fix credential executor and graph cleanup step mocks to return proper tuple values matching updated runtime signatures. Refs: #39 |
||
|
|
f281fa3b24 |
feat(credentials): refactor LLMAgent/AgentFactory for per-request credential injection and extended provider routing
CI / lint (pull_request) Successful in 35s
CI / typecheck (pull_request) Successful in 50s
CI / security (pull_request) Successful in 50s
CI / quality (pull_request) Successful in 32s
CI / unit_tests (pull_request) Successful in 3m32s
CI / integration_tests (pull_request) Successful in 59s
CI / build (pull_request) Successful in 34s
CI / lint (push) Successful in 33s
CI / build (push) Successful in 37s
CI / quality (push) Successful in 48s
CI / typecheck (push) Successful in 48s
CI / security (push) Successful in 1m4s
CI / integration_tests (push) Successful in 1m18s
CI / coverage (pull_request) Successful in 3m34s
CI / status-check (pull_request) Successful in 3s
CI / unit_tests (push) Successful in 3m45s
CI / coverage (push) Successful in 3m34s
CI / status-check (push) Successful in 3s
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 |
||
|
|
85ebe29f8e
|
feat(registry): add PackageType, PackageId, PackageReference, PackageContent core types
CI / quality (pull_request) Successful in 45s
CI / lint (pull_request) Successful in 53s
CI / typecheck (pull_request) Successful in 54s
CI / integration_tests (pull_request) Successful in 1m0s
CI / security (pull_request) Successful in 1m4s
CI / build (pull_request) Successful in 51s
CI / unit_tests (pull_request) Successful in 3m46s
CI / coverage (pull_request) Successful in 3m41s
CI / status-check (pull_request) Successful in 3s
CI / quality (push) Successful in 39s
CI / lint (push) Successful in 1m0s
CI / typecheck (push) Successful in 1m3s
CI / build (push) Successful in 1m3s
CI / security (push) Successful in 1m6s
CI / integration_tests (push) Failing after 1m3s
CI / unit_tests (push) Successful in 3m53s
CI / coverage (push) Successful in 3m43s
CI / status-check (push) Failing after 3s
Package Registry Standard v1.0.0 (§3, §5) data types: - PackageType enum matching §3.2: ACTOR, GRAPH, STREAM, AGENT, TEMPLATE, SKILL, MCP, LSP - PackageId: frozen/hashable value object parsing pkg_<type>_<40-hex-sha1> format - PackageReference: frozen/hashable parser for REGISTRY, ID, and LOCAL reference formats - PackageContent: frozen wrapper for fetched package content with metadata - 34 Behave BDD scenarios covering valid parsing, error paths, hashability, str/repr ISSUES CLOSED: #23 |
||
|
|
ab17407576
|
test(runtime): add BDD coverage tests for runtime Executor API
CI / lint (pull_request) Successful in 40s
CI / typecheck (pull_request) Successful in 58s
CI / security (pull_request) Successful in 1m8s
CI / quality (pull_request) Successful in 1m7s
CI / integration_tests (pull_request) Successful in 1m20s
CI / build (pull_request) Successful in 39s
CI / unit_tests (pull_request) Successful in 3m47s
CI / coverage (pull_request) Successful in 3m44s
CI / status-check (pull_request) Successful in 4s
CI / quality (push) Successful in 39s
CI / lint (push) Successful in 47s
CI / security (push) Successful in 52s
CI / integration_tests (push) Successful in 1m2s
CI / typecheck (push) Successful in 1m5s
CI / build (push) Successful in 1m11s
CI / unit_tests (push) Successful in 4m27s
CI / coverage (push) Successful in 4m28s
CI / status-check (push) Successful in 5s
Add 16 Behave BDD scenarios covering: - create_executor() factory function and Executor initialization - Executor.execute() dispatch to LLM, graph, tool, multi-actor agents - _execute_llm credential injection and error handling - _execute_graph PureGraphConfig construction - _execute_tool tool configuration - _execute_multi_actor routing and error cases - _build_factory_config credential/limit injection - _estimate_tokens with tiktoken and fallback heuristic - NodeUsage and ActorResult dataclasses ISSUES CLOSED: #33 Refs: #33 |
||
|
|
92d5305a20 |
feat(registry): implement async RegistryClient using httpx
CI / quality (pull_request) Successful in 38s
CI / lint (pull_request) Successful in 44s
CI / build (pull_request) Successful in 35s
CI / typecheck (pull_request) Successful in 1m11s
CI / security (pull_request) Successful in 1m8s
CI / integration_tests (pull_request) Successful in 1m8s
CI / unit_tests (pull_request) Successful in 3m35s
CI / coverage (pull_request) Failing after 3m49s
CI / status-check (pull_request) Failing after 3s
CI / lint (push) Successful in 41s
CI / security (push) Successful in 54s
CI / typecheck (push) Successful in 56s
CI / quality (push) Successful in 1m3s
CI / build (push) Successful in 42s
CI / integration_tests (push) Successful in 56s
CI / unit_tests (push) Successful in 3m38s
CI / coverage (push) Failing after 3m42s
CI / status-check (push) Failing after 3s
Add async RegistryClient implementing the Package Registry Standard
v1.0.0 HTTP API endpoints (§8) using httpx.AsyncClient.
- GET /packages/{id} — retrieve package content by PackageId
- GET /{type}/{ns}/{name}?version= — resolve references with
version alias resolution (latest, vx, vX.Y.x, vX.x per §4.2)
- GET /browse — discover published packages with type/namespace filters
- GET /.well-known/cleverthis-packages — registry metadata discovery
- Maps all 8 standard error types (§13.2) to typed exceptions
- Supports anonymous reads (§9.1) and optional API key auth (§9.2)
- Async context manager support (__aenter__/__aexit__)
- 32 Behave BDD scenarios, 7 Robot Framework integration tests,
ASV benchmarks, 100% registry module coverage
ISSUES CLOSED: #24
|
||
|
|
149feae15f |
feat(validate_dict): expose public validate_dict(config_dict, platform_limits) API
CI / quality (pull_request) Successful in 41s
CI / lint (pull_request) Successful in 46s
CI / integration_tests (pull_request) Successful in 1m0s
CI / build (pull_request) Successful in 1m2s
CI / typecheck (pull_request) Successful in 1m6s
CI / security (pull_request) Successful in 1m5s
CI / unit_tests (pull_request) Successful in 3m39s
CI / coverage (pull_request) Failing after 3m35s
CI / status-check (pull_request) Failing after 3s
CI / lint (push) Successful in 40s
CI / typecheck (push) Successful in 1m4s
CI / security (push) Successful in 1m4s
CI / quality (push) Successful in 32s
CI / integration_tests (push) Successful in 51s
CI / build (push) Successful in 45s
CI / unit_tests (push) Successful in 3m38s
CI / coverage (push) Failing after 3m34s
CI / status-check (push) Failing after 3s
Implement validate_dict() in cleveractors/validation.py and export it from
cleveractors/__init__.py as the first router-facing API (ADR-2024, Wave 1).
validate_dict(config_dict, platform_limits) -> dict[str, Any]:
- Validates that both 'agents' and 'routes' top-level keys are present.
- Validates each agent's type against the spec-defined vocabulary:
{llm, tool, composite, chain, template_instance}.
- Validates LLM agent 'provider' values against platform_limits['allowed_providers']
when present, or the default allowlist {openai, anthropic, google} when absent.
- Validates route/edge field names: 'from'/'to' are rejected in favour of
'source'/'target' as required by the spec-conformant format (ADR-2025).
- Validates graph node types against the spec vocabulary:
{start, end, agent, function, tool, conditional, subgraph, message_router}.
- Validates stream operator types against the spec vocabulary (all defined types).
- Enforces structural platform limits from platform_limits when present:
max_graph_depth (DFS longest-path check), max_subgraph_depth (recursive
subgraph-reference depth), max_total_nodes (total nodes across all graph routes).
- Returns config_dict unchanged when all checks pass (identity contract).
- Pure static validator: no file I/O, no env-var reads, no app construction.
Review fixes (Cycle 1):
- M3: Added isinstance(node_name, str) guard for graph node keys in
_validate_graph_route, with BDD scenario for non-string node key.
- m1: Added # pragma: no branch to _count_total_nodes defensive guard in
_limits.py (matching the identical guards in _subgraph_children).
- m2: Added # pragma: no branch to dead defensive continue branches in
_validate_graph_route edge validation loop.
- m3: Added dedicated BDD scenario and step for dangling source node
(src not in nodes_raw branch), plus renamed existing scenario from
"source node" to "target node" for accuracy.
- m4: Deleted 4 lines of commented-out InlineYAMLJinja setup code in
features/environment.py.
- n1: Added depth <= 0 ValueError guard in build_subgraph_chain helper.
- n2: Renamed scenario "Edge referencing a non-existent source node" to
"Edge referencing a non-existent target node" (the step tested a target).
- n3: Changed > to >= in both _MAX_DFS_STEPS and _MAX_SUBGRAPH_STEPS guards
to prevent off-by-one allowing one extra iteration beyond documented ceiling.
- n4: Added adjacency target deduplication in _compute_graph_depth to avoid
wasting DFS step budget on parallel edges.
- Spec review: M1 (entry_point optional) and M2 (start/end implicit injection)
were verified against the authoritative spec at docs/specification.md in the
cleveragents-webapp repo (develop branch). The spec does NOT support either
claim; entry_point is required and start/end must be explicitly declared.
No changes needed for M1/M2.
Review fixes (Cycle 2):
- M1: Deduplicated _subgraph_children yields with a seen set to prevent
duplicate subgraph references from consuming DFS steps redundantly and
causing false "too complex" errors.
- M2: Changed adjacency value type from list[str] to set[str] in
_compute_graph_depth for O(1) deduplication, reducing worst case
from O(N^3) to O(N^2) for dense graphs.
- m1: Added BDD scenario for LLM agent without provider field failing
when default "openai" is not in custom allowlist.
- m2: Added positive BDD scenarios for valid node types start, tool,
conditional, message_router, function.
- m3: Added positive BDD scenarios for additional stream operator types
filter, transform, reduce.
- m5: Added depth_cache memoization to _compute_subgraph_depth to avoid
recomputing depths across graph routes sharing subgraph trees.
- m6: Moved cleveractors.validation._limits import from module level into
after_scenario, guarded by hasattr.
- n1: Split validate_dict_routes_steps.py (was 473 lines) into
validate_dict_routes_steps.py (generic route steps) and
validate_dict_graph_route_steps.py (graph-specific steps).
- n2: Updated _MAX_DFS_STEPS module comment to accurately state O(N+E)
complexity and that dense graphs can exhaust the ceiling quickly.
BDD Behave scenarios cover all requirements.
Robot Framework integration tests (robot/validate_dict.robot) cover API usage.
ISSUES CLOSED: #10
|
||
|
|
6b80be2117 |
feat(merge_configs): expose public merge_configs(*dicts) API per §3.1 deep-merge algorithm
CI / lint (pull_request) Successful in 42s
CI / integration_tests (pull_request) Successful in 49s
CI / quality (pull_request) Successful in 53s
CI / build (pull_request) Successful in 50s
CI / typecheck (pull_request) Successful in 54s
CI / security (pull_request) Successful in 1m12s
CI / unit_tests (pull_request) Successful in 3m53s
CI / coverage (pull_request) Successful in 3m50s
CI / status-check (pull_request) Successful in 3s
CI / lint (push) Successful in 43s
CI / quality (push) Successful in 46s
CI / security (push) Successful in 47s
CI / typecheck (push) Successful in 48s
CI / build (push) Successful in 50s
CI / integration_tests (push) Successful in 54s
CI / unit_tests (push) Successful in 3m49s
CI / coverage (push) Successful in 3m42s
CI / status-check (push) Successful in 3s
Implement a new module-level merge_configs() function in
src/cleveractors/config_utils.py that exposes the §3.1 deep-merge
algorithm as a public, importable API.
Motivation: The internal ReactiveConfigParser._merge_configs() method
mutates its base dict in place and only accepts two dicts. The
CleverThis router (wave 2 of cleveragents/cleveragents-webapp#275)
needs a public merge_configs() to combine platform base configs with
actor configs from the database without side-effects.
Implementation:
- merge_configs(*dicts) accumulates into a fresh dict, applying each
overlay using the §3.1 rules:
* Key absent → add via deep copy.
* Both mappings → deep-merge recursively (_apply_merge helper).
* Both sequences → extend (existing + deep copy of new).
* Otherwise → replace with deep copy of new value.
- Runtime type guard: non-dict arguments raise TypeError immediately
(fail-fast per CONTRIBUTING.md argument validation guidelines).
- Zero-argument call returns {}.
- Inputs are never mutated (copy.deepcopy used for all stored values).
- Internal _merge_configs on ReactiveConfigParser is unchanged.
Exports: merge_configs added to cleveractors/__init__.py import and
__all__ so consumers can use 'from cleveractors import merge_configs'.
Tests:
- 15 Behave BDD scenarios in features/merge_configs.feature covering
zero-args, single-dict deep copy (including nested identity),
empty-dict arguments, TypeError on None, absent-key insertion,
scalar override, recursive mapping merge, sequence append,
three-dict chained merge, deep nesting, input mutation guard,
result-mutation independence from inputs, and type-mismatch
replacement.
- 4 Robot Framework integration tests in robot/config.robot with
strengthened assertions (len check, overlay immutability).
- 5 ASV benchmarks with realistically sized three-way merge fixtures
(50 keys each with overlap).
Project files: Added CONTRIBUTORS.md per CONTRIBUTING.md §PR Process
rule 8.
Coverage: 97% (config_utils.py: 100%).
Quality gates: ruff lint ✓, pyright strict ✓ (0 errors),
all BDD/Robot tests ✓.
ISSUES CLOSED: #11
|
||
|
|
9753b31c7e
|
test: add coverage gap tests improving coverage from 81.4% to 96.90%
CI / quality (push) Successful in 36s
CI / lint (push) Successful in 38s
CI / typecheck (push) Successful in 49s
CI / security (push) Successful in 51s
CI / integration_tests (push) Successful in 55s
CI / unit_tests (push) Successful in 3m35s
CI / build (push) Successful in 33s
CI / coverage (push) Successful in 3m36s
CI / status-check (push) Successful in 3s
Add 13 BDD scenarios covering previously uncovered code paths: - SAFE_BUILTINS validation (sandbox.py: 0% → 100%) - ConfigurationError re-raise path (config.py: 99.3% → 100%) - CLI hello/main functions (cli.py: 72.7% → 90.9%) - GraphState message truncation (state.py: 98.7% → 100%) - ProgressBarManager update/context rendering (progress.py: 0% → 87.7%) - MessageRouter regex/exact/contains routing (message_router.py: 0% → 59.4%) - RoutingAdapter parse_routing_command (routing_adapter.py) - DynamicRouterNode pattern-based routing (dynamic_router.py) - EnhancedTemplateRegistry unknown template type (enhanced_registry.py: 99.2%) - CompositeAgent null-graph error path (composite.py: 97.8% → 98.6%) Cover routing_adapter.py (17% → 100%): all GOTO/ROUTE patterns, create_routing_node, create_conditional_router, dynamic config conversion. Cover dynamic_router.py (21% → 87%): execute with empty/dict/string messages, extract_message with colon parsing, config creation, graph extension with edge generation. Cover message_router.py (59% → 78%): regex, exact, contains, prefix, suffix match types, invalid regex handling, non-string message, set_state. Exercise Node._prepare_conversation_history with invalid configs, _runtime error paths, _execute_message_router with rules, _execute_agent with current_message and metadata propagation, _execute_function with dynamic_router, and _execute_conditional with content_contains/content_not_contains/content_starts_with and custom condition types. Improves nodes.py from 71.3% to 72.5%. Exercise PureGraphConfig, PureLangGraph init with dict/config, RxPyLangGraphBridge registration/connection/lookup, ReactiveStreamRouter operator creation and condition functions, ReactiveConfigParser config/route/graph parsing, ToolAgent tool execution with JSON, space-separated, single, file_read, progress_bar invocations, and ReactiveCleverAgentsApp init/dispose/visualization. |
||
|
|
df468f821f | style: apply end-of-file and whitespace fixes across feature files | ||
|
|
e7816ad9c4 |
fix: resolve all bandit, semgrep, and vulture security findings
- Set jinja2 autoescape to select_autoescape(default_for_string=False) in all template engines to prevent HTML escaping of YAML config values while still configuring explicit autoescape behavior - Replace silent try/except/pass with logging.debug in langgraph bridge - Add logging before try/except/continue in langgraph nodes - Replace assert with proper if/raise ValueError in reactive config parser - Add # nosec annotations on intentional eval()/exec()/subprocess uses with justifications (sandboxed code execution for tool agent) - Add # nosemgrep annotations for intentional eval()/exec() in sandboxed tools - Create vulture whitelist for TYPE_CHECKING imports used in string annotations - Fix unused lambda variables with _ prefix |
||
|
|
5bbda89386 |
style: fix all lint errors across source and test files
- 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 |
||
|
|
2223a16d48 | feat: Minor code changes and linting and formatting issues fixes | ||
|
|
d827c93d83 | Chore: RxPy routing is not garunteed to work in run mode so added some guardrails to prevent that. | ||
|
|
1001020fa1 | Chore: Moved any pytest tests over to behave and removed pytest entierly | ||
|
|
656dbd5626 | feat: added --load-context argument to run command | ||
|
|
05aef2f41a | Fix: JINJA2 templates are now being honored. | ||
|
|
aa75d0978f | Fix: Verbosity wasnt being respected and errors were still leaking to the top, this is fixed | ||
|
|
c26197e90a | Feat: added verbosity flag to control logging output | ||
|
|
9a9aa04745 | Fixed linting and typechecking, tox fully works now | ||
|
|
3da2958a16 | tests: some improved code coverage | ||
|
|
44a53a16aa | feat: Added temperature override for run and interactive commands for better testing | ||
|
|
57254f57e5 | fix: Fixed many of the remaining issues and got a mostly working integration test for scientific paper writer | ||
|
|
d6477288fe | test: Added vetter coverage to network and decorators | ||
|
|
82ea476c5f | feat: Added context tracking when using via run, and added context command for managing it |