feat(aiagents): poll backend for hook enable/disable state#74
Merged
ashishkurmi merged 2 commits intoMay 16, 2026
Merged
Conversation
e4bee2d to
8013f56
Compare
16fb9fa to
a9e3bb1
Compare
Adds a new internal/aiagents/state package and wires it into the scheduled telemetry tick so a UI toggle on the agent-api side converges to local install/uninstall on the next run. - state package owns the cache file ~/.stepsecurity/hooks-state.json, the HTTP fetcher against /developer-mdm-agent/features, and the Reconciler that ties fetch → cache write → idempotent install or uninstall together. - _hook hot path reads the cache before any work and short-circuits to the allow response when disabled. Missing or unparseable cache reads as enabled, so first-run after install keeps working. - main.go runs the reconciler after telemetry.Run in send-telemetry and install paths; community mode (no enterprise config) is a silent no-op. No agent-api changes needed: the existing feature key ai_agents_hooks_install and the GET /developer-mdm-agent/features endpoint already serve the resolved state.
a9e3bb1 to
1ed02f2
Compare
ashishkurmi
approved these changes
May 16, 2026
ashishkurmi
approved these changes
May 16, 2026
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
Adds server-driven enable/disable of AI agent hooks. A toggle in the
Dev Machine Guard console writes desired state to agent-api; this
branch makes DMG observe and act on that state during its existing
scheduled telemetry tick — no daemon, no websocket, no new
configuration. Two transports converge through a single on-disk
cache: today only the scheduled poll writes it, but the design
leaves room for a WS client to write the same file later without
touching the hot path.
What this PR adds
A new
internal/aiagents/statepackage and the wire-up to call it.Plus:
Hot-path cache check in
internal/aiagents/hook/runtime.go—11 new lines that read
hooks-state.jsonand short-circuitRunto an allow response when
enabled=false. Missing or unparseablecache reads as
Default()(enabled) so first-run after installkeeps working. New test
TestRunHonorsDisabledStateCachecoversthe disabled path and asserts no upload / no enrichment / no
error-log entries.
runHookStateReconcileincmd/.../main.go— invoked aftertelemetry.Runin bothsend-telemetryandinstallpaths.Silent no-op in community mode (
ingest.Snapshotreturns false).Failures are logged via
cli.AppendErrorbut never crash main.Architecture
The cache file is the single source of truth for the hot path. Both
the polling reconciler (this PR) and any future WebSocket transport
are expected to converge on the same file, so the hot path never
has to know which transport is active.
Design decisions
hooks uninstallwhile backend says enabledenabled: bool) for v1hooks.per_agent: {claude-code, codex}).enabled: falseBackend dependency
The endpoint we call already exists in agent-api
int:Feature key constant in both sides:
ai_agents_hooks_install. UIPUT path (
PUT /v1/:customer/developer-mdm/features) is also live;console PR adds the toggle UI on top of it.
Failure modes covered by tests
cache_test.goTestReadMissingFileReturnsDefaultcache_test.goTestReadMalformedReturnsDefaultruntime_test.goTestRunHonorsDisabledStateCachefetcher_test.goreconciler_test.goTestReconcileFetchErrorPreservesCachereconciler_test.goTestReconcileInstallFailureSurfacesErrorfetcher_test.goTestFetcherMissingKeyMeansDisabledManual verification
End-to-end tested on a Fedora 42 EC2 VM against the
intenvironment:stepsecurity-dev-machine-guard installtriggered initial telemetry + reconcile.stepsecurity-dev-machine-guard.timerfired on the configured interval and reconciled.enabled: false, reconciler calledRunUninstall, hook entries removed from~/.claude/settings.jsonand~/.codex/hooks.jsonwith.dmg-<stamp>.baksiblings.enabled: true, reconciler calledRunInstall, entries reappeared._hookconfirmed the hot-path short-circuit emits the allow response without uploading.~/.stepsecurity/ai-agent-hook-errors.jsonlacross the test session.Files
Out of scope (explicit)
hooks status/hooks reconcileCLI verbs. Useful for debugging; follow-up.last_reconciled_atper-device on telemetry. Would let the UI render real "synced 8 min ago" badges per device instead of a generic disclaimer. Worth doing; not in this PR.Type of change
Testing
go test ./...green across all 24 packagesgo vet ./...cleangofmt -l .cleanint(toggle UI → DMG converges)