{"slug": "build-your-own-personal-ops-system-starter-prompt-job-ideas", "title": "Build your own personal-ops system: starter prompt + job ideas", "summary": "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.", "body_md": "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.\n\nTwo files here:\n\nstarter-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.\n\nideas.md — a list of jobs the system is good at, grouped by shape.\n\nWhy not just Claude's scheduled tasks?\n\nClaude 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:\n\nState between runs. Diffing against yesterday, \"proposed once, never re-nagged\", aged ledgers, open loops that close themselves. Scheduled tasks start cold every time.\n\nLocal CLIs, files and databases.git status across your repos, op, brew, Claude's own transcripts, the OS activity database, SQLite stores of local apps.\n\nPure-code jobs. Most of the ideas list never calls a model at all.\n\nThe quota gate. Subscription limits are only readable from a local session; the gate decides whether tonight's run can afford to happen.\n\nSo 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.\n\nPaste into a fresh Claude Code session in an empty directory.\n\nI 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.\n\nStart 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.\n\nThe shape of the system\n\nOne daemon, one directory per job, one dashboard.\n\n```\nlaunchd / systemd\n  └─ the daemon (KeepAlive)\n       ├─ scheduler   ticks every ~30s, matches each job's cron spec, catches up misses\n       ├─ runner      spawns jobs/<name>/run.sh, captures the log, records the outcome, notifies\n       └─ http        127.0.0.1 only — dashboard + local API\n```\n\nOne binary/entrypoint. daemon is the long-lived process; list, run, logs, pause 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.\n\nThe 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.\n\nAdding a job = one directory, three files, no registry\n\njobs/<name>/job.env — shell-sourceable manifest, so run.sh sources it and the daemon parses the same file. One declaration, not two that drift:\n\n```\nJOB_NAME=\"my-job\"            # must match the directory name\nJOB_DESCRIPTION=\"What it does\"\nSCHEDULE=\"0 8 * * *\"         # five-field cron, local time\nJOB_GATED=0                  # 1 = check the Claude quota gate first\nJOB_MODEL=\"claude-sonnet-5\"\nJOB_TIMEOUT=1800\nCATCHUP_WINDOW=\"12h\"\nARTIFACT_GLOB=\"*.md\"         # what the dashboard lists and will serve\n```\n\njobs/<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.\n\njobs/<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.\n\nOutput never lives in the repo. Artifacts, logs, ledger and state go under ~/ops/<job>/.\n\nThe outcome contract (the most important part)\n\nExit 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:\n\n```\noutcome=ok | warning | no-change | gated | failed\nheadline=One line, the finding itself, under ~110 chars\nartifact=2026-08-11.md\ndetail=A sentence of context\n```\n\nOutcome\n\nMeaning\n\nNotifies?\n\nok\n\nran, produced something worth seeing\n\nyes\n\nwarning\n\nran, output has a defect worth flagging\n\nyes\n\nno-change\n\nran fine, nothing worth reporting\n\nno\n\ngated\n\nnever ran — quota threshold\n\nno\n\nfailed\n\ncrashed, timed out, produced nothing\n\nyes\n\nno-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.\n\nKeep 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.\n\nNotifications\n\nNative 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.\n\nDashboard\n\nLocal 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.\n\nJobs that call Claude\n\nTwo rules that decide whether this is safe to leave running unattended:\n\nPure code by default. A job uses a model only when judgment is the feature. \"Could be a grep\" means it is a grep.\n\nUntrusted 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.)\n\nFor 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.\n\nQuota 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.\n\nWrite 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.\n\nWhat belongs here\n\nA 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.\n\nBuild order\n\nDaemon + scheduler + runner + ledger + outcome contract, with tests for cron matching and catch-up.\n\ninstall-daemon (launchd/systemd), make reload that rebuilds and restarts without a window where the schedule is unloaded.\n\nDashboard (reading list + job pages) and notifications.\n\nOne 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).\n\nQuota gate + one gated Claude job.\n\nA /new-job skill in .claude/skills/ that scaffolds the three files and walks the checklist.\n\nWrite 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.", "url": "https://wpnews.pro/news/build-your-own-personal-ops-system-starter-prompt-job-ideas", "canonical_source": "https://gist.github.com/MiniCodeMonkey/fb2a31a45689b027acf5b969ad1d69ca", "published_at": "2026-08-26 18:13:13+00:00", "updated_at": "2026-08-26 18:45:05.541604+00:00", "lang": "en", "topics": ["developer-tools", "ai-tools", "ai-agents"], "entities": ["Claude", "Claude Code", "Claude Desktop"], "alternates": {"html": "https://wpnews.pro/news/build-your-own-personal-ops-system-starter-prompt-job-ideas", "markdown": "https://wpnews.pro/news/build-your-own-personal-ops-system-starter-prompt-job-ideas.md", "text": "https://wpnews.pro/news/build-your-own-personal-ops-system-starter-prompt-job-ideas.txt", "jsonld": "https://wpnews.pro/news/build-your-own-personal-ops-system-starter-prompt-job-ideas.jsonld"}}