Repository navigation
feat: implement decoupled orchestrator with agent lifecycle state machine - #8
Merged
Merged
Conversation
Add new 'off' state to distinguish 'not activated' from 'idle but watching'. State semantics: - off: No orchestrator instance, no goroutine running, not watching - idle: Orchestrator running, polling for work, none available - active: Orchestrator running, processing tasks - paused: Orchestrator running but scheduler paused Changes: - Add OrchestratorOff constant - RegisterRepo() initializes in StateOff (not idle) - StopRun() and runOrchestrator() completion transition to StateOff - Error cleanup in StartRun() resets to StateOff
Rename the per-repo state/lifecycle wrapper from RepoOrchestrator to OrchestratorLifecycle. This name better reflects that the struct manages the orchestrator's lifecycle (Off/Idle/Active/Paused states), not the execution itself. Updated files: - pkg/daemon/orchestrator_manager.go (struct definition and all references) - docs/design/active-orchestrator-model.md (documentation references)
…-uxle) When using bwrap, setResourceLimits was not being called, which meant Setpgid=true was not set on the command. This caused killProcessGroup to fail silently on context cancellation because the process wasn't its own process group leader. Now setResourceLimits is always called regardless of bwrap usage. This ensures that when the orchestrator is stopped, the context cancellation properly terminates all running agents. The bwrap --die-with-parent flag is a backup for unexpected parent death, but for graceful shutdown we need process group isolation.
The kill agent button in the web UI was not working because the daemon was created with nil scheduler. The decoupled orchestrator model stores schedulers within OrchestratorManager, not the daemon. Changes: - Add KillAgent method to OrchestratorManager that looks up the agent's run and routes the kill request to the correct scheduler - Update HandleKillAgent to use OrchestratorManager when available, falling back to legacy scheduler interface - Add GetOrchestratorManager to DaemonInterface - Update mock daemon in tests
MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reflect refined understanding of the orchestrator model: - Add 'Off' state (no instance exists) to state machine - Clarify 'Idle' = activated, watching, no work available - Remove 'run completes → idle' transition (orchestrator never exits) - Keep 'runs' terminology (not 'sessions') - Document OrchestratorLifecycle wrapper (RepoOrchestrator renamed) - Update watch mode as default behavior with polling loop - Correct 'What's Already Implemented' section (canopy-o7ww scope) - Update epic reference from canopy-i4sm to canopy-2360 - Add ASCII state transition diagram
continuously The orchestrator now always runs in a continuous polling loop: - Check for work - If work: process tasks (Active state) - If no work: sleep PollInterval (Idle state) - Repeat until context cancelled Changes: - Remove Watch field from RunConfig struct in api.go - Update config.go comment to remove "watch mode" reference - Add KillAgent method to OrchestratorManager (was missing)
manager - Add missing nil argument for paused parameter in UpdateRunConfig call - Add KillAgent method to OrchestratorManager to route kill requests through active orchestrators Note: Activate/Deactivate methods were already implemented on OrchestratorLifecycle. StartRun/StopRun already use them as aliases.
observation Add StateCallbacks struct to orchestrator with OnIdle/OnActive functions. The Orchestrator calls OnIdle() when no work is available (entering poll-wait) and OnActive() when starting to process tasks. OrchestratorLifecycle (via OrchestratorManager.createStateCallbacks) registers callbacks to update its State field accordingly: - OnActive: State = OrchestratorActive - OnIdle: State = OrchestratorIdle Also fix build error in orchestration_handler.go where UpdateRunConfig was called with an extra nil argument.
The token counter in the dashboard header now shows a clickable dropdown that displays a detailed breakdown of all token types: - Input tokens (blue) - Output tokens (green) - Cache read tokens (purple, shown when > 0) - Cache write tokens (orange, shown when > 0) - Total tokens (bold) This allows users to see exactly how tokens are being used across all agents in the run, including cache efficiency metrics. Also adds the missing KillAgent method to OrchestratorManager to fix the build.
…xhd) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the old RunControlBar with a unified OrchestratorStateIndicator that displays the current orchestrator state (Off/Idle/Active/Paused) and provides appropriate controls. Layout: [Repo Selector] | ● Active (N agents) [Pause] [Deactivate] | [Run History ▾] Changes: - Add StateCallbacks to orchestrator for Idle/Active transitions - Add OrchestratorStateIndicator component with state-dependent buttons - Integrate indicator into DashboardHeader between repo and run selectors - Add orch:state_changed event handling in WebSocket hook - Add orchestratorState and activeAgentCount to state store - Remove unused RunControlBar component
handleAgentMergeStatus
The handleAgentMergeStatus function was only extracting 3 fields from
the event payload (merge_status, queue_pos, error), but the IPC server
sends many more fields including validation and repair tracking data.
This caused the dashboard's worker agent Details drawer to not appear
because the hasWorkerChainData check depends on validation_status and
repair_attempts fields that were never populated.
Now extracting all fields sent by the IPC server:
- commits_applied
- had_conflict
- resolver_spawned
- validation_status
- validation_error
- validation_duration_ms
- validation_steps
- repair_attempts
- last_repair_output
Added getInt64FromMap helper function to handle numeric extraction from
nested map[string]interface{} (needed for validation_steps parsing).
…fmu) When max_retries > 0 and an agent fails, the orchestrator retries the task but the UI had no indication this was a retry. This change adds: - AgentStartPayload now includes attempt and max_retries fields - Orchestrator exposes GetTaskAttempt() and GetMaxRetries() methods - IPC client has SendAgentStartWithOptions() for passing retry info - Dashboard shows "Attempt N of M" badge for retry attempts - Uses orange badge styling consistent with warning indicators The retry badge only appears when attempt > 1 (i.e., actual retries). For infinite retries (-1), shows "Retry N" instead of "Attempt N of M".
When merge completes, the agent could appear 'running' in the UI and canopy ps even though the task is complete with merged commits. This was due to a race condition where: 1. Merge processor marks task 'done' and sends TaskUpdated/MergeStatus 2. EventAgentCompleted comes later from a separate callback path 3. RuntimeState processes these independently During the window between merge completion and EventAgentCompleted, the task shows 'completed' but agent shows 'running'. Fix by updating AgentStatus to 'completed' or 'failed' atomically when handleAgentMergeStatus receives a terminal merge status (merged, merged_needs_repair, resolved, skipped, or failed). Also update task status and recalculate stats in the same handler. Added missing MergeStatus constant aliases (MergeStatusResolved, MergeStatusSkipped, MergeStatusMergedNeedsRepair) to the daemon package.
When validation fails and a repair agent is spawned, the parent agent's status in `canopy ps` would remain "running" with no indication that repair was in progress. Users would see their work product merged but the agent appearing stuck. Changes: - Add ValidationStatus and RepairAttempts fields to WorkerInfo and AgentInfo structs to capture repair state from daemon - Update status display logic to show "repairing (N)" when validation status is "repairing", where N is the repair attempt number - Prioritize validation status over merge status in display Now when repair is in progress, `canopy ps` shows: STATUS: repairing (1) instead of: STATUS: running This provides clear visibility that the agent is actively being repaired rather than appearing stuck.
Defines a unified state machine to replace the current multi-field agent status model (Status, MergeStatus, ValidationStatus). This addresses race conditions where agents appear "running" after merge completes, and lack of UI feedback during validation/repair. Key design elements: - Single AgentLifecycleState enum with 13 states - Explicit transition table with validation - Backwards-compatible legacy field derivation - 4-phase migration strategy with parallel tracking Architect review complete. Resolves design for canopy-jygc, canopy-vf1u.
…vfap) The Activate button was appearing twice because: 1. Button visibility was based solely on orchestratorState === 'off' 2. During activation, isActivating was true but orchestratorState remained 'off' until the WebSocket state update arrived Fix by: - Hide Activate button when isActivating is true - Hide Pause/Resume/Deactivate buttons when isDeactivating is true - Show transitioning visual feedback (yellow icon, "Activating..." text) during state transitions
Add orchestrator state (off, idle, active, paused) to the canopy ps output. When the orchestrator is active with running agents, the display shows the count: "Orchestrator: active (4 agents)". This helps operators understand the current orchestration state from CLI without needing the dashboard.
Implement pkg/lifecycle package with a unified state machine that replaces the multi-field status model (Status, MergeStatus, ValidationStatus). Key features: - 13 states covering the full agent lifecycle from starting to terminal - 17 events that trigger state transitions - Atomic transitions protected by mutex - Bounded state history (100 entries) for debugging - TransitionCallback for EventBus integration - Legacy compatibility functions for backwards compatibility This is Phase 1 of the agent lifecycle state machine migration. See docs/design/agent-lifecycle-state-machine.md for full design.
OrchestratorLifecycle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements the always-active orchestrator model as specified in docs/design/active-orchestrator-model.md: - Rename RepoOrchestrator → OrchestratorLifecycle with improved docs - Add Activate/Deactivate methods as preferred API for lifecycle control - Add DeactivateByRepo to stop orchestrator by repo path - Add new API endpoints: POST /api/orchestrator/activate, POST /api/orchestrator/deactivate - Update dashboard API client with activate/deactivate functions - Keep legacy StartRun/StopRun endpoints for backwards compatibility State machine: Off → Idle ↔ Active, with Paused as temporary state - Off: Not activated, no orchestrator instance - Idle: Activated, watching for work, none available - Active: Processing tasks - Paused: Temporarily stopped by user/agent The orchestrator polls continuously when activated - it never exits on its own, only through explicit Deactivate() call.
…p9uc) events Remove agent status setting from handleAgentMergeStatus to prevent race conditions when agent:merge_status and agent:completed events arrive in close succession. Only EventAgentCompleted should set terminal status (completed/failed) to maintain a single source of truth for agent completion state. The merge status event continues to update merge-specific fields (merge_status, queue_pos, validation fields, etc.) and task status.
Users identify repositories by path, not internal UUID. Look up the repository path from the ID returned by the daemon API using repository.FromID(), with fallback to ID if lookup fails.
'running' Validation status was jumping directly to 'running' without first sending 'pending', causing the frontend state machine to miss the intermediate transition. This matches the pattern used for merge status (MergeStatusPending) at line 196. The fix sends validation_status: "pending" immediately after confirming validation is enabled, before entering the validation loop where "running" status is sent.
…gro) tests Add exhaustive tests for all state machine transitions: - All 40+ valid transitions with context variations - Invalid transition rejection from all states - Property tests: terminal states never transition out, history integrity - Concurrency tests: parallel transitions, concurrent reads/writes - Race detector passes with all tests Coverage: 100% of nextState() logic
(canopy-03bs) Update `canopy ps` to display the agent lifecycle state from the state machine instead of deriving status from multiple legacy fields. Changes: - Add LifecycleState field to AgentState struct and GetSnapshot - Add LifecycleState field to AgentInfo and WorkerInfo in ps command - Update filter logic to use lifecycle state for determining active agents - Update display logic to prefer lifecycle state over derived status - Maintain backwards compatibility by falling back to legacy status fields when lifecycle_state is not set
Add merge queue visibility to the `canopy ps` command. Operators can now
see which agents are waiting to merge and their order in the queue.
Output format:
- Shows "Merge Queue: empty" when no agents are queued
- Shows ordered list with position, agent ID, task ID, and status (merging/waiting)
Example:
Merge Queue:
Position 1: agent-abc123 (beads-xyz) - merging
Position 2: agent-def456 (beads-uvw) - waiting
Changes:
- Add MergeQueueSnapshot and MergeQueueEntry types to daemon handlers
- Include merge queue info in /api/state response via buildMergeQueueSnapshot()
- Update ps command to consume and display merge queue from state API
- Add merge_queue to ProcessInfo for JSON output support
- Add tests for buildMergeQueueSnapshot and state response inclusion
AgentState Add AgentLifecycle field to AgentState to run the new state machine in parallel with legacy Status/MergeStatus fields during Phase 1 rollout. Changes: - Move AgentStatus to pkg/types to break import cycle between daemon and lifecycle packages - Add Lifecycle field to AgentState (not serialized to JSON) - Initialize lifecycle in handleAgentStarted() and transition to running - Call lifecycle.Transition() alongside legacy field updates in handleAgentMergeStatus() and handleAgentCompleted() - Add checkLifecycleDivergence() to log warnings when states diverge - Add canopy_lifecycle_divergence_total Prometheus metric for monitoring This enables detecting transition logic bugs before Phase 2 migration.
lifecycle (canopy-4qsj) When an agent starts, the state now goes through 'starting' before 'running'. This allows the UI to show the correct state if an agent crashes during initialization before actually beginning execution. Changes: - agent:started event now sets status to 'starting' (not 'running') - New agent:running event transitions from 'starting' to 'running' - agent:running is emitted on first live feed event from the agent - Frontend handles both states with proper transitions - Persistence layer tracks the intermediate state Also fixes a build error in orchestration_handler.go where Deactivate/ DeactivateByRepo methods were removed but still referenced.
…-y18w) flow Add granular validation status states during repair agent lifecycle: - pending_repair: Deciding to spawn repair agent (after validation failure) - spawning_repair: Creating repair agent and context - repairing: Repair agent actively executing This replaces the immediate jump from 'failed' to 'repairing' with intermediate states that provide better UI visibility and allow tracking of slow repair agent spawning. Updated files: - pkg/mergequeue/processor.go: Send pending_repair and spawning_repair - pkg/repairagent/spawner.go: Send repairing when execution begins - pkg/ipc/protocol.go: Document new status values - web/dashboard: Add ValidationStatusBadge config for new states
Adds a 150ms debounce delay before transitioning from active to idle state. This prevents UI flickering when a task completes and new work becomes available almost simultaneously (e.g., when a dependency is resolved). Active transitions remain immediate so the UI reflects new work quickly. Only the transition to idle is debounced, allowing the task polling to discover newly-ready work before emitting the idle state. The debounce timer is cancelled if new work arrives before it fires.
completed tasks The commit counter on agent cards was showing inconsistently because `commits_applied` from the `agent:merge_status` event was not being passed through to the state store. Changes: - Add `commits_applied` field to AgentMergeStatusEvent interface - Pass `commits_applied` through updateAgentMergeStatus function - Update commit counter to prefer `merge_commits_applied` for merged tasks (most accurate), falling back to `agent.commits` for pre-merge
The agent detail pane previously only showed the commit count, not the actual commits. Users had to switch to the Commits tab in the terminal panel to see commit details. Added a collapsible Commits section using the existing CommitList component. The section shows a badge with the commit count and expands to show full commit details (hash, message, author, files changed).
…xqqu) Connect existing retry infrastructure to event callbacks: - Add retry logging in completionCallback when merge fails but will retry - Add Attempt and MaxRetries fields to AgentState struct - Pass orchestrator to createEventCallbacks to access attempt info - Include attempt/max_retries in OnAgentStartFn event payload - Extract attempt/max_retries in handleAgentStarted state handler The IPC protocol already had these fields defined - this change populates them so retry information flows through to the state and dashboard.
When restoring agents with non-terminal lifecycles during Init(), lifecycle callbacks are registered that reference r.eventBus. Previously, SubscribeToEventBus (which sets r.eventBus) was called in Start() after Init() returned, so any lifecycle callback that fired during restoration would silently drop the event due to the nil check at makeLifecycleCallback. This fix moves the SubscribeToEventBus call into Init(), right after the eventBus and state are created but before state restoration occurs. This ensures r.eventBus is set when lifecycle callbacks are registered, so agent transitions publish events properly to the UI.
…gnz) [1] fix(daemon): add agent to state before lifecycle transition The lifecycle transition publishes events via callback. If we transition before AddAgent(), clients receiving the event may request a state snapshot that doesn't include the agent yet, causing stale 'Starting' state in UI. Reorder operations so AddAgent happens before lifecycle transitions to ensure atomicity of the add+transition sequence. [2] fix(orchestrator): complete truncated mergeRulesSettings function The function was truncated at line 779, missing the final clause that applies CLI override flags. Restore the complete function implementation.
…e3j3) Add staleness detection before applying patches directly. When HEAD has moved ahead of the overlay's base commit, skip direct patch application and spawn a resolver for proper 3-way merge. This prevents long-running agents with stale overlays from silently reverting recent commits when their patches are merged.
The beadsClient.Fail() method called `bd reopen`, which put failed tasks back in the ready queue even after max retries was exhausted. This created infinite retry loops. Changes: - Add FailPermanently() method to BeadsClient interface and Client - FailPermanently() closes the task and adds needs-investigation label - Update orchestrator to call FailPermanently() when retries exhausted - Keep existing Fail() for temporary failures (retries remaining) - Add mock support and test coverage for FailPermanently() The needs-investigation label ensures the task won't be picked up by future canopy runs until manually reviewed.
The scheduler stores contexts by task.ID (e.g., "canopy-xyz"), but KillAgent() was passing the full agentID (e.g., "agent-abc12345-canopy-xyz"). This caused 500 errors because the scheduler couldn't find the context. Changes: - Extract taskID from agentID parts in orchestrator_manager.go - Improve error handling in handlers.go to return 404 for "not found" errors Test plan: go test ./... and go build ./... pass
Refactor `canopy run` to delegate orchestrator lifecycle to the daemon instead of running a blocking local orchestrator. The CLI now: 1. POSTs to daemon's /api/orchestrator/run endpoint 2. Polls for run status and streams output 3. Handles Ctrl+C by calling stop endpoint Also adds rules override support to daemon API so CLI flags like --type, --exclude-type, --label, --exclude-label, and --assignee are properly passed through to the orchestrator.
Daemon shutdown now properly cleans up active overlays by calling scheduler.CleanupAll() on all running orchestrators. This prevents orphaned FUSE processes and overlay directories. Changes: - OrchestratorManager.CleanupAllOverlays() iterates through all active runs, calls scheduler.CleanupAll() with 30s timeout - Daemon.Stop() calls CleanupAllOverlays() before stopping other components - Added time import to daemon.go The cleanup happens before IPC/HTTP shutdown to ensure graceful cleanup while the orchestrators are still active.
When UnregisterRepo is called, the orchestrator context was cancelled but the function returned immediately without waiting for active overlays to be cleaned up. This could leave orphaned FUSE mounts. Changes: - Add Shutdown() method to Orchestrator that calls scheduler.CleanupAll() - Update UnregisterRepo to call Shutdown() before returning - Move cleanup logic outside the lock to avoid blocking other operations - Log overlay cleanup results for debugging The 30-second timeout prevents hanging on stuck mounts while giving adequate time for normal cleanup to complete.
…f7af-canopy-nsdl-resolver) settings Add SQLite table and CRUD methods for persisting per-repository orchestrator configuration settings: - Add RunConfig struct with concurrency, max_priority, use_bwrap, max_retries fields - Add migration V16 creating run_configs table with appropriate defaults - Implement GetRunConfig() returning defaults if no record exists - Implement SaveRunConfig() using upsert pattern - Add DefaultRunConfig() helper for default values Resolves merge conflict for canopy-nsdl by applying patch to current HEAD.
…f-canopy-ktmx-resolver) persistence Add task-level retry tracking fields to track retry attempts separately from validation repair attempts (RepairAttempts). Changes: - Add Attempt and MaxRetries fields to daemon.AgentState - Add Attempt and MaxRetries fields to persistence.Agent - Add migration V17 for new database columns (V16 was taken by run_configs) - Update CreateAgent to persist new fields - Update agentColumns and scan functions to read new fields - Update ConvertPersistenceAgentToState to restore fields - Update GetSnapshot to copy new fields Resolves: canopy-ktmx
…65cbf7af-canopy-37jl-resolver) lifecycle states When a WebSocket client connects, it receives a state:sync snapshot. However, events may have been published after the snapshot was taken but before the client processed it. This could cause the UI to show stale states by processing events that were already reflected in the snapshot. Add monotonic sequence numbers to events: - EventBus assigns increasing sequence numbers to each published event - RuntimeStateSnapshot includes the current sequence at snapshot time - Dashboard frontend discards events with sequence <= snapshot sequence This ensures that events occurring before the snapshot are ignored, preventing out-of-order state updates that could cause UI glitches.
…opy-wyy1-resolver) Add GET /api/config and PUT /api/config endpoints for loading and saving run configuration per repository. - GET returns saved config or defaults if none saved - PUT persists to SQLite, reloads into daemon memory, broadcasts config_updated event via WebSocket so dashboard updates Implementation: - Add EventConfigUpdated event type in pkg/events/events.go - Add HandleGetConfig and HandlePutConfig handlers in orchestration_handler.go - Register /api/config route in server.go - Use persistence.Store.GetRunConfig/SaveRunConfig for storage
…bf7af-canopy-p4te-resolver) Add onTerminal callback mechanism to lifecycle state machine that fires when any terminal state is reached (completed, failed, needs_attention, cancelled, timed_out). This provides a safety net for overlay cleanup: - TerminalCallback type and WithTerminalCallback option added to lifecycle - Callback fires from both Transition() and SetState() methods - Scheduler.CleanupOverlay() method for explicit cleanup by task ID - Orchestrator.CleanupOverlayByTaskID() exposes cleanup to callers The terminal callback ensures cleanup happens regardless of which path led to the terminal state (normal completion, context cancellation, timeout, or forced state recovery). This is defense-in-depth: the existing merge coordinator callback chain handles most cases, but the terminal callback provides a backup mechanism for edge cases where the callback chain might be bypassed.
…cbf7af-canopy-ye8z-resolver) Add Retry Information section to AgentDetail that shows: - Current attempt number and max retries - Previous failure reason (merge_error or validation_error) - Last repair output if available Only displayed when attempt > 1 (i.e., the agent is a retry).
…f-canopy-uzmw-resolver) persistence Add a Configure button adjacent to Activate that opens the run config dialog in "configure" mode, allowing users to save run settings without starting a run. Settings are persisted via PUT /api/config and synced across browser tabs via WebSocket config_updated events. Changes: - api/client.ts: Add getRunConfig/saveRunConfig API functions - OrchestratorStateIndicator: Add Configure button (outline style) - DashboardHeader: Pass onConfigure prop - Dashboard: Add config dialog mode state and handleSaveConfig handler - RunConfigDialog: Support dual-mode (start vs configure) - useWebSocket: Handle config:updated events - stateStore: Add loadSavedConfig and updateRunConfigFromServer actions Resolves canopy-uzmw
…cbf7af-canopy-ulxm-resolver) Wire up attempt/max_retries fields through the full event pipeline: - state.go: Extract attempt/max_retries from AgentStarted event payload and populate AgentState fields - persistence_handler.go: Extract and persist retry info when agent starts - useWebSocket.ts: Add attempt/max_retries to BackendAgentState interface and transform to frontend AgentState in state:sync handler The UI components (AgentCard, AgentDetail) were already implemented but had no data flowing through. Now when an agent restarts after a merge failure, the retry badge is displayed with attempt number and max retries. Merge conflict resolution: Preserved concurrent config:updated event handling that was added to the codebase between BASE and HEAD commits.
…af-canopy-ea8q-resolver) callback When the orchestrator runs inside the daemon process, the merge processor's ipcClient was never set. This caused all merge status events to be silently dropped, leaving agents stuck in queued_for_merge state with null merge_status. Add MergeStatusCallback to mergequeue.Processor as an alternative to IPC for daemon-mode operation. The callback is invoked whenever merge status would be sent via IPC, and the daemon wires this to publish events directly to EventBus. Changes: - Add MergeStatusEvent struct and MergeStatusCallback type to processor - Update sendMergeStatus/sendMergeStatusFull to use callback when set - Wire callback through MergeCoordinator and Orchestrator - Add createMergeStatusCallback in daemon's orchestrator_manager.go that publishes to EventBus and handles lifecycle transitions
loading When a new orchestrator run starts, a WebSocket run:started event may arrive before the initial getRuns() API call completes. Previously, setRuns() would replace the entire runs array with the API response, causing any recently-added runs to be lost if they weren't yet in the database. Add mergeRuns() function that combines API results with existing state: - Preserves runs added via WebSocket that aren't in the API response - Uses API data as the source of truth for runs that exist in both - Maintains descending sort order by started_at timestamp This fixes the issue where newly started runs wouldn't appear in the 'All runs' dropdown until the page was refreshed.
…py-72x8-resolver) repair/resolver agents Previously, repair and resolver agents created synthetic beads tasks with agent IDs (e.g., agent-65cbf7af-canopy-ea8q-resolver) as the task ID for execution tracking. This agent ID was then incorrectly used in commit messages instead of the original bead ID. The fix adds a BeadID field to agent.Result that repair/resolver agents set to the original bead ID after execution. The merge code now uses BeadID if set, falling back to TaskID for normal agents. This ensures commit messages use the format: type(component): description (canopy-abc) instead of: type(component): description (agent-65cbf7af-canopy-abc-resolver)
…3772cec2-canopy-k6zq-resolver) resolvers (canopy-k6zq) - Display 'Resolving' status on AgentCard when an active resolver is working on the task, replacing the normal lifecycle state - Add Related Agents section to AgentDetail showing parent and child agents with clickable rows to switch between them - Pass onSelectAgent callback through TerminalPanel to AgentDetail allowing navigation to resolver/repair agent logs - The TerminalPanel already rendered independently of grid filtering since it uses the full agents object, not filtered agents This fixes the issue where clicking on a task in the merge queue would not show details if the user was filtering by a different agent status.
…-canopy-qt1v-resolver) dashboard display (canopy-qt1v) In daemon mode, agent:commit events were being lost because sendMergedCommits() only used the IPC client (which is nil in daemon mode). The MergeStatusCallback pattern was added previously for merge status events, but commits were never given the same treatment. This adds a CommitCallback mechanism mirroring MergeStatusCallback: - CommitEvent struct to hold commit data - CommitCallback type for the callback function - SetCommitCallback methods on Processor, MergeCoordinator, Orchestrator - createCommitCallback in OrchestratorManager to publish to EventBus The dashboard now receives agent:commit events which populate the git_commits array, fixing the commits list display in the agent detail panel.
…qp4) run-configured overrides Adds a three-tier rules precedence system for the orchestrator: 1. Override rules (run-configured, highest priority) - set via Configure dialog 2. Config rules (from .canopy/config.toml) 3. Default rules (built-in, lowest priority) Changes: - Add RuleSource type to track rule origin (default/config/override) - Update rules Engine to track source for each rule - Add ApplyOverrides() and ClearOverrides() methods to Engine - Update RunConfig to accept rule_overrides for run-scoped rules - Add RuleOverrideRequest type to orchestration handler API - Add rules editing UI to RunConfigDialog with collapsible section - Update TypeScript interfaces to include source field - Fix test fixtures to include required source field
…kqav) rules The Configure dialog now pre-populates with rules from the config file, allowing users to modify them for a specific run: - Load rules from getRules API when opening the configure dialog - Display config rules in a "From Config" section with toggle switches - Visual indicators distinguish config rules from runtime-only overrides - Modified config rules show a "modified" badge and "Reset" button - Runtime-only rules shown in separate "Runtime Only" section with delete button - Rules can be enabled/disabled per-run without affecting persisted config - Only truly modified rules are sent as overrides when starting the run Files changed: - RunConfigDialog.tsx: Added DialogRule type, config rules display logic - Dashboard.tsx: Added configRulesForDialog state, loadRulesForDialog handler - EditRuleModal.test.tsx: Fixed test to expect source field in saved rule
- Check error return values in test files (errcheck) - Remove unused allStates variable and expectError field (unused) - Use type conversion instead of struct literal in ps.go (gosimple) - Fix unused variable assignments in state_test.go (staticcheck)
- Remove unused STATUS_COLORS in AgentCard.tsx - Remove unused currentRunId in Dashboard.tsx - Remove unused filteredIndex parameter in RunConfigDialog.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
pkg/lifecycle/state.go) to track agent states (Initializing → Running → Merging → MergeComplete → Terminal) with proper transitions and event handlingOrchestratorManager, removing the watch mode in favor of continuous pollingTest plan
pkg/lifecycle/*_test.go)pkg/daemon/handlers_test.go,pkg/daemon/server_test.go)