Skip to content

fix: strengthen immutability for BaseTool schema and ToolInvoker boundaries#117

Merged
Aureliolo merged 3 commits intomainfrom
feat/mutable-containers-strategy
Mar 6, 2026
Merged

fix: strengthen immutability for BaseTool schema and ToolInvoker boundaries#117
Aureliolo merged 3 commits intomainfrom
feat/mutable-containers-strategy

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

@Aureliolo Aureliolo commented Mar 6, 2026

Closes #113

Summary

  • Deep-copy at construction: BaseTool._parameters_schema now uses copy.deepcopy() before MappingProxyType wrapping, fully isolating nested structures from caller mutation
  • Deep-copy on property access: parameters_schema property returns a deep copy to prevent cross-caller nested mutation
  • Separated deepcopy error handling: ToolInvoker._execute_tool separates argument deep-copy into its own try-block for accurate error attribution (previously misattributed as tool execution failure)
  • Strengthened tests: Added nested mutation isolation tests for both construction and property access; added parameters_schema to _MutatingTool so boundary isolation tests exercise the validation path
  • Docs consistency: Removed stale (M3) tags from 5 implemented engine files in DESIGN_SPEC.md; updated immutability convention docs in CLAUDE.md and DESIGN_SPEC.md
  • Docstring fixes: Fixed misleading "accepts any arguments" wording; clarified ToolExecutionResult.metadata boundary-copy scope

Test plan

  • All 1784 tests pass
  • 95.21% coverage (80% minimum)
  • mypy strict mode clean
  • ruff lint + format clean
  • Pre-commit hooks pass
  • Verify nested mutation isolation works end-to-end (new tests cover this)
  • Verify deepcopy error attribution produces correct log event

Review coverage

Pre-reviewed by 10 agents: code-reviewer, python-reviewer, pr-test-analyzer, silent-failure-hunter, comment-analyzer, type-design-analyzer, logging-audit, resilience-audit, security-reviewer, docs-consistency. 11 findings addressed out of 12 total (1 reverted: async generator incompatible with tuple()).

Aureliolo and others added 2 commits March 6, 2026 07:09
#113)

Deep-copy at system boundaries for Pydantic frozen models;
MappingProxyType for non-Pydantic internal collections (BaseTool).

- ToolInvoker: deepcopy arguments at tool execution boundary
- BaseTool: MappingProxyType at construction, shallow dict on access
- Update docstrings (ToolDefinition, ToolCall, ToolExecutionResult)
- Update DESIGN_SPEC.md §11.1.1 and §15.5
- Update CLAUDE.md immutability convention
- Add boundary isolation and read-only tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…daries

- Deep-copy parameters_schema at construction before MappingProxyType
  wrapping, fully isolating nested structures from caller mutation
- Return deep copy from parameters_schema property to prevent cross-
  caller nested mutation
- Separate deepcopy of tool arguments into its own try-block in
  ToolInvoker._execute_tool for accurate error attribution
- Add _MutatingTool.parameters_schema so boundary isolation tests
  exercise the validation path
- Add nested mutation isolation tests for both construction and
  property access
- Remove stale (M3) tags from implemented engine files in DESIGN_SPEC
- Update immutability convention docs in CLAUDE.md and DESIGN_SPEC.md
- Fix misleading "accepts any arguments" docstring wording
- Clarify ToolExecutionResult.metadata boundary-copy scope

Pre-reviewed by 10 agents, 11 findings addressed

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 6, 2026 06:30
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 6, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 6, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced argument isolation for tool execution to prevent unintended mutations from affecting external state.
    • Consolidated event constants into a single module with improved organization.
  • Bug Fixes

    • Added robust error handling for deep-copy failures during tool invocation with detailed error messaging.
    • Improved parameter schema immutability protection.
  • Documentation

    • Updated immutability strategy documentation with clarified boundary-copy approach and frozen-state semantics.

Walkthrough

Deep-copy-at-boundary and MappingProxyType-based immutability fixes: BaseTool now deep-copies and stores a MappingProxyType-wrapped parameters schema; ToolInvoker deep-copies tool call arguments before execution and reports deepcopy failures; docs and tests updated to reflect and verify these behaviors.

Changes

Cohort / File(s) Summary
Design & Policy
CLAUDE.md, DESIGN_SPEC.md
Clarified immutability strategy: non‑Pydantic internal collections get deepcopy at construction + MappingProxyType; Pydantic frozen models rely on frozen=True with deepcopy at system boundaries; consolidated event constants and removed M3 annotations from engine listing.
Tool core
src/ai_company/tools/base.py
Store parameters_schema as MappingProxyType around a deepcopy of the input; property returns a copied dict (or None) to avoid exposing internal state; adjusted docstrings/comments.
Invocation boundary
src/ai_company/tools/invoker.py
Deep-copies tool_call.arguments before calling tool execution; catches deepcopy failures to emit a dedicated deepcopy error event and return an error ToolResult; handles MemoryError/RecursionError specially.
Observability
src/ai_company/observability/events.py
Added TOOL_INVOKE_DEEPCOPY_ERROR event constant for deepcopy failure reporting.
Models / Docs
src/ai_company/engine/prompt.py, src/ai_company/providers/models.py
Updated field descriptions and notes to state Pydantic fields are shallow-frozen (nested contents mutable) and document when boundary deep copies occur.
Tests
tests/unit/tools/conftest.py, tests/unit/tools/test_base.py, tests/unit/tools/test_invoker.py
Added _MutatingTool; expanded BaseTool tests to assert construction isolation, read-only internal schema, and returned-copy isolation; added invoker tests for boundary deepcopy isolation and deepcopy-failure behavior.
Manifests
requirements.txt
(manifest change reported) Updated manifest lines (minor).

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client/Caller
    participant Invoker as ToolInvoker
    participant Tool as BaseTool
    participant Executor as Tool.execute()

    Client->>Invoker: invoke_all(tool_calls)
    Invoker->>Invoker: safe_args = deepcopy(tool_call.arguments)
    rect rgba(76, 175, 80, 0.5)
        Note over Invoker,Executor: Boundary isolation — tool receives safe_args copy
    end
    Invoker->>Tool: execute(safe_args)
    Tool->>Executor: implementation runs with safe_args
    Executor-->>Tool: ToolResult
    Tool-->>Invoker: ToolResult
    Invoker-->>Client: aggregated results
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.91% which is insufficient. The required threshold is 100.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main focus: strengthening immutability for BaseTool schema and ToolInvoker boundaries, directly matching the primary changes in the changeset.
Description check ✅ Passed The description provides relevant details about deep-copy mechanisms, error handling, test improvements, and documentation updates, all related to the changeset objectives.
Linked Issues check ✅ Passed All acceptance criteria from issue #113 are addressed: decision documented (CLAUDE.md/DESIGN_SPEC.md updated), strategy applied consistently (MappingProxyType + boundary deepcopy), advisory comments removed/updated, ToolInvoker aligned with strategy, and tests verify boundary immutability.
Out of Scope Changes check ✅ Passed All changes directly support the core objectives: immutability enforcement in BaseTool and ToolInvoker, documentation alignment, test coverage for boundary isolation, and error instrumentation. No unrelated changes detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/mutable-containers-strategy

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the immutability guarantees within the system, particularly for BaseTool parameter schemas and ToolInvoker arguments. By implementing deep-copying at critical boundaries and leveraging MappingProxyType, it ensures that internal data structures are protected from unintended external modifications, leading to more robust and predictable tool interactions. The changes also refine error handling for argument copying and update documentation to reflect these strengthened conventions.

Highlights

  • Deep-copy at construction: The BaseTool._parameters_schema now uses copy.deepcopy() before MappingProxyType wrapping, fully isolating nested structures from caller mutation.
  • Deep-copy on property access: The parameters_schema property returns a deep copy to prevent cross-caller nested mutation.
  • Separated deepcopy error handling: The ToolInvoker._execute_tool separates argument deep-copy into its own try-block for accurate error attribution, preventing misattribution as tool execution failure.
  • Strengthened tests: Added nested mutation isolation tests for both construction and property access, and included parameters_schema to _MutatingTool to exercise the validation path.
  • Docs consistency: Removed stale (M3) tags from 5 implemented engine files in DESIGN_SPEC.md and updated immutability convention docs in CLAUDE.md and DESIGN_SPEC.md.
  • Docstring fixes: Fixed misleading "accepts any arguments" wording and clarified ToolExecutionResult.metadata boundary-copy scope.
Changelog
  • CLAUDE.md
    • Updated immutability convention documentation to specify copy.deepcopy() at construction for non-Pydantic internal collections and copy.deepcopy() at system boundaries for Pydantic frozen models.
  • DESIGN_SPEC.md
    • Updated the description of BaseTool.parameters_schema immutability strategy, detailing deep-copy at construction with MappingProxyType and deep-copy on property access.
    • Revised the "Immutability strategy" convention table entry to reflect the new deep-copy and MappingProxyType approach for BaseTool and boundary-copy for Pydantic models.
    • Removed stale (M3) tags from several engine files in the directory structure section.
  • src/ai_company/engine/prompt.py
    • Removed the "treat as read-only" note from the metadata field description in SystemPrompt.
  • src/ai_company/providers/models.py
    • Updated docstrings for ToolDefinition.parameters_schema and ToolCall.arguments to clarify that Pydantic's frozen=True provides shallow immutability and deep-copying is required at system boundaries for nested structures.
  • src/ai_company/tools/base.py
    • Imported MappingProxyType from types.
    • Modified BaseTool.__init__ to deep-copy the parameters_schema and wrap it in MappingProxyType for internal storage.
    • Updated the parameters_schema property to return a deep copy of the internal schema, ensuring external callers receive an isolated copy.
    • Clarified the docstring for parameters_schema to indicate it's None if no schema is defined, rather than accepting "any arguments".
  • src/ai_company/tools/invoker.py
    • Imported the copy module.
    • Refactored _execute_tool to introduce a separate try-except block specifically for deep-copying tool_call.arguments, allowing for distinct error attribution for deep-copy failures versus tool execution failures.
  • tests/unit/tools/conftest.py
    • Added a new _MutatingTool class that modifies its arguments during execution, designed to test immutability boundaries.
    • Included _MutatingTool in the extended_invoker fixture.
  • tests/unit/tools/test_base.py
    • Replaced test_schema_deep_copied_on_construction with test_schema_isolated_on_construction and test_schema_nested_isolated_on_construction to verify both top-level and nested immutability of the schema during BaseTool construction.
    • Added test_schema_internal_is_read_only to confirm that the internally stored _parameters_schema is protected by MappingProxyType.
    • Added test_schema_property_nested_mutation_isolated to ensure that nested structures returned by the parameters_schema property are also isolated from external mutation.
  • tests/unit/tools/test_invoker.py
    • Added TestInvokeBoundaryIsolation class with two new tests: test_tool_receives_deep_copy_of_arguments and test_nested_mutation_does_not_leak, verifying that ToolInvoker provides deep copies of arguments to tools and that tool mutations do not affect the original ToolCall object.
Activity
  • The pull request was pre-reviewed by 10 agents (code-reviewer, python-reviewer, pr-test-analyzer, silent-failure-hunter, comment-analyzer, type-design-analyzer, logging-audit, resilience-audit, security-reviewer, docs-consistency).
  • 11 findings from the reviews were addressed.
  • 1 finding was reverted due to incompatibility with async generators.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This is a high-quality pull request that effectively strengthens the immutability guarantees around tool schemas and execution boundaries. The use of copy.deepcopy and MappingProxyType at construction in BaseTool is a solid improvement. Separating the argument-copying logic in ToolInvoker with its own error handling significantly improves error attribution and robustness. The accompanying test additions are thorough and correctly validate the new behavior. The documentation updates are also clear and consistent with the changes. I have a couple of minor suggestions for improving code conciseness and documentation consistency.

)
metadata: dict[str, str] = Field(
description="Agent identity metadata (treat as read-only)",
description="Agent identity metadata",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This PR does a great job of strengthening immutability and updating docstrings to reflect that. However, simply removing the (treat as read-only) note here reduces clarity about the shallow-freezing nature of Pydantic's frozen=True on collection fields. For consistency with the detailed notes added in other models (like ToolDefinition and ToolCall), consider providing a more explicit description to guide developers.

Suggested change
description="Agent identity metadata",
description="Agent identity metadata. This dict is shallowly frozen; do not mutate in place.",

Comment on lines +111 to +113
if self._parameters_schema is None:
return None
return copy.deepcopy(dict(self._parameters_schema))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This implementation can be simplified. copy.deepcopy() correctly handles both MappingProxyType and None values, so the explicit check for None and the cast to dict() are unnecessary. A single line would be more concise and achieve the same result.

        return copy.deepcopy(self._parameters_schema)

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 6, 2026

Greptile Summary

This PR strengthens immutability guarantees for BaseTool and ToolInvoker by deep-copying the parameters schema at construction time (wrapping it in MappingProxyType), returning deep copies on property access, and separating the argument deep-copy in ToolInvoker._execute_tool into its own try-block with a dedicated TOOL_INVOKE_DEEPCOPY_ERROR event. Documentation in CLAUDE.md and DESIGN_SPEC.md is updated to reflect the adopted convention, and five stale (M3) tags are removed.

Core implementation: Correct and sound.

  • BaseTool._parameters_schema is now a MappingProxyType wrapping a deep-copied schema, and parameters_schema returns a fresh deep copy on each access — correctly preventing both top-level and nested mutation through the public API.
  • ToolInvoker._execute_tool correctly isolates the deepcopy failure path from the tool execution failure path using the new TOOL_INVOKE_DEEPCOPY_ERROR event.

Findings:

  • A misleading inline comment in base.py (line 115) incorrectly claims MappingProxyType cannot be deep-copied — the real reason dict() is needed is to satisfy the dict[str, Any] return type annotation.
  • The deepcopy-failure tests (TestInvokeDeepcopyFailure) use a fragile call_count > 1 counter that implicitly assumes exactly one prior copy.deepcopy call. Because the global module patch intercepts all deepcopy calls, any refactoring that changes the number of calls (even in base.py) would silently shift the counter and exercise the wrong failure mode.
  • test_schema_internal_is_read_only documents top-level MappingProxyType protection but does not cover or document the known gap: nested values in the private _parameters_schema remain mutable via direct access. A clarifying comment would prevent confusion about what "read-only" guarantees apply to this private attribute.

Confidence Score: 4/5

  • Safe to merge — core immutability logic is correct and well-tested; findings are comment clarity and test robustness style improvements with no runtime impact.
  • The production code changes are sound: deep-copy at construction, MappingProxyType wrapping, deep-copy on property access, and separated deepcopy error handling all work correctly. The dedicated TOOL_INVOKE_DEEPCOPY_ERROR event resolves the previous monitoring ambiguity. Deductions are for: (1) a misleading inline comment that could confuse maintainers about why dict() is needed; (2) brittle test counter logic in deepcopy failure tests that could silently fail during future refactors; (3) a missing clarifying comment in a test about a private attribute's immutability guarantees.
  • src/ai_company/tools/base.py — the inline comment on line 115 should be corrected; tests/unit/tools/test_invoker.py — the call_count > 1 logic should be made more robust; tests/unit/tools/test_base.py — a clarifying comment should be added to test_schema_internal_is_read_only.

Last reviewed commit: db36d19

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Strengthens immutability guarantees around tool parameter schemas (BaseTool) and tool execution boundaries (ToolInvoker) to prevent nested structure mutation from leaking across callers, while updating tests and documentation to match the new conventions.

Changes:

  • Deep-copies tool call arguments at the ToolInvoker execution boundary and separates deepcopy error handling from tool execution errors.
  • Freezes BaseTool parameter schemas using deepcopy + MappingProxyType, and returns deep copies from the schema property.
  • Adds/updates unit tests for nested mutation isolation and updates documentation to reflect the immutability strategy.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/unit/tools/test_invoker.py Adds boundary-isolation tests ensuring tool argument mutation doesn’t leak back into ToolCall.arguments.
tests/unit/tools/test_base.py Expands schema immutability tests (construction isolation, nested isolation, read-only internal).
tests/unit/tools/conftest.py Introduces a mutating test tool and wires it into the extended invoker fixture.
src/ai_company/tools/invoker.py Deep-copies arguments before execute() and improves error attribution for deepcopy failures.
src/ai_company/tools/base.py Freezes parameters_schema via MappingProxyType and returns deep copies on access; adjusts docstrings.
src/ai_company/providers/models.py Clarifies shallow immutability notes for ToolDefinition.parameters_schema and ToolCall.arguments.
src/ai_company/engine/prompt.py Updates field description text for SystemPrompt.metadata.
DESIGN_SPEC.md Updates immutability convention documentation and removes stale engine milestone tags.
CLAUDE.md Updates the documented immutability strategy for non-Pydantic vs Pydantic-frozen models.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +251 to +256
logger.exception(
TOOL_INVOKE_PARAMETER_ERROR,
tool_call_id=tool_call.id,
tool_name=tool_call.name,
error=f"Failed to deep-copy arguments: {error_msg}",
)
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TOOL_INVOKE_PARAMETER_ERROR is now used for two distinct failure modes: JSON Schema validation failures (_param_error_result, logged as warning) and argument deepcopy failures here (logged as exception). This will make observability/alerting ambiguous. Consider introducing a dedicated event constant for argument copy failures (or reusing a more appropriate existing event) so dashboards can distinguish validation errors from internal boundary-copy failures.

Copilot uses AI. Check for mistakes.
Comment on lines +31 to +33
``frozen=True``. This model is not forwarded to tool
implementations or LLM providers, so no additional boundary
copy is needed at this layer.
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ToolExecutionResult doc note says the model is "not forwarded to tool implementations", but tool implementations explicitly construct and return ToolExecutionResult. Consider rewording to clarify that metadata is not forwarded to LLM providers / external boundaries (and that the invoker converts this to ToolResult), rather than implying tools never see this model.

Suggested change
``frozen=True``. This model is not forwarded to tool
implementations or LLM providers, so no additional boundary
copy is needed at this layer.
``frozen=True``. Tool implementations construct and return this
model, but it is not forwarded to LLM providers or other external
boundaries. Instead, the invoker converts it into a provider-facing
``ToolResult``, so no additional boundary copy is needed at this
layer.

Copilot uses AI. Check for mistakes.
CLAUDE.md Outdated
- **Type hints**: all public functions, mypy strict mode
- **Docstrings**: Google style, required on public classes/functions (enforced by ruff D rules)
- **Immutability**: create new objects, never mutate existing ones. For `dict`/`list` fields in frozen Pydantic models, use `MappingProxyType` wrapping at construction (not `deepcopy` on access). Deep-copy only at system boundaries (e.g. passing data to `tool.execute()`, serializing for persistence).
- **Immutability**: create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, `BaseTool`), use `copy.deepcopy()` at construction + `MappingProxyType` wrapping for read-only enforcement. For `dict`/`list` fields in frozen Pydantic models, rely on `frozen=True` for field reassignment prevention and `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation). Deep-copy only at system boundaries (e.g. passing data to `tool.execute()`, serializing for persistence).
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This immutability bullet is a bit self-contradictory: it recommends copy.deepcopy() at construction for non-Pydantic collections, then ends with "Deep-copy only at system boundaries". Consider rephrasing the last sentence to explicitly scope it to Pydantic frozen models (or remove it) so readers don’t infer that construction-time deep-copies are discouraged.

Suggested change
- **Immutability**: create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, `BaseTool`), use `copy.deepcopy()` at construction + `MappingProxyType` wrapping for read-only enforcement. For `dict`/`list` fields in frozen Pydantic models, rely on `frozen=True` for field reassignment prevention and `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation). Deep-copy only at system boundaries (e.g. passing data to `tool.execute()`, serializing for persistence).
- **Immutability**: create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, `BaseTool`), use `copy.deepcopy()` at construction + `MappingProxyType` wrapping for read-only enforcement. For `dict`/`list` fields in frozen Pydantic models, rely on `frozen=True` for field reassignment prevention and deep-copy those fields only at system boundaries (e.g. passing data to `tool.execute()`, LLM provider serialization, inter-agent delegation, serializing for persistence).

Copilot uses AI. Check for mistakes.
DESIGN_SPEC.md Outdated
| Convention | Status | Decision | Rationale |
|------------|--------|----------|-----------|
| **Immutability strategy** | Adopted | `MappingProxyType` at construction for dict fields in registries and collections; `frozen=True` on all config/identity models | MappingProxyType is O(1) and prevents accidental mutation. Pydantic `frozen=True` is confirmed shallow (pydantic#7784). |
| **Immutability strategy** | Adopted | `copy.deepcopy()` at construction + `MappingProxyType` wrapping for non-Pydantic internal collections (registries, `BaseTool`). For Pydantic frozen models: `frozen=True` prevents field reassignment; `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization) prevents nested mutation. No MappingProxyType inside Pydantic models (serialization friction). | Deep-copy at construction fully isolates nested structures; `MappingProxyType` enforces read-only access. Boundary-copy for Pydantic models is simple, centralized, and Pydantic-native. PEP 814 `frozendict` (Python 3.15) will provide zero-friction field-level immutability when available. |
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rationale references "PEP 814 frozendict (Python 3.15)", but PEP 814 doesn’t appear elsewhere in the repo and is likely an incorrect/placeholder citation. Consider replacing this with a verified reference (or wording it generically as a potential future frozendict in CPython) to avoid publishing misleading spec guidance.

Suggested change
| **Immutability strategy** | Adopted | `copy.deepcopy()` at construction + `MappingProxyType` wrapping for non-Pydantic internal collections (registries, `BaseTool`). For Pydantic frozen models: `frozen=True` prevents field reassignment; `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization) prevents nested mutation. No MappingProxyType inside Pydantic models (serialization friction). | Deep-copy at construction fully isolates nested structures; `MappingProxyType` enforces read-only access. Boundary-copy for Pydantic models is simple, centralized, and Pydantic-native. PEP 814 `frozendict` (Python 3.15) will provide zero-friction field-level immutability when available. |
| **Immutability strategy** | Adopted | `copy.deepcopy()` at construction + `MappingProxyType` wrapping for non-Pydantic internal collections (registries, `BaseTool`). For Pydantic frozen models: `frozen=True` prevents field reassignment; `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization) prevents nested mutation. No MappingProxyType inside Pydantic models (serialization friction). | Deep-copy at construction fully isolates nested structures; `MappingProxyType` enforces read-only access. Boundary-copy for Pydantic models is simple, centralized, and Pydantic-native. If CPython eventually exposes a standardized immutable mapping type (for example, a built-in frozendict), it can be adopted to provide zero-friction field-level immutability when available. |

Copilot uses AI. Check for mistakes.
…and Copilot

- Add TOOL_INVOKE_DEEPCOPY_ERROR event constant for accurate log attribution
- Fix invoker module docstring contradicting re-raise behavior
- Update _execute_tool docstring for two-phase deepcopy+execute
- Fix ToolExecutionResult Note about tool implementation usage
- Fix ToolDefinition Note to reflect centralized deepcopy responsibility
- Fix CLAUDE.md immutability bullet self-contradiction
- Replace unverified PEP 814 reference in DESIGN_SPEC.md
- Add shallow-freeze note to SystemPrompt.metadata description
- Add inline comment about MappingProxyType deepcopy limitation
- Fix test arguments to trigger _MutatingTool nested mutation path
- Add tests for deepcopy failure exception and RecursionError paths

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Aureliolo Aureliolo merged commit 7e5e861 into main Mar 6, 2026
10 of 11 checks passed
@Aureliolo Aureliolo deleted the feat/mutable-containers-strategy branch March 6, 2026 07:06
Comment on lines +115 to +116
# dict() needed: MappingProxyType cannot be deep-copied directly
return copy.deepcopy(dict(self._parameters_schema))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Misleading comment — MappingProxyType can be deep-copied

The inline comment # dict() needed: MappingProxyType cannot be deep-copied directly is inaccurate. In Python 3, copy.deepcopy(MappingProxyType(…)) is well-defined and returns a new MappingProxyType wrapping a deep-copied dict. The real reason dict() is needed here is to satisfy the dict[str, Any] return-type annotation — a deepcopy of a MappingProxyType would return another MappingProxyType, which mismatches the declared return type.

Suggested change
# dict() needed: MappingProxyType cannot be deep-copied directly
return copy.deepcopy(dict(self._parameters_schema))
# Convert to dict first: deepcopy of MappingProxyType returns a
# MappingProxyType, but the public API declares dict[str, Any].
return copy.deepcopy(dict(self._parameters_schema))
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/ai_company/tools/base.py
Line: 115-116

Comment:
**Misleading comment — `MappingProxyType` *can* be deep-copied**

The inline comment `# dict() needed: MappingProxyType cannot be deep-copied directly` is inaccurate. In Python 3, `copy.deepcopy(MappingProxyType(…))` is well-defined and returns a new `MappingProxyType` wrapping a deep-copied dict. The real reason `dict()` is needed here is to satisfy the `dict[str, Any]` return-type annotation — a `deepcopy` of a `MappingProxyType` would return another `MappingProxyType`, which mismatches the declared return type.

```suggestion
        # Convert to dict first: deepcopy of MappingProxyType returns a
        # MappingProxyType, but the public API declares dict[str, Any].
        return copy.deepcopy(dict(self._parameters_schema))
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +358 to +415
async def test_deepcopy_failure_returns_error_result(
self,
extended_invoker: ToolInvoker,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""When deepcopy of arguments fails, a ToolResult error is returned."""
import copy as _copy_mod

real_deepcopy = _copy_mod.deepcopy
call_count = 0

def _fail_on_execute(obj: object, memo: object = None) -> object:
nonlocal call_count
call_count += 1
# First deepcopy call is in _validate_params via
# parameters_schema; let it pass. Fail on the second
# call in _execute_tool.
if call_count > 1:
msg = "cannot copy"
raise TypeError(msg)
return real_deepcopy(obj, memo) # type: ignore[arg-type]

call = ToolCall(id="c_dc", name="mutating", arguments={"key": "val"})
monkeypatch.setattr(
"ai_company.tools.invoker.copy.deepcopy",
_fail_on_execute,
)
result = await extended_invoker.invoke(call)
assert result.is_error is True
assert "safely copied" in result.content
assert result.tool_call_id == "c_dc"

async def test_recursion_error_during_deepcopy_propagates(
self,
extended_invoker: ToolInvoker,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""RecursionError during deepcopy is re-raised, not swallowed."""
import copy as _copy_mod

real_deepcopy = _copy_mod.deepcopy
call_count = 0

def _fail_on_execute(obj: object, memo: object = None) -> object:
nonlocal call_count
call_count += 1
if call_count > 1:
msg = "maximum recursion depth exceeded"
raise RecursionError(msg)
return real_deepcopy(obj, memo) # type: ignore[arg-type]

call = ToolCall(id="c_rec", name="mutating", arguments={"key": "val"})
monkeypatch.setattr(
"ai_company.tools.invoker.copy.deepcopy",
_fail_on_execute,
)
with pytest.raises(RecursionError, match="maximum recursion depth"):
await extended_invoker.invoke(call)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fragile call_count coupling to implementation internals

Both test_deepcopy_failure_returns_error_result (line 375: if call_count > 1) and test_recursion_error_during_deepcopy_propagates (line 404: if call_count > 1) rely on the assumption that exactly one copy.deepcopy call is made before _execute_tool's deepcopy — specifically the call inside BaseTool.parameters_schema triggered by _validate_params.

Because monkeypatch.setattr("ai_company.tools.invoker.copy.deepcopy", …) patches the copy module globally (it's a singleton), every copy.deepcopy call anywhere — including the one inside base.py — is intercepted. This means the counter is tightly coupled to the current code path: if _validate_params ever calls parameters_schema twice, if a new early-exit is added, or if the _MutatingTool fixture's schema is removed, the counter offset shifts and the test silently exercises the wrong failure mode (or never triggers the failure at all).

A more robust approach is to patch deepcopy only for the arguments object (e.g., by checking isinstance(obj, dict) and …) or to separate the two patched scopes — patch base.copy.deepcopy to a pass-through and patch invoker.copy.deepcopy to unconditionally fail, removing the counter entirely:

monkeypatch.setattr("ai_company.tools.base.copy.deepcopy", real_deepcopy)
monkeypatch.setattr("ai_company.tools.invoker.copy.deepcopy", lambda obj, memo=None: (_ for _ in ()).throw(TypeError("cannot copy")))
Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/unit/tools/test_invoker.py
Line: 358-415

Comment:
**Fragile `call_count` coupling to implementation internals**

Both `test_deepcopy_failure_returns_error_result` (line 375: `if call_count > 1`) and `test_recursion_error_during_deepcopy_propagates` (line 404: `if call_count > 1`) rely on the assumption that exactly **one** `copy.deepcopy` call is made before `_execute_tool`'s deepcopy — specifically the call inside `BaseTool.parameters_schema` triggered by `_validate_params`.

Because `monkeypatch.setattr("ai_company.tools.invoker.copy.deepcopy", …)` patches the `copy` module globally (it's a singleton), every `copy.deepcopy` call anywhere — including the one inside `base.py` — is intercepted. This means the counter is tightly coupled to the current code path: if `_validate_params` ever calls `parameters_schema` twice, if a new early-exit is added, or if the `_MutatingTool` fixture's schema is removed, the counter offset shifts and the test silently exercises the wrong failure mode (or never triggers the failure at all).

A more robust approach is to patch deepcopy *only for the arguments object* (e.g., by checking `isinstance(obj, dict) and …`) or to separate the two patched scopes — patch `base.copy.deepcopy` to a pass-through and patch `invoker.copy.deepcopy` to unconditionally fail, removing the counter entirely:

```python
monkeypatch.setattr("ai_company.tools.base.copy.deepcopy", real_deepcopy)
monkeypatch.setattr("ai_company.tools.invoker.copy.deepcopy", lambda obj, memo=None: (_ for _ in ()).throw(TypeError("cannot copy")))
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +119 to +123
def test_schema_internal_is_read_only(self) -> None:
schema = {"type": "object", "properties": {"x": {"type": "string"}}}
tool = _ConcreteTool(name="t", parameters_schema=schema)
with pytest.raises(TypeError):
tool._parameters_schema["injected"] = True # type: ignore[index]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_schema_internal_is_read_only only covers top-level mutation

The test correctly asserts that tool._parameters_schema["injected"] = True raises TypeError (top-level MappingProxyType enforcement). However, it does not cover the nested mutability gap: since _parameters_schema["properties"] is a plain dict, the following silently succeeds and corrupts internal state:

tool._parameters_schema["properties"]["x"]["type"] = "integer"  # No TypeError raised!

Given the PR's stated goal of strengthening immutability and the explicit test_schema_nested_isolated_on_construction / test_schema_property_nested_mutation_isolated additions, adding a clarifying comment would make the documented guarantees clear and prevent future confusion:

def test_schema_internal_is_read_only(self) -> None:
    schema = {"type": "object", "properties": {"x": {"type": "string"}}}
    tool = _ConcreteTool(name="t", parameters_schema=schema)
    with pytest.raises(TypeError):
        tool._parameters_schema["injected"] = True  # type: ignore[index]
    # Note: MappingProxyType provides only top-level protection. Nested dicts
    # in _parameters_schema remain mutable, but direct access is discouraged
    # since it's a private attribute. Public isolation is guaranteed by the
    # parameters_schema property, which returns a deep copy.
Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/unit/tools/test_base.py
Line: 119-123

Comment:
**`test_schema_internal_is_read_only` only covers top-level mutation**

The test correctly asserts that `tool._parameters_schema["injected"] = True` raises `TypeError` (top-level `MappingProxyType` enforcement). However, it does not cover the nested mutability gap: since `_parameters_schema["properties"]` is a plain `dict`, the following silently succeeds and corrupts internal state:

```python
tool._parameters_schema["properties"]["x"]["type"] = "integer"  # No TypeError raised!
```

Given the PR's stated goal of strengthening immutability and the explicit `test_schema_nested_isolated_on_construction` / `test_schema_property_nested_mutation_isolated` additions, adding a clarifying comment would make the documented guarantees clear and prevent future confusion:

```python
def test_schema_internal_is_read_only(self) -> None:
    schema = {"type": "object", "properties": {"x": {"type": "string"}}}
    tool = _ConcreteTool(name="t", parameters_schema=schema)
    with pytest.raises(TypeError):
        tool._parameters_schema["injected"] = True  # type: ignore[index]
    # Note: MappingProxyType provides only top-level protection. Nested dicts
    # in _parameters_schema remain mutable, but direct access is discouraged
    # since it's a private attribute. Public isolation is guaranteed by the
    # parameters_schema property, which returns a deep copy.
```

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Aureliolo added a commit that referenced this pull request Mar 10, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.1.1](ai-company-v0.1.0...ai-company-v0.1.1)
(2026-03-10)


### Features

* add autonomy levels and approval timeout policies
([#42](#42),
[#126](#126))
([#197](#197))
([eecc25a](eecc25a))
* add CFO cost optimization service with anomaly detection, reports, and
approval decisions
([#186](#186))
([a7fa00b](a7fa00b))
* add code quality toolchain (ruff, mypy, pre-commit, dependabot)
([#63](#63))
([36681a8](36681a8))
* add configurable cost tiers and subscription/quota-aware tracking
([#67](#67))
([#185](#185))
([9baedfa](9baedfa))
* add container packaging, Docker Compose, and CI pipeline
([#269](#269))
([435bdfe](435bdfe)),
closes [#267](#267)
* add coordination error taxonomy classification pipeline
([#146](#146))
([#181](#181))
([70c7480](70c7480))
* add cost-optimized, hierarchical, and auction assignment strategies
([#175](#175))
([ce924fa](ce924fa)),
closes [#173](#173)
* add design specification, license, and project setup
([8669a09](8669a09))
* add env var substitution and config file auto-discovery
([#77](#77))
([7f53832](7f53832))
* add FastestStrategy routing + vendor-agnostic cleanup
([#140](#140))
([09619cb](09619cb)),
closes [#139](#139)
* add HR engine and performance tracking
([#45](#45),
[#47](#47))
([#193](#193))
([2d091ea](2d091ea))
* add issue auto-search and resolution verification to PR review skill
([#119](#119))
([deecc39](deecc39))
* add memory retrieval, ranking, and context injection pipeline
([#41](#41))
([873b0aa](873b0aa))
* add pluggable MemoryBackend protocol with models, config, and events
([#180](#180))
([46cfdd4](46cfdd4))
* add pluggable MemoryBackend protocol with models, config, and events
([#32](#32))
([46cfdd4](46cfdd4))
* add pluggable PersistenceBackend protocol with SQLite implementation
([#36](#36))
([f753779](f753779))
* add progressive trust and promotion/demotion subsystems
([#43](#43),
[#49](#49))
([3a87c08](3a87c08))
* add retry handler, rate limiter, and provider resilience
([#100](#100))
([b890545](b890545))
* add SecOps security agent with rule engine, audit log, and ToolInvoker
integration ([#40](#40))
([83b7b6c](83b7b6c))
* add shared org memory and memory consolidation/archival
([#125](#125),
[#48](#48))
([4a0832b](4a0832b))
* design unified provider interface
([#86](#86))
([3e23d64](3e23d64))
* expand template presets, rosters, and add inheritance
([#80](#80),
[#81](#81),
[#84](#84))
([15a9134](15a9134))
* implement agent runtime state vs immutable config split
([#115](#115))
([4cb1ca5](4cb1ca5))
* implement AgentEngine core orchestrator
([#11](#11))
([#143](#143))
([f2eb73a](f2eb73a))
* implement basic tool system (registry, invocation, results)
([#15](#15))
([c51068b](c51068b))
* implement built-in file system tools
([#18](#18))
([325ef98](325ef98))
* implement communication foundation — message bus, dispatcher, and
messenger ([#157](#157))
([8e71bfd](8e71bfd))
* implement company template system with 7 built-in presets
([#85](#85))
([cbf1496](cbf1496))
* implement conflict resolution protocol
([#122](#122))
([#166](#166))
([e03f9f2](e03f9f2))
* implement core entity and role system models
([#69](#69))
([acf9801](acf9801))
* implement crash recovery with fail-and-reassign strategy
([#149](#149))
([e6e91ed](e6e91ed))
* implement engine extensions — Plan-and-Execute loop and call
categorization
([#134](#134),
[#135](#135))
([#159](#159))
([9b2699f](9b2699f))
* implement enterprise logging system with structlog
([#73](#73))
([2f787e5](2f787e5))
* implement graceful shutdown with cooperative timeout strategy
([#130](#130))
([6592515](6592515))
* implement hierarchical delegation and loop prevention
([#12](#12),
[#17](#17))
([6be60b6](6be60b6))
* implement LiteLLM driver and provider registry
([#88](#88))
([ae3f18b](ae3f18b)),
closes [#4](#4)
* implement LLM decomposition strategy and workspace isolation
([#174](#174))
([aa0eefe](aa0eefe))
* implement meeting protocol system
([#123](#123))
([ee7caca](ee7caca))
* implement message and communication domain models
([#74](#74))
([560a5d2](560a5d2))
* implement model routing engine
([#99](#99))
([d3c250b](d3c250b))
* implement parallel agent execution
([#22](#22))
([#161](#161))
([65940b3](65940b3))
* implement per-call cost tracking service
([#7](#7))
([#102](#102))
([c4f1f1c](c4f1f1c))
* implement personality injection and system prompt construction
([#105](#105))
([934dd85](934dd85))
* implement single-task execution lifecycle
([#21](#21))
([#144](#144))
([c7e64e4](c7e64e4))
* implement subprocess sandbox for tool execution isolation
([#131](#131))
([#153](#153))
([3c8394e](3c8394e))
* implement task assignment subsystem with pluggable strategies
([#172](#172))
([c7f1b26](c7f1b26)),
closes [#26](#26)
[#30](#30)
* implement task decomposition and routing engine
([#14](#14))
([9c7fb52](9c7fb52))
* implement Task, Project, Artifact, Budget, and Cost domain models
([#71](#71))
([81eabf1](81eabf1))
* implement tool permission checking
([#16](#16))
([833c190](833c190))
* implement YAML config loader with Pydantic validation
([#59](#59))
([ff3a2ba](ff3a2ba))
* implement YAML config loader with Pydantic validation
([#75](#75))
([ff3a2ba](ff3a2ba))
* initialize project with uv, hatchling, and src layout
([39005f9](39005f9))
* initialize project with uv, hatchling, and src layout
([#62](#62))
([39005f9](39005f9))
* Litestar REST API, WebSocket feed, and approval queue (M6)
([#189](#189))
([29fcd08](29fcd08))
* make TokenUsage.total_tokens a computed field
([#118](#118))
([c0bab18](c0bab18)),
closes [#109](#109)
* parallel tool execution in ToolInvoker.invoke_all
([#137](#137))
([58517ee](58517ee))
* testing framework, CI pipeline, and M0 gap fixes
([#64](#64))
([f581749](f581749))
* wire all modules into observability system
([#97](#97))
([f7a0617](f7a0617))


### Bug Fixes

* address Greptile post-merge review findings from PRs
[#170](https://github.com/Aureliolo/ai-company/issues/170)-[#175](https://github.com/Aureliolo/ai-company/issues/175)
([#176](#176))
([c5ca929](c5ca929))
* address post-merge review feedback from PRs
[#164](https://github.com/Aureliolo/ai-company/issues/164)-[#167](https://github.com/Aureliolo/ai-company/issues/167)
([#170](#170))
([3bf897a](3bf897a)),
closes [#169](#169)
* enforce strict mypy on test files
([#89](#89))
([aeeff8c](aeeff8c))
* harden Docker sandbox, MCP bridge, and code runner
([#50](#50),
[#53](#53))
([d5e1b6e](d5e1b6e))
* harden git tools security + code quality improvements
([#150](#150))
([000a325](000a325))
* harden subprocess cleanup, env filtering, and shutdown resilience
([#155](#155))
([d1fe1fb](d1fe1fb))
* incorporate post-merge feedback + pre-PR review fixes
([#164](#164))
([c02832a](c02832a))
* pre-PR review fixes for post-merge findings
([#183](#183))
([26b3108](26b3108))
* strengthen immutability for BaseTool schema and ToolInvoker boundaries
([#117](#117))
([7e5e861](7e5e861))


### Performance

* harden non-inferable principle implementation
([#195](#195))
([02b5f4e](02b5f4e)),
closes [#188](#188)


### Refactoring

* adopt NotBlankStr across all models
([#108](#108))
([#120](#120))
([ef89b90](ef89b90))
* extract _SpendingTotals base class from spending summary models
([#111](#111))
([2f39c1b](2f39c1b))
* harden BudgetEnforcer with error handling, validation extraction, and
review fixes
([#182](#182))
([c107bf9](c107bf9))
* harden personality profiles, department validation, and template
rendering ([#158](#158))
([10b2299](10b2299))
* pre-PR review improvements for ExecutionLoop + ReAct loop
([#124](#124))
([8dfb3c0](8dfb3c0))
* split events.py into per-domain event modules
([#136](#136))
([e9cba89](e9cba89))


### Documentation

* add ADR-001 memory layer evaluation and selection
([#178](#178))
([db3026f](db3026f)),
closes [#39](#39)
* add agent scaling research findings to DESIGN_SPEC
([#145](#145))
([57e487b](57e487b))
* add CLAUDE.md, contributing guide, and dev documentation
([#65](#65))
([55c1025](55c1025)),
closes [#54](#54)
* add crash recovery, sandboxing, analytics, and testing decisions
([#127](#127))
([5c11595](5c11595))
* address external review feedback with MVP scope and new protocols
([#128](#128))
([3b30b9a](3b30b9a))
* expand design spec with pluggable strategy protocols
([#121](#121))
([6832db6](6832db6))
* finalize 23 design decisions (ADR-002)
([#190](#190))
([8c39742](8c39742))
* update project docs for M2.5 conventions and add docs-consistency
review agent
([#114](#114))
([99766ee](99766ee))


### Tests

* add e2e single agent integration tests
([#24](#24))
([#156](#156))
([f566fb4](f566fb4))
* add provider adapter integration tests
([#90](#90))
([40a61f4](40a61f4))


### CI/CD

* add Release Please for automated versioning and GitHub Releases
([#278](#278))
([a488758](a488758))
* bump actions/checkout from 4 to 6
([#95](#95))
([1897247](1897247))
* bump actions/upload-artifact from 4 to 7
([#94](#94))
([27b1517](27b1517))
* harden CI/CD pipeline
([#92](#92))
([ce4693c](ce4693c))
* split vulnerability scans into critical-fail and high-warn tiers
([#277](#277))
([aba48af](aba48af))


### Maintenance

* add /worktree skill for parallel worktree management
([#171](#171))
([951e337](951e337))
* add design spec context loading to research-link skill
([8ef9685](8ef9685))
* add post-merge-cleanup skill
([#70](#70))
([f913705](f913705))
* add pre-pr-review skill and update CLAUDE.md
([#103](#103))
([92e9023](92e9023))
* add research-link skill and rename skill files to SKILL.md
([#101](#101))
([651c577](651c577))
* bump aiosqlite from 0.21.0 to 0.22.1
([#191](#191))
([3274a86](3274a86))
* bump pyyaml from 6.0.2 to 6.0.3 in the minor-and-patch group
([#96](#96))
([0338d0c](0338d0c))
* bump ruff from 0.15.4 to 0.15.5
([a49ee46](a49ee46))
* fix M0 audit items
([#66](#66))
([c7724b5](c7724b5))
* pin setup-uv action to full SHA
([#281](#281))
([4448002](4448002))
* post-audit cleanup — PEP 758, loggers, bug fixes, refactoring, tests,
hookify rules
([#148](#148))
([c57a6a9](c57a6a9))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
Aureliolo added a commit that referenced this pull request Mar 11, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.1.0](v0.0.0...v0.1.0)
(2026-03-11)


### Features

* add autonomy levels and approval timeout policies
([#42](#42),
[#126](#126))
([#197](#197))
([eecc25a](eecc25a))
* add CFO cost optimization service with anomaly detection, reports, and
approval decisions
([#186](#186))
([a7fa00b](a7fa00b))
* add code quality toolchain (ruff, mypy, pre-commit, dependabot)
([#63](#63))
([36681a8](36681a8))
* add configurable cost tiers and subscription/quota-aware tracking
([#67](#67))
([#185](#185))
([9baedfa](9baedfa))
* add container packaging, Docker Compose, and CI pipeline
([#269](#269))
([435bdfe](435bdfe)),
closes [#267](#267)
* add coordination error taxonomy classification pipeline
([#146](#146))
([#181](#181))
([70c7480](70c7480))
* add cost-optimized, hierarchical, and auction assignment strategies
([#175](#175))
([ce924fa](ce924fa)),
closes [#173](#173)
* add design specification, license, and project setup
([8669a09](8669a09))
* add env var substitution and config file auto-discovery
([#77](#77))
([7f53832](7f53832))
* add FastestStrategy routing + vendor-agnostic cleanup
([#140](#140))
([09619cb](09619cb)),
closes [#139](#139)
* add HR engine and performance tracking
([#45](#45),
[#47](#47))
([#193](#193))
([2d091ea](2d091ea))
* add issue auto-search and resolution verification to PR review skill
([#119](#119))
([deecc39](deecc39))
* add mandatory JWT + API key authentication
([#256](#256))
([c279cfe](c279cfe))
* add memory retrieval, ranking, and context injection pipeline
([#41](#41))
([873b0aa](873b0aa))
* add pluggable MemoryBackend protocol with models, config, and events
([#180](#180))
([46cfdd4](46cfdd4))
* add pluggable MemoryBackend protocol with models, config, and events
([#32](#32))
([46cfdd4](46cfdd4))
* add pluggable output scan response policies
([#263](#263))
([b9907e8](b9907e8))
* add pluggable PersistenceBackend protocol with SQLite implementation
([#36](#36))
([f753779](f753779))
* add progressive trust and promotion/demotion subsystems
([#43](#43),
[#49](#49))
([3a87c08](3a87c08))
* add retry handler, rate limiter, and provider resilience
([#100](#100))
([b890545](b890545))
* add SecOps security agent with rule engine, audit log, and ToolInvoker
integration ([#40](#40))
([83b7b6c](83b7b6c))
* add shared org memory and memory consolidation/archival
([#125](#125),
[#48](#48))
([4a0832b](4a0832b))
* design unified provider interface
([#86](#86))
([3e23d64](3e23d64))
* expand template presets, rosters, and add inheritance
([#80](#80),
[#81](#81),
[#84](#84))
([15a9134](15a9134))
* implement agent runtime state vs immutable config split
([#115](#115))
([4cb1ca5](4cb1ca5))
* implement AgentEngine core orchestrator
([#11](#11))
([#143](#143))
([f2eb73a](f2eb73a))
* implement AuditRepository for security audit log persistence
([#279](#279))
([94bc29f](94bc29f))
* implement basic tool system (registry, invocation, results)
([#15](#15))
([c51068b](c51068b))
* implement built-in file system tools
([#18](#18))
([325ef98](325ef98))
* implement communication foundation — message bus, dispatcher, and
messenger ([#157](#157))
([8e71bfd](8e71bfd))
* implement company template system with 7 built-in presets
([#85](#85))
([cbf1496](cbf1496))
* implement conflict resolution protocol
([#122](#122))
([#166](#166))
([e03f9f2](e03f9f2))
* implement core entity and role system models
([#69](#69))
([acf9801](acf9801))
* implement crash recovery with fail-and-reassign strategy
([#149](#149))
([e6e91ed](e6e91ed))
* implement engine extensions — Plan-and-Execute loop and call
categorization
([#134](#134),
[#135](#135))
([#159](#159))
([9b2699f](9b2699f))
* implement enterprise logging system with structlog
([#73](#73))
([2f787e5](2f787e5))
* implement graceful shutdown with cooperative timeout strategy
([#130](#130))
([6592515](6592515))
* implement hierarchical delegation and loop prevention
([#12](#12),
[#17](#17))
([6be60b6](6be60b6))
* implement LiteLLM driver and provider registry
([#88](#88))
([ae3f18b](ae3f18b)),
closes [#4](#4)
* implement LLM decomposition strategy and workspace isolation
([#174](#174))
([aa0eefe](aa0eefe))
* implement meeting protocol system
([#123](#123))
([ee7caca](ee7caca))
* implement message and communication domain models
([#74](#74))
([560a5d2](560a5d2))
* implement model routing engine
([#99](#99))
([d3c250b](d3c250b))
* implement parallel agent execution
([#22](#22))
([#161](#161))
([65940b3](65940b3))
* implement per-call cost tracking service
([#7](#7))
([#102](#102))
([c4f1f1c](c4f1f1c))
* implement personality injection and system prompt construction
([#105](#105))
([934dd85](934dd85))
* implement single-task execution lifecycle
([#21](#21))
([#144](#144))
([c7e64e4](c7e64e4))
* implement subprocess sandbox for tool execution isolation
([#131](#131))
([#153](#153))
([3c8394e](3c8394e))
* implement task assignment subsystem with pluggable strategies
([#172](#172))
([c7f1b26](c7f1b26)),
closes [#26](#26)
[#30](#30)
* implement task decomposition and routing engine
([#14](#14))
([9c7fb52](9c7fb52))
* implement Task, Project, Artifact, Budget, and Cost domain models
([#71](#71))
([81eabf1](81eabf1))
* implement tool permission checking
([#16](#16))
([833c190](833c190))
* implement YAML config loader with Pydantic validation
([#59](#59))
([ff3a2ba](ff3a2ba))
* implement YAML config loader with Pydantic validation
([#75](#75))
([ff3a2ba](ff3a2ba))
* initialize project with uv, hatchling, and src layout
([39005f9](39005f9))
* initialize project with uv, hatchling, and src layout
([#62](#62))
([39005f9](39005f9))
* Litestar REST API, WebSocket feed, and approval queue (M6)
([#189](#189))
([29fcd08](29fcd08))
* make TokenUsage.total_tokens a computed field
([#118](#118))
([c0bab18](c0bab18)),
closes [#109](#109)
* parallel tool execution in ToolInvoker.invoke_all
([#137](#137))
([58517ee](58517ee))
* testing framework, CI pipeline, and M0 gap fixes
([#64](#64))
([f581749](f581749))
* wire all modules into observability system
([#97](#97))
([f7a0617](f7a0617))


### Bug Fixes

* address Greptile post-merge review findings from PRs
[#170](https://github.com/Aureliolo/ai-company/issues/170)-[#175](https://github.com/Aureliolo/ai-company/issues/175)
([#176](#176))
([c5ca929](c5ca929))
* address post-merge review feedback from PRs
[#164](https://github.com/Aureliolo/ai-company/issues/164)-[#167](https://github.com/Aureliolo/ai-company/issues/167)
([#170](#170))
([3bf897a](3bf897a)),
closes [#169](#169)
* enforce strict mypy on test files
([#89](#89))
([aeeff8c](aeeff8c))
* harden Docker sandbox, MCP bridge, and code runner
([#50](#50),
[#53](#53))
([d5e1b6e](d5e1b6e))
* harden git tools security + code quality improvements
([#150](#150))
([000a325](000a325))
* harden subprocess cleanup, env filtering, and shutdown resilience
([#155](#155))
([d1fe1fb](d1fe1fb))
* incorporate post-merge feedback + pre-PR review fixes
([#164](#164))
([c02832a](c02832a))
* pre-PR review fixes for post-merge findings
([#183](#183))
([26b3108](26b3108))
* resolve circular imports, bump litellm, fix release tag format
([#286](#286))
([a6659b5](a6659b5))
* strengthen immutability for BaseTool schema and ToolInvoker boundaries
([#117](#117))
([7e5e861](7e5e861))


### Performance

* harden non-inferable principle implementation
([#195](#195))
([02b5f4e](02b5f4e)),
closes [#188](#188)


### Refactoring

* adopt NotBlankStr across all models
([#108](#108))
([#120](#120))
([ef89b90](ef89b90))
* extract _SpendingTotals base class from spending summary models
([#111](#111))
([2f39c1b](2f39c1b))
* harden BudgetEnforcer with error handling, validation extraction, and
review fixes
([#182](#182))
([c107bf9](c107bf9))
* harden personality profiles, department validation, and template
rendering ([#158](#158))
([10b2299](10b2299))
* pre-PR review improvements for ExecutionLoop + ReAct loop
([#124](#124))
([8dfb3c0](8dfb3c0))
* split events.py into per-domain event modules
([#136](#136))
([e9cba89](e9cba89))


### Documentation

* add ADR-001 memory layer evaluation and selection
([#178](#178))
([db3026f](db3026f)),
closes [#39](#39)
* add agent scaling research findings to DESIGN_SPEC
([#145](#145))
([57e487b](57e487b))
* add CLAUDE.md, contributing guide, and dev documentation
([#65](#65))
([55c1025](55c1025)),
closes [#54](#54)
* add crash recovery, sandboxing, analytics, and testing decisions
([#127](#127))
([5c11595](5c11595))
* address external review feedback with MVP scope and new protocols
([#128](#128))
([3b30b9a](3b30b9a))
* expand design spec with pluggable strategy protocols
([#121](#121))
([6832db6](6832db6))
* finalize 23 design decisions (ADR-002)
([#190](#190))
([8c39742](8c39742))
* update project docs for M2.5 conventions and add docs-consistency
review agent
([#114](#114))
([99766ee](99766ee))


### Tests

* add e2e single agent integration tests
([#24](#24))
([#156](#156))
([f566fb4](f566fb4))
* add provider adapter integration tests
([#90](#90))
([40a61f4](40a61f4))


### CI/CD

* add Release Please for automated versioning and GitHub Releases
([#278](#278))
([a488758](a488758))
* bump actions/checkout from 4 to 6
([#95](#95))
([1897247](1897247))
* bump actions/upload-artifact from 4 to 7
([#94](#94))
([27b1517](27b1517))
* bump anchore/scan-action from 6.5.1 to 7.3.2
([#271](#271))
([80a1c15](80a1c15))
* bump docker/build-push-action from 6.19.2 to 7.0.0
([#273](#273))
([dd0219e](dd0219e))
* bump docker/login-action from 3.7.0 to 4.0.0
([#272](#272))
([33d6238](33d6238))
* bump docker/metadata-action from 5.10.0 to 6.0.0
([#270](#270))
([baee04e](baee04e))
* bump docker/setup-buildx-action from 3.12.0 to 4.0.0
([#274](#274))
([5fc06f7](5fc06f7))
* bump sigstore/cosign-installer from 3.9.1 to 4.1.0
([#275](#275))
([29dd16c](29dd16c))
* harden CI/CD pipeline
([#92](#92))
([ce4693c](ce4693c))
* split vulnerability scans into critical-fail and high-warn tiers
([#277](#277))
([aba48af](aba48af))


### Maintenance

* add /worktree skill for parallel worktree management
([#171](#171))
([951e337](951e337))
* add design spec context loading to research-link skill
([8ef9685](8ef9685))
* add post-merge-cleanup skill
([#70](#70))
([f913705](f913705))
* add pre-pr-review skill and update CLAUDE.md
([#103](#103))
([92e9023](92e9023))
* add research-link skill and rename skill files to SKILL.md
([#101](#101))
([651c577](651c577))
* bump aiosqlite from 0.21.0 to 0.22.1
([#191](#191))
([3274a86](3274a86))
* bump pyyaml from 6.0.2 to 6.0.3 in the minor-and-patch group
([#96](#96))
([0338d0c](0338d0c))
* bump ruff from 0.15.4 to 0.15.5
([a49ee46](a49ee46))
* fix M0 audit items
([#66](#66))
([c7724b5](c7724b5))
* **main:** release ai-company 0.1.1
([#282](#282))
([2f4703d](2f4703d))
* pin setup-uv action to full SHA
([#281](#281))
([4448002](4448002))
* post-audit cleanup — PEP 758, loggers, bug fixes, refactoring, tests,
hookify rules
([#148](#148))
([c57a6a9](c57a6a9))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Signed-off-by: Aurelio <19254254+Aureliolo@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Establish consistent strategy for mutable containers in frozen Pydantic models

2 participants