Skip to content

/followup, local future-check-in skill

URL: https://mkdocs.justinsforge.com/memory/general/reference_followup_skill/

Why it exists

Forge does not use /schedule (cloud-side Anthropic routine): cloud sandbox cannot read ~/.forge-secrets/, cannot SSH to Console, has no access to local services or forge tooling, and burns subscription quota for a strictly-worse experience than running locally on Console (always-on, systemd, cron, notify, secrets, all forge scripts). /followup is the canonical "do this later" surface on Forge.

Architecture

  • One persistent systemd timer (forge-followup-dispatcher.timer) fires every minute on Console and runs forge_followup.py dispatch.
  • Dispatch reads forge/data/followups.json, identifies pending entries whose scheduled_for_utc <= now, fires each via claude -p --model <model> --dangerously-skip-permissions.
  • /followup create is a pure file write: appends an entry to the registry. No sudo, no daemon-reload, no per-job systemd unit. Schedule changes are atomic JSON writes.
  • Recurring jobs re-compute their next fire time inside the dispatcher and remain status=pending.
  • One-shots flip to status=success or status=failed after firing and stay in the registry as history.

Files

Path Role
forge/scripts/forge_followup.py CLI: create, list, show, run, cancel, dispatch
forge/data/followups.json Registry, single source of truth for job state
forge/memory/followups/<slug>-YYYY-MM-DD.md Per-fire output, mkdocs-served
forge/scripts/systemd/forge-followup-dispatcher.timer systemd timer, fires every minute
forge/scripts/systemd/forge-followup-dispatcher.service systemd service, runs dispatch
forge/scripts/systemd/install-forge-followup.sh One-time installer (sudo)
forge/.claude/skills/followup/SKILL.md User-invocable skill wrapper

Registry schema

{
  "followups": {
    "<id>": {
      "id": "<12-hex>",
      "name": "doctrine-audit",
      "slug": "doctrine-audit",
      "prompt": "...",
      "schedule_type": "once|recurring",
      "schedule_spec": "in 1 week",
      "recurring_spec": null,
      "scheduled_for_utc": "2026-05-08T14:00:00+00:00",
      "scheduled_for_local": "2026-05-08 09:00 CT",
      "model": "claude-sonnet-4-6",
      "model_alias": "sonnet",
      "created_at_utc": "...",
      "status": "pending|running|success|failed|cancelled",
      "last_run_utc": null,
      "last_status": null,
      "last_output_file": null,
      "last_duration_sec": null
    }
  }
}

Atomic writes use tempfile + os.replace.

Notify message format

On fire (success or failure), pushes through forge_notify.sh:

Subject: Followup ✓ done: doctrine-audit         (or ✗ failed)
Priority: info on success, warning on failure
Body:
  Status: ✓ done (47s, claude-sonnet-4-6)
  Scheduled: 2026-05-01 07:45 CT (7d ago)
  Fired: 2026-05-08 09:00 CT

  Summary:
    <one-line summary the agent emitted via the ##SUMMARY## tag>

  Results: memory/followups/doctrine-audit-2026-05-08.md
  Deep-dive: paste path into @forge_remote_bridge_bot

Doctrine compliance (Section 9.5)

  • Robust over quick: queue-polled dispatcher survives reboots; atomic JSON writes; systemd unit with Restart-safe service shape; no transient state lost on Console restart.
  • Fail loud: any non-zero exit from claude -p, timeout, or empty output flips the job to failed and pushes a warning notify with error excerpt. Output file is always written even on failure.
  • Idempotent: each job has a uuid; same-day re-runs overwrite the same output file path; recurring jobs re-arm by computing next fire from now, not from the missed slot.
  • Test before claiming done: dispatcher verifies the output file exists and is non-empty before declaring success in the notify.
  • Single source of truth: data/followups.json is the only persistence; no per-job systemd units, no parallel state.

Time parsing

Supported <when> patterns (parsed in this order): 1. every <weekday>[ at] HH[:MM][am|pm] -> recurring 2. daily[ at] HH[:MM][am|pm] -> recurring 3. in N (min|h|d|w) -> one-shot relative 4. tomorrow [HH[:MM][am|pm]] -> one-shot, defaults to 9am 5. anything else -> dateutil fuzzy parse, CT-local if naive, must be future

Setup (one-time)

/home/justinwieb/forge/scripts/systemd/install-forge-followup.sh

Installs the timer + service to /etc/systemd/system/ and enables. Idempotent; safe to re-run.

Limitations

  • Minimum granularity is the dispatcher poll interval (1 minute). A job scheduled for 09:00:00 CT may fire 0-60s late.
  • Long-running jobs (max 30 min by default, set in RUN_TIMEOUT_SEC) tie up the dispatcher for that window. Concurrent due jobs run sequentially in one dispatch tick. Adjust TimeoutStartSec in the service unit if longer runs are needed.
  • Each fire bills subscription quota proportional to the prompt + reasoning. Use haiku for trivial checks, sonnet for normal work, opus only for real judgment passes.
  • Section 9.5 doctrine (engineering standards)
  • feedback_subscription_over_api_for_automation.md (decision tree: procedural → /spawn → /followup)
  • feedback_subscription_over_api_for_automation.md (cost decision tree)
  • reference_notify.md (downstream notify pipeline)

[Claude Code]