TaskMonkey Handbuch

Tool als Config-Datei anlegen

Der empfohlene Weg für produktive Tools — versioniert, review-bar, testbar.

Für alles, was produktiv laufen soll, sind Config-Dateien der richtige Ort. Sie landen in Git, lassen sich im Review prüfen und können Helfer-Funktionen nutzen.

Auto-Discover — keine Registrierung nötig

Der Tenant-Loader scannt bei jedem Request alle .php-Dateien in config/tenants/<code>/ rekursiv und merged sie. Ein neues Tool tools/xyz.php mit return ['tools' => ['xyz' => [...]]] ist beim nächsten Request live.

Nicht in main.php registrieren. Nicht irgendwo importieren. Datei anlegen → fertig. Wenn du "Tool xyz nicht in tools registriert" siehst: der PHP-OPcache ist noch nicht durch — 1× Reload reicht typischerweise.

Sicherheits-Regeln für Handler-Code

Tool-Handler dürfen keine nativen PHP-Funktionen für Datei-I/O oder Prozesse benutzen. writeTenantConfig scannt jeden neuen Handler-Code darauf und lehnt sonst ab.

Verboten:

  • Schreiben: file_put_contents, fwrite, fputs, fputcsv, unlink, mkdir, rename, copy, chmod, touch
  • Lesen: file_get_contents, file, readfile, fread, fgets, fgetcsv, fopen, glob, scandir, opendir
  • Prozess: exec, shell_exec, system, passthru, popen, proc_open
  • Code-Injection: eval, create_function, unserialize, dynamische include $var

Nutze stattdessen die vorgesehenen _shared-Tools:

Use-Case Tool
Datei in <tenant>/files/ schreiben writeFile
Datei aus <tenant>/files/ lesen readFile
Verzeichnis-Listing in <tenant>/files/ listFilesInPath
Config-File schreiben (<tenant>/*.php) writeTenantConfig
Config-File lesen readTenantConfig
Config-File löschen deleteTenantConfig
Datei zum User-Download bereitstellen provideDownload
Google Drive lesen/schreiben readDriveFile / uploadDriveFile
Datenbank CakePHP ConnectionManager::get('default')

Der Grund: die vorgesehenen Tools haben Path-Traversal-Schutz, Auth-Checks, Backup, Logging und Tenant-Isolation. Deine native file_put_contents() hat nichts davon.

Wo landen Dateien?

  • <tenant>/files/ — Runtime-Daten (Uploads, Reports, Caches). NICHT im Git, .gitignore schließt es aus. Genutzt via writeFile/readFile.
  • <tenant>/*.php — Config (Assistants, Tools, APIs, Scheduled). IM Git, versioniert, review-bar. Genutzt via writeTenantConfig/deleteTenantConfig.
  • <tenant>/_backups/ — Auto-Backups von writeTenantConfig/deleteTenantConfig. NICHT im Git.
  • <tenant>/logs/, <tenant>/tmp/ — Runtime-Logs/Temp. NICHT im Git.

Ordnerstruktur

In deinem Workspace:

tools/
├── dropbox/
│   ├── uploads/
│   │   └── uploadFile.php
│   └── listing/
│       └── listFiles.php
├── jtl/
│   └── orders/
│       └── getOrder.php
└── knowledge/
    └── getKnowledge.php

Die Gruppierung nach API und Kategorie ist Konvention. Du darfst sie brechen, wenn du willst — der Loader scannt rekursiv, die Ordnernamen sind nur für dich.

Datei-Template

Jede .php-Datei gibt ein Array zurück, das einen oder mehrere Tools definiert. Der Schlüssel tools.<toolName> ist dabei zwingend.

<?php
return [
    'tools' => [
        'getInvoice' => [
            'description' => 'Rechnung per ID abrufen.',
            'parameters' => [
                'type' => 'object',
                'properties' => [
                    'id' => [
                        'type' => 'string',
                        'description' => 'Rechnungs-ID wie in JTL angezeigt.',
                    ],
                ],
                'required' => ['id'],
            ],
            'api' => 'jtl',
            'method' => 'GET',
            'path' => '/invoices/{id}',
            'mapping' => [
                'id' => 'invoice_id',
                'total' => 'amounts.gross',
                'customer' => 'customer.name',
            ],
        ],
    ],
];

Mit Preprocess und Postprocess

<?php
return [
    'tools' => [
        'getInvoice' => [
            // ... wie oben ...
            'preprocess' => function (array $args, $ctx): array {
                $args['id'] = strtoupper($args['id']);
                return $args;
            },
            'postprocess' => function (array $result, array $args, $ctx): array {
                $result['totalWithTax'] = round($result['total'] * 1.19, 2);
                return $result;
            },
        ],
    ],
];

Handler-only

Ohne API, reine PHP-Logik:

<?php
return [
    'tools' => [
        'calculateTax' => [
            'description' => 'Netto-Betrag mit MwSt. berechnen.',
            'parameters' => [
                'type' => 'object',
                'properties' => [
                    'net' => ['type' => 'number'],
                    'rate' => ['type' => 'number', 'description' => 'z. B. 0.19'],
                ],
                'required' => ['net', 'rate'],
            ],
            'handler' => function (array $results, array $args, array $ctx): array {
                $gross = $args['net'] * (1 + $args['rate']);
                return [
                    'net' => $args['net'],
                    'tax' => round($gross - $args['net'], 2),
                    'gross' => round($gross, 2),
                ];
            },
        ],
    ],
];

Handler-Signatur — genau drei Parameter

Der Handler bekommt IMMER drei Parameter in dieser Reihenfolge:

'handler' => function (array $results, array $args, array $ctx): array { … }
  • $results — Ergebnisse vorheriger Tool-Aufrufe im selben Turn (Nested-Calls). Meistens leer, aber Positional-Argument, nicht weglassen.
  • $args — die vom LLM übergebenen Argumente (validiert gegen dein parameters-Schema).
  • $ctx — der Runtime-Context: $ctx['tenant'], $ctx['chat_id'], $ctx['config'], $ctx['files_path'], $ctx['logger'], …

Falsche Signaturen wie function ($args, $context) (zwei Parameter, ohne array-Typing) crashen zur Laufzeit — der ToolRunner erwartet drei Argumente.

❌ Anti-Pattern — NICHT so schreiben

Verwechsele unser Tool-Schema nicht mit dem OpenAI Function-Calling-Schema. Sie sehen ähnlich aus, sind aber nicht kompatibel.

Falsch (OpenAI-Style, wird vom Runner ignoriert):

return [
    'tools' => [
        'getInvoice' => [
            'type' => 'function',              // ❌ existiert bei uns nicht
            'function' => [                    // ❌ falsch verschachtelt
                'name' => 'getInvoice',
                'description' => '…',
                'parameters' => [ … ],
            ],
            'handler' => function ($args, $ctx) { … },  // ❌ falsche Signatur
        ],
    ],
];

Richtig (unser Schema, flach unter dem Tool-Namen):

return [
    'tools' => [
        'getInvoice' => [
            'description' => '…',              // ✅ direkt hier, kein Nest
            'parameters' => [ … ],
            'handler' => function (array $results, array $args, array $ctx): array { … },
        ],
    ],
];

Wenn du type => function oder function => [...] innerhalb eines Tool-Eintrags siehst: das ist OpenAI-Function-Call-Notation, die ChatGPT/Claude beim Bauen halluziniert hat. Umbauen zum flachen Schema — sonst wird das Tool nie aufgerufen.

Mehrere Tools pro Datei

Möglich, aber: eine Datei pro Tool hält Diffs klein und Namen eindeutig. Nur gruppieren, wenn Tools sehr eng zusammengehören und z. B. dieselbe Helferfunktion teilen.

Test-Fixture

Für schnelle lokale Tests kannst du einem Tool ein Args-Fixture mitgeben:

'args_fixture' => ['id' => 'INV-2026-001'],

Damit läuft:

tm test-tool getInvoice

ohne dass du die Args manuell eintippen musst — das Fixture wird automatisch verwendet.

Shared Tools

Tools, die über mehrere Workspaces hinweg sinnvoll sind (z. B. getKnowledge), leben in einem geteilten Bereich. Du referenzierst sie mit extends:

return [
    'tools' => [
        'getKnowledge' => [
            'extends' => 'getKnowledge',
            'description' => 'Durchsucht unsere Produkt-Wissensbasis.',
            'args_fixture' => ['query' => 'Tomaten'],
        ],
    ],
];

Das zieht die Basis-Definition aus dem Shared-Bereich und erlaubt dir, einzelne Felder zu überschreiben.

Nach dem Anlegen

  • Konfig wird beim nächsten Request automatisch neu geladen (Dev).
  • In Production: je nach Setup ggf. bin/cake cache clear_all oder Deploy nötig.
  • Das Tool ist erst sichtbar, wenn es in der tools-Liste eines Chats oder Assistants steht.
Zuletzt aktualisiert: 2026-04-19