Dein AI Agent braucht mehr als Guardrails. Er braucht eine Welt mit Regeln.


Alle beschweren sich darüber, dass LLMs halluzinieren.
Aber was, wenn das kein Bug ist, den wir irgendwann wegpatchen?
Ein LLM ruft keine Wahrheit aus einer Datenbank ab. Es erzeugt die nächste plausible Fortsetzung. Genau dadurch kann es Code schreiben, neue Lösungswege vorschlagen und sich etwas vorstellen, das vorher nicht existiert hat.
Menschen können das auch. Wir nennen es Fantasie, Hypothese oder Idee.
Nur: Wenn ein Agent einen Refund ausführt, einen Vertrag prüft oder Daten in deinem System verändert, reicht „plausibel“ nicht mehr.
Dann brauchst du Zuverlässigkeit.
Wir versuchen dieses Problem heute mit besseren Prompts, mehr Context, RAG und Agentic Loops zu lösen. Das macht Agents leistungsfähiger. Aber nicht automatisch verlässlicher.
Ein Loop gibt dem Agenten mehr Möglichkeiten, seinen Fehler zu korrigieren. Er gibt ihm aber auch mehr Möglichkeiten, vom Ziel abzudriften, Tokens zu verbrennen oder denselben falschen Gedanken dreimal auszuführen.
Mehr Context hilft dem Agenten, mehr über die Aufgabe zu wissen. Aber Context allein definiert noch keine Welt.
Wenn ein Agent in deiner Domäne handeln soll, muss dein System explizit beschreiben:
- Welche Entitäten existieren?
- Wie hängen sie zusammen?
- Welche Zustände sind erlaubt?
- Welche Aktionen dürfen niemals passieren?
Der Agent braucht nicht weniger Fantasie.
Er braucht ein maschinenlesbares Verständnis der Welt, in der diese Fantasie handeln darf.
Genau hier werden Ontologien interessant.
Der Gedanke kam aus Frank Coyles Talk „Why Agentic Systems Need Ontologies“. Seine Kernthese trifft einen echten Production-Pain: Ein LLM kann einen zweiten Refund auf dieselbe Order vorschlagen, Geld an die falsche Rolle senden oder einen Status wie probably_shipped erfinden. Ein längerer System-Prompt verhindert das nicht zuverlässig.
Zwei Welten, die wir zusammenbringen müssen#
Coyle beschreibt zwei Entwicklungslinien der AI.
Auf der einen Seite stehen die AI Agents.
Sie nehmen ihre Umgebung wahr, entscheiden über den nächsten Schritt und schlagen eine Aktion vor. Unter der Haube bleibt das LLM probabilistisch. Es berechnet nicht die eine logisch richtige Aktion. Es erzeugt die Aktion, die im aktuellen Context am plausibelsten erscheint.
Genau deshalb ist es so flexibel.
Der Agent kann auf neue Situationen reagieren, zwischen Tools wählen und einen Plan anpassen, den wir nicht vollständig vorprogrammiert haben.
Auf der anderen Seite steht die Ontologie.
Der Begriff kommt ursprünglich aus der Philosophie. Ontologie ist die Lehre des Seins: Was existiert überhaupt? Welche Arten von Dingen gibt es? Und in welchen Beziehungen stehen sie zueinander?
In der Informatik wird daraus ein formales Modell einer Domäne.
Für ein Payment-System könnte dieses Modell beschreiben:
Customer,SupportRepresentative,OrderundRefundsind unterschiedliche Entitäten.- Ein Refund gehört zu genau einer Order.
- Der Empfänger eines Refunds muss der Käufer dieser Order sein.
- Eine bereits vollständig erstattete Order darf nicht erneut erstattet werden.
Der Agent erzeugt Möglichkeiten.
Die Ontologie beschreibt die Welt, in der diese Möglichkeiten bewertet werden.
Verbindest du beide Ansätze, entsteht das, was oft neuro-symbolische AI genannt wird:
LLM / Agent
→ schlägt eine Aktion probabilistisch vor
Ontologie
→ liefert das formale Domänenmodell und mögliche Inferenzen
Runtime-Validator
→ prüft den Vorschlag gegen Modell, Policies und aktuellen ZustandDer Agent darf weiterhin kreativ planen. Aber er entscheidet nicht allein, was in der echten Welt passieren darf.
Die Ontologie gibt ihm nicht automatisch menschliches Verständnis. Sie gibt dem System einen expliziten Rahmen, gegen den es seinen Vorschlag prüfen kann.

Warum das gerade in Agentic Loops wichtig wird#
Ein Agent beantwortet nicht einfach nur einen Prompt.
Er arbeitet in einem Loop:
LLM schlägt einen Tool Call vor
→ Tool wird ausgeführt
→ Ergebnis kommt als Observation zurück
→ LLM entscheidet über den nächsten Schritt
→ Loop beginnt erneutGenau dieser Loop macht aus einem LLM einen Agenten.
Das Modell kann Informationen nachladen, auf Fehler reagieren und seinen Plan während der Ausführung anpassen. Es muss nicht beim ersten Versuch richtigliegen.
Aber der Loop verstärkt nicht nur die Fähigkeiten des Modells. Er verstärkt auch seine Fehler.
Wenn der Agent eine falsche Annahme trifft, kann er auf dieser Annahme weitere Tool Calls aufbauen. Wenn ein Tool fehlschlägt, kann er dieselbe Aktion in leicht veränderter Form immer wieder versuchen. Mit jedem Durchlauf wächst der Context, die ursprüngliche Aufgabe rückt weiter nach hinten und der Token-Verbrauch steigt.
Aus einem falschen Vorschlag wird so schnell:
- ein endloser Reparaturversuch,
- Context Drift über mehrere Tool Calls,
- dreimal dieselbe unerlaubte Aktion,
- oder eine überraschend hohe API-Rechnung.
Ein max_steps = 6 verhindert die Endlosschleife.
Es macht die ersten fünf Schritte aber noch nicht richtig.
Genau hier wird die Ontologie praktisch. Wir können die Regeln unserer Domäne als Entitäten, Beziehungen und erlaubte Zustände im Graphen ausdrücken und jeden Tool Call gegen diese Welt prüfen.
Der Agent darf weiterhin probabilistisch vorschlagen:
{
"id": "call_01",
"tool": "refund_order",
"arguments": {
"order_id": "order_42",
"recipient_id": "support_17",
"amount_cents": 12900
}
}Im Graphen steht jedoch:
customer_9 ── owns ──> order_42
order_42 ── paid_by ──> customer_9
refund ── must_go_to ──> payer(order_42)Aber wie kommt diese Regel jetzt konkret zum Tool Call?
In der Praxis würde ich die Ontologie um eine operative Policy-Ebene erweitern. Das Tool selbst ist dabei ebenfalls ein Knoten im Graphen:
(:ToolAction {name: "refund_order"})
├──[:CHECK_BEFORE]──> (:Policy {key: "recipient_is_order_payer"})
├──[:CHECK_BEFORE]──> (:Policy {key: "order_has_refundable_balance"})
└──[:CHECK_AFTER]───> (:Policy {key: "refund_matches_approved_request"})
(:Policy {key: "recipient_is_order_payer"})
└──[:USES_RELATION]─> (:Relation {name: "PAID_BY"})
(:Order {id: "order_42"})
└──[:PAID_BY]───────> (:Customer {id: "customer_9"})Damit ist maschinenlesbar hinterlegt:
- welche Policies zu
refund_ordergehören, - wann sie geprüft werden,
- und auf welche Begriffe und Beziehungen der Domäne sie sich beziehen.
Das ist streng genommen nicht mehr nur eine reine OWL-Ontologie. Es ist eine operative Policy-Schicht auf demselben Knowledge Graph. Genau diese Trennung ist wichtig: Der Graph beschreibt und verknüpft die Regeln. Deterministischer Code führt sie aus.
So sieht der Check im Code aus#
Zuerst muss aus dem probabilistischen LLM-Output überhaupt ein gültiger Tool Call werden. Dafür ist Pydantic zuständig:
from typing import Literal
from pydantic import BaseModel, Field
class RefundArguments(BaseModel):
order_id: str = Field(pattern=r"^order_[0-9]+$")
recipient_id: str = Field(pattern=r"^(customer|support)_[0-9]+$")
amount_cents: int = Field(gt=0)
class RefundToolCall(BaseModel):
id: str
tool: Literal["refund_order"]
arguments: RefundArgumentsDamit kann das Backend den rohen Vorschlag des LLMs parsen:
call = RefundToolCall.model_validate(llm_output)Pydantic beantwortet dabei Fragen wie:
- Ist
amount_centswirklich eine positive Ganzzahl? - Hat
order_iddas erwartete Format? - Existieren alle benötigten Argumente?
- Hat das LLM überhaupt
refund_orderausgewählt?
Pydantic kann aber nicht beantworten, ob customer_9 diese Order bezahlt hat oder ob noch ein erstattbarer Betrag vorhanden ist.
Das ist die nächste Schicht.
Wenn der Agent refund_order aufrufen will, lädt der Runtime-Code zuerst alle CHECK_BEFORE-Policies des Tool-Knotens:
def load_policies(graph, tool_name: str, phase: str) -> list[str]:
relation = {
"before": "CHECK_BEFORE",
"after": "CHECK_AFTER",
}[phase]
rows = graph.query(
f"""
MATCH (:ToolAction {{name: $tool_name}})
-[:{relation}]->(policy:Policy)
RETURN policy.key AS key
ORDER BY policy.key
""",
tool_name=tool_name,
)
return [row["key"] for row in rows]Für jede Policy existiert eine deterministische Implementierung:
def recipient_is_order_payer(call, graph) -> bool:
return graph.exists(
"""
MATCH (:Order {id: $order_id})
-[:PAID_BY]->(:Customer {id: $recipient_id})
""",
order_id=call.arguments.order_id,
recipient_id=call.arguments.recipient_id,
)
def order_has_refundable_balance(call, graph) -> bool:
order = graph.get_order(call.arguments.order_id)
requested = call.arguments.amount_cents
remaining = order.paid_cents - order.refunded_cents
return requested > 0 and requested <= remaining
POLICY_EVALUATORS = {
"recipient_is_order_payer": recipient_is_order_payer,
"order_has_refundable_balance": order_has_refundable_balance,
}Der Runtime-Validator verbindet beide Seiten:
def validate_before(call, graph) -> list[str]:
policy_keys = load_policies(
graph,
tool_name=call.tool,
phase="before",
)
violations = []
for key in policy_keys:
evaluator = POLICY_EVALUATORS[key]
if not evaluator(call, graph):
violations.append(key)
return violationsFür den Vorschlag des Agenten:
{
"id": "call_01",
"tool": "refund_order",
"arguments": {
"order_id": "order_42",
"recipient_id": "support_17",
"amount_cents": 12900
}
}läuft der erste Check auf diese Graph-Abfrage hinaus:
Gibt es diesen Pfad?
(order_42:Order)-[:PAID_BY]->(support_17:Customer)Die Antwort ist false.
Der Runtime-Code ruft das Payment-Tool deshalb überhaupt nicht auf. Er gibt dem Agentic Loop stattdessen eine strukturierte Observation zurück:
{
"status": "rejected",
"failed_policy": "recipient_is_order_payer",
"message": "The refund recipient must be the customer who paid the order.",
"allowed_recipient_id": "customer_9"
}Das LLM muss im nächsten Durchlauf nicht blind raten. Es weiß, welche Regel verletzt wurde und welcher Pfad in der Domäne zulässig ist.
Und was wird nach dem Tool Call geprüft?#
Auch das Ergebnis eines Tools kann falsch oder unvollständig sein.
Ein vorbereitetes Refund-Ergebnis könnte zum Beispiel so aussehen:
{
"refund_id": "refund_88",
"order_id": "order_42",
"recipient_id": "customer_9",
"amount_cents": 12900,
"status": "prepared"
}Die CHECK_AFTER-Policy refund_matches_approved_request prüft, ob das Tool-Ergebnis noch exakt zum validierten Vorschlag passt:
def refund_matches_approved_request(call, result, graph) -> bool:
return all(
[
result.order_id == call.arguments.order_id,
result.recipient_id == call.arguments.recipient_id,
result.amount_cents == call.arguments.amount_cents,
result.status == "prepared",
]
)Auch hier bestimmt die Kante im Graphen, welcher Check ausgeführt wird:
AFTER_POLICY_EVALUATORS = {
"refund_matches_approved_request": (
refund_matches_approved_request
),
}
def validate_after(call, result, graph) -> list[str]:
policy_keys = load_policies(
graph,
tool_name=call.tool,
phase="after",
)
return [
key
for key in policy_keys
if not AFTER_POLICY_EVALUATORS[key](call, result, graph)
]Der vollständige Ablauf sieht damit so aus:
def rejected_observation(violations):
return {
"status": "rejected",
"failed_policies": violations,
}
async def execute_agent_tool(call, graph, tools):
before_violations = validate_before(call, graph)
if before_violations:
return rejected_observation(before_violations)
# Noch keine irreversible Aktion:
prepared_result = await tools[call.tool].prepare(
call.arguments.model_dump()
)
after_violations = validate_after(call, prepared_result, graph)
if after_violations:
await tools[call.tool].discard(prepared_result)
return rejected_observation(after_violations)
# Erst jetzt darf der externe Side Effect passieren:
receipt = await tools[call.tool].commit(
prepared_result,
idempotency_key=call.id,
)
return {
"status": "success",
"tool_result": receipt,
}Bis hierhin haben wir einen einzelnen Tool Call abgesichert.
Das allein verhindert aber noch keinen endlosen Agentic Loop. Dafür braucht der Orchestrator eigene, deterministische Abbruchbedingungen:
from collections import Counter
from pydantic import ValidationError
MAX_STEPS = 6
MAX_SAME_POLICY_FAILURES = 2
async def run_agent(task, llm, graph, tools):
observations = []
policy_failures = Counter()
for step in range(MAX_STEPS):
raw_proposal = await llm.propose(
task=task,
observations=observations,
)
# 1. Syntaktische Grenze
try:
call = RefundToolCall.model_validate(raw_proposal)
except ValidationError as error:
observation = {
"status": "rejected",
"reason": "INVALID_TOOL_SCHEMA",
"details": error.errors(),
}
observations.append(observation)
continue
# 2. Semantische Grenze und Tool-Ausführung
observation = await execute_agent_tool(
call,
graph,
tools,
)
observations.append(observation)
if observation["status"] == "success":
return observation
# 3. Circuit Breaker für wiederholte Regelverletzungen
for policy in observation.get("failed_policies", []):
policy_failures[policy] += 1
if (
policy_failures[policy]
>= MAX_SAME_POLICY_FAILURES
):
return {
"status": "human_review_required",
"reason": "REPEATED_POLICY_VIOLATION",
"failed_policy": policy,
"steps_used": step + 1,
}
# 4. Globales Limit gegen Drift und Token-Verbrauch
return {
"status": "stopped",
"reason": "MAX_STEPS_EXCEEDED",
"steps_used": MAX_STEPS,
}Jetzt sind die Verantwortlichkeiten sauber getrennt:
Pydantic
→ Ist der Tool Call strukturell gültig?
BEFORE-Policies am Tool-Knoten
→ Ist diese Aktion im aktuellen Domänenzustand erlaubt?
AFTER-Policies am Tool-Knoten
→ Entspricht das vorbereitete Ergebnis dem validierten Auftrag?
Circuit Breaker im Orchestrator
→ Wie oft darf der Agent scheitern, bevor der Loop endet?Scheitert eine Policy einmal, bekommt der Agent strukturiertes Feedback und darf seinen Plan korrigieren.
Scheitert dieselbe Policy wiederholt, stoppt der Circuit Breaker den Loop. Wird unabhängig davon das globale Step-Limit erreicht, endet der Loop ebenfalls.
Damit verhindert die Ontologie nicht selbst Context Drift oder Token-Verbrauch. Ihre Policy-Knoten liefern dem Orchestrator aber präzise Fehlersignale, auf deren Basis er gezielt entscheiden kann: noch ein Versuch, anderer Pfad oder Hard Stop.
Das ist der entscheidende Punkt:
Agent schlägt Tool Call vor
→ Runtime lädt BEFORE-Policies vom Tool-Knoten
→ deterministischer Code prüft Graph und aktuellen Zustand
→ Tool bereitet Ergebnis ohne Side Effect vor
→ Runtime lädt AFTER-Policies vom Tool-Knoten
→ deterministischer Code prüft das Ergebnis
→ erst danach wird committedNicht jedes externe Tool bietet ein echtes prepare und commit. Dann brauchst du je nach System Transaktionen, Idempotency Keys, eine Saga oder einen Approval-Schritt. Ein Post-Check allein kann einen bereits ausgeführten Refund nicht zurückholen.
Die Ontologie beendet den Loop also nicht von selbst.
Sie gibt dem deterministischen Code aber die Regeln, mit denen er einen Loop kontrollieren, falsche Aktionen früh blockieren und dem Agenten bessere Wege zurück in den erlaubten Raum zeigen kann.
Was Ontologien wirklich beitragen#
Eine Ontologie gibt deiner Domäne ein gemeinsames, maschinenlesbares Modell:
- Welche Entitäten existieren?
- Welche Eigenschaften haben sie?
- Wie hängen sie zusammen?
- Welche Schlussfolgerungen folgen aus diesen Beziehungen?
Mit RDFS kannst du zum Beispiel ausdrücken, dass jemand, der etwas unterrichtet, ein Teacher ist und dass jeder Teacher auch eine Person ist.
Mit OWL kannst du stärkere Aussagen modellieren:
ancestorOfist transitiv.CustomerundSupportRepresentativesind disjunkte Klassen.hasFatherist eine funktionale Eigenschaft.
Damit kann ein Reasoner neue Fakten ableiten oder Widersprüche im Modell erkennen.
Das ist mehr als eine Graph-Datenbank. Der Graph speichert Beziehungen. Die Ontologie gibt ihnen Bedeutung.
Aber hier wird es wichtig.
OWL ist nicht automatisch dein Validator#
Geminis Erklärung macht aus OWL schnell eine Datenbank-Constraint:
Eine Functional Property verhindert Duplikate und erzwingt Konsistenz.
So funktioniert OWL nicht.
OWL arbeitet ohne die übliche Unique-Name-Annahme. Wenn Peter und Peter_Griffin beide als Vater derselben Person eingetragen sind und hasFather funktional ist, kann ein Reasoner daraus ableiten, dass beide Namen dieselbe Person bezeichnen. Er wirft nicht automatisch einen Validation Error.
Auch fehlende Daten sind in OWL nicht automatisch falsch. Unter der Open-World-Assumption bedeutet „nicht im Graphen“ zunächst nur „unbekannt“.
Für Inferenz ist das nützlich.
Für einen Runtime-Check wie „jede Auszahlung braucht exakt einen verifizierten Empfänger“ reicht es nicht.
Wenn du RDF-Daten tatsächlich gegen geschlossene Bedingungen validieren willst, brauchst du zum Beispiel SHACL. SHACL prüft einen Data Graph gegen definierte Shapes und liefert einen strukturierten Validation Report.
Die Rollen sind also verschieden:
Pydantic → Ist der Tool Call syntaktisch wohlgeformt?
OWL → Welche Bedeutung und welche Inferenzen folgen aus dem Modell?
SHACL → Entspricht der aktuelle Graph den erwarteten Datenbedingungen?
Policy → Darf dieser Actor diese Aktion in diesem Zustand ausführen?Alles davon „Ontology Validator“ zu nennen, klingt einfacher. Es versteckt aber genau die Architektur, die du in Production verstehen musst.
Die Ontologie kennt nicht das Ziel des Agenten#
Der nächste Denkfehler ist subtiler.
Eine Ontologie verhindert nicht automatisch Context Drift.
Sie weiß, dass ein Customer kein SupportRepresentative ist. Sie weiß aber nicht, ob der Agent nach sechs Tool Calls noch an der ursprünglichen Aufgabe arbeitet.
Dafür muss dein System das Ziel explizit repräsentieren:
task = {
"goal": "prepare_refund_proposal",
"actor_id": "user_42",
"allowed_tools": ["get_order", "get_payment", "propose_refund"],
"max_steps": 6,
"max_cost_cents": 20,
"requires_approval": True,
}Jetzt kann der Loop harte Fragen beantworten:
- Ist das Tool für diesen Task erlaubt?
- Passt der aktuelle State noch zur nächsten Transition?
- Wurde dieselbe fehlgeschlagene Aktion schon wiederholt?
- Ist das Step-, Zeit- oder Token-Budget aufgebraucht?
- Muss ein Mensch übernehmen?
Die Ontologie liefert Domänenwissen.
Der Orchestrator kontrolliert den Loop.
Die State Machine kontrolliert erlaubte Übergänge.
Der Circuit Breaker zieht den Stecker.
Das sind vier verschiedene Jobs.
Ein Post-Check macht keinen Side Effect ungeschehen#
Geminis End-to-End-Beispiel schreibt zuerst den Verkäufer in ein Dictionary und setzt den Wert bei einer verletzten Post-Condition wieder auf None.
In einem Demo funktioniert das.
In Production wird daraus schnell:
- ein Refund bei Stripe,
- eine versendete E-Mail,
- eine gebuchte Reise,
- ein übermittelter Schriftsatz,
- ein Write in einem fremden System.
Diese Aktionen kannst du nicht mit einer Python-Zuweisung zurückrollen.
Deshalb sollte der Agent zuerst eine Zustandsänderung vorschlagen:
{
"transition": "REFUND_REQUESTED",
"order_id": "order_123",
"recipient_id": "customer_9",
"amount_cents": 4900
}Dann prüft dein System:
- Schema und Typen
- Authentifizierung und Autorisierung
- aktuelle Order- und Payment-Daten
- Domain-Invarianten
- Idempotency Key
- Approval Policy
Erst danach entsteht der Side Effect.
Und selbst dann brauchst du je nach System Transaktionen, Outbox-Patterns oder eine Saga mit kompensierenden Aktionen. „Rollback“ ist keine Eigenschaft des Agenten. Es ist eine Eigenschaft deiner Integrationsarchitektur.
Die Production-Architektur#
Der robuste Loop sieht eher so aus:
User-Ziel
↓
[Task Envelope]
Ziel · Actor · erlaubte Tools · Step-Budget · Approval Policy
↓
LLM schlägt Tool Call vor
↓
[1] Schema-Check
Pydantic / Structured Output
↓
[2] Policy- und Precondition-Check
Rollen · Berechtigungen · aktueller State
↓
[3] Semantik- und Daten-Check
Ontology Reasoner · SHACL · Business Invariants
↓
[4] Approval Boundary
automatisch · Human-in-the-Loop · abgelehnt
↓
[5] Side Effect
idempotent · transactional · auditiert
↓
[6] Postcondition
Ist der erwartete State wirklich eingetreten?
↓
Observation zurück an den AgentenWenn ein Check fehlschlägt, bekommt der Agent kein diffuses „Action rejected“. Er bekommt strukturiertes Feedback:
{
"code": "RECIPIENT_ROLE_MISMATCH",
"message": "Refund recipient must be the buyer of the order.",
"current_recipient_role": "SupportRepresentative",
"allowed_recipient_id": "customer_9",
"retryable": true
}Jetzt muss das LLM nicht blind raten.
Es kann seinen Vorschlag korrigieren. Nach zwei oder drei identischen Fehlern stoppt der Orchestrator den Loop und eskaliert.
Nicht die Ontologie stoppt die Schleife.
Dein Code stoppt sie — mit Informationen aus der Ontologie.
Was das für eine Legal-AI-SaaS bedeutet#
Legal AI ist ein guter Test für dieses Pattern, weil „klingt plausibel“ dort nicht reicht.
Angenommen, ein Anwalt lädt eine Akte hoch und fragt:
Können wir einen Verzugsschaden geltend machen?
Die naive Architektur wirft alle PDFs in eine Vector DB, sucht ähnliche Chunks und lässt das LLM eine juristische Antwort schreiben.
Das Problem: Ein ähnlicher Chunk ist noch kein bewiesener Fakt.
Ein Schreiben mit dem Satz „Zahlbar bis 26.04.“ beweist nicht automatisch, dass die Mahnung zugegangen ist. Ein LLM kann beides sprachlich sehr überzeugend zusammenziehen.
Ich würde die Daten deshalb in drei Ebenen trennen:
Quelldokumente
→ unveränderbare PDFs, Seiten und Chunks
Extraktionsvorschläge
→ "könnte Mahnung sein", mit Quelle und Confidence
Bestätigter Case Graph
→ behauptet · bestritten · belegt · bestätigtDer Extraction Agent darf neue Nodes und Edges vorschlagen. Er darf sie nicht still als Wahrheit in den Case Graph schreiben.
Ein sinnvoller Tool-Satz wäre:
search_case_evidence(query)
get_claim_requirements(claim_type, jurisdiction, valid_at)
propose_case_fact(fact_type, source_chunk_id)
link_evidence_proposal(fact_id, source_chunk_id)
get_missing_requirements(claim_id)
draft_argument(claim_id, approved_evidence_ids)Beachte das Verb propose.
Der Agent kann eine Fundstelle entdecken. Der Graph hält fest, woher sie kommt. Ein Anwalt oder ein ausreichend klarer Review-Workflow bestätigt, welchen Status sie bekommt.
Die Ontologie kann dann ausdrücken:
Verzugsschaden
erfordert → Fälligkeit
erfordert → Mahnung ODER Entbehrlichkeit
erfordert → Vertretenmüssen
erfordert → SchadenDas System kann zuverlässig zeigen:
- Fälligkeit: Quelle vorhanden und bestätigt
- Mahnung: Schreiben gefunden, Zugang nicht belegt
- Schaden: Betrag extrahiert, Berechnung nicht geprüft
Was es nicht sagen sollte:
Der Anspruch ist zu 66 % begründet.
Das ist falsche Präzision. Tatbestandsmerkmale sind keine gleich gewichteten Progress Bars.
Der bessere Output lautet:
Für die Mahnung wurde ein Schreiben gefunden. In der Akte fehlt bisher ein bestätigter Nachweis des Zugangs. Ohne diesen Nachweis bleibt dieses Tatbestandsmerkmal offen.
Das ist weniger spektakulär.
Und deutlich nützlicher.
Top-down oder bottom-up?#
Auch hier ist „beides“ allein noch keine Strategie.
Top-down modellierst du den stabilen Kern:
- Rollen und Berechtigungen
- erlaubte Zustände und Übergänge
- fachliche Invarianten
- Provenance
- Jurisdiktion und zeitliche Gültigkeit
Bottom-up lässt du aus echten Akten, Queries und Failure-Cases neue Kandidaten entstehen:
- unbekannte Dokumenttypen
- neue Bezeichnungen für bekannte Konzepte
- fehlende Beziehungen
- wiederkehrende Extraktionsmuster
Diese Kandidaten landen in einer Review Queue. Ein Domain-Experte entscheidet, ob sie ein Synonym, eine neue Subklasse oder einfach Rauschen sind.
So wächst die Ontologie mit der Realität, ohne dass ein probabilistischer Agent sein eigenes Regelwerk umschreibt.
Und noch ein Detail aus dem Talk: Wikipedia basiert nicht auf DBpedia. DBpedia extrahiert strukturierte Informationen aus Wikipedia. Das klingt klein, ist aber genau die Art semantischer Richtung, die in Knowledge-Graph-Systemen zählt.
Der eigentliche Shift#
Ich würde Coyles Satz „Pydantic at the door, ontology at the ledger“ erweitern:
Pydantic prüft die Form. OWL leitet Bedeutung ab. SHACL prüft den Graphen. Policy-Code erlaubt die Aktion. Die Transaction Boundary kontrolliert, was real wird.
Dann darf das LLM das tun, worin es gut ist:
Hypothesen bilden. Evidenz finden. Wege vorschlagen. Mit unvollständiger Sprache arbeiten.
Aber zwischen seinem Vorschlag und der echten Welt liegt Code, den du erklären, testen und auditieren kannst.
Das ist nicht weniger agentic.
Das ist der Punkt, an dem aus einem Agenten ein System wird.
Quellen: Frank Coyle, „Why Agentic Systems Need Ontologies“, AI Engineer World’s Fair 2026; W3C, OWL 2 Structural Specification und Shapes Constraint Language (SHACL); DBpedia Association.
AI Engineering, wöchentlich.
Praktisches AI Engineering direkt in dein Postfach.