TaskMonkey Handbuch

Agent-Mode (Plan-Exec-Reflect)

Autonomer Multi-Tool-Agent für komplexe Anfragen. Kostenkontrolle, Reference-Pattern, Hintergrund-Jobs.

Der Agent-Mode ist eine Erweiterung des normalen Chat-Loops mit token- und latenzsparenden Optimierungen, die für autonome Mehr-Step-Aufgaben gedacht sind. Aktiviert via agent_mode: true in der Assistant-Config.

Beispiel: config/tenants/bloomify/assistants/agent_proto.php.

Wann Agent-Mode?

Aktiviere agent_mode: true wenn der Assistant:

  • Mehrere Tools sequenziell aufruft (>3 Iterationen)
  • Mit grossen Daten-Sets arbeitet (Listen, Tabellen, Auswertungen)
  • runPython als zentrales Tool nutzt um Daten zu transformieren
  • Plan-Disziplin braucht (createPlan/updatePlan)

Aktiviere nicht für:

  • Klassische Frage-Antwort-Chats (Public, FAQ)
  • Spezialisierte Single-Tool-Assistants (z. B. nur Bestellungen-Lookup)

Was Agent-Mode aktiviert

Mechanismus Beschreibung
Reference-Pattern Tool-Results > 8 KB werden für das LLM auf einen Stub gekürzt. Volldaten bleiben im turnToolResults-Pool, zugreifbar über runPython via from tool_results import last, by_tool, all_results. Spart 95-99 % Token bei grossen Datasets.
Cache-aware History-Truncation Solange der Prompt unter 60 KB ist, wird nichts gekürzt → Anthropic-Prompt-Cache bleibt valide. Erst ab 60 KB werden die ältesten Tool-Results gestubbt (mit tool_call_id als Identifier — cache-stabil).
Loop-Detection Fingerprint pro Tool-Call-Batch. 3× identisch → System-Hint. 5× → Pause-Schwelle (hart abbrechen statt Token zu verbrennen).
Cost-Cap Pro-Turn-Limit in EUR. Wenn überschritten → Pause-Schwelle mit „Weitermachen/Stop".

Config-Beispiel

'assistants.my_agent' => [
    'name' => 'Mein Agent',
    'llm' => 'claude', // Sonnet 4.6
    'max_iterations' => 15,
    'agent_mode' => true,
    'cost_cap_eur' => 2.00,
    'tools' => [
        // Plan-Disziplin
        'createPlan', 'updatePlan', 'getPlan',
        // Code-Execution
        'runPython',
        // Async-Jobs
        'waitForAsyncJob',
        // ... tenant-spezifische Tools
    ],
    'greeting' => 'Hi, beschreib mir ein Ziel.',
],

Plan-Disziplin

Der Agent soll vor dem ersten anderen Tool einen Plan erstellen:

createPlan({steps: ["Bestellungen laden", "Filter anwenden", "CSV exportieren"]})

Pro Step:

  1. updatePlan({step_index: N, status: "in_progress"})
  2. Tool-Calls ausführen
  3. updatePlan({step_index: N, status: "done", note: "1247 Bestellungen geladen"})

Frontend rendert eine Live-Checkliste (plan_update-SSE-Event). Mobile-App: sticky Card oben. Web: Status-Bubble.

Reference-Pattern in Aktion

Tool-Aufruf:

searchShopifyCustomers({first: 250, query: "orders_count:>=3"})

Tool-Result roh:

{"success": true, "count": 250, "customers": [/* 250 Items, ~100 KB */]}

Was das LLM sieht (Stub):

{
  "_truncated_for_llm": true,
  "_original_bytes": 102341,
  "_tool_name": "searchShopifyCustomers",
  "_hint": "Voller Tool-Result ist zu gross. Nutze `runPython` mit `from tool_results import last` um an die kompletten Daten zu kommen.",
  "success": true,
  "count": 250,
  "_preview": [{...}, {...}, {...}]
}

Was Python sieht (über runPython):

from tool_results import last
data = last()  # vollständiger Dict mit allen 250 Customers
for c in data['customers']:
    # process all 250
    ...

Hintergrund-Jobs (waitForAsyncJob)

Tools mit async: true (z. B. importInventoryFromSheet) werden bei Agent-Aufruf in die task_executions-Queue geschoben. Der Cron-Worker (bin/cake.php run_async_jobs) führt sie sequenziell aus.

Pattern:

toolA() → {queued: true, execution_id: "uuid"}
waitForAsyncJob({execution_id: "uuid", max_wait_seconds: 90})
  → blockt 90s, schickt SSE-Heartbeats alle 6s
  → returnt {success: true, status: "ok", result: {...}}
  → oder bei Timeout: {still_running: true, execution_id: "..."}

Der Agent kann bei Timeout nochmal waitForAsyncJob aufrufen oder den User vertrösten.

Cost-Cap-Verhalten

Pro LLM-Call wird data.usage.prompt_tokens und completion_tokens akkumuliert. Schätzung mit Sonnet-Preisen ($3/$15 per 1M, 10% Discount auf cached):

estimated_eur = (prompt - cached) * 3 / 1M + cached * 0.3 / 1M + completion * 15 / 1M

Erreicht estimated_eur den cost_cap_eur, pausiert der Loop mit:

Ich habe das Kosten-Budget für diesen Turn erreicht (~2.14 EUR bei 12 Tool-Aufrufen). Soll ich weitermachen oder stoppen?

Suggestions: ["Weitermachen", "Stop, das reicht"]. Klick auf „Weitermachen" startet einen neuen Turn mit frischem Cap, History bleibt.

Token-Persistenz und Dashboard

Bei JEDEM LLM-Call (auch Tool-Call-Iterationen) wird usage in chat_messages.prompt_tokens/completion_tokens/total_tokens persistiert. Das Dashboard unter /manage/dashboard aggregiert das pro Tag/Chat mit EUR-Schätzung.

Historisch: vor dem Token-Bug-Fix (2026-05-17) wurden 96 % der Tool-Call-Iteration-Tokens nicht persistiert. Dashboards von Chats vor diesem Datum sind unterzählt.

Streaming + Heartbeats

Im Agent-Mode kommt echtes Anthropic-Streaming zum Einsatz (ClaudeProvider::sendStreamed). Während der LLM antwortet:

  • Jeder text_delta wird sofort als message_delta-SSE-Event an den Client durchgereicht
  • Vor jedem LLM-Call: : llm-start <ts>\n\n als SSE-Comment-Heartbeat
  • Während Tool-Execution: pcntl-alarm-Watchdog tickt alle 3s mit tool_progress-Event

Damit ist die längste Idle-Phase typischerweise < 5s — Mittwald-Proxy und iOS-NSURLSession killen die Connection nicht mehr.

Tests

Backend-Tests für den Agent-Mode:

  • tests/TestCase/Service/Chat/ChatServiceTest.php — Basic loop, error paths
  • tests/TestCase/Service/Chat/ChatServiceDbPersistenceTest.php — DB-Persistierung, Cost-Cap, Heartbeats, Token-Bug-Regression
  • tests/TestCase/Service/Chat/ChatServiceCacheTruncationTest.php — Cache-Aware Truncation, Idempotenz
  • tests/TestCase/Service/Chat/ChatServicePlanUpdateTest.php — plan_update-SSE-Event
  • tests/TestCase/AiProvider/ClaudeProviderStreamingTest.php — Anthropic-SSE-Frame-Parser

Verwandte Doks

Zuletzt aktualisiert: 2026-05-17