Repository navigation
feat(smithy,smithy-web): allow disabling agents (#58) - #60
Conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Expose updateAgentMetadata on OrchestratorAPI interface (was private) so CLI handlers can flip metadata.agent.disabled without bypassing the API layer. Add agentDisableCommand and agentEnableCommand mirroring the agentStopCommand pattern, register them in agentCommand subcommands, and update the help string. Add structure and validation tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds two tests that verify the actual metadata mutation: - disable round-trip: updateAgentMetadata sets disabled to true - enable round-trip: updateAgentMetadata removes disabled key from serialised JSON Uses an in-memory SQLite-backed OrchestratorAPI (same harness pattern as agent-registry.bun.test.ts) rather than invoking the handler literally, because createOrchestratorClient() always calls findStoneforgeDir() before honouring options.db - there is no .stoneforge dir in the test environment. The handler bodies are thin wrappers around api.updateAgentMetadata, so the coverage is equivalent. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add isAgentDisabled guard to agentStartHandler so a disabled agent returns a VALIDATION failure pointing the user at sf agent enable. Covers the new path with a chdir-based behavioural test that exercises the real handler via a tmpdir .stoneforge setup. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Append '(disabled)' to the status column in the table formatter when an agent's metadata has disabled: true. JSON and quiet output paths are unchanged. Adds a behavioural test that asserts the annotation is present for the disabled row and absent for the enabled row. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add isAgentDisabled guard inside registerSteward so disabled stewards return false and register no cron or event triggers. registerAllStewards gets the filter for free since it calls registerSteward per agent. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- packages/smithy: PATCH /api/agents/:id accepts disabled?: boolean. Toggling on a steward also (un)registers it with the running scheduler so behavior changes immediately without an sf serve restart. - smithy-web: useToggleAgentDisabled hook (mirrors useChangeAgentTriggers). - AgentCard: Disable/Enable menu item, dimmed card style + 'Disabled' badge when set, Start button hidden when disabled (the badge plus the menu item surface the state and the action). Disabled propagates through BaseAgentMetadata.disabled in the web types.
The CLI guard added in the disable feature only catches 'sf agent start'. The web UI's Start Session button (in WorkspacePane, DirectorPanel, DirectorTabContent) calls /api/agents/:id/start directly, bypassing the CLI guard. Same for /api/agents/:id/resume. Add the disabled check at both endpoints so every UI surface (current and future) inherits the refusal.
Mirrors the AgentCard guard. The server already refuses these requests (see prior commit), but proactively dimming the buttons makes the state discoverable instead of relying on a server error toast after click. Touches: WorkspacePane (worker pane, two start buttons), DirectorTabContent (idle overlay start + resume buttons), DirectorPanel (active director top bar, start + restart buttons). Each reads the disabled flag from agent metadata and adds it to the existing disabled prop plus a tooltip explaining the state.
|
Looks good overall — comprehensive filter coverage, sensible separation from
|
…reconciliation, route tests, docs Three review items from @notadamking on PR stoneforge-ai#60: 1. CLI bypassed the running scheduler. sf agent disable / enable now route through PATCH /api/agents/:id when STONEFORGE_API_URL (default http://localhost:3457) is reachable. The HTTP route already calls registerSteward / unregisterSteward live, so the scheduler reconciles immediately. When the server is unreachable, the CLI falls back to a direct updateAgentMetadata call and prints "Scheduler will pick up the change on next start" instead of the immediate-effect message. The end state is identical either way; only the live reconciliation differs. 2. PATCH /api/agents/:id disabled path was untested. New packages/smithy/src/server/routes/agents.test.ts covers nine cases: accepts disabled true / false, rejects non-boolean with 400 VALIDATION_ERROR, unregisters a steward when disabling and registers when enabling, leaves the scheduler untouched for non-stewards, leaves the scheduler untouched when isRunning() is false, swallows scheduler errors and still returns 200, and returns 404 for an unknown agent. Uses the existing tasks.test.ts harness pattern (vitest, vi.fn() mocks, app.request). 3. Docs not updated. - apps/docs/src/content/docs/reference/cli-orchestration.mdx adds sf agent disable / enable subcommand sections plus a note on the (disabled) marker in sf agent list and how disabled is independent of --status filtering. - apps/docs/src/content/docs/reference/orchestrator-api.mdx adds a "Disabled agents" subsection covering the flag semantics, the four enforcement sites (getAvailableDirector, dispatch daemon, scheduler, start/resume routes), the in-flight session contract, and the canonical read (isAgentDisabled / isAgentEnabled) and write (delete vs undefined) idioms.
|
Pushed 339106d addressing all three. CLI now PATCHes the running orchestrator first (default PATCH route now has tests in Docs updated in |
|
|
Tried running stoneforge against the fork — no docs in the workspace by default. Take you up on the post-merge follow-up. |
Summary
Closes #58. Adds a
disabledflag on agent metadata plus the matching CLI commands and UI controls so an agent can be parked (excluded from dispatch, pool selection, scheduler triggers, and the Start action) without being removed from the agent list. In-flight sessions are NOT terminated when an agent is disabled; only future work is blocked.Backend
disabled?: booleanonBaseAgentMetadata(default omitted, treated as enabled).isAgentDisabled/isAgentEnabledhelpers on the registry.getAvailableDirectorskips disabled directors even when their session isrunning.dispatch-daemonskips disabled agents at all candidate-selection sites inpollWorkerAvailability,pollInboxes,pollWorkflowTasks, andrecoverOrphanedAssignments. Attribution-onlygetAgentMetadatareads (task creator, director identity for branch propagation, fallback chain resolution) are deliberately not filtered.steward-scheduler.registerStewardreturnsfalseand registers no triggers for disabled stewards.registerAllStewardsinherits the filter through the existing call chain.PATCH /api/agents/:idacceptsdisabled?: boolean. Toggling on a steward triggers an immediate scheduler register/unregister so behavior changes without an orchestrator restart.CLI
sf agent disable <id>andsf agent enable <id>flip the flag throughupdateAgentMetadata. Enable deletes the property so the absent-means-enabled contract holds in the JSON-serialised metadata.sf agent start <id>refuses withAgent <id> is disabled. Run 'sf agent enable <id>' first to bring it back online.and exits withExitCode.VALIDATION.sf agent listappends(disabled)to the status column for affected agents (table output only; JSON serialisation is unchanged because the field passes through with the metadata).Web UI (smithy-web)
useToggleAgentDisabledhook (mirrorsuseChangeAgentTriggers).AgentCard: card dims to 60% opacity and shows a yellowDisabledpill in the header when set; menu item toggles betweenDisable agentandEnable agent(withPower/PowerOfficons); Start button is hidden while disabled (the badge plus the menu item surface the state and the action).BaseAgentMetadata.disabledpropagated through the web types.Out of scope
sf agent disable --all --role steward).--reasonannotation persisted asdisabledReasonon metadata.Test plan
cd packages/smithy && bun test src— 1559 pass, 19 skip, 0 fail.NODE_ENV=development npx turbo run typecheck— 16/16 packages.NODE_ENV=development pnpm build— 13/13 packages.sf agent disable <id>thensf agent listshows(disabled);sf agent startrefuses with the hinted command;sf agent enableclears the marker andsf agent startworks again.Disabledpill, dimmed card, and hidden Start button appear and disappear correctly; toggling a steward live updates the scheduler withoutsf serve smithyrestart.api.updateAgentMetadata, the registry filter ongetAvailableDirector, the dispatch-daemon worker filter, the start-handler refusal, the list marker, and the steward-scheduler skip path. All colocated as*.bun.test.ts.Notes for reviewers
OrchestratorAPI.updateAgentMetadata(private to public on the impl class plus added to the interface) is required because the new CLI handlers and PATCH route call it externally. The method body is unchanged.disabledfield is intentionally separate fromsessionStatus:sessionStatusmodels in-flight session lifecycle, whiledisabledis a property of the agent itself. A disabled agent can still havesessionStatus: 'running'from a session that started before it was disabled; that session is allowed to finish.