routine-anlegen

Der Klon-Skill fuer agents-platform. Eine neue Cron-Lambda-Routine ist ein Beleg-Pipeline-Klon mit anderen Permissions und anderem Handler-Body — der Rest ist Boilerplate. Dieser Skill macht den Boilerplate-Teil und laesst Marvin sich auf den eigentlichen Logik-Code konzentrieren.

Reference-Implementierung: _index (Beleg-Pipeline, Session 1 abgeschlossen 2026-05-12). Code-Layout, IAM-Pattern, CDK-Construct, Layer-Imports — alles dort live und durchgemartert.

Geltungsbereich: Cron-getriggerte Lambdas, Python 3.12 ARM_64, in av-production eu-central-1, mit Layer agentic-common. Kein HTTP-API. Kein Multi-Region. Kein eigenes Stack-Pattern erfinden — der AgenticVenturesAgent-Construct in infra/lib/agent-construct.ts wird wiederverwendet.

Wann nutze ich diesen Skill

  • „Bau mir eine Routine die jeden Morgen X macht.”
  • „Ich brauch einen Cron-Agent fuer Y.”
  • „Lambda fuer Z anlegen.”
  • „Wie Beleg-Pipeline aber fuer ABC.”

Wenn der Anwendungsfall kein Cron ist (interaktiver Chat-Agent, Multi-Turn-Memory, HTTP-API) → dieser Skill ist falsch, statt dessen mcp-eigenbau oder eine eigene Plattform-Entscheidung.

Ablauf

Phase 1: Brief sammeln  (6 Rueckfragen, eine nach der anderen)
Phase 2: Generierung    (Lambda + Stack + Vault-Doku)
Phase 3: Deploy + Smoke (Local + Lambda + Telegram-Push)

Marvin kann jederzeit „stop” sagen — der Skill bricht ab, ohne Halbfertiges zu deployen.


Phase 1: Brief sammeln

Wichtig: eine Frage nach der anderen. Nicht alle 6 auf einmal stellen. Marvin hat ADHS-Setup — kleine Schritte, jede Antwort kurz, naechste Frage kommt nach Bestaetigung. Antworten in Scratchpad oder TodoWrite-Notes halten.

Frage 1 — Identitaet

Wie soll die Routine heissen (Slug in kebab-case, z.B. email-summary, kalender-check, papierkram-sync)? Und in einem Satz: was macht sie?

Validieren:

  • Slug ist kebab-case, keine Umlaute, keine Sonderzeichen ausser -
  • Slug ist nicht bereits in ~/source/agents-platform/lambdas/ als Ordner vorhanden
  • Slug ist nicht bereits in infra/bin/app.ts als Stack-Instanzierung referenziert

Frage 2 — Trigger

Welcher Schedule? Beispiele: alle 15 Min, taeglich 7:00 Berlin, Mo-Fr 9:00, 1. jeden Monats 8:00, oder manuell-only (kein Cron, nur on-demand-Invoke)?

In EventBridge-Cron-Syntax umsetzen (Berlin → UTC! AWS Cron ist UTC). Beispiele:

  • alle 15 Minevents.Schedule.cron({ minute: '0/15' })
  • taeglich 7:00 Berlin → im Sommer cron({ hour: '5', minute: '0' }), im Winter cron({ hour: '6', minute: '0' }) — Hinweis: AWS Cron kennt keine Timezone, ggf. zwei Schedules oder fixe UTC-Zeit
  • Mo-Fr 9:00 Berlincron({ minute: '0', hour: '7', weekDay: 'MON-FRI' }) (Sommerzeit angenommen)
  • manuell-only → keine EventBridge-Rule, nur Lambda + manueller Invoke

Frage 3 — Datenquellen

Welche externen Tools / APIs ruft die Routine? Beispiele:

  • gsuite: Gmail-Pull, Calendar-Read, Drive-Read
  • papierkram: Voucher/Invoice anlegen, lesen
  • m365: SharePoint-Files, Excel, Outlook
  • ticketpay: Order-Listen
  • Bedrock: LLM-Calls (Klassifikation, Summary)
  • AWS-S3: Files lesen/schreiben
  • Externes (z.B. GitHub, OpenWeather, custom API)

Wichtig zu klaeren:

  • gsuite: welches Konto? hello@ oder marvinkuehlmann@? (siehe Gmail-OAuth-Secret-Bundle)
  • papierkram/ticketpay/m365: ueber den hosted mcp-vf Endpoint (PAPIERKRAM_MCP_URL Env-Var) oder direkt API?
  • S3: welcher Bucket? Lesen / Schreiben / beides?

Mapping zu IAM-Permissions im Stack:

  • gsuite → secretsmanager:GetSecretValue auf agent-platform/gmail-oauth-refresh-*
  • Bedrock → schon im AgenticVenturesAgent-Construct vorhanden (Haiku-4.5 Default), bei anderem Modell selber adden
  • S3-Read → s3:GetObject + s3:ListBucket
  • S3-Write → s3:PutObject + s3:PutObjectTagging + KMS-Key (wenn Bucket verschluesselt)
  • MCP-Hosted → keine extra Permissions, nur HTTP-Call (Token kommt aus Scalekit, das laeuft eh ueber den hosted Endpoint)

Frage 4 — Verarbeitung

Was muss die Routine intern tun? Konkret:

  • Bedrock-LLM-Call? Ja/Nein. Falls ja: kurz beschreiben — Klassifikation, Summary, Extraktion? Welches Schema kommt raus?
  • PDF/HTML-Parsing? Ja/Nein.
  • Cross-Account-Schreibzugriff (nach mk-privat per AssumeRole)?
  • State zwischen Runs (z.B. „seen IDs” in S3/DynamoDB) oder stateless?

Wenn PDF: pypdfium2 als Dependency, Layer-Build muss pip install -r requirements.txt -t /asset-output/python im CDK adden — derzeit ist der Layer Plain-Python. Skill warnt: „Layer-Build-Erweiterung erstmalig noetig — alternativ Dependency im Lambda-Bundle statt Layer.” → Marvin entscheidet.

Wenn State: schlag S3 + JSON-File vor (einfach, kein extra Service). DynamoDB nur wenn high-frequency reads.

Frage 5 — Outputs

Was passiert am Run-Ende? Default ist Telegram-Push immer. Plus zusaetzlich:

  • S3-Upload (welcher Bucket / Prefix)?
  • Email an dich (welcher Account, welches Format)?
  • Vault-Stub ueber GitHub-API (welcher Pfad im agentic-ventures-Repo)?
  • Calendar-Event ueber gsuite-MCP?
  • Papierkram-Voucher ueber mcp-vf?
  • Nichts ausser Telegram = ok, oft reicht das.

Telegram-Push-Format ist standardisiert (siehe telegram-push-konvention): TelegramReport mit successes, pendings, errors, deep_links. Skill packt diese Pattern im Handler-Skeleton automatisch.

Frage 6 — Neue Secrets

Brauchen wir neue Secrets in AWS Secrets Manager (zusaetzlich zu Telegram-Bot-Token, Gmail-OAuth-Refresh, GitHub-PAT die schon angelegt sind)? Beispiele:

  • API-Keys fuer externe APIs (OpenAI, Replicate, GitHub-App-Private-Key, etc.)
  • Service-Tokens (Stripe, Sendgrid, etc.)

Wenn ja: pro Secret den Namen abfragen (agent-platform/<name> Schema). Skill legt aws secretsmanager create-secret mit Placeholder-Wert an, Marvin tippt echte Werte separat.

Keine Geheimnisse in der Frage tippen lassen! Marvin kriegt nach create-secret den put-secret-value-Befehl als Copy-Paste mit <token> Platzhalter.


Phase 2: Generierung

Vorab: TodoWrite-Liste mit 8 Items anlegen (Files + Smoke + Deploy + Commit). Status laufend aktualisieren.

2.1 Lambda-Handler

lambdas/<slug>/main.py schreiben. Template:

"""
<slug> — <1-Satz-Zweck>.
 
Trigger: <Schedule oder manuell>
Outputs: <Liste>
"""
 
import os
import time
import traceback
 
from agentic_common.logging import get_logger, new_run_id
from agentic_common import secrets
from agentic_common.telegram import TelegramReport, push
# from agentic_common.bedrock import BedrockClassifier  # nur wenn Bedrock genutzt
 
AGENT_NAME = os.environ.get("AGENT_NAME", "<slug>")
 
 
def handler(event: dict, context) -> dict:
    run_id = new_run_id()
    logger = get_logger(AGENT_NAME, run_id=run_id)
    logger.info("invocation_start", "Cron getriggert", data={
        "trigger": event.get("source", "manual"),
        "request_id": getattr(context, "aws_request_id", "unknown"),
    })
 
    report = TelegramReport(agent_name=AGENT_NAME)
 
    try:
        # === PIPELINE-LOGIK HIER ===
        # Beispiele:
        # - secrets.get("agent-platform/...") fuer Token
        # - Bedrock-Call via BedrockClassifier
        # - HTTP-Call zu mcp-vf.agenticventures.de/mcp
        # - S3-Upload via boto3
        # - report.successes.append(...) / report.errors.append(...)
        pass
    except Exception as exc:
        logger.error("pipeline", "Pipeline-Fehler", data={"error": str(exc), "trace": traceback.format_exc()[:500]})
        report.errors.append(f"Pipeline-Fehler: {exc}")
 
    # Telegram-Push
    push_status = {"sent": False}
    try:
        bot_token = secrets.get(os.environ["TELEGRAM_SECRET_NAME"])
        chat_id = os.environ["TELEGRAM_CHAT_ID"]
        api_response = push(report, bot_token, chat_id)
        push_status = {"sent": True, "message_id": api_response["result"]["message_id"]}
        logger.info("telegram_push", "Push raus", data=push_status)
    except Exception as exc:
        push_status = {"sent": False, "error": str(exc)}
        logger.error("telegram_push", "Push fehlgeschlagen", data=push_status)
 
    result = {
        "agent": AGENT_NAME,
        "run_id": run_id,
        "status": "ok" if (push_status["sent"] and not report.errors) else "degraded",
        "telegram_sent": push_status["sent"],
        "errors_count": len(report.errors),
    }
    logger.info("invocation_end", "Run beendet", data=result)
    return result

lambdas/<slug>/requirements.txt mit Kommentar-Liste — Inhalt nur wenn Lambda-Build pip-Install macht. Aktuell laeuft alles ueber stdlib + boto3 + urllib3 (im Runtime preinstalled).

2.2 Stack

infra/lib/<slug>-stack.ts — kopiere beleg-pipeline-stack.ts als Template, passe an:

  • Props-Interface: nur die Secrets/Buckets/Roles die diese Routine braucht
  • AgenticVenturesAgent-Aufruf mit eigenem name, schedule, codePath
  • environment-Vars: pro Output/Datenquelle die passenden Namen
  • permissions-Array: pro Output/Datenquelle die passenden iam.PolicyStatements

Hardcoded Defaults (Fallstrick-Schutz, KEIN re-debug noetig):

  • Bedrock-Permissions sind schon im AgenticVenturesAgent-Construct (alle Agents duerfen Haiku-4.5 invoken)
  • Secrets-Manager-ARN-Pattern: mit -* Suffix (zufaelliges 6-char-Suffix pro Secret)
  • Inference-Profile-ARN: arn:aws:bedrock:eu-*:<account>:inference-profile/eu.anthropic.claude-haiku-4-5-* (mit Account-ID-Slot, weil Cross-Region-Profiles per-Account materialisiert sind)
  • BEDROCK_MODEL_ID: eu.anthropic.claude-haiku-4-5-20251001-v1:0 (mit Date-Tag!)
  • Memory-Default: 512 MB (1024 nur wenn PDF-Parsing)
  • Timeout-Default: 5 Min (10 Min wenn Bedrock + grosse Pipelines)

2.3 App-Entry

infra/bin/app.ts — neue Stack-Instanzierung adden, analog zu BelegPipelineStack. Account/Region wiederverwenden (avProductionEnv-Konstante).

2.4 Vault-Doku

intern/projekte/<slug>/_index.md als Projekt-Stub:

---
id: project-<slug>
type: project
name: "<Routine-Name>"
status: skeleton-deployed
description: "<1-Satz-Zweck>"
last_reviewed: <heute>
visibility: internal
schedule: "<Schedule>"
stack: "<slug>Stack in av-production"
---
 
# <Routine-Name>
 
## Was sie macht
 
<Brief-Antworten zusammenfassen>
 
## Architektur
 
<Datenquellen → Verarbeitung → Outputs als Diagramm oder Liste>
 
## Stand <heute>
 
| Komponente | Status |
|---|---|
| Stack deployed | ✅ |
| Smoke-Test gruen | ✅ |
| Pipeline-Logik | <Status> |
 
## Related
 
- [[../../capabilities/agents-platform]]

2.5 Capabilities-Update

intern/capabilities/agents-platform.md — Tabellen-Eintrag in „Aktive Agents”:

| `agent-<slug>` | `<Schedule>` | <1-Satz-Zweck> | <Stand> |

2.6 Secrets (falls Phase 1 Frage 6 = ja)

Pro neuem Secret:

aws --profile av-production --region eu-central-1 secretsmanager create-secret \
  --name "agent-platform/<name>" \
  --description "<Zweck>" \
  --secret-string "<placeholder-replace-via-put-secret-value>"

Danach Marvin den put-secret-value-Befehl als Copy-Paste mit Token-Platzhalter geben — er tippt selber.


Phase 3: Deploy + Smoke

3.1 Local-Smoke

Wenn moeglich, Handler lokal aufrufen:

cd ~/source/agents-platform/lambdas/<slug>
PYTHONPATH=~/source/agents-platform/layers/agentic-common/python \
AWS_PROFILE=av-production AWS_REGION=eu-central-1 \
TELEGRAM_CHAT_ID=8257793678 \
TELEGRAM_SECRET_NAME=agent-platform/telegram-bot-token \
AGENT_NAME=<slug>-localsmoke \
  python3 -c "
import main
class FakeContext: aws_request_id = 'local-smoke'
print(main.handler({'source': 'local'}, FakeContext()))
"

Wenn Smoke fehlschlaegt: stoppen, fixen, Marvin informieren. Nicht deployen.

3.2 cdk synth

cd ~/source/agents-platform/infra && \
AWS_PROFILE=av-production npx cdk synth <slug>Stack

Compile-Errors → fixen. Nie deployen wenn synth nicht durchlaeuft.

3.3 cdk deploy

cd ~/source/agents-platform/infra && \
AWS_PROFILE=av-production AWS_REGION=eu-central-1 \
  npx cdk deploy <slug>Stack --require-approval never

--require-approval never ist sicher solange der Stack nur die Standard-Pattern nutzt (keine neue Role mit AssumeRole-Trust nach fremden Accounts). Wenn der Stack so was hat: erst cdk diff zeigen, Marvin bestaetigt.

3.4 Lambda-Invoke + CloudWatch

aws --profile av-production --region eu-central-1 lambda invoke \
  --function-name agent-<slug> \
  --cli-binary-format raw-in-base64-out \
  --payload '{"source":"manual-smoke"}' \
  /tmp/<slug>-invoke.json
cat /tmp/<slug>-invoke.json | python3 -m json.tool
 
sleep 5
aws --profile av-production --region eu-central-1 logs tail \
  /aws/lambda/agent-<slug> --since 2m --format short

Pruefen:

  • StatusCode: 200
  • Result status: "ok"
  • CloudWatch zeigt strukturierte JSON-Logs mit run_id, step, level
  • Telegram-Push ist im Marvin’s Chat angekommen (er guckt aufs Phone)

3.5 Commit

cd ~/source/agents-platform && \
git add lambdas/<slug>/ infra/lib/<slug>-stack.ts infra/bin/app.ts && \
git commit -m "feat: <slug> Cron-Routine
 
<1-Satz-Zweck>. Schedule: <Schedule>. Deploy + Smoke gruen."

Push: Sandbox blockt direkten Push auf main. Skill gibt Marvin den exakten Befehl als Copy-Paste:

cd ~/source/agents-platform && git push origin main

3.6 Run-Log

intern/runs/<jahr>-<monat>.md ergaenzen — eine Zeile:

- **<datum>** routine-anlegen: `<slug>` deployed. Schedule `<Schedule>`. Brief-Phase X Min, Generierung Y Min, Deploy + Smoke Z Min.

Hardcoded Defaults (Session-1-Lessons-Learned)

Diese Werte sind nicht verhandelbar, weil sie auf Bugs basieren die wir schon einmal live entdeckt haben:

SettingWertGrund
BEDROCK_MODEL_IDeu.anthropic.claude-haiku-4-5-20251001-v1:0Date-Tag im ID, sonst Invalid Model Identifier
Inference-Profile-ARNarn:aws:bedrock:eu-*:<account>:inference-profile/eu.anthropic.claude-haiku-4-5-*Cross-Region-Profiles sind per-Account, Account-Slot Pflicht
Foundation-Model-ARNarn:aws:bedrock:eu-*::foundation-model/anthropic.claude-haiku-4-5-*Foundation-Models sind service-owned, kein Account-Slot
Secrets-Manager-ARNarn:aws:secretsmanager:eu-central-1:<account>:secret:agent-platform/<name>-*-* Pflicht — zufaelliges 6-char-Suffix pro Secret
Lambda-RuntimePython 3.12 ARM_64Graviton 20% billiger, Layer ist auf ARM_64 gepackt
HTTP-Libraryurllib3 (preinstalled mit boto3)Spart Layer-pip-Bundling, httpx erst wenn echtes Bedarf da
Telegram-Formatparse_mode=None (plain text)Vermeidet Markdown-V2-Escape-Hoelle, URLs sind eh clickable

Was der Skill NICHT macht

  • Keine Multi-Region-Stacks. Alles in eu-central-1. Wenn US-Region noetig: separater Skill.
  • Keine Multi-Account-Stacks. Alles in av-production. Cross-Account-Schreibzugriff per AssumeRole (z.B. nach mk-privat).
  • Keine HTTP-API-Endpoints / API-Gateway. Routinen sind Cron-getriggert oder manuell.
  • Keine neuen Layer-Module bauen. Wenn die Routine was Neues braucht (z.B. gmail.py, pdf.py, mcp_client.py): erstmal als lokales Modul in lambdas/<slug>/. Wenn drei Routinen dasselbe Modul brauchen, manuell in agentic-common extrahieren — nicht automatisch.
  • Kein automatischer git push. Sandbox blockt. Skill gibt Marvin Copy-Paste-Befehl.
  • Keine Phase-1-Frage-Abkuerzung. Marvin muss alle 6 Antworten geben — sonst fehlt das Skeleton-Material. Wenn er „weiss-nicht” sagt: Default vorschlagen, dann er nickt.

Trigger-Phrasen

  • „neue Routine fuer X”
  • „Cron-Agent anlegen”
  • „Lambda anlegen”
  • „Routine fuer X”
  • „Agent bauen wie Beleg-Pipeline aber X”
  • „bau mir einen Agent der Y macht”
  • „Cron-Job in AWS fuer X”
  • explizit: /routine-anlegen (wenn Marvin den Slash-Command nutzt)

Run-Log

Eintraege chronologisch:

  • 2026-05-12 Skill angelegt. Basiert auf Erfahrungen aus Beleg-Pipeline Session 1 (Layer-Code + IAM-Bugfixes + Deploy + Smoke). Erstanwendung: <naechste Routine>.
  • agents-platform — Plattform die der Skill bedient (Konstrukt, Konventionen, Telegram-Format)
  • _index — Beleg-Pipeline, Reference-Implementierung
  • _index — MCPs die als Datenquellen dienen
  • _index — AWS-Account-Map (Profile, Regionen, Berechtigungen)
  • buckets — S3-Bucket-Map (falls Routine S3 braucht)
  • ~/source/agents-platform/README.md — Repo-Doku