agent: listener-less (cadence-only) mode (F35)#175
Merged
mbertschler merged 4 commits intoJul 24, 2026
Merged
Conversation
Make the HTTP listener optional. When `[agent] listen` is absent/empty the agent runs its background schedulers (index/sync/audit cadences) WITHOUT binding an HTTP server — the mode for cadence-only machines (a roaming laptop that pushes to a hub and never receives peer syncs). When `listen` is set, behaviour is unchanged: scheduler + HTTP server, token required. - config: `listen` no longer required; a bearer token is required only when a listener is configured (nothing to authenticate otherwise). - agent: validateConfig relaxed accordingly; new Server.RunSchedulers runs the scan + cadence loops with the same shutdown discipline as Serve but no listener. - cmd: runAgent dispatches to RunSchedulers when listen is empty, and fails fast if a listener-less agent has no cadence and no scan (silent idling is degradation, not a valid config); the marker/init boundary is untouched — the agent still never escalates. - docs: reference-setup laptop drops the dummy listen + token; agent guide and configuration reference describe listener-less operation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Skm2Qm6wWUB7dD4CpPTz7
Strike the listener-less friction entry now that the cadence-only agent mode lands, per the friction log's PR-reference convention. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Skm2Qm6wWUB7dD4CpPTz7
There was a problem hiding this comment.
Pull request overview
Enables a listener-less (“cadence-only”) agent mode so machines can run the agent’s scan/scheduler loops without binding an HTTP server, removing the need for a dummy listen address and auth token on machines that never receive peer syncs.
Changes:
- Relaxed agent config validation so
listenis optional andauth.tokenis only required when an HTTP listener is configured. - Added a scheduler-only execution path (
Server.RunSchedulers) and CLI dispatch/fail-fast behavior for listener-less agents that would otherwise be idle. - Updated documentation and reference setup to describe and use listener-less operation.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| docs/src/content/docs/reference/configuration.md | Documents optional listen and listener-less scheduler-only behavior. |
| docs/src/content/docs/guides/agent.md | Adds a dedicated listener-less section with example config and behavior notes. |
| design/reference-setup.md | Updates reference laptop config to omit dummy listen/token. |
| config/config_test.go | Updates config tests to accept listener-less [agent] blocks. |
| config/agent.go | Makes listen optional and relaxes auth requirements for listener-less mode. |
| cmd/squirrel/agent.go | Dispatches to scheduler-only run path when listen is empty; adds “no work” fail-fast and banner. |
| cmd/squirrel/agent_listenerless_test.go | Adds end-to-end CLI tests for listener-less scheduling and fail-fast behavior. |
| agent/serve.go | Adds RunSchedulers to run scan/scheduler loops without an HTTP server. |
| agent/agent.go | Relaxes server config validation: token required only when listen is set. |
| agent/agent_test.go | Adds tests for listener-less server construction and RunSchedulers cancellation behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- config/agent.go: the "token required" error now states the requirement is conditional on a listener — "auth.token is required when [agent] listen is set (... omit listen for a listener-less, scheduler-only agent)" — instead of the unconditional "no agent without authentication", which read as wrong now that listener-less mode exists. - cmd/squirrel/agent.go: the listener-less "nothing to run" fail-fast now lists hook.interval alongside sync_every/index_every/scan_interval, so the remediation matches the real scheduling rules (agentHasWork and the scheduler both count an interval hook as work). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Skm2Qm6wWUB7dD4CpPTz7
F35 made Listen optional and Token conditional on Listen, but three doc comments on the public Config/Agent structs still declared them unconditionally required, contradicting the relaxed validateConfig / resolveAgentAuth. Correct the comments so the documented contract matches the validated one. Doc-only; no behaviour change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Skm2Qm6wWUB7dD4CpPTz7
mbertschler
merged commit Jul 24, 2026
efce410
into
claude/issue-162-bootstrap-helpers
3 checks passed
mbertschler
pushed a commit
that referenced
this pull request
Jul 24, 2026
main now carries #171 and the listener-less agent mode (#175), which is where this branch's two new cadences had to be reconciled. Textual conflicts: - store/runs_audit.go, agent/agent.go: both sides appended at the same spot — keep this branch's pull-durability transition and its VerifyRunner/DurabilityPuller types alongside main's reset-destination transition; main rewrote the Config doc comment for listener-less mode, so its version of that comment stands. - docs reference: both sides added to the [agent] section. Kept main's listener-less paragraph and this branch's key tables, and corrected the table to match — `listen` is no longer required, and `auth.token` is required only alongside it. Left as-is the two would have contradicted each other in adjacent paragraphs. Semantic conflict: main's listener-less start gate (cmd's agentHasWork) duplicated the scheduler's volume-cadence logic and predated this branch's verify_every / pull_durability_every, so it would have refused to start a listener-less agent whose only work is one of those cadences — exactly the receive-only htpc in design/reference-setup.md that F33 exists to serve. Replaced the duplicate with an exported agent.ScheduledWorkInConfig, built on the same resolvers the scheduler's own anyScheduledWork gate uses, so the two cannot drift again; the refusal message now names every cadence. Covered by a table test including the receive-only pull-only config. go vet, go test ./..., and gofmt are clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SQUzRmQHqbP2kV9kFrbBHL
5 tasks
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
Completes the last scoped item of #162 — friction F35: "cadence-only machines must run the full agent listener just to get the scheduler." The HTTP listener is now optional. When
[agent] listenis absent/empty the agent runs its background schedulers (index/sync/audit cadences) without binding an HTTP server; whenlistenis set, behaviour is exactly as before (scheduler + listener, token required). In the reference setup this lets the roaming laptop drop its dummylisten = 127.0.0.1:8443(and the now-pointless auth token) — the wart F35 named.Changes
config/agent.go) —listenis no longer required; a bearer token is required only when a listener is configured (a listener-less agent has no HTTP surface to authenticate). A bare[agent]block is valid.agent/agent.go,agent/serve.go) —validateConfigrelaxed to match; newServer.RunSchedulers(ctx)runs the scan + cadence loops with the same start-gating and shutdown discipline asServe, minus the HTTP server.cmd/squirrel/agent.go) —runAgentdispatches toRunSchedulerswhenlistenis empty, and fails fast if a listener-less agent has no cadence and noscan_interval(a silently-idle agent is degradation, not a valid config). The marker/init boundary is untouched — the agent still never escalates (never--init).reference-setup.mdlaptop config drops the dummylisten/token; the agent guide and configuration reference document listener-less operation.Design decision to verify
A listener-less agent with no cadences and no
scan_intervalis refused at startup (listener-less agent has nothing to run: …) rather than allowed to idle. Rationale: silent-idle is exactly the kind of quiet degradation ux-principle 4 warns against. A listener-ful agent with no cadences stays valid (a receive-only node like the htpc legitimately just serves). Please confirm the fail-fast is the behaviour you want.Testing
go vet ./...,go test ./...,golangci-lint run(v2.12.2 / go1.26.1),gofmt -l— all clean.agent.Newlistener-less accept + rejection-table update;Server.RunSchedulersreturns cleanly on cancel; CLI end-to-end (scheduler-only banner, no listener bound, clean shutdown) and the no-work fail-fast.Scope
Closes #162— this completes the deferred F35 item, the last box on the issue.Stacked on #162's bootstrap PR #171; base is
claude/issue-162-bootstrap-helpersand retargets tomainonce #171 merges. Expect a merge reconciliation with the in-flight agent-startup changes on #154 (scheduler start gate) and #157 (orphan-run reaping) — implemented cleanly against #162 only.🤖 Generated with Claude Code
https://claude.ai/code/session_014Skm2Qm6wWUB7dD4CpPTz7
Generated by Claude Code