feat: add cron agent for scheduled AI task dispatch#304
Conversation
- Create cron-agent.md subagent with full documentation - Add cron-helper.sh for job management (list, add, remove, pause, resume, logs, debug, status) - Add cron-dispatch.sh for executing jobs via OpenCode server API - Update subagent-index.toon with new scripts and updated automation folder description - Integrates with existing mailbox system for notifications - Supports persistent server setup via launchd (macOS) or systemd (Linux) Closes #t110
WalkthroughThis pull request introduces a complete cron-based job management system for dispatching AI agent tasks to an OpenCode server. Two bash scripts handle job scheduling and execution, complemented by documentation and index updates tracking the new automation capabilities. Changes
Sequence Diagram(s)sequenceDiagram
participant Crontab as System Crontab
participant Dispatch as cron-dispatch.sh
participant OpenCode as OpenCode Server
participant Config as cron-jobs.json
participant Mail as Mail Helper
Crontab->>Dispatch: Trigger with job-id
Dispatch->>Config: Load job config
Dispatch->>OpenCode: Health check
OpenCode-->>Dispatch: Server status
Dispatch->>OpenCode: Create session
OpenCode-->>Dispatch: Session ID
Dispatch->>OpenCode: Send task prompt with model
OpenCode-->>Dispatch: Response/Result
Dispatch->>Config: Update lastRun & lastStatus
Dispatch->>Mail: Send notification (optional)
Mail-->>Dispatch: Confirmation
sequenceDiagram
participant User
participant Helper as cron-helper.sh
participant Config as cron-jobs.json
participant Crontab as System Crontab
participant OpenCode as OpenCode Server
User->>Helper: add/remove/pause/resume
Helper->>Config: Read/update job definitions
Config-->>Helper: Job list
Helper->>Crontab: Sync active jobs
Crontab-->>Helper: Crontab updated
User->>Helper: status/debug
Helper->>OpenCode: Health check
OpenCode-->>Helper: Server status
Helper-->>User: Status report
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary of ChangesHello @marcusquinn, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces a new cron agent system designed to automate the dispatch of AI tasks via the OpenCode server. It provides a robust command-line interface for users to define, manage, and monitor scheduled AI jobs, enabling recurring operations like daily reports, health checks, and automated maintenance. The system handles job configuration, execution, logging, and optional notifications, significantly enhancing the automation capabilities of the AI agent platform. Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
|
🔍 Code Quality Report�[0;35m[MONITOR]�[0m Code Review Monitoring Report �[0;34m[INFO]�[0m Latest Quality Status: �[0;34m[INFO]�[0m Recent monitoring activity: 📈 Current Quality Metrics
Generated on: Wed Feb 4 03:55:13 UTC 2026 Generated by AI DevOps Framework Code Review Monitoring |
There was a problem hiding this comment.
Code Review
This pull request introduces a cron agent for scheduling AI tasks, including helper scripts for job management and dispatch, along with comprehensive documentation. The implementation is solid, but I've identified several areas for improvement in the shell scripts, primarily concerning security, robustness, and efficiency.
My main concerns are:
- Unsafe handling of credentials in
curlcommands, which could lead to security vulnerabilities. - A potential bug in job ID generation that could create duplicate IDs.
- Inefficient or fragile patterns in shell scripting that could be improved for better reliability and performance.
I've provided specific suggestions to address these points. Overall, this is a great feature addition.
| get_auth_header() { | ||
| if [[ -n "${OPENCODE_SERVER_PASSWORD:-}" ]]; then | ||
| local user="${OPENCODE_SERVER_USERNAME:-admin}" | ||
| echo "-u ${user}:${OPENCODE_SERVER_PASSWORD}" | ||
| else | ||
| echo "" | ||
| fi | ||
| } |
There was a problem hiding this comment.
The way get_auth_header is implemented and used is unsafe. It constructs a string with arguments and relies on word-splitting, which can fail or be a security risk if OPENCODE_SERVER_PASSWORD contains spaces or special characters. The shellcheck disable=SC2086 comments show an awareness of word-splitting, but this pattern is not robust.
A much safer way is to use arrays for command arguments. I suggest removing get_auth_header and using a small block of code at each curl call site. For example, in check_server:
check_server() {
local url="http://${OPENCODE_HOST}:${OPENCODE_PORT}/global/health"
local auth_args=()
if [[ -n "${OPENCODE_SERVER_PASSWORD:-}" ]]; then
local user="${OPENCODE_SERVER_USERNAME:-admin}"
auth_args=("-u" "${user}:${OPENCODE_SERVER_PASSWORD}")
fi
if curl -sf "${auth_args[@]}" "$url" &>/dev/null; then
return 0
else
return 1
fi
}This pattern should be applied to all curl calls that need authentication (check_server, create_session, send_prompt, delete_session). This avoids word-splitting issues and makes the script more robust and secure.
| curl -sf $auth_header -X POST "$url" \ | ||
| -H "Content-Type: application/json" \ | ||
| -d "{\"title\": \"$title\"}" | jq -r '.id' |
There was a problem hiding this comment.
The JSON payload is constructed by embedding the $title variable directly into a string. If $title contains a double quote ("), it will break the JSON, causing the curl command to fail or send a malformed request.
You should use jq to safely construct the JSON payload, similar to how it's done in the send_prompt function.
| curl -sf $auth_header -X POST "$url" \ | |
| -H "Content-Type: application/json" \ | |
| -d "{\"title\": \"$title\"}" | jq -r '.id' | |
| curl -sf $auth_header -X POST "$url" \ | |
| -H "Content-Type: application/json" \ | |
| -d "$(jq -n --arg title "$title" '{"title": $title}')" | jq -r '.id' |
| check_server() { | ||
| local url="http://${OPENCODE_HOST}:${OPENCODE_PORT}/global/health" | ||
| local auth_header="" | ||
|
|
||
| if [[ -n "${OPENCODE_SERVER_PASSWORD:-}" ]]; then | ||
| local user="${OPENCODE_SERVER_USERNAME:-admin}" | ||
| auth_header="-u ${user}:${OPENCODE_SERVER_PASSWORD}" | ||
| fi | ||
|
|
||
| # shellcheck disable=SC2086 | ||
| if curl -sf $auth_header "$url" &>/dev/null; then | ||
| return 0 | ||
| else | ||
| return 1 | ||
| fi | ||
| } |
There was a problem hiding this comment.
The curl command uses an unquoted variable $auth_header which is constructed from OPENCODE_SERVER_PASSWORD. This is unsafe and can lead to command injection or errors if the password contains spaces or shell metacharacters. A safer approach is to use an array for curl arguments to prevent word splitting.
| check_server() { | |
| local url="http://${OPENCODE_HOST}:${OPENCODE_PORT}/global/health" | |
| local auth_header="" | |
| if [[ -n "${OPENCODE_SERVER_PASSWORD:-}" ]]; then | |
| local user="${OPENCODE_SERVER_USERNAME:-admin}" | |
| auth_header="-u ${user}:${OPENCODE_SERVER_PASSWORD}" | |
| fi | |
| # shellcheck disable=SC2086 | |
| if curl -sf $auth_header "$url" &>/dev/null; then | |
| return 0 | |
| else | |
| return 1 | |
| fi | |
| } | |
| check_server() { | |
| local url="http://${OPENCODE_HOST}:${OPENCODE_PORT}/global/health" | |
| local auth_args=() | |
| if [[ -n "${OPENCODE_SERVER_PASSWORD:-}" ]]; then | |
| local user="${OPENCODE_SERVER_USERNAME:-admin}" | |
| auth_args=("-u" "${user}:${OPENCODE_SERVER_PASSWORD}") | |
| fi | |
| # shellcheck disable=SC2086 | |
| if curl -sf "${auth_args[@]}" "$url" &>/dev/null; then | |
| return 0 | |
| else | |
| return 1 | |
| fi | |
| } |
| name=$(echo "$job" | jq -r '.name') | ||
| task=$(echo "$job" | jq -r '.task') | ||
| workdir=$(echo "$job" | jq -r '.workdir') | ||
| timeout=$(echo "$job" | jq -r '.timeout // 600') | ||
| model=$(echo "$job" | jq -r '.model // "anthropic/claude-sonnet-4-20250514"') | ||
| notify=$(echo "$job" | jq -r '.notify // "none"') |
There was a problem hiding this comment.
You are calling jq multiple times on the same JSON object ($job) to extract different properties. This is inefficient as it spawns a new process for each property. You can extract all properties in a single, more efficient jq call using mapfile to read the values into an array. This is robust even if property values contain newlines.
| name=$(echo "$job" | jq -r '.name') | |
| task=$(echo "$job" | jq -r '.task') | |
| workdir=$(echo "$job" | jq -r '.workdir') | |
| timeout=$(echo "$job" | jq -r '.timeout // 600') | |
| model=$(echo "$job" | jq -r '.model // "anthropic/claude-sonnet-4-20250514"') | |
| notify=$(echo "$job" | jq -r '.notify // "none"') | |
| mapfile -t values < <(echo "$job" | jq -r '.name, .task, .workdir, (.timeout // 600), (.model // "anthropic/claude-sonnet-4-20250514"), (.notify // "none")') | |
| name="${values[0]}" | |
| task="${values[1]}" | |
| workdir="${values[2]}" | |
| timeout="${values[3]}" | |
| model="${values[4]}" | |
| notify="${values[5]}" |
| generate_job_id() { | ||
| local count | ||
| count=$(jq '.jobs | length' "$CONFIG_FILE" 2>/dev/null || echo "0") | ||
| printf "job-%03d" $((count + 1)) | ||
| } |
There was a problem hiding this comment.
The current method for generating a job ID is not robust. It's based on the total number of jobs, so if a job is removed, it can lead to duplicate IDs being generated later. For example, if you have job-001, job-002, job-003, and you remove job-002, the next job added will be job-003, which already exists.
A more robust approach would be to find the highest existing job number and increment it.
| generate_job_id() { | |
| local count | |
| count=$(jq '.jobs | length' "$CONFIG_FILE" 2>/dev/null || echo "0") | |
| printf "job-%03d" $((count + 1)) | |
| } | |
| generate_job_id() { | |
| local last_id | |
| last_id=$(jq -r '.jobs[].id' "$CONFIG_FILE" 2>/dev/null | grep -o '[0-9]\+' | sort -rn | head -n 1) | |
| local next_id=$(( ${last_id:-0} + 1 )) | |
| printf "job-%03d" "$next_id" | |
| } |
| schedule=$(echo "$line" | rev | cut -d' ' -f2- | rev) | ||
| job_id=$(echo "$line" | awk '{print $NF}') |
There was a problem hiding this comment.
The rev | cut | rev trick to extract the schedule is clever but can be fragile, especially with unusual whitespace. A more robust and readable way to extract the schedule and job ID is to use parameter expansion.
| schedule=$(echo "$line" | rev | cut -d' ' -f2- | rev) | |
| job_id=$(echo "$line" | awk '{print $NF}') | |
| job_id=${line##* } | |
| schedule=${line% "$job_id"} |
|
|
||
| if [[ "$follow" == "true" ]]; then | ||
| # shellcheck disable=SC2086 | ||
| tail -f $log_files |
There was a problem hiding this comment.
The $log_files variable is used unquoted in the tail -f command. If any log filename were to contain spaces or other special characters, this would fail. It could also fail if the list of files is very long and exceeds the system's argument length limit.
While the current job ID format prevents spaces in filenames, it's best practice to handle this robustly. A good way to handle a potentially large number of files is to use xargs.
| tail -f $log_files | |
| echo "$log_files" | xargs tail -f |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Fix all issues with AI agents
In @.agent/scripts/cron-dispatch.sh:
- Around line 75-88: The update_job_status function currently does an
unprotected read-modify-write on CONFIG_FILE; wrap the whole update sequence in
a lightweight file lock to prevent concurrent cron runs from clobbering each
other. Modify update_job_status to acquire a lock (e.g., using flock on a lock
file descriptor or a dedicated lock file) before reading CONFIG_FILE, perform
the jq update and atomic mv while holding the lock, and ensure the lock is
released in a finally/cleanup path (use trap to remove the lock and temp_file on
exit/error). Keep the existing temp_file/mv atomic replace logic and ensure the
lock name is unique to CONFIG_FILE so other code that edits cron-jobs.json will
coordinate with update_job_status.
- Around line 247-254: The create_session call can fail (and under set -e a
curl/jq error could abort before we update status); change the session creation
to capture both the output and exit code from create_session (e.g.,
session_output=$(create_session "Cron: $name" 2>&1; echo "__EXIT_CODE__$?") or
run create_session in a subshell to avoid set -e short-circuit), parse the exit
code and treat any non-zero/empty/null output as failure, then call log_error
with the captured error/output and call update_job_status "$job_id" "failed"
before returning 1; ensure you reference create_session, session_id (or
session_output), log_error, update_job_status and job_id so the failure is
handled explicitly rather than relying on set -e.
- Around line 137-140: The script uses the GNU-only timeout command (invoked as
timeout "$timeout" curl ...) which fails on macOS; modify the top of the script
to detect an available timeout binary (check for gtimeout first then timeout),
set a variable (e.g., CMD_TIMEOUT) to the found command, and replace direct uses
of timeout with "$CMD_TIMEOUT" "$timeout" ...; if neither command is found, exit
immediately with a clear error message indicating that timeout or gtimeout is
required so the cron job fails fast and gives actionable feedback.
In @.agent/scripts/cron-helper.sh:
- Around line 180-235: The --timeout value (variable timeout) is passed to jq
using --argjson which fails on non-numeric input; before calling jq (before the
jq --argjson "$timeout" line) validate that timeout is a positive integer (e.g.,
match against a regex like ^[0-9]+$ and ensure >0), error via log_error and
return 1 if invalid, and convert/ensure timeout is the numeric value to safely
use in --argjson; update the block around timeout, generate_job_id, and the jq
invocation to perform this check.
- Around line 401-419: The --since option in cmd_logs is parsed into the local
variable since but ignored; update cmd_logs (the function and its option
parsing) to either implement timestamp-based filtering or fail-fast: simplest
fix — after parsing (before the noop “: "${since:=}"”), check if since is
non-empty and call log_error "The --since option is not yet supported" and
return 1 (or remove the --since case from the while/case if you prefer to hide
it from help). Refer to the cmd_logs function, the local variable since, and the
option parsing block to make the change so the script no longer silently accepts
--since.
In @.agent/tools/automation/cron-agent.md:
- Around line 19-395: The doc currently embeds large inline
command/config/service blocks; replace those blocks with progressive-disclosure
references to the authoritative implementations by pointing to the actual script
lines (e.g., file:.agent/scripts/cron-helper.sh:613-672 and
file:.agent/scripts/cron-dispatch.sh:190-295) and move the launchd/systemd
plist/unit examples into repository templates (e.g.,
tools/templates/opencode-server-launchd.plist and
tools/templates/opencode-server.service) then reference them as file:...; ensure
the README keeps short usage snippets only when authoritative and remove long
inline blocks in favor of file:line pointers throughout the cron-agent.md (List
Jobs, Add Job, Debug, Persistent Server Setup sections) so readers are directed
to the implementation and template files.
🧹 Nitpick comments (1)
.agent/scripts/cron-helper.sh (1)
69-73: Avoid reusing job IDs after deletions.
Using.jobs | lengthwill reuse IDs and can collide with existing logs/history. Consider deriving the next ID from the max existing numeric suffix.♻️ Suggested fix
generate_job_id() { - local count - count=$(jq '.jobs | length' "$CONFIG_FILE" 2>/dev/null || echo "0") - printf "job-%03d" $((count + 1)) + local max_id + max_id=$(jq -r '.jobs[].id' "$CONFIG_FILE" 2>/dev/null \ + | sed -n 's/^job-\([0-9]\+\)$/\1/p' \ + | sort -n | tail -1) + max_id=${max_id:-0} + printf "job-%03d" $((max_id + 1)) }
| update_job_status() { | ||
| local job_id="$1" | ||
| local status="$2" | ||
| local timestamp | ||
| timestamp=$(log_timestamp) | ||
|
|
||
| local temp_file | ||
| temp_file=$(mktemp) | ||
| jq --arg id "$job_id" \ | ||
| --arg status "$status" \ | ||
| --arg timestamp "$timestamp" \ | ||
| '(.jobs[] | select(.id == $id)) |= . + {lastRun: $timestamp, lastStatus: $status}' \ | ||
| "$CONFIG_FILE" > "$temp_file" | ||
| mv "$temp_file" "$CONFIG_FILE" |
There was a problem hiding this comment.
Guard cron-jobs.json updates with a lock to prevent lost updates.
Concurrent cron runs can clobber the file since read-modify-write is unprotected. Add a lightweight lock to avoid race conditions.
🛡️ Suggested fix (lock around update)
update_job_status() {
local job_id="$1"
local status="$2"
local timestamp
timestamp=$(log_timestamp)
- local temp_file
- temp_file=$(mktemp)
- jq --arg id "$job_id" \
- --arg status "$status" \
- --arg timestamp "$timestamp" \
- '(.jobs[] | select(.id == $id)) |= . + {lastRun: $timestamp, lastStatus: $status}' \
- "$CONFIG_FILE" > "$temp_file"
- mv "$temp_file" "$CONFIG_FILE"
+ local lock_dir="${CONFIG_FILE}.lock"
+ if ! mkdir "$lock_dir" 2>/dev/null; then
+ log_error "Config update already in progress"
+ return 1
+ fi
+ local temp_file
+ temp_file=$(mktemp)
+ trap 'rm -rf "$temp_file" "$lock_dir"' RETURN
+ jq --arg id "$job_id" \
+ --arg status "$status" \
+ --arg timestamp "$timestamp" \
+ '(.jobs[] | select(.id == $id)) |= . + {lastRun: $timestamp, lastStatus: $status}' \
+ "$CONFIG_FILE" > "$temp_file"
+ mv "$temp_file" "$CONFIG_FILE"
+ rm -rf "$lock_dir"
+ trap - RETURN
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| update_job_status() { | |
| local job_id="$1" | |
| local status="$2" | |
| local timestamp | |
| timestamp=$(log_timestamp) | |
| local temp_file | |
| temp_file=$(mktemp) | |
| jq --arg id "$job_id" \ | |
| --arg status "$status" \ | |
| --arg timestamp "$timestamp" \ | |
| '(.jobs[] | select(.id == $id)) |= . + {lastRun: $timestamp, lastStatus: $status}' \ | |
| "$CONFIG_FILE" > "$temp_file" | |
| mv "$temp_file" "$CONFIG_FILE" | |
| update_job_status() { | |
| local job_id="$1" | |
| local status="$2" | |
| local timestamp | |
| timestamp=$(log_timestamp) | |
| local lock_dir="${CONFIG_FILE}.lock" | |
| if ! mkdir "$lock_dir" 2>/dev/null; then | |
| log_error "Config update already in progress" | |
| return 1 | |
| fi | |
| local temp_file | |
| temp_file=$(mktemp) | |
| trap 'rm -rf "$temp_file" "$lock_dir"' RETURN | |
| jq --arg id "$job_id" \ | |
| --arg status "$status" \ | |
| --arg timestamp "$timestamp" \ | |
| '(.jobs[] | select(.id == $id)) |= . + {lastRun: $timestamp, lastStatus: $status}' \ | |
| "$CONFIG_FILE" > "$temp_file" | |
| mv "$temp_file" "$CONFIG_FILE" | |
| rm -rf "$lock_dir" | |
| trap - RETURN | |
| } |
🤖 Prompt for AI Agents
In @.agent/scripts/cron-dispatch.sh around lines 75 - 88, The update_job_status
function currently does an unprotected read-modify-write on CONFIG_FILE; wrap
the whole update sequence in a lightweight file lock to prevent concurrent cron
runs from clobbering each other. Modify update_job_status to acquire a lock
(e.g., using flock on a lock file descriptor or a dedicated lock file) before
reading CONFIG_FILE, perform the jq update and atomic mv while holding the lock,
and ensure the lock is released in a finally/cleanup path (use trap to remove
the lock and temp_file on exit/error). Keep the existing temp_file/mv atomic
replace logic and ensure the lock name is unique to CONFIG_FILE so other code
that edits cron-jobs.json will coordinate with update_job_status.
| # Send with timeout | ||
| # shellcheck disable=SC2086 | ||
| timeout "$timeout" curl -sf $auth_header -X POST "$url" \ | ||
| -H "Content-Type: application/json" \ |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "cron-dispatch.sh" -type fRepository: marcusquinn/aidevops
Length of output: 97
🏁 Script executed:
wc -l ./.agent/scripts/cron-dispatch.shRepository: marcusquinn/aidevops
Length of output: 101
🏁 Script executed:
sed -n '130,150p' ./.agent/scripts/cron-dispatch.shRepository: marcusquinn/aidevops
Length of output: 665
🏁 Script executed:
head -50 ./.agent/scripts/cron-dispatch.shRepository: marcusquinn/aidevops
Length of output: 1398
🏁 Script executed:
grep -n "timeout" ./.agent/scripts/cron-dispatch.shRepository: marcusquinn/aidevops
Length of output: 424
🏁 Script executed:
grep -n "uname\|darwin\|macos\|command -v" ./.agent/scripts/cron-dispatch.shRepository: marcusquinn/aidevops
Length of output: 46
🏁 Script executed:
sed -n '100,145p' ./.agent/scripts/cron-dispatch.shRepository: marcusquinn/aidevops
Length of output: 1364
🏁 Script executed:
sed -n '1,100p' ./.agent/scripts/cron-dispatch.sh | grep -i "system\|platform\|os\|version"Repository: marcusquinn/aidevops
Length of output: 252
🏁 Script executed:
ls -la ./.agent/scripts/Repository: marcusquinn/aidevops
Length of output: 12119
🏁 Script executed:
cat -n ./.agent/scripts/cron-dispatch.shRepository: marcusquinn/aidevops
Length of output: 10413
Add portable timeout command detection for macOS compatibility.
The timeout command is GNU coreutils-specific and unavailable on macOS by default. Cron jobs will fail with exit code 127 when this command is missing. Detect the timeout command (or gtimeout on systems with coreutils installed) and fail fast with a clear error if neither is available.
🔧 Suggested fix
+#######################################
+# Get timeout command
+#######################################
+get_timeout_cmd() {
+ local timeout_cmd
+ timeout_cmd=$(command -v timeout || command -v gtimeout || true)
+ if [[ -z "$timeout_cmd" ]]; then
+ log_error "timeout not found (install GNU coreutils or homebrew coreutils)"
+ return 1
+ fi
+ echo "$timeout_cmd"
+}
+
#######################################
# Send prompt to session
#######################################
send_prompt() {
local session_id="$1"
local task="$2"
local model="$3"
local timeout="$4"
local url="http://${OPENCODE_HOST}:${OPENCODE_PORT}/session/${session_id}/message"
local auth_header
auth_header=$(get_auth_header)
+ local timeout_cmd
+ timeout_cmd=$(get_timeout_cmd) || return 1
+
# Parse model into provider and model ID
local provider_id model_id
provider_id=$(echo "$model" | cut -d'/' -f1)
model_id=$(echo "$model" | cut -d'/' -f2-)
# Build request body
local body
body=$(jq -n \
--arg provider "$provider_id" \
--arg model "$model_id" \
--arg task "$task" \
'{
model: {
providerID: $provider,
modelID: $model
},
parts: [{type: "text", text: $task}]
}')
# Send with timeout
# shellcheck disable=SC2086
- timeout "$timeout" curl -sf $auth_header -X POST "$url" \
+ "$timeout_cmd" "$timeout" curl -sf $auth_header -X POST "$url" \
-H "Content-Type: application/json" \
-d "$body"
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # Send with timeout | |
| # shellcheck disable=SC2086 | |
| timeout "$timeout" curl -sf $auth_header -X POST "$url" \ | |
| -H "Content-Type: application/json" \ | |
| ####################################### | |
| # Get timeout command | |
| ####################################### | |
| get_timeout_cmd() { | |
| local timeout_cmd | |
| timeout_cmd=$(command -v timeout || command -v gtimeout || true) | |
| if [[ -z "$timeout_cmd" ]]; then | |
| log_error "timeout not found (install GNU coreutils or homebrew coreutils)" | |
| return 1 | |
| fi | |
| echo "$timeout_cmd" | |
| } | |
| ####################################### | |
| # Send prompt to session | |
| ####################################### | |
| send_prompt() { | |
| local session_id="$1" | |
| local task="$2" | |
| local model="$3" | |
| local timeout="$4" | |
| local url="http://${OPENCODE_HOST}:${OPENCODE_PORT}/session/${session_id}/message" | |
| local auth_header | |
| auth_header=$(get_auth_header) | |
| local timeout_cmd | |
| timeout_cmd=$(get_timeout_cmd) || return 1 | |
| # Parse model into provider and model ID | |
| local provider_id model_id | |
| provider_id=$(echo "$model" | cut -d'/' -f1) | |
| model_id=$(echo "$model" | cut -d'/' -f2-) | |
| # Build request body | |
| local body | |
| body=$(jq -n \ | |
| --arg provider "$provider_id" \ | |
| --arg model "$model_id" \ | |
| --arg task "$task" \ | |
| '{ | |
| model: { | |
| providerID: $provider, | |
| modelID: $model | |
| }, | |
| parts: [{type: "text", text: $task}] | |
| }') | |
| # Send with timeout | |
| # shellcheck disable=SC2086 | |
| "$timeout_cmd" "$timeout" curl -sf $auth_header -X POST "$url" \ | |
| -H "Content-Type: application/json" \ | |
| -d "$body" | |
| } |
🤖 Prompt for AI Agents
In @.agent/scripts/cron-dispatch.sh around lines 137 - 140, The script uses the
GNU-only timeout command (invoked as timeout "$timeout" curl ...) which fails on
macOS; modify the top of the script to detect an available timeout binary (check
for gtimeout first then timeout), set a variable (e.g., CMD_TIMEOUT) to the
found command, and replace direct uses of timeout with "$CMD_TIMEOUT" "$timeout"
...; if neither command is found, exit immediately with a clear error message
indicating that timeout or gtimeout is required so the cron job fails fast and
gives actionable feedback.
| # Create session | ||
| local session_id | ||
| session_id=$(create_session "Cron: $name") | ||
| if [[ -z "$session_id" || "$session_id" == "null" ]]; then | ||
| log_error "Failed to create session" | ||
| update_job_status "$job_id" "failed" | ||
| return 1 | ||
| fi |
There was a problem hiding this comment.
Handle create_session failures without set -e aborts.
With set -e, a curl/jq failure inside create_session can exit before you update status. Capture the failure explicitly.
✅ Suggested fix
- session_id=$(create_session "Cron: $name")
- if [[ -z "$session_id" || "$session_id" == "null" ]]; then
+ if ! session_id=$(create_session "Cron: $name"); then
+ log_error "Failed to create session"
+ update_job_status "$job_id" "failed"
+ return 1
+ fi
+ if [[ -z "$session_id" || "$session_id" == "null" ]]; then
log_error "Failed to create session"
update_job_status "$job_id" "failed"
return 1
fi🤖 Prompt for AI Agents
In @.agent/scripts/cron-dispatch.sh around lines 247 - 254, The create_session
call can fail (and under set -e a curl/jq error could abort before we update
status); change the session creation to capture both the output and exit code
from create_session (e.g., session_output=$(create_session "Cron: $name" 2>&1;
echo "__EXIT_CODE__$?") or run create_session in a subshell to avoid set -e
short-circuit), parse the exit code and treat any non-zero/empty/null output as
failure, then call log_error with the captured error/output and call
update_job_status "$job_id" "failed" before returning 1; ensure you reference
create_session, session_id (or session_output), log_error, update_job_status and
job_id so the failure is handled explicitly rather than relying on set -e.
| local schedule="" task="" name="" notify="none" timeout="$DEFAULT_TIMEOUT" | ||
| local workdir="" model="$DEFAULT_MODEL" paused=false | ||
|
|
||
| while [[ $# -gt 0 ]]; do | ||
| case "$1" in | ||
| --schedule) [[ $# -lt 2 ]] && { log_error "--schedule requires a value"; return 1; }; schedule="$2"; shift 2 ;; | ||
| --task) [[ $# -lt 2 ]] && { log_error "--task requires a value"; return 1; }; task="$2"; shift 2 ;; | ||
| --name) [[ $# -lt 2 ]] && { log_error "--name requires a value"; return 1; }; name="$2"; shift 2 ;; | ||
| --notify) [[ $# -lt 2 ]] && { log_error "--notify requires a value"; return 1; }; notify="$2"; shift 2 ;; | ||
| --timeout) [[ $# -lt 2 ]] && { log_error "--timeout requires a value"; return 1; }; timeout="$2"; shift 2 ;; | ||
| --workdir) [[ $# -lt 2 ]] && { log_error "--workdir requires a value"; return 1; }; workdir="$2"; shift 2 ;; | ||
| --model) [[ $# -lt 2 ]] && { log_error "--model requires a value"; return 1; }; model="$2"; shift 2 ;; | ||
| --paused) paused=true; shift ;; | ||
| *) log_error "Unknown option: $1"; return 1 ;; | ||
| esac | ||
| done | ||
|
|
||
| # Validate required fields | ||
| if [[ -z "$schedule" ]]; then | ||
| log_error "--schedule is required (e.g., \"0 9 * * *\")" | ||
| return 1 | ||
| fi | ||
|
|
||
| if [[ -z "$task" ]]; then | ||
| log_error "--task is required (description of what the AI should do)" | ||
| return 1 | ||
| fi | ||
|
|
||
| # Generate name if not provided | ||
| if [[ -z "$name" ]]; then | ||
| name=$(echo "$task" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | cut -c1-30) | ||
| fi | ||
|
|
||
| # Set workdir to current if not specified | ||
| if [[ -z "$workdir" ]]; then | ||
| workdir="$(pwd)" | ||
| fi | ||
|
|
||
| local job_id | ||
| job_id=$(generate_job_id) | ||
| local timestamp | ||
| timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ) | ||
| local status="active" | ||
| [[ "$paused" == "true" ]] && status="paused" | ||
|
|
||
| # Add job to config | ||
| local temp_file | ||
| temp_file=$(mktemp) | ||
| jq --arg id "$job_id" \ | ||
| --arg name "$name" \ | ||
| --arg schedule "$schedule" \ | ||
| --arg task "$task" \ | ||
| --arg workdir "$workdir" \ | ||
| --argjson timeout "$timeout" \ | ||
| --arg notify "$notify" \ | ||
| --arg model "$model" \ |
There was a problem hiding this comment.
Validate --timeout as a positive integer before jq --argjson.
Non-numeric input will cause jq to error and abort the command with a cryptic message.
✅ Suggested validation
if [[ -z "$task" ]]; then
log_error "--task is required (description of what the AI should do)"
return 1
fi
+
+ if ! [[ "$timeout" =~ ^[0-9]+$ ]] || [[ "$timeout" -le 0 ]]; then
+ log_error "--timeout must be a positive integer (seconds)"
+ return 1
+ fi🤖 Prompt for AI Agents
In @.agent/scripts/cron-helper.sh around lines 180 - 235, The --timeout value
(variable timeout) is passed to jq using --argjson which fails on non-numeric
input; before calling jq (before the jq --argjson "$timeout" line) validate that
timeout is a positive integer (e.g., match against a regex like ^[0-9]+$ and
ensure >0), error via log_error and return 1 if invalid, and convert/ensure
timeout is the numeric value to safely use in --argjson; update the block around
timeout, generate_job_id, and the jq invocation to perform this check.
| cmd_logs() { | ||
| ensure_setup | ||
|
|
||
| local job_id="" tail_lines=50 follow=false | ||
| local since="" # TODO: Implement --since date filtering | ||
|
|
||
| while [[ $# -gt 0 ]]; do | ||
| case "$1" in | ||
| --job) [[ $# -lt 2 ]] && { log_error "--job requires a value"; return 1; }; job_id="$2"; shift 2 ;; | ||
| --tail) [[ $# -lt 2 ]] && { log_error "--tail requires a value"; return 1; }; tail_lines="$2"; shift 2 ;; | ||
| --follow|-f) follow=true; shift ;; | ||
| --since) [[ $# -lt 2 ]] && { log_error "--since requires a value"; return 1; }; since="$2"; shift 2 ;; | ||
| *) log_error "Unknown option: $1"; return 1 ;; | ||
| esac | ||
| done | ||
|
|
||
| # Use since to suppress shellcheck warning (future: filter logs by date) | ||
| : "${since:=}" | ||
|
|
There was a problem hiding this comment.
--since is documented but not implemented.
Right now it’s parsed then ignored, which is misleading. Either implement filtering or fail fast and remove it from help until supported.
🧭 Suggested interim guard
- # Use since to suppress shellcheck warning (future: filter logs by date)
- : "${since:=}"
+ if [[ -n "$since" ]]; then
+ log_error "--since is not implemented yet"
+ return 1
+ fi📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| cmd_logs() { | |
| ensure_setup | |
| local job_id="" tail_lines=50 follow=false | |
| local since="" # TODO: Implement --since date filtering | |
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| --job) [[ $# -lt 2 ]] && { log_error "--job requires a value"; return 1; }; job_id="$2"; shift 2 ;; | |
| --tail) [[ $# -lt 2 ]] && { log_error "--tail requires a value"; return 1; }; tail_lines="$2"; shift 2 ;; | |
| --follow|-f) follow=true; shift ;; | |
| --since) [[ $# -lt 2 ]] && { log_error "--since requires a value"; return 1; }; since="$2"; shift 2 ;; | |
| *) log_error "Unknown option: $1"; return 1 ;; | |
| esac | |
| done | |
| # Use since to suppress shellcheck warning (future: filter logs by date) | |
| : "${since:=}" | |
| cmd_logs() { | |
| ensure_setup | |
| local job_id="" tail_lines=50 follow=false | |
| local since="" # TODO: Implement --since date filtering | |
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| --job) [[ $# -lt 2 ]] && { log_error "--job requires a value"; return 1; }; job_id="$2"; shift 2 ;; | |
| --tail) [[ $# -lt 2 ]] && { log_error "--tail requires a value"; return 1; }; tail_lines="$2"; shift 2 ;; | |
| --follow|-f) follow=true; shift ;; | |
| --since) [[ $# -lt 2 ]] && { log_error "--since requires a value"; return 1; }; since="$2"; shift 2 ;; | |
| *) log_error "Unknown option: $1"; return 1 ;; | |
| esac | |
| done | |
| if [[ -n "$since" ]]; then | |
| log_error "--since is not implemented yet" | |
| return 1 | |
| fi | |
🤖 Prompt for AI Agents
In @.agent/scripts/cron-helper.sh around lines 401 - 419, The --since option in
cmd_logs is parsed into the local variable since but ignored; update cmd_logs
(the function and its option parsing) to either implement timestamp-based
filtering or fail-fast: simplest fix — after parsing (before the noop “:
"${since:=}"”), check if since is non-empty and call log_error "The --since
option is not yet supported" and return 1 (or remove the --since case from the
while/case if you prefer to hide it from help). Refer to the cmd_logs function,
the local variable since, and the option parsing block to make the change so the
script no longer silently accepts --since.
| ## Quick Reference | ||
|
|
||
| - **List jobs**: `cron-helper.sh list` | ||
| - **Add job**: `cron-helper.sh add --schedule "0 9 * * *" --task "Run daily report"` | ||
| - **Remove job**: `cron-helper.sh remove <job-id>` | ||
| - **Logs**: `cron-helper.sh logs [--job <id>] [--tail 50]` | ||
| - **Debug**: `cron-helper.sh debug <job-id>` | ||
| - **Status**: `cron-helper.sh status` | ||
| - **Config**: `~/.config/aidevops/cron-jobs.json` | ||
|
|
||
| <!-- AI-CONTEXT-END --> | ||
|
|
||
| Agent for setting up, managing, identifying, and debugging cron jobs that dispatch AI agents. Uses OpenCode server API for session management. | ||
|
|
||
| ## Architecture | ||
|
|
||
| ```text | ||
| ┌─────────────────────────────────────────────────────────────┐ | ||
| │ Cron Agent System │ | ||
| ├─────────────────────────────────────────────────────────────┤ | ||
| │ crontab │ | ||
| │ └── cron-dispatch.sh <job-id> │ | ||
| │ └── OpenCode Server API │ | ||
| │ └── AI Session (executes task) │ | ||
| │ └── Results → mail-helper.sh (optional) │ | ||
| ├─────────────────────────────────────────────────────────────┤ | ||
| │ Storage │ | ||
| │ ├── ~/.config/aidevops/cron-jobs.json (job definitions) │ | ||
| │ ├── ~/.aidevops/.agent-workspace/cron/ (execution logs) │ | ||
| │ └── ~/.aidevops/.agent-workspace/mail/ (result delivery) │ | ||
| └─────────────────────────────────────────────────────────────┘ | ||
| ``` | ||
|
|
||
| ## Commands | ||
|
|
||
| ### List Jobs | ||
|
|
||
| ```bash | ||
| # List all scheduled jobs | ||
| cron-helper.sh list | ||
|
|
||
| # Output: | ||
| # ID Schedule Task Status | ||
| # job-001 0 9 * * * Run daily SEO report active | ||
| # job-002 */30 * * * * Check deployment health active | ||
| # job-003 0 0 * * 0 Weekly backup verification paused | ||
| ``` | ||
|
|
||
| ### Add Job | ||
|
|
||
| ```bash | ||
| # Add a new scheduled job | ||
| cron-helper.sh add \ | ||
| --schedule "0 9 * * *" \ | ||
| --task "Generate daily SEO report for example.com" \ | ||
| --name "daily-seo-report" \ | ||
| --notify mail \ | ||
| --timeout 300 | ||
|
|
||
| # Options: | ||
| # --schedule Cron expression (required) | ||
| # --task Task description for AI (required) | ||
| # --name Human-readable name (optional, auto-generated) | ||
| # --notify Notification method: mail|none (default: none) | ||
| # --timeout Max execution time in seconds (default: 600) | ||
| # --workdir Working directory (default: current) | ||
| # --model Model to use (default: from config) | ||
| # --paused Create in paused state | ||
| ``` | ||
|
|
||
| ### Remove Job | ||
|
|
||
| ```bash | ||
| # Remove a job by ID | ||
| cron-helper.sh remove job-001 | ||
|
|
||
| # Remove with confirmation skip | ||
| cron-helper.sh remove job-001 --force | ||
| ``` | ||
|
|
||
| ### Pause/Resume | ||
|
|
||
| ```bash | ||
| # Pause a job (keeps definition, removes from crontab) | ||
| cron-helper.sh pause job-001 | ||
|
|
||
| # Resume a paused job | ||
| cron-helper.sh resume job-001 | ||
| ``` | ||
|
|
||
| ### View Logs | ||
|
|
||
| ```bash | ||
| # View recent execution logs | ||
| cron-helper.sh logs | ||
|
|
||
| # View logs for specific job | ||
| cron-helper.sh logs --job job-001 | ||
|
|
||
| # Tail logs in real-time | ||
| cron-helper.sh logs --tail 50 --follow | ||
|
|
||
| # View logs from specific date | ||
| cron-helper.sh logs --since "2024-01-15" | ||
| ``` | ||
|
|
||
| ### Debug Job | ||
|
|
||
| ```bash | ||
| # Debug a failing job | ||
| cron-helper.sh debug job-001 | ||
|
|
||
| # Output: | ||
| # Job: job-001 (daily-seo-report) | ||
| # Schedule: 0 9 * * * | ||
| # Last run: 2024-01-15T09:00:00Z | ||
| # Status: FAILED | ||
| # Exit code: 1 | ||
| # Duration: 45s | ||
| # | ||
| # Error output: | ||
| # [ERROR] OpenCode server not responding on port 4096 | ||
| # | ||
| # Suggestions: | ||
| # 1. Ensure OpenCode server is running: opencode serve --port 4096 | ||
| # 2. Check server health: curl http://localhost:4096/global/health | ||
| # 3. Verify OPENCODE_SERVER_PASSWORD if authentication is enabled | ||
| ``` | ||
|
|
||
| ### Status | ||
|
|
||
| ```bash | ||
| # Show overall cron system status | ||
| cron-helper.sh status | ||
|
|
||
| # Output: | ||
| # Cron Agent Status | ||
| # ───────────────── | ||
| # Jobs defined: 5 | ||
| # Jobs active: 4 | ||
| # Jobs paused: 1 | ||
| # | ||
| # OpenCode Server: running (port 4096) | ||
| # Last execution: 2024-01-15T09:00:00Z | ||
| # Failed jobs (24h): 1 | ||
| # | ||
| # Upcoming: | ||
| # job-002 (health-check) in 15 minutes | ||
| # job-001 (daily-seo-report) in 2 hours | ||
| ``` | ||
|
|
||
| ## Job Configuration | ||
|
|
||
| Jobs are stored in `~/.config/aidevops/cron-jobs.json`: | ||
|
|
||
| ```json | ||
| { | ||
| "version": "1.0", | ||
| "jobs": [ | ||
| { | ||
| "id": "job-001", | ||
| "name": "daily-seo-report", | ||
| "schedule": "0 9 * * *", | ||
| "task": "Generate daily SEO report for example.com using DataForSEO", | ||
| "workdir": "/Users/me/projects/example-site", | ||
| "timeout": 300, | ||
| "notify": "mail", | ||
| "model": "anthropic/claude-sonnet-4-20250514", | ||
| "status": "active", | ||
| "created": "2024-01-10T10:00:00Z", | ||
| "lastRun": "2024-01-15T09:00:00Z", | ||
| "lastStatus": "success" | ||
| } | ||
| ] | ||
| } | ||
| ``` | ||
|
|
||
| ## Execution Flow | ||
|
|
||
| When a cron job triggers: | ||
|
|
||
| 1. **crontab** calls `cron-dispatch.sh <job-id>` | ||
| 2. **cron-dispatch.sh**: | ||
| - Loads job config from `cron-jobs.json` | ||
| - Checks OpenCode server health | ||
| - Creates new session via API | ||
| - Sends task prompt | ||
| - Waits for completion (with timeout) | ||
| - Logs results | ||
| - Optionally sends notification via mailbox | ||
|
|
||
| ```bash | ||
| # Example crontab entry (auto-managed) | ||
| 0 9 * * * /Users/me/.aidevops/agents/scripts/cron-dispatch.sh job-001 >> /Users/me/.aidevops/.agent-workspace/cron/job-001.log 2>&1 | ||
| ``` | ||
|
|
||
| ## Integration with OpenCode Server | ||
|
|
||
| The cron agent requires OpenCode server to be running: | ||
|
|
||
| ```bash | ||
| # Start server (recommended: use launchd/systemd for persistence) | ||
| opencode serve --port 4096 | ||
|
|
||
| # With authentication (recommended for security) | ||
| OPENCODE_SERVER_PASSWORD=your-secret opencode serve --port 4096 | ||
| ``` | ||
|
|
||
| ### Persistent Server Setup (macOS) | ||
|
|
||
| Create `~/Library/LaunchAgents/com.aidevops.opencode-server.plist`: | ||
|
|
||
| ```xml | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
| <plist version="1.0"> | ||
| <dict> | ||
| <key>Label</key> | ||
| <string>com.aidevops.opencode-server</string> | ||
| <key>ProgramArguments</key> | ||
| <array> | ||
| <string>/usr/local/bin/opencode</string> | ||
| <string>serve</string> | ||
| <string>--port</string> | ||
| <string>4096</string> | ||
| </array> | ||
| <key>EnvironmentVariables</key> | ||
| <dict> | ||
| <key>OPENCODE_SERVER_PASSWORD</key> | ||
| <string>your-secret-here</string> | ||
| </dict> | ||
| <key>RunAtLoad</key> | ||
| <true/> | ||
| <key>KeepAlive</key> | ||
| <true/> | ||
| <key>StandardOutPath</key> | ||
| <string>/tmp/opencode-server.log</string> | ||
| <key>StandardErrorPath</key> | ||
| <string>/tmp/opencode-server.err</string> | ||
| </dict> | ||
| </plist> | ||
| ``` | ||
|
|
||
| Load with: `launchctl load ~/Library/LaunchAgents/com.aidevops.opencode-server.plist` | ||
|
|
||
| ### Persistent Server Setup (Linux) | ||
|
|
||
| Create `~/.config/systemd/user/opencode-server.service`: | ||
|
|
||
| ```ini | ||
| [Unit] | ||
| Description=OpenCode Server | ||
| After=network.target | ||
|
|
||
| [Service] | ||
| Type=simple | ||
| ExecStart=/usr/local/bin/opencode serve --port 4096 | ||
| Environment=OPENCODE_SERVER_PASSWORD=your-secret-here | ||
| Restart=always | ||
| RestartSec=10 | ||
|
|
||
| [Install] | ||
| WantedBy=default.target | ||
| ``` | ||
|
|
||
| Enable with: `systemctl --user enable --now opencode-server` | ||
|
|
||
| ## Use Cases | ||
|
|
||
| ### Daily Reports | ||
|
|
||
| ```bash | ||
| cron-helper.sh add \ | ||
| --schedule "0 9 * * *" \ | ||
| --task "Generate daily SEO performance report. Check rankings, traffic, and indexation status. Save to ~/reports/seo-$(date +%Y-%m-%d).md" \ | ||
| --name "daily-seo-report" \ | ||
| --notify mail | ||
| ``` | ||
|
|
||
| ### Health Checks | ||
|
|
||
| ```bash | ||
| cron-helper.sh add \ | ||
| --schedule "*/30 * * * *" \ | ||
| --task "Check deployment health for production servers. Verify SSL, response times, and error rates. Alert if issues found." \ | ||
| --name "health-check" \ | ||
| --timeout 120 | ||
| ``` | ||
|
|
||
| ### Automated Maintenance | ||
|
|
||
| ```bash | ||
| cron-helper.sh add \ | ||
| --schedule "0 3 * * 0" \ | ||
| --task "Run weekly maintenance: prune old logs, consolidate memory, clean temp files. Report summary." \ | ||
| --name "weekly-maintenance" \ | ||
| --workdir "~/.aidevops" | ||
| ``` | ||
|
|
||
| ### Content Publishing | ||
|
|
||
| ```bash | ||
| cron-helper.sh add \ | ||
| --schedule "0 8 * * 1-5" \ | ||
| --task "Check content calendar for today's scheduled posts. Publish any ready content to WordPress and social media." \ | ||
| --name "content-publisher" \ | ||
| --workdir "~/projects/blog" | ||
| ``` | ||
|
|
||
| ## Notification via Mailbox | ||
|
|
||
| When `--notify mail` is set, results are sent to the inter-agent mailbox: | ||
|
|
||
| ```bash | ||
| # Check for cron job results | ||
| mail-helper.sh check --type status_report | ||
|
|
||
| # Results include: | ||
| # - Job ID and name | ||
| # - Execution time | ||
| # - Success/failure status | ||
| # - AI response summary | ||
| # - Any errors encountered | ||
| ``` | ||
|
|
||
| ## Troubleshooting | ||
|
|
||
| ### Job Not Running | ||
|
|
||
| ```bash | ||
| # 1. Check crontab entry exists | ||
| crontab -l | grep cron-dispatch | ||
|
|
||
| # 2. Verify job is active (not paused) | ||
| cron-helper.sh list | ||
|
|
||
| # 3. Check cron daemon is running | ||
| pgrep cron || sudo service cron start | ||
| ``` | ||
|
|
||
| ### OpenCode Server Issues | ||
|
|
||
| ```bash | ||
| # 1. Check server is running | ||
| curl http://localhost:4096/global/health | ||
|
|
||
| # 2. Check authentication | ||
| curl -u admin:your-password http://localhost:4096/global/health | ||
|
|
||
| # 3. View server logs | ||
| tail -f /tmp/opencode-server.log | ||
| ``` | ||
|
|
||
| ### Permission Issues | ||
|
|
||
| ```bash | ||
| # Ensure scripts are executable | ||
| chmod +x ~/.aidevops/agents/scripts/cron-*.sh | ||
|
|
||
| # Check log directory permissions | ||
| ls -la ~/.aidevops/.agent-workspace/cron/ | ||
| ``` | ||
|
|
||
| ## Security Considerations | ||
|
|
||
| 1. **Server authentication**: Always use `OPENCODE_SERVER_PASSWORD` for network-exposed servers | ||
| 2. **Task validation**: Jobs only execute pre-defined tasks from `cron-jobs.json` | ||
| 3. **Timeout limits**: All jobs have configurable timeouts to prevent runaway sessions | ||
| 4. **Log rotation**: Old logs are automatically pruned (configurable retention) | ||
| 5. **Credential isolation**: Tasks inherit environment from cron, not from config files | ||
|
|
||
| ## Related Documentation | ||
|
|
||
| - `tools/ai-assistants/opencode-server.md` - OpenCode server API | ||
| - `mail-helper.sh` - Inter-agent mailbox for notifications | ||
| - `memory-helper.sh` - Cross-session memory for task context | ||
| - `workflows/ralph-loop.md` - Iterative AI development patterns |
There was a problem hiding this comment.
Replace inline examples with file:line references and pointers.
This doc embeds long inline command/config/service blocks. The docs standard here is progressive disclosure with references to authoritative implementation lines.
Please replace large inline blocks with:
- References to script help/implementation (e.g.,
file:.agent/scripts/cron-helper.sh:613-672,file:.agent/scripts/cron-dispatch.sh:190-295). - Pointers to dedicated templates (if you want to keep launchd/systemd examples, place them in repo templates and reference via
file:...).
As per coding guidelines: “.agent/**/*.md: Apply progressive disclosure pattern by using pointers to subagents rather than including inline content in agent documentation. Include code examples only when authoritative; use file:line references to point to actual implementation instead of inline code snippets.”
🤖 Prompt for AI Agents
In @.agent/tools/automation/cron-agent.md around lines 19 - 395, The doc
currently embeds large inline command/config/service blocks; replace those
blocks with progressive-disclosure references to the authoritative
implementations by pointing to the actual script lines (e.g.,
file:.agent/scripts/cron-helper.sh:613-672 and
file:.agent/scripts/cron-dispatch.sh:190-295) and move the launchd/systemd
plist/unit examples into repository templates (e.g.,
tools/templates/opencode-server-launchd.plist and
tools/templates/opencode-server.service) then reference them as file:...; ensure
the README keeps short usage snippets only when authoritative and remove long
inline blocks in favor of file:line pointers throughout the cron-agent.md (List
Jobs, Add Job, Debug, Persistent Server Setup sections) so readers are directed
to the implementation and template files.


Summary
@cron) for managing scheduled AI agent dispatch via OpenCode servercron-helper.shfor job management (list, add, remove, pause, resume, logs, debug, status)cron-dispatch.shfor executing jobs via OpenCode server APIChanges
.agent/tools/automation/cron-agent.md.agent/scripts/cron-helper.sh.agent/scripts/cron-dispatch.sh.agent/subagent-index.toonTODO.mdFeatures
Testing
Related
opencode serve)mail-helper.sh,memory-helper.shSummary by CodeRabbit