Skip to content

feat: agent performance, activity, and history API endpoints#811

Merged
Aureliolo merged 7 commits intomainfrom
feat/772-agent-performance-api
Mar 24, 2026
Merged

feat: agent performance, activity, and history API endpoints#811
Aureliolo merged 7 commits intomainfrom
feat/772-agent-performance-api

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

Summary

  • Add GET /api/v1/agents/{agent_name}/performance -- flat summary with tasks completed (total/7d/30d), success rate, cost per task, quality/collaboration scores, trend direction, plus full window metrics and trend results
  • Add GET /api/v1/agents/{agent_name}/activity -- paginated chronological timeline merging lifecycle events and task metric records (most recent first)
  • Add GET /api/v1/agents/{agent_name}/history -- career events (hired, fired, promoted, demoted, onboarded) in chronological order
  • Pure functions in hr/performance/summary.py and hr/activity.py follow the budget/trends.py pattern (domain modules, not API layer)
  • Agent name resolution via AgentRegistryService.get_by_name()
  • 37 unit tests covering all endpoints, pure functions, edge cases, 404/503 error paths

Test plan

  • uv run python -m pytest tests/unit/hr/performance/test_summary.py tests/unit/hr/test_activity.py -n auto (19 pure function tests)
  • uv run python -m pytest tests/unit/api/controllers/test_agents.py -n auto (18 controller tests including 503)
  • uv run python -m pytest tests/ -n auto (10529 passed, 0 regressions)
  • uv run ruff check src/ tests/ + uv run mypy src/ tests/ (clean)
  • OpenAPI schema regenerated

Review coverage

Pre-reviewed by 9 agents (code-reviewer, python-reviewer, conventions-enforcer, logging-audit, async-concurrency-reviewer, issue-resolution-verifier, type-design-analyzer, test-quality-reviewer, docs-consistency). 15 findings addressed:

  • Fixed tasks_completed_total semantic bug (was including failed tasks)
  • Removed asyncio.to_thread race condition on sync method
  • Moved DTOs from api/dto.py to HR domain modules (fixed inverted dependency + 800-line limit)
  • Added logger declarations, 503 test, additional edge case tests
  • Updated docs/design/operations.md API surface table

Closes #772

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 24, 2026

Dependency Review

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

Snapshot Warnings

⚠️: No snapshots were found for the head SHA 7048029.
Ensure that dependencies are being submitted on PR branches and consider enabling retry-on-snapshot-warnings. See the documentation for more information and troubleshooting advice.

Scanned Files

None

@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 management capabilities by introducing dedicated API endpoints for monitoring agent performance, tracking their activity timeline, and reviewing their career history. These additions provide a comprehensive view of an agent's operational and professional journey, supported by robust, test-driven domain logic and a clear separation of concerns. The changes also reflect a thorough pre-review process, ensuring high code quality and addressing potential issues proactively.

Highlights

  • New Agent API Endpoints: Introduced three new API endpoints under /api/v1/agents/{agent_name}: /performance for a flat summary of metrics, /activity for a paginated chronological timeline of events, and /history for career-relevant lifecycle events.
  • HR Domain Logic Refactoring: Implemented pure functions in new hr/activity.py and hr/performance/summary.py modules to encapsulate business logic for activity timelines, career event filtering, and performance summary extraction, separating it from the API layer.
  • Comprehensive Testing: Added 37 new unit tests covering all new API endpoints, pure functions, and various edge cases, including 404 and 503 error paths, ensuring robust functionality.
  • Pre-Review Findings Addressed: Incorporated feedback from 9 automated code reviewers, addressing 15 findings such as fixing a semantic bug in tasks_completed_total, resolving an asyncio.to_thread race condition, and relocating DTOs to appropriate domain modules.
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 24, 2026 10:18 — with GitHub Actions Inactive
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 new API endpoints for retrieving agent performance, activity, and history, which is a valuable addition for monitoring and analysis. The implementation includes well-structured pure functions for data processing and is accompanied by a comprehensive set of unit tests. My review includes a couple of suggestions to enhance performance and improve the clarity of the API documentation.

Comment on lines +213 to +216
events = await app_state.persistence.lifecycle_events.list_events(
agent_id=agent_id,
)
career = filter_career_events(events)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The current implementation fetches all lifecycle events for an agent from the database and then filters them in memory to get career-related events. For agents with a long history and many non-career events (e.g., status changes), this can be inefficient as it transfers unnecessary data from the database and puts pressure on application memory. For better performance and scalability, consider enhancing the persistence layer to allow filtering by multiple event types directly in the database query. This would reduce the amount of data transferred and processed by the application.

Comment on lines +50 to +53
tasks_completed_total: int = Field(
ge=0,
description="Lifetime successfully completed tasks",
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The description for tasks_completed_total is misleading. It's described as "Lifetime successfully completed tasks", but the implementation calculates it based on the maximum tasks completed within any available time window (e.g., 7d, 30d). This isn't a true lifetime total. To avoid confusion for API consumers, the description should accurately reflect how the value is derived.

Suggested change
tasks_completed_total: int = Field(
ge=0,
description="Lifetime successfully completed tasks",
)
tasks_completed_total: int = Field(
ge=0,
description="Total tasks completed within the largest available time window",
)

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 24, 2026

Codecov Report

❌ Patch coverage is 98.96907% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 92.33%. Comparing base (389a9f4) to head (7048029).
⚠️ Report is 5 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/synthorg/api/controllers/agents.py 96.87% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #811      +/-   ##
==========================================
+ Coverage   92.29%   92.33%   +0.04%     
==========================================
  Files         576      578       +2     
  Lines       29957    30051      +94     
  Branches     2896     2901       +5     
==========================================
+ Hits        27649    27749     +100     
+ Misses       1824     1820       -4     
+ Partials      484      482       -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.

Aureliolo and others added 4 commits March 24, 2026 11:40
Add three read-only endpoints under /agents/{agent_name}/:
- GET /performance -- flat summary with tasks completed (total/7d/30d),
  success rate, cost per task, quality/collaboration scores, trend direction
- GET /activity -- paginated chronological timeline merging lifecycle
  events and task metric records
- GET /history -- career events (hired, fired, promoted, demoted, onboarded)

Pure functions in hr/performance/summary.py and hr/activity.py follow the
budget/trends.py pattern. Agent name resolution via AgentRegistryService.

Closes #772

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix tasks_completed_total to use tasks_completed (not data_point_count
  which includes failures) -- semantic bug in API contract
- Remove asyncio.to_thread race condition on sync get_task_metrics;
  call synchronously since it is a fast in-memory read
- Move DTOs (AgentPerformanceSummary, ActivityEvent, CareerEvent) from
  api/dto.py to their HR domain modules, fixing inverted dependency
  (hr -> api) and bringing dto.py back under 800-line limit
- Add logger declarations to activity.py and summary.py per CLAUDE.md
- Add 503 test for unconfigured agent registry
- Add test_only_30d_window and identical-timestamp sort-stability tests
- Fix hardcoded $ symbol to USD text suffix
- Use dict lookup instead of linear scan in _find_window
- Add fixture docstring and description/related_ids assertions
- Update operations.md API surface table with new endpoints

Pre-reviewed by 9 agents, 15 findings addressed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix tasks_completed_total description: was "Lifetime" but is actually
  max across time windows; updated field description, docstring, comments
- Remove unused logger declarations from activity.py and summary.py
- Fix case-insensitive name matching in get_agent (was exact, now
  matches _resolve_agent_id registry behavior)
- Use static 404 error messages to prevent user-controlled reflection
- Add limit parameter to LifecycleEventRepository.list_events protocol
  + SQLite impl + fake impl; apply 10k safety cap in controller
- Fix docstrings: metrics fall back to 7d window, not exclusively 30d
- Fix agent_name param type: str -> NotBlankStr in extract_performance_summary
- Update AgentController docstring to include "history"
- Update CLAUDE.md package structure: hr/ now includes activity/career
- Update agents.md design page: add API sub-routes table
- Update operations.md: expand agents API surface table entries
- Add 503 tests for /activity and /history endpoints
- Add fallback description tests for empty details
- Strengthen test_performance_returns_summary assertions

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

coderabbitai bot commented Mar 24, 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: 4ce9edaa-23df-4a0e-8a71-e03b98e3d1b1

📥 Commits

Reviewing files that changed from the base of the PR and between ef6113f and 7048029.

📒 Files selected for processing (1)
  • tests/unit/hr/test_activity.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: Build Backend
  • GitHub Check: Build Sandbox
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Do NOT use from __future__ import annotations -- Python 3.14 has PEP 649.
Use PEP 758 except syntax: except A, B: (no parentheses) -- ruff enforces this on Python 3.14.
Type hints are required on all public functions, enforced with mypy strict mode.
Use Google-style docstrings on all public classes and functions (enforced by ruff D rules).
Create new objects for immutability, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement.
Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (with model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.
Use Pydantic v2 with adopted conventions: @computed_field for derived values instead of storing + validating redundant fields; use NotBlankStr from core.types 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 (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare create_task.
Keep functions under 50 lines and files under 800 lines.
Handle errors explicitly, never silently swallow them. Validate at system boundaries (user input, external APIs, config files).
Always use logger as the variable name (not _logger, not log) for the logger instance.
Use structured kwargs in logger calls: always logger.info(EVENT, key=value) -- never logger.info("msg %s", val).
All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG is for object creation, internal flow, and entry/exit of key functions.
Use line length of 88 characters (enforced by ruff) for Python code.

Files:

  • tests/unit/hr/test_activity.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 markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow to categorize tests.
Maintain 80% minimum code coverage, enforced in CI. Use @pytest.mark.parametrize for testing similar cases.
Use Hypothesis for property-based testing in Python with @given + @settings. Hypothesis profiles: ci (50 examples, default) and dev (1000 examples). Run dev profile: HYPOTHESIS_PROFILE=dev.
NEVER skip, dismiss, or ignore flaky tests -- always fix them fully and fundamentally. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() instead of widening timing margins. For tasks that must block indefinitely until cancelled, use asyncio.Event().wait() instead of asyncio.sleep(large_number).

Files:

  • tests/unit/hr/test_activity.py
🧠 Learnings (4)
📓 Common learnings
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)
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
📚 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-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-24T10:36:39.226Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-24T10:36:39.226Z
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()` instead of widening timing margins. For tasks that must block indefinitely until cancelled, use `asyncio.Event().wait()` instead of `asyncio.sleep(large_number)`.

Applied to files:

  • tests/unit/hr/test_activity.py
🧬 Code graph analysis (1)
tests/unit/hr/test_activity.py (1)
src/synthorg/hr/activity.py (2)
  • filter_career_events (133-159)
  • merge_activity_timeline (110-130)
🔇 Additional comments (3)
tests/unit/hr/test_activity.py (3)

16-57: Strong fixture helpers for deterministic test data.

The typed builders keep setup noise low while making scenario intent explicit, and the fixed UTC timestamp baseline improves reproducibility.


60-170: merge_activity_timeline coverage is solid and regression-focused.

Good breadth here: empty inputs, lifecycle-only, task-only, ordering, stable tie behavior, and related_ids population are all validated with clear assertions.


173-274: filter_career_events tests comprehensively validate the contract.

This section correctly exercises filtering boundaries, chronological ordering, fallback descriptions, and metadata/initiator preservation.


Walkthrough

Adds three read-only agent endpoints: GET /api/v1/agents/{agent_name}/performance, GET /api/v1/agents/{agent_name}/activity (paginated), and GET /api/v1/agents/{agent_name}/history. Introduces Pydantic models AgentPerformanceSummary, ActivityEvent, and CareerEvent plus pure helpers to convert performance snapshots, lifecycle events, and task metrics into API shapes. Updates controller routing and agent resolution (case-insensitive lookup and _resolve_agent_id), expands LifecycleEventRepository.list_events with an optional limit, adds observability event constants, tests, and documentation.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.75% which is insufficient. The required threshold is 40.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: agent performance, activity, and history API endpoints' directly and clearly describes the main change: adding three new API endpoints for agent data retrieval.
Description check ✅ Passed The description comprehensively relates to the changeset, detailing the three endpoints being added, implementation approach, domain module organization, testing strategy, and pre-review findings addressed.
Linked Issues check ✅ Passed All coding requirements from #772 are met: three endpoints implemented with required fields (tasks_completed total/7d/30d, success_rate_percent, cost_per_task_usd, quality/collaboration scores, trend_direction), pagination support for activity, career history filtering, unit test coverage, and OpenAPI schema updates.
Out of Scope Changes check ✅ Passed All changes directly support the three endpoints and their requirements. Documentation updates reflect API changes; repository protocol extension enables list limiting; observability constants support endpoint logging; test fixtures enable proper testing. No unrelated modifications detected.

✏️ 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.

@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 24, 2026 10:44 — with GitHub Actions Inactive
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: 8

Caution

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

⚠️ Outside diff range comments (1)
tests/unit/api/fakes.py (1)

144-155: ⚠️ Potential issue | 🟡 Minor

Consider adding timestamp-based sorting for parity with production.

The limit slicing is applied to unsorted results, whereas the SQLite implementation applies ORDER BY timestamp DESC before LIMIT. This means the fake returns "first N inserted" while production returns "most recent N".

This could mask ordering-related bugs in tests. Consider sorting before slicing:

🔧 Suggested fix for sort consistency
     async def list_events(
         self,
         *,
         agent_id: str | None = None,
         event_type: LifecycleEventType | None = None,
         since: datetime | None = None,
         limit: int | None = None,
     ) -> tuple[AgentLifecycleEvent, ...]:
         result = self._events
         if agent_id is not None:
             result = [e for e in result if e.agent_id == agent_id]
         if event_type is not None:
             result = [e for e in result if e.event_type == event_type]
         if since is not None:
             result = [e for e in result if e.timestamp >= since]
+        result = sorted(result, key=lambda e: e.timestamp, reverse=True)
         if limit is not None:
             result = result[:limit]
         return tuple(result)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/api/fakes.py` around lines 144 - 155, The fake events filter
currently slices result before ordering, causing tests to return the earliest
inserted events; update the logic in the method that builds `result` (which
reads from `self._events` and filters by `agent_id`, `event_type`, and `since`)
to sort `result` by `timestamp` in descending order (most recent first) prior to
applying the `limit` slice, then return the tuple of that ordered slice to match
the production SQLite `ORDER BY timestamp DESC` behavior.
🤖 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/agents.py`:
- Around line 39-60: The helper _resolve_agent_id currently returns only a
string ID, causing callers to echo the raw URL agent_name; change
_resolve_agent_id to return the resolved identity object (or both id and
identity) so callers can use identity.id for lookups and identity.name for
responses, then update call sites (e.g., where extract_performance_summary(...)
is invoked around the performance endpoint) to pass identity.name as
summary.agent_name instead of the raw agent_name; ensure any other uses (notably
the other occurrence noted at lines ~139-141) are updated to read identity.id
for operations and identity.name for returned/displayed fields.
- Around line 220-224: The current code applies limit=_MAX_LIFECYCLE_EVENTS in
lifecycle_events.list_events before calling filter_career_events, which can drop
older career milestones; change the logic so filtering for career event types
happens at the repository boundary (e.g., add an event_types or types parameter
to lifecycle_events.list_events and pass the career event types) or remove the
pre-filter limit and apply _MAX_LIFECYCLE_EVENTS only after calling
filter_career_events (i.e., call lifecycle_events.list_events without the cap,
run filter_career_events(events) and then slice to _MAX_LIFECYCLE_EVENTS) so
HIRED/PROMOTED/DEMOTED entries are not lost.
- Around line 177-189: The endpoint currently loads the full activity sets via
app_state.persistence.lifecycle_events.list_events and
app_state.performance_tracker.get_task_metrics then calls
merge_activity_timeline and paginate, which materializes everything; change this
to perform source-level paginated/streamed merging so only the requested page is
materialized. Update or add iterator/streaming variants (e.g.,
lifecycle_events.list_events_stream or add offset/limit params to list_events
and performance_tracker.get_task_metrics) and implement a merging generator in
merge_activity_timeline that accepts two sorted iterators and yields items until
it has produced offset+limit items (so you can skip offset and return only limit
items plus a flag/total hint). Replace the current calls to fetch full lists
with calls to the streaming/paginated APIs and use the generator to produce only
the page to return, preserving existing metadata semantics.

In `@src/synthorg/hr/activity.py`:
- Around line 92-107: The mapper _task_metric_to_activity currently folds cost
into the description so the API can't surface a separate cost_incurred event;
change it to emit two ActivityEvent objects per TaskMetricRecord: keep the
existing task_completed ActivityEvent (event_type "task_completed") as-is, then
create and return or append a second ActivityEvent with event_type
"cost_incurred" (timestamp record.completed_at, a clear description or amount
field for record.cost_usd, and the same related_ids including task_id and
agent_id) so consumers can see cost as a distinct event; ensure any callers that
expect a single ActivityEvent are updated to accept a list/stream of events or
adapt the function signature to return Iterable[ActivityEvent] or
List[ActivityEvent].
- Around line 1-16: This module is missing the standard observability logger;
import get_logger from synthorg.observability and add the module-level binding
logger = get_logger(__name__) near the top of the file so business-logic uses
logger; specifically add the import for get_logger and the logger =
get_logger(__name__) binding (referencing get_logger and logger and __name__)
alongside the existing imports.

In `@src/synthorg/hr/performance/summary.py`:
- Around line 143-149: The code currently sets tasks_total using
max(w.tasks_completed for w in snapshot.windows) which only gives a
rolling-window approximation; instead read the explicit all-time aggregate (e.g.
snapshot.all_time.tasks_completed or snapshot.tasks_completed_total) when
available and use that for tasks_total (fall back to the max-of-windows only if
the explicit all-time aggregate is missing). Update the assignment that sets
tasks_total (the current max(...) in summary.py) to prefer
snapshot.all_time.tasks_completed (or snapshot.tasks_completed_total) and only
use the rolling-window max as a fallback.
- Around line 1-16: This module lacks the standard module logger binding; add an
import for get_logger from synthorg.observability and create the logger variable
by calling get_logger(__name__) near the top of the file (e.g., alongside
existing imports in src/synthorg/hr/performance/summary.py) so the module
defines logger = get_logger(__name__) for use by functions that reference
logging.

In `@tests/unit/hr/test_activity.py`:
- Around line 139-154: The test test_identical_timestamps_stable_sort currently
only checks set membership so it won't catch regressions in tie-break ordering;
update the assertion to verify the stable order produced by
merge_activity_timeline when timestamps are identical by asserting that the
first timeline entry corresponds to the lifecycle event created with
_make_lifecycle_event (event_type "hired") and the second corresponds to the
task metric created with _make_task_metric (event_type "task_completed") — e.g.
compare the sequence of event_type values or specific identifiers from the
returned timeline to ["hired", "task_completed"] to ensure lifecycle stays
before task on tie.

---

Outside diff comments:
In `@tests/unit/api/fakes.py`:
- Around line 144-155: The fake events filter currently slices result before
ordering, causing tests to return the earliest inserted events; update the logic
in the method that builds `result` (which reads from `self._events` and filters
by `agent_id`, `event_type`, and `since`) to sort `result` by `timestamp` in
descending order (most recent first) prior to applying the `limit` slice, then
return the tuple of that ordered slice to match the production SQLite `ORDER BY
timestamp DESC` behavior.
🪄 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: d1478e35-3af2-49fd-baf5-936965c8e603

📥 Commits

Reviewing files that changed from the base of the PR and between c05860f and f446fd3.

📒 Files selected for processing (14)
  • CLAUDE.md
  • docs/design/agents.md
  • docs/design/operations.md
  • src/synthorg/api/controllers/agents.py
  • src/synthorg/hr/activity.py
  • src/synthorg/hr/performance/summary.py
  • src/synthorg/hr/persistence_protocol.py
  • src/synthorg/observability/events/api.py
  • src/synthorg/persistence/sqlite/hr_repositories.py
  • tests/unit/api/conftest.py
  • tests/unit/api/controllers/test_agents.py
  • tests/unit/api/fakes.py
  • tests/unit/hr/performance/test_summary.py
  • tests/unit/hr/test_activity.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: Build Backend
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Web
  • GitHub Check: Test (Python 3.14)
  • 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.
Use PEP 758 except syntax: except A, B: (no parentheses) -- ruff enforces this on Python 3.14.
Type hints are required on all public functions, enforced with mypy strict mode.
Use Google-style docstrings on all public classes and functions (enforced by ruff D rules).
Create new objects for immutability, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement.
Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (with model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.
Use Pydantic v2 with adopted conventions: @computed_field for derived values instead of storing + validating redundant fields; use NotBlankStr from core.types 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 (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare create_task.
Keep functions under 50 lines and files under 800 lines.
Handle errors explicitly, never silently swallow them. Validate at system boundaries (user input, external APIs, config files).
Always use logger as the variable name (not _logger, not log) for the logger instance.
Use structured kwargs in logger calls: always logger.info(EVENT, key=value) -- never logger.info("msg %s", val).
All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG is for object creation, internal flow, and entry/exit of key functions.
Use line length of 88 characters (enforced by ruff) for Python code.

Files:

  • tests/unit/api/conftest.py
  • src/synthorg/hr/persistence_protocol.py
  • src/synthorg/persistence/sqlite/hr_repositories.py
  • tests/unit/api/fakes.py
  • src/synthorg/observability/events/api.py
  • tests/unit/api/controllers/test_agents.py
  • tests/unit/hr/performance/test_summary.py
  • tests/unit/hr/test_activity.py
  • src/synthorg/hr/performance/summary.py
  • src/synthorg/hr/activity.py
  • src/synthorg/api/controllers/agents.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 markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow to categorize tests.
Maintain 80% minimum code coverage, enforced in CI. Use @pytest.mark.parametrize for testing similar cases.
Use Hypothesis for property-based testing in Python with @given + @settings. Hypothesis profiles: ci (50 examples, default) and dev (1000 examples). Run dev profile: HYPOTHESIS_PROFILE=dev.
NEVER skip, dismiss, or ignore flaky tests -- always fix them fully and fundamentally. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() instead of widening timing margins. For tasks that must block indefinitely until cancelled, use asyncio.Event().wait() instead of asyncio.sleep(large_number).

Files:

  • tests/unit/api/conftest.py
  • tests/unit/api/fakes.py
  • tests/unit/api/controllers/test_agents.py
  • tests/unit/hr/performance/test_summary.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__).
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 and handler-cleanup code).
Always 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.

Files:

  • src/synthorg/hr/persistence_protocol.py
  • src/synthorg/persistence/sqlite/hr_repositories.py
  • src/synthorg/observability/events/api.py
  • src/synthorg/hr/performance/summary.py
  • src/synthorg/hr/activity.py
  • src/synthorg/api/controllers/agents.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

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 as aliases. Vendor names may only appear in: (1) Operations design page, (2) .claude/ files, (3) third-party import paths.

Files:

  • src/synthorg/hr/persistence_protocol.py
  • src/synthorg/persistence/sqlite/hr_repositories.py
  • src/synthorg/observability/events/api.py
  • src/synthorg/hr/performance/summary.py
  • src/synthorg/hr/activity.py
  • src/synthorg/api/controllers/agents.py
docs/design/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

When approved deviations occur, update the relevant docs/design/ page to reflect the new reality.

Files:

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

📄 CodeRabbit inference engine (CLAUDE.md)

Docs must be Markdown files in docs/ directory built with Zensical (config: mkdocs.yml). Design spec: docs/design/ (9 pages). Architecture: docs/architecture/. Roadmap: docs/roadmap/.

Files:

  • docs/design/operations.md
  • docs/design/agents.md
🧠 Learnings (24)
📓 Common learnings
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)
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
📚 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:

  • CLAUDE.md
  • tests/unit/api/conftest.py
  • docs/design/agents.md
  • tests/unit/hr/test_activity.py
  • src/synthorg/hr/performance/summary.py
  • src/synthorg/hr/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:

  • CLAUDE.md
  • tests/unit/api/conftest.py
  • docs/design/agents.md
  • tests/unit/hr/test_activity.py
  • src/synthorg/hr/performance/summary.py
  • src/synthorg/hr/activity.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:

  • 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:

  • CLAUDE.md
  • tests/unit/api/conftest.py
  • docs/design/agents.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/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:

  • CLAUDE.md
  • tests/unit/api/conftest.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-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-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/core/**/*.py : Core module must contain shared domain models, base classes, resilience config (RetryConfig, RateLimiterConfig)

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 docs/design/*.md : Design spec pages: 7 pages in `docs/design/` — index, agents, organization, communication, engine, memory, operations

Applied to files:

  • CLAUDE.md
  • docs/design/agents.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:

  • 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/engine/coordination/**/*.py : Task coordination uses multi-agent pipeline with 4 dispatchers (SAS/centralized/decentralized/context-dependent), wave execution, and workspace lifecycle integration.

Applied to files:

  • 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/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/api.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/api.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/api.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/api.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/api.py
📚 Learning: 2026-03-24T10:36:39.226Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-24T10:36:39.226Z
Learning: Applies to src/synthorg/**/*.py : Always 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/api.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/api.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 src/**/*.py : Use event name constants from domain-specific modules under ai_company.observability.events (e.g., PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.) — import directly

Applied to files:

  • src/synthorg/observability/events/api.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/api.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/api.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 docs/design/**/*.md : Design specification pages in `docs/design/` must be consulted before implementing features (7 pages: index, agents, organization, communication, engine, memory, operations)

Applied to files:

  • docs/design/agents.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/api/**/*.py : API package (api/): Litestar REST + WebSocket with controllers, guards, channels, JWT + API key + WS ticket auth, approval gate integration, coordination endpoint, collaboration endpoint, settings endpoint, provider management endpoint (CRUD + test + presets), backup endpoint, RFC 9457 structured errors, AppState hot-reload slots, service auto-wiring (Phase 1 at construction, Phase 2 on startup), lifecycle helpers

Applied to files:

  • tests/unit/api/controllers/test_agents.py
  • src/synthorg/api/controllers/agents.py
🧬 Code graph analysis (5)
tests/unit/api/conftest.py (3)
tests/unit/settings/test_service.py (1)
  • registry (82-85)
tests/unit/hr/conftest.py (1)
  • registry (174-176)
src/synthorg/api/state.py (1)
  • agent_registry (302-304)
tests/unit/api/controllers/test_agents.py (4)
src/synthorg/hr/performance/tracker.py (1)
  • record_task_metric (136-159)
tests/unit/hr/test_activity.py (2)
  • _make_task_metric (35-55)
  • _make_lifecycle_event (16-32)
tests/unit/api/fakes.py (12)
  • FakePersistenceBackend (419-522)
  • lifecycle_events (475-476)
  • save (40-41)
  • save (72-73)
  • save (104-108)
  • save (135-136)
  • save (164-165)
  • save (190-191)
  • save (213-214)
  • save (238-242)
  • save (287-288)
  • save (321-322)
src/synthorg/hr/persistence_protocol.py (3)
  • save (25-34)
  • save (65-74)
  • save (103-112)
tests/unit/hr/performance/test_summary.py (2)
src/synthorg/hr/performance/models.py (3)
  • AgentPerformanceSnapshot (391-426)
  • TrendResult (302-319)
  • WindowMetrics (322-388)
src/synthorg/hr/performance/summary.py (1)
  • extract_performance_summary (125-171)
src/synthorg/hr/activity.py (1)
src/synthorg/hr/performance/models.py (1)
  • TaskMetricRecord (25-67)
src/synthorg/api/controllers/agents.py (7)
src/synthorg/hr/activity.py (4)
  • ActivityEvent (19-41)
  • CareerEvent (44-68)
  • filter_career_events (133-159)
  • merge_activity_timeline (110-130)
src/synthorg/hr/performance/summary.py (2)
  • AgentPerformanceSummary (23-99)
  • extract_performance_summary (125-171)
src/synthorg/api/state.py (1)
  • AppState (42-511)
src/synthorg/api/errors.py (1)
  • NotFoundError (167-175)
src/synthorg/api/dto.py (2)
  • ApiResponse (130-158)
  • PaginatedResponse (177-210)
src/synthorg/hr/performance/tracker.py (2)
  • get_snapshot (258-314)
  • get_task_metrics (374-400)
src/synthorg/hr/persistence_protocol.py (1)
  • list_events (36-58)
🔇 Additional comments (8)
CLAUDE.md (1)

112-112: LGTM!

The HR package description accurately reflects the new activity timeline and career history responsibilities added by this PR.

tests/unit/api/conftest.py (2)

200-204: LGTM!

The agent_registry fixture follows the established pattern and is correctly wired into the test_client fixture for API testing.


226-247: LGTM!

The test_client fixture correctly accepts and passes the agent_registry dependency to create_app(), enabling tests for the new agent performance/activity/history endpoints.

src/synthorg/persistence/sqlite/hr_repositories.py (1)

106-130: LGTM!

The limit parameter implementation is correct:

  • Parameterized query prevents SQL injection
  • LIMIT is applied after ORDER BY timestamp DESC (correct ordering)
  • Type annotation for params correctly updated to list[str | int]
docs/design/operations.md (1)

1064-1066: LGTM!

The API surface documentation correctly reflects the three new agent endpoints added by this PR.

src/synthorg/hr/persistence_protocol.py (1)

42-50: LGTM!

The protocol extension is backward-compatible with a sensible default (None for all), and the docstring clearly documents the parameter behavior.

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

66-68: LGTM!

The new event constants follow the established api.<noun>.<verb> naming convention and appropriately use the _queried suffix for read operations.

docs/design/agents.md (1)

303-310: LGTM!

The design documentation accurately describes the new performance API contract, including the three sub-routes and their response models. The table format clearly communicates the endpoint purposes.

Comment on lines +39 to +60
async def _resolve_agent_id(
app_state: AppState,
agent_name: str,
) -> str:
"""Resolve an agent name to its ID via the registry.

Args:
app_state: Application state with agent registry.
agent_name: Agent display name.

Returns:
Agent ID as string.

Raises:
NotFoundError: If the agent is not found in the registry.
"""
identity = await app_state.agent_registry.get_by_name(agent_name)
if identity is None:
msg = "Agent not found"
logger.warning(API_RESOURCE_NOT_FOUND, resource="agent", name=agent_name)
raise NotFoundError(msg)
return str(identity.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

Use the resolved identity name in the performance response.

If get_by_name() accepts mixed-case input, this path still echoes the raw URL segment into summary.agent_name. A request like /agents/ALICE/performance would therefore return "agent_name": "ALICE" instead of the canonical stored name. Return the resolved identity from the helper and pass identity.name to extract_performance_summary().

Also applies to: 139-141

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

In `@src/synthorg/api/controllers/agents.py` around lines 39 - 60, The helper
_resolve_agent_id currently returns only a string ID, causing callers to echo
the raw URL agent_name; change _resolve_agent_id to return the resolved identity
object (or both id and identity) so callers can use identity.id for lookups and
identity.name for responses, then update call sites (e.g., where
extract_performance_summary(...) is invoked around the performance endpoint) to
pass identity.name as summary.agent_name instead of the raw agent_name; ensure
any other uses (notably the other occurrence noted at lines ~139-141) are
updated to read identity.id for operations and identity.name for
returned/displayed fields.

Comment on lines +177 to +189
lifecycle_events = await app_state.persistence.lifecycle_events.list_events(
agent_id=agent_id,
limit=_MAX_LIFECYCLE_EVENTS,
)
task_metrics = app_state.performance_tracker.get_task_metrics(
agent_id=agent_id,
)

timeline = merge_activity_timeline(
lifecycle_events=lifecycle_events,
task_metrics=task_metrics,
)
page, meta = paginate(timeline, offset=offset, limit=limit)
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 endpoint still paginates after loading the entire timeline.

list_events(..., limit=_MAX_LIFECYCLE_EVENTS) plus get_task_metrics(agent_id=...) materialize the full activity set before paginate() slices it. That makes request cost grow with total history size, and the lifecycle cap means pagination.total can already be truncated for long-lived agents. This needs source-level pagination/merging instead of post-fetch slicing.

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

In `@src/synthorg/api/controllers/agents.py` around lines 177 - 189, The endpoint
currently loads the full activity sets via
app_state.persistence.lifecycle_events.list_events and
app_state.performance_tracker.get_task_metrics then calls
merge_activity_timeline and paginate, which materializes everything; change this
to perform source-level paginated/streamed merging so only the requested page is
materialized. Update or add iterator/streaming variants (e.g.,
lifecycle_events.list_events_stream or add offset/limit params to list_events
and performance_tracker.get_task_metrics) and implement a merging generator in
merge_activity_timeline that accepts two sorted iterators and yields items until
it has produced offset+limit items (so you can skip offset and return only limit
items plus a flag/total hint). Replace the current calls to fetch full lists
with calls to the streaming/paginated APIs and use the generator to produce only
the page to return, preserving existing metadata semantics.

Comment on lines +1 to +16
"""Pure functions for building agent activity timelines.

Merges lifecycle events and task metric records into a unified
chronological timeline, and filters career-relevant events.
"""

from typing import TYPE_CHECKING

from pydantic import AwareDatetime, BaseModel, ConfigDict, Field

from synthorg.core.types import NotBlankStr # noqa: TC001
from synthorg.hr.enums import LifecycleEventType

if TYPE_CHECKING:
from synthorg.hr.models import AgentLifecycleEvent
from synthorg.hr.performance.models import TaskMetricRecord
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add the standard module logger here.

This new HR business-logic module is missing the required get_logger import and logger = get_logger(__name__) binding. As per coding guidelines src/synthorg/**/*.py: 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 1 - 16, This module is missing the
standard observability logger; import get_logger from synthorg.observability and
add the module-level binding logger = get_logger(__name__) near the top of the
file so business-logic uses logger; specifically add the import for get_logger
and the logger = get_logger(__name__) binding (referencing get_logger and logger
and __name__) alongside the existing imports.

Comment on lines +92 to +107
def _task_metric_to_activity(record: TaskMetricRecord) -> ActivityEvent:
"""Convert a task metric record to a timeline activity event."""
status = "succeeded" if record.is_success else "failed"
desc = (
f"Task {record.task_id} {status} "
f"({record.duration_seconds:.1f}s, {record.cost_usd:.4f} USD)"
)
return ActivityEvent(
event_type="task_completed",
timestamp=record.completed_at,
description=desc,
related_ids={
"task_id": str(record.task_id),
"agent_id": str(record.agent_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 | 🟠 Major

cost_incurred can never surface from this mapper.

Each TaskMetricRecord is flattened into one task_completed event, with the cost only embedded in description. That means the /activity API can never expose the separate cost_incurred event type called out in the linked objective. Either emit a second ActivityEvent per metric or explicitly narrow the contract/docs.

🤖 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 92 - 107, The mapper
_task_metric_to_activity currently folds cost into the description so the API
can't surface a separate cost_incurred event; change it to emit two
ActivityEvent objects per TaskMetricRecord: keep the existing task_completed
ActivityEvent (event_type "task_completed") as-is, then create and return or
append a second ActivityEvent with event_type "cost_incurred" (timestamp
record.completed_at, a clear description or amount field for record.cost_usd,
and the same related_ids including task_id and agent_id) so consumers can see
cost as a distinct event; ensure any callers that expect a single ActivityEvent
are updated to accept a list/stream of events or adapt the function signature to
return Iterable[ActivityEvent] or List[ActivityEvent].

Comment on lines +1 to +16
"""Pure functions for extracting API-friendly performance summaries.

Transforms :class:`AgentPerformanceSnapshot` into a flat
:class:`AgentPerformanceSummary` suitable for dashboard display.
"""

from typing import TYPE_CHECKING

from pydantic import BaseModel, ConfigDict, Field

from synthorg.core.types import NotBlankStr # noqa: TC001
from synthorg.hr.enums import TrendDirection
from synthorg.hr.performance.models import TrendResult, WindowMetrics # noqa: TC001

if TYPE_CHECKING:
from synthorg.hr.performance.models import AgentPerformanceSnapshot
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add the standard module logger here.

This new HR business-logic module is missing the required get_logger import and logger = get_logger(__name__) binding. As per coding guidelines src/synthorg/**/*.py: 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/performance/summary.py` around lines 1 - 16, This module
lacks the standard module logger binding; add an import for get_logger from
synthorg.observability and create the logger variable by calling
get_logger(__name__) near the top of the file (e.g., alongside existing imports
in src/synthorg/hr/performance/summary.py) so the module defines logger =
get_logger(__name__) for use by functions that reference logging.

Comment on lines +143 to +149
# Best available completed count = max tasks_completed across all
# windows (windows overlap, so sum() would double-count; max()
# gives the widest window's count as the best approximation).
tasks_total = max(
(w.tasks_completed for w in snapshot.windows),
default=0,
)
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

tasks_completed_total is still a rolling-window approximation.

The new tests allow snapshots with only 7d/30d windows, and this computes tasks_completed_total as max(w.tasks_completed). For any agent with completed work older than 30 days, the "total" field collapses to the 30d count. Please source this from an explicit all-time aggregate instead of reusing rolling-window data.

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

In `@src/synthorg/hr/performance/summary.py` around lines 143 - 149, The code
currently sets tasks_total using max(w.tasks_completed for w in
snapshot.windows) which only gives a rolling-window approximation; instead read
the explicit all-time aggregate (e.g. snapshot.all_time.tasks_completed or
snapshot.tasks_completed_total) when available and use that for tasks_total
(fall back to the max-of-windows only if the explicit all-time aggregate is
missing). Update the assignment that sets tasks_total (the current max(...) in
summary.py) to prefer snapshot.all_time.tasks_completed (or
snapshot.tasks_completed_total) and only use the rolling-window max as a
fallback.

- Sort by timestamp DESC before limit slicing in fake list_events
  (matches SQLite ORDER BY behavior for test/production parity)
- Remove limit cap from history endpoint (career events are naturally
  few per agent; capping risks dropping older milestones like HIRED)
- Assert stable tie-break ordering in test_identical_timestamps

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: 1

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

Inline comments:
In `@tests/unit/hr/test_activity.py`:
- Around line 260-270: The test name test_metadata_and_initiated_by_preserved is
misleading because it asserts only initiated_by and description; either update
the helper _make_lifecycle_event to accept and propagate a metadata argument and
add an assertion that career[0].metadata equals the provided metadata (keeping
LifecycleEventType.HIRED and filter_career_events usage), or rename the test to
test_initiated_by_and_details_preserved to match current assertions; modify the
test definition and/or _make_lifecycle_event accordingly so the test and helper
consistently cover metadata if you choose the first option.
🪄 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: 623365b1-e224-4d71-bc07-44c2afc0ac7a

📥 Commits

Reviewing files that changed from the base of the PR and between f446fd3 and 141157f.

📒 Files selected for processing (3)
  • src/synthorg/api/controllers/agents.py
  • tests/unit/api/fakes.py
  • tests/unit/hr/test_activity.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: Build Sandbox
  • 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.
Use PEP 758 except syntax: except A, B: (no parentheses) -- ruff enforces this on Python 3.14.
Type hints are required on all public functions, enforced with mypy strict mode.
Use Google-style docstrings on all public classes and functions (enforced by ruff D rules).
Create new objects for immutability, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement.
Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (with model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.
Use Pydantic v2 with adopted conventions: @computed_field for derived values instead of storing + validating redundant fields; use NotBlankStr from core.types 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 (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare create_task.
Keep functions under 50 lines and files under 800 lines.
Handle errors explicitly, never silently swallow them. Validate at system boundaries (user input, external APIs, config files).
Always use logger as the variable name (not _logger, not log) for the logger instance.
Use structured kwargs in logger calls: always logger.info(EVENT, key=value) -- never logger.info("msg %s", val).
All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG is for object creation, internal flow, and entry/exit of key functions.
Use line length of 88 characters (enforced by ruff) for Python code.

Files:

  • tests/unit/hr/test_activity.py
  • tests/unit/api/fakes.py
  • src/synthorg/api/controllers/agents.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 markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow to categorize tests.
Maintain 80% minimum code coverage, enforced in CI. Use @pytest.mark.parametrize for testing similar cases.
Use Hypothesis for property-based testing in Python with @given + @settings. Hypothesis profiles: ci (50 examples, default) and dev (1000 examples). Run dev profile: HYPOTHESIS_PROFILE=dev.
NEVER skip, dismiss, or ignore flaky tests -- always fix them fully and fundamentally. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() instead of widening timing margins. For tasks that must block indefinitely until cancelled, use asyncio.Event().wait() instead of asyncio.sleep(large_number).

Files:

  • tests/unit/hr/test_activity.py
  • tests/unit/api/fakes.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__).
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 and handler-cleanup code).
Always 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.

Files:

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

📄 CodeRabbit inference engine (CLAUDE.md)

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 as aliases. Vendor names may only appear in: (1) Operations design page, (2) .claude/ files, (3) third-party import paths.

Files:

  • src/synthorg/api/controllers/agents.py
🧠 Learnings (4)
📓 Common learnings
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)
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
📚 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
  • src/synthorg/api/controllers/agents.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
  • src/synthorg/api/controllers/agents.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/api/**/*.py : API package (api/): Litestar REST + WebSocket with controllers, guards, channels, JWT + API key + WS ticket auth, approval gate integration, coordination endpoint, collaboration endpoint, settings endpoint, provider management endpoint (CRUD + test + presets), backup endpoint, RFC 9457 structured errors, AppState hot-reload slots, service auto-wiring (Phase 1 at construction, Phase 2 on startup), lifecycle helpers

Applied to files:

  • src/synthorg/api/controllers/agents.py
🧬 Code graph analysis (2)
tests/unit/hr/test_activity.py (3)
src/synthorg/core/enums.py (2)
  • Complexity (275-281)
  • TaskType (255-263)
src/synthorg/hr/activity.py (2)
  • filter_career_events (133-159)
  • merge_activity_timeline (110-130)
src/synthorg/hr/performance/models.py (1)
  • TaskMetricRecord (25-67)
src/synthorg/api/controllers/agents.py (9)
src/synthorg/hr/activity.py (4)
  • ActivityEvent (19-41)
  • CareerEvent (44-68)
  • filter_career_events (133-159)
  • merge_activity_timeline (110-130)
src/synthorg/hr/performance/summary.py (2)
  • AgentPerformanceSummary (23-99)
  • extract_performance_summary (125-171)
src/synthorg/api/state.py (1)
  • AppState (42-511)
tests/unit/api/conftest.py (2)
  • agent_registry (201-203)
  • performance_tracker (196-197)
src/synthorg/api/errors.py (1)
  • NotFoundError (167-175)
src/synthorg/api/dto.py (2)
  • ApiResponse (130-158)
  • PaginatedResponse (177-210)
src/synthorg/hr/performance/tracker.py (2)
  • get_snapshot (258-314)
  • get_task_metrics (374-400)
src/synthorg/hr/persistence_protocol.py (1)
  • list_events (36-58)
src/synthorg/persistence/sqlite/hr_repositories.py (1)
  • list_events (100-144)
🔇 Additional comments (9)
src/synthorg/api/controllers/agents.py (6)

39-61: Helper should return the resolved identity (or both ID and name).

The function returns only str(identity.id), but callers like get_agent_performance (line 141) need identity.name to populate the response with the canonical stored name rather than echoing the raw URL segment. Consider returning the full identity object or a tuple of (id, name).


138-147: Response uses raw URL segment instead of canonical agent name.

Line 141 passes the raw agent_name path parameter to extract_performance_summary(). If the URL is /agents/ALICE/performance, the response will contain "agent_name": "ALICE" instead of the canonical stored name. Use the resolved identity's name from the registry lookup.


177-189: Full timeline materialized before pagination; task metrics completely unbounded.

This endpoint loads all data before slicing:

  • Lifecycle events: capped at 10,000
  • Task metrics (line 181-183): no limit at all

For long-lived agents with many task completions, this can cause significant memory pressure. The paginate() call at line 189 only slices after the full merge. Source-level pagination or streaming merge would bound memory usage to the requested page size.


1-36: LGTM!

Imports are well-organized, and the safety cap constant _MAX_LIFECYCLE_EVENTS is appropriately documented with clear rationale.


92-118: LGTM!

The case-insensitive lookup is correctly implemented, and error handling follows the established pattern with proper logging.


197-232: LGTM — previous cap-before-filter issue addressed.

The limit was correctly removed from list_events() to preserve older career milestones. The inline comment (lines 220-222) clearly documents the rationale: career events are infrequent, and capping before filter_career_events() would risk dropping the original HIRED event.

tests/unit/api/fakes.py (1)

138-156: LGTM!

The fake correctly implements the updated protocol:

  • Sorting by timestamp descending matches the SQLite ORDER BY timestamp DESC behavior
  • Limit slicing after filtering ensures correct semantics
  • Test/production parity is maintained as noted in the commit messages
tests/unit/hr/test_activity.py (2)

139-155: LGTM! Past review finding has been addressed.

The test now correctly asserts the stable sort order for identical timestamps (lines 154-155), ensuring lifecycle events appear before task metrics when timestamps are equal.


1-270: Comprehensive test coverage with good structure.

The test suite thoroughly validates both merge_activity_timeline and filter_career_events with appropriate edge cases, ordering checks, and field validation. The helper factories promote test readability and maintainability.

…d_by_preserved

Add metadata kwarg to _make_lifecycle_event helper and assert
career[0].metadata in the test -- previously the test name claimed
to cover metadata but only checked initiated_by and description.

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: 1

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

Inline comments:
In `@tests/unit/hr/test_activity.py`:
- Around line 135-137: Replace the substring checks on task IDs with exact
equality assertions: change the assertions using
timeline[0].related_ids["task_id"] and timeline[1].related_ids["task_id"] from
using "in" to assert they equal the expected IDs ("task-b" and "task-a"
respectively); leave the description assertion (timeline[0].description) as-is
unless you also want exact-match behavior, in which case assert equality against
the expected "failed" string.
🪄 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: 5da9a886-6c3c-4049-be1f-b4659fab4048

📥 Commits

Reviewing files that changed from the base of the PR and between 141157f and ef6113f.

📒 Files selected for processing (1)
  • tests/unit/hr/test_activity.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 Sandbox
  • GitHub Check: Build Backend
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Do NOT use from __future__ import annotations -- Python 3.14 has PEP 649.
Use PEP 758 except syntax: except A, B: (no parentheses) -- ruff enforces this on Python 3.14.
Type hints are required on all public functions, enforced with mypy strict mode.
Use Google-style docstrings on all public classes and functions (enforced by ruff D rules).
Create new objects for immutability, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement.
Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (with model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.
Use Pydantic v2 with adopted conventions: @computed_field for derived values instead of storing + validating redundant fields; use NotBlankStr from core.types 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 (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare create_task.
Keep functions under 50 lines and files under 800 lines.
Handle errors explicitly, never silently swallow them. Validate at system boundaries (user input, external APIs, config files).
Always use logger as the variable name (not _logger, not log) for the logger instance.
Use structured kwargs in logger calls: always logger.info(EVENT, key=value) -- never logger.info("msg %s", val).
All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG is for object creation, internal flow, and entry/exit of key functions.
Use line length of 88 characters (enforced by ruff) for Python code.

Files:

  • tests/unit/hr/test_activity.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 markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow to categorize tests.
Maintain 80% minimum code coverage, enforced in CI. Use @pytest.mark.parametrize for testing similar cases.
Use Hypothesis for property-based testing in Python with @given + @settings. Hypothesis profiles: ci (50 examples, default) and dev (1000 examples). Run dev profile: HYPOTHESIS_PROFILE=dev.
NEVER skip, dismiss, or ignore flaky tests -- always fix them fully and fundamentally. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() instead of widening timing margins. For tasks that must block indefinitely until cancelled, use asyncio.Event().wait() instead of asyncio.sleep(large_number).

Files:

  • tests/unit/hr/test_activity.py
🧠 Learnings (3)
📓 Common learnings
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)
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
📚 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-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
🧬 Code graph analysis (1)
tests/unit/hr/test_activity.py (2)
src/synthorg/core/enums.py (2)
  • Complexity (275-281)
  • TaskType (255-263)
src/synthorg/hr/activity.py (2)
  • filter_career_events (133-159)
  • merge_activity_timeline (110-130)
🔇 Additional comments (2)
tests/unit/hr/test_activity.py (2)

141-157: Tie-break ordering test is now strong and explicit.

Great fix: this now directly verifies lifecycle-before-task ordering when timestamps are identical.


175-251: 🧹 Nitpick | 🔵 Trivial

Consider parametrizing similar career-event filter scenarios.

Several structurally similar cases here can be compacted with @pytest.mark.parametrize to reduce duplication and improve maintainability.

As per coding guidelines "Use @pytest.mark.parametrize for testing similar cases."

⛔ Skipped due to learnings
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.
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.
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-24T10:36:39.226Z
Learning: Applies to tests/**/*.py : Maintain 80% minimum code coverage, enforced in CI. Use `pytest.mark.parametrize` for testing similar cases.
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-24T10:36:39.226Z
Learning: Applies to tests/**/*.py : Use pytest markers: `pytest.mark.unit`, `pytest.mark.integration`, `pytest.mark.e2e`, `pytest.mark.slow` to categorize tests.

related_ids["task_id"] is always the exact string -- no reason
for substring checks when equality is more precise.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 24, 2026 11:34 — with GitHub Actions Inactive
@Aureliolo Aureliolo merged commit 9b75c1d into main Mar 24, 2026
31 checks passed
@Aureliolo Aureliolo deleted the feat/772-agent-performance-api branch March 24, 2026 11:39
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 24, 2026 11:39 — with GitHub Actions Inactive
Aureliolo added a commit that referenced this pull request Mar 24, 2026
…#818)

## Summary

- Add `GET /api/v1/departments/{name}/health` -- aggregates agent count,
utilization, 7-day cost sparkline, average performance and collaboration
scores per department
- Add `GET /api/v1/providers/{name}/health` -- returns health status
(up/degraded/down), error rate, avg response time, and call count from
new `ProviderHealthTracker`
- Add `GET /api/v1/activities` -- paginated org-wide activity feed (REST
fallback for WebSocket) with `type`, `agent_id`, and `last_n_hours`
(24/48/168) filters
- Add `ProviderHealthTracker` service (in-memory, append-only,
asyncio.Lock-protected) with `ProviderHealthRecord` model and
`ProviderHealthSummary` (health_status is a `@computed_field`)
- 58 unit tests across 4 test files covering happy paths, error
fallbacks, boundary values, concurrency, and model validation

## Design decisions

- **Provider health as separate endpoint** (`/providers/{name}/health`)
rather than extending existing `GET /providers/{name}` -- follows the
`/agents/{name}/performance` pattern, avoids coupling config data with
real-time health, avoids expensive health computation on provider list
calls
- **Reuses `ActivityEvent`** from `hr/activity.py` +
`merge_activity_timeline()` for the org-wide feed -- avoids model
fragmentation with per-agent activity endpoint from #811
- **`ProviderHealthSummary.health_status`** is a `@computed_field`
derived from `error_rate_percent_24h` -- per CLAUDE.md convention,
prevents inconsistent state
- **`asyncio.TaskGroup`** used for parallel fan-out in department health
(active count + cost records + agent ID resolution run concurrently;
snapshots fan out per-agent)

## Pre-PR review

Pre-reviewed by 6 agents (code-reviewer, test-analyzer,
issue-resolution-verifier, silent-failure-hunter,
async-concurrency-reviewer, type-design-analyzer). 15 findings
addressed:
- Refactored 109-line function into <50-line helpers
- Converted sequential loops to TaskGroup for parallel I/O
- Added MemoryError/RecursionError re-raise guards
- Fixed type annotation (`tuple[object, ...]` to `tuple[CostRecord,
...]`)
- Added `active_agent_count <= agent_count` model validator
- Added 19 additional tests for uncovered paths

## Test plan

- [x] `uv run python -m pytest tests/unit/providers/test_health.py -m
unit -n auto` -- 30 tests pass
- [x] `uv run python -m pytest
tests/unit/api/controllers/test_departments_health.py -m unit -n auto`
-- 9 tests pass
- [x] `uv run python -m pytest
tests/unit/api/controllers/test_provider_health.py -m unit -n auto` -- 6
tests pass
- [x] `uv run python -m pytest
tests/unit/api/controllers/test_activities.py -m unit -n auto` -- 12
tests pass (incl. graceful degradation)
- [x] `uv run mypy src/ tests/` -- 0 errors
- [x] `uv run ruff check src/ tests/` -- 0 errors
- [x] Full suite: 10435 passed, 5 skipped
- [x] OpenAPI schema exported with new endpoints verified

Closes #773

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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: agent performance metrics and activity timeline endpoints

1 participant