# Automation

> Create and manage scheduled automations. Use when the user wants something to happen automatically on a schedule (e.g. 'send me a daily report at 16:00', 'check the weather every morning', 'warn me about thunderstorms every hour', 'tell me when my train is delayed').

- Skill: `cutec-chris/automation` (Agent Skill)
- Install (CLI): `npx skillmds@latest add cutec-chris/automation`
- Raw SKILL.md: https://api.skillmd.com/api/skills/cutec-chris/automation/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Docs & Writing
- License: MIT
- Author: cutec-chris (https://skillmd.com/u/cutec-chris)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/cutec-chris/automation

---


# Automation

Creates scheduled automations the scheduler runs at the given times.

**Default to a script.** An automation is a deterministic script the scheduler
executes. The script decides for itself whether there is anything worth saying:
when nothing is up, it prints nothing and the user hears nothing; when something
*is* up, it prints the message (that text becomes the notification). This is the
key behaviour for monitoring jobs (thunderstorm alert, train-delay watch) — they
must stay **silent on a quiet day** instead of pinging "all ok" every hour.

The LLM is a *step inside the script* (via the harness `llm_call()`), used only
when there is genuinely something to phrase or curate — never the thing that runs
every tick and decides whether to notify.

A bare natural-language `--instruction` (LLM every tick, always replies) still
exists for trivial one-off jobs, but is the exception, not the default.

## How a job stays silent

`notify` modes (set via the CLI flags below):

- `output_only` *(default for `--script`)* — deliver only when the script prints
  something. Empty output → nothing sent. **This is what makes monitors silent.**
- `True` *(default for `--instruction`)* — always deliver (empty becomes "erledigt").
- `error` — deliver only when the job fails.
- `False` — never deliver on success.

Failures (script exits non-zero / raises) are **always** surfaced, in every mode —
a broken monitor must not fail silently.

A script reports by printing to stdout. To stay silent it simply prints nothing
(or the marker `PAWLIA_SILENT`).

## The harness

Job scripts import `pawlia.automation_harness` (always importable inside a job;
the scheduler puts it on `PYTHONPATH`):

```python
from pawlia.automation_harness import get_params, emit, silent, llm_call, log

get_params() -> dict      # the job's --params, e.g. {"city": "Magdeburg"}
emit(text)                # report text → becomes the notification (empty = silent)
silent()                  # say nothing (documents intent; a no-op)
log(msg)                  # diagnostics to stderr (never sent to the user)
llm_call(prompt, system=None, model=None) -> str   # one robust LLM call, retries
```

Rules: do the deterministic check first; call `emit()` only when there is
something to report; use `llm_call()` sparingly; let exceptions propagate so
failures are loud.

## Two patterns

### A — Silent monitor (Gewitter / Bahn): gate, mostly silent, little/no LLM

```python
#!/usr/bin/env python
from pawlia.automation_harness import get_params, emit, silent, log
import urllib.request, json

p = get_params()
city = p.get("city", "Magdeburg")
# ... fetch forecast from a weather API ...
data = json.loads(urllib.request.urlopen(f"https://api.example/forecast?q={city}").read())

if not data.get("thunderstorm"):
    silent()                      # quiet day → nothing sent
else:
    emit(f"⛈ Gewitterwarnung {city}: {data['summary']} ab {data['start']}")
```

### B — LLM-curated digest (Morgenbericht): gather, then let the LLM shape it

```python
#!/usr/bin/env python
import sys
from pawlia.automation_harness import get_params, emit, llm_call

messages = collect_all_messages()         # deterministic data gathering
if not messages:
    sys.exit(0)                           # nothing to report → silent
report = llm_call(
    system="Du bist ein knapper Nachrichten-Kurator. Fasse zusammen, gruppiere, priorisiere.",
    prompt="Erstelle einen aufgeräumten Morgenbericht aus:\n\n" + "\n".join(messages),
)
emit(report)
```

## Building a job

### Which job type? Monitor vs. one-shot

Pick the kind that matches the silence rule — *before* you build the script:

| Job kind | Stays silent when there is nothing to say? | Use |
|----------|--------------------------------------------|-----|
| **Monitor** (cyclic check: thunderstorm, train delay, inbox watch) | Yes — the script decides; empty stdout = no notification | script in `workspace/skills/scripts/<name>.py`, register with `add-job --script` |
| **One-shot** (must produce output every tick: morning digest, daily summary) | No — the LLM is invoked every run, the response is always delivered | `add-job --instruction` (or a script that always emits) |

Rule of thumb: "tell me only when something is up" → script. "give me an
update every morning" → instruction.

### Step 1 — build the script (use skill-creator for anything non-trivial)

A trivial gate you can write inline → write it to
`workspace/skills/scripts/<name>.py`. Anything with API calls, parsing, or
LLM curation → ask skill-creator to build it (it knows the harness skeleton
and will test the script before you register it):

```
Call skill-creator with: "Baue ein Automations-Skript das [task]. Nutze das
pawlia.automation_harness (get_params/emit/silent/llm_call) und bleib still wenn
nichts zu melden ist."
```

The script must live under `workspace/skills/scripts/` (the primary path the
scheduler resolves job scripts from; `workspace/.scripts/` and `automations/`
still resolve as legacy fallbacks for older jobs).

### Step 2 — register the job

For a monitor (script that may stay silent):

```bash
python <scripts_dir>/../organizer/scripts/organizer.py add-job \
  --name "<descriptive name>" \
  --schedule "<schedule>" \
  --script "<name>.py" \
  --params '{"city":"Magdeburg"}'
```

For a one-shot that always delivers output:

```bash
python <scripts_dir>/../organizer/scripts/organizer.py add-job \
  --name "<name>" --schedule "<schedule>" --instruction "<instruction>"
```

**Notify flags** (override the per-kind default):
- `--notify-on-output` — deliver only when there is output (silent on empty). Default for `--script`.
- `--notify-on-error` — deliver only on failure.
- `--no-notify` — never deliver on success (failures still surface).

**Schedule formats:**
- `"16:00"` — daily at 16:00
- `"interval:5m"` — every 5 minutes (m/h/d)
- `"interval:1h"` — every hour
- `"weekly:0:09:00"` — weekly Monday 09:00 (0=Mon..6=Sun)
- `"monthly:1:10:00"` — monthly on the 1st at 10:00
- `"30 8 * * 1,3"` — cron syntax (5 space-separated fields)

### Managing existing jobs

```bash
python <scripts_dir>/../organizer/scripts/organizer.py list-jobs
python <scripts_dir>/../organizer/scripts/organizer.py delete-job --job-id "<id>"
python <scripts_dir>/../organizer/scripts/organizer.py toggle-job --job-id "<id>"
```

### Manual trigger (for testing)

```bash
python <scripts_dir>/../organizer/scripts/organizer.py run-job --job-id "<id>"
```

Flags the job for immediate execution on the next scheduler tick (within 60s).
Use this after building/modifying the script to verify the job behaves: confirm
the silent case sends nothing and the alert case sends exactly one message.

## Working rules

- Before each bash call, output a short status line (e.g. "Registriere Job …", "Teste Script …") so the runner sees progress between steps.
- Build+Register is one continuous flow: decide type → (for monitors) build and test the script → register. Do not stop to ask for confirmation between steps.
- For monitors, always verify the silent case works after registering: run `run-job` and confirm no notification is sent when the script prints nothing.

## Output

After registering, confirm to the user:
- What the automation does and whether it's a script or an instruction
- How often it runs (the schedule)
- That it stays silent unless there is something to report (for monitors)
- If a skill/script was built: mention its name
- That they can list, disable, delete, or manually trigger it later

