Skip to content

feat: add remaining activity event types (task_started, tool_used, delegation, cost_incurred)#832

Merged
Aureliolo merged 9 commits intomainfrom
feat/activity-event-types
Mar 26, 2026
Merged

feat: add remaining activity event types (task_started, tool_used, delegation, cost_incurred)#832
Aureliolo merged 9 commits intomainfrom
feat/activity-event-types

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

Summary

  • Add 5 new activity event types to complete the 10-type spec from feat: agent performance metrics and activity timeline endpoints #772: task_started, tool_used, delegation_sent, delegation_received, cost_incurred
  • Each type has: data source, converter in hr/activity.py, merge_activity_timeline integration (backward-compatible keyword-only args), controller wiring with graceful degradation, and unit tests
  • New infrastructure: ToolInvocationRecord model, ToolInvocationTracker store, DelegationRecordStore with dual-perspective querying, invocation_bridge.py for invoker integration
  • Controller uses asyncio.TaskGroup for parallel async fetches
  • Pre-reviewed by 9 agents, 14 findings addressed

Test plan

  • 10,485 unit tests pass (uv run python -m pytest tests/ -n auto -m unit)
  • mypy strict: 1,208 files, no issues
  • ruff lint + format: all checks passed
  • All pre-commit and pre-push hooks pass
  • New converter tests: task_started (4), cost_incurred (3), tool_used (5), delegation (4), all-types merge (1)
  • New store tests: ToolInvocationTracker (8), DelegationRecordStore (9)
  • New controller tests: cost_incurred feed + filter, tool_used feed, delegation org-wide + agent-filtered (5)
  • Backward compatibility: positional-only merge call still works

Closes #812

🤖 Generated with Claude Code

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 26, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f3866cbc-0543-4c03-96a4-90846af244fa

📥 Commits

Reviewing files that changed from the base of the PR and between 73c83c0 and ff68765.

📒 Files selected for processing (1)
  • src/synthorg/api/controllers/activities.py
📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Backend
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Do NOT use from __future__ import annotations - Python 3.14 has PEP 649 with native lazy annotations
Use except A, B: syntax (no parentheses) per PEP 758 - ruff enforces this on Python 3.14

Files:

  • src/synthorg/api/controllers/activities.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.py: All public functions must have type hints. Type checking enforced by mypy in strict mode.
Google-style docstrings required on all public classes and functions, enforced by ruff D rules
Use Pydantic v2 conventions: BaseModel, model_validator, computed_field, ConfigDict. Use @computed_field for derived values instead of storing redundant fields. Use NotBlankStr for all identifier/name fields.
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g., multiple tool invocations, parallel agent calls) over bare create_task
Line length must be 88 characters (enforced by ruff)
Functions must be less than 50 lines, files less than 800 lines
Handle errors explicitly, never silently swallow them. All error paths must log at WARNING or ERROR with context before raising.
Never use import logging / logging.getLogger() / print() in application code (except observability/setup.py and observability/sinks.py which may use stdlib logging and stderr print for bootstrap)
Always use logger as the variable name (not _logger, not log)
Always use event name constants from domain-specific modules under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT
Use structured logging: always logger.info(EVENT, key=value) - never use printf-style formatting like logger.info("msg %s", val)
All state transitions must log at INFO level
DEBUG logging is appropriate for object creation, internal flow, and entry/exit of key functions
Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples. Use generic names: example-provider, example-large-001, example-medium-001, etc. Vendor names only in: (1) Operations design page, (2) .claude/ skill files, (3) third-party import paths.

Files:

  • src/synthorg/api/controllers/activities.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Every module with business logic MUST import: from synthorg.observability import get_logger and instantiate logger = get_logger(__name__)

Files:

  • src/synthorg/api/controllers/activities.py
src/synthorg/api/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/api/**/*.py: REST API defined with Litestar. Errors must use RFC 9457 format in src/synthorg/api/
Litestar API must include setup wizard, auth/, auto-wiring, and lifecycle management

Files:

  • src/synthorg/api/controllers/activities.py
🧠 Learnings (23)
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Handle errors explicitly; never silently swallow exceptions

Applied to files:

  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Use PEP 758 except syntax with `except A, B:` (no parentheses) for multiple exceptions—ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Handle errors explicitly, never silently swallow exceptions

Applied to files:

  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (no parentheses) for exception handling — PEP 758 exception syntax enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Handle errors explicitly—never silently swallow exceptions.

Applied to files:

  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: `except A, B:` (no parentheses) — enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (no parentheses) per PEP 758 - ruff enforces this on Python 3.14

Applied to files:

  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Prefer `asyncio.TaskGroup` for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare `create_task`. Existing code is being migrated incrementally.

Applied to files:

  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-17T18:52:05.142Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T18:52:05.142Z
Learning: Applies to **/*.py : Async concurrency: prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare create_task.

Applied to files:

  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to src/**/*.py : Prefer `asyncio.TaskGroup` for fan-out/fan-in parallel operations in new code (e.g., multiple tool invocations, parallel agent calls) over bare `create_task`

Applied to files:

  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/engine/**/*.py : Engine package (engine/): agent orchestration, parallel execution, task decomposition, routing, TaskEngine (centralized single-writer), task lifecycle/recovery/shutdown, workspace isolation, coordination (4 dispatchers: SAS/centralized/decentralized/context-dependent, wave execution), approval gates (escalation detection, context parking/resume), stagnation detection (ToolRepetitionDetector, corrective prompt injection), AgentRuntimeState (execution status), context budget management, conversation compaction (oldest-turns summarizer)

Applied to files:

  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-15T16:55:07.730Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T16:55:07.730Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising.

Applied to files:

  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : All error paths must log at WARNING or ERROR with context before raising.

Applied to files:

  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising

Applied to files:

  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions.

Applied to files:

  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to src/**/*.py : Handle errors explicitly, never silently swallow them. All error paths must log at WARNING or ERROR with context before raising.

Applied to files:

  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, and key function entry/exit

Applied to files:

  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-17T06:43:14.114Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:43:14.114Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions. Pure data models, enums, and re-exports do NOT need logging.

Applied to files:

  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Handle errors explicitly, never silently swallow. Validate at system boundaries (user input, external APIs, config files).

Applied to files:

  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)

Applied to files:

  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...

Applied to files:

  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.

Applied to files:

  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/hr/**/*.py : HR package (hr/): hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, LLM calibration, collaboration overrides, trend detection), promotion/demotion (criteria evaluation, approval strategies, model mapping)

Applied to files:

  • src/synthorg/api/controllers/activities.py
🔇 Additional comments (7)
src/synthorg/api/controllers/activities.py (7)

109-120: Synchronous _fetch_task_metrics still executes serially before the parallel TaskGroup.

The call on line 109 blocks until task metrics are fetched before entering the asyncio.TaskGroup. This serializes what could be concurrent I/O. Consider wrapping it with asyncio.to_thread() inside the TaskGroup if performance_tracker.get_task_metrics is CPU-bound/blocking, or convert it to an async function.


183-186: ServiceUnavailableError re-raised without logging context.

Lines 185-186 re-raise ServiceUnavailableError without emitting a warning/error log identifying the fetcher source (performance_tracker). The same pattern appears in _fetch_cost_records (lines 214-215), _fetch_tool_invocations (lines 243-244), and _safe_delegation_query (lines 264-265). As per coding guidelines: "All error paths must log at WARNING or ERROR with context before raising."

For MemoryError/RecursionError, skipping the log is reasonable to avoid further allocations. However, ServiceUnavailableError should log source context before propagating.

🛡️ Proposed fix to add logging before ServiceUnavailableError re-raise
     except MemoryError, RecursionError:
         raise
-    except ServiceUnavailableError:
-        raise
+    except ServiceUnavailableError:
+        logger.warning(
+            API_REQUEST_ERROR,
+            endpoint="activities",
+            error="performance_tracker_service_unavailable",
+            exc_info=True,
+        )
+        raise
     except Exception:

Apply the same pattern to _fetch_cost_records, _fetch_tool_invocations, and _safe_delegation_query with appropriate error labels.


111-140: TaskGroup exception handling and graceful degradation look correct.

The ExceptionGroup handling properly separates fatal errors (MemoryError, RecursionError) and service unavailability from recoverable failures. Non-fatal exceptions are logged with error_count and the code degrades gracefully by treating failed fetches as empty datasets. The delegation fetch correctly splits perspectives so one-sided failures don't discard the other (addressed in commit ff68765).


142-149: merge_activity_timeline call correctly passes all new data sources.

The call properly provides the four new keyword-only arguments (cost_records, tool_invocations, delegation_records_sent, delegation_records_received) matching the updated function signature in hr/activity.py.


255-273: _safe_delegation_query helper enables independent degradation per perspective.

Good extraction—this helper allows _fetch_delegation_records to query delegator and delegatee records independently, so a failure in one does not discard the other.


290-296: Org-wide delegation correctly returns same records for both perspectives.

Returning (all_records, all_records) is intentional: the downstream converters (_delegation_to_sent_activity and _delegation_to_received_activity in hr/activity.py) create distinct ActivityEvent entries with different event_type values and agent_id perspectives from the same physical record.


298-308: Agent-specific delegation queries correctly isolate failures.

Each perspective is fetched independently via _safe_delegation_query, ensuring a failure in one direction does not discard successfully-fetched records from the other.


Walkthrough

Adds persisted activity sources and plumbing: a ToolInvocationRecord model, ToolInvocationTracker, and recording bridge; a DelegationRecordStore and optional record-store persistence in DelegationService; and a started_at field on TaskMetricRecord with temporal validation. App wiring (create_app, AppState, AgentEngine, ToolInvoker) accepts optional tracker/store dependencies. The activities controller concurrently fetches cost, tool, and delegation records and passes them to an extended merge_activity_timeline, which now emits task_started, tool_used, cost_incurred, delegation_sent, and delegation_received. Documentation and tests updated.

Suggested labels

autorelease: tagged

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed Title accurately summarizes the main change: adding 5 remaining activity event types to complete the spec.
Description check ✅ Passed Description is comprehensive and directly related to the changeset, detailing the 5 new event types, infrastructure additions, testing approach, and issue resolution.
Linked Issues check ✅ Passed All objectives from #812 are met: task_started (started_at field), tool_used (ToolInvocationRecord/Tracker), delegation_sent/received (DelegationRecordStore), cost_incurred (CostRecord integration), converters, timeline integration, controller wiring, and tests.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the 5 event types specified in #812: data models, stores, converters, controller integration, and comprehensive tests. No extraneous changes detected.
Docstring Coverage ✅ Passed Docstring coverage is 41.73% which is sufficient. The required threshold is 40.00%.

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


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 agent activity timeline by integrating five new event types: task_started, tool_used, delegation_sent, delegation_received, and cost_incurred. These additions provide a much richer and more granular view of agent operations, from task initiation and tool interactions to financial costs and inter-agent delegations. The changes involve new data models and dedicated storage mechanisms for tool invocations and delegation records, along with updates to the core activity merging logic and the API controller to efficiently process and present this expanded dataset. The implementation prioritizes robustness, including graceful degradation for any data sources that might be temporarily unavailable.

Highlights

  • Expanded Activity Timeline: Introduced five new activity event types: task_started, tool_used, delegation_sent, delegation_received, and cost_incurred, completing the 10-type specification.
  • New Data Infrastructure: Implemented ToolInvocationRecord and ToolInvocationTracker for tracking tool usage, and DelegationRecordStore for managing delegation events, including dual-perspective querying.
  • Enhanced API Controller: Updated the activity feed API to fetch and merge these new event types in parallel using asyncio.TaskGroup, ensuring efficient data retrieval and graceful degradation for unavailable sources.
  • Backward Compatibility: Maintained backward compatibility for the merge_activity_timeline function, allowing existing calls to continue functioning without modification.
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.

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.

@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 26, 2026 08:20 — with GitHub Actions Inactive
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 26, 2026

Dependency Review

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

Snapshot Warnings

⚠️: No snapshots were found for the head SHA ff68765.
Ensure that dependencies are being submitted on PR branches. Re-running this action after a short time may resolve the issue. See the documentation for more information and troubleshooting advice.

Scanned Files

None

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 pull request introduces comprehensive activity tracking for agents, including tool invocations and delegation records. Key changes involve adding ToolInvocationTracker and DelegationRecordStore services, integrating them into the AppState, and extending the /api/v1/activities endpoint to merge these new event types into a unified chronological timeline. The TaskMetricRecord model was updated to include a started_at field, and corresponding logic was added to generate 'task_started' events. Documentation has been updated to reflect these new capabilities. A critical issue was identified where Python 2 except syntax is used in multiple places, which will lead to SyntaxError in Python 3.

Comment on lines +162 to +163
except MemoryError, RecursionError:
raise
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.

critical

This except syntax is for Python 2. For Python 3, multiple exceptions must be grouped in a tuple. This will cause a SyntaxError at runtime.

This pattern is repeated in this file for _fetch_cost_records (L189), _fetch_tool_invocations (L218), and _fetch_delegation_records (L266). Please update all occurrences.

    except (MemoryError, RecursionError):
        raise

Comment on lines +193 to +194
except MemoryError, RecursionError:
raise
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.

critical

This except syntax is for Python 2. In Python 3, multiple exceptions must be enclosed in parentheses. This will raise a SyntaxError at runtime.

Suggested change
except MemoryError, RecursionError:
raise
except (MemoryError, RecursionError):
raise

Comment on lines +49 to +50
except MemoryError, RecursionError:
raise
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.

critical

This except syntax is from Python 2 and will cause a SyntaxError in Python 3. Please group the exceptions in a tuple.

Suggested change
except MemoryError, RecursionError:
raise
except (MemoryError, RecursionError):
raise

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 26, 2026

Codecov Report

❌ Patch coverage is 93.75000% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.31%. Comparing base (41daf69) to head (ff68765).
⚠️ Report is 2 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/synthorg/api/controllers/activities.py 58.33% 9 Missing and 1 partial ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##             main     #832    +/-   ##
========================================
  Coverage   92.30%   92.31%            
========================================
  Files         580      584     +4     
  Lines       30343    30491   +148     
  Branches     2924     2934    +10     
========================================
+ Hits        28009    28147   +138     
- Misses       1846     1854     +8     
- Partials      488      490     +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/synthorg/tools/invoker.py (1)

106-128: ⚠️ Potential issue | 🔴 Critical

Tool invocation tracking is not wired through AgentEngine to ToolInvoker.

The invocation_tracker parameter is correctly added to ToolInvoker, but AgentEngine._make_tool_invoker() does not pass it when constructing ToolInvoker instances. Since AgentEngine itself does not receive tool_invocation_tracker from AppState, the tracker remains None at runtime, causing record_tool_invocation to silently skip recording tool usage events.

For production tracking to work:

  1. AgentEngine.__init__ must accept tool_invocation_tracker parameter
  2. AgentEngine._make_tool_invoker() must pass self._tool_invocation_tracker to the ToolInvoker constructor
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/tools/invoker.py` around lines 106 - 128, AgentEngine currently
doesn't propagate the ToolInvocationTracker into ToolInvoker, so tool usage
isn't recorded; update AgentEngine.__init__ to accept a tool_invocation_tracker
parameter and store it as self._tool_invocation_tracker, then modify
AgentEngine._make_tool_invoker to pass that tracker into the ToolInvoker
constructor (invocation_tracker=self._tool_invocation_tracker) so ToolInvoker
receives the tracker and record_tool_invocation will work.
src/synthorg/hr/activity.py (1)

8-20: 🛠️ Refactor suggestion | 🟠 Major

Add the standard module logger scaffold.

This src/synthorg/hr business-logic module still doesn't import get_logger / define logger = get_logger(__name__), so the new timeline-conversion logic is out of step with the repo's observability baseline.

As per coding guidelines, "Every module with business logic MUST have: from synthorg.observability import get_logger then logger = get_logger(__name__)"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/hr/activity.py` around lines 8 - 20, This module is missing the
standard module logger; import get_logger from synthorg.observability and define
logger = get_logger(__name__) near the top of src/synthorg/hr/activity.py
(alongside the other imports) so the module follows the repo observability
baseline; ensure the import uses the exact symbol get_logger and the logger
variable name logger so existing logging calls (or future ones) use the standard
logger.
tests/unit/hr/test_activity.py (1)

212-228: 🧹 Nitpick | 🔵 Trivial

Stable sort assertion depends on insertion order, not explicit guarantees.

The comment claims "Stable sort: lifecycle events appear before task metrics at same timestamp," but this relies on the current insertion order in merge_activity_timeline (lifecycle first, then task metrics). If the merge function's internal order changes, this test will fail unexpectedly.

Consider either:

  1. Documenting this as an implementation detail that tests rely on, or
  2. Asserting only that both events exist without enforcing relative order for same-timestamp events.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/hr/test_activity.py` around lines 212 - 228, The test
test_identical_timestamps_stable_sort relies on merge_activity_timeline
insertion order to assert lifecycle events precede task metrics at the same
timestamp; change the test to avoid relying on that implementation detail by
asserting both events are present regardless of order (e.g., collect timeline
event_type values from timeline produced by merge_activity_timeline and assert
that "hired" and "task_completed" are both present) instead of checking
timeline[0] and timeline[1]; reference the existing helpers
_make_lifecycle_event, _make_task_metric and LifecycleEventType.HIRED when
updating the assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/synthorg/api/controllers/activities.py`:
- Around line 149-173: _fetch_task_metrics is synchronous and blocks the event
loop when called outside the TaskGroup; change the call site to run it inside
the TaskGroup using asyncio.to_thread(...) or make _fetch_task_metrics async and
await the I/O in a background thread. Specifically, keep the function logic but
when invoking performance_tracker.get_task_metrics (or any call to
_fetch_task_metrics) start it with
asyncio.to_thread(app_state.performance_tracker.get_task_metrics, agent_id=...,
since=..., until=...) inside the TaskGroup so task metrics fetch runs
concurrently with cost/tool/delegation fetches; ensure exception handling
mirrors the existing try/except behavior and that the returned shape remains
tuple[TaskMetricRecord, ...].

In `@src/synthorg/communication/delegation/record_store.py`:
- Around line 35-51: record_sync currently mutates self._records without
acquiring self._lock while _snapshot reads under self._lock; to fix, make
record_sync async and acquire the asyncio lock (use "async def record_sync(...)"
and "async with self._lock:" around the append and logger call) so both writers
and readers use the same protection, and update callers (e.g.
DelegationService._record_delegation) to await the new async record_sync; ensure
the method signature and all call sites are updated accordingly to prevent
blocking or race conditions.

In `@src/synthorg/communication/delegation/service.py`:
- Around line 196-202: The log on the failure path is using the success constant
DELEGATION_CREATED; change that logger.warning call to emit a distinct failure
event (e.g. DELEGATION_CREATION_FAILED or DELEGATION_CREATE_FAILED) so success
and failure metrics aren’t conflated—update the constant reference in the
logger.warning call (and add the new failure constant to the events/constants
module if it doesn’t exist) in the same failure block where logger.warning(...,
delegator=request.delegator_id, delegatee=request.delegatee_id, note="Failed to
record delegation in activity store", exc_info=True) is called.
- Around line 190-193: The current write via
self._record_store.record_sync(record) uses the in-memory RecordStore and loses
data on restart; change to a durable persistence: update the
RecordStore.record_sync implementation (the class and method named record_sync
in RecordStore) to persist delegation records to a shared durable store (e.g.,
DB table, key/value store, or append-only log) instead of appending an in-memory
list, and update the call site in service.py (the code invoking
self._record_store.record_sync) to handle/await any async DB call or handle
returned errors; ensure record structure (IDs/timestamps/org_id) is persisted
and that the persistence layer provides queryable APIs for
delegation_sent/delegation_received so other workers can read them.

In `@src/synthorg/hr/activity.py`:
- Around line 154-168: The current logic places raw record.error_message into
the ActivityEvent.description which can leak sensitive data and exceed the
1024-char cap; update the construction of desc (used when record.is_success is
False) to avoid exposing raw errors by replacing record.error_message with a
sanitized summary: e.g., if record.error_message exists, extract only a short,
safe token such as the first line stripped of paths/stack traces and limit it to
a safe length (e.g. 200 chars) or else use a generic message like "error details
omitted"; ensure this change is applied where desc is built (referencing
record.error_message, record.tool_name, and ActivityEvent description) so
ActivityEvent.description never contains the full raw error and respects size
limits.

In `@src/synthorg/tools/invocation_bridge.py`:
- Around line 52-58: The event constant TOOL_INVOKE_EXECUTION_ERROR is
misleading here; define a new constant TOOL_INVOCATION_RECORD_FAILED in
synthorg.observability.events.tool and import it into invocation_bridge.py, then
replace TOOL_INVOKE_EXECUTION_ERROR in the logger.warning call (and keep the
same tool_call_id, tool_name, note, exc_info fields) with
TOOL_INVOCATION_RECORD_FAILED; ensure the new event constant follows the
existing event naming/format conventions and update any tests or references
expecting the old semantic if needed.
- Around line 35-38: The current flow in invocation_bridge.py returns early on
many error paths and never calls record_tool_invocation, so failed attempts
aren't recorded; update the code that uses invoker._invocation_tracker and
invoker._agent_id to ensure record_tool_invocation is invoked for every exit by
either (a) moving the record_tool_invocation call into a single finally block
that runs after the tool lookup/permission/validation/security/execution/parking
logic, or (b) adding explicit calls to record_tool_invocation (using the same
signature as the successful path) in each early-return branch that returns a
ToolResult; reference the identifiers record_tool_invocation,
invoker._invocation_tracker, invoker._agent_id, and ToolResult when making the
change.

In `@src/synthorg/tools/invocation_tracker.py`:
- Around line 24-33: ToolInvocationTracker currently stores _records in-process
so tool_used events are lost on restart and invisible across workers; replace
the in-memory list with a persistent backend by injecting a storage client
(e.g., a database or shared cache) into ToolInvocationTracker, remove or make
_records an optional local cache, and update its write/read methods (the
append/write code that records invocations and any query methods that read the
timeline) to persist and query from the injected storage asynchronously while
keeping the asyncio.Lock only for local concurrency; ensure the new constructor
accepts the storage client, use unique keys/indexes for org-wide queries, and
add migrations/initialization where needed so the timeline is durable and
consistent across multiple workers.

In `@tests/unit/api/controllers/test_activities.py`:
- Around line 335-401: Add regression tests that verify /api/v1/activities still
returns 200 when the optional sources are absent or failing: create a test
(e.g., test_feed_graceful_degradation_tool_missing) that does not provide the
tool_invocation_tracker fixture (or monkeypatches ToolInvocationTracker.record
to raise) then calls TestClient.get("/api/v1/activities") and asserts
status_code == 200 and that other activity sources (e.g., lifecycle events) are
still present; similarly add a test (e.g.,
test_feed_graceful_degradation_delegation_missing) that omits or makes
DelegationRecordStore.record_sync raise and asserts the same; use the existing
patterns in test_feed_with_tool_invocations and test_feed_with_delegation_events
to locate where to add these cases and to assert body["pagination"]["total"] and
presence/absence of event types.

---

Outside diff comments:
In `@src/synthorg/hr/activity.py`:
- Around line 8-20: This module is missing the standard module logger; import
get_logger from synthorg.observability and define logger = get_logger(__name__)
near the top of src/synthorg/hr/activity.py (alongside the other imports) so the
module follows the repo observability baseline; ensure the import uses the exact
symbol get_logger and the logger variable name logger so existing logging calls
(or future ones) use the standard logger.

In `@src/synthorg/tools/invoker.py`:
- Around line 106-128: AgentEngine currently doesn't propagate the
ToolInvocationTracker into ToolInvoker, so tool usage isn't recorded; update
AgentEngine.__init__ to accept a tool_invocation_tracker parameter and store it
as self._tool_invocation_tracker, then modify AgentEngine._make_tool_invoker to
pass that tracker into the ToolInvoker constructor
(invocation_tracker=self._tool_invocation_tracker) so ToolInvoker receives the
tracker and record_tool_invocation will work.

In `@tests/unit/hr/test_activity.py`:
- Around line 212-228: The test test_identical_timestamps_stable_sort relies on
merge_activity_timeline insertion order to assert lifecycle events precede task
metrics at the same timestamp; change the test to avoid relying on that
implementation detail by asserting both events are present regardless of order
(e.g., collect timeline event_type values from timeline produced by
merge_activity_timeline and assert that "hired" and "task_completed" are both
present) instead of checking timeline[0] and timeline[1]; reference the existing
helpers _make_lifecycle_event, _make_task_metric and LifecycleEventType.HIRED
when updating the assertions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8990a2c5-de6c-441a-a770-a5e78b43a65c

📥 Commits

Reviewing files that changed from the base of the PR and between d4dde90 and fc4f25e.

📒 Files selected for processing (21)
  • CLAUDE.md
  • docs/design/agents.md
  • docs/design/operations.md
  • src/synthorg/api/app.py
  • src/synthorg/api/controllers/activities.py
  • src/synthorg/api/state.py
  • src/synthorg/communication/delegation/record_store.py
  • src/synthorg/communication/delegation/service.py
  • src/synthorg/hr/activity.py
  • src/synthorg/hr/performance/models.py
  • src/synthorg/observability/events/delegation.py
  • src/synthorg/observability/events/tool.py
  • src/synthorg/tools/invocation_bridge.py
  • src/synthorg/tools/invocation_record.py
  • src/synthorg/tools/invocation_tracker.py
  • src/synthorg/tools/invoker.py
  • tests/unit/api/conftest.py
  • tests/unit/api/controllers/test_activities.py
  • tests/unit/communication/delegation/test_record_store.py
  • tests/unit/hr/test_activity.py
  • tests/unit/tools/test_invocation_tracker.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Backend
  • GitHub Check: Analyze (python)
  • GitHub Check: Dependency Review
🧰 Additional context used
📓 Path-based instructions (5)
docs/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Documentation is built with Zensical (config: mkdocs.yml). Docs structure: docs/design/ (10 pages), docs/architecture/, docs/roadmap/, docs/security.md, docs/licensing.md, docs/reference/, docs/rest-api.md + docs/_generated/api-reference.html (generated by scripts/export_openapi.py), docs/api/ (auto-generated via mkdocstrings + Griffe AST-based). Landing page: site/ (Astro with /get/ CLI install page, contact form, SEO)

Files:

  • docs/design/agents.md
  • docs/design/operations.md
docs/design/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Design spec pages are the mandatory starting point for architecture, data models, and behavior. Always read the relevant docs/design/ page before implementing features. If implementation deviates from spec, alert the user with explanation. Update docs/design/ pages when deviations are approved

Files:

  • docs/design/agents.md
  • docs/design/operations.md
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations -- Python 3.14 has PEP 649 native lazy annotations
Use except A, B: syntax without parentheses for exception handling (PEP 758) -- ruff enforces this on Python 3.14
Add type hints to all public functions and classes; mypy strict mode required
Use Google-style docstrings on all public classes and functions (enforced by ruff D rules)
Immutability enforcement: create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping. For dict/list fields in frozen Pydantic models, rely on frozen=True and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization)
Use Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict); use @computed_field for derived values instead of storing redundant fields; use NotBlankStr for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (multiple tool invocations, parallel agent calls). Use structured concurrency over bare create_task
Keep functions under 50 lines, files under 800 lines
Line length: 88 characters (ruff enforced)
Explicitly handle all errors; never silently swallow exceptions
Validate at system boundaries: user input, external APIs, config files

Files:

  • src/synthorg/observability/events/delegation.py
  • src/synthorg/observability/events/tool.py
  • tests/unit/tools/test_invocation_tracker.py
  • src/synthorg/tools/invoker.py
  • src/synthorg/api/app.py
  • src/synthorg/tools/invocation_bridge.py
  • src/synthorg/hr/activity.py
  • tests/unit/api/controllers/test_activities.py
  • src/synthorg/api/state.py
  • src/synthorg/communication/delegation/service.py
  • src/synthorg/api/controllers/activities.py
  • tests/unit/communication/delegation/test_record_store.py
  • src/synthorg/tools/invocation_tracker.py
  • src/synthorg/tools/invocation_record.py
  • tests/unit/api/conftest.py
  • tests/unit/hr/test_activity.py
  • src/synthorg/hr/performance/models.py
  • src/synthorg/communication/delegation/record_store.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/**/*.py: Every module with business logic MUST have: from synthorg.observability import get_logger then logger = get_logger(__name__)
Use logger.info(EVENT, key=value) with structured kwargs for logging; never use logger.info('msg %s', val) formatting
Never use import logging / logging.getLogger() / print() in application code. Exception: observability/setup.py and observability/sinks.py may use stdlib logging and print() for bootstrap code only
Use event name constants from domain-specific modules under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT
All provider calls go through BaseCompletionProvider which applies retry + rate limiting automatically. Never implement retry logic in driver subclasses or calling code. RetryConfig and RateLimiterConfig are set per-provider in ProviderConfig. Retryable errors (is_retryable=True): RateLimitError, ProviderTimeoutError, ProviderConnectionError, ProviderInternalError. Non-retryable errors raise immediately. RetryExhaustedError signals all retries failed -- engine layer catches this to trigger fallback chains. Rate limiter respects RateLimitError.retry_after
Always read the relevant docs/design/ page before implementing any feature or planning any issue. DESIGN_SPEC.md is a pointer file. When a spec topic is referenced (e.g., 'the Agents page'), read the relevant docs/design/ page before coding
If implementation deviates from the design spec (better approach, scope evolved), alert the user and explain why. User decides whether to proceed or update spec. Do NOT silently diverge. When approved deviations occur, update relevant docs/design/ page to reflect new reality

Files:

  • src/synthorg/observability/events/delegation.py
  • src/synthorg/observability/events/tool.py
  • src/synthorg/tools/invoker.py
  • src/synthorg/api/app.py
  • src/synthorg/tools/invocation_bridge.py
  • src/synthorg/hr/activity.py
  • src/synthorg/api/state.py
  • src/synthorg/communication/delegation/service.py
  • src/synthorg/api/controllers/activities.py
  • src/synthorg/tools/invocation_tracker.py
  • src/synthorg/tools/invocation_record.py
  • src/synthorg/hr/performance/models.py
  • src/synthorg/communication/delegation/record_store.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Mark all tests with @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, or @pytest.mark.slow
Always run pytest with -n auto for parallelism via pytest-xdist; never run tests sequentially. Minimum coverage: 80% (enforced in CI). Timeout: 30 seconds per test globally (do not add per-file markers; non-default overrides like timeout(60) are allowed)
Vendor-agnostic naming everywhere: NEVER use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples. Use generic names: example-provider, example-large-001, example-medium-001, example-small-001, large/medium/small aliases. Vendor names allowed only in: (1) docs/design/operations.md provider list, (2) .claude/ skill/agent files, (3) third-party import paths. Tests must use test-provider, test-small-001, etc.
Use Hypothesis for property-based testing (@given + @settings with HYPOTHESIS_PROFILE env var: 'ci' for 50 examples (default), 'dev' for 1000 examples). Run dev profile: HYPOTHESIS_PROFILE=dev uv run python -m pytest tests/ -m unit -n auto -k properties. .hypothesis/ is gitignored
Never skip, dismiss, or ignore flaky tests -- always fix them fully. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() for determinism. For tasks blocking indefinitely until cancelled, use asyncio.Event().wait() instead of asyncio.sleep(large_number) -- it is cancellation-safe

Files:

  • tests/unit/tools/test_invocation_tracker.py
  • tests/unit/api/controllers/test_activities.py
  • tests/unit/communication/delegation/test_record_store.py
  • tests/unit/api/conftest.py
  • tests/unit/hr/test_activity.py
🧠 Learnings (29)
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Engine: Agent orchestration, execution loops, parallel execution, task decomposition, routing, task assignment, centralized single-writer task state engine (TaskEngine), task lifecycle, recovery, shutdown, workspace isolation, coordination (multi-agent pipeline: TopologyDispatcher protocol, 4 dispatchers — SAS/centralized/decentralized/context-dependent, wave execution, workspace lifecycle integration, CoordinationSectionConfig company config bridge, build_coordinator factory), coordination error classification, prompt policy validation, checkpoint recovery (checkpoint/, per-turn persistence, heartbeat detection, CheckpointRecoveryStrategy), approval gate (escalation detection, context parking/resume, EscalationInfo/ResumePayload models), stagnation detection (stagnation/, StagnationDetector protocol, ToolRepetitionDetector, dual-signal analysis, corrective prompt injection), agent runtime state (AgentRuntimeState, lightweight per-agent execution status for dashboard queries and recove...

Applied to files:

  • docs/design/agents.md
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/hr/**/*.py : HR package (hr/): hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, LLM calibration, collaboration overrides, trend detection), promotion/demotion (criteria evaluation, approval strategies, model mapping)

Applied to files:

  • docs/design/agents.md
  • src/synthorg/api/controllers/activities.py
  • tests/unit/api/conftest.py
  • tests/unit/hr/test_activity.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from domain-specific modules under synthorg.observability.events (e.g., PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/observability/events/delegation.py
  • src/synthorg/observability/events/tool.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from synthorg.observability.events domain-specific modules (e.g., PROVIDER_CALL_START from events.provider). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.

Applied to files:

  • src/synthorg/observability/events/delegation.py
  • src/synthorg/observability/events/tool.py
📚 Learning: 2026-03-24T22:47:39.223Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-24T22:47:39.223Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from domain-specific modules under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`

Applied to files:

  • src/synthorg/observability/events/tool.py
📚 Learning: 2026-03-18T21:23:23.586Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T21:23:23.586Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from the domain-specific module under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly from synthorg.observability.events.<domain>.

Applied to files:

  • src/synthorg/observability/events/tool.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability package (observability/): structured logging, correlation tracking, log sinks; event constants organized by domain under observability/events/ (e.g., events.api, events.tool, events.git, events.context_budget, events.backup)

Applied to files:

  • src/synthorg/observability/events/tool.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`); import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`

Applied to files:

  • src/synthorg/observability/events/tool.py
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Use event constants from `synthorg.observability.events.<domain>` (e.g., `API_REQUEST_STARTED` from `events.api`); import directly and log with structured kwargs: `logger.info(EVENT, key=value)`, never interpolated strings

Applied to files:

  • src/synthorg/observability/events/tool.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from the domain-specific module under `synthorg.observability.events` in logging calls

Applied to files:

  • src/synthorg/observability/events/tool.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : Use event name constants from domain-specific modules under `ai_company.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`). Import directly: `from ai_company.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/observability/events/tool.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/security/**/*.py : Security package (security/): SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered), risk classifier, risk tier classifier, action type registry, ToolInvoker security integration, progressive trust (4 strategies), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume)

Applied to files:

  • src/synthorg/tools/invoker.py
  • src/synthorg/api/state.py
  • CLAUDE.md
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/security/**/*.py : Security module includes SecOps agent, rule engine (soft-allow/hard-deny), audit log, output scanner, risk classifier, autonomy levels (4 strategies), timeout policies.

Applied to files:

  • src/synthorg/tools/invoker.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/communication/**/*.py : Communication package (communication/): message bus, dispatcher, messenger, channels, delegation, loop prevention, conflict resolution; meeting/ subpackage for meeting protocol (round-robin, position papers, structured phases), scheduler (frequency, participant resolver), orchestrator

Applied to files:

  • src/synthorg/api/app.py
  • src/synthorg/api/state.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...

Applied to files:

  • src/synthorg/api/app.py
  • tests/unit/api/controllers/test_activities.py
  • src/synthorg/api/state.py
  • src/synthorg/api/controllers/activities.py
  • tests/unit/api/conftest.py
  • CLAUDE.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/backup/**/*.py : Backup package (backup/): scheduled/manual/lifecycle backups of persistence DB, agent memory, company config. BackupService orchestrator, BackupScheduler (periodic asyncio task), RetentionManager (count + age pruning), tar.gz compression, SHA-256 checksums, manifest tracking, validated restore with atomic rollback and safety backup. handlers/ subpackage: ComponentHandler protocol + concrete handlers (PersistenceComponentHandler, MemoryComponentHandler, ConfigComponentHandler)

Applied to files:

  • src/synthorg/api/app.py
  • src/synthorg/api/state.py
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to src/synthorg/providers/**/*.py : Providers: LLM provider abstraction (LiteLLM adapter), auth types (api_key/oauth/custom_header/none), presets (PROVIDER_PRESETS), runtime CRUD (ProviderManagementService with asyncio.Lock serialization), hot-reload via AppState swap.

Applied to files:

  • src/synthorg/api/app.py
  • tests/unit/api/conftest.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/settings/**/*.py : Settings package (settings/): runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces), Fernet encryption for sensitive values, config bridge (JSON serialization for Pydantic/collections), ConfigResolver (typed accessors), validation, registry, change notifications via message bus, SettingsSubscriber protocol, SettingsChangeDispatcher (polls `#settings` channel, routes to subscribers, restart_required filtering)

Applied to files:

  • src/synthorg/api/app.py
  • CLAUDE.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Prefer `asyncio.TaskGroup` for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare `create_task`. Existing code is being migrated incrementally.

Applied to files:

  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)

Applied to files:

  • src/synthorg/api/controllers/activities.py
  • tests/unit/api/conftest.py
  • tests/unit/hr/test_activity.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.

Applied to files:

  • src/synthorg/api/controllers/activities.py
  • tests/unit/hr/test_activity.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/hr/**/*.py : HR engine must provide: hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, trend detection), promotion/demotion

Applied to files:

  • tests/unit/hr/test_activity.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Settings: Runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces), Fernet encryption for sensitive values, config bridge, ConfigResolver (typed composed reads for controllers), validation, registry, change notifications via message bus. Per-namespace setting definitions in definitions/ submodule (api, company, providers, memory, budget, security, coordination, observability, backup).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Documentation source in `docs/` (Markdown, built with Zensical). Design spec in `docs/design/` (7 pages: index, agents, organization, communication, engine, memory, operations). Architecture in `docs/architecture/` (overview, tech-stack, decision log). Roadmap in `docs/roadmap/`. Security in `docs/security.md`. Licensing in `docs/licensing.md`. Reference in `docs/reference/`. REST API reference in `docs/rest-api.md`. Library reference in `docs/api/` (auto-generated from docstrings). Custom templates in `docs/overrides/`. Config in `mkdocs.yml`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Security: SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered), risk classifier, risk tier classifier, action type registry, ToolInvoker security integration, progressive trust (4 strategies: disabled/weighted/per-category/milestone), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to docs/** : Docs source in docs/ (Markdown, built with Zensical); design spec in docs/design/ (7 pages: index, agents, organization, communication, engine, memory, operations)

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-24T22:47:39.223Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-24T22:47:39.223Z
Learning: Applies to web/src/**/*.{ts,tsx} : React 19 with shadcn/ui + Tailwind CSS 4. Use React hooks (auth, login lockout, WebSocket, polling, optimistic updates), Zustand stores, and lazy-loaded page components. Maintain component organization: ui/ (shadcn primitives), layout/ (app shell, sidebar, status bar), feature dirs for pages

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-24T22:47:39.223Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-24T22:47:39.223Z
Learning: Applies to docs/**/*.md : Documentation is built with Zensical (config: mkdocs.yml). Docs structure: docs/design/ (10 pages), docs/architecture/, docs/roadmap/, docs/security.md, docs/licensing.md, docs/reference/, docs/rest-api.md + docs/_generated/api-reference.html (generated by scripts/export_openapi.py), docs/api/ (auto-generated via mkdocstrings + Griffe AST-based). Landing page: site/ (Astro with /get/ CLI install page, contact form, SEO)

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T21:20:09.993Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:20:09.993Z
Learning: Applies to web/src/components/** : Vue components organized by feature (agents/, approvals/, budget/, common/, dashboard/, layout/, messages/, org-chart/, tasks/).

Applied to files:

  • CLAUDE.md
🔇 Additional comments (17)
CLAUDE.md (1)

122-122: LGTM!

The documentation update accurately reflects the new invocation tracking functionality added to the tools subsystem.

src/synthorg/hr/performance/models.py (1)

53-83: LGTM!

The new started_at field and temporal ordering validator are well-implemented:

  • Correctly enforces strict ordering (started_at < completed_at)
  • Error message includes ISO-formatted timestamps for debugging
  • Pattern is consistent with the existing _validate_expiration_ordering validator in CollaborationOverride
docs/design/agents.md (1)

308-308: LGTM!

The documentation accurately reflects the expanded activity timeline with the five new event types (task_started, tool_used, cost_incurred, delegation_sent, delegation_received) merged alongside existing lifecycle and task completion events.

src/synthorg/observability/events/tool.py (1)

58-61: LGTM!

The new event constants follow the established naming convention (<domain>.<noun>.<verb>) and are consistent with the existing event taxonomy in this file.

src/synthorg/tools/invoker.py (1)

408-410: Recording invocation after building result is correct.

The placement of record_tool_invocation after _build_result and before return is appropriate — it ensures the full ToolResult (including any error state) is captured, and the await ensures recording completes before returning.

src/synthorg/observability/events/delegation.py (1)

26-29: LGTM!

The new delegation record store event constants follow the established naming convention and are consistent with the existing event taxonomy in this file.

docs/design/operations.md (1)

1066-1068: LGTM!

The documentation accurately reflects:

  1. The expanded event types in the per-agent activity endpoint (lifecycle, task, cost, tool, delegation)
  2. The new org-wide activity feed endpoint with filtering capabilities

Both entries are consistent with the implementation and follow the existing table format.

src/synthorg/api/app.py (1)

429-430: Confirm: Activity services need auto-wiring strategy or clear documentation.

Verification shows these services are never instantiated in the codebase—there are no factory functions, no auto-wiring logic in auto_wire_phase1, and no instances created anywhere. When run_server calls create_app(config=config), both tool_invocation_tracker and delegation_record_store remain None in production.

Unlike other optional services (persistence, message_bus, cost_tracker) which are auto-wired in auto_wire_phase1, these new services have no conditional initialization. The AppState properties will raise 503 if accessed when None.

Either:

  1. Add auto-wiring logic (similar to cost_tracker pattern) if these should work in production, or
  2. Document this as intentional MVP (features opt-in for testing only).

Without clarification, the current state risks production failures when activity events are triggered.

src/synthorg/api/controllers/activities.py (2)

107-119: Good use of asyncio.TaskGroup for parallel fetches.

The structured concurrency pattern correctly parallelizes independent data source fetches. This aligns with best practices for fan-out operations.


258-265: The org-wide activity timeline intentionally shows both delegation_sent and delegation_received perspectives for each delegation record, as validated by the test suite. This behavior is correct as designed—agent-specific views (filtered by agent_id) return only the relevant perspective, while org-wide views surface the complete delegation context from both sides. The code comment at line 261 already clarifies this intent.

tests/unit/hr/test_activity.py (2)

527-539: Test documents intentional dual-perspective behavior.

This test explicitly validates that passing the same DelegationRecord to both delegation_records_sent and delegation_records_received produces two distinct events. This confirms the design intent, but as noted in the controller review, this causes double-counting in org-wide queries.


1-680: Comprehensive test coverage for new event types.

The tests thoroughly cover all five new activity event types (task_started, cost_incurred, tool_used, delegation_sent, delegation_received) with edge cases including:

  • Conditional task_started generation based on started_at
  • Tool failures with/without error messages
  • Optional task_id in tool invocations
  • Dual-perspective delegation events
  • Merge ordering verification
src/synthorg/tools/invocation_bridge.py (1)

44-46: Truncation to 2048 chars matches model constraint.

Good alignment between the bridge's truncation (result.content[:2048]) and the ToolInvocationRecord.error_message field's max_length=2048 constraint.

src/synthorg/tools/invocation_record.py (2)

53-59: Validator allows is_success=False with error_message=None.

The validator enforces that success cannot have an error message, but does not require failures to provide one. This asymmetry is reasonable since some tool failures may lack descriptive messages. The test test_tool_used_failure_no_error_message in tests/unit/hr/test_activity.py confirms this is intentional.


15-51: Well-structured immutable record model.

The model correctly uses:

  • frozen=True for immutability (append-only pattern)
  • NotBlankStr for all identifier fields
  • AwareDatetime ensuring timezone-aware timestamps
  • max_length=2048 on error_message matching the bridge truncation
  • default_factory for auto-generated UUIDs
src/synthorg/communication/delegation/record_store.py (2)

146-175: Clean pure-function helpers for validation and filtering.

_validate_time_range and _filter are well-isolated, pure functions with clear semantics:

  • Time range validation: start < end when both provided
  • Filter semantics: start <= timestamp < end (half-open interval)

53-135: Query methods correctly filter by role.

get_records_as_delegator filters by delegator_id and get_records_as_delegatee filters by delegatee_id, ensuring records appear in exactly one perspective per agent. get_all_records correctly returns unfiltered results for org-wide queries.

Comment on lines +149 to +173
def _fetch_task_metrics(
app_state: AppState,
agent_id: str | None,
since: datetime,
now: datetime,
) -> tuple[TaskMetricRecord, ...]:
"""Fetch task metrics, falling back to empty on failure."""
try:
return app_state.performance_tracker.get_task_metrics(
agent_id=agent_id,
since=since,
until=now,
)
except MemoryError, RecursionError:
raise
except ServiceUnavailableError:
raise
except Exception:
logger.warning(
API_REQUEST_ERROR,
endpoint="activities",
error="performance_tracker_unavailable",
exc_info=True,
)
return ()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Synchronous function _fetch_task_metrics breaks parallel execution.

_fetch_task_metrics is a synchronous function (no async def) and is called directly on line 106, outside the TaskGroup. This means task metrics are fetched before the parallel cost/tool/delegation fetches begin, serializing what could be concurrent.

If performance_tracker.get_task_metrics is synchronous by design, consider wrapping it in asyncio.to_thread() inside the TaskGroup to avoid blocking the event loop, or document why serial execution is acceptable here.

♻️ Proposed fix to parallelize task metrics fetch
-        task_metrics = _fetch_task_metrics(app_state, agent_id, since, now)
         async with asyncio.TaskGroup() as tg:
+            metrics_task = tg.create_task(
+                asyncio.to_thread(_fetch_task_metrics, app_state, agent_id, since, now),
+            )
             cost_task = tg.create_task(
                 _fetch_cost_records(app_state, agent_id, since, now),
             )
             ...
+        task_metrics = metrics_task.result()
         cost_records = cost_task.result()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/api/controllers/activities.py` around lines 149 - 173,
_fetch_task_metrics is synchronous and blocks the event loop when called outside
the TaskGroup; change the call site to run it inside the TaskGroup using
asyncio.to_thread(...) or make _fetch_task_metrics async and await the I/O in a
background thread. Specifically, keep the function logic but when invoking
performance_tracker.get_task_metrics (or any call to _fetch_task_metrics) start
it with asyncio.to_thread(app_state.performance_tracker.get_task_metrics,
agent_id=..., since=..., until=...) inside the TaskGroup so task metrics fetch
runs concurrently with cost/tool/delegation fetches; ensure exception handling
mirrors the existing try/except behavior and that the returned shape remains
tuple[TaskMetricRecord, ...].

Comment on lines +35 to +51
def record_sync(self, delegation: DelegationRecord) -> None:
"""Append a delegation record (sync, for cooperative scheduling).

Safe to call from sync code under asyncio cooperative scheduling
since a plain list append cannot be interrupted by another
coroutine.

Args:
delegation: Immutable delegation record to store.
"""
self._records.append(delegation)
logger.debug(
DELEGATION_RECORD_STORED,
delegation_id=delegation.delegation_id,
delegator_id=delegation.delegator_id,
delegatee_id=delegation.delegatee_id,
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Inconsistent locking: record_sync writes without lock while _snapshot reads with lock.

record_sync appends to _records without acquiring _lock, while _snapshot acquires the lock before reading. This is inconsistent:

  1. If list.append() is atomic under CPython's GIL (as the docstring claims), then _snapshot's lock is unnecessary overhead.
  2. If the lock is needed for correctness, then record_sync should also acquire it.

The docstring's rationale about cooperative scheduling is valid for single-threaded asyncio, but the lock's presence suggests the design anticipated some form of concurrent access protection.

Consider either:

  • Adding lock acquisition to record_sync (via asyncio.Lock requires making it async), or
  • Removing the lock from _snapshot and documenting the GIL/cooperative-scheduling assumption explicitly.
♻️ Option A: Make record_sync async with lock
-    def record_sync(self, delegation: DelegationRecord) -> None:
-        """Append a delegation record (sync, for cooperative scheduling).
-
-        Safe to call from sync code under asyncio cooperative scheduling
-        since a plain list append cannot be interrupted by another
-        coroutine.
-
-        Args:
-            delegation: Immutable delegation record to store.
-        """
-        self._records.append(delegation)
+    async def record(self, delegation: DelegationRecord) -> None:
+        """Append a delegation record.
+
+        Args:
+            delegation: Immutable delegation record to store.
+        """
+        async with self._lock:
+            self._records.append(delegation)

Note: This would require updating DelegationService._record_delegation to call await self._record_store.record(...) from an async context.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def record_sync(self, delegation: DelegationRecord) -> None:
"""Append a delegation record (sync, for cooperative scheduling).
Safe to call from sync code under asyncio cooperative scheduling
since a plain list append cannot be interrupted by another
coroutine.
Args:
delegation: Immutable delegation record to store.
"""
self._records.append(delegation)
logger.debug(
DELEGATION_RECORD_STORED,
delegation_id=delegation.delegation_id,
delegator_id=delegation.delegator_id,
delegatee_id=delegation.delegatee_id,
)
async def record(self, delegation: DelegationRecord) -> None:
"""Append a delegation record.
Args:
delegation: Immutable delegation record to store.
"""
async with self._lock:
self._records.append(delegation)
logger.debug(
DELEGATION_RECORD_STORED,
delegation_id=delegation.delegation_id,
delegator_id=delegation.delegator_id,
delegatee_id=delegation.delegatee_id,
)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/communication/delegation/record_store.py` around lines 35 - 51,
record_sync currently mutates self._records without acquiring self._lock while
_snapshot reads under self._lock; to fix, make record_sync async and acquire the
asyncio lock (use "async def record_sync(...)" and "async with self._lock:"
around the append and logger call) so both writers and readers use the same
protection, and update callers (e.g. DelegationService._record_delegation) to
await the new async record_sync; ensure the method signature and all call sites
are updated accordingly to prevent blocking or race conditions.

Comment on lines +190 to +193
if self._record_store is not None:
try:
self._record_store.record_sync(record)
except MemoryError, RecursionError:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Delegation activity is still stored in process-local memory.

record_sync() currently just appends to an in-memory list in src/synthorg/communication/delegation/record_store.py:31-52, so this write is lost on restart and invisible to other workers. That makes delegation_sent / delegation_received incomplete for an org-wide feed and misses the linked issue's persisted/queryable requirement.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/communication/delegation/service.py` around lines 190 - 193, The
current write via self._record_store.record_sync(record) uses the in-memory
RecordStore and loses data on restart; change to a durable persistence: update
the RecordStore.record_sync implementation (the class and method named
record_sync in RecordStore) to persist delegation records to a shared durable
store (e.g., DB table, key/value store, or append-only log) instead of appending
an in-memory list, and update the call site in service.py (the code invoking
self._record_store.record_sync) to handle/await any async DB call or handle
returned errors; ensure record structure (IDs/timestamps/org_id) is persisted
and that the persistence layer provides queryable APIs for
delegation_sent/delegation_received so other workers can read them.

Comment on lines +35 to +38
tracker = invoker._invocation_tracker # noqa: SLF001
agent_id = invoker._agent_id # noqa: SLF001
if tracker is None or agent_id is None:
return
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Count early return paths in _invoke_single that bypass record_tool_invocation
rg -n "return tool_or_error|return permission_error|return param_error|return security_error|return parking_error" src/synthorg/tools/invoker.py

Repository: Aureliolo/synthorg

Length of output: 248


🏁 Script executed:

#!/bin/bash
# Examine the structure of _invoke_single in invoker.py to see control flow
sed -n '354,411p' src/synthorg/tools/invoker.py | cat -n

Repository: Aureliolo/synthorg

Length of output: 2490


🏁 Script executed:

#!/bin/bash
# Find the line number where record_tool_invocation is called in invoker.py
rg -n "record_tool_invocation" src/synthorg/tools/invoker.py

Repository: Aureliolo/synthorg

Length of output: 184


🏁 Script executed:

#!/bin/bash
# Check invocation_bridge.py to understand what record_tool_invocation does
cat -n src/synthorg/tools/invocation_bridge.py | head -60

Repository: Aureliolo/synthorg

Length of output: 2385


Early returns bypass the invocation recording.

Confirmed: record_tool_invocation is called only at line 56, after successful execution. Early-exit paths at tool lookup failure (line 15), permission check (line 19), parameter validation (line 23), security check (line 31), tool execution error (line 35), and parking metadata tracking (line 46) all return ToolResult objects without recording them.

These unrecorded failures prevent visibility into failed invocation attempts in the activity timeline. If comprehensive invocation tracking (including permission denials, validation failures, and security blocks) is desired, consider either calling record_tool_invocation from each early-return path or moving the call to a finally block.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/tools/invocation_bridge.py` around lines 35 - 38, The current
flow in invocation_bridge.py returns early on many error paths and never calls
record_tool_invocation, so failed attempts aren't recorded; update the code that
uses invoker._invocation_tracker and invoker._agent_id to ensure
record_tool_invocation is invoked for every exit by either (a) moving the
record_tool_invocation call into a single finally block that runs after the tool
lookup/permission/validation/security/execution/parking logic, or (b) adding
explicit calls to record_tool_invocation (using the same signature as the
successful path) in each early-return branch that returns a ToolResult;
reference the identifiers record_tool_invocation, invoker._invocation_tracker,
invoker._agent_id, and ToolResult when making the change.

Comment on lines +24 to +33
class ToolInvocationTracker:
"""In-memory, append-only tool invocation tracking service.

Records tool invocation outcomes and provides filtered queries
for the activity timeline.
"""

def __init__(self) -> None:
self._records: list[ToolInvocationRecord] = []
self._lock: asyncio.Lock = asyncio.Lock()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

This tracker is still worker-local, not persisted.

_records lives only in app memory, so tool_used events disappear on restart and the org-wide feed only sees invocations handled by the same process. That misses the linked issue's persisted/queryable requirement and will be inconsistent in multi-worker deployments.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/tools/invocation_tracker.py` around lines 24 - 33,
ToolInvocationTracker currently stores _records in-process so tool_used events
are lost on restart and invisible across workers; replace the in-memory list
with a persistent backend by injecting a storage client (e.g., a database or
shared cache) into ToolInvocationTracker, remove or make _records an optional
local cache, and update its write/read methods (the append/write code that
records invocations and any query methods that read the timeline) to persist and
query from the injected storage asynchronously while keeping the asyncio.Lock
only for local concurrency; ensure the new constructor accepts the storage
client, use unique keys/indexes for org-wide queries, and add
migrations/initialization where needed so the timeline is durable and consistent
across multiple workers.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
src/synthorg/hr/activity.py (1)

154-173: ⚠️ Potential issue | 🟠 Major

Raw tool error text exposure in activity descriptions remains unaddressed.

The converter appends record.error_message directly to the description when the tool invocation fails (lines 161-162). This has two issues:

  1. Security: Raw error messages can leak internal paths, prompts, or exception text to API consumers.
  2. Length overflow: error_message can be up to 2048 characters, while ActivityEvent.description has max_length=1024. The combined string ("Tool X failed: " + error_message) could exceed this limit and cause validation failures.
🔒 Proposed fix
     if record.is_success:
         desc = f"Tool {record.tool_name} executed successfully"
     else:
-        suffix = f": {record.error_message}" if record.error_message else ""
-        desc = f"Tool {record.tool_name} failed{suffix}"
+        desc = f"Tool {record.tool_name} failed"

If error details are needed for debugging, consider exposing them through related_ids or a separate secure field rather than in the user-facing description.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/hr/activity.py` around lines 154 - 173, The description
currently appends raw record.error_message in _tool_invocation_to_activity which
can leak sensitive data and exceed ActivityEvent.description's max_length;
change it to a generic failure description (e.g., "Tool {record.tool_name}
failed") and do NOT append raw error text to description, and if you need error
details for debugging put a sanitized and length-limited version into
related_ids (e.g., related_ids["tool_error"] =
sanitize_and_truncate(record.error_message, 512)) or a separate secure field,
ensuring any use of record.error_message is both sanitized and truncated before
storing.
src/synthorg/api/controllers/activities.py (1)

106-146: 🧹 Nitpick | 🔵 Trivial

TaskGroup usage is correct, but sync fetch serializes task metrics retrieval.

The asyncio.TaskGroup correctly parallelizes the async fetches for cost, tool, and delegation records. The ExceptionGroup handling properly propagates fatal errors while gracefully degrading on non-fatal failures.

However, _fetch_task_metrics (line 106) is synchronous and called before the TaskGroup, meaning task metrics are fetched serially before the parallel fetches begin. If performance_tracker.get_task_metrics performs I/O, consider wrapping it in asyncio.to_thread() inside the TaskGroup to avoid blocking.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/api/controllers/activities.py` around lines 106 - 146,
_fetch_task_metrics is called synchronously before the asyncio.TaskGroup,
causing potential blocking; move it into the TaskGroup as a background thread
task (use asyncio.to_thread) so it runs in parallel with _fetch_cost_records,
_fetch_tool_invocations, and _fetch_delegation_records, then read its result
from the created task like cost_task.result(); specifically, create a task via
tg.create_task(asyncio.to_thread(_fetch_task_metrics, app_state, agent_id,
since, now)) (or convert _fetch_task_metrics into an async function and create a
normal task), ensure the new task variable (e.g., metrics_task) is handled the
same as cost_task/tool_task/del_task in the ExceptionGroup and when extracting
the final value for merge_activity_timeline.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/synthorg/communication/delegation/service.py`:
- Around line 191-195: The current except block around
self._record_store.record_sync(record) re-raises MemoryError/RecursionError
leaving prior state mutated; on such fatal store errors undo the prior guard and
audit changes before re-raising: call the inverse of _guard.record_delegation
(e.g. a rollback/release method on _guard) and remove the appended record from
_audit_trail (or otherwise mark it as failed) if record_sync fails, then
re-raise the original exception so delegate() doesn't leave dedupe blockers or a
visible audit entry for a failed call.

In `@tests/unit/communication/delegation/test_service.py`:
- Around line 454-457: Tests are mutating the private attribute _record_store
after construction instead of exercising the public constructor wiring; update
the three test locations to pass the store via the DelegationService constructor
(i.e., use DelegationService(..., record_store=store) or change the
_build_service helper to accept a record_store argument and forward it into
DelegationService.__init__) so the tests validate the new constructor wiring for
DelegationRecordStore instead of assigning to service._record_store.

---

Duplicate comments:
In `@src/synthorg/api/controllers/activities.py`:
- Around line 106-146: _fetch_task_metrics is called synchronously before the
asyncio.TaskGroup, causing potential blocking; move it into the TaskGroup as a
background thread task (use asyncio.to_thread) so it runs in parallel with
_fetch_cost_records, _fetch_tool_invocations, and _fetch_delegation_records,
then read its result from the created task like cost_task.result();
specifically, create a task via
tg.create_task(asyncio.to_thread(_fetch_task_metrics, app_state, agent_id,
since, now)) (or convert _fetch_task_metrics into an async function and create a
normal task), ensure the new task variable (e.g., metrics_task) is handled the
same as cost_task/tool_task/del_task in the ExceptionGroup and when extracting
the final value for merge_activity_timeline.

In `@src/synthorg/hr/activity.py`:
- Around line 154-173: The description currently appends raw
record.error_message in _tool_invocation_to_activity which can leak sensitive
data and exceed ActivityEvent.description's max_length; change it to a generic
failure description (e.g., "Tool {record.tool_name} failed") and do NOT append
raw error text to description, and if you need error details for debugging put a
sanitized and length-limited version into related_ids (e.g.,
related_ids["tool_error"] = sanitize_and_truncate(record.error_message, 512)) or
a separate secure field, ensuring any use of record.error_message is both
sanitized and truncated before storing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 781518c3-73bc-4d10-88ff-f2b410f5e1bb

📥 Commits

Reviewing files that changed from the base of the PR and between fc4f25e and 7913e1a.

📒 Files selected for processing (16)
  • src/synthorg/api/controllers/activities.py
  • src/synthorg/api/state.py
  • src/synthorg/communication/delegation/models.py
  • src/synthorg/communication/delegation/record_store.py
  • src/synthorg/communication/delegation/service.py
  • src/synthorg/hr/activity.py
  • src/synthorg/observability/events/delegation.py
  • src/synthorg/observability/events/tool.py
  • src/synthorg/tools/invocation_bridge.py
  • src/synthorg/tools/invocation_record.py
  • tests/unit/api/controllers/test_activities.py
  • tests/unit/communication/delegation/test_record_store.py
  • tests/unit/communication/delegation/test_service.py
  • tests/unit/hr/performance/test_models.py
  • tests/unit/tools/test_invocation_bridge.py
  • tests/unit/tools/test_invocation_record.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Backend
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations -- Python 3.14 has PEP 649 native lazy annotations
Use except A, B: syntax without parentheses for exception handling (PEP 758) -- ruff enforces this on Python 3.14
Add type hints to all public functions and classes; mypy strict mode required
Use Google-style docstrings on all public classes and functions (enforced by ruff D rules)
Immutability enforcement: create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping. For dict/list fields in frozen Pydantic models, rely on frozen=True and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization)
Use Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict); use @computed_field for derived values instead of storing redundant fields; use NotBlankStr for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (multiple tool invocations, parallel agent calls). Use structured concurrency over bare create_task
Keep functions under 50 lines, files under 800 lines
Line length: 88 characters (ruff enforced)
Explicitly handle all errors; never silently swallow exceptions
Validate at system boundaries: user input, external APIs, config files

Files:

  • src/synthorg/communication/delegation/models.py
  • tests/unit/tools/test_invocation_bridge.py
  • tests/unit/communication/delegation/test_service.py
  • tests/unit/api/controllers/test_activities.py
  • src/synthorg/observability/events/delegation.py
  • src/synthorg/observability/events/tool.py
  • src/synthorg/communication/delegation/service.py
  • tests/unit/tools/test_invocation_record.py
  • tests/unit/hr/performance/test_models.py
  • src/synthorg/api/state.py
  • src/synthorg/hr/activity.py
  • tests/unit/communication/delegation/test_record_store.py
  • src/synthorg/communication/delegation/record_store.py
  • src/synthorg/api/controllers/activities.py
  • src/synthorg/tools/invocation_record.py
  • src/synthorg/tools/invocation_bridge.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/**/*.py: Every module with business logic MUST have: from synthorg.observability import get_logger then logger = get_logger(__name__)
Use logger.info(EVENT, key=value) with structured kwargs for logging; never use logger.info('msg %s', val) formatting
Never use import logging / logging.getLogger() / print() in application code. Exception: observability/setup.py and observability/sinks.py may use stdlib logging and print() for bootstrap code only
Use event name constants from domain-specific modules under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT
All provider calls go through BaseCompletionProvider which applies retry + rate limiting automatically. Never implement retry logic in driver subclasses or calling code. RetryConfig and RateLimiterConfig are set per-provider in ProviderConfig. Retryable errors (is_retryable=True): RateLimitError, ProviderTimeoutError, ProviderConnectionError, ProviderInternalError. Non-retryable errors raise immediately. RetryExhaustedError signals all retries failed -- engine layer catches this to trigger fallback chains. Rate limiter respects RateLimitError.retry_after
Always read the relevant docs/design/ page before implementing any feature or planning any issue. DESIGN_SPEC.md is a pointer file. When a spec topic is referenced (e.g., 'the Agents page'), read the relevant docs/design/ page before coding
If implementation deviates from the design spec (better approach, scope evolved), alert the user and explain why. User decides whether to proceed or update spec. Do NOT silently diverge. When approved deviations occur, update relevant docs/design/ page to reflect new reality

Files:

  • src/synthorg/communication/delegation/models.py
  • src/synthorg/observability/events/delegation.py
  • src/synthorg/observability/events/tool.py
  • src/synthorg/communication/delegation/service.py
  • src/synthorg/api/state.py
  • src/synthorg/hr/activity.py
  • src/synthorg/communication/delegation/record_store.py
  • src/synthorg/api/controllers/activities.py
  • src/synthorg/tools/invocation_record.py
  • src/synthorg/tools/invocation_bridge.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Mark all tests with @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, or @pytest.mark.slow
Always run pytest with -n auto for parallelism via pytest-xdist; never run tests sequentially. Minimum coverage: 80% (enforced in CI). Timeout: 30 seconds per test globally (do not add per-file markers; non-default overrides like timeout(60) are allowed)
Vendor-agnostic naming everywhere: NEVER use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples. Use generic names: example-provider, example-large-001, example-medium-001, example-small-001, large/medium/small aliases. Vendor names allowed only in: (1) docs/design/operations.md provider list, (2) .claude/ skill/agent files, (3) third-party import paths. Tests must use test-provider, test-small-001, etc.
Use Hypothesis for property-based testing (@given + @settings with HYPOTHESIS_PROFILE env var: 'ci' for 50 examples (default), 'dev' for 1000 examples). Run dev profile: HYPOTHESIS_PROFILE=dev uv run python -m pytest tests/ -m unit -n auto -k properties. .hypothesis/ is gitignored
Never skip, dismiss, or ignore flaky tests -- always fix them fully. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() for determinism. For tasks blocking indefinitely until cancelled, use asyncio.Event().wait() instead of asyncio.sleep(large_number) -- it is cancellation-safe

Files:

  • tests/unit/tools/test_invocation_bridge.py
  • tests/unit/communication/delegation/test_service.py
  • tests/unit/api/controllers/test_activities.py
  • tests/unit/tools/test_invocation_record.py
  • tests/unit/hr/performance/test_models.py
  • tests/unit/communication/delegation/test_record_store.py
🧠 Learnings (42)
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state

Applied to files:

  • src/synthorg/communication/delegation/models.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/communication/delegation/models.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : 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, serializing for persistence).

Applied to files:

  • src/synthorg/communication/delegation/models.py
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to **/*.py : Config vs runtime state: use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/communication/delegation/models.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 BaseModel, model_validator, computed_field, ConfigDict.

Applied to files:

  • src/synthorg/communication/delegation/models.py
📚 Learning: 2026-03-16T23:05:29.577Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T23:05:29.577Z
Learning: Applies to **/*.py : 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, serializing for persistence).

Applied to files:

  • src/synthorg/communication/delegation/models.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to **/*.py : Config vs runtime state: frozen Pydantic models for config/identity; separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/communication/delegation/models.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. For derived values use `computed_field` instead of storing + validating redundant fields. Use `NotBlankStr` (from `core.types`) for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • src/synthorg/communication/delegation/models.py
📚 Learning: 2026-03-15T18:42:17.990Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:42:17.990Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`

Applied to files:

  • src/synthorg/communication/delegation/models.py
📚 Learning: 2026-03-17T11:41:02.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T11:41:02.964Z
Learning: Applies to src/**/*.py : Models: Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`). Use `computed_field` for derived values instead of storing + validating redundant fields. Use `NotBlankStr` for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • src/synthorg/communication/delegation/models.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from domain-specific modules under synthorg.observability.events (e.g., PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/observability/events/delegation.py
  • src/synthorg/observability/events/tool.py
  • src/synthorg/tools/invocation_bridge.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from synthorg.observability.events domain-specific modules (e.g., PROVIDER_CALL_START from events.provider). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.

Applied to files:

  • src/synthorg/observability/events/delegation.py
  • src/synthorg/observability/events/tool.py
📚 Learning: 2026-03-24T22:47:39.223Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-24T22:47:39.223Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from domain-specific modules under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`

Applied to files:

  • src/synthorg/observability/events/delegation.py
  • src/synthorg/observability/events/tool.py
  • src/synthorg/tools/invocation_bridge.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability package (observability/): structured logging, correlation tracking, log sinks; event constants organized by domain under observability/events/ (e.g., events.api, events.tool, events.git, events.context_budget, events.backup)

Applied to files:

  • src/synthorg/observability/events/delegation.py
  • src/synthorg/observability/events/tool.py
📚 Learning: 2026-03-18T21:23:23.586Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T21:23:23.586Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from the domain-specific module under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly from synthorg.observability.events.<domain>.

Applied to files:

  • src/synthorg/observability/events/tool.py
  • src/synthorg/tools/invocation_bridge.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`); import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`

Applied to files:

  • src/synthorg/observability/events/tool.py
  • src/synthorg/tools/invocation_bridge.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from the domain-specific module under `synthorg.observability.events` in logging calls

Applied to files:

  • src/synthorg/observability/events/tool.py
  • src/synthorg/tools/invocation_bridge.py
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Use event constants from `synthorg.observability.events.<domain>` (e.g., `API_REQUEST_STARTED` from `events.api`); import directly and log with structured kwargs: `logger.info(EVENT, key=value)`, never interpolated strings

Applied to files:

  • src/synthorg/observability/events/tool.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : Use event name constants from domain-specific modules under `ai_company.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`). Import directly: `from ai_company.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/observability/events/tool.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (no parentheses) for exception handling — PEP 758 exception syntax enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/communication/delegation/service.py
  • src/synthorg/api/controllers/activities.py
  • src/synthorg/tools/invocation_bridge.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Handle errors explicitly; never silently swallow exceptions

Applied to files:

  • src/synthorg/communication/delegation/service.py
  • src/synthorg/api/controllers/activities.py
  • src/synthorg/tools/invocation_bridge.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Use PEP 758 except syntax with `except A, B:` (no parentheses) for multiple exceptions—ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/communication/delegation/service.py
  • src/synthorg/api/controllers/activities.py
  • src/synthorg/tools/invocation_bridge.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Handle errors explicitly, never silently swallow exceptions

Applied to files:

  • src/synthorg/communication/delegation/service.py
  • src/synthorg/api/controllers/activities.py
  • src/synthorg/tools/invocation_bridge.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Handle errors explicitly—never silently swallow exceptions.

Applied to files:

  • src/synthorg/communication/delegation/service.py
  • src/synthorg/api/controllers/activities.py
  • src/synthorg/tools/invocation_bridge.py
📚 Learning: 2026-03-24T22:47:39.223Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-24T22:47:39.223Z
Learning: Applies to **/*.py : Use `except A, B:` syntax without parentheses for exception handling (PEP 758) -- ruff enforces this on Python 3.14

Applied to files:

  • src/synthorg/communication/delegation/service.py
  • src/synthorg/api/controllers/activities.py
  • src/synthorg/tools/invocation_bridge.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: `except A, B:` (no parentheses) — enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/communication/delegation/service.py
  • src/synthorg/api/controllers/activities.py
  • src/synthorg/tools/invocation_bridge.py
📚 Learning: 2026-03-24T22:47:39.223Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-24T22:47:39.223Z
Learning: Applies to **/*.py : Explicitly handle all errors; never silently swallow exceptions

Applied to files:

  • src/synthorg/communication/delegation/service.py
  • src/synthorg/api/controllers/activities.py
  • src/synthorg/tools/invocation_bridge.py
📚 Learning: 2026-03-15T16:55:07.730Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T16:55:07.730Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: use `except A, B:` (no parentheses) — ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/communication/delegation/service.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions.

Applied to files:

  • src/synthorg/communication/delegation/service.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, and key function entry/exit

Applied to files:

  • src/synthorg/communication/delegation/service.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : All error paths must log at WARNING or ERROR with context before raising.

Applied to files:

  • src/synthorg/communication/delegation/service.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/communication/**/*.py : Communication package (communication/): message bus, dispatcher, messenger, channels, delegation, loop prevention, conflict resolution; meeting/ subpackage for meeting protocol (round-robin, position papers, structured phases), scheduler (frequency, participant resolver), orchestrator

Applied to files:

  • src/synthorg/api/state.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...

Applied to files:

  • src/synthorg/api/state.py
  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/backup/**/*.py : Backup package (backup/): scheduled/manual/lifecycle backups of persistence DB, agent memory, company config. BackupService orchestrator, BackupScheduler (periodic asyncio task), RetentionManager (count + age pruning), tar.gz compression, SHA-256 checksums, manifest tracking, validated restore with atomic rollback and safety backup. handlers/ subpackage: ComponentHandler protocol + concrete handlers (PersistenceComponentHandler, MemoryComponentHandler, ConfigComponentHandler)

Applied to files:

  • src/synthorg/api/state.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/security/**/*.py : Security package (security/): SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered), risk classifier, risk tier classifier, action type registry, ToolInvoker security integration, progressive trust (4 strategies), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume)

Applied to files:

  • src/synthorg/api/state.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Prefer `asyncio.TaskGroup` for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare `create_task`. Existing code is being migrated incrementally.

Applied to files:

  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-17T18:52:05.142Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T18:52:05.142Z
Learning: Applies to **/*.py : Async concurrency: prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare create_task.

Applied to files:

  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-24T22:47:39.223Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-24T22:47:39.223Z
Learning: Applies to **/*.py : Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (multiple tool invocations, parallel agent calls). Use structured concurrency over bare create_task

Applied to files:

  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/engine/**/*.py : Engine package (engine/): agent orchestration, parallel execution, task decomposition, routing, TaskEngine (centralized single-writer), task lifecycle/recovery/shutdown, workspace isolation, coordination (4 dispatchers: SAS/centralized/decentralized/context-dependent, wave execution), approval gates (escalation detection, context parking/resume), stagnation detection (ToolRepetitionDetector, corrective prompt injection), AgentRuntimeState (execution status), context budget management, conversation compaction (oldest-turns summarizer)

Applied to files:

  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)

Applied to files:

  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.

Applied to files:

  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/hr/**/*.py : HR package (hr/): hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, LLM calibration, collaboration overrides, trend detection), promotion/demotion (criteria evaluation, approval strategies, model mapping)

Applied to files:

  • src/synthorg/api/controllers/activities.py
🔇 Additional comments (21)
tests/unit/api/controllers/test_activities.py (4)

369-417: Delegation event tests are correct.

The tests properly verify:

  • Org-wide feed returns both delegation_sent and delegation_received events from the same record
  • Agent-scoped filtering correctly returns only delegation_sent for the delegator

However, the past review comment about adding graceful-degradation coverage for the new activity sources (tool invocations, delegation records) remains unaddressed. Consider adding tests that verify /api/v1/activities still returns 200 when tool_invocation_tracker or delegation_record_store are absent or raise exceptions.


29-43: Deterministic time freezing is well-implemented.

The _FrozenDatetime subclass and autouse fixture correctly freeze time in the controller module, ensuring test determinism. This aligns with the coding guideline to mock time for timing-sensitive tests.


300-320: Cost record integration test is correct.

The test properly verifies that cost records are surfaced in the activity feed with the correct event_type and uses vendor-agnostic naming (test-provider, test-medium-001).


351-367: Tool invocation integration test is correct.

The test properly verifies that tool invocation records are surfaced in the activity feed with event_type="tool_used".

src/synthorg/tools/invocation_bridge.py (2)

35-57: Implementation follows best-effort pattern correctly.

The exception handling properly uses PEP 758 syntax (except MemoryError, RecursionError:) and the TOOL_INVOCATION_RECORD_FAILED event constant addresses the previous review feedback about semantic accuracy.

Note: Per the past review comment, this function is only called after successful tool execution (see invoker.py:409). Early-exit paths (permission denials, validation failures, security blocks) do not record invocations. This is a design choice—if comprehensive tracking of all invocation attempts is desired, consider recording from earlier points or using a finally block pattern.


1-34: Module setup and documentation are well-structured.

The module clearly documents its best-effort nature and the function signature is properly typed with a Google-style docstring.

src/synthorg/communication/delegation/record_store.py (3)

24-56: Concurrency model is documented, addressing previous review concern.

The class docstring (lines 30-33) now explicitly documents the intentional locking asymmetry:

  • record_sync doesn't acquire the lock (atomic list.append under cooperative asyncio)
  • _lock serializes concurrent async readers only

This documentation addresses the previous review comment about inconsistent locking by explaining the design rationale. The approach is valid for single-threaded asyncio contexts.


58-145: Query methods are well-structured with consistent patterns.

All three query methods follow the same pattern:

  1. Validate time range
  2. Acquire immutable snapshot under lock
  3. Log the query
  4. Filter and return

The docstrings correctly document the half-open interval semantics (start <= timestamp < end).


148-180: Pure helper functions are correctly implemented.

_validate_time_range provides clear error messages and _filter correctly implements the half-open interval semantics along with optional role-based filtering.

tests/unit/communication/delegation/test_record_store.py (3)

1-66: Basic store tests are well-implemented.

The test setup with _make_record helper and basic CRUD/filtering tests provide good foundational coverage for DelegationRecordStore.


84-161: Time range and combined filtering tests are comprehensive.

The tests correctly verify:

  • Half-open interval semantics (start <= timestamp < end)
  • Error handling for invalid time ranges
  • Combined role + time range filtering

163-188: Delegatee time range filtering test is correct.

Good symmetry with the delegator test, ensuring both perspectives are equally tested.

src/synthorg/api/controllers/activities.py (3)

1-31: Imports are correctly updated for new activity sources.

The new imports for CostRecord, DelegationRecord, TaskMetricRecord, and ToolInvocationRecord properly support the expanded timeline merging.


167-191: Task metrics fetcher implements graceful degradation correctly.

The exception handling follows the established pattern: re-raising fatal errors (MemoryError, RecursionError, ServiceUnavailableError) while gracefully degrading to an empty tuple for other failures with appropriate warning logging.


194-299: Async fetchers implement graceful degradation consistently.

The cost, tool invocation, and delegation record fetchers all:

  • Check the has_* guard before accessing the service
  • Apply time range filtering
  • Re-raise fatal errors while gracefully degrading on others

The org-wide delegation logic correctly fetches all records once and passes them to both sent and received perspectives, which aligns with the merge_activity_timeline design.

src/synthorg/hr/activity.py (3)

1-21: Module documentation accurately reflects expanded functionality.

The docstring correctly describes all event sources merged into the timeline.


117-151: Task started and cost record converters are well-implemented.

The _task_metric_to_started_activity correctly uses an assertion to enforce the started_at precondition (caller must ensure it's not None). The _cost_record_to_activity provides useful summary information in the description.


176-258: Delegation converters and merge function are correctly implemented.

The delegation converters properly set agent_id based on perspective (delegator for sent, delegatee for received). The merge_activity_timeline function maintains backward compatibility with positional-only lifecycle_events and task_metrics arguments while adding keyword-only parameters for the new sources.

src/synthorg/tools/invocation_record.py (1)

15-59: ToolInvocationRecord model is well-designed.

The model correctly:

  • Uses NotBlankStr for identifier fields
  • Enforces immutability with frozen=True
  • Validates the consistency constraint between is_success and error_message
  • Sets appropriate max_length=2048 for error messages (matching the truncation in invocation_bridge.py)
src/synthorg/api/state.py (2)

64-156: AppState initialization correctly extended for new services.

The new _tool_invocation_tracker and _delegation_record_store slots, init parameters, and assignments follow the established pattern for optional services in AppState.


193-400: New service properties follow established patterns.

The has_* boolean guards and accessor properties for cost_tracker, tool_invocation_tracker, and delegation_record_store are consistent with the existing pattern for optional services in AppState.

Comment on lines +191 to +195
if self._record_store is not None:
try:
self._record_store.record_sync(record)
except MemoryError, RecursionError:
raise
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't leave delegation state half-committed on fatal store errors.

By the time record_sync() runs on Line 193, _guard.record_delegation() and _audit_trail.append(record) have already executed. If Lines 194-195 re-raise, delegate() fails but retries can still be dedup-blocked and get_audit_trail() already shows a record for a call that never returned success.

Suggested fix
-        self._guard.record_delegation(
-            request.delegator_id,
-            request.delegatee_id,
-            request.task.id,
-        )
         record = DelegationRecord(
             delegation_id=str(uuid4()),
             delegator_id=request.delegator_id,
             delegatee_id=request.delegatee_id,
             original_task_id=request.task.id,
             delegated_task_id=sub_task.id,
             timestamp=datetime.now(UTC),
             refinement=request.refinement,
         )
-        self._audit_trail.append(record)
         if self._record_store is not None:
             try:
                 self._record_store.record_sync(record)
             except MemoryError, RecursionError:
                 raise
             except Exception:
                 logger.warning(
                     DELEGATION_RECORD_STORE_FAILED,
                     delegator=request.delegator_id,
                     delegatee=request.delegatee_id,
                     exc_info=True,
                 )
+        self._guard.record_delegation(
+            request.delegator_id,
+            request.delegatee_id,
+            request.task.id,
+        )
+        self._audit_trail.append(record)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/communication/delegation/service.py` around lines 191 - 195, The
current except block around self._record_store.record_sync(record) re-raises
MemoryError/RecursionError leaving prior state mutated; on such fatal store
errors undo the prior guard and audit changes before re-raising: call the
inverse of _guard.record_delegation (e.g. a rollback/release method on _guard)
and remove the appended record from _audit_trail (or otherwise mark it as
failed) if record_sync fails, then re-raise the original exception so delegate()
doesn't leave dedupe blockers or a visible audit entry for a failed call.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/synthorg/hr/activity.py (1)

241-256: 🧹 Nitpick | 🔵 Trivial

Consider the insertion order dependency for stable sort behavior.

The current implementation adds events in this order: lifecycle → task_completed → task_started → cost → tool → delegation_sent → delegation_received. Python's sort() is stable, so events with identical timestamps preserve this insertion order.

The test test_identical_timestamps_stable_sort at line 226-228 asserts lifecycle appears before task_completed at the same timestamp, which depends on this insertion order. This is fine as-is, but if the insertion order ever changes, the stable sort assertion would break silently.

Consider adding a brief comment documenting this intentional ordering, or acknowledge this is acceptable behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/hr/activity.py` around lines 241 - 256, The current assembly of
activities relies on insertion order to achieve stable ordering for identical
timestamps (calls to _lifecycle_to_activity,
_task_metric_to_activity/_task_metric_to_started_activity,
_cost_record_to_activity, _tool_invocation_to_activity,
_delegation_to_sent_activity, _delegation_to_received_activity before sorting),
which the test `test_identical_timestamps_stable_sort` depends on; add a concise
inline comment above the activities list explaining that the specific insertion
order is intentional and relied upon because Python's sort is stable (or
alternatively document this behavior in the function/class docstring) so future
maintainers don't accidentally reorder these extends and break the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/synthorg/hr/activity.py`:
- Around line 241-256: The current assembly of activities relies on insertion
order to achieve stable ordering for identical timestamps (calls to
_lifecycle_to_activity,
_task_metric_to_activity/_task_metric_to_started_activity,
_cost_record_to_activity, _tool_invocation_to_activity,
_delegation_to_sent_activity, _delegation_to_received_activity before sorting),
which the test `test_identical_timestamps_stable_sort` depends on; add a concise
inline comment above the activities list explaining that the specific insertion
order is intentional and relied upon because Python's sort is stable (or
alternatively document this behavior in the function/class docstring) so future
maintainers don't accidentally reorder these extends and break the test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3c7058d3-7b17-44b7-b82f-cffd83b9851e

📥 Commits

Reviewing files that changed from the base of the PR and between 7913e1a and 23c61b5.

📒 Files selected for processing (2)
  • src/synthorg/hr/activity.py
  • tests/unit/hr/test_activity.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations -- Python 3.14 has PEP 649 native lazy annotations
Use except A, B: syntax without parentheses for exception handling (PEP 758) -- ruff enforces this on Python 3.14
Add type hints to all public functions and classes; mypy strict mode required
Use Google-style docstrings on all public classes and functions (enforced by ruff D rules)
Immutability enforcement: create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping. For dict/list fields in frozen Pydantic models, rely on frozen=True and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization)
Use Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict); use @computed_field for derived values instead of storing redundant fields; use NotBlankStr for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (multiple tool invocations, parallel agent calls). Use structured concurrency over bare create_task
Keep functions under 50 lines, files under 800 lines
Line length: 88 characters (ruff enforced)
Explicitly handle all errors; never silently swallow exceptions
Validate at system boundaries: user input, external APIs, config files

Files:

  • src/synthorg/hr/activity.py
  • tests/unit/hr/test_activity.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/**/*.py: Every module with business logic MUST have: from synthorg.observability import get_logger then logger = get_logger(__name__)
Use logger.info(EVENT, key=value) with structured kwargs for logging; never use logger.info('msg %s', val) formatting
Never use import logging / logging.getLogger() / print() in application code. Exception: observability/setup.py and observability/sinks.py may use stdlib logging and print() for bootstrap code only
Use event name constants from domain-specific modules under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT
All provider calls go through BaseCompletionProvider which applies retry + rate limiting automatically. Never implement retry logic in driver subclasses or calling code. RetryConfig and RateLimiterConfig are set per-provider in ProviderConfig. Retryable errors (is_retryable=True): RateLimitError, ProviderTimeoutError, ProviderConnectionError, ProviderInternalError. Non-retryable errors raise immediately. RetryExhaustedError signals all retries failed -- engine layer catches this to trigger fallback chains. Rate limiter respects RateLimitError.retry_after
Always read the relevant docs/design/ page before implementing any feature or planning any issue. DESIGN_SPEC.md is a pointer file. When a spec topic is referenced (e.g., 'the Agents page'), read the relevant docs/design/ page before coding
If implementation deviates from the design spec (better approach, scope evolved), alert the user and explain why. User decides whether to proceed or update spec. Do NOT silently diverge. When approved deviations occur, update relevant docs/design/ page to reflect new reality

Files:

  • src/synthorg/hr/activity.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Mark all tests with @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, or @pytest.mark.slow
Always run pytest with -n auto for parallelism via pytest-xdist; never run tests sequentially. Minimum coverage: 80% (enforced in CI). Timeout: 30 seconds per test globally (do not add per-file markers; non-default overrides like timeout(60) are allowed)
Vendor-agnostic naming everywhere: NEVER use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples. Use generic names: example-provider, example-large-001, example-medium-001, example-small-001, large/medium/small aliases. Vendor names allowed only in: (1) docs/design/operations.md provider list, (2) .claude/ skill/agent files, (3) third-party import paths. Tests must use test-provider, test-small-001, etc.
Use Hypothesis for property-based testing (@given + @settings with HYPOTHESIS_PROFILE env var: 'ci' for 50 examples (default), 'dev' for 1000 examples). Run dev profile: HYPOTHESIS_PROFILE=dev uv run python -m pytest tests/ -m unit -n auto -k properties. .hypothesis/ is gitignored
Never skip, dismiss, or ignore flaky tests -- always fix them fully. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() for determinism. For tasks blocking indefinitely until cancelled, use asyncio.Event().wait() instead of asyncio.sleep(large_number) -- it is cancellation-safe

Files:

  • tests/unit/hr/test_activity.py
🧠 Learnings (4)
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)

Applied to files:

  • tests/unit/hr/test_activity.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/hr/**/*.py : HR package (hr/): hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, LLM calibration, collaboration overrides, trend detection), promotion/demotion (criteria evaluation, approval strategies, model mapping)

Applied to files:

  • tests/unit/hr/test_activity.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.

Applied to files:

  • tests/unit/hr/test_activity.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/hr/**/*.py : HR engine must provide: hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, trend detection), promotion/demotion

Applied to files:

  • tests/unit/hr/test_activity.py
🔇 Additional comments (10)
src/synthorg/hr/activity.py (3)

117-133: LGTM! Clean conversion with appropriate precondition.

The assert record.started_at is not None is a valid internal contract since the docstring clearly states the caller must ensure started_at is not None, and the caller (merge_activity_timeline) properly filters with if r.started_at is not None at line 248.


154-172: LGTM! Error message properly redacted.

The implementation correctly omits record.error_message from the description, preventing leakage of internal paths, stack traces, or sensitive data through the activity feed API. This aligns with the commit message "fix: redact raw error messages from tool_used activity descriptions."


175-211: LGTM! Dual-perspective delegation converters are well-designed.

The converters correctly distinguish the perspective:

  • delegation_sent: agent_id = delegator, includes delegatee_id
  • delegation_received: agent_id = delegatee, includes delegator_id

This enables proper agent-specific timeline queries from both directions.

tests/unit/hr/test_activity.py (7)

65-125: LGTM! Well-structured factory helpers with vendor-agnostic naming.

The new factory helpers _make_cost_record, _make_tool_invocation, and _make_delegation_record follow the same pattern as existing helpers with keyword-only arguments and sensible defaults. The use of test-medium-001 and test-provider complies with the vendor-agnostic naming guideline.


243-250: LGTM! Good backward compatibility test.

This test verifies the PR objective of maintaining backward compatibility for positional-only calls to merge_activity_timeline.


256-319: LGTM! Comprehensive task_started event coverage.

Tests cover all key scenarios:

  • Event generation when started_at is present
  • No event when started_at is None
  • Both events from same record
  • Correct ordering relative to other event types

325-372: LGTM! Solid cost_incurred event coverage.

Tests validate the event fields, description format including token counts and cost, and proper merge ordering.


402-421: LGTM! Critical test for error message redaction.

Lines 420-421 explicitly verify that the raw error_message ("Permission denied") is NOT exposed in the activity description. This test ensures the security fix remains in place.


542-574: LGTM! Comprehensive integration test for all event types.

The test_merge_all_event_types test is an excellent integration-style test that:

  • Exercises all 7 event sources in one call
  • Verifies the expected count (7 events)
  • Validates descending timestamp ordering

This provides confidence that all converters work together correctly.


580-681: LGTM! Existing career events tests preserved.

No changes to the TestFilterCareerEvents class, maintaining existing coverage.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/synthorg/engine/agent_engine.py`:
- Around line 227-228: The Args docstring for the AgentEngine constructor was
not updated after adding new parameters; update the class/function docstring
(the Args: section for __init__ in agent_engine.py) to include entries for
tool_invocation_tracker: ToolInvocationTracker | None and provider_registry:
<appropriate type> (describe purpose, default/None behavior), following
Google-style docstring format and matching existing wording style for other
parameters so the public API docs remain accurate.

In `@tests/unit/communication/delegation/test_service.py`:
- Around line 493-510: Add a sibling unit test mirroring
test_memory_error_in_record_store_propagates that asserts RecursionError is
propagated: mock DelegationRecordStore (spec=DelegationRecordStore) and set
store.record_sync.side_effect = RecursionError("recursion"), build the service
with that record_store using _build_service, create the same DelegationRequest
and agents, and assert that service.delegate(request, delegator, delegatee)
raises RecursionError; reference the existing
test_memory_error_in_record_store_propagates, DelegationRecordStore.record_sync,
and service.delegate to place the new test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 77695e06-dd48-49ee-8d27-fd5e036c3f4d

📥 Commits

Reviewing files that changed from the base of the PR and between 23c61b5 and b74b258.

📒 Files selected for processing (2)
  • src/synthorg/engine/agent_engine.py
  • tests/unit/communication/delegation/test_service.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Backend
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations -- Python 3.14 has PEP 649 native lazy annotations
Use except A, B: syntax without parentheses for exception handling (PEP 758) -- ruff enforces this on Python 3.14
Add type hints to all public functions and classes; mypy strict mode required
Use Google-style docstrings on all public classes and functions (enforced by ruff D rules)
Immutability enforcement: create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping. For dict/list fields in frozen Pydantic models, rely on frozen=True and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization)
Use Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict); use @computed_field for derived values instead of storing redundant fields; use NotBlankStr for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (multiple tool invocations, parallel agent calls). Use structured concurrency over bare create_task
Keep functions under 50 lines, files under 800 lines
Line length: 88 characters (ruff enforced)
Explicitly handle all errors; never silently swallow exceptions
Validate at system boundaries: user input, external APIs, config files

Files:

  • src/synthorg/engine/agent_engine.py
  • tests/unit/communication/delegation/test_service.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/**/*.py: Every module with business logic MUST have: from synthorg.observability import get_logger then logger = get_logger(__name__)
Use logger.info(EVENT, key=value) with structured kwargs for logging; never use logger.info('msg %s', val) formatting
Never use import logging / logging.getLogger() / print() in application code. Exception: observability/setup.py and observability/sinks.py may use stdlib logging and print() for bootstrap code only
Use event name constants from domain-specific modules under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT
All provider calls go through BaseCompletionProvider which applies retry + rate limiting automatically. Never implement retry logic in driver subclasses or calling code. RetryConfig and RateLimiterConfig are set per-provider in ProviderConfig. Retryable errors (is_retryable=True): RateLimitError, ProviderTimeoutError, ProviderConnectionError, ProviderInternalError. Non-retryable errors raise immediately. RetryExhaustedError signals all retries failed -- engine layer catches this to trigger fallback chains. Rate limiter respects RateLimitError.retry_after
Always read the relevant docs/design/ page before implementing any feature or planning any issue. DESIGN_SPEC.md is a pointer file. When a spec topic is referenced (e.g., 'the Agents page'), read the relevant docs/design/ page before coding
If implementation deviates from the design spec (better approach, scope evolved), alert the user and explain why. User decides whether to proceed or update spec. Do NOT silently diverge. When approved deviations occur, update relevant docs/design/ page to reflect new reality

Files:

  • src/synthorg/engine/agent_engine.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Mark all tests with @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, or @pytest.mark.slow
Always run pytest with -n auto for parallelism via pytest-xdist; never run tests sequentially. Minimum coverage: 80% (enforced in CI). Timeout: 30 seconds per test globally (do not add per-file markers; non-default overrides like timeout(60) are allowed)
Vendor-agnostic naming everywhere: NEVER use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples. Use generic names: example-provider, example-large-001, example-medium-001, example-small-001, large/medium/small aliases. Vendor names allowed only in: (1) docs/design/operations.md provider list, (2) .claude/ skill/agent files, (3) third-party import paths. Tests must use test-provider, test-small-001, etc.
Use Hypothesis for property-based testing (@given + @settings with HYPOTHESIS_PROFILE env var: 'ci' for 50 examples (default), 'dev' for 1000 examples). Run dev profile: HYPOTHESIS_PROFILE=dev uv run python -m pytest tests/ -m unit -n auto -k properties. .hypothesis/ is gitignored
Never skip, dismiss, or ignore flaky tests -- always fix them fully. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() for determinism. For tasks blocking indefinitely until cancelled, use asyncio.Event().wait() instead of asyncio.sleep(large_number) -- it is cancellation-safe

Files:

  • tests/unit/communication/delegation/test_service.py
🧠 Learnings (4)
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/engine/**/*.py : Engine package (engine/): agent orchestration, parallel execution, task decomposition, routing, TaskEngine (centralized single-writer), task lifecycle/recovery/shutdown, workspace isolation, coordination (4 dispatchers: SAS/centralized/decentralized/context-dependent, wave execution), approval gates (escalation detection, context parking/resume), stagnation detection (ToolRepetitionDetector, corrective prompt injection), AgentRuntimeState (execution status), context budget management, conversation compaction (oldest-turns summarizer)

Applied to files:

  • src/synthorg/engine/agent_engine.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...

Applied to files:

  • src/synthorg/engine/agent_engine.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/security/**/*.py : Security package (security/): SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered), risk classifier, risk tier classifier, action type registry, ToolInvoker security integration, progressive trust (4 strategies), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume)

Applied to files:

  • src/synthorg/engine/agent_engine.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/communication/**/*.py : Communication package (communication/): message bus, dispatcher, messenger, channels, delegation, loop prevention, conflict resolution; meeting/ subpackage for meeting protocol (round-robin, position papers, structured phases), scheduler (frequency, participant resolver), orchestrator

Applied to files:

  • src/synthorg/engine/agent_engine.py
  • tests/unit/communication/delegation/test_service.py
🔇 Additional comments (3)
tests/unit/communication/delegation/test_service.py (2)

80-86: Constructor-based record_store wiring is correctly validated.

Nice change: the helper now injects record_store through DelegationService(...), so tests exercise the public API rather than private attribute mutation.

Also applies to: 124-129


454-471: Positive-path persistence test is solid.

This test cleanly verifies that a successful delegation writes exactly one record with the expected delegator/delegatee IDs.

src/synthorg/engine/agent_engine.py (1)

293-293: Tracker wiring into ToolInvoker looks correct.

Line [293] persists the injected tracker on the engine, and Line [1191] forwards it to ToolInvoker with the correct keyword/type. This closes the runtime gap where invocations could be missed when the tracker is provided.

Also applies to: 1191-1191

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/synthorg/hr/activity.py (1)

1-20: 🛠️ Refactor suggestion | 🟠 Major

Add the required module logger.

This is business-logic code under src/synthorg/**/*.py, but the module still doesn't import get_logger or define logger = get_logger(__name__).

♻️ Minimal fix
 from typing import TYPE_CHECKING
 
 from pydantic import AwareDatetime, BaseModel, ConfigDict, Field
 
+from synthorg.observability import get_logger
 from synthorg.core.types import NotBlankStr  # noqa: TC001
 from synthorg.hr.enums import LifecycleEventType
@@
+logger = get_logger(__name__)
+

As per coding guidelines "Every module with business logic MUST import: from synthorg.observability import get_logger and instantiate logger = get_logger(__name__)".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/hr/activity.py` around lines 1 - 20, The module is missing the
required logger; import get_logger from synthorg.observability and add a
module-level logger variable. Specifically, add "from synthorg.observability
import get_logger" to the top import block and instantiate "logger =
get_logger(__name__)" (module-level) in src/synthorg/hr/activity.py so functions
and classes in this file (e.g., any timeline builders) can use logger
consistently.
♻️ Duplicate comments (2)
src/synthorg/communication/delegation/service.py (1)

191-202: ⚠️ Potential issue | 🟠 Major

Ordering concern: guard/audit mutations happen before store persistence.

The _guard.record_delegation() and _audit_trail.append() calls (lines 176-190) execute before the record_store.record_sync() attempt. If MemoryError or RecursionError is raised at line 194-195, the delegation is already recorded in the guard (blocking retries via dedup) and audit trail, but delegate() will propagate the exception to the caller without returning success.

This creates a state inconsistency: the guard blocks future attempts, but the caller sees a failure.

Consider moving guard/audit mutations after the store persistence completes successfully, or implementing rollback logic for the guard/audit on fatal errors.

🛠️ Suggested reordering to avoid half-committed state
-        self._guard.record_delegation(
-            request.delegator_id,
-            request.delegatee_id,
-            request.task.id,
-        )
         record = DelegationRecord(
             delegation_id=str(uuid4()),
             delegator_id=request.delegator_id,
             delegatee_id=request.delegatee_id,
             original_task_id=request.task.id,
             delegated_task_id=sub_task.id,
             timestamp=datetime.now(UTC),
             refinement=request.refinement,
         )
-        self._audit_trail.append(record)
         if self._record_store is not None:
             try:
                 self._record_store.record_sync(record)
             except MemoryError, RecursionError:
                 raise
             except Exception:
                 logger.warning(
                     DELEGATION_RECORD_STORE_FAILED,
                     delegator=request.delegator_id,
                     delegatee=request.delegatee_id,
                     exc_info=True,
                 )
+        # Only commit guard/audit after store persistence succeeds or is skipped
+        self._guard.record_delegation(
+            request.delegator_id,
+            request.delegatee_id,
+            request.task.id,
+        )
+        self._audit_trail.append(record)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/communication/delegation/service.py` around lines 191 - 202, The
guard and audit are being mutated before persistence which can leave a
half-committed state if _record_store.record_sync(record) raises; move the calls
to _guard.record_delegation(...) and _audit_trail.append(...) so they execute
only after _record_store.record_sync(...) completes successfully in delegate(),
or alternatively add rollback logic that removes the guard entry and audit
append in the MemoryError/RecursionError/other fatal-exception handlers so the
system state remains consistent with persistence.
src/synthorg/tools/invocation_tracker.py (1)

24-33: ⚠️ Potential issue | 🟠 Major

tool_used tracking is still process-local and ephemeral.

This tracker stores records only in worker memory. That means activity history disappears on restart, diverges across multiple API workers, and grows without bound over process lifetime. The linked objective called for a persisted/queryable source, so tool_used is still incomplete for real deployments.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/tools/invocation_tracker.py` around lines 24 - 33,
ToolInvocationTracker currently keeps records only in-memory (see class
ToolInvocationTracker, attribute _records and __init__), which makes tool_used
ephemeral and inconsistent across workers; persist records to a durable,
queryable store instead (e.g., add a persistence backend interface and
implementation using your app DB or a lightweight append log), replace or
augment writes to self._records with calls to that backend (write on record
creation and read/filtering methods should query the store), ensure
thread-safety by keeping or adapting _lock around local cache writes only, and
migrate/convert existing ToolInvocationRecord reads/writes to use the new
persistence layer so activity history survives restarts and is consistent across
processes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/synthorg/communication/delegation/models.py`:
- Line 112: The models in this file are inconsistent: DelegationRecord sets
model_config = ConfigDict(frozen=True, allow_inf_nan=False) while
DelegationRequest and DelegationResult only use ConfigDict(frozen=True). Make
them consistent by adding allow_inf_nan=False to the model_config of
DelegationRequest and DelegationResult (i.e., update their model_config
declarations to ConfigDict(frozen=True, allow_inf_nan=False)) so all three
classes (DelegationRequest, DelegationResult, DelegationRecord) use the same
defensive setting.

In `@src/synthorg/engine/agent_engine.py`:
- Line 95: Move the runtime import of ToolInvocationTracker into the
TYPE_CHECKING block to avoid an unnecessary runtime dependency: remove the
top-level "from synthorg.tools.invocation_tracker import ToolInvocationTracker"
and instead add "from synthorg.tools.invocation_tracker import
ToolInvocationTracker" inside the existing TYPE_CHECKING section so that the
AgentEngine __init__ signature (tool_invocation_tracker: ToolInvocationTracker |
None) and the instance attribute self.tool_invocation_tracker keep their type
information without importing at runtime.

In `@src/synthorg/hr/activity.py`:
- Around line 175-210: The delegation activity converters
_delegation_to_sent_activity and _delegation_to_received_activity currently only
include original_task_id in related_ids; add the child/delegated task identifier
so clients can correlate events. Update both functions to include a
"delegated_task_id" entry in the returned ActivityEvent.related_ids (e.g.,
"delegated_task_id": str(record.delegated_task_id)) alongside the existing
original_task_id and other IDs; keep the rest of the payload unchanged.

In `@tests/unit/api/controllers/test_activities.py`:
- Around line 300-417: Add tests that mimic the pattern in
test_graceful_degradation_no_performance_tracker to ensure /api/v1/activities
returns 200 and partial data when optional trackers fail: create new async tests
(e.g., test_graceful_degradation_no_tool_invocation_tracker and
test_graceful_degradation_no_delegation_store) that inject a
ToolInvocationTracker which raises on record() or a DelegationRecordStore whose
methods raise, call test_client.get("/api/v1/activities"), and assert
resp.status_code == 200 and that returned body contains other available event
types (e.g., lifecycle or cost events) while not crashing; reuse the same
assertions style and pagination checks as in existing tests to confirm graceful
degradation.

In `@tests/unit/communication/delegation/test_service.py`:
- Around line 473-529: Combine the three similar tests into one parametrized
pytest case that iterates over the different exceptions and expected outcomes:
parametrize over record_store.record_sync.side_effect values
(RuntimeError("storage failure"), MemoryError("oom"), RecursionError("max
depth")) and expected behavior (for RuntimeError assert
service.delegate(request, delegator, delegatee).success is True; for MemoryError
and RecursionError assert service.delegate raises the respective exception using
pytest.raises). Use the same setup logic from the original tests
(_build_service(record_store=store), _make_task(), _make_agent(...),
DelegationRequest) and reference record_sync, DelegationRecordStore,
service.delegate, and _build_service in the single parametrized test.

In `@tests/unit/hr/performance/test_models.py`:
- Around line 118-122: Move the local imports (timedelta and TaskMetricRecord)
to the module-level imports alongside other test imports; then combine the two
“started after/equal completed” tests into a single parametrized test using
pytest.mark.parametrize (referencing test_started_at_after_completed_at_rejected
and test_started_at_equal_completed_at_rejected) that supplies started_offset
values (timedelta(hours=1) and timedelta(0)) and asserts TaskMetricRecord raises
ValidationError with the same match; keep using NOW, TaskType, Complexity and
the same construction of TaskMetricRecord inside the parametrized test to
preserve test data.

In `@tests/unit/tools/test_invocation_bridge.py`:
- Around line 102-110: The test currently only asserts that
record_tool_invocation re-raises MemoryError; add coverage for the
RecursionError branch by parameterizing the test to run for both exception types
(MemoryError and RecursionError). Update test_memory_error_propagates (or rename
to a parameterized test) to accept an exception class, set
tracker.record.side_effect to that exception (using
AsyncMock/spec=ToolInvocationTracker), and assert that await
record_tool_invocation(invoker, tool_call, result) raises the provided exception
type; keep existing setup (invoker from _make_invoker, tool_call from
_make_tool_call, result from _make_result) and use pytest.mark.parametrize to
enumerate (MemoryError, RecursionError).

---

Outside diff comments:
In `@src/synthorg/hr/activity.py`:
- Around line 1-20: The module is missing the required logger; import get_logger
from synthorg.observability and add a module-level logger variable.
Specifically, add "from synthorg.observability import get_logger" to the top
import block and instantiate "logger = get_logger(__name__)" (module-level) in
src/synthorg/hr/activity.py so functions and classes in this file (e.g., any
timeline builders) can use logger consistently.

---

Duplicate comments:
In `@src/synthorg/communication/delegation/service.py`:
- Around line 191-202: The guard and audit are being mutated before persistence
which can leave a half-committed state if _record_store.record_sync(record)
raises; move the calls to _guard.record_delegation(...) and
_audit_trail.append(...) so they execute only after
_record_store.record_sync(...) completes successfully in delegate(), or
alternatively add rollback logic that removes the guard entry and audit append
in the MemoryError/RecursionError/other fatal-exception handlers so the system
state remains consistent with persistence.

In `@src/synthorg/tools/invocation_tracker.py`:
- Around line 24-33: ToolInvocationTracker currently keeps records only
in-memory (see class ToolInvocationTracker, attribute _records and __init__),
which makes tool_used ephemeral and inconsistent across workers; persist records
to a durable, queryable store instead (e.g., add a persistence backend interface
and implementation using your app DB or a lightweight append log), replace or
augment writes to self._records with calls to that backend (write on record
creation and read/filtering methods should query the store), ensure
thread-safety by keeping or adapting _lock around local cache writes only, and
migrate/convert existing ToolInvocationRecord reads/writes to use the new
persistence layer so activity history survives restarts and is consistent across
processes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d95529f3-3717-496f-910c-6abd25e6cac4

📥 Commits

Reviewing files that changed from the base of the PR and between 9b18e73 and 49cbfad.

📒 Files selected for processing (27)
  • CLAUDE.md
  • docs/design/agents.md
  • docs/design/operations.md
  • src/synthorg/api/app.py
  • src/synthorg/api/controllers/activities.py
  • src/synthorg/api/state.py
  • src/synthorg/communication/delegation/models.py
  • src/synthorg/communication/delegation/record_store.py
  • src/synthorg/communication/delegation/service.py
  • src/synthorg/engine/agent_engine.py
  • src/synthorg/hr/activity.py
  • src/synthorg/hr/performance/models.py
  • src/synthorg/observability/events/delegation.py
  • src/synthorg/observability/events/tool.py
  • src/synthorg/tools/invocation_bridge.py
  • src/synthorg/tools/invocation_record.py
  • src/synthorg/tools/invocation_tracker.py
  • src/synthorg/tools/invoker.py
  • tests/unit/api/conftest.py
  • tests/unit/api/controllers/test_activities.py
  • tests/unit/communication/delegation/test_record_store.py
  • tests/unit/communication/delegation/test_service.py
  • tests/unit/hr/performance/test_models.py
  • tests/unit/hr/test_activity.py
  • tests/unit/tools/test_invocation_bridge.py
  • tests/unit/tools/test_invocation_record.py
  • tests/unit/tools/test_invocation_tracker.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Web
  • GitHub Check: Build Backend
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Do NOT use from __future__ import annotations - Python 3.14 has PEP 649 with native lazy annotations
Use except A, B: syntax (no parentheses) per PEP 758 - ruff enforces this on Python 3.14

Files:

  • src/synthorg/tools/invoker.py
  • tests/unit/tools/test_invocation_tracker.py
  • src/synthorg/communication/delegation/models.py
  • src/synthorg/tools/invocation_bridge.py
  • tests/unit/tools/test_invocation_record.py
  • tests/unit/communication/delegation/test_service.py
  • src/synthorg/api/app.py
  • src/synthorg/communication/delegation/service.py
  • src/synthorg/engine/agent_engine.py
  • src/synthorg/observability/events/tool.py
  • tests/unit/api/conftest.py
  • tests/unit/hr/performance/test_models.py
  • tests/unit/tools/test_invocation_bridge.py
  • tests/unit/communication/delegation/test_record_store.py
  • src/synthorg/hr/activity.py
  • src/synthorg/api/state.py
  • tests/unit/api/controllers/test_activities.py
  • src/synthorg/tools/invocation_tracker.py
  • src/synthorg/tools/invocation_record.py
  • src/synthorg/hr/performance/models.py
  • tests/unit/hr/test_activity.py
  • src/synthorg/communication/delegation/record_store.py
  • src/synthorg/api/controllers/activities.py
  • src/synthorg/observability/events/delegation.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.py: All public functions must have type hints. Type checking enforced by mypy in strict mode.
Google-style docstrings required on all public classes and functions, enforced by ruff D rules
Use Pydantic v2 conventions: BaseModel, model_validator, computed_field, ConfigDict. Use @computed_field for derived values instead of storing redundant fields. Use NotBlankStr for all identifier/name fields.
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g., multiple tool invocations, parallel agent calls) over bare create_task
Line length must be 88 characters (enforced by ruff)
Functions must be less than 50 lines, files less than 800 lines
Handle errors explicitly, never silently swallow them. All error paths must log at WARNING or ERROR with context before raising.
Never use import logging / logging.getLogger() / print() in application code (except observability/setup.py and observability/sinks.py which may use stdlib logging and stderr print for bootstrap)
Always use logger as the variable name (not _logger, not log)
Always use event name constants from domain-specific modules under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT
Use structured logging: always logger.info(EVENT, key=value) - never use printf-style formatting like logger.info("msg %s", val)
All state transitions must log at INFO level
DEBUG logging is appropriate for object creation, internal flow, and entry/exit of key functions
Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples. Use generic names: example-provider, example-large-001, example-medium-001, etc. Vendor names only in: (1) Operations design page, (2) .claude/ skill files, (3) third-party import paths.

Files:

  • src/synthorg/tools/invoker.py
  • src/synthorg/communication/delegation/models.py
  • src/synthorg/tools/invocation_bridge.py
  • src/synthorg/api/app.py
  • src/synthorg/communication/delegation/service.py
  • src/synthorg/engine/agent_engine.py
  • src/synthorg/observability/events/tool.py
  • src/synthorg/hr/activity.py
  • src/synthorg/api/state.py
  • src/synthorg/tools/invocation_tracker.py
  • src/synthorg/tools/invocation_record.py
  • src/synthorg/hr/performance/models.py
  • src/synthorg/communication/delegation/record_store.py
  • src/synthorg/api/controllers/activities.py
  • src/synthorg/observability/events/delegation.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Every module with business logic MUST import: from synthorg.observability import get_logger and instantiate logger = get_logger(__name__)

Files:

  • src/synthorg/tools/invoker.py
  • src/synthorg/communication/delegation/models.py
  • src/synthorg/tools/invocation_bridge.py
  • src/synthorg/api/app.py
  • src/synthorg/communication/delegation/service.py
  • src/synthorg/engine/agent_engine.py
  • src/synthorg/observability/events/tool.py
  • src/synthorg/hr/activity.py
  • src/synthorg/api/state.py
  • src/synthorg/tools/invocation_tracker.py
  • src/synthorg/tools/invocation_record.py
  • src/synthorg/hr/performance/models.py
  • src/synthorg/communication/delegation/record_store.py
  • src/synthorg/api/controllers/activities.py
  • src/synthorg/observability/events/delegation.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Tests must use test-provider, test-small-001, etc. instead of real vendor names
Use @pytest.mark.parametrize for testing similar cases
Mark all tests with appropriate pytest markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow
Use Hypothesis for property-based testing in Python with @given and @settings decorators. Run dev profile with 1000 examples via HYPOTHESIS_PROFILE=dev environment variable.
Never skip, dismiss, or ignore flaky tests. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() to make them deterministic. For tasks blocking indefinitely, use asyncio.Event().wait() instead of asyncio.sleep(large_number).

Files:

  • tests/unit/tools/test_invocation_tracker.py
  • tests/unit/tools/test_invocation_record.py
  • tests/unit/communication/delegation/test_service.py
  • tests/unit/api/conftest.py
  • tests/unit/hr/performance/test_models.py
  • tests/unit/tools/test_invocation_bridge.py
  • tests/unit/communication/delegation/test_record_store.py
  • tests/unit/api/controllers/test_activities.py
  • tests/unit/hr/test_activity.py
docs/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Documentation uses Markdown and is built with Zensical (config: mkdocs.yml). API reference auto-generated from OpenAPI schema via scripts/export_openapi.py. Library reference auto-generated via mkdocstrings + Griffe.

Files:

  • docs/design/agents.md
  • docs/design/operations.md
src/synthorg/api/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/api/**/*.py: REST API defined with Litestar. Errors must use RFC 9457 format in src/synthorg/api/
Litestar API must include setup wizard, auth/, auto-wiring, and lifecycle management

Files:

  • src/synthorg/api/app.py
  • src/synthorg/api/state.py
  • src/synthorg/api/controllers/activities.py
🧠 Learnings (56)
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/security/**/*.py : Security package (security/): SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered), risk classifier, risk tier classifier, action type registry, ToolInvoker security integration, progressive trust (4 strategies), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume)

Applied to files:

  • src/synthorg/tools/invoker.py
  • CLAUDE.md
  • src/synthorg/engine/agent_engine.py
  • src/synthorg/api/state.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/security/**/*.py : Security module includes SecOps agent, rule engine (soft-allow/hard-deny), audit log, output scanner, risk classifier, autonomy levels (4 strategies), timeout policies.

Applied to files:

  • src/synthorg/tools/invoker.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/communication/delegation/models.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state

Applied to files:

  • src/synthorg/communication/delegation/models.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : 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, serializing for persistence).

Applied to files:

  • src/synthorg/communication/delegation/models.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 BaseModel, model_validator, computed_field, ConfigDict.

Applied to files:

  • src/synthorg/communication/delegation/models.py
  • src/synthorg/tools/invocation_record.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. For derived values use `computed_field` instead of storing + validating redundant fields. Use `NotBlankStr` (from `core.types`) for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • src/synthorg/communication/delegation/models.py
  • src/synthorg/tools/invocation_record.py
📚 Learning: 2026-03-16T23:05:29.577Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T23:05:29.577Z
Learning: Applies to **/*.py : 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, serializing for persistence).

Applied to files:

  • src/synthorg/communication/delegation/models.py
📚 Learning: 2026-03-15T18:42:17.990Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:42:17.990Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`

Applied to files:

  • src/synthorg/communication/delegation/models.py
  • src/synthorg/tools/invocation_record.py
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to **/*.py : Config vs runtime state: use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/communication/delegation/models.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to **/*.py : Config vs runtime state: frozen Pydantic models for config/identity; separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/communication/delegation/models.py
📚 Learning: 2026-03-17T11:41:02.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T11:41:02.964Z
Learning: Applies to src/**/*.py : Models: Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`). Use `computed_field` for derived values instead of storing + validating redundant fields. Use `NotBlankStr` for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • src/synthorg/communication/delegation/models.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Prefer `asyncio.TaskGroup` for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare `create_task`. Existing code is being migrated incrementally.

Applied to files:

  • src/synthorg/tools/invocation_bridge.py
  • src/synthorg/tools/invocation_tracker.py
  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (no parentheses) for exception handling — PEP 758 exception syntax enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/tools/invocation_bridge.py
  • src/synthorg/communication/delegation/service.py
  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Handle errors explicitly; never silently swallow exceptions

Applied to files:

  • src/synthorg/tools/invocation_bridge.py
  • src/synthorg/communication/delegation/service.py
  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Handle errors explicitly, never silently swallow exceptions

Applied to files:

  • src/synthorg/tools/invocation_bridge.py
  • src/synthorg/communication/delegation/service.py
  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Use PEP 758 except syntax with `except A, B:` (no parentheses) for multiple exceptions—ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/tools/invocation_bridge.py
  • src/synthorg/communication/delegation/service.py
  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Handle errors explicitly—never silently swallow exceptions.

Applied to files:

  • src/synthorg/tools/invocation_bridge.py
  • src/synthorg/communication/delegation/service.py
  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (no parentheses) per PEP 758 - ruff enforces this on Python 3.14

Applied to files:

  • src/synthorg/tools/invocation_bridge.py
  • src/synthorg/communication/delegation/service.py
  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: `except A, B:` (no parentheses) — enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/tools/invocation_bridge.py
  • src/synthorg/communication/delegation/service.py
  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-18T21:23:23.586Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T21:23:23.586Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from the domain-specific module under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly from synthorg.observability.events.<domain>.

Applied to files:

  • src/synthorg/tools/invocation_bridge.py
  • src/synthorg/observability/events/tool.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from domain-specific modules under synthorg.observability.events (e.g., PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/tools/invocation_bridge.py
  • src/synthorg/observability/events/tool.py
  • src/synthorg/observability/events/delegation.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`); import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`

Applied to files:

  • src/synthorg/tools/invocation_bridge.py
  • src/synthorg/observability/events/tool.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from the domain-specific module under `synthorg.observability.events` in logging calls

Applied to files:

  • src/synthorg/tools/invocation_bridge.py
  • src/synthorg/observability/events/tool.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Engine: Agent orchestration, execution loops, parallel execution, task decomposition, routing, task assignment, centralized single-writer task state engine (TaskEngine), task lifecycle, recovery, shutdown, workspace isolation, coordination (multi-agent pipeline: TopologyDispatcher protocol, 4 dispatchers — SAS/centralized/decentralized/context-dependent, wave execution, workspace lifecycle integration, CoordinationSectionConfig company config bridge, build_coordinator factory), coordination error classification, prompt policy validation, checkpoint recovery (checkpoint/, per-turn persistence, heartbeat detection, CheckpointRecoveryStrategy), approval gate (escalation detection, context parking/resume, EscalationInfo/ResumePayload models), stagnation detection (stagnation/, StagnationDetector protocol, ToolRepetitionDetector, dual-signal analysis, corrective prompt injection), agent runtime state (AgentRuntimeState, lightweight per-agent execution status for dashboard queries and recove...

Applied to files:

  • docs/design/agents.md
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/hr/**/*.py : HR package (hr/): hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, LLM calibration, collaboration overrides, trend detection), promotion/demotion (criteria evaluation, approval strategies, model mapping)

Applied to files:

  • docs/design/agents.md
  • tests/unit/api/conftest.py
  • tests/unit/hr/test_activity.py
  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Settings: Runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces), Fernet encryption for sensitive values, config bridge, ConfigResolver (typed composed reads for controllers), validation, registry, change notifications via message bus. Per-namespace setting definitions in definitions/ submodule (api, company, providers, memory, budget, security, coordination, observability, backup).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...

Applied to files:

  • CLAUDE.md
  • src/synthorg/api/app.py
  • src/synthorg/engine/agent_engine.py
  • tests/unit/api/conftest.py
  • src/synthorg/api/state.py
  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Documentation source in `docs/` (Markdown, built with Zensical). Design spec in `docs/design/` (7 pages: index, agents, organization, communication, engine, memory, operations). Architecture in `docs/architecture/` (overview, tech-stack, decision log). Roadmap in `docs/roadmap/`. Security in `docs/security.md`. Licensing in `docs/licensing.md`. Reference in `docs/reference/`. REST API reference in `docs/rest-api.md`. Library reference in `docs/api/` (auto-generated from docstrings). Custom templates in `docs/overrides/`. Config in `mkdocs.yml`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Documentation structure: `docs/design/` (10 pages), `docs/architecture/`, `docs/roadmap/`, `docs/security.md`, `docs/licensing.md`, `docs/reference/`, `docs/rest-api.md` + generated API reference

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Security: SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered), risk classifier, risk tier classifier, action type registry, ToolInvoker security integration, progressive trust (4 strategies: disabled/weighted/per-category/milestone), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to docs/** : Docs source in docs/ (Markdown, built with Zensical); design spec in docs/design/ (7 pages: index, agents, organization, communication, engine, memory, operations)

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/settings/**/*.py : Settings package (settings/): runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces), Fernet encryption for sensitive values, config bridge (JSON serialization for Pydantic/collections), ConfigResolver (typed accessors), validation, registry, change notifications via message bus, SettingsSubscriber protocol, SettingsChangeDispatcher (polls `#settings` channel, routes to subscribers, restart_required filtering)

Applied to files:

  • CLAUDE.md
  • src/synthorg/api/app.py
📚 Learning: 2026-03-15T21:20:09.993Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:20:09.993Z
Learning: Applies to web/src/components/** : Vue components organized by feature (agents/, approvals/, budget/, common/, dashboard/, layout/, messages/, org-chart/, tasks/).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to web/src/**/*.{ts,tsx} : React 19 + shadcn/ui + Tailwind CSS for web dashboard. TypeScript required in `web/src/`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/communication/**/*.py : Communication package (communication/): message bus, dispatcher, messenger, channels, delegation, loop prevention, conflict resolution; meeting/ subpackage for meeting protocol (round-robin, position papers, structured phases), scheduler (frequency, participant resolver), orchestrator

Applied to files:

  • tests/unit/communication/delegation/test_service.py
  • src/synthorg/api/app.py
  • src/synthorg/engine/agent_engine.py
  • src/synthorg/api/state.py
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to src/synthorg/api/**/*.py : Litestar API must include setup wizard, auth/, auto-wiring, and lifecycle management

Applied to files:

  • src/synthorg/api/app.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/backup/**/*.py : Backup package (backup/): scheduled/manual/lifecycle backups of persistence DB, agent memory, company config. BackupService orchestrator, BackupScheduler (periodic asyncio task), RetentionManager (count + age pruning), tar.gz compression, SHA-256 checksums, manifest tracking, validated restore with atomic rollback and safety backup. handlers/ subpackage: ComponentHandler protocol + concrete handlers (PersistenceComponentHandler, MemoryComponentHandler, ConfigComponentHandler)

Applied to files:

  • src/synthorg/api/app.py
  • src/synthorg/api/state.py
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to src/synthorg/providers/**/*.py : Providers: LLM provider abstraction (LiteLLM adapter), auth types (api_key/oauth/custom_header/none), presets (PROVIDER_PRESETS), runtime CRUD (ProviderManagementService with asyncio.Lock serialization), hot-reload via AppState swap.

Applied to files:

  • src/synthorg/api/app.py
  • tests/unit/api/conftest.py
📚 Learning: 2026-03-15T16:55:07.730Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T16:55:07.730Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: use `except A, B:` (no parentheses) — ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/communication/delegation/service.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions.

Applied to files:

  • src/synthorg/communication/delegation/service.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, and key function entry/exit

Applied to files:

  • src/synthorg/communication/delegation/service.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/engine/**/*.py : Engine package (engine/): agent orchestration, parallel execution, task decomposition, routing, TaskEngine (centralized single-writer), task lifecycle/recovery/shutdown, workspace isolation, coordination (4 dispatchers: SAS/centralized/decentralized/context-dependent, wave execution), approval gates (escalation detection, context parking/resume), stagnation detection (ToolRepetitionDetector, corrective prompt injection), AgentRuntimeState (execution status), context budget management, conversation compaction (oldest-turns summarizer)

Applied to files:

  • src/synthorg/engine/agent_engine.py
  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability package (observability/): structured logging, correlation tracking, log sinks; event constants organized by domain under observability/events/ (e.g., events.api, events.tool, events.git, events.context_budget, events.backup)

Applied to files:

  • src/synthorg/observability/events/tool.py
  • src/synthorg/observability/events/delegation.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from synthorg.observability.events domain-specific modules (e.g., PROVIDER_CALL_START from events.provider). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.

Applied to files:

  • src/synthorg/observability/events/tool.py
  • src/synthorg/observability/events/delegation.py
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Use event constants from `synthorg.observability.events.<domain>` (e.g., `API_REQUEST_STARTED` from `events.api`); import directly and log with structured kwargs: `logger.info(EVENT, key=value)`, never interpolated strings

Applied to files:

  • src/synthorg/observability/events/tool.py
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to src/**/*.py : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`

Applied to files:

  • src/synthorg/observability/events/tool.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)

Applied to files:

  • tests/unit/api/conftest.py
  • tests/unit/hr/test_activity.py
  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST import: `from synthorg.observability import get_logger` and instantiate `logger = get_logger(__name__)`

Applied to files:

  • src/synthorg/api/state.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic must import `from synthorg.observability import get_logger` and define `logger = get_logger(__name__)`

Applied to files:

  • src/synthorg/api/state.py
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to tests/**/*.py : Never skip, dismiss, or ignore flaky tests. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic. For tasks blocking indefinitely, use `asyncio.Event().wait()` instead of `asyncio.sleep(large_number)`.

Applied to files:

  • tests/unit/api/controllers/test_activities.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to tests/**/*.py : NEVER skip, dismiss, or ignore flaky tests — always fix them fully and fundamentally. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins

Applied to files:

  • tests/unit/api/controllers/test_activities.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.

Applied to files:

  • tests/unit/hr/test_activity.py
  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/hr/**/*.py : HR engine must provide: hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, trend detection), promotion/demotion

Applied to files:

  • tests/unit/hr/test_activity.py
📚 Learning: 2026-03-17T18:52:05.142Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T18:52:05.142Z
Learning: Applies to **/*.py : Async concurrency: prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare create_task.

Applied to files:

  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to src/**/*.py : Prefer `asyncio.TaskGroup` for fan-out/fan-in parallel operations in new code (e.g., multiple tool invocations, parallel agent calls) over bare `create_task`

Applied to files:

  • src/synthorg/api/controllers/activities.py

from synthorg.providers.models import ChatMessage
from synthorg.security.audit import AuditLog
from synthorg.security.autonomy.models import EffectiveAutonomy # noqa: TC001
from synthorg.tools.invocation_tracker import ToolInvocationTracker # noqa: TC001
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider moving ToolInvocationTracker import to TYPE_CHECKING block.

The import is used only for type hints in the constructor signature (tool_invocation_tracker: ToolInvocationTracker | None) and stored on self. For consistency with similar optional dependencies (lines 99-132), consider moving this to the TYPE_CHECKING block to avoid unnecessary runtime imports.

♻️ Suggested change
-from synthorg.tools.invocation_tracker import ToolInvocationTracker  # noqa: TC001
+
 if TYPE_CHECKING:
     from synthorg.api.approval_store import ApprovalStore
     # ... existing imports ...
+    from synthorg.tools.invocation_tracker import ToolInvocationTracker
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/engine/agent_engine.py` at line 95, Move the runtime import of
ToolInvocationTracker into the TYPE_CHECKING block to avoid an unnecessary
runtime dependency: remove the top-level "from synthorg.tools.invocation_tracker
import ToolInvocationTracker" and instead add "from
synthorg.tools.invocation_tracker import ToolInvocationTracker" inside the
existing TYPE_CHECKING section so that the AgentEngine __init__ signature
(tool_invocation_tracker: ToolInvocationTracker | None) and the instance
attribute self.tool_invocation_tracker keep their type information without
importing at runtime.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/synthorg/hr/activity.py (1)

8-20: 🛠️ Refactor suggestion | 🟠 Major

Add the module logger required for src/synthorg/ business logic.

This file now carries more HR timeline logic, but it still doesn't initialize logger = get_logger(__name__), which makes it an outlier from the repo-wide observability contract.

As per coding guidelines src/synthorg/**/*.py: Every module with business logic MUST import: from synthorg.observability import get_logger and instantiate logger = get_logger(__name__)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/hr/activity.py` around lines 8 - 20, This module is missing the
standardized module logger; import get_logger from synthorg.observability and
create the module-level logger by adding logger = get_logger(__name__) near the
top of the file (after the imports). Ensure you add the import statement for
get_logger and instantiate the logger variable so subsequent functions/classes
in this module can use logger consistently.
♻️ Duplicate comments (1)
src/synthorg/engine/agent_engine.py (1)

197-230: 🧹 Nitpick | 🔵 Trivial

Document provider_registry in the constructor Args block too.

The updated docstring covers tool_invocation_tracker, but the public signature at Line 229 still exposes provider_registry without a matching Args entry, so the constructor docs remain incomplete.

As per coding guidelines src/**/*.py: Google-style docstrings required on all public classes and functions, enforced by ruff D rules

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/engine/agent_engine.py` around lines 197 - 230, The
constructor's docstring Args block is missing documentation for the
provider_registry parameter; update the __init__ docstring (the Args section for
the constructor of the class containing __init__) to add an entry describing
provider_registry: mention its type (ProviderRegistry | None), purpose (registry
of completion/other providers used by the engine), and default behavior when
None, matching the style of the existing tool_invocation_tracker entry so doc
checks (ruff D) pass.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/synthorg/communication/delegation/models.py`:
- Line 22: The wrapper models DelegationRequest, DelegationResult, and
DelegationRecord currently set model_config = ConfigDict(allow_inf_nan=False)
but that does not propagate to nested Task instances; update the Task model to
include model_config = ConfigDict(frozen=True, allow_inf_nan=False) so Task's
float fields (e.g., budget_limit) reject inf/NaN, or alternatively add an
explicit field validator on the wrapper models
(DelegationRequest/DelegationResult) that checks task.budget_limit for isfinite
and raises a ValueError; note DelegationRecord has no float fields so its config
has no effect.

In `@tests/unit/api/controllers/test_activities.py`:
- Around line 300-417: Add one end-to-end test that records a "task_started"
lifecycle event and verifies it appears in the activities feed; create a new
async test (e.g. test_feed_with_task_started) that uses the same helper
_make_lifecycle_event to save a lifecycle event with event_type="task_started"
and a timestamp (e.g. _NOW - timedelta(hours=1)), then call GET
"/api/v1/activities" and assert resp.status_code == 200,
body["pagination"]["total"] == 1 and body["data"][0]["event_type"] ==
"task_started" so the controller and pagination for task_started are exercised.

---

Outside diff comments:
In `@src/synthorg/hr/activity.py`:
- Around line 8-20: This module is missing the standardized module logger;
import get_logger from synthorg.observability and create the module-level logger
by adding logger = get_logger(__name__) near the top of the file (after the
imports). Ensure you add the import statement for get_logger and instantiate the
logger variable so subsequent functions/classes in this module can use logger
consistently.

---

Duplicate comments:
In `@src/synthorg/engine/agent_engine.py`:
- Around line 197-230: The constructor's docstring Args block is missing
documentation for the provider_registry parameter; update the __init__ docstring
(the Args section for the constructor of the class containing __init__) to add
an entry describing provider_registry: mention its type (ProviderRegistry |
None), purpose (registry of completion/other providers used by the engine), and
default behavior when None, matching the style of the existing
tool_invocation_tracker entry so doc checks (ruff D) pass.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9b0d4a13-95cb-4244-b26a-9bc6ac64ff58

📥 Commits

Reviewing files that changed from the base of the PR and between 49cbfad and 2f9cc45.

📒 Files selected for processing (8)
  • src/synthorg/communication/delegation/models.py
  • src/synthorg/engine/agent_engine.py
  • src/synthorg/hr/activity.py
  • tests/unit/api/controllers/test_activities.py
  • tests/unit/communication/delegation/test_service.py
  • tests/unit/hr/performance/test_models.py
  • tests/unit/hr/test_activity.py
  • tests/unit/tools/test_invocation_bridge.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Backend
  • GitHub Check: Build Sandbox
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Do NOT use from __future__ import annotations - Python 3.14 has PEP 649 with native lazy annotations
Use except A, B: syntax (no parentheses) per PEP 758 - ruff enforces this on Python 3.14

Files:

  • src/synthorg/engine/agent_engine.py
  • src/synthorg/hr/activity.py
  • src/synthorg/communication/delegation/models.py
  • tests/unit/api/controllers/test_activities.py
  • tests/unit/tools/test_invocation_bridge.py
  • tests/unit/hr/test_activity.py
  • tests/unit/communication/delegation/test_service.py
  • tests/unit/hr/performance/test_models.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.py: All public functions must have type hints. Type checking enforced by mypy in strict mode.
Google-style docstrings required on all public classes and functions, enforced by ruff D rules
Use Pydantic v2 conventions: BaseModel, model_validator, computed_field, ConfigDict. Use @computed_field for derived values instead of storing redundant fields. Use NotBlankStr for all identifier/name fields.
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g., multiple tool invocations, parallel agent calls) over bare create_task
Line length must be 88 characters (enforced by ruff)
Functions must be less than 50 lines, files less than 800 lines
Handle errors explicitly, never silently swallow them. All error paths must log at WARNING or ERROR with context before raising.
Never use import logging / logging.getLogger() / print() in application code (except observability/setup.py and observability/sinks.py which may use stdlib logging and stderr print for bootstrap)
Always use logger as the variable name (not _logger, not log)
Always use event name constants from domain-specific modules under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT
Use structured logging: always logger.info(EVENT, key=value) - never use printf-style formatting like logger.info("msg %s", val)
All state transitions must log at INFO level
DEBUG logging is appropriate for object creation, internal flow, and entry/exit of key functions
Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples. Use generic names: example-provider, example-large-001, example-medium-001, etc. Vendor names only in: (1) Operations design page, (2) .claude/ skill files, (3) third-party import paths.

Files:

  • src/synthorg/engine/agent_engine.py
  • src/synthorg/hr/activity.py
  • src/synthorg/communication/delegation/models.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Every module with business logic MUST import: from synthorg.observability import get_logger and instantiate logger = get_logger(__name__)

Files:

  • src/synthorg/engine/agent_engine.py
  • src/synthorg/hr/activity.py
  • src/synthorg/communication/delegation/models.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Tests must use test-provider, test-small-001, etc. instead of real vendor names
Use @pytest.mark.parametrize for testing similar cases
Mark all tests with appropriate pytest markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow
Use Hypothesis for property-based testing in Python with @given and @settings decorators. Run dev profile with 1000 examples via HYPOTHESIS_PROFILE=dev environment variable.
Never skip, dismiss, or ignore flaky tests. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() to make them deterministic. For tasks blocking indefinitely, use asyncio.Event().wait() instead of asyncio.sleep(large_number).

Files:

  • tests/unit/api/controllers/test_activities.py
  • tests/unit/tools/test_invocation_bridge.py
  • tests/unit/hr/test_activity.py
  • tests/unit/communication/delegation/test_service.py
  • tests/unit/hr/performance/test_models.py
🧠 Learnings (30)
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/engine/**/*.py : Engine package (engine/): agent orchestration, parallel execution, task decomposition, routing, TaskEngine (centralized single-writer), task lifecycle/recovery/shutdown, workspace isolation, coordination (4 dispatchers: SAS/centralized/decentralized/context-dependent, wave execution), approval gates (escalation detection, context parking/resume), stagnation detection (ToolRepetitionDetector, corrective prompt injection), AgentRuntimeState (execution status), context budget management, conversation compaction (oldest-turns summarizer)

Applied to files:

  • src/synthorg/engine/agent_engine.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Prefer `asyncio.TaskGroup` for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare `create_task`. Existing code is being migrated incrementally.

Applied to files:

  • src/synthorg/engine/agent_engine.py
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to src/synthorg/providers/**/*.py : Providers: LLM provider abstraction (LiteLLM adapter), auth types (api_key/oauth/custom_header/none), presets (PROVIDER_PRESETS), runtime CRUD (ProviderManagementService with asyncio.Lock serialization), hot-reload via AppState swap.

Applied to files:

  • src/synthorg/engine/agent_engine.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/security/**/*.py : Security package (security/): SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered), risk classifier, risk tier classifier, action type registry, ToolInvoker security integration, progressive trust (4 strategies), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume)

Applied to files:

  • src/synthorg/engine/agent_engine.py
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST import: `from synthorg.observability import get_logger` and instantiate `logger = get_logger(__name__)`

Applied to files:

  • src/synthorg/engine/agent_engine.py
📚 Learning: 2026-03-16T19:13:36.562Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T19:13:36.562Z
Learning: Applies to src/synthorg/providers/**/*.py : RetryConfig and RateLimiterConfig are set per-provider in ProviderConfig.

Applied to files:

  • src/synthorg/engine/agent_engine.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic must import `from synthorg.observability import get_logger` and define `logger = get_logger(__name__)`

Applied to files:

  • src/synthorg/engine/agent_engine.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...

Applied to files:

  • src/synthorg/engine/agent_engine.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/hr/**/*.py : HR package (hr/): hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, LLM calibration, collaboration overrides, trend detection), promotion/demotion (criteria evaluation, approval strategies, model mapping)

Applied to files:

  • src/synthorg/hr/activity.py
  • tests/unit/hr/test_activity.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/communication/delegation/models.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state

Applied to files:

  • src/synthorg/communication/delegation/models.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : 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, serializing for persistence).

Applied to files:

  • src/synthorg/communication/delegation/models.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. For derived values use `computed_field` instead of storing + validating redundant fields. Use `NotBlankStr` (from `core.types`) for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • src/synthorg/communication/delegation/models.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 BaseModel, model_validator, computed_field, ConfigDict.

Applied to files:

  • src/synthorg/communication/delegation/models.py
📚 Learning: 2026-03-15T18:42:17.990Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:42:17.990Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`

Applied to files:

  • src/synthorg/communication/delegation/models.py
📚 Learning: 2026-03-16T23:05:29.577Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T23:05:29.577Z
Learning: Applies to **/*.py : 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, serializing for persistence).

Applied to files:

  • src/synthorg/communication/delegation/models.py
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to **/*.py : Config vs runtime state: use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/communication/delegation/models.py
📚 Learning: 2026-03-17T11:41:02.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T11:41:02.964Z
Learning: Applies to src/**/*.py : Models: Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`). Use `computed_field` for derived values instead of storing + validating redundant fields. Use `NotBlankStr` for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • src/synthorg/communication/delegation/models.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to **/*.py : Config vs runtime state: frozen Pydantic models for config/identity; separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/communication/delegation/models.py
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to tests/**/*.py : Never skip, dismiss, or ignore flaky tests. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic. For tasks blocking indefinitely, use `asyncio.Event().wait()` instead of `asyncio.sleep(large_number)`.

Applied to files:

  • tests/unit/api/controllers/test_activities.py
  • tests/unit/hr/performance/test_models.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to tests/**/*.py : NEVER skip, dismiss, or ignore flaky tests — always fix them fully and fundamentally. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins

Applied to files:

  • tests/unit/api/controllers/test_activities.py
  • tests/unit/hr/performance/test_models.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)

Applied to files:

  • tests/unit/hr/test_activity.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.

Applied to files:

  • tests/unit/hr/test_activity.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/hr/**/*.py : HR engine must provide: hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, trend detection), promotion/demotion

Applied to files:

  • tests/unit/hr/test_activity.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to tests/**/*.py : Prefer `pytest.mark.parametrize` for testing similar cases.

Applied to files:

  • tests/unit/communication/delegation/test_service.py
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to tests/**/*.py : Use `pytest.mark.parametrize` for testing similar cases

Applied to files:

  • tests/unit/communication/delegation/test_service.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to tests/**/*.py : Parametrize: Prefer pytest.mark.parametrize for testing similar cases.

Applied to files:

  • tests/unit/communication/delegation/test_service.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/communication/**/*.py : Communication package (communication/): message bus, dispatcher, messenger, channels, delegation, loop prevention, conflict resolution; meeting/ subpackage for meeting protocol (round-robin, position papers, structured phases), scheduler (frequency, participant resolver), orchestrator

Applied to files:

  • tests/unit/communication/delegation/test_service.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to tests/**/*.py : Test markers: `pytest.mark.unit`, `pytest.mark.integration`, `pytest.mark.e2e`, `pytest.mark.slow`. Coverage: 80% minimum. Async: `asyncio_mode = 'auto'` — no manual `pytest.mark.asyncio` needed. Timeout: 30 seconds per test. Parallelism: `pytest-xdist` via `-n auto` — ALWAYS include `-n auto` when running pytest, never run tests sequentially.

Applied to files:

  • tests/unit/hr/performance/test_models.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to tests/**/*.py : Test markers: pytest.mark.unit, pytest.mark.integration, pytest.mark.e2e, pytest.mark.slow. Coverage: 80% minimum (enforced in CI).

Applied to files:

  • tests/unit/hr/performance/test_models.py
🔇 Additional comments (1)
tests/unit/hr/performance/test_models.py (1)

119-162: Good temporal-ordering coverage.

The valid, invalid, and None cases pin the new started_at contract cleanly, and the invalid-ordering checks are nicely consolidated with parametrization.

"""

model_config = ConfigDict(frozen=True)
model_config = ConfigDict(frozen=True, allow_inf_nan=False)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
python -m pip install --quiet 'pydantic==2.12.5'
python - <<'PY'
from pydantic import BaseModel, ConfigDict, ValidationError

class Task(BaseModel):
    budget_limit: float

class Wrapper(BaseModel):
    model_config = ConfigDict(frozen=True, allow_inf_nan=False)
    task: Task

for value in (float("inf"), float("nan")):
    try:
        Wrapper.model_validate({"task": {"budget_limit": value}})
    except ValidationError as exc:
        print(f"REJECTED {value!r}: {exc.errors()[0]['msg']}")
    else:
        print(f"ACCEPTED {value!r}")
PY

Repository: Aureliolo/synthorg

Length of output: 87


allow_inf_nan=False on wrapper models doesn't validate nested Task floats.

Pydantic ConfigDict on a parent model doesn't propagate to nested BaseModel instances—each validates independently with its own config. Since Task has no allow_inf_nan=False, wrapper models here (DelegationRequest, DelegationResult, DelegationRecord) won't reject delegated tasks with budget_limit=inf / NaN.

To enforce this validation: either add allow_inf_nan=False to the Task model itself, or add an explicit field validator in the wrapper models. (Note: DelegationRecord has no float fields, so the config is inert there regardless.)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/communication/delegation/models.py` at line 22, The wrapper
models DelegationRequest, DelegationResult, and DelegationRecord currently set
model_config = ConfigDict(allow_inf_nan=False) but that does not propagate to
nested Task instances; update the Task model to include model_config =
ConfigDict(frozen=True, allow_inf_nan=False) so Task's float fields (e.g.,
budget_limit) reject inf/NaN, or alternatively add an explicit field validator
on the wrapper models (DelegationRequest/DelegationResult) that checks
task.budget_limit for isfinite and raises a ValueError; note DelegationRecord
has no float fields so its config has no effect.

Aureliolo and others added 8 commits March 26, 2026 15:35
…legation, cost_incurred)

Add 5 new activity event types to complete the 10-type spec from #772:
- task_started: derived from TaskMetricRecord.started_at field
- cost_incurred: from existing CostRecord via CostTracker
- tool_used: new ToolInvocationRecord model + ToolInvocationTracker store
- delegation_sent/received: new DelegationRecordStore with dual-perspective
  querying (delegator vs delegatee)

Each type has a converter function in hr/activity.py, merge integration
via keyword-only args (backward compatible), controller wiring with
graceful degradation, and comprehensive unit tests.

Closes #812

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Extract _record_invocation to invocation_bridge.py (invoker.py < 800 lines)
- Add ServiceUnavailableError re-raise to tool/delegation fetchers
- Fix _record_invocation log level: DEBUG -> WARNING with exc_info
- Add error handling around DelegationService record_sync call
- Add started_at < completed_at model_validator on TaskMetricRecord
- Add success/error_message cross-field validator on ToolInvocationRecord
- Use asyncio.TaskGroup for parallel async fetches in controller
- Update DelegationService docstring with record_store param
- Update docs/design/agents.md activity timeline description
- Update docs/design/operations.md route table (add /activities)
- Update CLAUDE.md tools/ package description
- Add missing test cases (failure w/o error_message, start==end boundary)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add ExceptionGroup handling around TaskGroup in activity controller
- Add has_cost_tracker guard for consistent graceful degradation
- Fix wrong event constants: DELEGATION_CREATED -> DELEGATION_RECORD_STORE_FAILED,
  TOOL_INVOKE_EXECUTION_ERROR -> TOOL_INVOCATION_RECORD_FAILED
- Replace type: ignore with assert for started_at precondition
- Fix docstrings: activity module, converter, validator, record_store, bridge
- Add allow_inf_nan=False to DelegationRecord (convention consistency)
- Document record_sync lock bypass in DelegationRecordStore
- Fix non-deterministic _NOW in controller tests with frozen datetime
- Add delegator/delegatee time range tests to record_store
- Add tests: invocation_bridge (7), invocation_record validator (6),
  TaskMetricRecord temporal validator (4), DelegationService record_store (3)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Avoids leaking internal paths, stack traces, or partial secrets
through the activity feed API response.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Without this wiring, invocation_tracker was always None at runtime
and tool invocations were never recorded. Also uses constructor
wiring in delegation service tests instead of private attr access.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Document tool_invocation_tracker in AgentEngine Args docstring
- Add RecursionError propagation test for delegation record store

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add allow_inf_nan=False to DelegationRequest and DelegationResult
- Move ToolInvocationTracker import to TYPE_CHECKING in AgentEngine
- Add delegated_task_id to delegation activity related_ids
- Add graceful degradation tests for broken tool/delegation trackers
- Parametrize fatal-error propagation tests (MemoryError + RecursionError)
- Parametrize temporal validation tests, move imports to module level

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add task_started controller test via TaskMetricRecord.started_at
- Document provider_registry in AgentEngine Args docstring

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
src/synthorg/communication/delegation/record_store.py (1)

24-38: ⚠️ Potential issue | 🟠 Major

This activity source is still process-local.

The backing list on Line 37 means delegation_sent / delegation_received disappear on restart and split across workers. That falls short of the persisted/queryable source the activity feed needs; this should be backed by shared durable storage instead of in-memory state.

Also applies to: 40-50

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/communication/delegation/record_store.py` around lines 24 - 38,
DelegationRecordStore currently keeps records in an in-memory list (_records) so
delegation_sent/delegation_received disappear on restart and are not shared
across workers; replace the in-memory backing with a durable, shared storage
backend (e.g., a database or persistent queue) and update DelegationRecordStore
methods (constructor __init__, record_sync, and the async query methods for
delegation_sent / delegation_received) to read/write via that backend instead of
list append/iteration; ensure record_sync becomes transactional or retries on
failure and the async readers acquire _lock only when needed but query the
shared store so records persist and are visible across processes.
src/synthorg/communication/delegation/service.py (1)

176-202: ⚠️ Potential issue | 🟠 Major

Don't mutate local delegation state before the store write can fail.

By Line 176 and Line 190, the guard and audit trail are already updated before Line 193 can re-raise. If record_sync() hits MemoryError / RecursionError, delegate() fails but retries are still dedupe-blocked and get_audit_trail() already exposes a delegation that never completed.

♻️ One way to avoid leaving half-committed state on fatal store errors
-        self._guard.record_delegation(
-            request.delegator_id,
-            request.delegatee_id,
-            request.task.id,
-        )
         record = DelegationRecord(
             delegation_id=str(uuid4()),
             delegator_id=request.delegator_id,
             delegatee_id=request.delegatee_id,
             original_task_id=request.task.id,
             delegated_task_id=sub_task.id,
             timestamp=datetime.now(UTC),
             refinement=request.refinement,
         )
-        self._audit_trail.append(record)
         if self._record_store is not None:
             try:
                 self._record_store.record_sync(record)
             except MemoryError, RecursionError:
                 raise
             except Exception:
                 logger.warning(
                     DELEGATION_RECORD_STORE_FAILED,
                     delegator=request.delegator_id,
                     delegatee=request.delegatee_id,
                     exc_info=True,
                 )
+        self._guard.record_delegation(
+            request.delegator_id,
+            request.delegatee_id,
+            request.task.id,
+        )
+        self._audit_trail.append(record)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/communication/delegation/service.py` around lines 176 - 202,
Build the DelegationRecord object first but defer mutating local state: if
self._record_store is not None, call self._record_store.record_sync(record)
inside the try/except and only on success call
self._guard.record_delegation(request.delegator_id, request.delegatee_id,
request.task.id) and self._audit_trail.append(record); re-raise MemoryError and
RecursionError as currently written and log other exceptions (logger.warning)
without mutating guard/audit state; if self._record_store is None keep the
original behavior (append and guard) so delegate() still records locally.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/synthorg/api/controllers/activities.py`:
- Around line 180-183: The except blocks that catch MemoryError, RecursionError
and ServiceUnavailableError should log the failure with source/fetcher context
before re-raising; update each catch (the blocks handling MemoryError,
RecursionError and ServiceUnavailableError in activities.py and the similar
blocks at the other locations) to call a structured logger (e.g., logger.error
or logger.warning) including the source/fetcher identifier and include exception
details (exc_info=True) immediately prior to re-raising, or implement a small
helper like log_and_reraise(exc, source_name, fetcher_name) and call that from
each except block to avoid duplication.
- Around line 267-297: The current single try block around both
store.get_records_as_delegator and store.get_records_as_delegatee discards a
successful result if the other call fails; split the two lookups so each is
attempted and handled independently: call store.get_records_as_delegator(...) in
its own try/except (preserving its successful result into the local variable
sent or setting sent = () on failure and logging/returning as the original
excepts dictate), then call store.get_records_as_delegatee(...) in a separate
try/except (preserving received or setting received = ()), keeping the same
exception types (MemoryError, RecursionError re-raises; ServiceUnavailableError
re-raises; other Exceptions log API_REQUEST_ERROR with the same fields and
exc_info=True). Ensure the org-wide branch remains unchanged and that the
function ultimately returns the pair (sent, received).

---

Duplicate comments:
In `@src/synthorg/communication/delegation/record_store.py`:
- Around line 24-38: DelegationRecordStore currently keeps records in an
in-memory list (_records) so delegation_sent/delegation_received disappear on
restart and are not shared across workers; replace the in-memory backing with a
durable, shared storage backend (e.g., a database or persistent queue) and
update DelegationRecordStore methods (constructor __init__, record_sync, and the
async query methods for delegation_sent / delegation_received) to read/write via
that backend instead of list append/iteration; ensure record_sync becomes
transactional or retries on failure and the async readers acquire _lock only
when needed but query the shared store so records persist and are visible across
processes.

In `@src/synthorg/communication/delegation/service.py`:
- Around line 176-202: Build the DelegationRecord object first but defer
mutating local state: if self._record_store is not None, call
self._record_store.record_sync(record) inside the try/except and only on success
call self._guard.record_delegation(request.delegator_id, request.delegatee_id,
request.task.id) and self._audit_trail.append(record); re-raise MemoryError and
RecursionError as currently written and log other exceptions (logger.warning)
without mutating guard/audit state; if self._record_store is None keep the
original behavior (append and guard) so delegate() still records locally.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a91b025a-11a0-4cb0-8040-43617bd4a417

📥 Commits

Reviewing files that changed from the base of the PR and between 2f9cc45 and 73c83c0.

📒 Files selected for processing (27)
  • CLAUDE.md
  • docs/design/agents.md
  • docs/design/operations.md
  • src/synthorg/api/app.py
  • src/synthorg/api/controllers/activities.py
  • src/synthorg/api/state.py
  • src/synthorg/communication/delegation/models.py
  • src/synthorg/communication/delegation/record_store.py
  • src/synthorg/communication/delegation/service.py
  • src/synthorg/engine/agent_engine.py
  • src/synthorg/hr/activity.py
  • src/synthorg/hr/performance/models.py
  • src/synthorg/observability/events/delegation.py
  • src/synthorg/observability/events/tool.py
  • src/synthorg/tools/invocation_bridge.py
  • src/synthorg/tools/invocation_record.py
  • src/synthorg/tools/invocation_tracker.py
  • src/synthorg/tools/invoker.py
  • tests/unit/api/conftest.py
  • tests/unit/api/controllers/test_activities.py
  • tests/unit/communication/delegation/test_record_store.py
  • tests/unit/communication/delegation/test_service.py
  • tests/unit/hr/performance/test_models.py
  • tests/unit/hr/test_activity.py
  • tests/unit/tools/test_invocation_bridge.py
  • tests/unit/tools/test_invocation_record.py
  • tests/unit/tools/test_invocation_tracker.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Backend
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (6)
docs/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Documentation uses Markdown and is built with Zensical (config: mkdocs.yml). API reference auto-generated from OpenAPI schema via scripts/export_openapi.py. Library reference auto-generated via mkdocstrings + Griffe.

Files:

  • docs/design/agents.md
  • docs/design/operations.md
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Do NOT use from __future__ import annotations - Python 3.14 has PEP 649 with native lazy annotations
Use except A, B: syntax (no parentheses) per PEP 758 - ruff enforces this on Python 3.14

Files:

  • src/synthorg/tools/invoker.py
  • src/synthorg/api/app.py
  • src/synthorg/engine/agent_engine.py
  • tests/unit/hr/performance/test_models.py
  • src/synthorg/communication/delegation/service.py
  • src/synthorg/hr/performance/models.py
  • tests/unit/tools/test_invocation_record.py
  • tests/unit/communication/delegation/test_service.py
  • tests/unit/tools/test_invocation_tracker.py
  • tests/unit/api/conftest.py
  • src/synthorg/communication/delegation/models.py
  • tests/unit/api/controllers/test_activities.py
  • src/synthorg/observability/events/delegation.py
  • src/synthorg/tools/invocation_bridge.py
  • src/synthorg/tools/invocation_tracker.py
  • tests/unit/tools/test_invocation_bridge.py
  • src/synthorg/api/controllers/activities.py
  • src/synthorg/hr/activity.py
  • src/synthorg/observability/events/tool.py
  • tests/unit/communication/delegation/test_record_store.py
  • src/synthorg/tools/invocation_record.py
  • src/synthorg/communication/delegation/record_store.py
  • src/synthorg/api/state.py
  • tests/unit/hr/test_activity.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.py: All public functions must have type hints. Type checking enforced by mypy in strict mode.
Google-style docstrings required on all public classes and functions, enforced by ruff D rules
Use Pydantic v2 conventions: BaseModel, model_validator, computed_field, ConfigDict. Use @computed_field for derived values instead of storing redundant fields. Use NotBlankStr for all identifier/name fields.
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g., multiple tool invocations, parallel agent calls) over bare create_task
Line length must be 88 characters (enforced by ruff)
Functions must be less than 50 lines, files less than 800 lines
Handle errors explicitly, never silently swallow them. All error paths must log at WARNING or ERROR with context before raising.
Never use import logging / logging.getLogger() / print() in application code (except observability/setup.py and observability/sinks.py which may use stdlib logging and stderr print for bootstrap)
Always use logger as the variable name (not _logger, not log)
Always use event name constants from domain-specific modules under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT
Use structured logging: always logger.info(EVENT, key=value) - never use printf-style formatting like logger.info("msg %s", val)
All state transitions must log at INFO level
DEBUG logging is appropriate for object creation, internal flow, and entry/exit of key functions
Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples. Use generic names: example-provider, example-large-001, example-medium-001, etc. Vendor names only in: (1) Operations design page, (2) .claude/ skill files, (3) third-party import paths.

Files:

  • src/synthorg/tools/invoker.py
  • src/synthorg/api/app.py
  • src/synthorg/engine/agent_engine.py
  • src/synthorg/communication/delegation/service.py
  • src/synthorg/hr/performance/models.py
  • src/synthorg/communication/delegation/models.py
  • src/synthorg/observability/events/delegation.py
  • src/synthorg/tools/invocation_bridge.py
  • src/synthorg/tools/invocation_tracker.py
  • src/synthorg/api/controllers/activities.py
  • src/synthorg/hr/activity.py
  • src/synthorg/observability/events/tool.py
  • src/synthorg/tools/invocation_record.py
  • src/synthorg/communication/delegation/record_store.py
  • src/synthorg/api/state.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Every module with business logic MUST import: from synthorg.observability import get_logger and instantiate logger = get_logger(__name__)

Files:

  • src/synthorg/tools/invoker.py
  • src/synthorg/api/app.py
  • src/synthorg/engine/agent_engine.py
  • src/synthorg/communication/delegation/service.py
  • src/synthorg/hr/performance/models.py
  • src/synthorg/communication/delegation/models.py
  • src/synthorg/observability/events/delegation.py
  • src/synthorg/tools/invocation_bridge.py
  • src/synthorg/tools/invocation_tracker.py
  • src/synthorg/api/controllers/activities.py
  • src/synthorg/hr/activity.py
  • src/synthorg/observability/events/tool.py
  • src/synthorg/tools/invocation_record.py
  • src/synthorg/communication/delegation/record_store.py
  • src/synthorg/api/state.py
src/synthorg/api/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/api/**/*.py: REST API defined with Litestar. Errors must use RFC 9457 format in src/synthorg/api/
Litestar API must include setup wizard, auth/, auto-wiring, and lifecycle management

Files:

  • src/synthorg/api/app.py
  • src/synthorg/api/controllers/activities.py
  • src/synthorg/api/state.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Tests must use test-provider, test-small-001, etc. instead of real vendor names
Use @pytest.mark.parametrize for testing similar cases
Mark all tests with appropriate pytest markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow
Use Hypothesis for property-based testing in Python with @given and @settings decorators. Run dev profile with 1000 examples via HYPOTHESIS_PROFILE=dev environment variable.
Never skip, dismiss, or ignore flaky tests. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() to make them deterministic. For tasks blocking indefinitely, use asyncio.Event().wait() instead of asyncio.sleep(large_number).

Files:

  • tests/unit/hr/performance/test_models.py
  • tests/unit/tools/test_invocation_record.py
  • tests/unit/communication/delegation/test_service.py
  • tests/unit/tools/test_invocation_tracker.py
  • tests/unit/api/conftest.py
  • tests/unit/api/controllers/test_activities.py
  • tests/unit/tools/test_invocation_bridge.py
  • tests/unit/communication/delegation/test_record_store.py
  • tests/unit/hr/test_activity.py
🧠 Learnings (66)
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...

Applied to files:

  • CLAUDE.md
  • src/synthorg/api/app.py
  • src/synthorg/engine/agent_engine.py
  • tests/unit/api/conftest.py
  • src/synthorg/api/controllers/activities.py
  • src/synthorg/api/state.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Settings: Runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces), Fernet encryption for sensitive values, config bridge, ConfigResolver (typed composed reads for controllers), validation, registry, change notifications via message bus. Per-namespace setting definitions in definitions/ submodule (api, company, providers, memory, budget, security, coordination, observability, backup).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Documentation source in `docs/` (Markdown, built with Zensical). Design spec in `docs/design/` (7 pages: index, agents, organization, communication, engine, memory, operations). Architecture in `docs/architecture/` (overview, tech-stack, decision log). Roadmap in `docs/roadmap/`. Security in `docs/security.md`. Licensing in `docs/licensing.md`. Reference in `docs/reference/`. REST API reference in `docs/rest-api.md`. Library reference in `docs/api/` (auto-generated from docstrings). Custom templates in `docs/overrides/`. Config in `mkdocs.yml`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Documentation structure: `docs/design/` (10 pages), `docs/architecture/`, `docs/roadmap/`, `docs/security.md`, `docs/licensing.md`, `docs/reference/`, `docs/rest-api.md` + generated API reference

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Security: SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered), risk classifier, risk tier classifier, action type registry, ToolInvoker security integration, progressive trust (4 strategies: disabled/weighted/per-category/milestone), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to docs/** : Docs source in docs/ (Markdown, built with Zensical); design spec in docs/design/ (7 pages: index, agents, organization, communication, engine, memory, operations)

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/security/**/*.py : Security package (security/): SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered), risk classifier, risk tier classifier, action type registry, ToolInvoker security integration, progressive trust (4 strategies), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume)

Applied to files:

  • CLAUDE.md
  • src/synthorg/tools/invoker.py
  • src/synthorg/engine/agent_engine.py
  • src/synthorg/api/state.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/settings/**/*.py : Settings package (settings/): runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces), Fernet encryption for sensitive values, config bridge (JSON serialization for Pydantic/collections), ConfigResolver (typed accessors), validation, registry, change notifications via message bus, SettingsSubscriber protocol, SettingsChangeDispatcher (polls `#settings` channel, routes to subscribers, restart_required filtering)

Applied to files:

  • CLAUDE.md
  • src/synthorg/api/app.py
📚 Learning: 2026-03-15T21:20:09.993Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:20:09.993Z
Learning: Applies to web/src/components/** : Vue components organized by feature (agents/, approvals/, budget/, common/, dashboard/, layout/, messages/, org-chart/, tasks/).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to web/src/**/*.{ts,tsx} : React 19 + shadcn/ui + Tailwind CSS for web dashboard. TypeScript required in `web/src/`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Engine: Agent orchestration, execution loops, parallel execution, task decomposition, routing, task assignment, centralized single-writer task state engine (TaskEngine), task lifecycle, recovery, shutdown, workspace isolation, coordination (multi-agent pipeline: TopologyDispatcher protocol, 4 dispatchers — SAS/centralized/decentralized/context-dependent, wave execution, workspace lifecycle integration, CoordinationSectionConfig company config bridge, build_coordinator factory), coordination error classification, prompt policy validation, checkpoint recovery (checkpoint/, per-turn persistence, heartbeat detection, CheckpointRecoveryStrategy), approval gate (escalation detection, context parking/resume, EscalationInfo/ResumePayload models), stagnation detection (stagnation/, StagnationDetector protocol, ToolRepetitionDetector, dual-signal analysis, corrective prompt injection), agent runtime state (AgentRuntimeState, lightweight per-agent execution status for dashboard queries and recove...

Applied to files:

  • docs/design/agents.md
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/hr/**/*.py : HR package (hr/): hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, LLM calibration, collaboration overrides, trend detection), promotion/demotion (criteria evaluation, approval strategies, model mapping)

Applied to files:

  • docs/design/agents.md
  • tests/unit/api/conftest.py
  • src/synthorg/api/controllers/activities.py
  • tests/unit/hr/test_activity.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/security/**/*.py : Security module includes SecOps agent, rule engine (soft-allow/hard-deny), audit log, output scanner, risk classifier, autonomy levels (4 strategies), timeout policies.

Applied to files:

  • src/synthorg/tools/invoker.py
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to src/synthorg/api/**/*.py : Litestar API must include setup wizard, auth/, auto-wiring, and lifecycle management

Applied to files:

  • src/synthorg/api/app.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/communication/**/*.py : Communication package (communication/): message bus, dispatcher, messenger, channels, delegation, loop prevention, conflict resolution; meeting/ subpackage for meeting protocol (round-robin, position papers, structured phases), scheduler (frequency, participant resolver), orchestrator

Applied to files:

  • src/synthorg/api/app.py
  • tests/unit/communication/delegation/test_service.py
  • src/synthorg/api/state.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/backup/**/*.py : Backup package (backup/): scheduled/manual/lifecycle backups of persistence DB, agent memory, company config. BackupService orchestrator, BackupScheduler (periodic asyncio task), RetentionManager (count + age pruning), tar.gz compression, SHA-256 checksums, manifest tracking, validated restore with atomic rollback and safety backup. handlers/ subpackage: ComponentHandler protocol + concrete handlers (PersistenceComponentHandler, MemoryComponentHandler, ConfigComponentHandler)

Applied to files:

  • src/synthorg/api/app.py
  • src/synthorg/api/state.py
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to src/synthorg/providers/**/*.py : Providers: LLM provider abstraction (LiteLLM adapter), auth types (api_key/oauth/custom_header/none), presets (PROVIDER_PRESETS), runtime CRUD (ProviderManagementService with asyncio.Lock serialization), hot-reload via AppState swap.

Applied to files:

  • src/synthorg/api/app.py
  • src/synthorg/engine/agent_engine.py
  • tests/unit/api/conftest.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/engine/**/*.py : Engine package (engine/): agent orchestration, parallel execution, task decomposition, routing, TaskEngine (centralized single-writer), task lifecycle/recovery/shutdown, workspace isolation, coordination (4 dispatchers: SAS/centralized/decentralized/context-dependent, wave execution), approval gates (escalation detection, context parking/resume), stagnation detection (ToolRepetitionDetector, corrective prompt injection), AgentRuntimeState (execution status), context budget management, conversation compaction (oldest-turns summarizer)

Applied to files:

  • src/synthorg/engine/agent_engine.py
  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Prefer `asyncio.TaskGroup` for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare `create_task`. Existing code is being migrated incrementally.

Applied to files:

  • src/synthorg/engine/agent_engine.py
  • src/synthorg/tools/invocation_bridge.py
  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST import: `from synthorg.observability import get_logger` and instantiate `logger = get_logger(__name__)`

Applied to files:

  • src/synthorg/engine/agent_engine.py
  • src/synthorg/api/state.py
📚 Learning: 2026-03-16T19:13:36.562Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T19:13:36.562Z
Learning: Applies to src/synthorg/providers/**/*.py : RetryConfig and RateLimiterConfig are set per-provider in ProviderConfig.

Applied to files:

  • src/synthorg/engine/agent_engine.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic must import `from synthorg.observability import get_logger` and define `logger = get_logger(__name__)`

Applied to files:

  • src/synthorg/engine/agent_engine.py
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic must import logger via `from synthorg.observability import get_logger` and initialize with `logger = get_logger(__name__)`

Applied to files:

  • src/synthorg/engine/agent_engine.py
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to tests/**/*.py : Never skip, dismiss, or ignore flaky tests. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic. For tasks blocking indefinitely, use `asyncio.Event().wait()` instead of `asyncio.sleep(large_number)`.

Applied to files:

  • tests/unit/hr/performance/test_models.py
  • tests/unit/api/controllers/test_activities.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to tests/**/*.py : NEVER skip, dismiss, or ignore flaky tests — always fix them fully and fundamentally. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins

Applied to files:

  • tests/unit/hr/performance/test_models.py
  • tests/unit/api/controllers/test_activities.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to tests/**/*.py : Test markers: `pytest.mark.unit`, `pytest.mark.integration`, `pytest.mark.e2e`, `pytest.mark.slow`. Coverage: 80% minimum. Async: `asyncio_mode = 'auto'` — no manual `pytest.mark.asyncio` needed. Timeout: 30 seconds per test. Parallelism: `pytest-xdist` via `-n auto` — ALWAYS include `-n auto` when running pytest, never run tests sequentially.

Applied to files:

  • tests/unit/hr/performance/test_models.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to tests/**/*.py : Test markers: pytest.mark.unit, pytest.mark.integration, pytest.mark.e2e, pytest.mark.slow. Coverage: 80% minimum (enforced in CI).

Applied to files:

  • tests/unit/hr/performance/test_models.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (no parentheses) for exception handling — PEP 758 exception syntax enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/communication/delegation/service.py
  • src/synthorg/tools/invocation_bridge.py
  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Handle errors explicitly; never silently swallow exceptions

Applied to files:

  • src/synthorg/communication/delegation/service.py
  • src/synthorg/tools/invocation_bridge.py
  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Use PEP 758 except syntax with `except A, B:` (no parentheses) for multiple exceptions—ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/communication/delegation/service.py
  • src/synthorg/tools/invocation_bridge.py
  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Handle errors explicitly, never silently swallow exceptions

Applied to files:

  • src/synthorg/communication/delegation/service.py
  • src/synthorg/tools/invocation_bridge.py
  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Handle errors explicitly—never silently swallow exceptions.

Applied to files:

  • src/synthorg/communication/delegation/service.py
  • src/synthorg/tools/invocation_bridge.py
  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: `except A, B:` (no parentheses) — enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/communication/delegation/service.py
  • src/synthorg/tools/invocation_bridge.py
  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (no parentheses) per PEP 758 - ruff enforces this on Python 3.14

Applied to files:

  • src/synthorg/communication/delegation/service.py
  • src/synthorg/tools/invocation_bridge.py
  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-15T16:55:07.730Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T16:55:07.730Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: use `except A, B:` (no parentheses) — ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/communication/delegation/service.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions.

Applied to files:

  • src/synthorg/communication/delegation/service.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, and key function entry/exit

Applied to files:

  • src/synthorg/communication/delegation/service.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. For derived values use `computed_field` instead of storing + validating redundant fields. Use `NotBlankStr` (from `core.types`) for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • src/synthorg/hr/performance/models.py
  • src/synthorg/communication/delegation/models.py
  • src/synthorg/tools/invocation_record.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to tests/**/*.py : Prefer `pytest.mark.parametrize` for testing similar cases.

Applied to files:

  • tests/unit/communication/delegation/test_service.py
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to tests/**/*.py : Use `pytest.mark.parametrize` for testing similar cases

Applied to files:

  • tests/unit/communication/delegation/test_service.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to tests/**/*.py : Parametrize: Prefer pytest.mark.parametrize for testing similar cases.

Applied to files:

  • tests/unit/communication/delegation/test_service.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)

Applied to files:

  • tests/unit/api/conftest.py
  • src/synthorg/api/controllers/activities.py
  • tests/unit/hr/test_activity.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/communication/delegation/models.py
  • src/synthorg/tools/invocation_record.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state

Applied to files:

  • src/synthorg/communication/delegation/models.py
  • src/synthorg/tools/invocation_record.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : 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, serializing for persistence).

Applied to files:

  • src/synthorg/communication/delegation/models.py
  • src/synthorg/tools/invocation_record.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 BaseModel, model_validator, computed_field, ConfigDict.

Applied to files:

  • src/synthorg/communication/delegation/models.py
  • src/synthorg/tools/invocation_record.py
📚 Learning: 2026-03-15T18:42:17.990Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:42:17.990Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`

Applied to files:

  • src/synthorg/communication/delegation/models.py
  • src/synthorg/tools/invocation_record.py
📚 Learning: 2026-03-16T23:05:29.577Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T23:05:29.577Z
Learning: Applies to **/*.py : 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, serializing for persistence).

Applied to files:

  • src/synthorg/communication/delegation/models.py
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to **/*.py : Config vs runtime state: use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/communication/delegation/models.py
📚 Learning: 2026-03-17T11:41:02.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T11:41:02.964Z
Learning: Applies to src/**/*.py : Models: Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`). Use `computed_field` for derived values instead of storing + validating redundant fields. Use `NotBlankStr` for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • src/synthorg/communication/delegation/models.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to **/*.py : Config vs runtime state: frozen Pydantic models for config/identity; separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/communication/delegation/models.py
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to src/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. Use `computed_field` for derived values instead of storing redundant fields. Use `NotBlankStr` for all identifier/name fields.

Applied to files:

  • src/synthorg/communication/delegation/models.py
📚 Learning: 2026-03-16T20:14:00.937Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:14:00.937Z
Learning: Applies to **/*.py : Validate: at system boundaries (user input, external APIs, config files).

Applied to files:

  • src/synthorg/communication/delegation/models.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from domain-specific modules under synthorg.observability.events (e.g., PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/observability/events/delegation.py
  • src/synthorg/tools/invocation_bridge.py
  • src/synthorg/observability/events/tool.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from synthorg.observability.events domain-specific modules (e.g., PROVIDER_CALL_START from events.provider). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.

Applied to files:

  • src/synthorg/observability/events/delegation.py
  • src/synthorg/observability/events/tool.py
📚 Learning: 2026-03-18T21:23:23.586Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T21:23:23.586Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from the domain-specific module under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly from synthorg.observability.events.<domain>.

Applied to files:

  • src/synthorg/observability/events/delegation.py
  • src/synthorg/tools/invocation_bridge.py
  • src/synthorg/observability/events/tool.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability package (observability/): structured logging, correlation tracking, log sinks; event constants organized by domain under observability/events/ (e.g., events.api, events.tool, events.git, events.context_budget, events.backup)

Applied to files:

  • src/synthorg/observability/events/delegation.py
  • src/synthorg/observability/events/tool.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`); import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`

Applied to files:

  • src/synthorg/observability/events/delegation.py
  • src/synthorg/tools/invocation_bridge.py
  • src/synthorg/observability/events/tool.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from the domain-specific module under `synthorg.observability.events` in logging calls

Applied to files:

  • src/synthorg/observability/events/delegation.py
  • src/synthorg/tools/invocation_bridge.py
  • src/synthorg/observability/events/tool.py
📚 Learning: 2026-03-17T18:52:05.142Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T18:52:05.142Z
Learning: Applies to **/*.py : Async concurrency: prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare create_task.

Applied to files:

  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to src/**/*.py : Prefer `asyncio.TaskGroup` for fan-out/fan-in parallel operations in new code (e.g., multiple tool invocations, parallel agent calls) over bare `create_task`

Applied to files:

  • src/synthorg/api/controllers/activities.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.

Applied to files:

  • src/synthorg/api/controllers/activities.py
  • tests/unit/hr/test_activity.py
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to src/**/*.py : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`

Applied to files:

  • src/synthorg/observability/events/tool.py
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Use event constants from `synthorg.observability.events.<domain>` (e.g., `API_REQUEST_STARTED` from `events.api`); import directly and log with structured kwargs: `logger.info(EVENT, key=value)`, never interpolated strings

Applied to files:

  • src/synthorg/observability/events/tool.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : Use event name constants from domain-specific modules under `ai_company.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`). Import directly: `from ai_company.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/observability/events/tool.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/hr/**/*.py : HR engine must provide: hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, trend detection), promotion/demotion

Applied to files:

  • tests/unit/hr/test_activity.py
🔇 Additional comments (24)
CLAUDE.md (1)

122-122: LGTM! Documentation accurately reflects the new capability.

The addition of "invocation tracking" to the tools package description correctly documents the new ToolInvocationTracker, ToolInvocationRecord, and related infrastructure introduced in this PR.

src/synthorg/communication/delegation/models.py (1)

22-22: Verify the non-finite guard on nested Task models.

allow_inf_nan=False is model-local in Pydantic, so the new configs on Line 22 and Line 59 only harden floats declared on DelegationRequest / DelegationResult themselves. If synthorg.core.task.Task still allows non-finite floats, task and delegated_task remain open; Line 112 adds no effective validation today because DelegationRecord has no float fields.

#!/bin/bash
set -euo pipefail

TASK_FILE="$(fd '^task\.py$' src | head -n1)"
echo "Inspecting $TASK_FILE"
rg -n -C2 'class Task|model_config|budget_limit' "$TASK_FILE"

python -m pip install --quiet 'pydantic==2.12.5'
python - <<'PY'
from pydantic import BaseModel, ConfigDict, ValidationError

class Task(BaseModel):
    budget_limit: float

class Wrapper(BaseModel):
    model_config = ConfigDict(frozen=True, allow_inf_nan=False)
    task: Task

for value in (float("inf"), float("nan")):
    try:
        Wrapper.model_validate({"task": {"budget_limit": value}})
    except ValidationError as exc:
        print("REJECTED", value, exc.errors()[0]["msg"])
    else:
        print("ACCEPTED", value)
PY

Also applies to: 59-59, 112-112

tests/unit/communication/delegation/test_service.py (1)

448-517: LGTM! Well-structured record-store integration tests.

The new TestDelegationServiceRecordStore class properly exercises the constructor wiring (addressing past review feedback), uses parametrization for the fatal error cases (MemoryError, RecursionError), and follows the test guidelines. The graceful degradation test for RuntimeError correctly verifies that delegation succeeds despite storage failures.

docs/design/agents.md (1)

308-308: LGTM! Documentation accurately reflects expanded activity timeline.

The updated description correctly lists all the activity sources now merged into the timeline: lifecycle events, task metrics, cost records, tool invocations, and delegation records.

src/synthorg/engine/agent_engine.py (1)

131-131: LGTM! Clean dependency injection for tool invocation tracking.

The tool_invocation_tracker parameter is properly:

  • Imported in the TYPE_CHECKING block (addressing past review feedback)
  • Documented in the Args docstring (addressing past review feedback)
  • Stored on self._tool_invocation_tracker
  • Passed through to ToolInvoker in _make_tool_invoker

Also applies to: 200-204, 232-232, 298-298, 1196-1196

src/synthorg/tools/invoker.py (1)

408-410: LGTM! Clean integration of tool invocation recording.

The recording is correctly placed after the result is built but before returning, ensuring all tool invocations (successful or error) are captured. The record_tool_invocation bridge handles errors gracefully (best-effort recording), so this won't disrupt tool execution.

src/synthorg/api/app.py (1)

429-430: LGTM! Proper dependency injection for new activity sources.

The new tool_invocation_tracker and delegation_record_store parameters follow the established pattern: optional with None defaults for testing flexibility, documented in the docstring, and correctly passed to AppState.

Also applies to: 564-565

tests/unit/hr/performance/test_models.py (1)

119-162: LGTM! Comprehensive temporal validation tests.

The started_at field tests properly cover:

  • Valid case: started_at before completed_at (line 119-134)
  • Invalid cases: parametrized test for started_at after and equal to completed_at (lines 136-158)
  • None allowed: confirms optional nature (lines 160-162)

Past review feedback about module-level imports and parametrization has been addressed.

src/synthorg/hr/performance/models.py (1)

53-56: LGTM! Well-implemented temporal validation for started_at.

The implementation correctly:

  • Makes started_at optional with None default for backward compatibility
  • Uses Pydantic's @model_validator(mode="after") for cross-field validation
  • Enforces strict ordering (< not <=) via the >= rejection condition
  • Provides informative error messages with ISO timestamps for debugging

Also applies to: 74-83

src/synthorg/observability/events/tool.py (1)

58-62: LGTM! Properly organized event constants for invocation tracking.

The new constants follow the established naming conventions (TOOL_* prefix, tool.invocation.* dotted values) and are correctly typed with Final[str]. The section comment keeps the file well-organized.

src/synthorg/tools/invocation_bridge.py (1)

21-25: Failed tool attempts still bypass recording.

src/synthorg/tools/invoker.py:354-413 only calls record_tool_invocation() on the post-_build_result() path. Lookup, permission, validation, security, execution, and parking failures still return before this helper runs, so many failed tool calls never produce tool_used events. Please move the recording into a single finally in _invoke_single() or record on each early-return path.

src/synthorg/tools/invocation_tracker.py (1)

24-33: Persist tool_used records outside process memory.

This tracker is still backed only by the in-process _records list, so restarts and multi-worker deployments will lose or fragment tool activity. That means the new tool_used feed is not actually durable/queryable at the system level yet; please back it with a shared store or repository before shipping.

src/synthorg/api/state.py (4)

15-17: LGTM - New imports align with feature requirements.

The imports for DelegationRecordStore and ToolInvocationTracker are correctly placed and use # noqa: TC001 consistently with other type-only imports in this file.

Also applies to: 42-42


73-73: LGTM - Service wiring follows established patterns.

The new _delegation_record_store and _tool_invocation_tracker slots, constructor parameters, and assignments follow the exact pattern used for other optional services. The constructor's keyword-only args with None defaults enable graceful degradation.

Also applies to: 87-87, 113-114, 135-136


193-196: LGTM - Adds missing has_cost_tracker for consistency.

This completes the has_* / accessor pair pattern for cost_tracker, which was previously missing the presence check. Controllers can now check availability before accessing.


376-400: LGTM - New service accessors follow established conventions.

The has_tool_invocation_tracker / tool_invocation_tracker and has_delegation_record_store / delegation_record_store property pairs:

  • Follow the existing has_* + accessor pattern
  • Use _require_service for consistent 503 behavior with logging
  • Have proper docstrings consistent with the class style
tests/unit/hr/test_activity.py (8)

7-8: LGTM - New imports for test data model dependencies.

Imports for CostRecord, DelegationRecord, and ToolInvocationRecord are correctly added to support the new test helper factories.

Also applies to: 14-14


40-62: LGTM - Updated helper supports started_at for task_started tests.

The addition of the optional started_at parameter with None default maintains backward compatibility while enabling tests for the new task_started event generation.


65-125: LGTM - Well-structured test helper factories.

The new helpers _make_cost_record, _make_tool_invocation, and _make_delegation_record:

  • Follow the existing factory pattern with sensible defaults
  • Use guideline-compliant test vendor names (test-provider, test-medium-001)
  • Have consistent parameter ordering and typing

243-250: LGTM - Important backward compatibility test.

This test validates that callers using positional-only arguments continue to work after the signature changes to support keyword-only new parameters.


256-319: LGTM - Comprehensive task_started event coverage.

The test class covers:

  • Event generation when started_at is present
  • No event when started_at is None
  • Both events from same record with correct timestamps
  • Merge ordering with lifecycle events

325-372: LGTM - cost_incurred tests validate description format and field population.

Good coverage of the description format ("test-medium-001", "500+100 tokens", "0.0025 USD") and merge behavior with empty inputs.


378-471: LGTM - Thorough tool_used tests with security consideration.

The test suite covers success/failure states and importantly verifies that error messages are not leaked in descriptions (line 421: assert "Permission denied" not in evt.description). The task_id=None edge case is also properly tested.


477-576: LGTM - Delegation and all-types merge tests.

The delegation tests correctly validate:

  • Dual perspective (delegation_sent vs delegation_received) from the same record
  • Proper agent_id assignment (delegator for sent, delegatee for received)
  • related_ids population

The test_merge_all_event_types is an excellent integration test verifying all 7 events merge with correct descending timestamp order.

Comment on lines +180 to +183
except MemoryError, RecursionError:
raise
except ServiceUnavailableError:
raise
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Log these source-specific failures before re-raising them.

These fatal/service-unavailable branches bubble out without a warning/error log that identifies which fetcher failed, so the request loses source context right where it is known. Please emit a structured log before each re-raise, or centralize that behavior in a helper. As per coding guidelines "Handle errors explicitly, never silently swallow them. All error paths must log at WARNING or ERROR with context before raising."

Also applies to: 209-212, 238-241, 286-289

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/api/controllers/activities.py` around lines 180 - 183, The
except blocks that catch MemoryError, RecursionError and ServiceUnavailableError
should log the failure with source/fetcher context before re-raising; update
each catch (the blocks handling MemoryError, RecursionError and
ServiceUnavailableError in activities.py and the similar blocks at the other
locations) to call a structured logger (e.g., logger.error or logger.warning)
including the source/fetcher identifier and include exception details
(exc_info=True) immediately prior to re-raising, or implement a small helper
like log_and_reraise(exc, source_name, fetcher_name) and call that from each
except block to avoid duplication.

Fetch delegator and delegatee records independently so a failure
in one does not discard the successful result of the other.
Extracted _safe_delegation_query helper to reduce complexity.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 26, 2026 15:09 — with GitHub Actions Inactive
@Aureliolo Aureliolo merged commit 4252fac into main Mar 26, 2026
30 of 31 checks passed
@Aureliolo Aureliolo deleted the feat/activity-event-types branch March 26, 2026 15:14
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 26, 2026 15:14 — with GitHub Actions Inactive
Aureliolo added a commit that referenced this pull request Mar 30, 2026
🤖 I have created a release *beep* *boop*
---
#MAJOR CHANGES; We got a somewhat working webui :)

##
[0.5.0](v0.4.9...v0.5.0)
(2026-03-30)


### Features

* add analytics trends and budget forecast API endpoints
([#798](#798))
([16b61f5](16b61f5))
* add department policies to default templates
([#852](#852))
([7a41548](7a41548))
* add remaining activity event types (task_started, tool_used,
delegation, cost_incurred)
([#832](#832))
([4252fac](4252fac))
* agent performance, activity, and history API endpoints
([#811](#811))
([9b75c1d](9b75c1d))
* Agent Profiles and Detail pages (biography, career, performance)
([#874](#874))
([62d7880](62d7880))
* app shell, Storybook, and CI/CD pipeline
([#819](#819))
([d4dde90](d4dde90))
* Approvals page with risk grouping, urgency indicators, batch actions
([#889](#889))
([4e9673d](4e9673d))
* Budget Panel page (P&L dashboard, breakdown charts, forecast)
([#890](#890))
([b63b0f1](b63b0f1))
* build infrastructure layer (API client, auth, WebSocket)
([#815](#815))
([9f01d3e](9f01d3e))
* CLI global options infrastructure, UI modes, exit codes, env vars
([#891](#891))
([fef4fc5](fef4fc5))
* CodeMirror editor and theme preferences toggle
([#905](#905),
[#807](#807))
([#909](#909))
([41fbedc](41fbedc))
* Company page (department/agent management)
([#888](#888))
([cfb88b0](cfb88b0))
* comprehensive hint coverage across all CLI commands
([#900](#900))
([937974e](937974e))
* config system extensions, per-command flags for
init/start/stop/status/logs
([#895](#895))
([32f83fe](32f83fe))
* configurable currency system replacing hardcoded USD
([#854](#854))
([b372551](b372551))
* Dashboard page (metric cards, activity feed, budget burn)
([#861](#861))
([7d519d5](7d519d5))
* department health, provider status, and activity feed endpoints
([#818](#818))
([6d5f196](6d5f196))
* design tokens and core UI components
([#833](#833))
([ed887f2](ed887f2))
* extend approval, meeting, and budget API responses
([#834](#834))
([31472bf](31472bf))
* frontend polish -- real-time UX, accessibility, responsive,
performance ([#790](#790),
[#792](#792),
[#791](#791),
[#793](#793))
([#917](#917))
([f04a537](f04a537))
* implement human roles and access control levels
([#856](#856))
([d6d8a06](d6d8a06))
* implement semantic conflict detection in workspace merge
([#860](#860))
([d97283b](d97283b))
* interaction components and animation patterns
([#853](#853))
([82d4b01](82d4b01))
* Login page + first-run bootstrap + Company page
([#789](#789),
[#888](#888))
([#896](#896))
([8758e8d](8758e8d))
* Meetings page with timeline viz, token bars, contribution formatting
([#788](#788))
([#904](#904))
([b207f46](b207f46))
* Messages page with threading, channel badges, sender indicators
([#787](#787))
([#903](#903))
([28293ad](28293ad))
* Org Chart force-directed view and drag-drop reassignment
([#872](#872),
[#873](#873))
([#912](#912))
([a68a938](a68a938))
* Org Chart page (living nodes, status, CRUD, department health)
([#870](#870))
([0acbdae](0acbdae))
* per-command flags for remaining commands, auto-behavior wiring,
help/discoverability
([#897](#897))
([3f7afa2](3f7afa2))
* Providers page with backend rework -- health, CRUD, subscription auth
([#893](#893))
([9f8dd98](9f8dd98))
* scaffold React + Vite + TypeScript + Tailwind project
([#799](#799))
([bd151aa](bd151aa))
* Settings page with search, dependency indicators, grouped rendering
([#784](#784))
([#902](#902))
([a7b9870](a7b9870))
* Setup Wizard rebuild with template comparison, cost estimator, theme
customization ([#879](#879))
([ae8b50b](ae8b50b))
* setup wizard UX -- template filters, card metadata, provider form
reuse ([#910](#910))
([7f04676](7f04676))
* setup wizard UX overhaul -- mode choice, step reorder, provider fixes
([#907](#907))
([ee964c4](ee964c4))
* structured ModelRequirement in template agent configs
([#795](#795))
([7433548](7433548))
* Task Board page (rich Kanban, filtering, dependency viz)
([#871](#871))
([04a19b0](04a19b0))


### Bug Fixes

* align frontend types with backend and debounce WS refetches
([#916](#916))
([134c11b](134c11b))
* auto-cleanup targets newly pulled images instead of old ones
([#884](#884))
([50e6591](50e6591))
* correct wipe backup-skip flow and harden error handling
([#808](#808))
([c05860f](c05860f))
* improve provider setup in wizard, subscription auth, dashboard bugs
([#914](#914))
([87bf8e6](87bf8e6))
* improve update channel detection and add config get command
([#814](#814))
([6b137f0](6b137f0))
* resolve all ESLint warnings, add zero-warnings enforcement
([#899](#899))
([079b46a](079b46a))
* subscription auth uses api_key, base URL optional for cloud providers
([#915](#915))
([f0098dd](f0098dd))


### Refactoring

* semantic analyzer cleanup -- shared filtering, concurrency, extraction
([#908](#908))
([81372bf](81372bf))


### Documentation

* brand identity and UX design system from
[#765](#765) exploration
([#804](#804))
([389a9f4](389a9f4))
* page structure and information architecture for v0.5.0 dashboard
([#809](#809))
([f8d6d4a](f8d6d4a))
* write UX design guidelines with WCAG-verified color system
([#816](#816))
([4a4594e](4a4594e))


### Tests

* add unit tests for agent hooks and page components
([#875](#875))
([#901](#901))
([1d81546](1d81546))


### CI/CD

* bump actions/deploy-pages from 4.0.5 to 5.0.0 in the major group
([#831](#831))
([01c19de](01c19de))
* bump astral-sh/setup-uv from 7.6.0 to 8.0.0 in
/.github/actions/setup-python-uv in the all group
([#920](#920))
([5f6ba54](5f6ba54))
* bump codecov/codecov-action from 5.5.3 to 6.0.0 in the major group
([#868](#868))
([f22a181](f22a181))
* bump github/codeql-action from 4.34.1 to 4.35.0 in the all group
([#883](#883))
([87a4890](87a4890))
* bump sigstore/cosign-installer from 4.1.0 to 4.1.1 in the
minor-and-patch group
([#830](#830))
([7a69050](7a69050))
* bump the all group with 3 updates
([#923](#923))
([ff27c8e](ff27c8e))
* bump wrangler from 4.76.0 to 4.77.0 in /.github in the minor-and-patch
group ([#822](#822))
([07d43eb](07d43eb))
* bump wrangler from 4.77.0 to 4.78.0 in /.github in the all group
([#882](#882))
([f84118d](f84118d))


### Maintenance

* add design system enforcement hook and component inventory
([#846](#846))
([15abc43](15abc43))
* add dev-only auth bypass for frontend testing
([#885](#885))
([6cdcd8a](6cdcd8a))
* add pre-push rebase check hook
([#855](#855))
([b637a04](b637a04))
* backend hardening -- eviction/size-caps and model validation
([#911](#911))
([81253d9](81253d9))
* bump axios from 1.13.6 to 1.14.0 in /web in the all group across 1
directory ([#922](#922))
([b1b0232](b1b0232))
* bump brace-expansion from 5.0.4 to 5.0.5 in /web
([#862](#862))
([ba4a565](ba4a565))
* bump eslint-plugin-react-refresh from 0.4.26 to 0.5.2 in /web
([#801](#801))
([7574bb5](7574bb5))
* bump faker from 40.11.0 to 40.11.1 in the minor-and-patch group
([#803](#803))
([14d322e](14d322e))
* bump https://github.com/astral-sh/ruff-pre-commit from v0.15.7 to
0.15.8 ([#864](#864))
([f52901e](f52901e))
* bump nginxinc/nginx-unprivileged from `6582a34` to `f99cc61` in
/docker/web in the all group
([#919](#919))
([df85e4f](df85e4f))
* bump nginxinc/nginx-unprivileged from `ccbac1a` to `6582a34` in
/docker/web ([#800](#800))
([f4e9450](f4e9450))
* bump node from `44bcbf4` to `71be405` in /docker/sandbox
([#827](#827))
([91bec67](91bec67))
* bump node from `5209bca` to `cf38e1f` in /docker/web
([#863](#863))
([66d6043](66d6043))
* bump picomatch in /site
([#842](#842))
([5f20bcc](5f20bcc))
* bump recharts 2-&gt;3 and @types/node 22-&gt;25 in /web
([#802](#802))
([a908800](a908800))
* Bump requests from 2.32.5 to 2.33.0
([#843](#843))
([41daf69](41daf69))
* bump smol-toml from 1.6.0 to 1.6.1 in /site
([#826](#826))
([3e5dbe4](3e5dbe4))
* bump the all group with 3 updates
([#921](#921))
([7bace0b](7bace0b))
* bump the minor-and-patch group across 1 directory with 2 updates
([#829](#829))
([93e611f](93e611f))
* bump the minor-and-patch group across 1 directory with 3 updates
([#841](#841))
([7010c8e](7010c8e))
* bump the minor-and-patch group across 1 directory with 3 updates
([#869](#869))
([548cee5](548cee5))
* bump the minor-and-patch group in /site with 2 updates
([#865](#865))
([9558101](9558101))
* bump the minor-and-patch group with 2 updates
([#867](#867))
([4830706](4830706))
* consolidate Dependabot groups to 1 PR per ecosystem
([06d2556](06d2556))
* consolidate Dependabot groups to 1 PR per ecosystem
([#881](#881))
([06d2556](06d2556))
* improve worktree skill with full dep sync and status enhancements
([#906](#906))
([772c625](772c625))
* remove Vue remnants and document framework decision
([#851](#851))
([bf2adf6](bf2adf6))
* update web dependencies and fix brace-expansion CVE
([#880](#880))
([a7a0ed6](a7a0ed6))
* upgrade to Storybook 10 and TypeScript 6
([#845](#845))
([52d95f2](52d95f2))

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

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@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.

feat: add remaining activity event types (task_started, tool_used, delegation, cost_incurred)

1 participant