Skip to content

feat: add meeting scheduler, event-triggered meetings, and Go CLI lint fixes#407

Merged
Aureliolo merged 17 commits intomainfrom
feat/meeting-scheduler
Mar 14, 2026
Merged

feat: add meeting scheduler, event-triggered meetings, and Go CLI lint fixes#407
Aureliolo merged 17 commits intomainfrom
feat/meeting-scheduler

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

@Aureliolo Aureliolo commented Mar 14, 2026

Summary

  • Meeting scheduler service (MeetingScheduler) — periodic scheduling via asyncio tasks + event-triggered meetings on demand, with lifecycle wiring into _safe_startup/_safe_shutdown
  • MeetingFrequency enum replaces NotBlankStr — compile-time validation, Pydantic coerces strings from YAML
  • ParticipantResolver protocol + RegistryParticipantResolver — resolves department names, agent names, "all", literal IDs to agent ID tuples
  • REST APIGET /meetings (paginated + filters), GET /meetings/{id}, POST /meetings/trigger with bounded TriggerMeetingRequest
  • WebSocketmeetings channel, meeting.started/completed/failed events, wired publisher from channels_plugin
  • FrontendMeetingLogsPage (DataTable + detail sidebar + trigger dialog + canWrite guard), reactive sidebar via store, Pinia store with WS re-fetch
  • Go CLI fixes — golangci-lint v2 config (gofmt→formatter, removed gosimple), all errcheck/gofmt/staticcheck violations fixed, Windows PowerShell bash shell fix for go test

Closes #264

Test plan

  • uv run pytest tests/unit/communication/meeting/ -n auto — scheduler, frequency, participant, config tests
  • uv run pytest tests/unit/api/controllers/test_meetings.py -n auto — API endpoints + validation
  • npm --prefix web run test — store + WS handler + re-fetch tests
  • uv run pytest tests/ -n auto --cov=ai_company --cov-fail-under=80 — full suite + coverage
  • uv run mypy src/ tests/ — type checking
  • npm --prefix web run type-check — frontend types
  • cd cli && golangci-lint run — Go lint clean

Review coverage

Pre-reviewed by 16 local agents + 4 external reviewers (Copilot, Gemini, CodeRabbit, Greptile) across 5+ review rounds. 60+ findings addressed.

Implement the meeting scheduler service that bridges meeting configuration
and meeting execution. Adds frequency-based periodic scheduling (via
asyncio tasks) and event-triggered meetings (on-demand API).

New modules:
- frequency.py: MeetingFrequency enum (daily/weekly/bi_weekly/per_sprint_day/monthly)
- participant.py: ParticipantResolver protocol + RegistryParticipantResolver
- scheduler.py: MeetingScheduler background service (start/stop/trigger_event)

API changes:
- GET /meetings: list meeting records with status/type filters + pagination
- GET /meetings/{id}: get meeting record by ID (or 404)
- POST /meetings/trigger: trigger event-based meetings
- Added "meetings" WebSocket channel + meeting event types
- AppState wired with meeting_orchestrator + meeting_scheduler

Config change:
- MeetingTypeConfig.frequency: NotBlankStr -> MeetingFrequency enum
  (Pydantic coerces strings transparently, YAML configs work unchanged)

Frontend:
- Meeting TypeScript types + API client + Pinia store
- MeetingLogsPage: DataTable with filters, detail sidebar, trigger dialog
Pre-reviewed by 14 agents, 19 findings addressed:

- Wrap event publisher in try/except (data-loss prevention)
- Add error recovery in _run_periodic loop body
- Extract _publish_meeting_event helper (function length)
- Split _execute_meeting try/except for clarity
- Replace assert with proper TypeError guard
- Merge double context.items() iteration
- Add exhaustiveness check for frequency mapping
- Log before raising SchedulerAlreadyRunningError
- Add logger usage in meetings controller (404 + trigger)
- Bound TriggerMeetingRequest.context (max keys/lengths)
- Remove ambiguous pass-through debug log
- Add MeetingPhase type union in frontend types.ts
- Re-fetch full record on WS meeting events (not just status)
- Keep trigger dialog open on error
- Update CLAUDE.md (package structure, stores, events)
- Fix assert_awaited_with -> assert_awaited_once_with
- Add NoParticipantsResolvedError test
- Remove unnecessary async on sync static method tests
Critical fixes:
- Wire scheduler start()/stop() into app lifecycle (_safe_startup/_safe_shutdown)
- Add 5 missing meeting event constants to CLAUDE.md logging section
- Add console.warn in meetings store empty catch block

Major fixes:
- Replace MEETING_SCHEDULER_ERROR with MEETING_NOT_FOUND for 404 path
- Use TaskGroup for parallel meeting execution in trigger_event
- Fix TriggerMeetingRequest.context type: Record<string, string>
- Replace _tasks list mutation with immutable patterns
- Sync selectedMeeting on WS refresh
- Validate context list values in participant resolver
- Re-raise CancelledError instead of swallowing in _run_periodic
- Add Final annotations to all channel constants
- Add Meeting Scheduler section to communication design doc
- Add meeting/ subpackage entry in CLAUDE.md package structure
- Add channel guard to handleWsEvent
- Document leader selection convention
- Make resolver raise NoParticipantsResolvedError on empty results
- Rewrite MeetingLogsPage.test.ts to test store, not duplicate
- Add fetchMeetings/triggerMeeting store tests
- Add TriggerMeetingRequest validation boundary tests

Medium fixes:
- Add _publish_meeting_event error + MemoryError re-raise tests
- Fix test name typo: callsmock → calls_mock
- Fix comment typo: meetingmock_scheduler → meeting_scheduler
- Remove unnecessary async from mock_orchestrator fixture
- Use comprehensions in _build_default_agenda
- Move NoParticipantsResolvedError import to top-level
- Fix errors.py module docstring overclaim
- Add per_sprint_day to coercion test parametrize
- Pass data.context directly instead of `or None`
- Extract _create_app_without_meetings helper
- Replace inline styles with Tailwind classes in MeetingLogsPage
- Add stop_noop_when_not_running test
…solver

When a context value is present but is neither str nor list/tuple,
explicitly log a warning and return empty instead of silently falling
through to registry lookups. Prevents misresolution via pass-through
for internal callers using dict[str, Any] context.
- Widen TriggerMeetingRequest.context to dict[str, str | list[str]]
  so API callers can pass multi-value participant entries; update
  validator to handle list items; align frontend TS type
- Extract _resolve_participants and _run_and_publish from
  _execute_meeting to stay under 50-line function limit
- Replace f-string status formatting in _publish_meeting_event with
  explicit _STATUS_TO_WS_EVENT mapping (no API layer import)
- Guard trigger_event with self._config.enabled check
- Assert mock_scheduler.trigger_event called with expected args
- Add exact-boundary test for max key/value lengths
- Add docstring to test_accepts_enum_value
- Rename MeetingLogsPage.test.ts to .store-integration.test.ts
dict[str, str] is not assignable to dict[str, str | list[str]] under
mypy strict mode (dict is invariant in value type). Add explicit type
annotations on test variables.
- Remove unreachable "in_progress" → "meeting.started" mapping from
  _STATUS_TO_WS_EVENT (status is always terminal post-run_meeting)
- Wrap _build_default_agenda in exception guard so failures don't
  propagate to TaskGroup and crash the entire trigger_event call
- Execute meeting immediately on first periodic loop entry, then
  sleep (run-now-then-repeat pattern avoids missing today's meeting)
- Fix "e.g." → "e.g.," in CLAUDE.md event names line
- Add DEBUG log before literal ID fallback in participant resolver
- Add test for oversized context list items in TriggerMeetingRequest
- Add meeting scheduler lifecycle test (_safe_startup/_safe_shutdown)
- Use pydantic.ValidationError in config frequency tests
- Add test_context_tuple_value for tuple context branch
- Batch triggerMeeting store updates (single array spread)
- Parametrize test_all_channels_contains_expected
Replace the local selected ref with a computed over
meetingStore.selectedMeeting so the sidebar reflects real-time
WS refresh updates instead of holding a stale object reference.
…tore

- Strip whitespace-only context strings in participant resolver and
  log warning instead of passing blank IDs downstream
- Log periodic task errors during scheduler stop() instead of
  silently discarding them via return_exceptions=True
- Add _MAX_CONTEXT_LIST_ITEMS=50 limit to TriggerMeetingRequest
  validator with test coverage
- Fix meeting_client fixture return type: Generator → Iterator
- Add loading state to triggerMeeting store action for consistent UI
- Move gofmt from linters to formatters section in .golangci.yml
  (v2 reclassified it, causing "gofmt is a formatter" config error)
- Force bash shell for go test step to prevent PowerShell from
  splitting -coverprofile=coverage.out into separate arguments
- Format list context values as comma-joined strings in agenda
  descriptions instead of Python repr (e.g. "a, b" not "['a', 'b']")
- Re-raise MemoryError/RecursionError in _run_periodic before the
  broad except Exception handler
- Use arrow wrapper for ErrorBoundary @Retry to prevent immediate
  invocation on render
- Add optional chaining on decisions/action_items/contributions
  .length in sidebar template
- Guard "Trigger Meeting" button with v-if="canWrite" to match
  write-action pattern used in TaskBoardPage
Copilot AI review requested due to automatic review settings March 14, 2026 17:00
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 14, 2026

Dependency Review

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

Scanned Files

None

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 14, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Summary by CodeRabbit

Release Notes

  • New Features

    • Added comprehensive meeting scheduling system with frequency-based (daily, weekly, monthly) and event-triggered meeting support
    • Introduced meeting management API with endpoints for listing, retrieving, and triggering meetings with context filters
    • Added real-time meeting status updates via WebSocket channels with lifecycle events (started, completed, failed)
    • New Meeting Logs page with status filtering, meeting details sidebar, and event triggering capability
  • Bug Fixes

    • Improved CLI error handling across multiple commands

Walkthrough

Adds a Meeting Scheduler subsystem (frequency-based and event-triggered), participant resolution, API endpoints and WS publishing, AppState/lifecycle wiring, observability events, frontend types/endpoints/store/UI, docs, and extensive backend/frontend tests.

Changes

Cohort / File(s) Summary
Scheduler Core
src/ai_company/communication/meeting/scheduler.py, src/ai_company/communication/meeting/frequency.py, src/ai_company/communication/meeting/participant.py, src/ai_company/communication/meeting/errors.py, src/ai_company/communication/meeting/__init__.py
New MeetingScheduler implementation, MeetingFrequency enum and converter, ParticipantResolver protocol and RegistryParticipantResolver, new scheduler-specific errors and public exports.
API Lifecycle & State
src/ai_company/api/app.py, src/ai_company/api/state.py
App lifecycle extended to accept/start/stop meeting_scheduler and meeting_orchestrator; AppState stores and exposes meeting services; startup/shutdown wiring updated and meeting WS publisher auto-wired.
API Surface & Controllers
src/ai_company/api/controllers/meetings.py, src/ai_company/api/channels.py, src/ai_company/api/ws_models.py
Implemented MeetingController endpoints (list/get/trigger) with TriggerMeetingRequest validation; added CHANNEL_MEETINGS, annotated channel constants as Final[str], and added WS meeting event types.
Observability
src/ai_company/observability/events/meeting.py
Added meeting scheduler and API event constants (started/stopped/triggered/participants_resolved/no_participants/error/not_found).
Communication Config & Docs
src/ai_company/communication/config.py, docs/design/communication.md, CLAUDE.md
MeetingTypeConfig.frequency now uses MeetingFrequency; design doc and CLAUDE notes added describing scheduler behavior and logging guidance.
Frontend API, Types & Store
web/src/api/types.ts, web/src/api/endpoints/meetings.ts, web/src/stores/meetings.ts
Added meeting domain types, endpoints (list/get/trigger), and a Pinia meeting store with fetch/trigger/WS handling.
Frontend UI & Styles
web/src/views/MeetingLogsPage.vue, web/src/styles/theme.ts
New Meeting Logs UI with WS integration, trigger dialog, detail sidebar; status type/colors extended for meeting statuses.
Controllers Tests & Integration
tests/unit/api/controllers/test_meetings.py, tests/integration/communication/test_meeting_integration.py, tests/unit/api/test_app.py, tests/unit/api/test_channels.py
Controller and lifecycle tests updated/added to cover listing, get, trigger, validation, and scheduler lifecycle integration.
Scheduler Unit Tests
tests/unit/communication/meeting/*
Comprehensive tests for frequency, scheduler, participant resolver, and config validation.
Frontend Tests
web/src/__tests__/*
Added/updated frontend tests for meeting store, endpoints, and integration; removed an obsolete view unit test.
CLI & Tooling
.github/workflows/cli.yml, cli/.golangci.yml, cli/cmd/*, cli/internal/*
CI shell explicitness and linter formatter change; many CLI files changed to ignore returned errors from fmt/Close/Remove calls (cleanup/error suppression).

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Scheduler as MeetingScheduler
    participant Resolver as ParticipantResolver
    participant Registry as AgentRegistry
    participant Orchestrator as MeetingOrchestrator
    participant WS as WsPublisher

    Client->>Scheduler: trigger_event(event_name, context)
    Scheduler->>Scheduler: find matching meeting types
    loop per meeting type
        Scheduler->>Resolver: resolve(participant_refs, context)
        Resolver->>Registry: query (list_active / list_by_department / get_by_name)
        Registry-->>Resolver: agent IDs
        Resolver-->>Scheduler: deduplicated participant IDs
        Scheduler->>Scheduler: build default agenda from context
        Scheduler->>Orchestrator: execute(meeting_type, agenda, participants)
        Orchestrator-->>Scheduler: MeetingRecord
        Scheduler->>WS: publish meeting.started/completed/failed
    end
    Scheduler-->>Client: tuple[MeetingRecord, ...]
Loading
sequenceDiagram
    participant System
    participant Scheduler as MeetingScheduler
    participant Async as AsyncIO
    participant Orchestrator as MeetingOrchestrator

    System->>Scheduler: start()
    Scheduler->>Scheduler: mark running
    Scheduler->>Async: spawn periodic tasks per frequency
    loop intervals
        Async->>Scheduler: _run_periodic(meeting_type)
        Scheduler->>Orchestrator: execute(...)
        Orchestrator-->>Scheduler: MeetingRecord
        Scheduler->>Scheduler: publish WS event
    end
    System->>Scheduler: stop()
    Scheduler->>Async: cancel tasks
    Async-->>Scheduler: tasks cancelled
    Scheduler->>Scheduler: mark stopped
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.85% which is insufficient. The required threshold is 80.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 accurately summarizes the three main changes: meeting scheduler implementation, event-triggered meetings support, and Go CLI linting fixes.
Description check ✅ Passed The description provides clear, relevant details about the PR including the meeting scheduler service, API endpoints, WebSocket integration, frontend implementation, and CLI fixes, all directly related to the changeset.
Linked Issues check ✅ Passed The PR fully implements all requirements from #264: MeetingScheduler service with periodic and event-triggered meetings, MeetingFrequency enum, ParticipantResolver, integration with MeetingOrchestrator, and API/WebSocket/frontend support.
Out of Scope Changes check ✅ Passed All changes are directly scoped to meeting scheduler implementation (#264), API integration, frontend support, and Go CLI linting—no unrelated modifications detected across the changeset.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/meeting-scheduler
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feat/meeting-scheduler
📝 Coding Plan
  • Generate coding plan for human review comments

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

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

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

This pull request introduces a comprehensive meeting management system, enabling scheduled and event-driven agent meetings. It provides the core backend services for scheduling, participant resolution, and API exposure, alongside a fully functional frontend interface for monitoring and initiating these meetings. The changes enhance the platform's ability to orchestrate multi-agent interactions and provide visibility into their collaborative processes.

Highlights

  • Meeting Scheduler Service: Introduced a new MeetingScheduler service that manages both periodic (frequency-based) and event-triggered meetings. It integrates with the application's lifecycle for proper startup and shutdown.
  • Dynamic Participant Resolution: Implemented a ParticipantResolver protocol and RegistryParticipantResolver concrete class to dynamically resolve meeting participants from configuration references (e.g., department names, 'all' agents, context-based values) into concrete agent IDs.
  • Meetings API Endpoints: Developed new REST API endpoints for meetings, including GET /meetings for paginated and filtered lists, GET /meetings/{id} for fetching a specific meeting record, and POST /meetings/trigger for initiating event-based meetings with context-aware validation.
  • Frontend Integration: Added a new MeetingLogsPage in the frontend, featuring a DataTable for displaying meeting records, a detail sidebar for in-depth information, and a dialog to trigger event-based meetings. A Pinia store handles meeting state, API interactions, and WebSocket event processing.
  • Enhanced Meeting Configuration: Updated MeetingTypeConfig to use a new MeetingFrequency enum for compile-time validation and transparent Pydantic coercion of frequency strings from YAML, replacing the previous NotBlankStr.
  • WebSocket Events for Meetings: Extended WebSocket functionality with a dedicated meetings channel and new event types (meeting.started, meeting.completed, meeting.failed) to provide real-time updates on meeting lifecycle.
Changelog
  • CLAUDE.md
    • Updated the directory structure documentation to reflect the new meeting/ module under communication/.
    • Updated the web/stores/ description to include the new meetings store.
    • Expanded the list of recognized event names in the observability guidelines to include new meeting scheduler events.
  • cli/.golangci.yml
    • Removed gosimple and gofmt from the linters section.
    • Added gofmt to the formatters section, aligning with updated golangci-lint configuration practices.
  • docs/design/communication.md
    • Added a new section detailing the MeetingScheduler, covering frequency-based scheduling, event-triggered meetings, and participant resolution mechanisms.
  • src/ai_company/api/app.py
    • Imported MeetingOrchestrator and MeetingScheduler.
    • Modified _build_lifecycle to accept meeting_scheduler as an argument.
    • Updated _safe_shutdown to include stopping the meeting_scheduler.
    • Extended _cleanup_on_failure to handle meeting_scheduler cleanup during startup failures.
    • Updated _safe_startup to include starting the meeting_scheduler.
    • Modified create_app to accept meeting_orchestrator and meeting_scheduler as optional parameters and pass them to AppState.
  • src/ai_company/api/channels.py
    • Added CHANNEL_MEETINGS as a new WebSocket channel constant.
    • Included CHANNEL_MEETINGS in the ALL_CHANNELS tuple.
  • src/ai_company/api/controllers/meetings.py
    • Transformed the file from a stub to a functional meeting controller.
    • Added TriggerMeetingRequest Pydantic model with comprehensive validation for event context.
    • Implemented list_meetings endpoint with pagination and filtering by status and meeting type.
    • Implemented get_meeting endpoint to retrieve a specific meeting record by ID, returning 404 if not found.
    • Added trigger_meeting endpoint to initiate event-based meetings via the scheduler, requiring write access.
  • src/ai_company/api/state.py
    • Imported MeetingOrchestrator and MeetingScheduler.
    • Added _meeting_orchestrator and _meeting_scheduler to AppState's __slots__.
    • Updated AppState.__init__ to accept meeting_orchestrator and meeting_scheduler.
    • Added properties meeting_orchestrator and meeting_scheduler to AppState with service requirement checks.
  • src/ai_company/api/ws_models.py
    • Added new WsEventType members: MEETING_STARTED, MEETING_COMPLETED, and MEETING_FAILED.
  • src/ai_company/communication/config.py
    • Imported MeetingFrequency.
    • Changed the type of MeetingTypeConfig.frequency from NotBlankStr | None to MeetingFrequency | None.
  • src/ai_company/communication/meeting/init.py
    • Exported new error types: MeetingSchedulerError, NoParticipantsResolvedError, SchedulerAlreadyRunningError.
    • Exported MeetingFrequency.
    • Exported ParticipantResolver and RegistryParticipantResolver.
    • Exported MeetingScheduler.
  • src/ai_company/communication/meeting/errors.py
    • Added MeetingSchedulerError as a base exception for scheduler-related issues.
    • Introduced NoParticipantsResolvedError for cases where no participants can be resolved.
    • Added SchedulerAlreadyRunningError to indicate attempts to start an already running scheduler.
  • src/ai_company/communication/meeting/frequency.py
    • Added new file defining the MeetingFrequency StrEnum for recurrence schedules.
    • Implemented frequency_to_seconds utility function to convert enum values to time intervals.
  • src/ai_company/communication/meeting/participant.py
    • Added new file defining the ParticipantResolver protocol for resolving agent IDs.
    • Implemented RegistryParticipantResolver which resolves participants using a five-step cascade (context, 'all', department, agent name, literal ID).
  • src/ai_company/communication/meeting/scheduler.py
    • Added new file implementing the MeetingScheduler class.
    • Provided functionality to start and stop periodic tasks for frequency-based meetings.
    • Implemented trigger_event to execute event-triggered meetings.
    • Integrated participant resolution and agenda building into meeting execution.
    • Added logic to publish WebSocket events for meeting status changes.
  • src/ai_company/observability/events/meeting.py
    • Added new event constants for meeting scheduler lifecycle (started, stopped, periodic/event triggered, participants resolved/not resolved, error) and API-level events (not found).
  • tests/integration/communication/test_meeting_integration.py
    • Updated test_meeting_type_config_has_protocol_config to use MeetingFrequency.DAILY instead of a string literal.
  • tests/unit/api/controllers/test_meetings.py
    • Refactored meeting controller tests to use mock orchestrator and scheduler.
    • Added tests for list_meetings with and without filters.
    • Added tests for get_meeting by ID, including 404 scenarios.
    • Added tests for trigger_meeting endpoint, verifying scheduler interaction.
    • Added tests for TriggerMeetingRequest validation, covering context size and content bounds.
    • Added a test case to ensure 503 is returned when meeting services are not configured.
  • tests/unit/api/test_app.py
    • Updated calls to _safe_startup and _safe_shutdown to include the meeting_scheduler parameter.
    • Added a new test test_meeting_scheduler_lifecycle to verify that the scheduler's start/stop methods are called during app lifecycle.
  • tests/unit/api/test_channels.py
    • Updated test_all_channels_contains_expected to include CHANNEL_MEETINGS.
    • Adjusted test_all_channels_has_six_entries to test_all_channels_has_seven_entries to reflect the new channel count.
  • tests/unit/communication/conftest.py
    • Imported MeetingFrequency.
    • Updated sample_meeting_type to use MeetingFrequency.PER_SPRINT_DAY.
  • tests/unit/communication/meeting/test_config_frequency.py
    • Added new file with unit tests for MeetingTypeConfig's frequency field, verifying enum acceptance, string coercion, and mutual exclusivity with trigger.
  • tests/unit/communication/meeting/test_frequency.py
    • Added new file with unit tests for the MeetingFrequency enum and its frequency_to_seconds conversion function.
  • tests/unit/communication/meeting/test_participant.py
    • Added new file with unit tests for RegistryParticipantResolver, covering resolution of 'all' agents, departments, agent names, context values, literal IDs, and deduplication.
  • tests/unit/communication/meeting/test_scheduler.py
    • Added new file with extensive unit tests for MeetingScheduler, covering startup/shutdown, periodic task creation, event triggering, participant resolution, error handling, and event publishing.
  • tests/unit/communication/test_config.py
    • Imported MeetingFrequency.
    • Updated TestMeetingTypeConfigConstruction and TestMeetingsConfigValidation to use MeetingFrequency enum values instead of string literals for frequency.
    • Adjusted validation tests for MeetingTypeConfig to reflect the new enum type for frequency.
  • web/src/tests/stores/meetings.test.ts
    • Added new file with unit tests for the useMeetingStore Pinia store, covering initial state, WebSocket event handling, data fetching, and meeting triggering.
  • web/src/tests/views/MeetingLogsPage.store-integration.test.ts
    • Added new file with integration tests for MeetingLogsPage and its interaction with the meetings store.
  • web/src/tests/views/MeetingLogsPage.test.ts
    • Removed the old stub test file for MeetingLogsPage.
  • web/src/api/endpoints/meetings.ts
    • Added new file defining API client functions for listMeetings, getMeeting, and triggerMeeting.
  • web/src/api/types.ts
    • Added new TypeScript types for meeting-related data structures, including MeetingStatus, MeetingProtocolType, MeetingAgendaItem, MeetingAgenda, MeetingPhase, MeetingContribution, ActionItem, MeetingMinutes, MeetingRecord, MeetingFilters, and TriggerMeetingRequest.
    • Updated WsChannel to include meetings.
    • Updated WsEventType to include meeting.started, meeting.completed, and meeting.failed.
  • web/src/stores/meetings.ts
    • Added new file defining the useMeetingStore Pinia store.
    • Implemented state management for meeting records, selected meeting, loading status, and errors.
    • Provided actions for fetching meetings, fetching a single meeting, and triggering meetings.
    • Included a handler for WebSocket events to update meeting status in real-time.
  • web/src/styles/theme.ts
    • Updated the Status type to include MeetingStatus.
    • Added new color mappings for scheduled and budget_exhausted meeting statuses.
  • web/src/views/MeetingLogsPage.vue
    • Replaced the 'Coming Soon' placeholder with a fully functional MeetingLogsPage component.
    • Implemented a DataTable to display meeting records with sorting and status filtering.
    • Added a Sidebar for detailed viewing of selected meeting records, including summary, decisions, action items, and contributions.
    • Integrated a 'Trigger Meeting' dialog for initiating event-based meetings.
    • Connected to the useMeetingStore and useWebSocketStore for data management and real-time updates.
Ignored Files
  • Ignored by pattern: .github/workflows/** (1)
    • .github/workflows/cli.yml
Activity
  • The pull request has undergone extensive pre-review by 16 local agents and 4 external reviewers (Copilot, Gemini, CodeRabbit, Greptile) across 4 review rounds, leading to over 50 addressed findings.
Using Gemini Code Assist

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

Invoking Gemini

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

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

Customization

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

Limitations & Feedback

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

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

Footnotes

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

@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 14, 2026 17:01 — 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 a significant and well-architected feature for meeting scheduling, including periodic and event-triggered meetings. The changes span the entire stack, from backend services and API endpoints to a new frontend UI with a corresponding Pinia store and WebSocket integration. The code is generally of high quality, with comprehensive tests and updated documentation. My review has identified a critical syntax error in the new scheduler module that needs to be addressed, as well as a couple of opportunities for performance improvements in the new meetings API controller.

Comment on lines +245 to +246
except MemoryError, RecursionError:
raise
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

critical

The syntax except MemoryError, RecursionError: is from Python 2 and will raise a SyntaxError in Python 3. To catch multiple exceptions, they should be enclosed in a tuple. This is a critical bug that will prevent the periodic meeting task from handling these errors correctly.

                except (MemoryError, RecursionError):
                    raise

Comment on lines +403 to +404
except MemoryError, RecursionError:
raise
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

critical

Similar to another part of this file, the syntax except MemoryError, RecursionError: is invalid in Python 3 and will cause a SyntaxError. You should use a tuple to catch multiple exception types. This will prevent the event publisher from correctly handling these specific errors.

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

Comment on lines +111 to +114
if status is not None:
records = tuple(r for r in records if r.status == status)
if meeting_type is not None:
records = tuple(r for r in records if r.meeting_type_name == meeting_type)
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 for filtering records is inefficient as it reconstructs the records tuple for each applied filter. For a large number of records, this can lead to unnecessary memory allocations and processing. A more performant approach would be to chain generator expressions for filtering and only materialize the final list or tuple once.

Suggested change
if status is not None:
records = tuple(r for r in records if r.status == status)
if meeting_type is not None:
records = tuple(r for r in records if r.meeting_type_name == meeting_type)
records_iter = orchestrator.get_records()
if status is not None:
records_iter = (r for r in records_iter if r.status == status)
if meeting_type is not None:
records_iter = (r for r in records_iter if r.meeting_type_name == meeting_type)
page, meta = paginate(tuple(records_iter), offset=offset, limit=limit)

Comment on lines +137 to +142
for record in records:
if record.meeting_id == meeting_id:
return Response(
content=ApiResponse[MeetingRecord](data=record),
status_code=200,
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

This implementation iterates through all meeting records to find one by its ID, which is an O(N) operation. This can become a performance bottleneck if the number of meetings grows large. For better performance, consider using a more direct lookup method. A potential long-term improvement would be to have the MeetingOrchestrator provide a method like get_record_by_id(meeting_id) that uses a dictionary/hash map for O(1) lookups.

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 14, 2026

Codecov Report

❌ Patch coverage is 83.03887% with 48 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.65%. Comparing base (7a9e516) to head (17d0f35).
⚠️ Report is 2 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/ai_company/communication/meeting/scheduler.py 73.45% 26 Missing and 4 partials ⚠️
src/ai_company/api/app.py 57.69% 9 Missing and 2 partials ⚠️
...rc/ai_company/communication/meeting/participant.py 85.00% 4 Missing and 2 partials ⚠️
src/ai_company/api/controllers/meetings.py 98.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #407      +/-   ##
==========================================
- Coverage   93.79%   93.65%   -0.15%     
==========================================
  Files         466      469       +3     
  Lines       21944    22211     +267     
  Branches     2103     2141      +38     
==========================================
+ Hits        20582    20801     +219     
- Misses       1058     1097      +39     
- Partials      304      313       +9     

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

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

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Implements the missing “meeting scheduler” layer and wires it end-to-end: backend scheduling/triggering + API/WS exposure + frontend meeting logs UI, along with supporting type/config changes and CI tweaks.

Changes:

  • Added MeetingScheduler (periodic + event-triggered meetings), MeetingFrequency, and participant resolution (ParticipantResolver / RegistryParticipantResolver) with lifecycle wiring.
  • Exposed meetings via REST (GET /meetings, GET /meetings/{id}, POST /meetings/trigger) and WebSocket events/channel; added observability event constants.
  • Implemented MeetingLogsPage UI + Pinia store + API client + TS types; updated related tests and CLI CI lint/workflow config.

Reviewed changes

Copilot reviewed 34 out of 34 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
web/src/views/MeetingLogsPage.vue Replaces “Coming Soon” with full meeting logs table, detail sidebar, WS wiring, filtering, and trigger dialog.
web/src/styles/theme.ts Extends status color typing/mapping to include meeting statuses.
web/src/stores/meetings.ts Adds Pinia meetings store (fetch/list/get/trigger + WS refresh handler).
web/src/api/types.ts Adds meeting domain TS types + WS channel/event type extensions.
web/src/api/endpoints/meetings.ts Adds meetings REST client endpoints (list/get/trigger).
web/src/tests/views/MeetingLogsPage.test.ts Removes old “Coming Soon” view test.
web/src/tests/views/MeetingLogsPage.store-integration.test.ts Adds minimal store integration test scaffold.
web/src/tests/stores/meetings.test.ts Adds store behavior tests (WS refresh, fetch, trigger dedup, error handling).
tests/unit/communication/test_config.py Updates meeting config tests to use MeetingFrequency.
tests/unit/communication/meeting/test_scheduler.py Adds unit tests for scheduler behavior (periodic lifecycle, triggers, publishing).
tests/unit/communication/meeting/test_participant.py Adds tests for participant resolver resolution cascade + dedup + context behavior.
tests/unit/communication/meeting/test_frequency.py Adds tests for MeetingFrequency and frequency_to_seconds.
tests/unit/communication/meeting/test_config_frequency.py Adds tests for config coercion/validation with MeetingFrequency.
tests/unit/communication/conftest.py Updates shared fixtures to use MeetingFrequency.
tests/unit/api/test_channels.py Extends WS channel set to include meetings.
tests/unit/api/test_app.py Updates lifecycle tests to cover scheduler start/stop and new startup/shutdown signatures.
tests/unit/api/controllers/test_meetings.py Adds comprehensive controller tests for list/get/trigger + request validation bounds.
tests/integration/communication/test_meeting_integration.py Updates integration test config to use MeetingFrequency.
src/ai_company/observability/events/meeting.py Adds scheduler/API observability event constants.
src/ai_company/communication/meeting/scheduler.py Implements MeetingScheduler with periodic tasks, event trigger execution, publishing.
src/ai_company/communication/meeting/participant.py Introduces participant resolver protocol + registry-based resolver implementation.
src/ai_company/communication/meeting/frequency.py Introduces MeetingFrequency and interval conversion helper.
src/ai_company/communication/meeting/errors.py Adds scheduler-specific error types.
src/ai_company/communication/meeting/init.py Re-exports new meeting scheduler/resolver/frequency symbols.
src/ai_company/communication/config.py Switches meeting type frequency field to `MeetingFrequency
src/ai_company/api/ws_models.py Adds meeting WS event types.
src/ai_company/api/state.py Adds meeting orchestrator/scheduler to AppState with 503 enforcement helpers.
src/ai_company/api/controllers/meetings.py Implements meetings controller + bounded trigger request validation.
src/ai_company/api/channels.py Adds meetings WS channel constant and includes it in ALL_CHANNELS.
src/ai_company/api/app.py Wires meeting scheduler into startup/shutdown and app factory signature.
docs/design/communication.md Documents scheduler responsibilities, modes, and participant resolution rules.
cli/.golangci.yml Updates golangci-lint v2 config (formatters section, removes incompatible linter entries).
CLAUDE.md Updates repo architecture/docs and event-constant guidance to include meeting scheduler events.
.github/workflows/cli.yml Fixes Go workflow shell usage for cross-platform execution.

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

Comment on lines +151 to +155
if isinstance(result, Exception):
logger.warning(
MEETING_SCHEDULER_ERROR,
note="periodic task error during shutdown",
exc_info=result,
)
resolved = []
elif isinstance(val, (list, tuple)):
resolved = [v for v in val if isinstance(v, str) and v.strip()]
Comment on lines +45 to +60
onMounted(async () => {
try {
if (authStore.token && !wsStore.connected) {
wsStore.connect(authStore.token)
}
wsStore.subscribe(['meetings'])
wsStore.onChannelEvent('meetings', meetingStore.handleWsEvent)
} catch (err) {
console.error('WebSocket setup failed:', sanitizeForLog(err))
}
try {
await meetingStore.fetchMeetings()
} catch (err) {
console.error('Initial data fetch failed:', sanitizeForLog(err))
}
})
@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 14, 2026

Greptile Summary

This PR delivers a well-structured meeting scheduler system — periodic asyncio tasks, event-triggered meetings, a REST/WS API, and a Vue frontend — on top of a clean MeetingFrequency enum and ParticipantResolver protocol. The Go CLI lint fixes are routine and correct. Two functional issues need attention before merge.

Key findings:

  • meeting.started WS payload missing meeting_id_publish_started_event fires before any meeting ID exists, so the frontend's handleWsEvent case for meeting.started always receives undefined for payload.meeting_id and silently skips _refreshMeeting. The event is emitted and received but causes no UI update, making it dead code on the frontend.
  • POST /meetings/trigger blocks until all meetings completetrigger_event awaits orchestrator.run_meeting() for every matched type inside an asyncio.TaskGroup. LLM-driven meetings can take many minutes; the HTTP connection will time out before the response is sent. The endpoint should fire tasks in the background and return 202 Accepted immediately, letting the meetings WS channel deliver completion events.
  • Private _event_publisher mutated directly in create_app — suppressed with noqa: SLF001; a set_event_publisher method on MeetingScheduler would remove the need to bypass encapsulation.
  • _make_meeting_publisher callback lacks error handling — unlike _make_expire_callback, it does not wrap channels_plugin.publish() in try/except; this is safe because callers handle it, but the inconsistency could mislead future maintainers.

Confidence Score: 2/5

  • Not safe to merge as-is — the trigger endpoint will time out on all real workloads and the meeting.started WS event is non-functional
  • Two logic-level issues: the trigger API blocks the HTTP connection for the full duration of LLM meetings (minutes), making the endpoint unusable in practice; and the meeting.started WS event's missing meeting_id means the frontend real-time update path for in-progress meetings is silently broken. The rest of the implementation is high quality.
  • src/ai_company/api/controllers/meetings.py (blocking trigger endpoint) and src/ai_company/communication/meeting/scheduler.py + web/src/stores/meetings.ts (meeting.started payload/handler mismatch)

Important Files Changed

Filename Overview
src/ai_company/communication/meeting/scheduler.py New background service for periodic/event-triggered meetings; sleep-first loop avoids restart duplicates; meeting.started payload missing meeting_id makes the frontend WS handler a no-op
src/ai_company/api/controllers/meetings.py REST endpoints for listing, getting, and triggering meetings; trigger_meeting blocks the HTTP handler until all triggered meetings complete, causing inevitable client timeouts for long-running LLM meetings
src/ai_company/api/app.py Wires meeting scheduler and WS publisher into app lifecycle; publisher callback lacks error handling unlike the approvals equivalent; scheduler's private _event_publisher is set directly without a setter
src/ai_company/communication/meeting/participant.py Clean ParticipantResolver protocol and RegistryParticipantResolver with ordered resolution strategy (context → all → department → name → pass-through); deduplication preserves insertion order
src/ai_company/communication/meeting/frequency.py Clean MeetingFrequency StrEnum with compile-time completeness assertion on the seconds mapping; straightforward and correct
web/src/stores/meetings.ts Pinia store for meeting state with WS re-fetch; meeting.started handler silently does nothing because the event payload never contains a meeting_id
web/src/views/MeetingLogsPage.vue DataTable with detail sidebar, trigger dialog, and canWrite guard; WS subscription lifecycle managed cleanly in onMounted/onUnmounted; overall solid implementation
src/ai_company/api/state.py Adds meeting_orchestrator and meeting_scheduler properties with the standard 503-raising guard pattern; consistent with existing service properties
cli/internal/selfupdate/updater.go Routine errcheck/gofmt lint fixes: _, _ for ignored fmt.Fprintf returns, defer func() { _ = x.Close() }() wrappers, and alignment fixes; no functional changes
src/ai_company/api/ws_models.py Adds MEETING_STARTED, MEETING_COMPLETED, MEETING_FAILED to WsEventType; straightforward enum additions, now correctly published by _publish_started_event
.github/workflows/cli.yml Adds shell: bash to the Go test step to fix PowerShell compatibility on Windows runners; correct fix
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/ai_company/communication/meeting/scheduler.py
Line: 423-446

Comment:
**`meeting.started` payload missing `meeting_id` — frontend handler is dead code**

`_publish_started_event` publishes a payload of `{"meeting_type": ..., "status": "in_progress"}` — deliberately before the meeting runs, so no `meeting_id` exists yet.

However, in `web/src/stores/meetings.ts` the `handleWsEvent` handler for `meeting.started` does:

```ts
case 'meeting.started': {
  const meetingId = payload.meeting_id   // always undefined here
  if (typeof meetingId === 'string' && meetingId) {
    void _refreshMeeting(meetingId)      // never reached
  }
  break
}
```

Because `meeting_id` is absent from the payload, `typeof undefined === 'string'` is `false`, so `_refreshMeeting` is **never called** for `meeting.started` events. The entire case branch is dead code.

Options to fix:
1. Keep the event but emit it after the orchestrator creates an ID (i.e. from within `run_meeting` at the point a `meeting_id` is assigned) so the payload can include it.
2. Since a `meeting_id` cannot exist before the meeting runs, remove the `meeting_id` check from the `meeting.started` branch and instead refresh the full list (`fetchMeetings`) on this event rather than a single record.
3. Drop `meeting.started` until a `meeting_id` can be provided in the payload.

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

---

This is a comment left during a code review.
Path: src/ai_company/api/controllers/meetings.py
Line: 170-174

Comment:
**HTTP request blocks until all triggered meetings complete**

`trigger_event` calls `orchestrator.run_meeting(...)` for each matching meeting type inside an `asyncio.TaskGroup`, and the `trigger_meeting` endpoint `await`s the full result before sending a response.

Since LLM-driven meetings can take minutes per meeting, and multiple meetings can match the same event, the HTTP connection will be held open for the sum of all execution times. In practice this means:
- HTTP clients (browsers, API callers, load balancers) will time out long before meetings finish.
- The caller receives no response at all, making it impossible to tell if the trigger succeeded.

The typical pattern for long-running triggers is to fire the tasks as background work and return immediately with an accepted status. For example, create the tasks with `asyncio.create_task` (storing them in `_tasks` so `stop()` can cancel them), return 202 Accepted with the meeting IDs that will be populated, and let clients poll `GET /meetings` or subscribe to the `meetings` WS channel for completion events.

```python
# Instead of awaiting the full execution:
records = await scheduler.trigger_event(data.event_name, context=data.context)
return Response(content=..., status_code=200)

# Fire and return 202 immediately, letting WS events deliver the result:
asyncio.create_task(scheduler.trigger_event(data.event_name, context=data.context))
return Response(content=..., status_code=202)
```

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

---

This is a comment left during a code review.
Path: src/ai_company/api/app.py
Line: 484-487

Comment:
**Direct private attribute mutation — expose a setter instead**

The factory directly writes to `meeting_scheduler._event_publisher` (a private slot), requiring `noqa: SLF001` on both lines. `MeetingScheduler` already receives `event_publisher` as a constructor keyword argument (defaulting to `None`) but there is no public setter.

A `set_event_publisher` method (or accepting it directly in `create_app` before constructing the scheduler) would remove the need to bypass encapsulation here:

```python
# In MeetingScheduler:
def set_event_publisher(
    self, publisher: Callable[[str, dict[str, Any]], None]
) -> None:
    """Wire the WebSocket event publisher (called once, during app creation)."""
    if self._event_publisher is not None:
        msg = "Event publisher already set"
        raise RuntimeError(msg)
    self._event_publisher = publisher
```

Then in `create_app`:
```python
if meeting_scheduler is not None and meeting_scheduler._event_publisher is None:
    meeting_scheduler.set_event_publisher(_make_meeting_publisher(channels_plugin))
```

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

---

This is a comment left during a code review.
Path: src/ai_company/api/app.py
Line: 126-140

Comment:
**`_on_meeting_event` missing error handling — inconsistent with `_make_expire_callback`**

`_make_expire_callback` wraps its `channels_plugin.publish()` call in a `try/except` and logs failures as warnings (best-effort semantics built into the callback itself). `_on_meeting_event` has no equivalent guard — exceptions propagate to the scheduler's `_publish_started_event` / `_publish_meeting_event` callers, which do catch them, so the system survives. However:

1. The two publisher factories have inconsistent contracts (one is self-contained best-effort, one relies on callers for safety).
2. A `ValueError` from `WsEventType(event_name)` with an unrecognised string will propagate and be logged as a generic scheduler warning rather than a more specific publish failure.

Consider adding the same defensive wrapper:
```python
def _on_meeting_event(event_name: str, payload: dict[str, Any]) -> None:
    try:
        event = WsEvent(
            event_type=WsEventType(event_name),
            channel=CHANNEL_MEETINGS,
            timestamp=datetime.now(UTC),
            payload=payload,
        )
        channels_plugin.publish(event.model_dump_json(), channels=[CHANNEL_MEETINGS])
    except MemoryError, RecursionError:
        raise
    except Exception:
        logger.warning(
            API_APPROVAL_PUBLISH_FAILED,
            event_name=event_name,
            exc_info=True,
        )
```

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

Last reviewed commit: 17d0f35

Comment on lines 50 to +52
COORDINATION_FAILED = "coordination.failed"

MEETING_STARTED = "meeting.started"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

meeting.started event is never published

WsEventType.MEETING_STARTED is defined here and handled in the frontend store (handleWsEvent has a case 'meeting.started' branch), but the scheduler's _STATUS_TO_WS_EVENT dict only maps "completed", "failed", and "budget_exhausted" — and _publish_meeting_event is only called after execution finishes in _run_and_publish. There is no code path that emits meeting.started at any point.

This creates a permanent divergence between the declared protocol and the runtime behaviour. Either:

  • Add a publish call in _run_and_publish before awaiting orchestrator.run_meeting() and wire the "in_progress" status to "meeting.started" in _STATUS_TO_WS_EVENT, or
  • Remove MEETING_STARTED from WsEventType and meeting.started from the TypeScript WsEventType union and the frontend handleWsEvent switch until it is implemented.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/ai_company/api/ws_models.py
Line: 50-52

Comment:
**`meeting.started` event is never published**

`WsEventType.MEETING_STARTED` is defined here and handled in the frontend store (`handleWsEvent` has a `case 'meeting.started'` branch), but the scheduler's `_STATUS_TO_WS_EVENT` dict only maps `"completed"`, `"failed"`, and `"budget_exhausted"` — and `_publish_meeting_event` is only called after execution finishes in `_run_and_publish`. There is no code path that emits `meeting.started` at any point.

This creates a permanent divergence between the declared protocol and the runtime behaviour. Either:
- Add a publish call in `_run_and_publish` before awaiting `orchestrator.run_meeting()` and wire the `"in_progress"` status to `"meeting.started"` in `_STATUS_TO_WS_EVENT`, or  
- Remove `MEETING_STARTED` from `WsEventType` and `meeting.started` from the TypeScript `WsEventType` union and the frontend `handleWsEvent` switch until it is implemented.

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

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

🤖 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/ai_company/api/app.py`:
- Around line 403-404: The MeetingScheduler is never given an event publisher so
it cannot emit lifecycle events to WebSocket clients; create a publisher from
channels_plugin scoped to the "meetings" channel (e.g. call the channels_plugin
publisher factory/creator API) and pass that publisher into the MeetingScheduler
instance (either via its constructor or the scheduler.set_event_publisher
method) when wiring up MeetingScheduler alongside MeetingOrchestrator; ensure
the same publisher is used in the lifecycle hooks that start/stop the scheduler
so published events go to the "meetings" channel.

In `@src/ai_company/api/controllers/meetings.py`:
- Around line 175-179: Remove the duplicate pre-trigger telemetry by deleting
the logger.info call that emits MEETING_EVENT_TRIGGERED in the meetings
controller (the logger.info block that passes event_name=data.event_name and
source="api"); MeetingScheduler.trigger_event() already emits this event after
matching configs, so remove this logging statement to avoid double-counting and
false positives while leaving other logging and the trigger_event call intact.

In `@src/ai_company/communication/meeting/frequency.py`:
- Around line 34-40: The module-level mapping _FREQUENCY_SECONDS should be made
immutable: construct the dict using the existing keys (MeetingFrequency,
_SECONDS_PER_DAY, _SECONDS_PER_WEEK, _SECONDS_PER_BI_WEEK, _SECONDS_PER_MONTH,
_SECONDS_PER_DAY for PER_SPRINT_DAY), perform a deepcopy of that constructed
dict, then wrap it with mapping proxy semantics (MappingProxyType) so
_FREQUENCY_SECONDS is a read-only mapping at import time and cannot be mutated
at runtime; update any imports to include MappingProxyType and copy.deepcopy as
needed and keep the symbol name _FREQUENCY_SECONDS unchanged so existing lookups
work.

In `@src/ai_company/communication/meeting/participant.py`:
- Around line 138-139: The list/tuple branch currently filters values with
v.strip() but returns the raw items, which preserves surrounding whitespace;
update the branch that sets resolved from val so it returns normalized IDs
(e.g., apply v.strip() and keep only non-empty strings) — locate the code
handling isinstance(val, (list, tuple)) and change the list comprehension that
builds resolved to use v.strip() (and possibly deduplicate if desired) so
downstream matching receives clean IDs like "agent-7" instead of " agent-7 ".
- Around line 110-171: The _resolve_entry method is too long and mixes multiple
resolution paths; split it into smaller helpers: extract context resolution into
a new private method (e.g., _resolve_from_context(entry, ctx)) that implements
the ctx lookup logic (string, list/tuple, unexpected types) and returns
list[str], and extract registry-based resolution into another private method
(e.g., _resolve_from_registry(entry)) that handles the "all" case (calling
self._registry.list_active()), department lookup
(self._registry.list_by_department(entry)), and name lookup
(self._registry.get_by_name(entry)); then have _resolve_entry call these helpers
in order and keep its body under 50 lines while preserving existing logging
calls (MEETING_NO_PARTICIPANTS, MEETING_PARTICIPANTS_RESOLVED) and return
semantics.

In `@src/ai_company/communication/meeting/scheduler.py`:
- Around line 41-47: The WS event mapping and publish flow only emits terminal
events because _publish_meeting_event() is called after run_meeting() and
_STATUS_TO_WS_EVENT only contains terminal keys; add a pre-run publish to emit
the "meeting.started" event by either extending _STATUS_TO_WS_EVENT with a
"started" key mapped to "meeting.started" and invoking
_publish_meeting_event("started", ...) before run_meeting(), or create a
dedicated pre-run hook in the scheduler/orchestrator that calls the WS publisher
with the "meeting.started" event prior to calling run_meeting(); update any
scheduler paths that start meetings (e.g., the function that
schedules/orchestrates run_meeting()) to call this pre-run publisher so
scheduled meetings can emit meeting.started.
- Around line 192-197: The current code in the block that creates tasks with
asyncio.TaskGroup hides failures by filtering out None results from
self._execute_meeting(), so callers (e.g., trigger_event) cannot distinguish “no
matches” from “all matches failed.” Change the logic after the TaskGroup to
explicitly handle task outcomes: collect each tg-created task, check
t.exception() and, if present, either raise that exception (re-raising
immediately) or convert it into an explicit failure record (including the
matching trigger identifier and error) to return alongside successful results;
treat a literal None returned by _execute_meeting() as an explicit failure
record rather than silently dropping it. Ensure you update the method that calls
this code (trigger_event) to expect the new explicit failure records or
propagate exceptions accordingly.

In `@tests/unit/communication/meeting/test_participant.py`:
- Around line 150-160: Move the local import of NoParticipantsResolvedError out
of the test body into the module-level imports for consistency; specifically,
add "from ai_company.communication.meeting.errors import
NoParticipantsResolvedError" alongside the other imports at top of the file and
remove the in-function import inside test_empty_result_raises which calls await
resolver.resolve(()), leaving the test otherwise unchanged.

In `@web/src/__tests__/views/MeetingLogsPage.store-integration.test.ts`:
- Around line 16-22: The test currently only checks that useMeetingStore exposes
methods (fetchMeetings, fetchMeeting, triggerMeeting, handleWsEvent); replace it
with behavior-focused assertions: mock the API calls and websocket events used
by fetchMeetings/fetchMeeting/triggerMeeting/handleWsEvent, invoke each method,
and assert the store's state changes (e.g., meetings list, currentMeeting,
loading/error flags) and that API/WS mocks were called with expected args;
ensure you test that triggerMeeting causes the appropriate refresh (e.g., calls
fetchMeetings or updates state) and that handleWsEvent processes a ws message to
update the store.

In `@web/src/views/MeetingLogsPage.vue`:
- Around line 221-237: The template uses optional chaining on
selected.minutes.decisions and selected.minutes.action_items but the
MeetingMinutes type declares them as required arrays; either update the
MeetingMinutes interface to make decisions and action_items optional
(decisions?: string[]; action_items?: ActionItem[]) so the template can safely
use ?.length, or remove the defensive ?. operators in the template (the v-if
checks) to match the current required-array types; locate the MeetingMinutes
type and the template references to selected.minutes.decisions /
selected.minutes.action_items and apply the change consistently.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e8b650e6-a9fd-4524-9b7b-b7536d50cba3

📥 Commits

Reviewing files that changed from the base of the PR and between 0353d9e and 31d215e.

📒 Files selected for processing (34)
  • .github/workflows/cli.yml
  • CLAUDE.md
  • cli/.golangci.yml
  • docs/design/communication.md
  • src/ai_company/api/app.py
  • src/ai_company/api/channels.py
  • src/ai_company/api/controllers/meetings.py
  • src/ai_company/api/state.py
  • src/ai_company/api/ws_models.py
  • src/ai_company/communication/config.py
  • src/ai_company/communication/meeting/__init__.py
  • src/ai_company/communication/meeting/errors.py
  • src/ai_company/communication/meeting/frequency.py
  • src/ai_company/communication/meeting/participant.py
  • src/ai_company/communication/meeting/scheduler.py
  • src/ai_company/observability/events/meeting.py
  • tests/integration/communication/test_meeting_integration.py
  • tests/unit/api/controllers/test_meetings.py
  • tests/unit/api/test_app.py
  • tests/unit/api/test_channels.py
  • tests/unit/communication/conftest.py
  • tests/unit/communication/meeting/test_config_frequency.py
  • tests/unit/communication/meeting/test_frequency.py
  • tests/unit/communication/meeting/test_participant.py
  • tests/unit/communication/meeting/test_scheduler.py
  • tests/unit/communication/test_config.py
  • web/src/__tests__/stores/meetings.test.ts
  • web/src/__tests__/views/MeetingLogsPage.store-integration.test.ts
  • web/src/__tests__/views/MeetingLogsPage.test.ts
  • web/src/api/endpoints/meetings.ts
  • web/src/api/types.ts
  • web/src/stores/meetings.ts
  • web/src/styles/theme.ts
  • web/src/views/MeetingLogsPage.vue
💤 Files with no reviewable changes (1)
  • web/src/tests/views/MeetingLogsPage.test.ts
📜 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). (7)
  • GitHub Check: Agent
  • GitHub Check: CLI Test (windows-latest)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Web
  • GitHub Check: Build Backend
  • GitHub Check: Greptile Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{py,ts,tsx,js,jsx,vue}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{py,ts,tsx,js,jsx,vue}: Enforce 88-character line length via ruff
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. Tests must use test-provider, test-small-001, etc.

Files:

  • web/src/api/endpoints/meetings.ts
  • src/ai_company/communication/meeting/errors.py
  • web/src/styles/theme.ts
  • tests/unit/api/controllers/test_meetings.py
  • src/ai_company/api/ws_models.py
  • src/ai_company/communication/config.py
  • src/ai_company/communication/meeting/scheduler.py
  • tests/unit/communication/conftest.py
  • src/ai_company/communication/meeting/participant.py
  • web/src/__tests__/views/MeetingLogsPage.store-integration.test.ts
  • tests/integration/communication/test_meeting_integration.py
  • web/src/__tests__/stores/meetings.test.ts
  • web/src/api/types.ts
  • src/ai_company/observability/events/meeting.py
  • web/src/stores/meetings.ts
  • tests/unit/api/test_channels.py
  • src/ai_company/communication/meeting/frequency.py
  • tests/unit/communication/meeting/test_participant.py
  • src/ai_company/api/state.py
  • src/ai_company/api/channels.py
  • src/ai_company/api/app.py
  • tests/unit/api/test_app.py
  • web/src/views/MeetingLogsPage.vue
  • src/ai_company/api/controllers/meetings.py
  • tests/unit/communication/meeting/test_scheduler.py
  • tests/unit/communication/meeting/test_frequency.py
  • tests/unit/communication/meeting/test_config_frequency.py
  • src/ai_company/communication/meeting/__init__.py
  • tests/unit/communication/test_config.py
**/*.{py,ts,tsx,js,jsx,vue,go}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{py,ts,tsx,js,jsx,vue,go}: Functions must be less than 50 lines; files must be less than 800 lines
Handle errors explicitly; never silently swallow exceptions
Validate at system boundaries: user input, external APIs, config files

Files:

  • web/src/api/endpoints/meetings.ts
  • src/ai_company/communication/meeting/errors.py
  • web/src/styles/theme.ts
  • tests/unit/api/controllers/test_meetings.py
  • src/ai_company/api/ws_models.py
  • src/ai_company/communication/config.py
  • src/ai_company/communication/meeting/scheduler.py
  • tests/unit/communication/conftest.py
  • src/ai_company/communication/meeting/participant.py
  • web/src/__tests__/views/MeetingLogsPage.store-integration.test.ts
  • tests/integration/communication/test_meeting_integration.py
  • web/src/__tests__/stores/meetings.test.ts
  • web/src/api/types.ts
  • src/ai_company/observability/events/meeting.py
  • web/src/stores/meetings.ts
  • tests/unit/api/test_channels.py
  • src/ai_company/communication/meeting/frequency.py
  • tests/unit/communication/meeting/test_participant.py
  • src/ai_company/api/state.py
  • src/ai_company/api/channels.py
  • src/ai_company/api/app.py
  • tests/unit/api/test_app.py
  • web/src/views/MeetingLogsPage.vue
  • src/ai_company/api/controllers/meetings.py
  • tests/unit/communication/meeting/test_scheduler.py
  • tests/unit/communication/meeting/test_frequency.py
  • tests/unit/communication/meeting/test_config_frequency.py
  • src/ai_company/communication/meeting/__init__.py
  • tests/unit/communication/test_config.py
web/**/*.{ts,tsx,js,jsx,vue}

📄 CodeRabbit inference engine (CLAUDE.md)

Web dashboard linting: npm --prefix web run lint via ESLint

Files:

  • web/src/api/endpoints/meetings.ts
  • web/src/styles/theme.ts
  • web/src/__tests__/views/MeetingLogsPage.store-integration.test.ts
  • web/src/__tests__/stores/meetings.test.ts
  • web/src/api/types.ts
  • web/src/stores/meetings.ts
  • web/src/views/MeetingLogsPage.vue
web/**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (CLAUDE.md)

web/**/*.{ts,tsx,vue}: Web dashboard type checking: npm --prefix web run type-check via vue-tsc
Use TypeScript in Vue components and web dashboard for type safety

Files:

  • web/src/api/endpoints/meetings.ts
  • web/src/styles/theme.ts
  • web/src/__tests__/views/MeetingLogsPage.store-integration.test.ts
  • web/src/__tests__/stores/meetings.test.ts
  • web/src/api/types.ts
  • web/src/stores/meetings.ts
  • web/src/views/MeetingLogsPage.vue
web/**/*.{ts,tsx,js,jsx,vue,json}

📄 CodeRabbit inference engine (CLAUDE.md)

Web dashboard uses PrimeVue components, Tailwind CSS for styling, Pinia for state management, Vue Router for routing, and Axios for API calls

Files:

  • web/src/api/endpoints/meetings.ts
  • web/src/styles/theme.ts
  • web/src/__tests__/views/MeetingLogsPage.store-integration.test.ts
  • web/src/__tests__/stores/meetings.test.ts
  • web/src/api/types.ts
  • web/src/stores/meetings.ts
  • web/src/views/MeetingLogsPage.vue
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations — Python 3.14 has PEP 649 native lazy annotations
Use except A, B: (no parentheses) syntax for exception handling — ruff enforces PEP 758 except syntax on Python 3.14
All public functions require type hints; mypy strict mode enforced
Google-style docstrings required on all public classes and functions; enforced by ruff D rules
Create new objects instead of mutating existing ones; for non-Pydantic internal collections, use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement
Use frozen Pydantic models for config/identity; separate mutable-via-copy models for runtime state using model_copy(update=...); never mix static config fields with mutable runtime fields
Use Pydantic v2 with BaseModel, model_validator, computed_field, ConfigDict. Use @computed_field for derived values; use NotBlankStr for all identifier/name fields instead of manual whitespace validators
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code; avoid bare create_task and prefer structured concurrency
Python 3.14+ required; supports PEP 649 native lazy annotations

Files:

  • src/ai_company/communication/meeting/errors.py
  • tests/unit/api/controllers/test_meetings.py
  • src/ai_company/api/ws_models.py
  • src/ai_company/communication/config.py
  • src/ai_company/communication/meeting/scheduler.py
  • tests/unit/communication/conftest.py
  • src/ai_company/communication/meeting/participant.py
  • tests/integration/communication/test_meeting_integration.py
  • src/ai_company/observability/events/meeting.py
  • tests/unit/api/test_channels.py
  • src/ai_company/communication/meeting/frequency.py
  • tests/unit/communication/meeting/test_participant.py
  • src/ai_company/api/state.py
  • src/ai_company/api/channels.py
  • src/ai_company/api/app.py
  • tests/unit/api/test_app.py
  • src/ai_company/api/controllers/meetings.py
  • tests/unit/communication/meeting/test_scheduler.py
  • tests/unit/communication/meeting/test_frequency.py
  • tests/unit/communication/meeting/test_config_frequency.py
  • src/ai_company/communication/meeting/__init__.py
  • tests/unit/communication/test_config.py
src/ai_company/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/ai_company/**/*.py: Every module with business logic must import from ai_company.observability import get_logger and define logger = get_logger(__name__); never use import logging, logging.getLogger(), or print() in application code
Always use constants from domain-specific event modules (e.g., events.provider, events.budget, events.cfo, events.conflict) under ai_company.observability.events for event names; import directly as from ai_company.observability.events.<domain> import EVENT_CONSTANT
Use structured logging kwargs format: logger.info(EVENT, key=value); never use positional format strings like 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 level
DEBUG level for object creation, internal flow, entry/exit of key functions
Pure data models, enums, and re-exports do not need logging

Files:

  • src/ai_company/communication/meeting/errors.py
  • src/ai_company/api/ws_models.py
  • src/ai_company/communication/config.py
  • src/ai_company/communication/meeting/scheduler.py
  • src/ai_company/communication/meeting/participant.py
  • src/ai_company/observability/events/meeting.py
  • src/ai_company/communication/meeting/frequency.py
  • src/ai_company/api/state.py
  • src/ai_company/api/channels.py
  • src/ai_company/api/app.py
  • src/ai_company/api/controllers/meetings.py
  • src/ai_company/communication/meeting/__init__.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Use pytest markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow
Prefer @pytest.mark.parametrize for testing similar cases

Files:

  • tests/unit/api/controllers/test_meetings.py
  • tests/unit/communication/conftest.py
  • tests/integration/communication/test_meeting_integration.py
  • tests/unit/api/test_channels.py
  • tests/unit/communication/meeting/test_participant.py
  • tests/unit/api/test_app.py
  • tests/unit/communication/meeting/test_scheduler.py
  • tests/unit/communication/meeting/test_frequency.py
  • tests/unit/communication/meeting/test_config_frequency.py
  • tests/unit/communication/test_config.py
web/**/__tests__/**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Web dashboard testing: npm --prefix web run test via Vitest

Files:

  • web/src/__tests__/views/MeetingLogsPage.store-integration.test.ts
  • web/src/__tests__/stores/meetings.test.ts
🧠 Learnings (17)
📚 Learning: 2026-03-14T16:17:59.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:17:59.180Z
Learning: CLI workflow (`.github/workflows/cli.yml`): Go lint (golangci-lint + go vet) + test (race + coverage) + cross-compile build (linux/darwin/windows × amd64/arm64) + govulncheck on `cli/**` changes. GoReleaser release on `v*` tags (attaches assets, pins checksums in install scripts, appends table to Release notes).

Applied to files:

  • .github/workflows/cli.yml
  • cli/.golangci.yml
📚 Learning: 2026-03-14T16:17:59.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:17:59.180Z
Learning: Coverage: Codecov integration with coverage upload + test results analytics via codecov-action + test-results-action (best-effort, CI not gated on Codecov availability)

Applied to files:

  • .github/workflows/cli.yml
📚 Learning: 2026-03-14T16:17:59.179Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:17:59.179Z
Learning: Pre-push hooks enforce: mypy type-check + pytest unit tests + golangci-lint + go vet + go test (CLI, conditional on `cli/**/*.go`)

Applied to files:

  • .github/workflows/cli.yml
  • cli/.golangci.yml
📚 Learning: 2026-03-14T16:17:59.179Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:17:59.179Z
Learning: CLI tests: `cd cli && go test ./...` to run all Go tests

Applied to files:

  • .github/workflows/cli.yml
📚 Learning: 2026-03-14T16:17:59.179Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:17:59.179Z
Learning: CLI linting: `cd cli && golangci-lint run` for comprehensive Go linting

Applied to files:

  • .github/workflows/cli.yml
  • cli/.golangci.yml
📚 Learning: 2026-03-14T16:17:59.179Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:17:59.179Z
Learning: Full test suite with coverage: `uv run pytest tests/ -n auto --cov=ai_company --cov-fail-under=80`

Applied to files:

  • .github/workflows/cli.yml
📚 Learning: 2026-03-14T16:17:59.179Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:17:59.179Z
Learning: Applies to cli/**/*.go : Go 1.26+ required for CLI development

Applied to files:

  • cli/.golangci.yml
📚 Learning: 2026-03-14T16:17:59.179Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:17:59.179Z
Learning: CLI vetting: `cd cli && go vet ./...` for Go analysis

Applied to files:

  • cli/.golangci.yml
📚 Learning: 2026-03-14T16:17:59.179Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:17:59.179Z
Learning: Applies to web/**/__tests__/**/*.{ts,tsx,js} : Web dashboard testing: `npm --prefix web run test` via Vitest

Applied to files:

  • web/src/__tests__/views/MeetingLogsPage.store-integration.test.ts
📚 Learning: 2026-03-14T16:17:59.179Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:17:59.179Z
Learning: Applies to src/ai_company/**/*.py : Always use constants from domain-specific event modules (e.g., `events.provider`, `events.budget`, `events.cfo`, `events.conflict`) under `ai_company.observability.events` for event names; import directly as `from ai_company.observability.events.<domain> import EVENT_CONSTANT`

Applied to files:

  • src/ai_company/observability/events/meeting.py
  • src/ai_company/api/app.py
  • CLAUDE.md
📚 Learning: 2026-03-14T16:17:59.179Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:17:59.179Z
Learning: Applies to src/ai_company/**/*.py : Every module with business logic must import `from ai_company.observability import get_logger` and define `logger = get_logger(__name__)`; never use `import logging`, `logging.getLogger()`, or `print()` in application code

Applied to files:

  • src/ai_company/api/state.py
  • src/ai_company/api/app.py
  • CLAUDE.md
📚 Learning: 2026-03-14T16:17:59.179Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:17:59.179Z
Learning: Applies to web/**/*.{ts,tsx,js,jsx,vue,json} : Web dashboard uses PrimeVue components, Tailwind CSS for styling, Pinia for state management, Vue Router for routing, and Axios for API calls

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-14T16:17:59.179Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:17:59.179Z
Learning: Applies to src/ai_company/**/*.py : Use structured logging kwargs format: `logger.info(EVENT, key=value)`; never use positional format strings like `logger.info('msg %s', val)`

Applied to files:

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

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-14T16:17:59.179Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:17:59.179Z
Learning: Applies to src/ai_company/**/*.py : All state transitions must log at INFO level

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-14T16:17:59.179Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:17:59.179Z
Learning: Applies to src/ai_company/**/*.py : Pure data models, enums, and re-exports do not need logging

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-14T16:17:59.179Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:17:59.179Z
Learning: Applies to src/ai_company/**/*.py : DEBUG level for object creation, internal flow, entry/exit of key functions

Applied to files:

  • CLAUDE.md
🧬 Code graph analysis (17)
web/src/api/endpoints/meetings.ts (2)
web/src/api/types.ts (5)
  • MeetingFilters (570-575)
  • PaginatedResponse (105-107)
  • MeetingRecord (560-568)
  • ApiResponse (94-96)
  • TriggerMeetingRequest (577-580)
web/src/api/client.ts (3)
  • apiClient (12-16)
  • unwrapPaginated (68-84)
  • unwrap (56-62)
web/src/styles/theme.ts (1)
web/src/api/types.ts (4)
  • TaskStatus (5-14)
  • ApprovalStatus (28-28)
  • AgentStatus (42-42)
  • MeetingStatus (492-498)
tests/unit/api/controllers/test_meetings.py (2)
src/ai_company/api/app.py (1)
  • create_app (392-539)
src/ai_company/api/controllers/meetings.py (1)
  • TriggerMeetingRequest (29-74)
src/ai_company/communication/config.py (1)
src/ai_company/communication/meeting/frequency.py (1)
  • MeetingFrequency (16-31)
src/ai_company/communication/meeting/scheduler.py (6)
src/ai_company/communication/meeting/errors.py (2)
  • NoParticipantsResolvedError (34-35)
  • SchedulerAlreadyRunningError (38-39)
src/ai_company/communication/meeting/frequency.py (1)
  • frequency_to_seconds (48-57)
src/ai_company/communication/meeting/orchestrator.py (1)
  • run_meeting (103-181)
src/ai_company/communication/meeting/participant.py (2)
  • resolve (24-43)
  • resolve (65-108)
src/ai_company/observability/_logger.py (1)
  • get_logger (8-28)
src/ai_company/communication/config.py (1)
  • MeetingTypeConfig (83-137)
tests/unit/communication/conftest.py (1)
src/ai_company/communication/meeting/frequency.py (1)
  • MeetingFrequency (16-31)
src/ai_company/communication/meeting/participant.py (2)
src/ai_company/communication/meeting/errors.py (1)
  • NoParticipantsResolvedError (34-35)
src/ai_company/hr/registry.py (3)
  • list_active (129-138)
  • list_by_department (140-158)
  • get_by_name (113-127)
web/src/__tests__/views/MeetingLogsPage.store-integration.test.ts (1)
web/src/stores/meetings.ts (1)
  • useMeetingStore (12-117)
tests/integration/communication/test_meeting_integration.py (1)
src/ai_company/communication/meeting/frequency.py (1)
  • MeetingFrequency (16-31)
web/src/__tests__/stores/meetings.test.ts (2)
web/src/api/types.ts (2)
  • MeetingRecord (560-568)
  • WsEvent (615-620)
web/src/stores/meetings.ts (1)
  • useMeetingStore (12-117)
web/src/api/types.ts (1)
web/src/styles/theme.ts (1)
  • Priority (6-6)
src/ai_company/api/state.py (2)
src/ai_company/communication/meeting/orchestrator.py (1)
  • MeetingOrchestrator (68-457)
src/ai_company/communication/meeting/scheduler.py (1)
  • MeetingScheduler (55-437)
src/ai_company/api/app.py (3)
src/ai_company/communication/meeting/orchestrator.py (1)
  • MeetingOrchestrator (68-457)
src/ai_company/communication/meeting/scheduler.py (3)
  • MeetingScheduler (55-437)
  • stop (138-160)
  • start (98-136)
src/ai_company/api/state.py (4)
  • persistence (110-112)
  • meeting_scheduler (166-171)
  • AppState (29-218)
  • meeting_orchestrator (158-163)
src/ai_company/api/controllers/meetings.py (5)
src/ai_company/api/dto.py (2)
  • ApiResponse (39-57)
  • PaginatedResponse (76-96)
src/ai_company/api/guards.py (2)
  • require_read_access (83-107)
  • require_write_access (56-80)
src/ai_company/api/pagination.py (1)
  • paginate (26-58)
src/ai_company/api/state.py (2)
  • meeting_orchestrator (158-163)
  • meeting_scheduler (166-171)
src/ai_company/communication/meeting/scheduler.py (1)
  • trigger_event (162-197)
tests/unit/communication/meeting/test_frequency.py (1)
src/ai_company/communication/meeting/frequency.py (2)
  • MeetingFrequency (16-31)
  • frequency_to_seconds (48-57)
src/ai_company/communication/meeting/__init__.py (4)
src/ai_company/communication/meeting/errors.py (3)
  • MeetingSchedulerError (30-31)
  • NoParticipantsResolvedError (34-35)
  • SchedulerAlreadyRunningError (38-39)
src/ai_company/communication/meeting/frequency.py (1)
  • MeetingFrequency (16-31)
src/ai_company/communication/meeting/participant.py (2)
  • ParticipantResolver (21-43)
  • RegistryParticipantResolver (46-171)
src/ai_company/communication/meeting/scheduler.py (1)
  • MeetingScheduler (55-437)
tests/unit/communication/test_config.py (2)
src/ai_company/communication/meeting/frequency.py (1)
  • MeetingFrequency (16-31)
src/ai_company/communication/config.py (1)
  • MeetingTypeConfig (83-137)
🔇 Additional comments (20)
.github/workflows/cli.yml (1)

93-96: LGTM!

Explicitly specifying shell: bash ensures consistent shell behavior across all matrix platforms (ubuntu, macos, windows). This is a good fix since Windows runners default to PowerShell, and using bash provides uniformity for the test command execution.

cli/.golangci.yml (1)

22-24: Formatter migration to v2 is correct and properly configured.

The formatters.enable: [gofmt] block follows golangci-lint v2 schema correctly. CI workflow is pinned to golangci-lint v2.11.3, and the .golangci.yml configuration uses version: "2" with go: "1.26" as required.

tests/unit/communication/meeting/test_frequency.py (1)

11-56: Strong coverage for enum parsing and interval conversion.

This test set cleanly validates valid values, invalid value handling, and
all frequency_to_seconds mappings.

tests/integration/communication/test_meeting_integration.py (1)

19-19: Enum migration in integration tests is correctly applied.

The updated import and usage keep this test aligned with the new
MeetingFrequency type contract.

Also applies to: 424-425

src/ai_company/api/ws_models.py (1)

52-54: Meeting WebSocket event types are well-scoped and consistent.

These additions fit the existing event namespace and lifecycle naming pattern.

web/src/styles/theme.ts (1)

3-6: Status theme updates correctly include meeting states.

The Status type and statusColors map are updated consistently for
schedule-related UI badges.

Also applies to: 60-61

tests/unit/communication/meeting/test_config_frequency.py (1)

10-72: Validation-focused tests are comprehensive and targeted.

Good coverage of enum acceptance, coercion, and mutual exclusivity rules.

src/ai_company/communication/meeting/frequency.py (1)

16-32: Enum and conversion API design look solid.

Public surface is typed, documented, and easy to consume from config/scheduler code.

Also applies to: 48-57

docs/design/communication.md (1)

374-408: Scheduler design documentation is clear and implementation-aligned.

The added section captures scheduling modes and participant resolution behavior
in a way that matches the introduced architecture.

tests/unit/communication/test_config.py (1)

21-21: Enum-based frequency migration is consistently applied across config tests.

The updated inputs cover construction, validation, immutability, and
serialization paths without introducing gaps.

Also applies to: 101-102, 116-117, 131-133, 144-145, 148-148, 151-153, 164-167, 175-178, 184-184, 190-198, 204-205, 209-210, 220-221, 245-246, 254-255, 260-261, 274-275

web/src/api/endpoints/meetings.ts (1)

1-29: LGTM!

The API endpoints are well-structured with proper type annotations, URL encoding for path parameters, and consistent use of the unwrap utilities for error handling. The return types correctly match the expected API response structures.

web/src/__tests__/stores/meetings.test.ts (1)

1-208: LGTM!

Comprehensive test coverage for the meeting store including initial state verification, WebSocket event handling (completed, started, failed), API fetch operations, deduplication logic, and error handling. The mock setup is clean with proper reset in beforeEach.

web/src/stores/meetings.ts (1)

1-117: LGTM!

Well-structured Pinia store with proper separation of concerns. The error handling strategy is appropriate: public actions set the error state for UI feedback while the internal _refreshMeeting silently logs failures to prevent WebSocket events from disrupting the user experience. The deduplication logic in triggerMeeting is efficient.

tests/unit/communication/meeting/test_participant.py (1)

26-160: LGTM on test coverage!

Comprehensive test coverage for the participant resolver including context precedence, deduplication, various input types (list, tuple, literals), and error conditions. The async test patterns and mock assertions are correctly implemented.

web/src/views/MeetingLogsPage.vue (2)

45-60: Consider consolidating error handling in onMounted.

The WS setup and initial fetch have separate try/catch blocks, which is good for isolation. However, if the WS setup fails (line 52-54), the page still attempts to fetch meetings, which is the correct resilient behavior. The implementation looks intentional.


127-287: LGTM on the Meeting Logs page implementation!

Well-structured Vue component with comprehensive meeting management features:

  • Real-time updates via WebSocket integration
  • Status filtering with proper error feedback
  • Trigger dialog with validation and loading states
  • Detailed sidebar with nested minutes display
  • Proper accessibility attributes (aria-label on filter)
src/ai_company/communication/meeting/__init__.py (1)

22-99: LGTM!

The package __init__.py correctly expands the public API surface with the new meeting scheduler components. Imports are properly organized, and __all__ maintains alphabetical order for consistency.

tests/unit/api/controllers/test_meetings.py (2)

1-322: LGTM on test coverage!

Comprehensive test suite covering:

  • CRUD operations (list, get by ID, 404 handling)
  • Filtering by status and meeting_type
  • Trigger endpoint integration with scheduler mock
  • Service unavailability (503) handling
  • Extensive TriggerMeetingRequest validation including all boundary conditions

The test fixtures are well-organized with proper auth seeding and mock configuration.


234-292: Boundary values in validation tests are correct.

All constants match production code: _MAX_CONTEXT_KEYS=20, _MAX_CONTEXT_KEY_LEN=256, _MAX_CONTEXT_VAL_LEN=1024, _MAX_CONTEXT_LIST_ITEMS=50.

src/ai_company/api/state.py (1)

13-16: LGTM!

The AppState extension for meeting services follows the established patterns used by other services:

  • Proper __slots__ entries for memory efficiency
  • Optional constructor parameters with None defaults
  • Property accessors that use _require_service for consistent 503 handling
  • Logging via get_logger(__name__) per project guidelines

Based on learnings: "Every module with business logic must import from ai_company.observability import get_logger and define logger = get_logger(__name__)" - this is correctly implemented at line 22-26.

Also applies to: 52-53, 75-76, 89-90, 157-171

Comment on lines +192 to +197
async with asyncio.TaskGroup() as tg:
tasks = [
tg.create_task(self._execute_meeting(mt, context)) for mt in matching
]

return tuple(r for t in tasks if (r := t.result()) is not None)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't collapse matching-trigger failures into an empty result.

Line 197 drops every None returned by _execute_meeting(), but this class returns None for participant-resolution, agenda-build, and orchestrator errors. That makes trigger_event() use the same () for both “nothing matched” and “every matching meeting failed”, so POST /meetings/trigger can report a successful empty response while all matching triggers actually errored. Return explicit failure records, or raise once any matched trigger fails.

As per coding guidelines "Handle errors explicitly; never silently swallow exceptions".

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

In `@src/ai_company/communication/meeting/scheduler.py` around lines 192 - 197,
The current code in the block that creates tasks with asyncio.TaskGroup hides
failures by filtering out None results from self._execute_meeting(), so callers
(e.g., trigger_event) cannot distinguish “no matches” from “all matches failed.”
Change the logic after the TaskGroup to explicitly handle task outcomes: collect
each tg-created task, check t.exception() and, if present, either raise that
exception (re-raising immediately) or convert it into an explicit failure record
(including the matching trigger identifier and error) to return alongside
successful results; treat a literal None returned by _execute_meeting() as an
explicit failure record rather than silently dropping it. Ensure you update the
method that calls this code (trigger_event) to expect the new explicit failure
records or propagate exceptions accordingly.

Comment on lines +16 to +22
it('meeting store has expected API methods', () => {
const store = useMeetingStore()
expect(typeof store.fetchMeetings).toBe('function')
expect(typeof store.fetchMeeting).toBe('function')
expect(typeof store.triggerMeeting).toBe('function')
expect(typeof store.handleWsEvent).toBe('function')
})
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

This integration test is too shallow to catch store regressions.

The test only asserts method presence. It won’t fail if API calls, state updates, or WS-triggered refresh behavior break.

✅ Suggested behavior-focused test
+import * as meetingsApi from '@/api/endpoints/meetings'
...
-it('meeting store has expected API methods', () => {
+it('fetchMeetings calls API and updates state', async () => {
   const store = useMeetingStore()
-  expect(typeof store.fetchMeetings).toBe('function')
-  expect(typeof store.fetchMeeting).toBe('function')
-  expect(typeof store.triggerMeeting).toBe('function')
-  expect(typeof store.handleWsEvent).toBe('function')
+  await store.fetchMeetings()
+  expect(meetingsApi.listMeetings).toHaveBeenCalledOnce()
+  expect(store.meetings).toEqual([])
+  expect(store.total).toBe(0)
 })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/__tests__/views/MeetingLogsPage.store-integration.test.ts` around
lines 16 - 22, The test currently only checks that useMeetingStore exposes
methods (fetchMeetings, fetchMeeting, triggerMeeting, handleWsEvent); replace it
with behavior-focused assertions: mock the API calls and websocket events used
by fetchMeetings/fetchMeeting/triggerMeeting/handleWsEvent, invoke each method,
and assert the store's state changes (e.g., meetings list, currentMeeting,
loading/error flags) and that API/WS mocks were called with expected args;
ensure you test that triggerMeeting causes the appropriate refresh (e.g., calls
fetchMeetings or updates state) and that handleWsEvent processes a ws message to
update the store.

- Add `_, _ =` to all unchecked fmt.Fprintln/fmt.Fprintf calls
- Use `defer func() { _ = resp.Body.Close() }()` for HTTP responses
- Add `_ =` to tmpFile.Close/os.Remove in cleanup paths
- Fix unchecked os.Remove in selfupdate test
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: 6

Caution

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

⚠️ Outside diff range comments (4)
cli/cmd/init.go (1)

46-52: ⚠️ Potential issue | 🟡 Minor

Duplicate comment on lines 46-47.

There's a duplicated comment line. Remove one of them.

Proposed fix
 	// Warn if re-initializing over existing config (JWT secret will change).
-	// Warn if re-initializing over existing config (JWT secret will change).
 	// isInteractive() is already checked at function entry, so prompt is safe.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/cmd/init.go` around lines 46 - 52, Remove the duplicated comment above
the config overwrite check: delete one of the repeated "// Warn if
re-initializing over existing config (JWT secret will change)." lines found
immediately before the if existing := config.StatePath(state.DataDir);
fileExists(existing) { block so only a single comment remains; the rest of the
logic (isInteractive() check at function entry, the if block using
config.StatePath, fileExists, and fmt.Fprintf to cmd.ErrOrStderr()) should be
left unchanged.
cli/cmd/doctor.go (1)

27-84: 🧹 Nitpick | 🔵 Trivial

Function runDoctor exceeds 50-line limit (58 lines).

Consider extracting the GitHub issue URL generation (lines 52-79) into a separate helper function to improve readability and comply with the 50-line guideline.

Suggested extraction
+func buildIssueURL(report diagnostics.Report) string {
+	issueTitle := fmt.Sprintf("[CLI] Bug report — %s/%s, CLI %s", report.OS, report.Arch, report.CLIVersion)
+	issueBody := fmt.Sprintf(
+		"## Environment\n\nOS: %s/%s\nCLI: %s (%s)\nDocker: %s\nCompose: %s\nHealth: %s\n\n"+
+			"> Full diagnostics saved to: attach the file from `synthorg doctor` output\n\n"+
+			"## Steps to Reproduce\n\n1. \n\n## Expected Behavior\n\n\n## Actual Behavior\n\n",
+		report.OS, report.Arch, report.CLIVersion, report.CLICommit,
+		report.DockerVersion, report.ComposeVersion, report.HealthStatus,
+	)
+
+	encodedBody := url.QueryEscape(issueBody)
+	if len(encodedBody) > 3500 {
+		issueBody = fmt.Sprintf(
+			"## Environment\n\nOS: %s/%s\nCLI: %s\nDocker: %s\nHealth: %s\n\n"+
+				"> Attach the full diagnostics file from `synthorg doctor` output\n",
+			report.OS, report.Arch, report.CLIVersion,
+			report.DockerVersion, report.HealthStatus,
+		)
+		encodedBody = url.QueryEscape(issueBody)
+	}
+
+	return fmt.Sprintf(
+		"%s/issues/new?title=%s&labels=type%%3Abug&body=%s",
+		version.RepoURL,
+		url.QueryEscape(issueTitle),
+		encodedBody,
+	)
+}

Then in runDoctor:

 	_, _ = fmt.Fprintln(out, text)
 
-	// Generate GitHub issue URL (exclude logs — may contain secrets).
-	issueTitle := fmt.Sprintf("[CLI] Bug report — %s/%s, CLI %s", report.OS, report.Arch, report.CLIVersion)
-	issueBody := fmt.Sprintf(
-		"## Environment\n\nOS: %s/%s\nCLI: %s (%s)\nDocker: %s\nCompose: %s\nHealth: %s\n\n"+
-			"> Full diagnostics saved to: attach the file from `synthorg doctor` output\n\n"+
-			"## Steps to Reproduce\n\n1. \n\n## Expected Behavior\n\n\n## Actual Behavior\n\n",
-		report.OS, report.Arch, report.CLIVersion, report.CLICommit,
-		report.DockerVersion, report.ComposeVersion, report.HealthStatus,
-	)
-
-	// Truncate body if URL would exceed browser limits (~4000 chars).
-	encodedBody := url.QueryEscape(issueBody)
-	if len(encodedBody) > 3500 {
-		issueBody = fmt.Sprintf(
-			"## Environment\n\nOS: %s/%s\nCLI: %s\nDocker: %s\nHealth: %s\n\n"+
-				"> Attach the full diagnostics file from `synthorg doctor` output\n",
-			report.OS, report.Arch, report.CLIVersion,
-			report.DockerVersion, report.HealthStatus,
-		)
-		encodedBody = url.QueryEscape(issueBody)
-	}
-
-	issueURL := fmt.Sprintf(
-		"%s/issues/new?title=%s&labels=type%%3Abug&body=%s",
-		version.RepoURL,
-		url.QueryEscape(issueTitle),
-		encodedBody,
-	)
-
-	_, _ = fmt.Fprintf(out, "File a bug report:\n  %s\n", issueURL)
+	// Generate GitHub issue URL (exclude logs — may contain secrets).
+	_, _ = fmt.Fprintf(out, "File a bug report:\n  %s\n", buildIssueURL(report))

As per coding guidelines: "Functions must be less than 50 lines".

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

In `@cli/cmd/doctor.go` around lines 27 - 84, runDoctor is over the 50-line limit
because the GitHub issue URL construction is inline; extract that logic into a
new helper (e.g., buildGitHubIssueURL or createIssueURL) that accepts the
diagnostics report (and any needed version.RepoURL constant) and returns the
final issue URL string. Move the creation of issueTitle, issueBody, the
QueryEscape/truncation logic, and the final fmt.Sprintf into that helper,
leaving runDoctor to call buildGitHubIssueURL(report) and print the returned
URL; update imports/usages as needed and keep function behavior unchanged.
cli/internal/selfupdate/updater.go (1)

224-292: 🛠️ Refactor suggestion | 🟠 Major

Refactor ReplaceAt into smaller steps.

ReplaceAt exceeds the 50-line limit and now has repeated cleanup/error branches. Split into helper functions (write temp binary, Windows backup, swap/rollback, cleanup) to satisfy the size rule and reduce branching complexity.

As per coding guidelines, “Functions must be less than 50 lines; files must be less than 800 lines.”

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

In `@cli/internal/selfupdate/updater.go` around lines 224 - 292, ReplaceAt is too
large and has repeated cleanup branches; refactor it by extracting smaller
helpers: create writeTempBinary(binaryData, dir) that creates temp file, writes
data, chmods, syncs and returns tmpPath (ensuring close/remove on error);
prepareWindowsBackup(execPath, dir) that on windows creates oldPath by moving
execPath to a temp backup and returns oldPath (or "" on non-windows) and handles
cleanup on errors; swapBinaryWithRollback(tmpPath, execPath, oldPath) that
renames tmpPath to execPath and on failure tries to restore oldPath back to
execPath; and cleanupOldBinary(oldPath) that best-effort removes the backup;
then make ReplaceAt call these helpers in sequence and propagate errors,
preserving existing error messages and best-effort removals.
cli/internal/diagnostics/collect.go (1)

40-101: 🛠️ Refactor suggestion | 🟠 Major

Split Collect into smaller helpers.

Collect is over the 50-line cap. Extract health collection, docker collection, and config redaction into helper functions to meet the limit and improve maintainability.

As per coding guidelines, “Functions must be less than 50 lines; files must be less than 800 lines.”

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

In `@cli/internal/diagnostics/collect.go` around lines 40 - 101, The Collect
function exceeds the 50-line limit—extract the Docker detection/compose output
block into a helper (e.g., collectDockerInfo(ctx, state) returning
DockerVersion, ComposeVersion, ContainerPS, RecentLogs and any errors), extract
the health-check block into a helper (e.g., collectHealth(ctx, backendPort)
returning HealthStatus, HealthBody and any errors), and extract the config
redaction/marshal into a helper (e.g., redactConfig(state) returning
ConfigRedacted string); then simplify Collect to call collectDockerInfo,
collectHealth, redactConfig, set their returned fields on the Report, and append
any returned errors to r.Errors so behavior stays identical.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cli/cmd/status.go`:
- Line 116: The defer currently swallows resp.Body.Close() errors; change the
anonymous defer to check the error and write a visible warning to the command
output (use the existing out writer). Replace defer func() { _ =
resp.Body.Close() }() with: defer func() { if err := resp.Body.Close(); err !=
nil { fmt.Fprintln(out, "warning: failed to close response body:", err) } }() so
the cleanup failure in the status command (where resp is used) is not silently
ignored.

In `@cli/internal/diagnostics/collect.go`:
- Line 79: The defer that currently swallows response close errors in Collect
must record any Close failure into the diagnostics.Errors; replace the anonymous
defer that does "_ = resp.Body.Close()" with a defer that calls
resp.Body.Close(), captures its error, and if non-nil appends a descriptive
error (including the returned error) to r.Errors (the diagnostics collector used
in Collect) so cleanup failures are recorded rather than ignored.

In `@cli/internal/health/check.go`:
- Line 67: The defer that currently discards resp.Body.Close() in checkOnce
should capture and propagate any close error when no earlier error exists:
replace the anonymous discard defer with a deferred function that stores the
close error (from resp.Body.Close()) into a named error return or checks the
current err variable and sets/returns the close error if err == nil; update
function signature of checkOnce to use a named error return if needed and ensure
the close error is returned rather than ignored.

In `@cli/internal/selfupdate/updater_test.go`:
- Line 394: Replace the silent os.Remove calls that discard errors (e.g., the
remove of oldPath and the other remove around lines referencing newPath) with
explicit error handling: call err := os.Remove(oldPath) and if err != nil &&
!errors.Is(err, os.ErrNotExist) { t.Fatalf("failed to remove %s: %v", oldPath,
err) } (and similarly for newPath); import "errors" if missing and keep behavior
tolerant only for os.ErrNotExist.

In `@cli/internal/selfupdate/updater.go`:
- Around line 239-240: The code currently discards errors from tmpFile.Close()
and os.Remove(tmpPath) in ReplaceAt and related branches (calls like _ =
tmpFile.Close(), _ = os.Remove(tmpPath)), masking filesystem failures and
rollback cleanup errors; update ReplaceAt (and the other error branches at the
noted locations) to capture and handle these errors explicitly by checking the
returned error values and combining them with the primary error using
errors.Join (or wrapping) so cleanup failures are reported alongside the
original error, and ensure rollback logic also surfaces any errors from
Close/Remove instead of silently swallowing them.
- Line 106: The deferred Close() calls currently swallow errors (e.g., defer
func() { _ = resp.Body.Close() }() at resp.Body usage and similar defers around
zip/reader closes); change each deferred anonymous func to capture Close()'s
error and merge or return it instead of discarding—e.g., use a named error
variable in the surrounding function (or capture the function's returned error)
and in the defer check closeErr := resp.Body.Close(); if err == nil { err =
closeErr } else if closeErr != nil { err = fmt.Errorf("%w; close error: %v",
err, closeErr) } (or log/propagate consistently). Update the defer blocks around
resp.Body.Close(), zip entry/reader closes (the defer at lines referenced) to
follow this pattern so I/O cleanup failures are surfaced.

---

Outside diff comments:
In `@cli/cmd/doctor.go`:
- Around line 27-84: runDoctor is over the 50-line limit because the GitHub
issue URL construction is inline; extract that logic into a new helper (e.g.,
buildGitHubIssueURL or createIssueURL) that accepts the diagnostics report (and
any needed version.RepoURL constant) and returns the final issue URL string.
Move the creation of issueTitle, issueBody, the QueryEscape/truncation logic,
and the final fmt.Sprintf into that helper, leaving runDoctor to call
buildGitHubIssueURL(report) and print the returned URL; update imports/usages as
needed and keep function behavior unchanged.

In `@cli/cmd/init.go`:
- Around line 46-52: Remove the duplicated comment above the config overwrite
check: delete one of the repeated "// Warn if re-initializing over existing
config (JWT secret will change)." lines found immediately before the if existing
:= config.StatePath(state.DataDir); fileExists(existing) { block so only a
single comment remains; the rest of the logic (isInteractive() check at function
entry, the if block using config.StatePath, fileExists, and fmt.Fprintf to
cmd.ErrOrStderr()) should be left unchanged.

In `@cli/internal/diagnostics/collect.go`:
- Around line 40-101: The Collect function exceeds the 50-line limit—extract the
Docker detection/compose output block into a helper (e.g.,
collectDockerInfo(ctx, state) returning DockerVersion, ComposeVersion,
ContainerPS, RecentLogs and any errors), extract the health-check block into a
helper (e.g., collectHealth(ctx, backendPort) returning HealthStatus, HealthBody
and any errors), and extract the config redaction/marshal into a helper (e.g.,
redactConfig(state) returning ConfigRedacted string); then simplify Collect to
call collectDockerInfo, collectHealth, redactConfig, set their returned fields
on the Report, and append any returned errors to r.Errors so behavior stays
identical.

In `@cli/internal/selfupdate/updater.go`:
- Around line 224-292: ReplaceAt is too large and has repeated cleanup branches;
refactor it by extracting smaller helpers: create writeTempBinary(binaryData,
dir) that creates temp file, writes data, chmods, syncs and returns tmpPath
(ensuring close/remove on error); prepareWindowsBackup(execPath, dir) that on
windows creates oldPath by moving execPath to a temp backup and returns oldPath
(or "" on non-windows) and handles cleanup on errors;
swapBinaryWithRollback(tmpPath, execPath, oldPath) that renames tmpPath to
execPath and on failure tries to restore oldPath back to execPath; and
cleanupOldBinary(oldPath) that best-effort removes the backup; then make
ReplaceAt call these helpers in sequence and propagate errors, preserving
existing error messages and best-effort removals.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d86b2cab-3d84-4e98-9fb4-1be2e99a8270

📥 Commits

Reviewing files that changed from the base of the PR and between 31d215e and 9b938ce.

📒 Files selected for processing (9)
  • cli/cmd/doctor.go
  • cli/cmd/init.go
  • cli/cmd/root.go
  • cli/cmd/start.go
  • cli/cmd/status.go
  • cli/internal/diagnostics/collect.go
  • cli/internal/health/check.go
  • cli/internal/selfupdate/updater.go
  • cli/internal/selfupdate/updater_test.go
📜 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). (6)
  • GitHub Check: CLI Test (windows-latest)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Web
  • GitHub Check: Build Backend
  • GitHub Check: Greptile Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{py,ts,tsx,js,jsx,vue,go}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{py,ts,tsx,js,jsx,vue,go}: Functions must be less than 50 lines; files must be less than 800 lines
Handle errors explicitly; never silently swallow exceptions
Validate at system boundaries: user input, external APIs, config files

Files:

  • cli/internal/diagnostics/collect.go
  • cli/internal/selfupdate/updater.go
  • cli/cmd/start.go
  • cli/cmd/status.go
  • cli/internal/health/check.go
  • cli/cmd/root.go
  • cli/cmd/doctor.go
  • cli/internal/selfupdate/updater_test.go
  • cli/cmd/init.go
cli/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

Go 1.26+ required for CLI development

Files:

  • cli/internal/diagnostics/collect.go
  • cli/internal/selfupdate/updater.go
  • cli/cmd/start.go
  • cli/cmd/status.go
  • cli/internal/health/check.go
  • cli/cmd/root.go
  • cli/cmd/doctor.go
  • cli/internal/selfupdate/updater_test.go
  • cli/cmd/init.go
🧠 Learnings (2)
📚 Learning: 2026-03-14T16:17:59.179Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:17:59.179Z
Learning: CLI (Go binary) must be built with `cd cli && go build -o synthorg ./main.go`; working directory must be the module root (exception to 'never use cd' rule)

Applied to files:

  • cli/cmd/start.go
  • cli/cmd/init.go
📚 Learning: 2026-03-14T16:17:59.179Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:17:59.179Z
Learning: Applies to cli/**/*.go : Go 1.26+ required for CLI development

Applied to files:

  • cli/cmd/start.go
  • cli/cmd/doctor.go
  • cli/cmd/init.go
🧬 Code graph analysis (3)
cli/cmd/start.go (2)
cli/internal/docker/client.go (1)
  • CheckMinVersions (69-78)
cli/internal/health/check.go (1)
  • WaitForHealthy (22-52)
cli/cmd/doctor.go (2)
cli/internal/diagnostics/collect.go (1)
  • Collect (40-101)
cli/internal/config/paths.go (1)
  • DataDir (17-23)
cli/cmd/init.go (2)
cli/internal/config/paths.go (1)
  • DataDir (17-23)
cli/internal/config/state.go (1)
  • StatePath (37-39)
🔇 Additional comments (4)
cli/cmd/init.go (1)

70-74: LGTM!

The explicit blank identifier assignments (_, _ =) appropriately satisfy errcheck while acknowledging that write errors to stdout are non-recoverable in a CLI context.

cli/cmd/root.go (1)

54-59: LGTM!

Appropriate use of blank identifier to satisfy errcheck for error output to stderr.

cli/cmd/doctor.go (1)

37-50: LGTM on errcheck changes.

The blank identifier pattern correctly suppresses errcheck warnings for diagnostic output operations.

cli/cmd/start.go (1)

46-76: LGTM!

All errcheck suppressions are appropriate for CLI stdout/stderr operations. The function stays within the 50-line limit.

return
}
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don’t silently swallow close errors in health output path.

Line 116 ignores resp.Body.Close() failures. Emit an explicit warning to out so cleanup errors are visible.

Proposed fix
-	defer func() { _ = resp.Body.Close() }()
+	defer func() {
+		if cerr := resp.Body.Close(); cerr != nil {
+			fmt.Fprintf(out, "  Backend: warning closing response body (%v)\n", cerr)
+		}
+	}()

As per coding guidelines, “Handle errors explicitly; never silently swallow exceptions.”

📝 Committable suggestion

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

Suggested change
defer func() { _ = resp.Body.Close() }()
defer func() {
if cerr := resp.Body.Close(); cerr != nil {
fmt.Fprintf(out, " Backend: warning closing response body (%v)\n", cerr)
}
}()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/cmd/status.go` at line 116, The defer currently swallows
resp.Body.Close() errors; change the anonymous defer to check the error and
write a visible warning to the command output (use the existing out writer).
Replace defer func() { _ = resp.Body.Close() }() with: defer func() { if err :=
resp.Body.Close(); err != nil { fmt.Fprintln(out, "warning: failed to close
response body:", err) } }() so the cleanup failure in the status command (where
resp is used) is not silently ignored.

r.Errors = append(r.Errors, fmt.Sprintf("health: %v", err))
} else {
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()
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

Record response close failures in diagnostics instead of ignoring them.

Line 79 currently hides close errors. In Collect, append close failures to r.Errors to keep cleanup explicit.

Proposed fix
-		defer func() { _ = resp.Body.Close() }()
+		defer func() {
+			if cerr := resp.Body.Close(); cerr != nil {
+				r.Errors = append(r.Errors, fmt.Sprintf("health close: %v", cerr))
+			}
+		}()

As per coding guidelines, “Handle errors explicitly; never silently swallow exceptions.”

📝 Committable suggestion

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

Suggested change
defer func() { _ = resp.Body.Close() }()
defer func() {
if cerr := resp.Body.Close(); cerr != nil {
r.Errors = append(r.Errors, fmt.Sprintf("health close: %v", cerr))
}
}()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/internal/diagnostics/collect.go` at line 79, The defer that currently
swallows response close errors in Collect must record any Close failure into the
diagnostics.Errors; replace the anonymous defer that does "_ =
resp.Body.Close()" with a defer that calls resp.Body.Close(), captures its
error, and if non-nil appends a descriptive error (including the returned error)
to r.Errors (the diagnostics collector used in Collect) so cleanup failures are
recorded rather than ignored.

return err
}
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()
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

Propagate resp.Body.Close() errors instead of discarding them.

Line 67 silently swallows the close error. Since checkOnce already returns error, capture and return close failures when no prior error exists.

Proposed fix
-func checkOnce(ctx context.Context, url string) error {
+func checkOnce(ctx context.Context, url string) (retErr error) {
 	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
 	if err != nil {
 		return err
 	}

 	resp, err := healthClient.Do(req)
 	if err != nil {
 		return err
 	}
-	defer func() { _ = resp.Body.Close() }()
+	defer func() {
+		if cerr := resp.Body.Close(); cerr != nil && retErr == nil {
+			retErr = fmt.Errorf("closing health response body: %w", cerr)
+		}
+	}()

As per coding guidelines, “Handle errors explicitly; never silently swallow exceptions.”

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

In `@cli/internal/health/check.go` at line 67, The defer that currently discards
resp.Body.Close() in checkOnce should capture and propagate any close error when
no earlier error exists: replace the anonymous discard defer with a deferred
function that stores the close error (from resp.Body.Close()) into a named error
return or checks the current err variable and sets/returns the close error if
err == nil; update function signature of checkOnce to use a named error return
if needed and ensure the close error is returned rather than ignored.

if runtime.GOOS == "windows" {
oldPath := fakeBinary + ".old"
os.Remove(oldPath)
_ = os.Remove(oldPath)
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

Avoid silent os.Remove suppression in test cleanup.

Lines 394 and 401 ignore delete failures. Keep cleanup explicit and tolerate only os.ErrNotExist intentionally.

Proposed fix
+		if err := os.Remove(oldPath); err != nil && !os.IsNotExist(err) {
+			t.Fatalf("remove old path: %v", err)
+		}
...
-		_ = os.Remove(oldPath)
+		if err := os.Remove(oldPath); err != nil && !os.IsNotExist(err) {
+			t.Fatalf("remove old path: %v", err)
+		}

As per coding guidelines, “Handle errors explicitly; never silently swallow exceptions.”

Also applies to: 401-401

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

In `@cli/internal/selfupdate/updater_test.go` at line 394, Replace the silent
os.Remove calls that discard errors (e.g., the remove of oldPath and the other
remove around lines referencing newPath) with explicit error handling: call err
:= os.Remove(oldPath) and if err != nil && !errors.Is(err, os.ErrNotExist) {
t.Fatalf("failed to remove %s: %v", oldPath, err) } (and similarly for newPath);
import "errors" if missing and keep behavior tolerant only for os.ErrNotExist.

return Release{}, fmt.Errorf("querying GitHub releases: %w", err)
}
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()
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

Close errors are being silently dropped in update download/extract paths.

Lines 106, 320, and 400 suppress Close() errors. These should be surfaced (or merged) so I/O cleanup failures are explicit, especially during update fetch/extract.

Proposed fix pattern (example for zip entry close)
 			result, readErr := io.ReadAll(io.LimitReader(rc, maxArchiveEntryBytes))
-			_ = rc.Close()
-			return result, readErr
+			if closeErr := rc.Close(); closeErr != nil && readErr == nil {
+				return nil, fmt.Errorf("closing zip entry: %w", closeErr)
+			}
+			return result, readErr

As per coding guidelines, “Handle errors explicitly; never silently swallow exceptions.”

Also applies to: 320-320, 400-400

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

In `@cli/internal/selfupdate/updater.go` at line 106, The deferred Close() calls
currently swallow errors (e.g., defer func() { _ = resp.Body.Close() }() at
resp.Body usage and similar defers around zip/reader closes); change each
deferred anonymous func to capture Close()'s error and merge or return it
instead of discarding—e.g., use a named error variable in the surrounding
function (or capture the function's returned error) and in the defer check
closeErr := resp.Body.Close(); if err == nil { err = closeErr } else if closeErr
!= nil { err = fmt.Errorf("%w; close error: %v", err, closeErr) } (or
log/propagate consistently). Update the defer blocks around resp.Body.Close(),
zip entry/reader closes (the defer at lines referenced) to follow this pattern
so I/O cleanup failures are surfaced.

Comment on lines +239 to +240
_ = tmpFile.Close()
_ = os.Remove(tmpPath)
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

ReplaceAt cleanup failures are masked across multiple error branches.

These branches swallow Close/Remove/rollback cleanup errors, which can hide filesystem problems and leak temp artifacts. Handle cleanup errors explicitly (e.g., aggregate with errors.Join).

As per coding guidelines, “Handle errors explicitly; never silently swallow exceptions.”

Also applies to: 244-245, 249-250, 254-255, 264-265, 268-269, 272-273, 282-283

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

In `@cli/internal/selfupdate/updater.go` around lines 239 - 240, The code
currently discards errors from tmpFile.Close() and os.Remove(tmpPath) in
ReplaceAt and related branches (calls like _ = tmpFile.Close(), _ =
os.Remove(tmpPath)), masking filesystem failures and rollback cleanup errors;
update ReplaceAt (and the other error branches at the noted locations) to
capture and handle these errors explicitly by checking the returned error values
and combining them with the primary error using errors.Join (or wrapping) so
cleanup failures are reported alongside the original error, and ensure rollback
logic also surfaces any errors from Close/Remove instead of silently swallowing
them.

Scheduler:
- Revert to sleep-first periodic loop (prevents duplicate meetings on
  restart/deploy)
- Emit meeting.started WS event before run_meeting (fixes dead event)
- Wire event publisher from channels_plugin in create_app
- Re-raise MemoryError/RecursionError in _resolve_participants and
  _run_and_publish (fixes inverted guard)
- Ignore CancelledError in stop() results (normal shutdown)
- Remove duplicate MEETING_EVENT_TRIGGERED log from controller
- MappingProxyType for _FREQUENCY_SECONDS immutability

Participant resolver:
- Strip whitespace in list/tuple context values
- Split _resolve_entry into _resolve_from_context + _resolve_from_registry
- Move NoParticipantsResolvedError import to module level in test

Go CLI:
- Fix all golangci-lint v2 violations (errcheck, gofmt, staticcheck)
- Format all Go files with gofmt

Frontend:
- Remove optional chaining on required MeetingMinutes arrays
…tend)

Scheduler:
- Revert to sleep-first periodic loop (prevents duplicate meetings)
- Emit meeting.started WS event before run_meeting (fixes dead event)
- Wire event publisher from channels_plugin in create_app
- Re-raise MemoryError/RecursionError in _resolve_participants/_run_and_publish
- Ignore CancelledError in stop() results (normal shutdown)
- Remove duplicate MEETING_EVENT_TRIGGERED log from controller

Participant resolver:
- Strip whitespace in list/tuple context values
- Split _resolve_entry into _resolve_from_context + _resolve_from_registry
- Move NoParticipantsResolvedError import to module level in test

Other:
- MappingProxyType for _FREQUENCY_SECONDS
- Remove optional chaining on required MeetingMinutes arrays
@Aureliolo Aureliolo changed the title feat: add meeting scheduler and event-triggered meetings feat: add meeting scheduler, event-triggered meetings, and Go CLI lint fixes Mar 14, 2026
Copilot AI review requested due to automatic review settings March 14, 2026 17:34
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 14, 2026 17:35 — with GitHub Actions Inactive
@Aureliolo Aureliolo merged commit 5550fa1 into main Mar 14, 2026
45 of 46 checks passed
@Aureliolo Aureliolo deleted the feat/meeting-scheduler branch March 14, 2026 17:37
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 14, 2026 17:37 — with GitHub Actions Inactive
Comment on lines +423 to +446
def _publish_started_event(self, meeting_type_name: str) -> None:
"""Publish a meeting.started WS event before execution begins.

Best-effort: publish errors are logged and swallowed.
"""
if self._event_publisher is None:
return
try:
self._event_publisher(
"meeting.started",
{
"meeting_type": meeting_type_name,
"status": "in_progress",
},
)
except MemoryError, RecursionError:
raise
except Exception:
logger.warning(
MEETING_SCHEDULER_ERROR,
meeting_type=meeting_type_name,
note="started event publish failed",
exc_info=True,
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

meeting.started payload missing meeting_id — frontend handler is dead code

_publish_started_event publishes a payload of {"meeting_type": ..., "status": "in_progress"} — deliberately before the meeting runs, so no meeting_id exists yet.

However, in web/src/stores/meetings.ts the handleWsEvent handler for meeting.started does:

case 'meeting.started': {
  const meetingId = payload.meeting_id   // always undefined here
  if (typeof meetingId === 'string' && meetingId) {
    void _refreshMeeting(meetingId)      // never reached
  }
  break
}

Because meeting_id is absent from the payload, typeof undefined === 'string' is false, so _refreshMeeting is never called for meeting.started events. The entire case branch is dead code.

Options to fix:

  1. Keep the event but emit it after the orchestrator creates an ID (i.e. from within run_meeting at the point a meeting_id is assigned) so the payload can include it.
  2. Since a meeting_id cannot exist before the meeting runs, remove the meeting_id check from the meeting.started branch and instead refresh the full list (fetchMeetings) on this event rather than a single record.
  3. Drop meeting.started until a meeting_id can be provided in the payload.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/ai_company/communication/meeting/scheduler.py
Line: 423-446

Comment:
**`meeting.started` payload missing `meeting_id` — frontend handler is dead code**

`_publish_started_event` publishes a payload of `{"meeting_type": ..., "status": "in_progress"}` — deliberately before the meeting runs, so no `meeting_id` exists yet.

However, in `web/src/stores/meetings.ts` the `handleWsEvent` handler for `meeting.started` does:

```ts
case 'meeting.started': {
  const meetingId = payload.meeting_id   // always undefined here
  if (typeof meetingId === 'string' && meetingId) {
    void _refreshMeeting(meetingId)      // never reached
  }
  break
}
```

Because `meeting_id` is absent from the payload, `typeof undefined === 'string'` is `false`, so `_refreshMeeting` is **never called** for `meeting.started` events. The entire case branch is dead code.

Options to fix:
1. Keep the event but emit it after the orchestrator creates an ID (i.e. from within `run_meeting` at the point a `meeting_id` is assigned) so the payload can include it.
2. Since a `meeting_id` cannot exist before the meeting runs, remove the `meeting_id` check from the `meeting.started` branch and instead refresh the full list (`fetchMeetings`) on this event rather than a single record.
3. Drop `meeting.started` until a `meeting_id` can be provided in the payload.

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

Comment on lines +170 to +174
scheduler = state.app_state.meeting_scheduler
records = await scheduler.trigger_event(
data.event_name,
context=data.context,
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

HTTP request blocks until all triggered meetings complete

trigger_event calls orchestrator.run_meeting(...) for each matching meeting type inside an asyncio.TaskGroup, and the trigger_meeting endpoint awaits the full result before sending a response.

Since LLM-driven meetings can take minutes per meeting, and multiple meetings can match the same event, the HTTP connection will be held open for the sum of all execution times. In practice this means:

  • HTTP clients (browsers, API callers, load balancers) will time out long before meetings finish.
  • The caller receives no response at all, making it impossible to tell if the trigger succeeded.

The typical pattern for long-running triggers is to fire the tasks as background work and return immediately with an accepted status. For example, create the tasks with asyncio.create_task (storing them in _tasks so stop() can cancel them), return 202 Accepted with the meeting IDs that will be populated, and let clients poll GET /meetings or subscribe to the meetings WS channel for completion events.

# Instead of awaiting the full execution:
records = await scheduler.trigger_event(data.event_name, context=data.context)
return Response(content=..., status_code=200)

# Fire and return 202 immediately, letting WS events deliver the result:
asyncio.create_task(scheduler.trigger_event(data.event_name, context=data.context))
return Response(content=..., status_code=202)
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/ai_company/api/controllers/meetings.py
Line: 170-174

Comment:
**HTTP request blocks until all triggered meetings complete**

`trigger_event` calls `orchestrator.run_meeting(...)` for each matching meeting type inside an `asyncio.TaskGroup`, and the `trigger_meeting` endpoint `await`s the full result before sending a response.

Since LLM-driven meetings can take minutes per meeting, and multiple meetings can match the same event, the HTTP connection will be held open for the sum of all execution times. In practice this means:
- HTTP clients (browsers, API callers, load balancers) will time out long before meetings finish.
- The caller receives no response at all, making it impossible to tell if the trigger succeeded.

The typical pattern for long-running triggers is to fire the tasks as background work and return immediately with an accepted status. For example, create the tasks with `asyncio.create_task` (storing them in `_tasks` so `stop()` can cancel them), return 202 Accepted with the meeting IDs that will be populated, and let clients poll `GET /meetings` or subscribe to the `meetings` WS channel for completion events.

```python
# Instead of awaiting the full execution:
records = await scheduler.trigger_event(data.event_name, context=data.context)
return Response(content=..., status_code=200)

# Fire and return 202 immediately, letting WS events deliver the result:
asyncio.create_task(scheduler.trigger_event(data.event_name, context=data.context))
return Response(content=..., status_code=202)
```

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

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Implements the missing “meeting scheduler + triggering” layer for the communication subsystem, exposes meeting records via REST + WebSocket events, and adds a full Meeting Logs UI in the dashboard; also includes Go CLI lint/CI fixes.

Changes:

  • Added MeetingScheduler service (periodic + event-triggered) with participant resolution and lifecycle wiring.
  • Added meetings REST endpoints (/meetings, /meetings/{id}, /meetings/trigger) + WS channel/events (meetings, meeting.started/completed/failed), and updated web UI/store to consume them.
  • Updated Go CLI linting configuration and resolved errcheck/bodyclose-related issues; tweaked CI shell for Go tests.

Reviewed changes

Copilot reviewed 47 out of 47 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
web/src/views/MeetingLogsPage.vue Replaced placeholder with interactive meetings table, details sidebar, status filter, and trigger dialog + WS wiring
web/src/styles/theme.ts Expanded Status union + added meeting status color tokens
web/src/stores/meetings.ts Added Pinia store for meeting list/detail, trigger API call, and WS-driven refresh
web/src/api/types.ts Added meeting domain types + WS meetings channel and meeting event types
web/src/api/endpoints/meetings.ts Added REST client for list/get/trigger meeting endpoints
web/src/tests/views/MeetingLogsPage.test.ts Removed old “Coming Soon” view tests (placeholder no longer applies)
web/src/tests/views/MeetingLogsPage.store-integration.test.ts Added minimal store presence/integration test
web/src/tests/stores/meetings.test.ts Added unit tests for meeting store API + WS refresh behavior
tests/unit/communication/test_config.py Updated meeting config tests for MeetingFrequency enum usage
tests/unit/communication/meeting/test_scheduler.py Added scheduler unit tests (periodic start/stop, triggering, publishing)
tests/unit/communication/meeting/test_participant.py Added participant resolver tests (context/registry resolution + dedup)
tests/unit/communication/meeting/test_frequency.py Added MeetingFrequency + frequency_to_seconds tests
tests/unit/communication/meeting/test_config_frequency.py Added tests for config coercion/validation with MeetingFrequency
tests/unit/communication/conftest.py Updated fixture to use MeetingFrequency enum
tests/unit/api/test_channels.py Added meetings channel constant to channel set tests
tests/unit/api/test_app.py Updated lifecycle tests for new startup/shutdown signature and scheduler lifecycle
tests/unit/api/controllers/test_meetings.py Added REST controller tests for list/get/trigger + request validation bounds
tests/integration/communication/test_meeting_integration.py Updated integration test to use MeetingFrequency enum
src/ai_company/observability/events/meeting.py Added scheduler + API-level meeting event constants
src/ai_company/communication/meeting/scheduler.py Implemented meeting scheduler (periodic + event-triggered) and WS publish hooks
src/ai_company/communication/meeting/participant.py Added ParticipantResolver protocol + RegistryParticipantResolver implementation
src/ai_company/communication/meeting/frequency.py Added MeetingFrequency StrEnum and seconds conversion mapping
src/ai_company/communication/meeting/errors.py Added scheduler-specific error classes
src/ai_company/communication/meeting/init.py Exported new scheduler/frequency/participant APIs
src/ai_company/communication/config.py Switched MeetingTypeConfig.frequency to `MeetingFrequency
src/ai_company/api/ws_models.py Added meeting WS event types to backend enum
src/ai_company/api/state.py Extended AppState with meeting orchestrator + scheduler service accessors
src/ai_company/api/controllers/meetings.py Implemented meetings REST controller (list/get/trigger) + bounded trigger request model
src/ai_company/api/channels.py Added CHANNEL_MEETINGS and included it in ALL_CHANNELS
src/ai_company/api/app.py Wired scheduler lifecycle into startup/shutdown and added meeting WS publisher integration
docs/design/communication.md Documented scheduler design (frequency scheduling, triggers, participant resolution)
cli/internal/selfupdate/updater_test.go Lint-related fixes (errcheck-style ignores; fmt.Fprintf usage)
cli/internal/selfupdate/updater.go Lint fixes (defer body close wrapper; errcheck-style ignores; formatting)
cli/internal/health/check.go Lint fix: ignore close error via deferred wrapper
cli/internal/diagnostics/collect.go Lint fix: ignore close error via deferred wrapper
cli/cmd/version.go Lint fix: ignore fmt.Fprintf return values
cli/cmd/update.go Lint fix: ignore fmt.Fprint* return values
cli/cmd/uninstall.go Lint fix: ignore fmt.Fprint* return values
cli/cmd/stop.go Lint fix: ignore fmt.Fprintln return values
cli/cmd/status.go Lint fix: ignore fmt.Fprint* return values; ignore close error wrapper
cli/cmd/start.go Lint fix: ignore fmt.Fprint* return values
cli/cmd/root.go Lint fix: ignore fmt.Fprintln return values
cli/cmd/init.go Lint fix: ignore fmt.Fprintf return values
cli/cmd/doctor.go Lint fix: ignore fmt.Fprint* return values
cli/.golangci.yml Updated golangci-lint config for v2 (formatters section; removed gosimple)
CLAUDE.md Updated repo structure/docs to reflect meeting scheduler + meetings store
.github/workflows/cli.yml Set shell: bash for go test step for Windows compatibility

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

Comment on lines +45 to +65
onMounted(async () => {
try {
if (authStore.token && !wsStore.connected) {
wsStore.connect(authStore.token)
}
wsStore.subscribe(['meetings'])
wsStore.onChannelEvent('meetings', meetingStore.handleWsEvent)
} catch (err) {
console.error('WebSocket setup failed:', sanitizeForLog(err))
}
try {
await meetingStore.fetchMeetings()
} catch (err) {
console.error('Initial data fetch failed:', sanitizeForLog(err))
}
})

onUnmounted(() => {
wsStore.unsubscribe(['meetings'])
wsStore.offChannelEvent('meetings', meetingStore.handleWsEvent)
})
Comment on lines +431 to +437
self._event_publisher(
"meeting.started",
{
"meeting_type": meeting_type_name,
"status": "in_progress",
},
)
Comment on lines +483 to +486
# Wire meeting event publisher to the meetings WS channel.
if meeting_scheduler is not None and meeting_scheduler._event_publisher is None: # noqa: SLF001
meeting_scheduler._event_publisher = _make_meeting_publisher( # noqa: SLF001
channels_plugin,
Aureliolo added a commit that referenced this pull request Mar 15, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.2.0](v0.1.4...v0.2.0)
(2026-03-15)

##First probably usable release? Most likely not no and everything will break
### Features

* add /get/ installation page for CLI installer
([#413](#413))
([6a47e4a](6a47e4a))
* add cross-platform Go CLI for container lifecycle management
([#401](#401))
([0353d9e](0353d9e)),
closes [#392](#392)
* add explicit ScanOutcome signal to OutputScanResult
([#394](#394))
([be33414](be33414)),
closes [#284](#284)
* add meeting scheduler, event-triggered meetings, and Go CLI lint fixes
([#407](#407))
([5550fa1](5550fa1))
* wire MultiAgentCoordinator into runtime
([#396](#396))
([7a9e516](7a9e516))


### Bug Fixes

* CLA signatures branch + declutter repo root
([#409](#409))
([cabe953](cabe953))
* correct Release Please branch name in release workflow
([#410](#410))
([515d816](515d816))
* replace slsa-github-generator with attest-build-provenance, fix DAST
([#424](#424))
([eeaadff](eeaadff))
* resolve CodeQL path-injection alerts in Go CLI
([#412](#412))
([f41bf16](f41bf16))


### Refactoring

* rename package from ai_company to synthorg
([#422](#422))
([df27c6e](df27c6e)),
closes [#398](#398)


### Tests

* add fuzz and property-based testing across all layers
([#421](#421))
([115a742](115a742))


### CI/CD

* add SLSA L3 provenance for CLI binaries and container images
([#423](#423))
([d3dc75d](d3dc75d))
* bump the major group with 4 updates
([#405](#405))
([20c7a04](20c7a04))


### Maintenance

* bump github.com/spf13/cobra from 1.9.1 to 1.10.2 in /cli in the
minor-and-patch group
([#402](#402))
([e31edbb](e31edbb))
* narrow BSL Additional Use Grant and add CLA
([#408](#408))
([5ab15bd](5ab15bd)),
closes [#406](#406)

---
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: implement meeting scheduler and event-triggered meetings

2 participants