cd /news/developer-tools/build-your-own-personal-ops-system-s… Β· home β€Ί topics β€Ί developer-tools β€Ί article
[ARTICLE Β· art-112224] src=gist.github.com β†— pub= topic=developer-tools verified=true sentiment=Β· neutral

Build your own personal-ops system: starter prompt + job ideas

A developer has shared a starter prompt and job ideas for building a personal-ops system: a local platform that runs scheduled jobs, stores their output, and notifies the user when something needs attention. The system uses a daemon with a scheduler, runner, and local HTTP dashboard, and jobs are defined as directories with a shell-sourceable manifest. The developer emphasizes that jobs belong in this system only if they require local state, local tools, or local data, and provides a starter prompt for Claude Code to scaffold the foundation.

read7 min views1 publishedAug 26, 2026

A small platform on your own machine that runs scheduled jobs for you β€” research memos, watching things, weekly reviews, self-observation β€” stores their output, and tells you when something is worth looking at. Some jobs call Claude via the claude CLI; many are plain code.

Two files here:

starter-prompt.md β€” paste into a fresh Claude Code session in an empty directory. It carries the design decisions (job layout, outcome contract, notifications, security model, build order) without prescribing your OS, language, or jobs.

ideas.md β€” a list of jobs the system is good at, grouped by shape.

Why not just Claude's scheduled tasks?

Claude Desktop already runs prompts on a schedule. If that covers your job, use it β€” it's zero infrastructure. This system exists for what scheduled tasks can't do:

State between runs. Diffing against yesterday, "proposed once, never re-nagged", aged ledgers, open loops that close themselves. Scheduled tasks start cold every time.

Local CLIs, files and databases.git status across your repos, op, brew, Claude's own transcripts, the OS activity database, SQLite stores of local apps.

Pure-code jobs. Most of the ideas list never calls a model at all.

The quota gate. Subscription limits are only readable from a local session; the gate decides whether tonight's run can afford to happen.

So the rule: a job belongs here only if it needs local state, local tools, or local data. If a cloud task could do it equally well, it doesn't belong.

Paste into a fresh Claude Code session in an empty directory.

I want to build a personal-ops system: a small platform on my own machine that runs scheduled jobs for me (research memos, watching things, weekly reviews, self-observation), stores their output, and tells me when something is worth looking at. Some jobs will call Claude via the claude CLI; many should be plain code. Help me build the foundation first, then the first job.

Start by asking me (in one round, not a back-and-forth): what OS I'm on, my preferred language for the daemon, the first two or three jobs I actually want, and whether I have a Claude subscription (so the quota gate matters). Then design and build. Don't ask again unless you're actually blocked.

The shape of the system

One daemon, one directory per job, one dashboard.

launchd / systemd
  └─ the daemon (KeepAlive)
       β”œβ”€ scheduler   ticks every ~30s, matches each job's cron spec, catches up misses
       β”œβ”€ runner      spawns jobs/<name>/run.sh, captures the log, records the outcome, notifies
       └─ http        127.0.0.1 only β€” dashboard + local API

One binary/entrypoint. daemon is the long-lived process; list, run, logs, are thin clients over its local API, so the CLI and the dashboard's buttons drive the same code path and can't drift. Add run <job> --local to bypass the daemon for debugging.

The daemon owns cron, so it must own what the OS scheduler used to do for free: persist each job's last honoured fire and catch up missed runs on start and after sleep (detect it as a wall-clock jump). A missed fire runs once, only the most recent one, and only inside a per-job catch-up window β€” a laptop closed for a week must not wake into a stampede. Write a heartbeat every tick and show downtime on the dashboard; a dead scheduler looks exactly like a quiet week.

Adding a job = one directory, three files, no registry

jobs/<name>/job.env β€” shell-sourceable manifest, so run.sh sources it and the daemon parses the same file. One declaration, not two that drift:

JOB_NAME="my-job"            # must match the directory name
JOB_DESCRIPTION="What it does"
SCHEDULE="0 8 * * *"         # five-field cron, local time
JOB_GATED=0                  # 1 = check the Claude quota gate first
JOB_MODEL="claude-sonnet-5"
JOB_TIMEOUT=1800
CATCHUP_WINDOW="12h"
ARTIFACT_GLOB="*.md"         # what the dashboard lists and will serve

jobs/<name>/about.md β€” two or three paragraphs, shown on the job's dashboard page: what it does and how it works. Written for the version of me that has forgotten why the job exists.

jobs/<name>/run.sh β€” does the work. The runner passes it everything via env so it never guesses where output belongs: OPS_ROOT, OPS_JOB_DIR, OPS_ARTIFACT_DIR (~/ops/<name>), OPS_STATE_DIR (scratch that survives between runs), OPS_RESULT_FILE, OPS_RUN_ID, OPS_LOG_FILE, OPS_MODEL.

Output never lives in the repo. Artifacts, logs, ledger and state go under ~/ops/<job>/.

The outcome contract (the most important part)

Exit code is not enough β€” only the job knows what success means (zero new listings is a fine day; zero listings found is a broken scraper; both exit 0). So the job writes KEY=value to $OPS_RESULT_FILE:

outcome=ok | warning | no-change | gated | failed
headline=One line, the finding itself, under ~110 chars
artifact=2026-08-11.md
detail=A sentence of context

Outcome

Meaning

Notifies?

ok

ran, produced something worth seeing

yes

warning

ran, output has a defect worth flagging

yes

no-change

ran fine, nothing worth reporting

no

gated

never ran β€” quota threshold

no

failed

crashed, timed out, produced nothing

yes

no-change is the one people skip and shouldn't: notifying about a quiet day teaches me to ignore the app, and that's paid for by the notifications that matter. Two things a job is not trusted about: declaring ok while exiting non-zero is recorded as failed, and the result file is deleted before every run so a crash can't leave yesterday's verdict to be adopted today.

Keep an append-only run ledger per job (JSONL): run id, start/end, outcome, headline, artifact. Everything downstream (dashboard, notifications, a later "platform audit" job) reads from it.

Notifications

Native OS notifications. One notification group per job so re-posts replace rather than stack. A result is "unread" until I open it in the dashboard β€” opening is what marks it read; there is no button, because a button that has to be pressed is one that won't be. One re-post each morning for anything still unread. Once a day, not every two hours.

Dashboard

Local web UI, 127.0.0.1 only, never 0.0.0.0 β€” it serves a Run button. No login (single-user machine), but mutations are POST-only and require same-origin Origin/Sec-Fetch-Site so a website I'm visiting can't POST to my Run endpoint. Front page is a reading list: every artifact across every job, unread first. Per-job page: about.md, run strip (last N outcomes), schedule, next run, logs. Artifact paths are confined to the job's own directory and filtered to its ARTIFACT_GLOB so the ledger and state are never reachable.

Jobs that call Claude

Two rules that decide whether this is safe to leave running unattended:

Pure code by default. A job uses a model only when judgment is the feature. "Could be a grep" means it is a grep.

Untrusted text (emails, listings, web pages) only ever meets a one-shot, tool-less model call: claude -p ... --tools "", reply treated as data β€” parse, validate the shape, drop what fails. Never pipe stranger-written text into an agentic session that holds shell or MCP access. (--allowedTools only pre-approves; it removes nothing. --tools "" is what actually strips Bash/Write/Edit.)

For the jobs that do run an agentic session (e.g. nightly research over the web), the boundary is the OS sandbox, not the model's cooperation and not a list of blessed shell commands: deny the source repo, deny ~/.ssh and credentials, allow HTTPS. Set the sandbox to fail hard if it's unavailable β€” at 18:47 nobody is there to see a warning.

Quota gate. Subscription rate limits are only exposed to a Claude Code status-line command on stdin (rate_limits.five_hour / seven_day). Write a status-line script that snapshots them to disk, and a gate.sh that reads the snapshot: exit 0 go, 10 skip (gated), 1 broken. Gated jobs run it first. The whole design depends on not spending tomorrow's quota tonight.

Write a short docs/threat-model.md that ranks jobs by what happens if injected text is obeyed (output corruption β†’ state poisoning β†’ tool-carrying sessions β†’ acting sessions that write where other people see), and make its conventions binding for new jobs.

What belongs here

A job belongs on this machine only if it needs something that exists solely here: local working trees, Claude profiles and transcripts, the quota snapshot, locally authenticated CLIs/MCP servers, the OS's own databases, or state that accumulates between runs. Anything a cloud routine could do equally well is out of scope.

Build order

Daemon + scheduler + runner + ledger + outcome contract, with tests for cron matching and catch-up.

install-daemon (launchd/systemd), make reload that rebuilds and restarts without a window where the schedule is unloaded.

Dashboard (reading list + job pages) and notifications.

One pure-code job end to end (e.g. walk ~/projects for uncommitted/unpushed work every morning β€” it will be no-change most days, which is the point).

Quota gate + one gated Claude job.

A /new-job skill in .claude/skills/ that scaffolds the three files and walks the checklist.

Write a README as you go in the style of "things that cost real time to discover" β€” every silent failure you hit gets a paragraph. Commit after each step.

── more in #developer-tools 4 stories Β· sorted by recency
── more on @claude 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain β€” perfect for shipping the agent you just read about.

$git push zahid main
β†’ Live at https://your-agent.zahid.host βœ“
Get free account β†’ Pricing
from €0/mo Β· no card required
LIVE [news/build-your-own-perso…] indexed:0 read:7min 2026-08-26 Β· β€”