feat(runtime): the cron-management tool disables jobs instead of deleting them (#6159)#6165
Merged
Conversation
…ting them (#6159) The agent-facing cron tools (`cron_cancel`, `schedule_delete`) used to hard-delete a scheduled job via `KernelHandle::cron_cancel`, which discards the schedule / action / delivery configuration entirely. If a bot over-eagerly "cancelled" a configured job, the operator lost what was set up and had no way to recover it. These tools now pause the job via a new `KernelHandle::cron_set_enabled(job_id, false)` rather than deleting it, so the job still exists with `enabled=false` and its configuration is preserved. Hard deletion stays a human-only dashboard operation (`DELETE /api/cron/jobs/{id}`). Pause is reversible from the agent loop: `cron_enable` (low-level) and `schedule_resume` (natural-language) re-enable a paused job. The ownership guard that `cron_cancel` / `schedule_delete` already enforced is preserved and shared with the new tools, so an agent can only pause/resume jobs it owns. The new `cron_set_enabled` kernel-handle method calls the existing `CronScheduler::set_enabled` (which clears error counters and recomputes `next_run` on re-enable) and persists, mirroring the HTTP `toggle_cron_job` route (#3515).
Deploying librefang-docs with
|
| Latest commit: |
5214367
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://7c32707c.librefang-docs.pages.dev |
| Branch Preview URL: | https://feat-cron-disable-not-delete.librefang-docs.pages.dev |
All new doc-comments and MDX paragraphs added by this PR had sentences wrapped at arbitrary column widths, violating the repo's prose-wrapping rule (CLAUDE.md: one sentence = one line, break only at sentence boundaries). Rewrap to sentence-per-line in the six touched files.
houko
enabled auto-merge (squash)
June 17, 2026 12:26
houko
marked this pull request as draft
June 17, 2026 12:32
auto-merge was automatically disabled
June 17, 2026 12:32
Pull request was converted to draft
houko
marked this pull request as ready for review
June 17, 2026 12:37
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.
Design decision
The agent (bot) must not be able to silently throw away a configured scheduled job.
Before this change, the agent-facing cron tools
cron_cancelandschedule_deleteboth calledKernelHandle::cron_cancel, which hard-deletes the job from the scheduler and discards its schedule / action / delivery configuration.A bot that "cancelled" a job left the operator with no record of what was set up and no way to recover it.
I repointed the two destructive tools to pause (disable) the job instead of deleting it, and added matching resume (enable) tools so the pause is reversible from the agent loop.
I chose to repoint rather than rename the existing tools: the names
cron_cancel/schedule_deleteare already in agent allow-lists, thetool_compatnative-tool whitelist, the subagent deny-list, and persisted manifests, so renaming would be a wide backward-incompatible churn for no behavioural gain.The tool descriptions now state honestly that they pause (not delete) and that permanent deletion is a human-only dashboard operation.
Hard deletion stays human-only: the dashboard
DELETE /api/cron/jobs/{id}route is unchanged.The agent has no tool that reaches
KernelHandle::cron_cancelanymore.What changed
New kernel-handle capability:
crates/librefang-kernel-handle/src/cron_control.rs— addedCronControl::cron_set_enabled(job_id, enabled)(default impl returnsUnavailable, matching the other methods).crates/librefang-kernel/src/kernel/handles/cron_control.rs— implemented it against the existingCronScheduler::set_enabled(which clears error counters and recomputesnext_runon re-enable) followed bypersist(), mirroring the HTTPtoggle_cron_jobroute's persist-after-toggle (PUT /api/cron/jobs/{id} swallows persist error while sibling DELETE/toggle log it #3515).Agent tools repointed and added:
crates/librefang-runtime/src/tool_runner/cron.rs—tool_cron_cancelnow callscron_set_enabled(false); addedtool_cron_enable(callscron_set_enabled(true)).The shared
require_owned_job_idhelper keeps the existing per-agent ownership guard for both.crates/librefang-runtime/src/tool_runner/schedule.rs—tool_schedule_deletenow callscron_set_enabled(false); addedtool_schedule_resume.Shared
require_owned_schedule_idhelper preserves the ownership guard (arch: inconsistent error contracts — String / anyhow / LibreFangError / LlmError mixed #3576 second slice) for both.crates/librefang-runtime/src/tool_runner/dispatch.rs— routedcron_enableandschedule_resume.crates/librefang-runtime/src/tool_runner/mod.rs— imports for the new tool fns.crates/librefang-runtime/src/tool_runner/definitions.rs— addedcron_enable/schedule_resumeToolDefinitions; rewrote thecron_cancel/schedule_deletedescriptions to say "Pause (disable) … config preserved … only a human can hard-delete via the dashboard".Definitions are appended in source order, so deterministic prompt ordering (Enforce deterministic ordering for LLM-bound registries to stabilize prompt cache #3298) is unaffected.
Supporting surfaces:
crates/librefang-runtime/src/tool_policy.rs—cron_enable/schedule_resumeadded toSUBAGENT_DENY_ALWAYS(consistent with the existing scheduling tools being top-level-only).crates/librefang-types/src/tool_compat.rs—schedule_resumeadded to the native-tool whitelist (theschedule_*family is listed there; the low-levelcron_*family is not, socron_enableis not added — matchingcron_cancel).docs/src/app/agent/tools/page.mdx,docs/src/app/zh/agent/tools/page.mdx,docs/src/app/architecture/page.mdx,docs/src/app/zh/architecture/page.mdx— documented the pause/resume semantics and updated the tool tables/counts.Verification
Run in the repo's sanctioned Docker dev image (
Dockerfile.rust-dev) with a per-worktree target volume, per CLAUDE.md "Verifying without a native toolchain".cargo fmt --all— clean (no diff beyond the intended change).cargo check --workspace --lib— clean.cargo clippy -p librefang-kernel-handle -p librefang-types -p librefang-runtime --all-targets -- -D warnings— clean.cargo clippy -p librefang-kernel --all-targets -- -D warnings— clean.Tests (all green):
cargo test -p librefang-runtime --test tool_runner_forwarding_task_cron— 18 tests, including new behaviour tests:test_cron_cancel_disables_and_does_not_hard_delete— asserts the tool callscron_set_enabled(job, false)and never reachesKernelHandle::cron_cancel; user message says "paused/disabled".test_schedule_delete_disables_and_does_not_hard_delete— same for the natural-language tool.test_cron_enable_resumes_owned_job/test_schedule_resume_resumes_owned_job— assertcron_set_enabled(job, true).test_cron_enable_unowned_job_renders_as_not_found— ownership guard on the new enable tool.cron_set_enabledis never called for an unowned job.cargo test -p librefang-runtime --lib -- tool_runner::cron::tests tool_runner::schedule::tests tool_policy::tests filter_tools_by_depth tool_runner::tests::workspace— 57 tests (newcron_enable_*/schedule_resume_without_kernel_*unit tests;test_builtin_tool_definitionsupdated for the two new tools).cargo test -p librefang-kernel-handle --test defaults_returns— 7 tests (newcron_set_enableddefault-Unavailable assertion intest_cron_defaults_return_errors).cargo test -p librefang-types --lib tool_compat— 3 tests (whitelist update covered).cargo test -p librefang-kernel --lib cron::— 79 tests;cron::tests::test_set_enabledalready proves the scheduler keeps the job (config preserved) after disable and resets on re-enable.The Docker image runs Linux only; this change is platform-independent (no OS-specific paths), so the Linux run is sufficient.
Maintainer notes — delete-vs-disable design choice
I repointed
cron_cancel/schedule_deleteto disable rather than renaming them, to avoid a backward-incompatible churn across agent allow-lists,tool_compat, the subagent deny-list, and persisted manifests.The trade-off is that the tool names still read "cancel" / "delete" while the behaviour is "pause"; I made the tool descriptions explicit about this and added the resume counterparts, so the LLM-visible contract is honest.
If you would prefer fully renamed tools (e.g.
cron_pause/cron_resume) with deprecation aliases instead, I can follow up — but that touches a wider surface than this issue's scope, so I kept it to the behavioural fix.Closes #6159