Kernkonzept · Architektur

03 — Die Drei-Schichten-Architektur

Verstehe das Fundament des Claw Code Projekts: Wie Spiegelung, Orchestrierung und Infrastruktur zusammenspielen, um einen robusten KI-Agent-Harness zu bilden.

⏱️ ~25 Minuten 📚 6 Abschnitte 🔧 Fortgeschritten
🏗️

Architektur im Überblick

Die klare Trennung in drei Schichten ermöglicht modulare Erweiterung, einfaches Testen und nachvollziehbare Datenflüsse.

Claw Code folgt einem strengen Drei-Schichten-Modell. Jede Schicht hat eine definierte Verantwortung und kommuniziert nur über klar definierte Schnittstellen mit den anderen Schichten. Diese Architektur entspricht dem Dependency Inversion Principle: Abhängigkeiten zeigen immer nach innen, zur Infrastruktur hin.

1
Spiegelungsschicht Daten
Unveränderliche JSON-Snapshots als Quelle der Wahrheit. Lädt ~150 Befehle und ~100 Tools in typisierte Python-Objekte.
commands.py tools.py reference_data/*.json
2
Orchestrierungsschicht Logik
Das "Gehirn" des Systems. Token-basiertes Routing, Session-Management, Multi-Turn-Loops und Budget-Kontrolle.
runtime.py query_engine.py
3
Infrastrukturschicht System
Alles was das System am Laufen hält: Umgebungserkennung, Persistenz, Trust-Gating und Bootstrap-Sequenz.
setup.py context.py session_store.py
💡
Architektur-Prinzip
Die Schichten sind unidirektional gekoppelt: Die Orchestrierungsschicht nutzt die Spiegelungsschicht, die Infrastrukturschicht stellt Dienste für beide bereit. Nie umgekehrt.
📦

Spiegelungsschicht

Die unveränderliche Datengrundlage — archivierte Snapshots als typisierte Python-Objekte.

Die Spiegelungsschicht ist die Quelle der Wahrheit für alle Befehle und Tools. Sie lädt archivierte JSON-Dateien und stellt sie als unveränderliche (frozen) Dataclasses bereit. Dies ermöglicht einen schnellen, typisierten Zugriff ohne externe Datenbank.

commands.py

Dieses Modul verwaltet die ~150 gespiegelten Befehle aus dem TypeScript-Original:

Python
# src/commands.py — Kernfunktionen

@lru_cache(maxsize=1)
def load_command_snapshot() -> tuple[PortingModule, ...]:
    # Lädt ~150 Befehle aus JSON, gecached für Performance
    raw_entries = json.loads(SNAPSHOT_PATH.read_text())
    return tuple(
        PortingModule(
            name=entry['name'],
            responsibility=entry['responsibility'],
            source_hint=entry['source_hint'],
            status='mirrored',
        )
        for entry in raw_entries
    )

PORTED_COMMANDS = load_command_snapshot()

def get_command(name: str) -> PortingModule | None:
    # O(1) Lookup durch Tuple-Iteration
    needle = name.lower()
    for module in PORTED_COMMANDS:
        if module.name.lower() == needle:
            return module
    return None

Wichtige Eigenschaften:

  • LRU-Cache — Die Snapshot-Ladung wird gecached (maxsize=1), da sich die Daten zur Laufzeit nicht ändern
  • Frozen DataclassesPortingModule ist immutable — keine versehentlichen Mutationen
  • Tuple-Struktur — Unveränderliche Sequenz statt Liste für Thread-Safety

tools.py

Strukturell identisch zu commands.py, aber mit zusätzlicher Berechtigungslogik:

Python
# src/tools.py — Erweiterte Filterung

def filter_tools_by_permission_context(
    tools: tuple[PortingModule, ...],
    permission_context: ToolPermissionContext | None = None
) -> tuple[PortingModule, ...]:
    # Filtert Tools basierend auf Permission-Denials
    if permission_context is None:
        return tools
    return tuple(
        module for module in tools 
        if not permission_context.blocks(module.name)
    )

def get_tools(
    simple_mode: bool = False,
    include_mcp: bool = True,
    permission_context: ToolPermissionContext | None = None,
) -> tuple[PortingModule, ...]:
    # Flexible Tool-Selektion mit Filtern
    tools = list(PORTED_TOOLS)
    if simple_mode:
        tools = [m for m in tools if m.name in 
                 {'BashTool', 'FileReadTool', 'FileEditTool'}]
    if not include_mcp:
        tools = [m for m in tools if 'mcp' not in m.name.lower()]
    return filter_tools_by_permission_context(tuple(tools), permission_context)
🎯
Core Konzept: Unveränderlichkeit
Die Verwendung von frozen=True in Dataclasses und Tupeln statt Listen garantiert, dass die gespiegelten Daten während der gesamten Laufzeit konsistent bleiben. Keine Race Conditions, keine Überraschungen.

Die Referenzdaten

src/reference_data/
  • commands_snapshot.json # ~150 Befehle
  • tools_snapshot.json # ~100 Tools
  • __init__.py

Die JSON-Dateien werden aus dem ursprünglichen TypeScript-System extrahiert und archiviert. Sie enthalten:

  • Name — Der Befehls-/Tool-Name
  • Responsibility — Beschreibung der Funktionalität
  • Source-Hint — Herkunft (welches Modul definiert dies)
⚙️

Orchestrierungsschicht

Das "Gehirn" des Systems — Routing, Sessions und Ausführungskontrolle.

Hier passiert die Intelligenz des Systems. Die Orchestrierungsschicht nimmt Benutzer-Prompts entgegen, findet passende Befehle und Tools, verwaltet Sessions und koordiniert die Ausführung.

runtime.py

Das Herzstück: PortRuntime mit dem Token-basierten Routing-Algorithmus:

Python
# src/runtime.py — Der Routing-Algorithmus

class PortRuntime:
    def route_prompt(self, prompt: str, limit: int = 5) -> list[RoutedMatch]:
        # 1. Tokenisierung: Normalisierung des Prompts
        tokens = {
            token.lower() 
            for token in prompt.replace('/', ' ').replace('-', ' ').split() 
            if token
        }
        
        # 2. Scoring für Commands und Tools
        by_kind = {
            'command': self._collect_matches(tokens, PORTED_COMMANDS, 'command'),
            'tool': self._collect_matches(tokens, PORTED_TOOLS, 'tool'),
        }
        
        # 3. Fair-Share: Je ein bester Treffer pro Kategorie
        selected: list[RoutedMatch] = []
        for kind in ('command', 'tool'):
            if by_kind[kind]:
                selected.append(by_kind[kind].pop(0))
        
        # 4. Auffüllung nach Score bis zum Limit
        leftovers = sorted(
            [m for matches in by_kind.values() for m in matches],
            key=lambda item: (-item.score, item.kind, item.name),
        )
        selected.extend(leftovers[: max(0, limit - len(selected))])
        return selected[:limit]

    @staticmethod
    def _score(tokens: set[str], module: PortingModule) -> int:
        # Scoring: Wie viele Tokens matchen?
        haystacks = [
            module.name.lower(),
            module.source_hint.lower(), 
            module.responsibility.lower()
        ]
        return sum(
            1 for token in tokens 
            if any(token in h for h in haystacks)
        )

Der Routing-Algorithmus im Detail:

PhaseBeschreibungBeispiel
1. TokenisierungPrompt wird normalisiert"git-branch erstellen"{"git", "branch", "erstellen"}
2. ScoringJeder Treffer erhält PunkteName-Match: +1, Source-Hint: +1
3. Fair-ShareMindestens ein Command + ein ToolGarantiert Vielfalt
4. SortierungRest nach Score sortiertHöchster Score zuerst

query_engine.py

Verwaltet Sessions, Nachrichten-Transkripte und Budget-Kontrolle:

Python
# src/query_engine.py — Session-Management

@dataclass
class QueryEnginePort:
    manifest: PortManifest
    config: QueryEngineConfig = field(default_factory=QueryEngineConfig)
    session_id: str = field(default_factory=lambda: uuid4().hex)
    mutable_messages: list[str] = field(default_factory=list)
    total_usage: UsageSummary = field(default_factory=UsageSummary)
    transcript_store: TranscriptStore = field(default_factory=TranscriptStore)

    def submit_message(self, prompt: str, ...) -> TurnResult:
        # Budget-Prüfung vor Verarbeitung
        if len(self.mutable_messages) >= self.config.max_turns:
            return TurnResult(stop_reason='max_turns_reached', ...)
        
        # Verarbeitung und Usage-Tracking
        projected_usage = self.total_usage.add_turn(prompt, output)
        
        # Budget-Überschreitung?
        total = projected_usage.input_tokens + projected_usage.output_tokens
        if total > self.config.max_budget_tokens:
            stop_reason = 'max_budget_reached'
        
        self.compact_messages_if_needed()
        return TurnResult(...)

    def persist_session(self) -> str:
        # Speichert Session nach .port_sessions/
        return save_session(StoredSession(...))

TurnResult enthält:

  • prompt — Der ursprüngliche Prompt
  • matched_commands/tools — Gefundene Befehle und Tools
  • permission_denials — Blockierte Tools (z.B. Bash)
  • usage — Token-Verbrauch
  • stop_reason — Warum der Turn endete
bash
# Routing ausprobieren
$ python3 -m src.main route "Erstelle einen Git-Branch"

Routed Matches (5):
1. [command] git-branch (Score: 2) — git/commands
2. [tool] Bash (Score: 1) — tools/shell
3. [command] git-checkout (Score: 1) — git/commands
4. [command] git-status (Score: 1) — git/commands
5. [tool] FileReadTool (Score: 0) — tools/filesystem
🔧

Infrastrukturschicht

Das Fundament — Umgebung, Persistenz und sichere Initialisierung.

Die Infrastrukturschicht stellt alle Dienste bereit, die das System zum Laufen braucht. Sie kennt keine Geschäftslogik, sondern bietet reine technische Services.

context.py

Erfasst den Workspace-Kontext zur Laufzeit:

Python
# src/context.py — Workspace-Kontext

@dataclass(frozen=True)
class PortContext:
    source_root: Path
    tests_root: Path
    assets_root: Path
    archive_root: Path
    python_file_count: int
    test_file_count: int
    asset_file_count: int
    archive_available: bool

def build_port_context(base: Path | None = None) -> PortContext:
    root = base or Path(__file__).resolve().parent.parent
    source_root = root / 'src'
    tests_root = root / 'tests'
    assets_root = root / 'assets'
    archive_root = root / 'archive' / 'claude_code_ts_snapshot' / 'src'
    
    return PortContext(
        source_root=source_root,
        tests_root=tests_root,
        assets_root=assets_root,
        archive_root=archive_root,
        python_file_count=sum(1 for p in source_root.rglob('*.py') if p.is_file()),
        test_file_count=sum(1 for p in tests_root.rglob('*.py') if p.is_file()),
        asset_file_count=sum(1 for p in assets_root.rglob('*') if p.is_file()),
        archive_available=archive_root.exists(),
    )

Der PortContext ist wieder frozen — einmal erfasst, ändert er sich nicht. Das verhindert Inkonsistenzen während einer Session.

setup.py

Initialisiert die Arbeitsumgebung und führt Prefetch-Operationen durch:

Python
# src/setup.py — System-Initialisierung

@dataclass(frozen=True)
class WorkspaceSetup:
    python_version: str
    implementation: str          # CPython, PyPy, etc.
    platform_name: str
    test_command: str = 'python3 -m unittest discover -s tests -v'

@dataclass(frozen=True)
class SetupReport:
    setup: WorkspaceSetup
    prefetches: tuple[PrefetchResult, ...]
    deferred_init: DeferredInitResult
    trusted: bool               # Trust-Gating Flag
    cwd: Path

def run_setup(cwd: Path | None = None, trusted: bool = True) -> SetupReport:
    root = cwd or Path(__file__).resolve().parent.parent
    
    # Parallele Prefetch-Operationen
    prefetches = [
        start_mdm_raw_read(),         # Geräte-Info
        start_keychain_prefetch(),    # Schlüsselbund
        start_project_scan(root),     # Projekt-Struktur
    ]
    
    return SetupReport(
        setup=build_workspace_setup(),
        prefetches=tuple(prefetches),
        deferred_init=run_deferred_init(trusted=trusted),
        trusted=trusted,
        cwd=root,
    )

session_store.py

Persistiert Sessions im Dateisystem:

python
# Sessions werden gespeichert nach:
.port_sessions/{session_id}.json

# Inhalt:
{
  "session_id": "abc123...",
  "messages": ["prompt1", "prompt2", ...],
  "input_tokens": 1500,
  "output_tokens": 800
}
Design-Entscheidung
Die Infrastrukturschicht nutzt ausschließlich die Python-Standardbibliothek. Keine externen Abhängigkeiten für maximale Portabilität.
🔒

Trust-Gating erklärt

Verzögerte Initialisierung für sichere Code-Ausführung.

Trust-Gating ist ein Sicherheitsmechanismus, der sensible Initialisierung erst nach expliziter Freigabe erlaubt. Das verhindert, dass potenziell gefährlicher Code bei bloßem Import ausgeführt wird.

Wie es funktioniert

Python
# src/deferred_init.py — Verzögerte Initialisierung

@dataclass(frozen=True)
class DeferredInitResult:
    trusted: bool
    plugin_init: bool      # Plugins laden?
    skill_init: bool       # Skills initialisieren?
    mcp_prefetch: bool     # MCP-Tools prefetch?
    session_hooks: bool    # Session-Hooks aktiv?

def run_deferred_init(trusted: bool) -> DeferredInitResult:
    enabled = bool(trusted)
    return DeferredInitResult(
        trusted=trusted,
        plugin_init=enabled,
        skill_init=enabled,
        mcp_prefetch=enabled,
        session_hooks=enabled,
    )

Permission-System

Python
# src/permissions.py — Tool-Blockierung

@dataclass(frozen=True)
class ToolPermissionContext:
    deny_names: frozenset[str] = field(default_factory=frozenset)
    deny_prefixes: tuple[str, ...] = ()

    def blocks(self, tool_name: str) -> bool:
        lowered = tool_name.lower()
        return (
            lowered in self.deny_names or
            any(lowered.startswith(p) for p in self.deny_prefixes)
        )

# Verwendung:
ctx = ToolPermissionContext.from_iterables(
    deny_names=['Bash', 'Write'],
    deny_prefixes=['mcp__']
)
filtered_tools = filter_tools_by_permission_context(tools, ctx)
bash
# Tool-Blockierung über CLI
$ python3 -m src.main tools --deny-tool Bash
$ python3 -m src.main tools --deny-prefix mcp__
$ python3 -m src.main tools --deny-tool Bash --deny-tool Write

Bash-Tool automatisch blockiert

Das Bash-Tool wird automatisch als PermissionDenial markiert, da Shell-Ausführung im Port nicht erlaubt ist:

Python
def _infer_permission_denials(self, matches: list[RoutedMatch]) -> list[PermissionDenial]:
    denials: list[PermissionDenial] = []
    for match in matches:
        if match.kind == 'tool' and 'bash' in match.name.lower():
            denials.append(PermissionDenial(
                tool_name=match.name,
                reason='destructive shell execution remains gated in the Python port'
            ))
    return denials
🚀

Bootstrap-Phasen

Die 7 Phasen vom Start bis zur ersten Prompt-Verarbeitung.

Der Bootstrap-Vorgang folgt einer strikten Sequenz. Jede Phase baut auf den vorherigen auf und fügt neue Fähigkeiten hinzu.

1
Top-Level Prefetch Side Effects
Parallele Ladevorgänge: Geräte-Info, Schlüsselbund, Projekt-Scan
2
Warning Handler & Environment Guards
Abfangen von Warnungen, Umgebungsvariablen-Prüfung
3
CLI Parser & Pre-Action Trust Gate
Argument-Parsing, erste Trust-Prüfung
4
Setup + Commands/Agents Parallel Load
Workspace-Setup, paralleles Laden der Spiegelungsdaten
5
Deferred Init After Trust
Plugins, Skills, MCP-Prefetch (nur wenn trusted=True)
6
Mode Routing
Lokal, Remote, SSH, Teleport, Direct-Connect, Deep-Link
7
Query Engine Submit Loop
Prompt-Verarbeitung, Session-Management, Ausgabe
bash
# Bootstrap-Graph anzeigen
$ python3 -m src.main bootstrap-graph

# Bootstrap Graph

- top-level prefetch side effects
- warning handler and environment guards
- CLI parser and pre-action trust gate
- setup() + commands/agents parallel load
- deferred init after trust
- mode routing: local / remote / ssh / teleport / direct-connect / deep-link
- query engine submit loop

Die komplette Bootstrap-Session

bash
# Vollständige Session starten
$ python3 -m src.main bootstrap "Analysiere die Codebasis"

# Runtime Session

Prompt: Analysiere die Codebasis

## Context
Source root: src/
Python files: 66
Archive available: True

## Setup
- Python: 3.11.4 (CPython)
- Platform: macOS-14.0-arm64

## Routed Matches
- [command] analyze (Score: 1) — tools/analysis
- [tool] FileReadTool (Score: 0) — tools/filesystem
...

Persisted session path: .port_sessions/abc123.json
⚠️
Wichtig
Die Bootstrap-Phasen müssen in dieser Reihenfolge ausgeführt werden. Eine Umkehr (z.B. Trust-Gate vor Prefetch) würde Sicherheitslücken öffnen.
📋

Die Architektur im Überblick

Ein Blick auf das Gesamtbild.

SchichtKernaufgabeHauptdateienMerkmale
SpiegelungDaten bereitstellencommands.py, tools.pyImmutable, gecached, typisiert
OrchestrierungLogik ausführenruntime.py, query_engine.pyRouting, Sessions, Budget
InfrastrukturSystem-Dienstesetup.py, context.py, session_store.pyPersistenz, Trust-Gating, Env

Datenfluss durch die Schichten

Datenfluss
Benutzer-Prompt
    ↓
[Infrastruktur] setup.py initialisiert Umgebung
    ↓
[Orchestrierung] runtime.py routed den Prompt
    ↓
[Spiegelung] commands.py/tools.py liefern Daten
    ↓
[Orchestrierung] query_engine.py verwaltet Session
    ↓
[Infrastruktur] session_store.py persistiert
    ↓
Ausgabe an Benutzer
🎉
Du hast es verstanden!
Die Drei-Schichten-Architektur ist das Fundament des Claw Code Projekts. Sie ermöglicht saubere Trennung von Daten, Logik und Infrastruktur — essenziell für wartbare KI-Systeme.