Docs
Aromba — Chores
A chore is one file: a header that describes it and a script that does it. The engine reads the header to decide when and how it may run. Nothing runs without a valid header.
The format is meant to carry any recurring or reactive task on a machine — cleaning is just the first use. A chore can run on a schedule, on an event, on a threshold, on demand, or when another chore asks for it.
# chores/sweep/old-downloads.sh
# ---
# name: Old Downloads
# verb: sweep # sweep | fix | prepare | guard | watch | report | run
# os: linux, mac
# safety: yellow # green | yellow | red
# touches: files in ~/Downloads older than {days} days
# needs: find
# trigger: night # night | often | hourly | daily | on-demand | event:<name> | after:<chore>
# params: days=30 path=~/Downloads
# default: off
# author: core # core | community | assistant
# ---
set -euo pipefail
if [ "${AROMBA_DRY_RUN:-0}" = "1" ]; then
find "$P_path" -type f -mtime +"$P_days" | wc -l
exit 0
fi
n=0
while IFS= read -r f; do
mkdir -p "$AROMBA_QUARANTINE$(dirname "$f")" && mv "$f" "$AROMBA_QUARANTINE$f" && n=$((n + 1))
done < <(find "$P_path" -type f -mtime +"$P_days")
echo "moved=$n" >> "$AROMBA_RESULT"
echo "Old Downloads: moved $n files to quarantine" >> "$AROMBA_RECEIPT"
| Field | Meaning |
|---|
name | Shown in Telegram and dashboard. Sentence case, 2–4 words |
verb | Category. sweep cleans, fix maintains, prepare gets things ready, guard backs up, watch checks and alerts, report writes summaries, run executes a user job (the cron replacement) |
os | Where it can run |
safety | See colors below |
touches | Plain-language list of what it changes, with {param} placeholders filled in when shown to the user |
needs | Binaries that must exist. Missing → chore is hidden, not failed |
asks | Optional. Read-only questions the Warden answers before the run, from its allow list (05 §5): asks: systemctl --failed --plain --no-legend, apt list --upgradable. Each answer is a file, $AROMBA_ASK/1, /2… A question the Warden refuses or that fails refuses the run; a question naming a command the host lacks hides the chore, like needs |
trigger | When it may run. See triggers below |
params | Editable knobs with defaults. Exposed as $P_<name> in the script. Edited via /chores or config file |
default | on only for green core chores that are safe on every server |
author | Who wrote it. assistant chores are always red and need the owner’s approval on every run |
timeout | Optional. Seconds, default 600 |
Triggers
| Trigger | Runs when |
|---|
night | Quiet window (default 03:00–05:00 server time) and load is low |
often | Every five minutes, green watchers only: nothing that writes runs that often, and an hour is too long to not know a service stopped answering (PLAN D117) |
hourly, daily | On the clock, load permitting |
on-demand | Only from /run <chore> or the assistant |
event:<name> | The engine sees the event: boot, disk-85, disk-95, service-failed, ssh-bruteforce, deploy (a file touched in a watched folder), login, low-memory; and, raised by the skills that watch them, container-down (a container stopped or restarting in a loop: docker), site-down (uptime), cert-expired (nginx, caddy) |
after:<chore> | Right after another chore finishes; reads its AROMBA_RESULT as $PREV_* |
Events are emitted by watchers (below) and by the engine itself. A chore can list several triggers: trigger: night, event:disk-95.
Environment the engine provides
| Variable | Meaning |
|---|
AROMBA_DRY_RUN=1 | Report what would happen, change nothing. Every chore must support it |
AROMBA_RESULT | Append key=value lines: freed=, moved=, updated=, alert=, event=, do= (below), or anything custom |
AROMBA_ASK | Folder holding the answers to the header’s asks, one file per question, in order |
AROMBA_RECEIPT | Append one human line per action. This is what the user reads |
AROMBA_QUARANTINE | Folder for yellow chores to move files into (/var/lib/aromba/quarantine/<date>/), each under its own full path — …/<date>/home/james/Downloads/x.zip — which is how the Warden knows where a file goes back to. After the run the Warden indexes what arrived; only a regular file that can have come from the chore’s own touches is indexed, and only an indexed file can be restored |
AROMBA_HOME | Home of the user whose caches are being cleaned |
P_<name> | Each param from the header |
PREV_<key> | Results of the previous chore when triggered with after: |
2. Safety colors
| Color | Rule | Examples |
|---|
| Green | Runs alone. Touches only what can be regenerated with no user effort, or reads only | Docker prune, apt cache, journal vacuum, all watchers |
| Yellow | Never deletes. Moves to quarantine for 30 days, then deletes. One-tap restore | old Downloads, old logs beyond rotation, node_modules in dead projects |
| Red | Asks in Telegram every time. Shows exactly what it will do, with params filled in | security updates, install fail2ban, restart a service, any run chore, anything from assistant |
Promotion is manual: a community chore starts red; a maintainer can move it to yellow or green after review. An assistant-written chore stays red until the user saves it with /keep, and even then stays red.
3. Rules the engine enforces before running
- Header complete and valid. Missing field → chore skipped and logged.
- Dry-run supported: the engine runs every chore once with
AROMBA_DRY_RUN=1 before its first real run and shows the user the result.
- Idempotent: running twice in a row changes nothing the second time.
- One result line minimum in
AROMBA_RESULT, one human line in AROMBA_RECEIPT. Silent chores are reported as “ran, said nothing”.
- Fails loudly: non-zero exit on unexpected state. Engine reports the failure in the receipt, retries once next night, then disables the chore and tells the user.
- Time cap:
timeout, default 10 minutes.
- Load guard: scheduled chores don’t start if the 5-minute load average is above the core count, and pause if it goes over during the run. Event chores ignore the guard, and so do the watchers: one on
often (PLAN D122), and one that only reads — green, verb: watch, no path in touches, the test prod mode uses (PLAN D206) — whatever its trigger. The load watcher is hourly, and a guard that held it stopped the one chore that reports high load (PLAN D208). The calendar’s hold (PLAN D107) lets the same watchers through.
needs present, os matches, color rules respected. Red chores never run without a fresh approval (valid for one run, expires after 24 h). Approval message shows the exact params.
- Chaining depth:
after: chains stop at 5 links. A chore can’t trigger itself.
- systemd and apt are never reached from inside the cage: their sockets live under
/run, which the cage hides (05 §3). A chore asks (the asks header) and, after a clean real run, may write do=<command> result lines for the few commands the Warden runs on its behalf outside the cage, each judged on its own and each a receipt line: systemctl restart <unit>, timedatectl set-ntp true, apt-get install …, docker image prune -f and docker builder prune -f (PLAN D230). A red chore may ask for any of them — its approval is the button 05 §5 gives such commands; a green chore only for time sync’s two. Nothing runs after a dry run or a failed run. The plain files /run/reboot-required and /run/reboot-required.pkgs are visible inside the cage.
4. Watchers
A watcher is a chore with verb: watch, usually trigger: hourly — often for the few that must notice within minutes. Its two first dry runs must both succeed, but they need not print the same thing: a watcher measures, and a measurement differs (PLAN D124; the pair’s agreement rule is for chores that change something). A question in asks: may name one of the chore’s own params as <name> — docker logs --tail 200 <container> — and the Warden fills it from the run’s values before it checks the command against its list, so what the policy sees is what will run; a param left empty leaves that question unasked and its answer file empty (PLAN D120). A watcher is given its own last result as PREV_* (PLAN D117): half of what one is asked to notice is a change — two failures in a row, an error rate against the hour before, a feed that stopped moving — and a chore has no memory of its own between runs. It writes alert=<text> when something is wrong and event=<name> to fire an event, nothing otherwise. Beside an alert it may also say what it measured — value=<number>, line=<the number it compared against> and metric=<a health metric's name, e.g. disk_pct> — and when it does, the alert is weighed before it is sent: how far past its own line, and how far from normal for this server, decide whether the phone rings, whether the message waits quietly in the chat, or whether it is one line in the receipt (03 §6, PLAN D115). A watcher that says none of them is weighed on its history alone. The engine sends an alert at most once per 24 h per watcher unless the value gets worse. Watchers never fix anything. In phase 3 an alert can be handed to the assistant, which is what makes the AI cheap: it only wakes up when a watcher says so.
5. run chores — the cron replacement
A run chore wraps any user job: a backup script, a scraper, a build, a report generator. What Aromba adds over cron: dry-run, timeout, load guard, receipt line, failure retry and alert, and the assistant can look at a failure. Always red on first run; the user can lower it to green for their own scripts with /chores → “trust this”. Trust is for the script as it is and the values it runs with — a run chore’s command and folder are its params — so a changed file or a changed value asks again; once trusted, it runs on its schedule like a green chore, outside the first week and prod mode.
# ---
# name: Nightly scraper
# verb: run
# os: linux
# safety: red
# touches: runs /home/james/scraper/run.sh
# needs: bash
# trigger: night
# params: cmd=/home/james/scraper/run.sh
# default: off
# author: mine
# ---
6. Catalog
Phase 2 ships the chores marked ★: the system skill in full (OS level only, 13-skills.md) and the minimum of each phase-2 skill; the skill in parentheses owns the chore, no parentheses means system. Everything else is the target catalog for phases 2–4, in rough priority order inside each verb. Skills for other audiences are not listed here; they arrive through the skill catalog (see 06-architecture.md).
sweep — clean regenerable or stale things
| Name | Color |
|---|
| ★ | Docker cleanup (dangling images, build cache, stopped containers older than N days) (docker) | green |
| ★ | Apt cache cleanup | green |
| ★ | Journal vacuum (keep N days) | green |
| ★ | Temp folders (/tmp, /var/tmp) older than N days | green |
| ★ | Language caches: npm, pnpm, yarn (node); pip (python); cargo (rust); go later | green |
| ★ | Old log files beyond rotation | yellow |
| ★ | Dead project node_modules (untouched N days) (node) | yellow |
| Old Downloads (older than N days) — the format example in section 1; a Mac chore (mac, phase 6) | yellow |
| Old snapshots, .bak, .orig, editor swap files | yellow |
| Rotated but never deleted logs (*.gz older than N days) | yellow |
| Orphaned Docker volumes | red |
| Old kernels | red |
| Crash dumps and core files | yellow |
| Old Python virtualenvs in dead projects (python) | yellow |
| Mac only: Xcode DerivedData, iOS simulators, Homebrew cleanup, AI model caches (HF, Ollama, LM Studio), old screenshots, Trash older than N days | green/yellow |
fix — keep the system healthy
| Name | Color |
|---|
| ★ | Security updates | red |
| ★ | Install fail2ban with sane defaults | red |
| ★ | Restart failed services (systemd units; containers are the docker skill’s) | red |
| ★ | Time sync check and fix: timedatectl set-ntp true and a restart of chrony or timesyncd, never a manual date | green |
| All updates (not just security) | red |
| Reboot if required, in the night window | red |
| Renew TLS certificates (certbot) and alert 14 days before expiry | green (renew) / watch |
| Docker: restart containers stuck in restart loop, with backoff | red |
| Log rotation setup for apps that don’t have it | red |
| Swap file if none and memory < 2 GB | red |
| Fix common permission mistakes in web roots | red |
| Homebrew / pip / npm global upgrades (Mac) | red |
prepare — have things ready before you sit down
| Name | Color |
|---|
| git fetch --prune in all repos under a folder | green |
| Pull active repos (fast-forward only) and run install when lockfile changed | red |
| Pre-pull Docker images referenced in compose files | green |
| Run the test suite of a project, leave results | red (first), green (trusted) |
| Warm caches / preload models | green |
| Queue a Claude Code task to run at night (claude -p with a prompt file) | red |
guard — backups and safety copies
| Name | Color |
|---|
| ★ | Postgres / MySQL dump to local folder, keep N (postgres, mysql) | green |
| Copy dumps off-server with rclone (S3, B2, GDrive) | red (first), green (trusted) |
| Dotfiles and app configs snapshot, encrypted with a passphrase | red |
| Verify last backup is restorable (test restore into temp DB) | green |
| Snapshot a folder before a red chore touches it | green |
watch — look and alert, never touch
| Name | Color |
|---|
| ★ | Disk usage: the root, the fullest filesystem that is not the root’s, and the inode table of both (alert at N%) | green |
| ★ | Failed logins / brute force | green |
| ★ | Service health: failed units; restarting containers (docker) | green |
| ★ | Memory and swap pressure | green |
| ★ | Load average sustained above N | green |
| ★ | Pending updates watcher (daily; the reboot-required flag too) | green |
| Website up-check (HTTP status and response time for URLs you list) | green |
| TLS certificate expiry | green |
| Open ports changed since last check | green |
| New sudo users, changed authorized_keys | green |
| Process gone (a named process you expect to run) | green |
| Log pattern (regex you give, e.g. “OutOfMemory”) | green |
| Cron job missed (a job you list didn’t run on time) | green |
| Domain expiry (WHOIS) | green |
| Large file appeared (single file over N GB) | green |
report — write things down
| Name | Color |
|---|
| ★ | Morning receipt — built into the daemon (journal package), shown in /chores as a built-in row, not a chore file | green |
| Weekly summary: trends for disk, alerts, uptime | green |
| ★ | Inventory: what’s installed, versions, listening ports, cron jobs (weekly, read by the assistant) — built into the daemon (inventory package) | green |
| Change log: what changed on the server since last week (packages, configs, users) | green |
run — your own jobs, supervised
| Name | Color |
|---|
| Any command or script on a schedule | red → green when trusted |
| Any command on an event (event:deploy → run migrations; event:disk-95 → emergency cleanup) | red |
| Chain: after backup → upload → verify → receipt line | inherits |
7. Folder layout
/opt/aromba/chores/
core/ the `system` skill's chores, shipped with the binary, updated with it
community/ installed with `aromba chore add <url>`
mine/ written by the user or the assistant, never overwritten by updates
Same file name in mine/ overrides core/. Skills install into /opt/aromba/skills/<name>/chores/ with the same format.