# Blueprint: Job-Runner

Version 1.0 · denzer.ai · Lizenz: frei nutzbar mit Quellenangabe

Regelmäßige Aufträge für deinen Agent, ausgelöst nach Zeitplan, ausgeführt im Hintergrund, mit Output an einen festen Ablageort. Mehr als ein Cronjob: jeder Job ist ein Markdown-Prompt mit klarem Skill und einer Datei pro Tag.

**Fallbeispiel.** Du willst sechs Dinge täglich oder wöchentlich automatisch laufen lassen:
- Morgenbriefing in deinem Chat
- Themen-Radar aus YouTube, X, Web
- Tägliches Backup
- Wochenlog-Konsolidierung
- AI-News-Scan
- Daily-Recap am Abend

Jeder Job liest seinen Prompt aus einem Ordner, ruft deinen Agent mit klarem Auftrag, legt das Ergebnis als Markdown ab. Du siehst pro Tag eine Übersicht, kannst Jobs ein- und ausschalten.

---

## Teil B — Für dich (Operator)

### Was ist das?

Ein leichter Cron-Aufsatz. Jeder Job besteht aus drei Dingen:

1. **Prompt** als Markdown-Datei (z. B. `jobs/morgenbriefing/prompt.md`)
2. **Schedule** über `launchd` (Mac) oder system-cron (Linux)
3. **Output-Ordner** mit einer Markdown-Datei pro Lauf (`jobs/morgenbriefing/data/2026-05-12.md`)

Wenn der Job feuert, ruft ein Wrapper-Skript deinen Agent mit dem Prompt auf, fängt die Antwort, schreibt sie in den Output-Ordner. Bei Fehler kommt eine Mail oder Push.

Der Vorteil gegenüber pur Cron: jeder Job ist selbsterklärend (Prompt liest sich wie ein Auftrag an einen Mitarbeiter), portabel zwischen Maschinen, und du kannst pro Job Skills aus deiner Skill-Bibliothek referenzieren.

### Warum so und nicht anders?

Vier Vergleichspunkte:

1. **Workflow-SaaS wie Zapier oder n8n.** Schön graphisch, aber Logik liegt in fremden Systemen, Prompt-Anpassungen sind unhandlich, Modell-Calls werden teurer
2. **Reiner Cron mit Inline-Skripten.** Funktioniert, aber Logik versteckt sich im Skript. Schwer zu lesen, schwer zu übergeben
3. **Spezielle Agent-Frameworks (LangChain Scheduler, Temporal).** Mächtig für komplexe Multi-Step-Flows, aber Overkill für "einmal pro Tag ein Report"
4. **Cloud-Functions mit Schedule.** Bindet dich an einen Cloud-Anbieter, verbergen Logs hinter ihrer UI

Daher: **leichter Aufsatz**. Prompt als Markdown, Schedule per launchd oder cron, Output als Datei. Jeder Job kann von Hand getriggert werden, jeder Job kann auf eine andere Maschine ziehen, indem du einen Ordner mitnimmst.

Markdown-Prompts statt Code, weil du sie wie einen Auftrag an einen Mitarbeiter liest. Wenn du selbst nicht verstehst, was dein Job tun soll, ist der Prompt unscharf — Symptom, nicht Bug.

**Output als Datei pro Tag**, weil du nachblättern können willst, ohne durch eine DB zu graben. Markdown ist portabel, durchsuchbar, druckbar.

### Was brauchst du dafür?

- Ein Rechner, der durchläuft (Mac, Linux-Server, kein Cloud-Zwang)
- Dein Agent als CLI ansprechbar (z. B. `claude -p`, oder OpenAI-CLI, oder eigenes Skript)
- Etwas Speicher: pro Job ~50 KB Output pro Lauf

### Wie startest du?

1. Starter laden, Agent geben
2. Sagen: "richte mir den Job-Runner ein"
3. Agent legt Ordner-Struktur an, einen Beispieljob, ein Wrapper-Skript
4. Erster Test: Job manuell aufrufen, Output prüfen
5. Dann via `launchd` oder cron einplanen

### Wo sind die Grenzen?

- Jobs laufen nur, wenn der Rechner an ist. Bei Mac mit Sleep: launchd wartet, holt verpasste Jobs nicht nach (außer mit `Period` statt `StartCalendarInterval`)
- Lange Jobs (>10 Minuten) lieber in Schritte zerlegen
- Wenn der Output stark variiert (Modell halluziniert), brauchst du eine Validation-Schicht im Prompt

### Was kostet das?

- Software: null
- Modell pro Job-Lauf: paar Cent bis paar Euro (je nach Länge)
- Tägliche Briefing-Routine: ~20–50 Cent/Tag bei Anthropic Opus

### Wann lohnt es sich, wann nicht?

Lohnt sich, sobald du mehr als zwei wiederkehrende Recherche- oder Report-Aufgaben hast. Lohnt sich nicht, wenn du nur einmal pro Woche manuell etwas zusammenstellen musst.

### Wie weißt du, dass es funktioniert?

- Tägliche Output-Datei in `jobs/<slug>/data/YYYY-MM-DD.md`
- Job-Übersicht in deiner Agent-UI listet Status und letzten Lauf
- Bei Fehler kommt eine Mail oder Push innerhalb von 5 Minuten

### Stolpersteine

- **Pfad-Hölle.** launchd kennt dein `$PATH` nicht — im Job-Wrapper immer absolute Pfade nutzen
- **Vergessene Logs.** Jeder Job soll seinen eigenen Logfile schreiben (`logs/jobs/<slug>.log`), sonst suchst du im stderr-Mix
- **Modell-Halluzinationen.** Wenn dein Job Zahlen oder Quellen liefert, im Prompt explizit "kein Halluzinieren, lieber leere Felder" schreiben und im Wrapper validieren
- **Konkurrenz beim Schreiben.** Wenn zwei Jobs gleichzeitig dieselbe Datei anfassen wollen: File-Lock oder unterschiedliche Ablage-Pfade
- **Daylight-Saving.** `StartCalendarInterval` verschiebt sich um eine Stunde im März und Oktober. Wenn das wehtut: lieber `Period` (alle X Sekunden) plus Tageszeit-Check im Job

---

## Teil A — Für deinen Agent

### Agent-Triggersätze

- "Richte mir den Job-Runner ein"
- "Lege mir einen Job `<slug>` an, der täglich um `<zeit>` läuft"
- "Plane den Job `<slug>` aus"
- "Zeig mir Status aller Jobs"

### Ziel

Verzeichnisstruktur unter `jobs/<slug>/` mit Prompt-Markdown, Output-Ordner und optional Source-Files. Wrapper-Skript, das Job ausführt, Output ablegt, Fehler meldet. launchd-Plists oder cron-Einträge automatisch generiert.

### Erfolgskriterien

- Pro Job: `prompt.md` als reiner Markdown-Auftrag, ohne Code
- Output landet in `jobs/<slug>/data/<datum>.md`
- Wrapper-Skript: `_bin/run-job.sh <slug>`
- Status-API: `/api/jobs` liefert pro Job letzten Lauf, letztes Output, letzten Fehler
- Fehler triggern Mail oder Push, nicht stilles Schweigen
- Skills aus `skills/`-Ordner referenzierbar via absolutem Pfad im Prompt

### Eingabe-Schema

```yaml
job:
  slug:            "morgenbriefing"
  prompt_file:     "<root>/jobs/morgenbriefing/prompt.md"
  output_dir:      "<root>/jobs/morgenbriefing/data"
  schedule:        "cron"                # cron, launchd, manual
  cron:            "0 6 * * *"
  agent_cli:       "claude -p"           # oder openai chat, eigenes Skript
  skills:          ["daily-log-format", "deck-voice"]
  timeout_seconds: 600
  notify_on_fail:  "mail" | "push"
```

### Verarbeitung

1. **Trigger** — launchd/cron ruft `run-job.sh <slug>`
2. **Read Prompt** — Wrapper liest `jobs/<slug>/prompt.md`, ersetzt Variablen wie `{{date}}`, `{{root}}`
3. **Run Agent** — `claude -p < prompt.md > tmp-output.md`
4. **Validate** — Output nicht leer, Pflichtfelder vorhanden (job-spezifisch)
5. **Place Output** — `mv tmp-output.md jobs/<slug>/data/<datum>.md`
6. **Index Update** — `jobs/<slug>/data/index.json` bekommt neuen Eintrag (Datum, Größe, Status)
7. **Status** — API listet Job-Status

### Endpoints

| Methode | Pfad | Zweck |
|---|---|---|
| GET | `/api/jobs` | Liste aller Jobs mit Status |
| GET | `/api/jobs/<slug>` | Details, letzte Läufe |
| POST | `/api/jobs/<slug>/run` | Manuell triggern |
| GET | `/api/jobs/<slug>/output/<datum>` | Output abrufen |

### Verzeichnisstruktur

```
<root>/
├── jobs/
│   ├── _bin/
│   │   └── run-job.sh
│   ├── morgenbriefing/
│   │   ├── prompt.md
│   │   ├── data/
│   │   │   ├── 2026-05-12.md
│   │   │   └── index.json
│   │   └── sources/
│   ├── themen-radar/
│   │   └── ...
│   └── backup/
│       └── ...
├── skills/
│   ├── daily-log-format/SKILL.md
│   └── deck-voice/SKILL.md
└── logs/
    └── jobs/
        └── morgenbriefing.log
```

### Sicherheit

- Wrapper-Skript läuft als dein User, keine sudo-Rechte
- `.env` wird vor Job-Start in Subshell geladen
- Logs ohne API-Keys (gerade Modell-Antworten checken)

### Fehler-/Edge-Cases

- Agent-CLI nicht gefunden → klarer Fehler, Pfad checken
- Timeout → Job kill, Status `timeout`, Notify
- Output leer → Status `empty`, alter Output bleibt erhalten
- Schreibrechte fehlen → Status `permission_denied`
- Doppel-Run (Job läuft noch beim nächsten Trigger) → File-Lock, neuer Run wird geskippt

### Code-Snippets

Im Starter findest du `run-job.sh`, eine `launchctl`-Plist-Vorlage, ein FastAPI-Endpunkt für die Status-API, und drei fertige Job-Prompts (Morgenbriefing, Backup, Themen-Radar als Brücke zum [Themen-Radar-Blueprint](./themen-radar.md)).

---

## Teil C — Was du als Nächstes tust

1. Starter laden, Agent geben
2. Sagen: "richte mir den Job-Runner ein und lege als ersten Job `morgenbriefing` an"
3. Prompt für deinen ersten Job in `jobs/morgenbriefing/prompt.md` schreiben (oder Vorlage nehmen)
4. Manuell triggern: `bash jobs/_bin/run-job.sh morgenbriefing`
5. Output ansehen
6. launchd/cron-Eintrag aktivieren
7. Weitere Jobs gleich anlegen

### Lern-Checkliste

- Wo liegen meine Prompts und wo die Outputs?
- Wie triggere ich einen Job manuell zum Testen?
- Was passiert, wenn der Rechner schläft, wenn der Job feuern soll?
- Wie merke ich, wenn ein Job seit Tagen nichts mehr liefert?
- Wie referenziere ich einen Skill in einem Job-Prompt?

Fragen: christian@denzer.ai
