mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-22 08:06:42 +02:00
feat(agent-loop): M1 — policy gate + reminder channel + parallel reads
Three Claude-Code-inspired primitives for runPlannerLoop, derived from the
reverse-engineering reports in docs/reports/:
1. **Policy gate** (@mana/tool-registry) — evaluatePolicy() gates every tool
dispatch: denies admin-scope, denies destructive tools not in the user's
opt-in list, rate-limits per tool (30/60s default), flags prompt-injection
markers in freetext without blocking. Wired into mana-mcp with a
per-user rolling invocation log and POLICY_MODE env (off|log-only|enforce,
default log-only). mana-ai uses detectInjectionMarker only — tool dispatch
there is plan-only, so rate-limit/destructive checks don't apply yet.
2. **Reminder channel** (packages/shared-ai/src/planner/loop.ts) — new
reminderChannel callback in PlannerLoopInput. Called once per round with
LoopState snapshot (round, toolCallCount, usage, lastCall); returned
strings wrap in <reminder> tags and inject as transient system messages
into THIS LLM request only. Never pushed to messages[] — the Claude-Code
<system-reminder> pattern that keeps the KV-cache prefix stable.
3. **Parallel reads** (loop.ts) — isParallelSafe predicate enables
Promise.all dispatch when every tool_call in a round is parallel-safe,
in batches of PARALLEL_TOOL_BATCH_SIZE=10. Any non-safe call downgrades
the whole round to sequential. messages[] always appends in source
order, never completion order, so the debug log stays linear.
Default-off (undefined predicate) preserves pre-M1 behaviour.
Tests: 21 new in tool-registry (policy), 9 new in shared-ai (5 parallel,
4 reminder). All 74 green, type-check clean across 4 packages.
Design/plan: docs/plans/agent-loop-improvements-m1.md
Reports: docs/reports/claude-code-architecture.md,
docs/reports/mana-agent-improvements-from-claude-code.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
493db0c3b2
commit
e5d230e599
19 changed files with 2550 additions and 29 deletions
385
docs/plans/agent-loop-improvements-m1.md
Normal file
385
docs/plans/agent-loop-improvements-m1.md
Normal file
|
|
@ -0,0 +1,385 @@
|
|||
# Agent-Loop Improvements — M1
|
||||
|
||||
_Started 2026-04-23._
|
||||
|
||||
Drei kleine, voneinander unabhängige Verbesserungen am Mana-Agent-Stack,
|
||||
abgeleitet aus der Claude-Code-Architektur-Analyse. Alle drei zusammen
|
||||
~1.5 Arbeitstage, mit hohem qualitativem und Sicherheits-Impact.
|
||||
|
||||
**Hintergrund:**
|
||||
- [`docs/reports/claude-code-architecture.md`](../reports/claude-code-architecture.md) — wie Claude Code intern aufgebaut ist
|
||||
- [`docs/reports/mana-agent-improvements-from-claude-code.md`](../reports/mana-agent-improvements-from-claude-code.md) — vollständige Gap-Analyse mit 8 Verbesserungen; dies hier ist die priorisierte M1-Teilmenge
|
||||
|
||||
## Ziel in einem Satz
|
||||
|
||||
Den `runPlannerLoop` um drei Primitive erweitern, die Claude Code hat und
|
||||
wir nicht haben: einen **Permission-Gate vor Tool-Execution**, einen
|
||||
**transienten Reminder-Channel** für Per-Round-Hinweise, und
|
||||
**Parallelisierung für reine Read-Tools**.
|
||||
|
||||
## Nicht-Ziele
|
||||
|
||||
- **Kein** Umbau von `runPlannerLoop`s Grundstruktur — nur Erweiterungen.
|
||||
- **Keine** Änderung am Message-Log-Format — Iterations bleiben binärkompatibel.
|
||||
- **Keine** neue LLM-Route, kein neues Modell, kein Haiku-Tier (das ist M2).
|
||||
- **Kein** Context-Compressor (das ist M2, braucht eigene Archiv-Tabelle).
|
||||
- **Kein** Sub-Agent-Pattern (das ist M3, zusammen mit dem Persona-Runner).
|
||||
|
||||
## Wer profitiert
|
||||
|
||||
| Konsument | Nutzen |
|
||||
|-----------------------|----------------------------------------------------------|
|
||||
| `services/mana-ai` | bessere Mission-Pläne + schnellere Multi-Read-Ticks |
|
||||
| `services/mana-mcp` | Schutz gegen missbräuchliche MCP-Clients |
|
||||
| Webapp Companion-Chat | bessere Antworten durch Per-Round-Context-Hinweise |
|
||||
| Persona-Runner (M3) | Fundament — braucht Permission-Gate bevor es live darf |
|
||||
|
||||
---
|
||||
|
||||
## Verbesserung 1 — Permission-Gate vor Tool-Execution
|
||||
|
||||
### Was es macht
|
||||
|
||||
Bevor ein Tool-Handler aufgerufen wird, läuft ein zentrales `evaluatePolicy()`
|
||||
aus `@mana/tool-registry`. Das Gate entscheidet anhand von Tool-Scope,
|
||||
Policy-Hint, Usage-History und User-Settings, ob die Ausführung erlaubt ist.
|
||||
|
||||
### Was es ermöglicht
|
||||
|
||||
- **Destructive-Tools werden per Default blockiert.** Heute ist `policyHint:
|
||||
'destructive'` nur dokumentiert ([types.ts:48](../../packages/mana-tool-registry/src/types.ts#L48)),
|
||||
nicht durchgesetzt. Künftig: User muss in Settings explizit opt-in pro
|
||||
Tool oder Scope.
|
||||
- **Rate-Limiting pro User pro Tool.** Heute kann ein entwendeter JWT in
|
||||
10 Sekunden hunderte Calls machen. Künftig: Cap 30 Calls/Tool/Minute
|
||||
(konfigurierbar pro Tool).
|
||||
- **Freitext-Input-Inspektion.** Für Tools mit String-Feldern (`content`,
|
||||
`description`, `note`): Marker wie `{{`, `<system`, `ignore previous`
|
||||
werden erkannt und als Metrik markiert. Nicht blockiert (zu viele False
|
||||
Positives), aber sichtbar.
|
||||
- **Ein Policy-Ort für beide Consumer.** `mana-mcp` und `mana-ai` rufen
|
||||
denselben Code — keine Drift mehr.
|
||||
|
||||
### Heutiger Zustand (Problem)
|
||||
|
||||
[`services/mana-mcp/src/mcp-adapter.ts:34-37`](../../services/mana-mcp/src/mcp-adapter.ts#L34):
|
||||
|
||||
```ts
|
||||
function isExposable(spec: AnyToolSpec): boolean {
|
||||
return spec.scope === 'user-space';
|
||||
}
|
||||
```
|
||||
|
||||
Das ist der gesamte Gate. `mana-ai`s `onToolCall` hat gar nichts.
|
||||
|
||||
### Neuer Zustand (Lösung)
|
||||
|
||||
Neues Modul `packages/mana-tool-registry/src/policy.ts`:
|
||||
|
||||
```ts
|
||||
export interface PolicyDecision {
|
||||
readonly allow: boolean;
|
||||
readonly reason?: string;
|
||||
/** Optional hint, wird von M1 Verbesserung 2 als Reminder-Tag
|
||||
* an den nächsten LLM-Turn angehängt. */
|
||||
readonly reminder?: string;
|
||||
}
|
||||
|
||||
export interface PolicyInput {
|
||||
readonly spec: AnyToolSpec;
|
||||
readonly ctx: ToolContext;
|
||||
readonly rawInput: unknown;
|
||||
readonly userSettings: {
|
||||
readonly allowDestructive: readonly string[]; // Tool-Names Whitelist
|
||||
readonly perToolRateLimit?: number; // default 30/min
|
||||
};
|
||||
readonly recentInvocations: readonly { toolName: string; at: number }[];
|
||||
}
|
||||
|
||||
export function evaluatePolicy(input: PolicyInput): PolicyDecision;
|
||||
```
|
||||
|
||||
Integration:
|
||||
|
||||
- [`services/mana-mcp/src/mcp-adapter.ts`](../../services/mana-mcp/src/mcp-adapter.ts) ruft `evaluatePolicy()` in `invoke()` **vor** `spec.handler()`.
|
||||
- [`services/mana-ai/src/cron/tick.ts`](../../services/mana-ai/src/cron/tick.ts) ruft es im `onToolCall`-Callback.
|
||||
- `recentInvocations` kommt aus einer In-Memory-Ringbuffer pro User (beide Services).
|
||||
|
||||
### Aufwand
|
||||
|
||||
~1 Arbeitstag (6-8h).
|
||||
|
||||
### Tests
|
||||
|
||||
- Unit-Tests in `packages/mana-tool-registry/src/policy.test.ts`: je ein
|
||||
Case für allow/deny pro Policy-Regel.
|
||||
- MCP-Integration-Test: Destructive-Tool-Call ohne Opt-In → 403 mit
|
||||
klarer Fehlermeldung.
|
||||
- Rate-Limit-Test: 31 Calls in 60s → letzter wird geblockt.
|
||||
|
||||
### Rollout
|
||||
|
||||
Flag-gated per ENV `POLICY_ENFORCE=true` (default off). Erst eine Woche
|
||||
**log-only** (alle Decisions werden geloggt, nichts blockiert), dann
|
||||
enforcement flippen.
|
||||
|
||||
---
|
||||
|
||||
## Verbesserung 2 — Reminder-Channel im Planner-Loop
|
||||
|
||||
### Was es macht
|
||||
|
||||
`runPlannerLoop` bekommt einen optionalen `reminderChannel`-Callback. Vor
|
||||
jedem LLM-Call fragt die Loop den Channel nach aktuellen Per-Round-Hinweisen
|
||||
(„du hast 80 % deines Token-Budgets verbraucht", „Mission ist in 2 min
|
||||
überfällig"). Die Hinweise werden als **transiente** System-Message vor den
|
||||
API-Call gesetzt und danach **wieder entfernt**. Sie leben nie in der
|
||||
persistierten Message-History.
|
||||
|
||||
### Was es ermöglicht
|
||||
|
||||
- **Per-Round-Steering ohne History-Mutation.** Der Loop sieht den Zustand,
|
||||
die Iteration speichert aber nur die Entscheidungen — kein KV-Cache-
|
||||
Invalidation, kein Log-Rauschen.
|
||||
- **Token-Budget-Awareness.** Aktuell weiß das LLM nicht, wie viele Calls
|
||||
es noch hat. Künftig: „du hast 2 von 5 Rounds noch".
|
||||
- **Stale-Data-Warnings.** Wenn `mana-ai` länger nicht sync'd hat, kann
|
||||
das LLM warnen statt zu halluzinieren.
|
||||
- **Zero-Knowledge-Hinweise.** Bei ZK-Usern: „verbotene Tabellen sind
|
||||
nicht resolvable — frag nicht nach". Heute muss das im System-Prompt
|
||||
stehen und bleibt dort ewig.
|
||||
- **Policy-Feedback.** `evaluatePolicy()` (Verbesserung 1) kann einen
|
||||
`reminder`-String zurückgeben, der dem LLM in der nächsten Runde erklärt,
|
||||
warum ein Tool-Call geblockt wurde — statt nur einen Fehler zu werfen.
|
||||
|
||||
### Heutiger Zustand (Problem)
|
||||
|
||||
[`packages/shared-ai/src/planner/loop.ts:131-135`](../../packages/shared-ai/src/planner/loop.ts#L131):
|
||||
|
||||
```ts
|
||||
const messages: ChatMessage[] = [
|
||||
{ role: 'system', content: input.systemPrompt },
|
||||
...(input.priorMessages ?? []),
|
||||
{ role: 'user', content: input.userPrompt },
|
||||
];
|
||||
```
|
||||
|
||||
Transienter Context geht heute auf einem von zwei schlechten Wegen rein:
|
||||
|
||||
1. in den `systemPrompt` eingebacken → bleibt ewig stehen, veraltet schnell,
|
||||
2. an den `userPrompt` per Concatenation → mutiert die History, landet in Logs.
|
||||
|
||||
### Neuer Zustand (Lösung)
|
||||
|
||||
[`packages/shared-ai/src/planner/loop.ts`](../../packages/shared-ai/src/planner/loop.ts) bekommt neuen Input-Slot:
|
||||
|
||||
```ts
|
||||
export interface LoopState {
|
||||
readonly round: number;
|
||||
readonly toolCallCount: number;
|
||||
readonly tokensUsed: TokenUsage;
|
||||
readonly lastCall?: ExecutedCall;
|
||||
}
|
||||
|
||||
export interface PlannerLoopInput {
|
||||
// … bestehende Felder …
|
||||
/** Called before each LLM request. Return an array of transient
|
||||
* system-message strings to inject into THIS request only. They
|
||||
* are removed from `messages` before the next iteration and never
|
||||
* appear in the returned message log. */
|
||||
readonly reminderChannel?: (state: LoopState) => readonly string[];
|
||||
}
|
||||
```
|
||||
|
||||
Implementation skizziert (in der Loop):
|
||||
|
||||
```ts
|
||||
while (rounds < maxRounds) {
|
||||
rounds++;
|
||||
const reminders = input.reminderChannel?.({ round: rounds, /* … */ }) ?? [];
|
||||
const reminderMessages: ChatMessage[] = reminders.map(text => ({
|
||||
role: 'system',
|
||||
content: `<reminder>${text}</reminder>`,
|
||||
}));
|
||||
const response = await llm.complete({
|
||||
messages: [...messages, ...reminderMessages], // transient, nicht an messages push
|
||||
// …
|
||||
});
|
||||
// … bestehende Logik (messages.push für assistant/tool, NICHT für reminder) …
|
||||
}
|
||||
```
|
||||
|
||||
### Erste Producer (Beispiele, nicht Scope von M1)
|
||||
|
||||
Die Channel-API kommt in M1; die konkreten Reminder-Producer können
|
||||
inkrementell danach entstehen. Niedrig hängende Früchte:
|
||||
|
||||
```ts
|
||||
// services/mana-ai/src/planner/reminders.ts (später)
|
||||
export function tokenBudgetReminder(agent: ServerAgent, usage24h: number) {
|
||||
if (!agent.maxTokensPerDay) return null;
|
||||
const pct = usage24h / agent.maxTokensPerDay;
|
||||
if (pct < 0.75) return null;
|
||||
return `Agent ${agent.name} hat ${Math.round(pct * 100)}% des Tagesbudgets verbraucht. Plane sparsam.`;
|
||||
}
|
||||
```
|
||||
|
||||
### Aufwand
|
||||
|
||||
4h für die Loop-Änderung + Test. Producer sind eigene kleine PRs danach.
|
||||
|
||||
### Tests
|
||||
|
||||
- `loop.test.ts`: Reminder wird injiziert, erscheint im LLM-Call, **nicht**
|
||||
im `result.messages`.
|
||||
- `loop.test.ts`: Reminder ist pro Round unabhängig — Round 2 kriegt nicht
|
||||
Round 1's Reminder zurück.
|
||||
|
||||
### Rollout
|
||||
|
||||
Keine Flag-Gating nötig — Channel ist optional. Bestehende Caller, die
|
||||
ihn nicht setzen, verhalten sich identisch zu heute.
|
||||
|
||||
---
|
||||
|
||||
## Verbesserung 3 — Parallel-Execution für Read-Tools
|
||||
|
||||
### Was es macht
|
||||
|
||||
Wenn das LLM in einer Runde mehrere Tool-Calls zurückgibt und **alle**
|
||||
davon `policyHint: 'read'` sind, führt `runPlannerLoop` sie mit
|
||||
`Promise.all` parallel aus, Cap bei 10 gleichzeitigen Calls. Sobald
|
||||
ein Write oder Destructive im Batch ist: wie heute sequenziell.
|
||||
|
||||
Die Reihenfolge in `messages` bleibt **Source-Order** (wie das LLM sie
|
||||
gesendet hat), nicht Completion-Order. Debug-Log bleibt linear lesbar.
|
||||
|
||||
### Was es ermöglicht
|
||||
|
||||
- **Schnellere Multi-Read-Missions.** Eine Research-Mission mit 5 Read-
|
||||
Tools: heute 5× Read-Latenz sequenziell, künftig ~1× Latenz parallel.
|
||||
Realer Gewinn: Wall-Clock-Zeit pro Tick halbiert sich in den Fällen,
|
||||
wo es zählt.
|
||||
- **Freie Kapazität für Compactor und Policy-Gate.** Beide Verbesserungen
|
||||
von M1/M2 kosten Latenz; der Parallel-Gain gleicht das aus.
|
||||
- **Kein Risiko bei Writes.** Die Regel „Read-only parallel, Writes
|
||||
seriell" ist dieselbe wie in Claude Codes `gW5` — sie macht Consistency
|
||||
trivial, ohne dass das Modell darüber nachdenken muss.
|
||||
|
||||
### Heutiger Zustand (Problem)
|
||||
|
||||
[`packages/shared-ai/src/planner/loop.ts:172-188`](../../packages/shared-ai/src/planner/loop.ts#L172) — expliziter Code-Kommentar:
|
||||
|
||||
> „Parallel execution is a perfectly valid optimisation for pure-read tools
|
||||
> but we keep order here so the message log tells a linear story when the
|
||||
> user debugs a failure."
|
||||
|
||||
Das Argument ist legitim, aber der Message-Log kann Source-Order behalten,
|
||||
auch wenn die Calls parallel laufen. Wir verlieren nichts an Debug-Ergonomie.
|
||||
|
||||
### Neuer Zustand (Lösung)
|
||||
|
||||
In [`loop.ts`](../../packages/shared-ai/src/planner/loop.ts) wird der
|
||||
Tool-Exec-Block ersetzt:
|
||||
|
||||
```ts
|
||||
// Bestimme Parallel-Eligibility aus der Registry
|
||||
const policyHints = response.toolCalls.map(c => getPolicyHintByName(c.name));
|
||||
const allRead = policyHints.every(h => h === 'read');
|
||||
|
||||
if (allRead && response.toolCalls.length > 1) {
|
||||
// Cap 10: bei mehr Tools in Batches à 10
|
||||
const BATCH_SIZE = 10;
|
||||
const allResults: ExecutedCall[] = [];
|
||||
for (let i = 0; i < response.toolCalls.length; i += BATCH_SIZE) {
|
||||
const batch = response.toolCalls.slice(i, i + BATCH_SIZE);
|
||||
const results = await Promise.all(
|
||||
batch.map(async (call) => ({
|
||||
round: rounds,
|
||||
call,
|
||||
result: await onToolCall(call),
|
||||
})),
|
||||
);
|
||||
allResults.push(...results);
|
||||
}
|
||||
// Append in Source-Order (nicht Completion-Order)
|
||||
for (const ex of allResults) {
|
||||
executedCalls.push(ex);
|
||||
messages.push({
|
||||
role: 'tool',
|
||||
toolCallId: ex.call.id,
|
||||
content: JSON.stringify({ /* … */ }),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Sequenziell wie heute
|
||||
for (const call of response.toolCalls) {
|
||||
/* bestehend */
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Helper `getPolicyHintByName` kommt aus der Registry (lesbar, da in M1 eh
|
||||
integriert — Verbesserung 1 zieht die Policy-Information schon an die
|
||||
Loop-Grenze).
|
||||
|
||||
### Abhängigkeit
|
||||
|
||||
Braucht **Verbesserung 1** vorher, damit `policyHint` autoritativ
|
||||
verfügbar ist. Ohne Policy-Gate müsste die Loop die Hints aus der Registry
|
||||
direkt nachschlagen — nicht schlimm, aber die Abfolge ist sauberer.
|
||||
|
||||
### Aufwand
|
||||
|
||||
~2h Code + Test.
|
||||
|
||||
### Tests
|
||||
|
||||
- `loop.test.ts`: 3 Read-Calls → `Promise.all` wird aufgerufen, Wall-Clock
|
||||
~= max(read) statt sum(reads).
|
||||
- `loop.test.ts`: 2 Read + 1 Write → sequenzielle Abarbeitung.
|
||||
- `loop.test.ts`: 11 Read-Calls → 2 Batches (10 + 1), aber Source-Order in
|
||||
`messages` erhalten.
|
||||
|
||||
### Rollout
|
||||
|
||||
Keine Flag-Gating nötig. Verhalten ist strikt additiv (sequenzieller Pfad
|
||||
bleibt unverändert für gemischte Batches und für bestehende Caller, die
|
||||
keine Registry haben).
|
||||
|
||||
---
|
||||
|
||||
## Reihenfolge & Zeitplan
|
||||
|
||||
| Reihenfolge | Verbesserung | Aufwand | Voraussetzung |
|
||||
|-------------|---------------------------|-------------|---------------------|
|
||||
| 1. | Permission-Gate (§1) | 1 Tag | — |
|
||||
| 2. | Reminder-Channel (§2) | 4 h | — (parallel zu §1) |
|
||||
| 3. | Parallel-Reads (§3) | 2 h | §1 (für policyHint) |
|
||||
|
||||
**Gesamt: ~1.5 Arbeitstage.**
|
||||
|
||||
Die drei Verbesserungen sind bewusst *klein*. Der Plan ist:
|
||||
|
||||
1. Alle drei in einem Sprint zusammen mergen (eine PR pro Verbesserung,
|
||||
drei PRs gesamt).
|
||||
2. `POLICY_ENFORCE=false` starten (log-only), eine Woche beobachten.
|
||||
3. Im gleichen Zeitraum die ersten Reminder-Producer in `mana-ai`
|
||||
nachziehen (eigene kleine PRs, nicht Teil von M1).
|
||||
4. Flag flippen, Metriken prüfen (`policy_deny_total`, `parallel_read_batches_total`).
|
||||
|
||||
## Exit-Kriterien für M1
|
||||
|
||||
- [ ] `evaluatePolicy()` existiert in `@mana/tool-registry`, wird von beiden Consumern aufgerufen.
|
||||
- [ ] `POLICY_ENFORCE=true` läuft eine Woche in Staging ohne False-Positive-Rate > 1 %.
|
||||
- [ ] `runPlannerLoop` hat `reminderChannel`-API, Tests grün, mindestens ein Real-Producer live (z. B. Token-Budget-Reminder in `mana-ai`).
|
||||
- [ ] Multi-Read-Mission in `mana-ai` zeigt messbare Wall-Clock-Verkürzung in der Metrik `mana_ai_tick_duration_seconds` (Ziel: -30 % p95 bei Research-Missions).
|
||||
|
||||
## Danach
|
||||
|
||||
M2 (Context-Compressor + Haiku-Tier) und M3 (In-Process Sub-Agents +
|
||||
Persona-Runner) bauen auf allen drei M1-Primitiven auf — besonders der
|
||||
Reminder-Channel ist das Vehikel, über das M2's Compactor dem LLM mitteilen
|
||||
kann, dass komprimiert wurde. Details: siehe
|
||||
[`docs/reports/mana-agent-improvements-from-claude-code.md`](../reports/mana-agent-improvements-from-claude-code.md)
|
||||
§12 Roadmap.
|
||||
449
docs/reports/claude-code-architecture.md
Normal file
449
docs/reports/claude-code-architecture.md
Normal file
|
|
@ -0,0 +1,449 @@
|
|||
# Claude Code — Anatomie eines Agent-Harness
|
||||
|
||||
**Stand:** 2026-04-23
|
||||
**Quellenlage:** Reverse-Engineering / Leaks aus Community-Analysen (siehe §13)
|
||||
|
||||
> Technischer Bericht über die interne Architektur von Claude Code (Anthropics offiziellem CLI),
|
||||
> konsolidiert aus öffentlich dokumentierten Reverse-Engineering-Analysen des
|
||||
> minified `@anthropic-ai/claude-code`-Pakets sowie live mitgeschnittenen API-Roundtrips.
|
||||
|
||||
---
|
||||
|
||||
## Inhaltsverzeichnis
|
||||
|
||||
1. [Kontext zur Quellenlage](#1-kontext-zur-quellenlage)
|
||||
2. [System-Architektur](#2-system-architektur)
|
||||
3. [Prompt-System](#3-prompt-system)
|
||||
4. [Tool-System](#4-tool-system)
|
||||
5. [Sub-Agent-System (I2A / Task-Tool)](#5-sub-agent-system-i2a--task-tool)
|
||||
6. [Context-Management](#6-context-management)
|
||||
7. [Steering-Mechanismen](#7-steering-mechanismen)
|
||||
8. [Security & Sandboxing](#8-security--sandboxing)
|
||||
9. [Real-Time Steering: `h2A`](#9-real-time-steering-h2a)
|
||||
10. [UI/Terminal-Layer](#10-uiterminal-layer)
|
||||
11. [Memory & Todos](#11-memory--todos)
|
||||
12. [Model-Routing](#12-model-routing)
|
||||
13. [Bemerkenswerte Clever Tricks](#13-bemerkenswerte-clever-tricks)
|
||||
14. [Relevanz für das Mana-Monorepo](#14-relevanz-für-das-mana-monorepo)
|
||||
15. [Quellen](#15-quellen)
|
||||
|
||||
---
|
||||
|
||||
## 1. Kontext zur Quellenlage
|
||||
|
||||
Das ursprünglich viel zitierte Repo `shareAI-lab/analysis_claude_code` wurde inzwischen
|
||||
archiviert bzw. in `shareAI-lab/learn-claude-code` überführt — der neue Fokus liegt auf
|
||||
Didaktik („Harness Engineering"), nicht mehr auf den deobfuscierten Funktionsnamen. Die
|
||||
ursprünglichen Funktionsnamen (`nO`, `h2A`, `wU2`, `I2A`, `UH1`, `gW5`, `tU2`, `KN5` etc.)
|
||||
leben aber in mehreren Folge-Analysen weiter (BrightCoding Juli 2025, xugj520
|
||||
„Efficient Coder", PromptLayer-Blog, Medium Sujay Pawar).
|
||||
|
||||
`Yuyz0112/claude-code-reverse` geht einen komplementären Weg: Statt statische
|
||||
Code-Analyse monkey-patcht er `beta.messages.create` im installierten `cli.js` und loggt
|
||||
die echten API-Roundtrips — daraus lassen sich die System-Prompts, Tool-Definitionen und
|
||||
Modell-Routing-Entscheidungen direkt ablesen.
|
||||
|
||||
`Piebald-AI/claude-code-system-prompts` pflegt per Version ein vollständiges
|
||||
Prompt-Archiv. Version 2.1.117 hat ~110 Prompt-Strings, 24 Built-in-Tool-Beschreibungen
|
||||
und ~40 System-Reminder.
|
||||
|
||||
Die folgenden Befunde sind die konsolidierte Lesart über diese Quellen.
|
||||
|
||||
---
|
||||
|
||||
## 2. System-Architektur
|
||||
|
||||
Die Gesamtarchitektur ist ein vierlagiges Harness:
|
||||
|
||||
1. **UI Layer** — CLI (Ink/React), VSCode-Plugin, optionale Web-Frontends.
|
||||
2. **Agent Core Scheduling Layer** — `nO` (Master-Loop) plus `h2A` (asynchrone
|
||||
Message-Queue als Steering-Bus).
|
||||
3. **Execution Layer** — Streaming-Generator (`wu`), Context-Compressor (`wU2`),
|
||||
Tool-Execution-Engine (`MH1`), Tool-Scheduler (`UH1`/`gW5`).
|
||||
4. **Storage Layer** — Messages, komprimierte Summaries, CLAUDE.md-basierter
|
||||
Langzeitspeicher, `~/.claude/todos/{session}.json`.
|
||||
|
||||
Der zentrale Prozessor ist `nO`: ein single-threaded Master-Loop, implementiert als
|
||||
Generator-basierte async-Runtime. Das Muster ist das kanonische
|
||||
|
||||
```ts
|
||||
while (response.stop_reason === "tool_use") {
|
||||
executeTools();
|
||||
appendResults();
|
||||
recallModel();
|
||||
}
|
||||
```
|
||||
|
||||
das bei reinem Text terminiert. Um diesen Kern-Loop drapiert sich der gesamte Rest der
|
||||
Maschinerie. `tU2` ist in den Analysen der Name für den Konversations-Flow-Wrapper, der
|
||||
Messages in `nO` rein- und rausleitet und dabei System-Reminder einmischt.
|
||||
|
||||
### Flow-Diagramm
|
||||
|
||||
```
|
||||
User
|
||||
→ tU2 (conversation flow)
|
||||
→ System-Prompt-Zusammenbau + Reminder-Injection
|
||||
→ nO (master loop) ←→ h2A (steering bus)
|
||||
→ Tool-Call?
|
||||
→ UH1 Permission-Gate
|
||||
→ gW5 Concurrent-Scheduler (max 10)
|
||||
→ MH1 exec
|
||||
→ Results zurück in h2A
|
||||
→ nO → next iteration
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Prompt-System
|
||||
|
||||
Jeder Turn geht mit drei Schichten an die API:
|
||||
|
||||
1. **System-Prompt**: Claude-Code-Identität, Kernrichtlinien, Tool-Policies,
|
||||
Sandbox-Regeln, Git-Hinweise. Piebalds Archiv zeigt: mehrere Dutzend Strings werden
|
||||
*konditional* injiziert je nach Mode (Auto/Learning/Plan), IDE-Integration,
|
||||
aktiviertem Sandboxing etc.
|
||||
2. **User-Message** mit angehängtem `<system-reminder>`-Block, der den dynamischen
|
||||
Zustand trägt: Working-Directory, Git-Branch, Platform, Date, CLAUDE.md-Inhalte,
|
||||
Todo-Liste, File-Freshness-Warnungen, Skills-Liste.
|
||||
3. **Turn-spezifische Reminder** am Anfang und Ende einer Konversation
|
||||
(`system-reminder-start`, `system-reminder-end` bei Yuyz0112) — der Start-Reminder
|
||||
lädt Umgebungskontext, der End-Reminder prüft, ob Todos re-injiziert werden müssen.
|
||||
|
||||
### Besonderheit: CLAUDE.md-Disclaimer
|
||||
|
||||
Der CLAUDE.md-Block wird **mit einem expliziten Disclaimer** injiziert:
|
||||
|
||||
> „IMPORTANT: this context may or may not be relevant to your tasks. You should not
|
||||
> respond to this context unless it is highly relevant."
|
||||
|
||||
Claude Code gibt dem Modell also bewusst die Freiheit, den Projektkontext zu ignorieren.
|
||||
Das adressiert den klassischen Bug, dass lange System-Prompts das Modell zu rigiden
|
||||
Interpretationen zwingen.
|
||||
|
||||
---
|
||||
|
||||
## 4. Tool-System
|
||||
|
||||
Die ursprünglichen Analysen sprechen von „15 Kern-Tools"; die aktuelle Version 2.1.117
|
||||
laut Piebald-Archiv hat **24 Built-in-Tool-Beschreibungen**.
|
||||
|
||||
### Historische Kern-Menge
|
||||
|
||||
`View/Read`, `LS`, `Glob`, `Grep`, `Edit`, `Write/Replace`, `Bash`, `WebFetch`,
|
||||
`WebSearch`, `NotebookRead`, `NotebookEdit`, `TodoWrite`, `Task` (Sub-Agent-Launcher),
|
||||
`BatchTool` (historischer Vorläufer der heute impliziten Parallel-Tool-Calls),
|
||||
`exit_plan_mode`.
|
||||
|
||||
### Neuere Additionen
|
||||
|
||||
`EnterPlanMode`/`ExitPlanMode`, `Worktree`-Management, `TaskCreate`/`TaskUpdate`,
|
||||
`CronCreate`, `Computer` (Browser-Automation), `LSP`.
|
||||
|
||||
### Execution-Pipeline
|
||||
|
||||
- **`UH1` — Permission Gateway**: Statisch-strukturelle Prüfung vor Execution. Jeder
|
||||
Tool-Call wird gegen die Whitelist aus `settings.json` gemappt (z. B.
|
||||
`Bash(npm test:*)`, `Edit(src/**)`). Read-only Tools (`Read`, `Glob`, `Grep`, `LS`,
|
||||
`WebFetch`, `WebSearch`) sind per Default auto-approved. Writes und Bash-Befehle
|
||||
gehen durch Permission-Prompt, außer der Mode ist `acceptEdits`, `dontAsk` oder
|
||||
`bypassPermissions`. Permission-Modes-Katalog: `plan` (read-only), `default`,
|
||||
`acceptEdits`, `dontAsk`, `bypassPermissions`.
|
||||
- **`gW5` — Parallel Scheduler**: Concurrent-Executor mit **Maximum 10 parallelen
|
||||
Tool-Calls**. Wenn das Modell in einem Turn mehrere unabhängige Tool-Blocks
|
||||
zurückgibt (z. B. gleichzeitig `Grep` + `Glob` + `Read`), werden sie in einem Pool
|
||||
von maximal 10 Workern parallel abgearbeitet. Sobald ein Call ein Write ist oder
|
||||
Side-Effects hat, serialisiert `gW5` für diesen Block.
|
||||
- **`MH1` — Tool Execution Engine**: Der eigentliche Dispatcher mit dem Handler-Map
|
||||
`toolName → handler`.
|
||||
|
||||
### Performance-Messwerte (Reverse-Engineering)
|
||||
|
||||
| Metrik | Wert | Quelle |
|
||||
|---------------------------|---------------------|---------------------------|
|
||||
| `gW5`/`UH1`-Overhead | ~0.8 ms pro Tool | xugj520 (M2 Max) |
|
||||
| `h2A`-Throughput | >10 000 msg/s | xugj520 (M2 Max) |
|
||||
| Max parallele Tool-Calls | 10 | ComeOnOliver/Analysis |
|
||||
|
||||
Das sind Reverse-Engineering-Schätzwerte, nicht Anthropic-offiziell.
|
||||
|
||||
---
|
||||
|
||||
## 5. Sub-Agent-System (I2A / Task-Tool)
|
||||
|
||||
Das `Task`-Tool ruft `KN5` als Launcher auf, der einen neuen `I2A`-SubAgent-Kontext
|
||||
startet:
|
||||
|
||||
- **Fresh messages[]** — der Sub-Agent bekommt ein leeres History-Array, plus die aus
|
||||
dem Parent extrahierte Task-Description als initiale User-Message.
|
||||
- **Isolation**: eigene Tool-Permissions (restriktiver als der Parent, meist
|
||||
read-only + Grep/Glob), eigener Token-Budget, eigener v8-Isolate-ähnlicher Scope.
|
||||
- **Return-Contract**: Nur die finale Zusammenfassung des Sub-Agents wird als ein
|
||||
einzelnes `tool_result` in die Parent-History eingefügt — das Kernziel: „dirty
|
||||
context" (hunderte gescannte Files) bleibt im Kind, im Parent landet nur der
|
||||
Destillat-Absatz.
|
||||
- **Rekursionsgrenze**: Sub-Agents können **keine weiteren Sub-Agents** starten („One
|
||||
level deep, no further"). Maximale Parallelität in einem Batch: ebenfalls 10.
|
||||
- **Modell-Routing per Sub-Agent-Typ**: Der `Explore`-SubAgent läuft auf Haiku
|
||||
(billig + schnell, read-only-Recherche), `Plan`-Mode auf Sonnet, General-Purpose
|
||||
auf dem Parent-Modell.
|
||||
|
||||
---
|
||||
|
||||
## 6. Context-Management
|
||||
|
||||
### `wU2` — 92%-Threshold-Compressor
|
||||
|
||||
Die zentrale Komponente ist `wU2` — der **92%-Threshold-Compressor**. Sobald die
|
||||
Token-Nutzung der Konversation ca. 92 % des Context-Window-Limits erreicht, feuert
|
||||
`wU2` automatisch einen zusätzlichen API-Call mit einem speziellen
|
||||
`system-compact`-Prompt (per Yuyz0112 belegt).
|
||||
|
||||
Das Modell wird instruiert, den Verlauf nach einem festen Schema zu komprimieren:
|
||||
|
||||
- **Task Goal**
|
||||
- **Decisions Made**
|
||||
- **Files Changed**
|
||||
- **Current Progress**
|
||||
|
||||
werden explizit erhalten; verbatim-Details und intermediäre Tool-Outputs fallen weg.
|
||||
Die Analysen berichten Kompressionsraten von ~6.8×.
|
||||
|
||||
In neueren Versionen existiert zusätzlich ein „soft"-Trigger bei ~50 % Auslastung, der
|
||||
eine leichte Summary mit einbaut, ohne die Raw-Turns zu ersetzen.
|
||||
|
||||
### CLAUDE.md-Loading (8-Stage-Pipeline)
|
||||
|
||||
In der Praxis landen folgende Scopes gestapelt im System-Prompt:
|
||||
|
||||
1. `~/.claude/CLAUDE.md` (global)
|
||||
2. Parent-Dirs hoch zur Git-Root
|
||||
3. `./CLAUDE.md` (Projekt)
|
||||
4. `./CLAUDE.local.md` (user-local, gitignored)
|
||||
5. Sub-Directory CLAUDE.md-Dateien (beim Cd-ähnlichen Navigieren „imported")
|
||||
6. Auto-Memory aus `~/.claude/projects/{project}/memory/MEMORY.md`
|
||||
7. Session-spezifische Reminder
|
||||
8. Turn-spezifische Reminder (siehe §7)
|
||||
|
||||
Größe wird empfohlen unter 200 Zeilen zu halten.
|
||||
|
||||
---
|
||||
|
||||
## 7. Steering-Mechanismen
|
||||
|
||||
Dies ist der unterschätzte Teil. Claude Code injiziert zur Laufzeit kontinuierlich
|
||||
kleine Reminder-Blöcke ins nächste Turn, **ohne die Konversations-History zu
|
||||
mutieren** — sie kommen als transiente `<system-reminder>`-Tags im nächsten
|
||||
User-Message-Envelope:
|
||||
|
||||
| Mechanismus | Trigger | Effekt |
|
||||
|--------------------------|---------------------------------------------------|------------------------------------------------------------------------|
|
||||
| Todo-Re-Injection | Nach jedem `TodoWrite`-Call | Modell „sieht" seine Todo-Liste in *jedem* API-Request |
|
||||
| File-Freshness-Tracking | File modifiziert nach letztem `Read` | Blockiert nächsten `Edit`; injiziert „File modified"-Warning |
|
||||
| Stale-Todo-Reminder | Task-Tools zu lange nicht genutzt | Injiziert Hinweis, Planungs-Tools zu verwenden |
|
||||
| Hook-Notifications | `PreToolUse`, `PostToolUse`, `SessionStart/End` | User-Hooks triggern; Output wird als `<system-reminder>` zurückgespielt |
|
||||
|
||||
**Kernidee:** Der Assistant-Teil der History bleibt unberührt — was kritisch ist, weil
|
||||
jede Assistant-Mutation KV-Cache invalidieren würde. Cache-Stability wird explizit
|
||||
priorisiert.
|
||||
|
||||
---
|
||||
|
||||
## 8. Security & Sandboxing
|
||||
|
||||
xugj520 beschreibt ein **6-Layer-Permission-Framework**:
|
||||
|
||||
1. **UI-Input-Validation**
|
||||
2. **Prompt-Analyse auf Injection-Patterns**
|
||||
3. **`UH1`-Policy-Matching** gegen Whitelist
|
||||
4. **Pro-Tool-Arg-Validation** (z. B. Path-Canonicalization mit Blacklist außerhalb
|
||||
des Workspaces)
|
||||
5. **LLM-basierte Command-Injection-Detection** für Bash (ein separater Haiku-Call
|
||||
prüft `rm -rf /`, `curl | sh` etc. vor Execution)
|
||||
6. **Output-Filter/Redaction** (Secrets, Tokens)
|
||||
|
||||
Das Bash-Tool entstringt explizit Backticks und `$()`-Substitutionen in
|
||||
User-gelieferten Argumenten. MCP-Server laufen in einem Bridge-Modus mit pro-Policy
|
||||
entweder Docker- oder WASM-Isolation.
|
||||
|
||||
---
|
||||
|
||||
## 9. Real-Time Steering: `h2A`
|
||||
|
||||
`h2A` ist die namentlich auffälligste Komponente — **Dual-Buffer-Async-Message-Queue
|
||||
mit Promise-basiertem Async-Iterator und Backpressure**. Funktionell:
|
||||
|
||||
- **Zero-Latency-Delivery**: Der Producer (Streaming-API-Response-Chunks,
|
||||
Tool-Result-Events, User-Interjections) pusht in Buffer A, während der Consumer aus
|
||||
Buffer B liest. Swap passiert atomar — keine Lock-Kontention, keine Drops.
|
||||
- **Mid-Task-User-Interjections**: Der User kann *während* ein Tool läuft Input
|
||||
senden; `h2A` merged das in die nächste Iteration, ohne den Loop neu zu starten.
|
||||
Das erklärt, warum Claude Code auf Tastatureingaben reagiert, während es z. B. einen
|
||||
langen Bash-Command ausführt.
|
||||
- **Stream-JSON-I/O**: Externe Clients (IDE-Plugins, Remote-Control) sprechen Claude
|
||||
Code über line-delimited JSON-Events über stdin/stdout, die direkt in `h2A` gepumpt
|
||||
werden.
|
||||
|
||||
---
|
||||
|
||||
## 10. UI/Terminal-Layer
|
||||
|
||||
Ink (React-Renderer für Terminals) mit einem stark geforkten Custom-Reconciler via
|
||||
`react-reconciler`. Jeder Frame:
|
||||
|
||||
1. React-Commit
|
||||
2. In-memory DOM-Tree (DOMElement/TextNode)
|
||||
3. **Yoga (Facebook's Flexbox-Engine, als yoga.wasm eingebunden)** berechnet Layout
|
||||
4. `renderNodeToOutput` schreibt styled Characters in ein getyptes Screen-Buffer-Array
|
||||
5. Diff gegen den vorherigen Frame
|
||||
6. Minimale ANSI-Patches werden als single buffered write rausgeschrieben
|
||||
|
||||
**Primitive:** `ink-root`, `ink-box`, `ink-text`, `ink-raw-ansi`.
|
||||
Version 2.1.x enthält laut DeepWiki-Mirrors 130+ React-Komponenten.
|
||||
|
||||
---
|
||||
|
||||
## 11. Memory & Todos
|
||||
|
||||
`TodoWrite` ist bewusst *nicht* in-process persistiert — es schreibt einen JSON-File
|
||||
unter `~/.claude/todos/{session_id}.json`. Der Persistenz-Pfad erlaubt Session-Resume
|
||||
und Inter-Session-Kontinuität.
|
||||
|
||||
**Felder:** `id`, `content`, `status` (pending/in_progress/completed), `priority`.
|
||||
|
||||
Die Re-Injection (siehe §7) macht den Todo-File zum De-facto-Arbeitsspeicher-Format.
|
||||
Zusätzlich gibt's `MEMORY.md` (User-Memory, cross-session, per Projekt) und die
|
||||
„Dream"-/Kairos-Features in neueren Builds, die Session-Summaries verdichtet ablegen.
|
||||
|
||||
---
|
||||
|
||||
## 12. Model-Routing
|
||||
|
||||
Claude Code ist in Wahrheit ein Multi-Modell-System:
|
||||
|
||||
| Modell | Einsatz |
|
||||
|----------------|----------------------------------------------------------------------------|
|
||||
| **Sonnet** 4.x/4.7 | Default für den Haupt-Agent-Loop |
|
||||
| **Opus** | opt-in via `--model opus`; auto für Complex Debugging/Plan-Mode |
|
||||
| **Haiku** | Hochfrequente Background-Calls (siehe unten) |
|
||||
|
||||
### Haiku-Aufgaben
|
||||
|
||||
- **(a) Quota-Check beim Startup** — ein 1-Token-Dummy-Request mit Text „quota",
|
||||
gelingt nur, wenn Budget da ist
|
||||
- **(b) Topic-Detection** — nach jedem User-Input (entscheidet, ob der Terminal-Title
|
||||
geupdatet werden muss)
|
||||
- **(c) Session-Summarization** — beim Resume
|
||||
- **(d) Command-Injection-Detection** — pre-Bash
|
||||
- **(e) Auto-Compact-Fallback** — wenn der Primär-Compactor teuer wird
|
||||
|
||||
Yuyz0112s Logs zeigen: all das läuft über `beta.messages.create` mit explizitem
|
||||
`model: "claude-haiku-3.5"`.
|
||||
|
||||
**Quota-Handling ist proaktiv:** Die Haiku-Probe beim Start fängt 429-Fehler bevor der
|
||||
User die erste echte Query tippt; bei Budget-Exhaustion wird auf einen Reduced-Mode
|
||||
degradiert (kein Auto-Compact, kein Topic-Detection).
|
||||
|
||||
---
|
||||
|
||||
## 13. Bemerkenswerte Clever Tricks
|
||||
|
||||
Was technisch am interessantesten ist:
|
||||
|
||||
1. **Reminder-Injection statt History-Pollution** — Der gesamte Steuerungskanal
|
||||
(Todos, File-Freshness, Plan-Mode-Hints) läuft über transiente
|
||||
`<system-reminder>`-Tags im User-Turn. Der Assistant-Teil der History bleibt
|
||||
unberührt — was kritisch ist, weil jede Assistant-Mutation KV-Cache invalidieren
|
||||
würde. Cache-Stability wird explizit priorisiert.
|
||||
2. **Der 92%-Trigger vs. hartes Limit** — Anstatt bei 100 % zu crashen, wird bei
|
||||
92 % präventiv komprimiert. `wU2` ist eine Insurance-Policy, kein Notausgang.
|
||||
3. **`h2A`-Dual-Buffer mit User-Interjection** — Agent-Frameworks die man sonst sieht
|
||||
(LangGraph, CrewAI) sind turn-based. Claude Codes User-Interjection mitten im
|
||||
Tool-Call ist architektonisch der Unterschied zwischen „Chat-Loop" und
|
||||
„Interactive Shell".
|
||||
4. **Sub-Agent als Context-Laundering** — `I2A` ist nicht primär für Parallelisierung
|
||||
da, sondern um „dreckige" Kontexte aus der Parent-History zu isolieren. Pattern
|
||||
stammt wohl aus Reinforcement-Learning-Tradition: Episoden sauber halten.
|
||||
5. **LLM für Security** — Die Haiku-basierte Command-Injection-Detection ist ein
|
||||
bemerkenswerter Bruch mit klassischer Security-Praxis (Regex-Blacklists).
|
||||
Anthropic vertraut einem Modell, Angriffsmuster zu erkennen, die Regex nicht
|
||||
erwischt.
|
||||
6. **Der versteckte Quota-Ping** — Mit einem einzigen „quota"-Token-Request beim
|
||||
Start wird das Budget getestet, bevor der User auch nur getippt hat. Billig und
|
||||
clever.
|
||||
7. **yoga.wasm** — Dass ein CLI-Tool einen Flexbox-Engine aus dem
|
||||
React-Native-Umfeld als WASM einbindet, um Terminal-Layout zu rendern, ist
|
||||
technisch elegant overkill — und erklärt, wie Ink so robust mit Resize-Events
|
||||
umgeht.
|
||||
8. **Parallel-Tool-Policy ohne Write** — `gW5` parallelisiert nur Read-Tools. Sobald
|
||||
ein Write kommt, wird serialisiert. Das macht Consistency trivial, ohne dass das
|
||||
Modell überhaupt darüber nachdenken muss.
|
||||
9. **BatchTool wurde deprecated**, weil das Modell selbst gelernt hat, mehrere
|
||||
`tool_use`-Blocks in einem Response-Turn zurückzugeben — das Harness brauchte
|
||||
irgendwann keinen expliziten Batch-Wrapper mehr. Modell-Training hat das Feature
|
||||
wegtrainiert.
|
||||
10. **CLAUDE.md-Disclaimer** — Der „may or may not be relevant"-Disclaimer ist
|
||||
subtile Instruktions-Entkopplung: Anthropic möchte, dass CLAUDE.md *Kontext* ist,
|
||||
nicht *Befehl*.
|
||||
|
||||
### Kurzes Fazit
|
||||
|
||||
Das technisch Bemerkenswerte an Claude Code ist nicht eine einzelne Komponente,
|
||||
sondern die Konsequenz, mit der **alles, was nicht der Loop ist, zu Harness degradiert
|
||||
wurde**. `nO` ist 20 Zeilen. Der Rest — `h2A`, `wU2`, `UH1`, `gW5`, `I2A`, `KN5`,
|
||||
`tU2`, die Reminder-Injektion, die Haiku-Nebencalls — ist Environment-Engineering um
|
||||
einen minimalen, modellgetriebenen Kern herum. Die explizite Botschaft der
|
||||
Community-Analysen: Wer heute „Agents" baut und stattdessen in Rule-Trees und
|
||||
Prompt-Ketten denkt, hat das Grundpattern verfehlt.
|
||||
|
||||
---
|
||||
|
||||
## 14. Relevanz für das Mana-Monorepo
|
||||
|
||||
Direkte Ableitungen für laufende Initiativen:
|
||||
|
||||
- **`services/mana-mcp` (:3069)** — Der Reminder-Injection-Mechanismus aus §7 ist
|
||||
direkt übertragbar: Tool-Results könnten nicht nur rohe JSON-Payloads zurückgeben,
|
||||
sondern transiente `<system-reminder>`-Blöcke mit Space-Context, Tier-Status oder
|
||||
stale-Data-Warnungen. Siehe
|
||||
[`docs/plans/mana-mcp-and-personas.md`](../plans/mana-mcp-and-personas.md).
|
||||
- **`services/mana-ai` Mission-Runner** — Das `nO`/`h2A`-Pattern (single-threaded
|
||||
Master-Loop + Async-Steering-Bus) ist eine sauberere Alternative zu der
|
||||
cross-tick-Statemachine, die dort aktuell Gemini Deep Research orchestriert. Siehe
|
||||
`project_gemini_deep_research`-Memory.
|
||||
- **Shared Tool-Registry** (`packages/mana-tool-registry`) — Das Permission-Gateway
|
||||
(`UH1`) mit Whitelist-Matching ist ein brauchbares Mental-Model für die
|
||||
Tool-Authorization, die wir persona-scoped einführen müssen.
|
||||
- **Compression-Pattern (`wU2`)** — Für lange Sync-Logs oder Missions-Historie mit
|
||||
>50k Tokens sinnvoll: präventives Komprimieren bei 92 % Budget-Auslastung nach
|
||||
festem Schema (Goal / Decisions / Files / Progress).
|
||||
|
||||
---
|
||||
|
||||
## 15. Quellen
|
||||
|
||||
- [shareAI-lab/learn-claude-code](https://github.com/shareAI-lab/learn-claude-code)
|
||||
(Nachfolger von `analysis_claude_code`)
|
||||
- [Yuyz0112/claude-code-reverse](https://github.com/Yuyz0112/claude-code-reverse)
|
||||
(API-Monkey-Patch-Logs)
|
||||
- [Piebald-AI/claude-code-system-prompts](https://github.com/Piebald-AI/claude-code-system-prompts)
|
||||
(System-Prompt-Archiv v2.1.117)
|
||||
- [ComeOnOliver/claude-code-analysis](https://github.com/ComeOnOliver/claude-code-analysis)
|
||||
(17-Sektion-Dokumentation)
|
||||
- [Inside Claude Code: A Deep-Dive Reverse Engineering Report — BrightCoding (Juli 2025)](https://www.blog.brightcoding.dev/2025/07/17/inside-claude-code-a-deep-dive-reverse-engineering-report/)
|
||||
- [Claude Code Reverse Engineering v1.0.33 — Efficient Coder (xugj520)](https://www.xugj520.cn/en/archives/claude-code-reverse-engineering.html)
|
||||
- [Claude Code: Behind-the-scenes of the master agent loop — PromptLayer Blog](https://blog.promptlayer.com/claude-code-behind-the-scenes-of-the-master-agent-loop/)
|
||||
- [How Claude Code Actually Works — Sujay Pawar (Medium)](https://medium.com/@sujaypawar/how-claude-code-actually-works-1f6d4f1eea82)
|
||||
- [ZenML LLMOps Database: Claude Code Agent Architecture](https://www.zenml.io/llmops-database/claude-code-agent-architecture-single-threaded-master-loop-for-autonomous-coding)
|
||||
- [Reverse-Engineering Claude Code — sathwick.xyz](https://sathwick.xyz/blog/claude-code.html)
|
||||
- [Claude Code Architecture (Reverse Engineered) — vrungta.substack](https://vrungta.substack.com/p/claude-code-architecture-reverse)
|
||||
- [How Claude Code Uses React in the Terminal — DEV.to](https://dev.to/vilvaathibanpb/how-claude-code-uses-react-in-the-terminal-2f3b)
|
||||
- [Pan Xinghan: what the shareAI-lab analysis adds — Medium](https://medium.com/@sampan090611/claude-code-feels-like-a-senior-dev-heres-what-actually-makes-it-different-and-what-the-49c02b456d9c)
|
||||
|
||||
---
|
||||
|
||||
**Verwandte Berichte in diesem Repo:**
|
||||
|
||||
- [`docs/reports/ai-agent-architecture-comparison.md`](./ai-agent-architecture-comparison.md)
|
||||
- [`docs/reports/gemini-deep-research.md`](./gemini-deep-research.md)
|
||||
- [`docs/reports/web-research-capabilities.md`](./web-research-capabilities.md)
|
||||
631
docs/reports/mana-agent-improvements-from-claude-code.md
Normal file
631
docs/reports/mana-agent-improvements-from-claude-code.md
Normal file
|
|
@ -0,0 +1,631 @@
|
|||
# Mana-Agent-Infrastruktur — Verbesserungen aus den Claude-Code-Learnings
|
||||
|
||||
**Stand:** 2026-04-23
|
||||
**Voraussetzung:** [`claude-code-architecture.md`](./claude-code-architecture.md)
|
||||
|
||||
> Konkrete, priorisierte Verbesserungsvorschläge für unser Agent-Stack
|
||||
> (`services/mana-ai`, `services/mana-mcp`, `packages/mana-tool-registry`,
|
||||
> `packages/shared-ai`, Persona-Runner), abgeleitet aus den Patterns, die
|
||||
> Claude Code durch Reverse-Engineering exponiert hat.
|
||||
|
||||
---
|
||||
|
||||
## Inhalt
|
||||
|
||||
1. [Zusammenfassung](#1-zusammenfassung)
|
||||
2. [Ist-Stand: Wo steht unser Stack wirklich?](#2-ist-stand-wo-steht-unser-stack-wirklich)
|
||||
3. [Gap-Analyse gegen Claude Code](#3-gap-analyse-gegen-claude-code)
|
||||
4. [Verbesserung 1 — Permission-Gateway `UH1`-Style](#4-verbesserung-1--permission-gateway-uh1-style)
|
||||
5. [Verbesserung 2 — Reminder-Injection statt History-Pollution](#5-verbesserung-2--reminder-injection-statt-history-pollution)
|
||||
6. [Verbesserung 3 — Context-Compressor `wU2`-Style](#6-verbesserung-3--context-compressor-wu2-style)
|
||||
7. [Verbesserung 4 — Parallel-Execution `gW5`-Style](#7-verbesserung-4--parallel-execution-gw5-style)
|
||||
8. [Verbesserung 5 — Sub-Agent-Pattern `I2A`-Style](#8-verbesserung-5--sub-agent-pattern-i2a-style)
|
||||
9. [Verbesserung 6 — Haiku-Tier für Background-Tasks](#9-verbesserung-6--haiku-tier-für-background-tasks)
|
||||
10. [Verbesserung 7 — Async-Steering-Bus `h2A`-Style](#10-verbesserung-7--async-steering-bus-h2a-style)
|
||||
11. [Verbesserung 8 — Deprecated-Tool-Training](#11-verbesserung-8--deprecated-tool-training)
|
||||
12. [Roadmap und Priorisierung](#12-roadmap-und-priorisierung)
|
||||
13. [Explizit nicht übernehmen](#13-explizit-nicht-übernehmen)
|
||||
|
||||
---
|
||||
|
||||
## 1. Zusammenfassung
|
||||
|
||||
Claude Code ist im Kern ein **minimaler Agent-Loop mit sehr viel Environment-
|
||||
Engineering drumherum**. Unser Mana-Stack hat den Loop (`runPlannerLoop` in
|
||||
[`packages/shared-ai/src/planner/loop.ts`](../../packages/shared-ai/src/planner/loop.ts))
|
||||
und die Tool-Registry bereits sauber getrennt — aber fast das gesamte
|
||||
„drumherum" fehlt: Permission-Gating, Reminder-Injection, Context-Compression,
|
||||
parallele Tool-Execution, Sub-Agent-Isolation, Async-Steering.
|
||||
|
||||
Die gute Nachricht: unsere Architektur ist *vorbereitet*. Die Registry-
|
||||
Trennung (`@mana/tool-registry`, `@mana/shared-ai`), die saubere `ToolContext`-
|
||||
Abstraktion, die LWW-Projektionen — all das sind solide Fundamente, auf denen
|
||||
man die Claude-Code-Patterns inkrementell nachziehen kann, ohne den Stack
|
||||
umzubauen.
|
||||
|
||||
**Größter Impact-Hebel:** Reminder-Injection + Context-Compression.
|
||||
**Größtes Sicherheitsdefizit:** fehlendes Permission-Gate auf MCP-Ebene.
|
||||
**Größter Performance-Hebel:** Parallel-Tool-Execution bei Read-Tools.
|
||||
|
||||
---
|
||||
|
||||
## 2. Ist-Stand: Wo steht unser Stack wirklich?
|
||||
|
||||
### Die Haupt-Loop: `runPlannerLoop`
|
||||
|
||||
Unser Äquivalent zu Claude Codes `nO`-Master-Loop lebt in
|
||||
[`packages/shared-ai/src/planner/loop.ts:117-210`](../../packages/shared-ai/src/planner/loop.ts).
|
||||
Das Muster ist isomorph zu Claude Code:
|
||||
|
||||
```ts
|
||||
while (rounds < maxRounds) { // entspricht Claude's while-loop
|
||||
const response = await llm.complete(...); // entspricht stop_reason-Check
|
||||
if (response.toolCalls.length === 0) break; // terminiert bei Text
|
||||
for (const call of response.toolCalls) {
|
||||
await onToolCall(call); // entspricht MH1-Dispatch
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Abweichungen:**
|
||||
|
||||
- `DEFAULT_MAX_ROUNDS = 5` ([loop.ts:115](../../packages/shared-ai/src/planner/loop.ts#L115)) — Claude Code hat kein hartes Round-Limit, sondern ein Token-Limit.
|
||||
- Tool-Calls werden **sequenziell** abgearbeitet ([loop.ts:172-188](../../packages/shared-ai/src/planner/loop.ts#L172)) — explizit so dokumentiert: „Parallel execution is a perfectly valid optimisation for pure-read tools but we keep order here".
|
||||
- Kein Permission-Gate — `onToolCall` wird einfach aufgerufen.
|
||||
- Kein Reminder-Injection-Mechanismus — System + Prior + User, fertig.
|
||||
|
||||
### Mission-Runner: `mana-ai`
|
||||
|
||||
[`services/mana-ai/src/cron/tick.ts`](../../services/mana-ai/src/cron/tick.ts)
|
||||
(670 Zeilen) orchestriert den Loop im Background. Besonderheiten:
|
||||
|
||||
- **60-Sekunden-Tick statt event-driven** ([tick.ts:102-286](../../services/mana-ai/src/cron/tick.ts#L102)) — das Polling-Modell fängt DB-Changes nur mit Lag auf.
|
||||
- **Overlap-Guard** via Module-Level-Boolean `running` ([tick.ts:100](../../services/mana-ai/src/cron/tick.ts#L100)) — einfach aber funktioniert.
|
||||
- **Cross-Tick-State-Machine** für Deep Research ([tick.ts](../../services/mana-ai/src/cron/tick.ts), `handleDeepResearch`) — das einzige Feature, das „länger als ein Tick" überbrückt.
|
||||
- **Per-Agent-Concurrency** ([tick.ts:194-208](../../services/mana-ai/src/cron/tick.ts#L194)) — mit Budget-Gate auf Token-Ebene. Gut.
|
||||
- **Key-Grants** ([tick.ts, crypto/](../../services/mana-ai/src/crypto)) — RSA-OAEP-gewrappte MDKs pro Mission, TTL-clamped. Sehr solide.
|
||||
|
||||
### MCP-Gateway: `mana-mcp`
|
||||
|
||||
[`services/mana-mcp/src/`](../../services/mana-mcp/src) ist **bereits
|
||||
implementiert**, nicht nur geplant. 379 LOC total, stateless, JWT-gated.
|
||||
Tool-Registrierung in
|
||||
[`mcp-adapter.ts:81-124`](../../services/mana-mcp/src/mcp-adapter.ts#L81):
|
||||
|
||||
```ts
|
||||
for (const spec of getRegistry()) {
|
||||
if (!isExposable(spec)) continue; // filter admin-scoped
|
||||
server.tool(spec.name, spec.description, shape, invoke);
|
||||
}
|
||||
```
|
||||
|
||||
Das ist elegant — aber **`isExposable` ist die einzige Policy-Schicht**
|
||||
([mcp-adapter.ts:35-37](../../services/mana-mcp/src/mcp-adapter.ts#L35)). Es
|
||||
gibt keine Rate-Limits, keine pro-Request-Policy, keine User-Whitelist pro
|
||||
Tool, keine Command-Injection-Prüfung für freie Text-Felder.
|
||||
|
||||
### Tool-Registry: `@mana/tool-registry`
|
||||
|
||||
[`packages/mana-tool-registry/src/`](../../packages/mana-tool-registry/src)
|
||||
(rund 400 LOC). Sehr sauber:
|
||||
|
||||
- `ToolSpec<I, O>` mit Zod-Schemas ([types.ts:91-122](../../packages/mana-tool-registry/src/types.ts#L91))
|
||||
- `ToolContext` mit `userId`/`spaceId`/`jwt`/`invoker`/`getMasterKey` ([types.ts:58-74](../../packages/mana-tool-registry/src/types.ts#L58))
|
||||
- `registerTool` + `getRegistry` Singleton ([registry.ts](../../packages/mana-tool-registry/src/registry.ts))
|
||||
- `encryptedFields` als **deklaratives** Feld — nicht handler-intern. Genial für zukünftige CI-Drift-Checks gegen die web-app `crypto/registry.ts`.
|
||||
|
||||
**Aktuell abgedeckte Module:** `habits`, `spaces`, `todo`, `notes`, `journal`,
|
||||
`calendar`, `contacts`, `articles`, `missions`, `tags` ([types.ts:18-29](../../packages/mana-tool-registry/src/types.ts#L18)).
|
||||
Laut `mana-ai/CLAUDE.md`: 31 propose-Tools über 16 Module sind server-seitig
|
||||
sichtbar; 28 weitere auto-Tools leben ausschließlich in der Webapp.
|
||||
|
||||
### Persona-Runner
|
||||
|
||||
Nicht implementiert. Plan in
|
||||
[`docs/plans/mana-mcp-and-personas.md`](../plans/mana-mcp-and-personas.md).
|
||||
Wichtig: wir haben dort die Chance, die Sub-Agent-Patterns aus §8 **direkt
|
||||
richtig** zu bauen, statt nachträglich nachzurüsten.
|
||||
|
||||
---
|
||||
|
||||
## 3. Gap-Analyse gegen Claude Code
|
||||
|
||||
| Pattern (Claude Code) | Mana-Äquivalent | Status | Priorität |
|
||||
|-------------------------------------|-------------------------------------------------|----------------------------|-----------|
|
||||
| `nO` Master-Loop | `runPlannerLoop` | ✅ vorhanden, solide | — |
|
||||
| `MH1` Tool-Dispatcher | `onToolCall` + Registry-Handler | ✅ vorhanden | — |
|
||||
| `UH1` Permission-Gateway | nur `isExposable` Admin-Filter | ⚠️ stark lückenhaft | **hoch** |
|
||||
| `gW5` Parallel-Scheduler (max 10) | sequenziell | ❌ fehlt | mittel |
|
||||
| `wU2` 92%-Compressor | keinerlei Context-Kompression | ❌ fehlt | **hoch** |
|
||||
| `<system-reminder>` Reminder-Injection | User-Prompt-Concat, kein transientes Channel | ❌ fehlt | **hoch** |
|
||||
| `h2A` Async-Message-Queue | 60s-Tick, kein mid-task interrupt | ❌ fehlt | niedrig |
|
||||
| `I2A` Sub-Agent (Fresh-Context) | Persona-Runner (extern, geplant) | 🟡 im Plan, nicht isomorph | mittel |
|
||||
| File-Freshness-Tracking | n/a — wir editieren keine Files | — n/a | — |
|
||||
| Haiku für Background-Tasks | alle Calls gehen an mana-llm primary model | ❌ fehlt | mittel |
|
||||
| BatchTool deprecated | wir haben weder Batch noch parallel | — n/a | — |
|
||||
| CLAUDE.md-Disclaimer-Pattern | Agent-Context / Memory ohne Disclaimer | 🟡 improvement-worth | niedrig |
|
||||
|
||||
---
|
||||
|
||||
## 4. Verbesserung 1 — Permission-Gateway `UH1`-Style
|
||||
|
||||
### Problem
|
||||
|
||||
[`services/mana-mcp/src/mcp-adapter.ts:34-37`](../../services/mana-mcp/src/mcp-adapter.ts#L34)
|
||||
— der einzige Gate ist Scope-Filter:
|
||||
|
||||
```ts
|
||||
function isExposable(spec: AnyToolSpec): boolean {
|
||||
return spec.scope === 'user-space';
|
||||
}
|
||||
```
|
||||
|
||||
Das reicht nicht:
|
||||
|
||||
- Kein **pro-User-Opt-In** für gefährliche Tools (z. B. `habits.delete`).
|
||||
- Kein **Rate-Limit** pro User pro Tool (MCP ist JWT-gated, aber ein entwendeter JWT kann in 10 Sekunden 1000 Calls machen).
|
||||
- Kein **Path-/Content-Filter** für Freitext-Argumente (Tool `notes.create` mit `content` könnte Prompt-Injection ins Frontend tragen).
|
||||
- `destructive`-Policy-Hint ist **dokumentiert** ([types.ts:48](../../packages/mana-tool-registry/src/types.ts#L48)) aber nicht **durchgesetzt** — die Registry weiß, welches Tool destructive ist, aber niemand liest das an der Grenze.
|
||||
|
||||
### Vorschlag
|
||||
|
||||
Ein zentrales `evaluatePolicy()` in `@mana/tool-registry`:
|
||||
|
||||
```ts
|
||||
// packages/mana-tool-registry/src/policy.ts (neu)
|
||||
export interface PolicyDecision {
|
||||
allow: boolean;
|
||||
reason?: string;
|
||||
/** Optional: inject as <system-reminder> on next turn. */
|
||||
reminder?: string;
|
||||
}
|
||||
|
||||
export function evaluatePolicy(
|
||||
spec: AnyToolSpec,
|
||||
ctx: ToolContext,
|
||||
rawInput: unknown,
|
||||
opts: {
|
||||
userSettings?: { allowDestructive: boolean; perToolRateLimit?: number };
|
||||
recentInvocations?: readonly { toolName: string; at: Date }[];
|
||||
},
|
||||
): PolicyDecision;
|
||||
```
|
||||
|
||||
Aufgerufen wird sie in `mcp-adapter.ts` **vor** `spec.handler()` und — wichtig
|
||||
— auch in `mana-ai`s `onToolCall`-Callback. Damit ist die Policy an einer
|
||||
Stelle und für beide Consumer gültig.
|
||||
|
||||
**Konkrete Regeln für M1:**
|
||||
|
||||
- `policyHint: 'destructive'` → Default `deny`, User muss explizit in Settings
|
||||
opt-in (pro Tool oder pro Scope).
|
||||
- Rolling 60-Sekunden-Window: Cap bei 30 Calls/Tool/User/Minute auf MCP.
|
||||
- Für Tools mit Freitext-Argumenten (`content`, `description`, `note`): ein
|
||||
Zod `.refine()` das klassische Injection-Marker (`{{`, `<system`,
|
||||
`ignore previous`) erkennt und loggt — nicht blockiert, aber markiert.
|
||||
|
||||
### Aufwand
|
||||
|
||||
~1 Tag. Die Registry ist dafür gebaut.
|
||||
|
||||
---
|
||||
|
||||
## 5. Verbesserung 2 — Reminder-Injection statt History-Pollution
|
||||
|
||||
### Problem
|
||||
|
||||
In [`runPlannerLoop`](../../packages/shared-ai/src/planner/loop.ts#L131) wird
|
||||
die `messages`-History pro Round durch Assistant- und Tool-Turns erweitert —
|
||||
korrekt und nötig. Was **nicht** passiert: transienter Kontext (Token-Budget,
|
||||
Agent-Memory-Updates, User-Interjections, Mission-Deadline-Änderungen) wird
|
||||
entweder
|
||||
|
||||
1. in den System-Prompt eingebacken und bleibt dort ewig (veraltet), oder
|
||||
2. in den User-Prompt per String-Concatenation injiziert (mutiert die
|
||||
History, invalidiert KV-Cache, landet in Logs).
|
||||
|
||||
Die `<agent_context>`-Blöcke aus
|
||||
[`mana-ai` v0.5](../../services/mana-ai/CLAUDE.md) sind schon ein Schritt in
|
||||
die richtige Richtung, aber sie sind im System-Prompt und nicht transient.
|
||||
|
||||
### Vorschlag
|
||||
|
||||
**`ReminderChannel`** als neuer Input-Slot für `runPlannerLoop`:
|
||||
|
||||
```ts
|
||||
// packages/shared-ai/src/planner/loop.ts
|
||||
export interface PlannerLoopInput {
|
||||
// … bestehende Felder …
|
||||
/** Per-round transient hints. Called after every assistant turn;
|
||||
* injected as a fresh system message at the end of `messages` before
|
||||
* the next LLM call. NOT persisted in the returned message log. */
|
||||
readonly reminderChannel?: (roundIndex: number, state: LoopState) => string | null;
|
||||
}
|
||||
```
|
||||
|
||||
Die Reminder-Strings werden als transiente `{ role: 'system', content: '<reminder>…</reminder>' }`
|
||||
**vor jedem LLM-Call** eingefügt und **nach** dem Call wieder entfernt — sie
|
||||
leben nie in `messages`, landen nicht in der Iteration-History. Genau das
|
||||
Pattern von Claude Codes `<system-reminder>`-Tags.
|
||||
|
||||
**Use-Cases heute schon sinnvoll:**
|
||||
|
||||
- Token-Budget: „Du hast 80 % deines Mission-Budgets verbraucht. Plane Tool-Calls sparsam."
|
||||
- Mission-Timer: „Mission ist in 2 Minuten überfällig — priorisiere."
|
||||
- Zero-Knowledge-Mode: „User ist ZK — verbotene Tabellen werden nicht decrypted. Frag nicht nach."
|
||||
- Nach TodoWrite: aktuellen Todo-State echoen (wie in Claude Code, §7).
|
||||
- Stale-Data-Warning: „Letzter Sync vor 45 min — Daten könnten veraltet sein."
|
||||
|
||||
### Aufwand
|
||||
|
||||
~4h für die Loop-Änderung, ~2 Tage für die ersten drei Reminder-Producer.
|
||||
|
||||
### Warum wichtig
|
||||
|
||||
Das ist der **größte qualitative Hebel** — er wirkt sich auf jede einzelne
|
||||
Mission-Iteration aus, nicht nur auf Edge-Cases. Genau das, was Claude Code
|
||||
so feedback-sensitiv macht.
|
||||
|
||||
---
|
||||
|
||||
## 6. Verbesserung 3 — Context-Compressor `wU2`-Style
|
||||
|
||||
### Problem
|
||||
|
||||
Bei langlaufenden Missions (Deep Research, Multi-Round-Plans) wird die
|
||||
Iteration-History in `Mission.iterations[]` immer länger. Heute wird sie
|
||||
komplett in den `buildSystemPrompt()`-Call geschoben — irgendwann overflowed
|
||||
das den Context.
|
||||
|
||||
[`services/mana-ai/src/cron/tick.ts:211-221`](../../services/mana-ai/src/cron/tick.ts#L211)
|
||||
ruft `planOneMission`, das via `runPlannerLoop` alle Iterations durchreicht.
|
||||
**Kein** Abbruch, kein Pruning, keine Summary.
|
||||
|
||||
### Vorschlag
|
||||
|
||||
Einen dedizierten `compactHistory()` pro Mission-Lifecycle:
|
||||
|
||||
```ts
|
||||
// packages/shared-ai/src/planner/compact.ts (neu)
|
||||
export async function compactIterations(
|
||||
iterations: readonly MissionIteration[],
|
||||
llm: LlmClient,
|
||||
opts: { budgetTokens: number; maxInputTokens: number },
|
||||
): Promise<{ preserved: MissionIteration[]; summary: CompactSummary }>;
|
||||
```
|
||||
|
||||
**Trigger-Heuristik** (analog zum 92 %-Trigger):
|
||||
|
||||
- Wenn die kumulierte Token-Schätzung der `iterations[]` > `0.6 × maxInputTokens` → komprimieren.
|
||||
- Alle Iterations älter als die letzten 3 werden in eine einzelne **Compact-Iteration** gefasst mit dem Schema `{ goal, decisions, filesChanged, currentProgress }` (genau das, was Claude Code persistiert).
|
||||
- Die Compact-Iteration wird als synthetische Iteration mit `actor: { kind:'system', source:'compactor' }` in `Mission.iterations[]` geschrieben und die summierten Originale werden **archiviert** in einer neuen Tabelle `mana_ai.iteration_archive` (nicht gelöscht, nur nicht mehr Teil des Prompt-Contexts).
|
||||
|
||||
**Kompressionsrate** aus Claude Code: ~6.8× gemeldet. Bei uns realistisch
|
||||
~3-5×, weil Iterations schon strukturiert sind.
|
||||
|
||||
### Aufwand
|
||||
|
||||
~3-5 Tage inkl. Archiv-Tabelle und Migration.
|
||||
|
||||
### Wann sinnvoll
|
||||
|
||||
**Jetzt** für Deep-Research-Missions (die schon heute Token-Explosion
|
||||
riskieren), später für normale Multi-Round-Plans.
|
||||
|
||||
---
|
||||
|
||||
## 7. Verbesserung 4 — Parallel-Execution `gW5`-Style
|
||||
|
||||
### Problem
|
||||
|
||||
[`packages/shared-ai/src/planner/loop.ts:172-188`](../../packages/shared-ai/src/planner/loop.ts#L172) —
|
||||
Kommentar im Code:
|
||||
|
||||
> „Parallel execution is a perfectly valid optimisation for pure-read tools
|
||||
> but we keep order here so the message log tells a linear story when the
|
||||
> user debugs a failure."
|
||||
|
||||
Das Argument ist legitim für Debug-Ergonomie, kostet aber bei multi-Read-
|
||||
Plans linear Zeit. Mission mit 5 `read_*`-Tools: 5× LLM-Latency statt 1×.
|
||||
|
||||
### Vorschlag
|
||||
|
||||
Claude Codes `gW5`-Regel direkt übernehmen:
|
||||
|
||||
1. **Parallelisieren** wenn alle `toolCalls` einer Round `policyHint: 'read'` haben.
|
||||
2. **Serialisieren** sobald eine davon `write`/`destructive` ist.
|
||||
3. **Harte Grenze 10 parallel** — bei mehr: in Batches à 10.
|
||||
|
||||
```ts
|
||||
// packages/shared-ai/src/planner/loop.ts (patch)
|
||||
const allRead = calls.every(c => getPolicyHint(c.name) === 'read');
|
||||
if (allRead && calls.length > 1) {
|
||||
const results = await Promise.all(
|
||||
calls.slice(0, 10).map(call => onToolCall(call))
|
||||
);
|
||||
// … append to messages in source order, not completion order
|
||||
} else {
|
||||
for (const call of calls) { /* sequential */ }
|
||||
}
|
||||
```
|
||||
|
||||
Wichtig: Reihenfolge in `messages` bleibt **Source-Order**, nicht
|
||||
Completion-Order. Das erhält die Debug-Lesbarkeit, die der bisherige
|
||||
Kommentar schützen wollte — wir verlieren also nichts, gewinnen aber
|
||||
Wanduhr-Zeit.
|
||||
|
||||
### Aufwand
|
||||
|
||||
~2h. Die Information (`policyHint`) existiert bereits in der Registry.
|
||||
|
||||
### Voraussetzung
|
||||
|
||||
Verbesserung 1 (Policy-Gate) sollte vorher laufen, damit `policyHint` an der
|
||||
Loop-Grenze autoritativ ist.
|
||||
|
||||
---
|
||||
|
||||
## 8. Verbesserung 5 — Sub-Agent-Pattern `I2A`-Style
|
||||
|
||||
### Problem
|
||||
|
||||
Der Plan sieht den Persona-Runner als **eigenes Service** auf :3070 vor
|
||||
([`docs/plans/mana-mcp-and-personas.md`](../plans/mana-mcp-and-personas.md)).
|
||||
Das ist für Deployment-Isolation sinnvoll, aber es **ist nicht** das
|
||||
Claude-Code-Pattern.
|
||||
|
||||
Claude Codes `I2A` ist *in-process*:
|
||||
|
||||
- Fresh `messages[]` (kein Parent-History-Leak)
|
||||
- eigenes Token-Budget
|
||||
- eigene Tool-Permissions (restriktiver)
|
||||
- Parent kriegt **nur die finale Summary** zurück, nicht die Zwischenschritte
|
||||
- Rekursions-Grenze: 1 Level
|
||||
|
||||
### Vorschlag
|
||||
|
||||
**Zwei-Schichten-Modell**:
|
||||
|
||||
**(a) In-Process Sub-Loop** in `@mana/shared-ai`:
|
||||
|
||||
```ts
|
||||
// packages/shared-ai/src/planner/sub-agent.ts (neu)
|
||||
export async function runSubAgent(opts: {
|
||||
readonly parentLoop: { messages: readonly ChatMessage[]; spec: ToolSpec };
|
||||
readonly task: string;
|
||||
readonly allowedTools: readonly string[]; // Whitelist, restriktiver als Parent
|
||||
readonly maxRounds?: number; // Default 3
|
||||
readonly llm: LlmClient;
|
||||
readonly onToolCall: (call: ToolCallRequest) => Promise<ToolResult>;
|
||||
}): Promise<{ summary: string; usage: TokenUsage }>;
|
||||
```
|
||||
|
||||
Wird vom `Task`-ähnlichen Tool in der Registry aufgerufen. Rekursion wird
|
||||
über einen Depth-Counter im `ToolContext` verhindert
|
||||
(`ctx.subAgentDepth >= 1 → error`).
|
||||
|
||||
**(b) Persona-Runner als Out-of-Process Orchestrator** für Langläufer — der
|
||||
bleibt, wie im Plan, ein eigener Service. Aber: er ruft intern denselben
|
||||
`runSubAgent`-Code, nur mit höherem Round-Budget und Persona-spezifischen
|
||||
System-Prompt.
|
||||
|
||||
### Warum zweistufig
|
||||
|
||||
In-Process-Sub-Agents sind für **Context-Laundering** da (dirty Recherche-
|
||||
Kontext vom Parent fernhalten). Der Persona-Runner ist für **Langzeit-
|
||||
Lifecycles** (eine Persona lebt über mehrere Wochen). Beides braucht dasselbe
|
||||
primitive `runSubAgent`, aber andere Deployment-Modelle.
|
||||
|
||||
### Aufwand
|
||||
|
||||
~1 Woche.
|
||||
|
||||
---
|
||||
|
||||
## 9. Verbesserung 6 — Haiku-Tier für Background-Tasks
|
||||
|
||||
### Problem
|
||||
|
||||
Claude Code nutzt Haiku für hochfrequente Nebencalls:
|
||||
|
||||
- Quota-Check
|
||||
- Topic-Detection
|
||||
- Session-Summarization
|
||||
- Command-Injection-Detection
|
||||
- Auto-Compact-Fallback
|
||||
|
||||
Bei uns geht **jeder** Call an `mana-llm` mit dem Default-Modell — das ist
|
||||
für Routing-Entscheidungen ("ist dieser User-Input eine Frage oder eine
|
||||
Mission?") overkill und teuer.
|
||||
|
||||
### Vorschlag
|
||||
|
||||
`@mana/shared-ai` bekommt einen `TieredLlmClient`:
|
||||
|
||||
```ts
|
||||
// packages/shared-ai/src/planner/tiered-client.ts (neu)
|
||||
export function createTieredLlmClient(baseUrl: string): {
|
||||
primary: LlmClient; // für runPlannerLoop
|
||||
fast: LlmClient; // für Classification, Summarization, Guard
|
||||
};
|
||||
```
|
||||
|
||||
Konkrete Einsätze:
|
||||
|
||||
- **`compactIterations`** (§6) → `fast` statt `primary`. Spart 80 % Kosten
|
||||
beim Kompressor.
|
||||
- **Mission-Trigger-Klassifikation** statt Regex (heute
|
||||
[`tick.ts:73-82`](../../services/mana-ai/src/cron/tick.ts#L73)): statt
|
||||
`DEEP_RESEARCH_TRIGGER` als Regex ein Haiku-Call „Ist dieses Mission-
|
||||
Objective Deep Research?" — robuster und überrascht nicht bei neuen
|
||||
Formulierungen.
|
||||
- **Reminder-Producer** (§5): Der Token-Budget-Reminder wird via `fast`
|
||||
formuliert statt hartkodiert — variiert die Phrase pro Runde (weniger
|
||||
Prompt-Staleness).
|
||||
- **Command-Injection-Check** für Freitext-Tool-Args (in §4 erwähnt) →
|
||||
`fast`.
|
||||
|
||||
### Modell-Mapping in mana-llm
|
||||
|
||||
Wir müssen `mana-llm` einen `tier: 'primary' | 'fast'` Request-Parameter geben,
|
||||
der dann intern auf ein billigeres Modell routet (z. B. Ollama `llama3.1:8b`
|
||||
lokal für `fast`, Claude/Gemini-primary über Cloud für `primary`).
|
||||
|
||||
### Aufwand
|
||||
|
||||
~3 Tage, fast alles in `mana-llm`.
|
||||
|
||||
---
|
||||
|
||||
## 10. Verbesserung 7 — Async-Steering-Bus `h2A`-Style
|
||||
|
||||
### Problem
|
||||
|
||||
Unser Mission-Runner tickt alle 60 Sekunden
|
||||
([`tick.ts:102`](../../services/mana-ai/src/cron/tick.ts#L102)). Wenn der
|
||||
User mid-Mission etwas ändert (neues Objective, Mission pausieren, neuen
|
||||
Kontext hinzufügen), wird das erst im **nächsten** Tick sichtbar.
|
||||
|
||||
Claude Codes `h2A` ermöglicht User-Interjections *während* ein Tool läuft.
|
||||
Das ist für uns **nur teilweise** relevant — Missions sind explizit als
|
||||
Background-Jobs konzipiert — aber es gibt einen konkreten Use-Case:
|
||||
|
||||
### Konkreter Use-Case: Companion-Chat im Frontend
|
||||
|
||||
Die Webapp hat einen Companion-Chat (unified mana app). Der läuft interaktiv.
|
||||
Heute nutzt er vermutlich
|
||||
([`packages/shared-ai/src/planner/loop.ts`](../../packages/shared-ai/src/planner/loop.ts))
|
||||
direkt — also dieselbe sequenzielle Loop.
|
||||
|
||||
**Vorschlag:** `runPlannerLoop` bekommt einen optionalen `AbortSignal` und
|
||||
einen `InterruptChannel`:
|
||||
|
||||
```ts
|
||||
export async function runPlannerLoop(opts: {
|
||||
// … bestehend …
|
||||
readonly signal?: AbortSignal;
|
||||
readonly interruptChannel?: {
|
||||
readonly take: () => ChatMessage | null; // non-blocking pull
|
||||
};
|
||||
}): Promise<PlannerLoopResult>;
|
||||
```
|
||||
|
||||
Vor jedem nächsten LLM-Call: `const msg = interruptChannel?.take()` — falls
|
||||
vorhanden, als `user`-Message einfügen statt die Loop stumpf weiterlaufen
|
||||
zu lassen.
|
||||
|
||||
### Aufwand
|
||||
|
||||
~1 Tag.
|
||||
|
||||
### Ausdrücklich nicht tun
|
||||
|
||||
`h2A` **nicht** für `mana-ai`-Background-Missions einbauen. Der Tick-
|
||||
Ansatz ist für Server-side-Missions korrekt — User-Interjections kommen dort
|
||||
über den normalen Sync-Flow (Mission-Update → nächster Tick sieht es).
|
||||
|
||||
---
|
||||
|
||||
## 11. Verbesserung 8 — Deprecated-Tool-Training
|
||||
|
||||
### Problem
|
||||
|
||||
Wir haben aktuell 59+ Tools in der Registry/Shared-AI. Nicht alle sind
|
||||
gleich sinnvoll für LLMs zum Planen — manche sind redundant (`notes.create`
|
||||
vs. `notes.append_to_note` vs. `notes.update_note`), manche werden praktisch
|
||||
nie genutzt.
|
||||
|
||||
Claude Codes **BatchTool-Deprecation** ist instruktiv: Anthropic hat das
|
||||
Tool rausgenommen, weil das Modell selbst gelernt hat, mehrere `tool_use`-
|
||||
Blocks pro Turn zu senden — das Feature war wegtrainiert.
|
||||
|
||||
### Vorschlag
|
||||
|
||||
Einen monatlichen **Tool-Usage-Audit**:
|
||||
|
||||
- Metrik `mana_ai_tool_invocations_total{tool}` aus bereits existierenden Metrics
|
||||
- Report aller Tools unter Top-50-Percentile-Calls → Kandidat für Deprecation
|
||||
- Alternative: `mcp-adapter.ts` loggt den **vom Modell geforderten aber
|
||||
erfolglosen** Tool-Call — daraus wird sichtbar, welche Tools das Modell
|
||||
erfindet, weil der Name „intuitiv" wäre (z. B. `notes.delete` wenn wir nur
|
||||
`notes.archive` haben).
|
||||
|
||||
Das ist weniger eine Code-Änderung und mehr ein **Prozess**: alle 6 Wochen
|
||||
einen 1-Stunden-Review, Tools konsolidieren.
|
||||
|
||||
### Aufwand
|
||||
|
||||
0 für die Infra (Metrics sind da). 1h pro Audit-Zyklus.
|
||||
|
||||
---
|
||||
|
||||
## 12. Roadmap und Priorisierung
|
||||
|
||||
### M1 (2 Wochen)
|
||||
|
||||
| # | Verbesserung | Aufwand | Abhängigkeit |
|
||||
|---|------------------------------------|---------|--------------------|
|
||||
| 1 | Permission-Gateway (§4) | 1 Tag | — |
|
||||
| 2 | Reminder-Injection Loop-API (§5) | 4 h | — |
|
||||
| 3 | Parallel-Execution für Reads (§7) | 2 h | §4 (policyHint) |
|
||||
| 4 | Async-Steering im Companion (§10) | 1 Tag | — |
|
||||
|
||||
**M1-Outcome:** Sicherer MCP-Gateway, qualitativ bessere Mission-Planung durch
|
||||
Reminders, schnellere Multi-Read-Plans, Companion-Chat abbruchbar.
|
||||
|
||||
### M2 (3-4 Wochen)
|
||||
|
||||
| # | Verbesserung | Aufwand | Abhängigkeit |
|
||||
|---|--------------------------------------|----------|---------------|
|
||||
| 5 | Context-Compressor `wU2` (§6) | 3-5 Tage | §9 |
|
||||
| 6 | Haiku-Tier in `mana-llm` (§9) | 3 Tage | — |
|
||||
| 7 | Reminder-Producer Library (§5) | 2 Tage | M1 #2 |
|
||||
|
||||
**M2-Outcome:** Deep-Research-Missions skalieren, Background-Calls 80 %
|
||||
billiger, Reminder-Channel in Produktion.
|
||||
|
||||
### M3 (Persona-Runner)
|
||||
|
||||
| # | Verbesserung | Aufwand | Abhängigkeit |
|
||||
|---|--------------------------------------|-----------|---------------|
|
||||
| 8 | In-Process `runSubAgent` (§8) | 1 Woche | M1 #1, M2 #5 |
|
||||
| 9 | Persona-Runner nutzt `runSubAgent` | 2 Wochen | M3 #8 |
|
||||
|
||||
**M3-Outcome:** Sub-Agent-Pattern einheitlich, Persona-Runner kann
|
||||
komplexe Multi-Step-Personas orchestrieren, ohne Parent-Context zu
|
||||
verseuchen.
|
||||
|
||||
### Ongoing
|
||||
|
||||
| # | Verbesserung | Aufwand |
|
||||
|---|--------------------------------------|---------------------|
|
||||
| 10 | Tool-Usage-Audit (§11) | 1h alle 6 Wochen |
|
||||
|
||||
---
|
||||
|
||||
## 13. Explizit nicht übernehmen
|
||||
|
||||
Nicht jedes Claude-Code-Pattern macht für uns Sinn:
|
||||
|
||||
- **File-Freshness-Tracking** — wir editieren keine Dateien im
|
||||
Agent-Kontext. Das Äquivalent wäre „Sync-Freshness-Tracking", das aber
|
||||
schon durch `mana_sync` LWW-Semantik adressiert ist.
|
||||
- **`BatchTool` einführen** — das Claude-Code-Pattern ist, `BatchTool` zu
|
||||
*deprecaten*, weil das Modell nativ parallele `tool_use`-Blocks sendet.
|
||||
Wir sollten das direkt als Endzustand adoptieren (§7), nicht über ein
|
||||
Batch-Zwischenstadium gehen.
|
||||
- **yoga.wasm / Ink** — die Mana-Webapp ist SvelteKit, kein Terminal-UI.
|
||||
Das UI-Layer-Muster ist für uns irrelevant.
|
||||
- **`--bypassPermissions`-Mode** — für ein Multi-User-Produkt mit
|
||||
Zero-Knowledge-Option darf es kein Opt-Out aus der Policy geben.
|
||||
- **Der Haiku-Quota-Ping** — unser Billing läuft über `mana-credits`, wir
|
||||
sehen Quota deterministisch vor dem Call, nicht probabilistisch.
|
||||
|
||||
---
|
||||
|
||||
## Related
|
||||
|
||||
- [`claude-code-architecture.md`](./claude-code-architecture.md) — Technische Grundlage dieses Berichts
|
||||
- [`docs/plans/mana-mcp-and-personas.md`](../plans/mana-mcp-and-personas.md) — Ongoing-Plan für mana-mcp + persona-runner
|
||||
- [`docs/architecture/COMPANION_BRAIN_ARCHITECTURE.md`](../architecture/COMPANION_BRAIN_ARCHITECTURE.md) §20-22 — Agent-Design der Webapp
|
||||
- [`docs/reports/ai-agent-architecture-comparison.md`](./ai-agent-architecture-comparison.md) — Weiterer externer Vergleich
|
||||
Loading…
Add table
Add a link
Reference in a new issue