Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
fb7ce53
feat: add meeting scheduler and event-triggered meetings (#264)
Aureliolo Mar 14, 2026
0d39fcf
fix: address 19 review findings from 14 pre-PR agents
Aureliolo Mar 14, 2026
d5a82c9
fix: address 32 review findings from 16 agents + 4 external reviewers
Aureliolo Mar 14, 2026
eadc1c2
fix: log warning on unrecognized context value type in participant re…
Aureliolo Mar 14, 2026
abd189d
fix: address 8 external review findings
Aureliolo Mar 14, 2026
9286402
fix: annotate context dict type in tests for mypy strict invariance
Aureliolo Mar 14, 2026
3726dd5
fix: address 3 scheduler review findings
Aureliolo Mar 14, 2026
48f0d00
fix: address 8 review findings across docs, tests, and frontend
Aureliolo Mar 14, 2026
34fba42
fix: derive sidebar selected from store to stay reactive to WS updates
Aureliolo Mar 14, 2026
e90add0
fix: address 5 review findings across resolver, scheduler, API, and s…
Aureliolo Mar 14, 2026
7511fcc
fix: correct _run_periodic docstring to match execute-first loop order
Aureliolo Mar 14, 2026
e08823d
fix: resolve CLI CI failures (golangci-lint v2 + Windows PowerShell)
Aureliolo Mar 14, 2026
7166a56
fix: address 5 reviewer findings (scheduler + frontend)
Aureliolo Mar 14, 2026
31d215e
fix: remove gosimple linter (merged into staticcheck in golangci-lint…
Aureliolo Mar 14, 2026
9b938ce
fix: resolve 29 errcheck violations in Go CLI code
Aureliolo Mar 14, 2026
2704e81
fix: address reviewer findings across scheduler, Go CLI, and frontend
Aureliolo Mar 14, 2026
17d0f35
fix: address reviewer findings (scheduler, resolver, app wiring, fron…
Aureliolo Mar 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ jobs:

- name: Run tests with race detector and coverage
working-directory: cli
shell: bash
run: go test -race -coverprofile=coverage.out -covermode=atomic ./...

- name: Upload coverage
Expand Down
7 changes: 4 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ src/ai_company/
api/ # Litestar REST + WebSocket API (controllers, guards, channels, JWT + API key auth, approval gate integration, coordination endpoint)
budget/ # Cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)
cli/ # Python CLI module (superseded by top-level cli/ Go binary)
communication/ # Message bus, dispatcher, messenger, channels, delegation, loop prevention, conflict resolution, meeting protocol
communication/ # Message bus, dispatcher, messenger, channels, delegation, loop prevention, conflict resolution
meeting/ # Meeting protocol (round-robin, position papers, structured phases), scheduler (frequency, participant resolver), orchestrator
config/ # YAML company config loading and validation
core/ # Shared domain models, base classes, and resilience config (RetryConfig, RateLimiterConfig)
engine/ # Agent orchestration, execution loops, parallel execution, task decomposition, routing, task assignment, centralized single-writer task state engine (TaskEngine), task lifecycle, recovery, shutdown, workspace isolation, coordination (multi-agent pipeline: TopologyDispatcher protocol, 4 dispatchers — SAS/centralized/decentralized/context-dependent, wave execution, workspace lifecycle integration, CoordinationSectionConfig company config bridge, build_coordinator factory), coordination error classification, prompt policy validation, checkpoint recovery (checkpoint/, per-turn persistence, heartbeat detection, CheckpointRecoveryStrategy), approval gate (escalation detection, context parking/resume, EscalationInfo/ResumePayload models)
Expand All @@ -131,7 +132,7 @@ web/ # Vue 3 + PrimeVue + Tailwind CSS dashboard
components/ # Vue components organized by feature (agents/, approvals/, budget/, common/, dashboard/, layout/, messages/, org-chart/, tasks/)
composables/ # Reusable composition functions (useAuth, useLoginLockout, usePolling, useOptimisticUpdate)
router/ # Vue Router config with auth guards
stores/ # Pinia stores (auth, agents, tasks, budget, messages, approvals, websocket, analytics, company, providers)
stores/ # Pinia stores (auth, agents, tasks, budget, messages, meetings, approvals, websocket, analytics, company, providers)
styles/ # Global CSS and PrimeVue theme configuration
utils/ # Constants, formatters, error helpers
views/ # Page-level components (LoginPage, SetupPage, DashboardPage, OrgChartPage, TaskBoardPage, MessageFeedPage, ApprovalQueuePage, AgentProfilesPage, AgentDetailPage, BudgetPanelPage, MeetingLogsPage, ArtifactBrowserPage, SettingsPage)
Expand Down Expand Up @@ -177,7 +178,7 @@ cli/ # Go CLI binary (cross-platform, manages Docker lifecycle)
- **Every module** with business logic MUST have: `from ai_company.observability import get_logger` then `logger = get_logger(__name__)`
- **Never** use `import logging` / `logging.getLogger()` / `print()` in application code
- **Variable name**: always `logger` (not `_logger`, not `log`)
- **Event names**: always use constants from the domain-specific module under `ai_company.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`, `BUDGET_RECORD_ADDED` from `events.budget`, `CFO_ANOMALY_DETECTED` from `events.cfo`, `CONFLICT_DETECTED` from `events.conflict`, `MEETING_STARTED` from `events.meeting`, `CLASSIFICATION_START` from `events.classification`, `CONSOLIDATION_START` from `events.consolidation`, `ORG_MEMORY_QUERY_START` from `events.org_memory`, `API_REQUEST_STARTED` from `events.api`, `API_ROUTE_NOT_FOUND` from `events.api`, `API_COORDINATION_STARTED` from `events.api`, `API_COORDINATION_COMPLETED` from `events.api`, `API_COORDINATION_FAILED` from `events.api`, `API_COORDINATION_AGENT_RESOLVE_FAILED` from `events.api`, `CODE_RUNNER_EXECUTE_START` from `events.code_runner`, `DOCKER_EXECUTE_START` from `events.docker`, `MCP_INVOKE_START` from `events.mcp`, `SECURITY_EVALUATE_START` from `events.security`, `HR_HIRING_REQUEST_CREATED` from `events.hr`, `PERF_METRIC_RECORDED` from `events.performance`, `TRUST_EVALUATE_START` from `events.trust`, `PROMOTION_EVALUATE_START` from `events.promotion`, `PROMPT_BUILD_START` from `events.prompt`, `MEMORY_RETRIEVAL_START` from `events.memory`, `MEMORY_BACKEND_CONNECTED` from `events.memory`, `MEMORY_ENTRY_STORED` from `events.memory`, `MEMORY_BACKEND_SYSTEM_ERROR` from `events.memory`, `AUTONOMY_ACTION_AUTO_APPROVED` from `events.autonomy`, `TIMEOUT_POLICY_EVALUATED` from `events.timeout`, `PERSISTENCE_AUDIT_ENTRY_SAVED` from `events.persistence`, `TASK_ENGINE_STARTED` from `events.task_engine`, `COORDINATION_STARTED` from `events.coordination`, `COORDINATION_FACTORY_BUILT` from `events.coordination`, `COMMUNICATION_DISPATCH_START` from `events.communication`, `COMPANY_STARTED` from `events.company`, `CONFIG_LOADED` from `events.config`, `CORRELATION_ID_CREATED` from `events.correlation`, `DECOMPOSITION_STARTED` from `events.decomposition`, `DELEGATION_STARTED` from `events.delegation`, `EXECUTION_LOOP_START` from `events.execution`, `CHECKPOINT_SAVED` from `events.checkpoint`, `PERSISTENCE_CHECKPOINT_SAVED` from `events.persistence`, `GIT_OPERATION_START` from `events.git`, `PARALLEL_GROUP_START` from `events.parallel`, `PERSONALITY_LOADED` from `events.personality`, `QUOTA_CHECKED` from `events.quota`, `ROLE_ASSIGNED` from `events.role`, `ROUTING_STARTED` from `events.routing`, `SANDBOX_EXECUTE_START` from `events.sandbox`, `TASK_CREATED` from `events.task`, `TASK_ASSIGNMENT_STARTED` from `events.task_assignment`, `TASK_ROUTING_STARTED` from `events.task_routing`, `TEMPLATE_LOADED` from `events.template`, `TOOL_INVOKE_START` from `events.tool`, `TOOL_OUTPUT_WITHHELD` from `events.tool`, `WORKSPACE_CREATED` from `events.workspace`, `APPROVAL_GATE_ESCALATION_DETECTED` from `events.approval_gate`, `APPROVAL_GATE_ESCALATION_FAILED` from `events.approval_gate`, `APPROVAL_GATE_INITIALIZED` from `events.approval_gate`, `APPROVAL_GATE_RISK_CLASSIFIED` from `events.approval_gate`, `APPROVAL_GATE_RISK_CLASSIFY_FAILED` from `events.approval_gate`, `APPROVAL_GATE_CONTEXT_PARKED` from `events.approval_gate`, `APPROVAL_GATE_CONTEXT_PARK_FAILED` from `events.approval_gate`, `APPROVAL_GATE_PARK_TASKLESS` from `events.approval_gate`, `APPROVAL_GATE_RESUME_STARTED` from `events.approval_gate`, `APPROVAL_GATE_CONTEXT_RESUMED` from `events.approval_gate`, `APPROVAL_GATE_RESUME_FAILED` from `events.approval_gate`, `APPROVAL_GATE_RESUME_DELETE_FAILED` from `events.approval_gate`, `APPROVAL_GATE_RESUME_TRIGGERED` from `events.approval_gate`, `APPROVAL_GATE_NO_PARKED_CONTEXT` from `events.approval_gate`, `APPROVAL_GATE_LOOP_WIRING_WARNING` from `events.approval_gate`). Import directly: `from ai_company.observability.events.<domain> import EVENT_CONSTANT`
- **Event names**: always use constants from the domain-specific module under `ai_company.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`, `BUDGET_RECORD_ADDED` from `events.budget`, `CFO_ANOMALY_DETECTED` from `events.cfo`, `CONFLICT_DETECTED` from `events.conflict`, `MEETING_STARTED` from `events.meeting`, `MEETING_SCHEDULER_STARTED` from `events.meeting`, `MEETING_SCHEDULER_ERROR` from `events.meeting`, `MEETING_SCHEDULER_STOPPED` from `events.meeting`, `MEETING_PERIODIC_TRIGGERED` from `events.meeting`, `MEETING_EVENT_TRIGGERED` from `events.meeting`, `MEETING_PARTICIPANTS_RESOLVED` from `events.meeting`, `MEETING_NO_PARTICIPANTS` from `events.meeting`, `MEETING_NOT_FOUND` from `events.meeting`, `CLASSIFICATION_START` from `events.classification`, `CONSOLIDATION_START` from `events.consolidation`, `ORG_MEMORY_QUERY_START` from `events.org_memory`, `API_REQUEST_STARTED` from `events.api`, `API_ROUTE_NOT_FOUND` from `events.api`, `API_COORDINATION_STARTED` from `events.api`, `API_COORDINATION_COMPLETED` from `events.api`, `API_COORDINATION_FAILED` from `events.api`, `API_COORDINATION_AGENT_RESOLVE_FAILED` from `events.api`, `CODE_RUNNER_EXECUTE_START` from `events.code_runner`, `DOCKER_EXECUTE_START` from `events.docker`, `MCP_INVOKE_START` from `events.mcp`, `SECURITY_EVALUATE_START` from `events.security`, `HR_HIRING_REQUEST_CREATED` from `events.hr`, `PERF_METRIC_RECORDED` from `events.performance`, `TRUST_EVALUATE_START` from `events.trust`, `PROMOTION_EVALUATE_START` from `events.promotion`, `PROMPT_BUILD_START` from `events.prompt`, `MEMORY_RETRIEVAL_START` from `events.memory`, `MEMORY_BACKEND_CONNECTED` from `events.memory`, `MEMORY_ENTRY_STORED` from `events.memory`, `MEMORY_BACKEND_SYSTEM_ERROR` from `events.memory`, `AUTONOMY_ACTION_AUTO_APPROVED` from `events.autonomy`, `TIMEOUT_POLICY_EVALUATED` from `events.timeout`, `PERSISTENCE_AUDIT_ENTRY_SAVED` from `events.persistence`, `TASK_ENGINE_STARTED` from `events.task_engine`, `COORDINATION_STARTED` from `events.coordination`, `COORDINATION_FACTORY_BUILT` from `events.coordination`, `COMMUNICATION_DISPATCH_START` from `events.communication`, `COMPANY_STARTED` from `events.company`, `CONFIG_LOADED` from `events.config`, `CORRELATION_ID_CREATED` from `events.correlation`, `DECOMPOSITION_STARTED` from `events.decomposition`, `DELEGATION_STARTED` from `events.delegation`, `EXECUTION_LOOP_START` from `events.execution`, `CHECKPOINT_SAVED` from `events.checkpoint`, `PERSISTENCE_CHECKPOINT_SAVED` from `events.persistence`, `GIT_OPERATION_START` from `events.git`, `PARALLEL_GROUP_START` from `events.parallel`, `PERSONALITY_LOADED` from `events.personality`, `QUOTA_CHECKED` from `events.quota`, `ROLE_ASSIGNED` from `events.role`, `ROUTING_STARTED` from `events.routing`, `SANDBOX_EXECUTE_START` from `events.sandbox`, `TASK_CREATED` from `events.task`, `TASK_ASSIGNMENT_STARTED` from `events.task_assignment`, `TASK_ROUTING_STARTED` from `events.task_routing`, `TEMPLATE_LOADED` from `events.template`, `TOOL_INVOKE_START` from `events.tool`, `TOOL_OUTPUT_WITHHELD` from `events.tool`, `WORKSPACE_CREATED` from `events.workspace`, `APPROVAL_GATE_ESCALATION_DETECTED` from `events.approval_gate`, `APPROVAL_GATE_ESCALATION_FAILED` from `events.approval_gate`, `APPROVAL_GATE_INITIALIZED` from `events.approval_gate`, `APPROVAL_GATE_RISK_CLASSIFIED` from `events.approval_gate`, `APPROVAL_GATE_RISK_CLASSIFY_FAILED` from `events.approval_gate`, `APPROVAL_GATE_CONTEXT_PARKED` from `events.approval_gate`, `APPROVAL_GATE_CONTEXT_PARK_FAILED` from `events.approval_gate`, `APPROVAL_GATE_PARK_TASKLESS` from `events.approval_gate`, `APPROVAL_GATE_RESUME_STARTED` from `events.approval_gate`, `APPROVAL_GATE_CONTEXT_RESUMED` from `events.approval_gate`, `APPROVAL_GATE_RESUME_FAILED` from `events.approval_gate`, `APPROVAL_GATE_RESUME_DELETE_FAILED` from `events.approval_gate`, `APPROVAL_GATE_RESUME_TRIGGERED` from `events.approval_gate`, `APPROVAL_GATE_NO_PARKED_CONTEXT` from `events.approval_gate`, `APPROVAL_GATE_LOOP_WIRING_WARNING` from `events.approval_gate`). Import directly: `from ai_company.observability.events.<domain> import EVENT_CONSTANT`
- **Structured kwargs**: always `logger.info(EVENT, key=value)` — never `logger.info("msg %s", val)`
- **All error paths** must log at WARNING or ERROR with context before raising
- **All state transitions** must log at INFO
Expand Down
6 changes: 4 additions & 2 deletions cli/.golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ linters:
- ineffassign
- staticcheck
- unused
- gosimple
- gocritic
- gofmt
- bodyclose
- errname
- errorlint
Expand All @@ -21,6 +19,10 @@ linters:
- unconvert
- unparam

formatters:
enable:
- gofmt

issues:
max-issues-per-linter: 50
max-same-issues: 5
10 changes: 5 additions & 5 deletions cli/cmd/doctor.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,20 @@ func runDoctor(cmd *cobra.Command, args []string) error {
return fmt.Errorf("loading config: %w", err)
}

fmt.Fprintln(out, "Collecting diagnostics...")
_, _ = fmt.Fprintln(out, "Collecting diagnostics...")
report := diagnostics.Collect(ctx, state)
text := report.FormatText()

// Save to file.
filename := fmt.Sprintf("synthorg-diagnostic-%s.txt", time.Now().Format("20060102-150405"))
savePath := filepath.Join(state.DataDir, filename)
if err := os.WriteFile(savePath, []byte(text), 0o600); err != nil {
fmt.Fprintf(cmd.ErrOrStderr(), "Warning: could not save diagnostic file: %v\n", err)
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Warning: could not save diagnostic file: %v\n", err)
} else {
fmt.Fprintf(out, "Saved to: %s\n\n", savePath)
_, _ = fmt.Fprintf(out, "Saved to: %s\n\n", savePath)
}

fmt.Fprintln(out, text)
_, _ = 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)
Expand Down Expand Up @@ -78,7 +78,7 @@ func runDoctor(cmd *cobra.Command, args []string) error {
encodedBody,
)

fmt.Fprintf(out, "File a bug report:\n %s\n", issueURL)
_, _ = fmt.Fprintf(out, "File a bug report:\n %s\n", issueURL)

return nil
}
12 changes: 6 additions & 6 deletions cli/cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func runInit(cmd *cobra.Command, _ []string) error {
// Warn if re-initializing over existing config (JWT secret will change).
// isInteractive() is already checked at function entry, so prompt is safe.
if existing := config.StatePath(state.DataDir); fileExists(existing) {
fmt.Fprintf(cmd.ErrOrStderr(),
_, _ = fmt.Fprintf(cmd.ErrOrStderr(),
"Warning: existing config at %s will be overwritten.\n"+
"A new JWT secret will be generated — running containers will need a restart.\n", existing)
var proceed bool
Expand All @@ -67,11 +67,11 @@ func runInit(cmd *cobra.Command, _ []string) error {
}

composePath := filepath.Join(state.DataDir, "compose.yml")
fmt.Fprintf(cmd.OutOrStdout(), "\nSynthOrg initialized in %s\n", state.DataDir)
fmt.Fprintf(cmd.OutOrStdout(), " Compose file: %s\n", composePath)
fmt.Fprintf(cmd.OutOrStdout(), " Config: %s\n", config.StatePath(state.DataDir))
fmt.Fprintf(cmd.OutOrStdout(), "\nKeep compose.yml and config.json private — they contain your JWT secret.\n")
fmt.Fprintf(cmd.OutOrStdout(), "Run 'synthorg start' to launch.\n")
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "\nSynthOrg initialized in %s\n", state.DataDir)
_, _ = fmt.Fprintf(cmd.OutOrStdout(), " Compose file: %s\n", composePath)
_, _ = fmt.Fprintf(cmd.OutOrStdout(), " Config: %s\n", config.StatePath(state.DataDir))
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "\nKeep compose.yml and config.json private — they contain your JWT secret.\n")
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "Run 'synthorg start' to launch.\n")

return nil
}
Expand Down
2 changes: 1 addition & 1 deletion cli/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func isInteractive() bool {
// Execute runs the root command.
func Execute() error {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(rootCmd.ErrOrStderr(), err)
_, _ = fmt.Fprintln(rootCmd.ErrOrStderr(), err)
return err
}
return nil
Expand Down
18 changes: 9 additions & 9 deletions cli/cmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,36 +43,36 @@ func runStart(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
fmt.Fprintf(cmd.OutOrStdout(), "Docker %s, Compose %s\n", info.DockerVersion, info.ComposeVersion)
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "Docker %s, Compose %s\n", info.DockerVersion, info.ComposeVersion)

// Check minimum versions.
for _, w := range docker.CheckMinVersions(info) {
fmt.Fprintf(cmd.ErrOrStderr(), "Warning: %s\n", w)
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Warning: %s\n", w)
}

// Pull latest images.
fmt.Fprintln(cmd.OutOrStdout(), "Pulling images...")
_, _ = fmt.Fprintln(cmd.OutOrStdout(), "Pulling images...")
if err := composeRun(ctx, cmd, info, state.DataDir, "pull"); err != nil {
return fmt.Errorf("pulling images: %w", err)
}

// Start containers.
fmt.Fprintln(cmd.OutOrStdout(), "Starting containers...")
_, _ = fmt.Fprintln(cmd.OutOrStdout(), "Starting containers...")
if err := composeRun(ctx, cmd, info, state.DataDir, "up", "-d"); err != nil {
return fmt.Errorf("starting containers: %w", err)
}

// Wait for health.
fmt.Fprintln(cmd.OutOrStdout(), "Waiting for backend to become healthy...")
_, _ = fmt.Fprintln(cmd.OutOrStdout(), "Waiting for backend to become healthy...")
healthURL := fmt.Sprintf("http://localhost:%d/api/v1/health", state.BackendPort)
if err := health.WaitForHealthy(ctx, healthURL, 90*time.Second, 2*time.Second, 5*time.Second); err != nil {
fmt.Fprintf(cmd.ErrOrStderr(), "Containers are running but health check failed. Run 'synthorg doctor' for diagnostics.\n")
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Containers are running but health check failed. Run 'synthorg doctor' for diagnostics.\n")
return fmt.Errorf("health check did not pass: %w", err)
}

fmt.Fprintln(cmd.OutOrStdout(), "SynthOrg is running!")
fmt.Fprintf(cmd.OutOrStdout(), " API: http://localhost:%d/api/v1/health\n", state.BackendPort)
fmt.Fprintf(cmd.OutOrStdout(), " Dashboard: http://localhost:%d\n", state.WebPort)
_, _ = fmt.Fprintln(cmd.OutOrStdout(), "SynthOrg is running!")
_, _ = fmt.Fprintf(cmd.OutOrStdout(), " API: http://localhost:%d/api/v1/health\n", state.BackendPort)
_, _ = fmt.Fprintf(cmd.OutOrStdout(), " Dashboard: http://localhost:%d\n", state.WebPort)
return nil
}

Expand Down
Loading
Loading