Skip to content

docs(adr): goal-driven cronjob (disable_on_success)#816

Open
chaodu-agent wants to merge 10 commits into
mainfrom
docs/adr-goal-driven-cron
Open

docs(adr): goal-driven cronjob (disable_on_success)#816
chaodu-agent wants to merge 10 commits into
mainfrom
docs/adr-goal-driven-cron

Conversation

@chaodu-agent
Copy link
Copy Markdown
Collaborator

@chaodu-agent chaodu-agent commented May 13, 2026

Summary

ADR for extending usercron [[jobs]] with disable_on_success — enabling goal-driven "escape room" mode.

Key Decisions

  • Extend usercron, not new system — minimal change, reuses existing scheduler
  • disable_on_success — command evaluates the goal before sending the regular prompt
  • Explicit success match required — command must exit 0 and print disable_on_success_match before OpenAB treats the goal as achieved
  • Usercron-only — scheduler writes enabled = false directly to $HOME/.openab/cronjob.toml, no separate state file
  • Stable thread — auto-create + persist in usercron file
  • Actor is OpenAB scheduler — not the ACP model/agent
  • Success notification mandatory — posts ✅ Goal achieved before disabling

Phase 1 vs Phase 2

  • Phase 1 (this ADR): usercron + explicit success check + auto-disable
  • Phase 2 (future): full goal runner with state delta, stuck detection, escalation, LLM judge

Context

Design discussion: https://discord.com/channels/1491295327620169908/1504239931940409587

cc @pahud

@chaodu-agent chaodu-agent requested a review from thepagent as a code owner May 13, 2026 22:09
@github-actions github-actions Bot added the pending-screening PR awaiting automated screening label May 13, 2026
@chaodu-agent
Copy link
Copy Markdown
Collaborator Author

Reviewed #816. This ADR captures the agreed direction well: Phase 1 is [[cron.jobs]] + disable_on_success, with stable id, generation reset, prefixed command fields, stable thread lifecycle, and Phase 2 deferred.

One small clarification I recommend before/while merging: specify the state file location, not just the filename. For consistency with usercron, I would make it $HOME/.openab/cron-state.json (or explicitly say it follows the same state/config base directory). That avoids implementations accidentally writing cron-state.json relative to the process cwd.

No blocking issues from me. Since #816 supersedes #815, #815 can be closed once this ADR is accepted.

@chaodu-agent
Copy link
Copy Markdown
Collaborator Author

This usercron-only simplification is reasonable, but the ADR now conflicts with the existing usercron contract in docs/cronjob.md and needs another pass before merge.

Blocking inconsistencies:

  1. Wrong usercron path. Existing docs say usercron_path = "cronjob.toml" resolves to $HOME/.openab/cronjob.toml. The ADR now says .openab/usercron/cronjob.toml. Please align with the existing usercron path rules, or explicitly propose changing them.

  2. Wrong usercron table name. Existing usercron files use [[jobs]], not [[cron.jobs]]. The ADR examples and implementation plan now say usercron [[cron.jobs]]. Please change the usercron examples to [[jobs]], or call out a deliberate format change.

  3. PR body is stale. It still says State file — cron-state.json survives restarts, but the ADR now says no separate state file and usercron is the state.

  4. Generation is now redundant/ambiguous. If re-enable is simply editing usercron enabled = true, we may not need generation at all for Phase 1. If you keep generation, define what it does when enabled = false remains in the file. Right now the ADR says either setting enabled = true or bumping generation reactivates, but bumping generation alone cannot reactivate if the scheduler skips disabled jobs before evaluating generation.

  5. Thread writeback actor. The ADR says "agent writes enabled = false" and "agent writes thread_id", but implementation-wise this should be OpenAB scheduler code mutating the usercron file after command success / thread creation, not the ACP agent. Please use "OpenAB" / "scheduler" to avoid implying the model edits config as part of normal completion.

Once these are aligned, the usercron-only approach can be clean.

@chaodu-agent
Copy link
Copy Markdown
Collaborator Author

Latest #816 is closer: path is now $HOME/.openab/cronjob.toml, usercron uses [[jobs]], actor is OpenAB scheduler, and PR body no longer mentions cron-state.json.

Still not LGTM because the ADR now has internal contradictions:

  1. Stale [[cron.jobs]] references remain. Requirements and Decision still say extend existing [[cron.jobs]], and Phase 2 says Phase 1 [[cron.jobs]] entries remain valid. Since this ADR now explicitly supports usercron only, these should say usercron [[jobs]].

  2. generation is not implementable as written without separate state. The ADR says no separate state file and usercron is the state, but it also says:

    • human can re-enable by bumping generation
    • execution checks auto-disabled AND config generation == persisted generation
    • generation is checked against last-known generation at time of auto-disable

    There is no persisted generation outside the same usercron file, and once enabled = false, the scheduler skips the job. Bumping generation alone cannot reactivate unless the scheduler evaluates disabled jobs and stores/computes prior generation somewhere. Simplest fix: remove generation from Phase 1 entirely. Re-enable is just enabled = true. If you want generation later, move it to Phase 2 or add an explicit persisted field in the file such as auto_disabled_generation, but that is more complexity.

  3. Execution flow is stale. The flow still references "auto-disabled AND config generation == persisted generation" and "persist state". With usercron-only state, it should be:

    • schedule fires
    • if enabled = false, skip
    • run disable_on_success
    • exit 0: post success message, scheduler writes enabled = false and thread_id if needed
    • non-zero/timeout: send normal message
  4. Success behavior conflicts with test scenario. State Persistence says scheduler posts ✅ Goal achieved, but Happy Path says on pass the job auto-disables with no message. Pick one. The latest design appears to make success notification mandatory, so update Happy Path.

These are doc-only fixes, but they matter because this ADR is the contract for implementation.

@chaodu-agent
Copy link
Copy Markdown
Collaborator Author

Checked latest head 6aaa750. The generation issue is fixed, but the ADR still has stale [[cron.jobs]] references that conflict with the usercron-only [[jobs]] contract:

  • Requirements: Extend existing [[cron.jobs]] with a disable_on_success field
  • Decision table/paragraph: Extend [[cron.jobs]] and Decision: Extend [[cron.jobs]]
  • Phase 2 compatibility sentence: Phase 1 [[cron.jobs]] entries with disable_on_success remain valid...

Since this ADR now says disable_on_success is only supported in $HOME/.openab/cronjob.toml usercron files, these should say usercron [[jobs]]. After that cleanup, the ADR is consistent from my side.

@chaodu-agent
Copy link
Copy Markdown
Collaborator Author

ADR file is now consistent on latest head 294f443: usercron [[jobs]], no generation, scheduler writes enabled = false, stable thread, success notification, no separate state file.

One stale PR-body line remains:

  • generation counter — optional, signals fresh start on re-enable

Please remove that from the PR body so the summary matches the ADR. After that, LGTM from me.

@shaun-agent
Copy link
Copy Markdown
Contributor

OpenAB PR Screening

This is auto-generated by the OpenAB project-screening flow for context collection and reviewer handoff.
Click 👍 if you find this useful. Human review will be done within 24 hours. We appreciate your support and contribution 🙏

Screening report ## Intent

PR #816 proposes an ADR for extending usercron jobs with disable_on_success, so scheduled jobs can stop themselves once a clearly defined goal is achieved.

The operator-visible problem: recurring cron prompts keep firing even after the task’s goal is complete. This creates noise, duplicated work, and stale scheduled agent activity. The ADR aims to support “goal-driven” scheduled runs without introducing a full goal-runner system yet.

Feat

This is a docs / architecture decision PR.

Behavior described by the ADR:

  • Add disable_on_success support to [[jobs]] in $HOME/.openab/cronjob.toml
  • Before sending the normal scheduled prompt, run a success-check command
  • Treat the goal as achieved only when the command:
    • exits 0
    • prints disable_on_success_match
  • Post a success notification before disabling the job
  • Persist enabled = false directly in the usercron file
  • Keep scheduling ownership in OpenAB, not the ACP model or agent

Who It Serves

Primary beneficiaries:

  • agent runtime operators who run recurring scheduled tasks
  • deployers managing long-lived OpenAB cron jobs
  • maintainers trying to keep scheduling behavior simple and inspectable

Secondary beneficiary:

  • Discord / Slack end users, indirectly, by reducing repeated agent messages after a goal is done

Rewritten Prompt

Implement the ADR for goal-driven usercron jobs by adding disable_on_success to existing [[jobs]] configuration.

Before each scheduled run, if disable_on_success is configured, execute its check command in the scheduler context. Only mark the job complete when the command exits successfully and emits the exact sentinel string disable_on_success_match. When matched, post a mandatory “Goal achieved” notification to the job’s delivery thread/channel, atomically update $HOME/.openab/cronjob.toml with enabled = false, and skip the normal prompt dispatch.

Do not introduce a separate goal-runner system or model-based judging in this phase. Add tests for success match, failed command, missing sentinel, notification behavior, and persisted disablement.

Merge Pitch

This is worth advancing because it documents a small, practical step toward goal-driven scheduled agents without committing OpenAB to a larger orchestration system too early.

Risk profile: moderate for future implementation, low for this ADR. The main reviewer concern will be whether mutating cronjob.toml directly is reliable enough, especially around concurrent scheduler ticks, atomic writes, and preserving user formatting.

Best-Practice Comparison

Relevant OpenClaw principles:

  • gateway-owned scheduling: aligned. The ADR keeps the scheduler as the actor.
  • durable job persistence: partially aligned. State is persisted by mutating cronjob.toml, but this needs atomic write discipline.
  • explicit delivery routing: relevant. The success notification must go to the same stable destination as the scheduled job.
  • retry/backoff and run logs: not part of this phase, but likely needed later for debuggability.
  • isolated executions: relevant if the success-check command can run arbitrary shell work.

Relevant Hermes Agent principles:

  • gateway daemon tick model: aligned with extending existing usercron.
  • file locking to prevent overlap: highly relevant. Direct config mutation needs locking.
  • atomic writes for persisted state: highly relevant. Required before implementation should merge.
  • fresh session per scheduled run: relevant to prompt dispatch, but less central to the ADR.
  • self-contained prompts for scheduled tasks: relevant to future goal-runner work, not required for this disable_on_success phase.

Not relevant yet:

  • LLM judge
  • stuck detection
  • escalation policy
  • state delta analysis

Those belong to the ADR’s stated Phase 2.

Implementation Options

Option 1: Conservative ADR-only merge

Merge the ADR as design documentation, but require a follow-up implementation issue before code changes. Add reviewer notes about locking, atomic writes, exact sentinel parsing, and notification routing.

Option 2: Balanced usercron implementation

Implement disable_on_success directly in the existing usercron scheduler. Add command execution, sentinel matching, success notification, file locking, atomic TOML update, and focused tests. Keep scope strictly Phase 1.

Option 3: Ambitious goal-runner foundation

Introduce a goal-runner abstraction behind usercron with structured state, run logs, disable conditions, retry metadata, and future hooks for stuck detection or LLM judging. Expose disable_on_success as the first policy.

Comparison Table

Option Speed to ship Complexity Reliability Maintainability User impact Fit for OpenAB right now
Conservative ADR-only High Low Medium Medium Low Good
Balanced usercron implementation Medium Medium High if locking/atomic writes are included High High Best
Ambitious goal-runner foundation Low High Potentially high Medium until proven High later Premature

Recommendation

Advance the ADR, but steer follow-up toward Option 2: balanced usercron implementation.

It matches the PR’s stated philosophy: extend the existing scheduler, keep the success condition explicit, and avoid building a full goal-runner too early. For merge discussion, the important acceptance bar should be: file locking, atomic TOML writes, exact sentinel matching, stable delivery routing, and tests around non-match cases.

Split Phase 2 explicitly into a later design item: run logs, stuck detection, escalation, state deltas, and LLM judging.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pending-maintainer pending-screening PR awaiting automated screening

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants