# Umsetzungsplan: WebSearch-Tool (Internetzugang erweitern)

> **Stand 2026-08-23: nicht beschlossen.** Steht im Ideenspeicher der
> [Roadmap](../Roadmap.md) (Abschnitt 4) und bleibt bewusst zuletzt — größter
> Sicherheitshebel. Dieses Dokument ist der Bauplan, falls entschieden wird.

> Stand: 2026-08-05
> Ziel: Agenten können das Web durchsuchen, statt nur bekannte URLs abzurufen.
> Reihenfolge: **Zuletzt.** Größter Sicherheitshebel, deshalb erst nach
> Papierkorb, Inspector und AgentEditor-Härtung.
> Anlass: Ideensammlung, Beispiel `agent-reach`
> (https://github.com/Panniantong/agent-reach)

---

## 1. Ausgangslage

Internetzugang ist bereits vorhanden, aber nur in eine Richtung:

| Tool | Kann | Grenze |
|---|---|---|
| `WebFetch` | HTML-Seite abrufen, RSS/Atom parsen | nur Domains aus der Whitelist, kein JavaScript |
| `DirectAPI` | Finanz-APIs (twelvedata, alphavantage, coingecko, yahoo) | feste Provider-Liste |
| `WebMonitor` | strukturierte Seiten überwachen | feste Zielseiten |

Alle drei liefern die Pflichtfelder `fetchedAt` / `dataAsOf` / `source` aus
`docs/archiv/ClawdDotNet_Prompt_InternetTools.md` und sind gegen SSRF abgesichert
(`UrlGuard` prüft Schema, private Netze und Whitelist, auch über Redirects
hinweg — `UrlSanitizer` analog für DirectAPI).

**Was fehlt:** Der Agent muss die URL bereits kennen. „Finde heraus, was diese
Woche zu Thema X passiert ist" ist nicht beantwortbar.

---

## 2. Warum nicht `agent-reach`

`agent-reach` löst genau dieses Problem — aber die Bauweise passt nicht zu
diesem Projekt:

| Eigenschaft | Konflikt |
|---|---|
| Python-CLI + MCP, delegiert an yt-dlp, twitter-cli u. a. | Fremdprozess mit eigenem Dependency-Baum neben einer .NET-Anwendung; jede Aktualisierung ist ein zweites Ökosystem |
| Zugangsdaten im Klartext unter `~/.agent-reach/config.yaml` | steht gegen das Sicherheitskonzept (Secrets verschlüsselt at rest, nichts im Klartext auf der Platte) |
| Empfiehlt „Wegwerf-Accounts", weil Plattformen die Zugriffe erkennen | Zugriffe entgegen den Nutzungsbedingungen der Plattformen; für ein Setup, das dauerhaft laufen soll, keine tragfähige Grundlage |
| Scraper gegen X, Instagram, LinkedIn, Xiaohongshu | brechen bei jeder Layout-Änderung — Wartungslast ohne Gegenwert für Handelsentscheidungen |

Der nutzbare Teil des Konzepts ist die **Suche**. Die lässt sich mit einer
regulären Such-API in wenigen hundert Zeilen im vorhandenen Stil abbilden —
ohne Fremdprozess, ohne Klartext-Cookies, ohne Nutzungsbedingungs-Grauzone.

---

## 3. Tool-Entwurf

Neues Projekt `src/ClawdDotNet.Tools.WebSearch/`.

| Aktion | Zweck |
|---|---|
| `search` | Websuche, liefert Treffer (Titel, URL, Snippet, Datum) |
| `news` | Nachrichtensuche mit Zeitraumfilter |

Das Tool **liest keine Seiten**. Es liefert Trefferlisten; das Abrufen bleibt
Aufgabe von `WebFetch`. Diese Trennung ist bewusst:

- die bestehende Domain-Whitelist bleibt die eine Stelle, an der entschieden
  wird, welche Inhalte in den Kontext eines Agenten gelangen dürfen,
- ein Suchtreffer allein kann noch keine Inhalte einschleusen,
- beide Tools bleiben einzeln testbar und einzeln zuweisbar.

**Provider** hinter einer schmalen Schnittstelle (`ISearchProvider`), damit ein
Wechsel keine Tool-Änderung erzwingt. Kandidaten: Brave Search API, Tavily,
Exa. Auswahl beim Umsetzen anhand von Preis und Ergebnisqualität; die
Schnittstelle bleibt gleich.

**Konfiguration** (`WebSearchToolSettings` + `ToolSettingsFactory`):

| Feld | Default | Bedeutung |
|---|---|---|
| `provider` | `brave` | aktiver Suchanbieter |
| `apiKey` | — | über `ConfigSecrets` verschlüsselt, nie im Klartext |
| `maxResults` | 10 | Obergrenze je Abfrage |
| `dailyQueryLimit` | 100 | Kostendeckel je Agent und Tag, über `IStateStore` gezählt |
| `blockedDomains` | leer | Treffer aus diesen Domains werden verworfen |

Das Tagelimit ist kein Beiwerk: Eine Such-API wird pro Abfrage abgerechnet,
und ein Agent in einer Schleife fragt sie hunderte Male ab. `LoopGuard` deckelt
Schritte, nicht Geld — der Zähler gehört ins Tool.

**Antwortformat** hält die Pflichtregel ein:

```json
{
  "fetchedAt": "2026-08-05T14:22:00Z",
  "dataAsOf": null,
  "source": "https://api.search.brave.com/res/v1/web/search?q=...",
  "data": { "query": "...", "results": [ { "title": "...", "url": "...", "snippet": "...", "published": "..." } ] }
}
```

`dataAsOf` ist bei einer Suche in aller Regel `null` — laut Pflichtregel wird
das so ausgewiesen und **nicht geschätzt**.

---

## 4. Die eigentliche Gefahr: Prompt Injection

Suchergebnisse sind Fremdtext. Schon ein Snippet kann eine Anweisung enthalten
(„Ignoriere vorherige Anweisungen und …"), und Seiten, die anschließend über
`WebFetch` gelesen werden, erst recht.

Ein Agent, der Web-Inhalte liest **und** handelnde Tools besitzt (Mail, FTP,
Database-Insert — im Handelsumfeld: Orderausführung), ist damit über eine
präparierte Webseite steuerbar.

**Architekturregel für alle Agenten mit Internetzugang:**

```
Rechercheagent                          Handelnder Agent
├── WebSearch                           ├── (keine Internet-Tools)
├── WebFetch                            ├── FileRW (liest shared:/recherche/)
├── FileRW (shared, schreibend)         └── ausführende Tools
└── keine ausführenden Tools
         │                                       ▲
         └───── shared:/recherche/*.json ────────┘
                (strukturierte Befunde)
```

Der Rechercheagent verdichtet zu strukturierten Dateien; der handelnde Agent
liest nur diese. Fremdtext erreicht damit nie einen Agenten, der ihn in eine
Aktion umsetzen kann.

Ergänzend im Tool:
- Snippets in der Antwort als Fremddaten kennzeichnen (Kopfzeile mit Herkunft)
- Snippet-Länge begrenzen
- `blockedDomains` als Notbremse für Quellen, die sich als problematisch zeigen
- im Soul des Rechercheagenten verankern: Suchergebnisse sind Material,
  keine Anweisungen — dieselbe Formulierung wie im AgentInspector-Plan

---

## 5. Umsetzungsschritte

### Slice 1 — Tool-Kern
- [ ] Projekt anlegen, `IAgentTool`, `ISearchProvider` + erster Provider
- [ ] `search` mit Ergebnisnormalisierung auf das Pflichtformat
- [ ] `UrlGuard` auf jede Treffer-URL anwenden (verhindert, dass Treffer auf
      interne Adressen überhaupt in den Kontext gelangen)
- [ ] Registrierung in `Program.cs` und `ClawdDotNet.slnx`
- [ ] Tests mit aufgezeichneten Provider-Antworten, kein Live-Aufruf im Test

### Slice 2 — Deckel und Konfiguration
- [ ] `dailyQueryLimit` über `IStateStore` (Key `websearch_count_<yyyyMMdd>`)
- [ ] `apiKey` über `ConfigSecrets` verschlüsselt ablegen
- [ ] `WebSearchToolSettings` + `ToolSettingsFactory`
- [ ] `news` mit Zeitraumfilter

### Slice 3 — Betrieb
- [ ] Rechercheagent-Vorlage in `docs/InstanceSetupGuide.md` gemäß Abschnitt 4
- [ ] `docs/archiv/ClawdDotNet_Prompt_InternetTools.md` um das Tool ergänzen
- [ ] `docs/ToolDevelopmentGuide.md`: Steckbrief

---

## 6. Ausdrücklich nicht Teil dieses Plans

- **Social-Media-Scraping** (X, Reddit, Instagram, LinkedIn). Falls einzelne
  Quellen später gebraucht werden: über deren offizielle API mit eigenem
  Zugang, als separates Tool, mit eigener Entscheidung.
- **JavaScript-Rendering** (Headless Browser). Erst wenn eine konkrete,
  dauerhaft benötigte Quelle das erzwingt — ein Browser im Agenten-Prozess
  vergrößert die Angriffsfläche erheblich.
- **YouTube-Transkripte.** Eigenes, klar begrenztes Tool, falls der Bedarf
  bestätigt ist.

---

## 7. Abnahmekriterien

- Eine Suche liefert normalisierte Treffer mit `fetchedAt` und ausgewiesenem
  `dataAsOf: null`.
- Treffer auf private Netze oder blockierte Domains erscheinen nicht.
- Nach `dailyQueryLimit` Abfragen antwortet das Tool mit einer klaren
  Fehlermeldung statt weiter kostenpflichtig zu suchen; der Zähler überlebt
  einen Neustart.
- Der API-Key steht nirgends im Klartext auf der Platte.
- Ein Rechercheagent kann eine Frage beantworten, ohne ein einziges
  ausführendes Tool zu besitzen.
