Client-side meander.plan SDK: transport setup + a form-checked plan onto a real OTel span, with thin Claude/OpenAI bindings. Imports meander NOT.
Project description
meander-agent
Kundenseitiges SDK für die Plan-Sende-Seite von meander. Dein Agent hält
fest, was er getan hat, als nachvollziehbare Behauptung mit Herkunft, und stellt
GENAU EINE Frage an eine Regel deiner Ontologie. Das Paket baut daraus ein
meander.plan, prüft dessen FORM und hängt es an einen echten OpenTelemetry-Span,
den dein OTLP-Export zu meander trägt. Kein meander-Import, keine Ontologie im
Client.
Das Modell in einem Absatz
Ein meander.plan ist eine Behauptung plus eine Frage, keine Entscheidung:
- facts — was der Agent beobachtet/eingeschätzt hat. Jeder Fakt trägt SEINE
Herkunft (
origin.kind:tool|api|human|agent). Ein Tool-Ergebnis isttool, die eigene Einschätzung des Agenten istagent. - relations — behauptete Beziehungen zwischen Entities (optional).
- question — GENAU EINE Frage an eine bestehende Derivation (Regel) deiner
Welt, z.B. „gilt hier
requires_review?". Der Agent entscheidet nicht; ob die Regel greift, bestimmt meander serverseitig, und ein Mensch prüft.
Der Client prüft nur die FORM (Pflichtfelder, Typen, genau eine Frage,
origin-Vokabular, plan_version == 1). Ob die Entities/Properties/Derivations
wirklich existieren, weiß nur der Server (er kennt deine Ontologie). Darum
importiert dieses Paket meander NICHT und lädt keine Ontologie.
Garantien (nie stilles Nichtstun): ein Fehlerlauf hängt NIE ein Attribut an;
ein nicht mehr beschreibbarer Span verweigert das Attribut laut
(AttachResult(ok=False, span_not_recording)); ein formfehlerhafter Plan wird mit
Feldpfad gemeldet, statt halb geschrieben zu werden.
Installation
pip install meander-agent # Transport + Emitter (schlank, ohne LLM-SDK)
pip install "meander-agent[claude]" # + Claude-Agent-SDK-Binding
pip install "meander-agent[openai]" # + OpenAI-Agents-SDK-Binding
1. Dein Vokabular deklarieren
Du gibst dem Agenten die Namen deiner Welt (er soll nichts erfinden). Je Entity ihre identity-Felder und Properties, je Relation ihre Endpunkte, plus die Derivation-IDs, die er befragen darf:
vocabulary = {
"entities": {
"Order": {"identity": ["order_id"], "properties": ["amount", "risk"]},
"Case": {"identity": ["case_id"], "properties": []},
"Outcome": {"identity": ["outcome_id"], "properties": []},
},
"relations": {
"concerns": {"from": "Case", "to": "Order"},
},
"derivation_ids": ["requires_review.high_value"],
}
Daraus erzeugt das Paket den Prompt-Baustein (nennt dem Modell nur diese Namen) und das JSON-Schema der strukturierten Ausgabe (erlaubt nur diese Namen).
2. Transport einrichten
Ohne eigenes OTel genügt ein Aufruf. endpoint ist die volle OTLP-Traces-URL
deiner Source, source_key das Bearer-Token dieser Source:
from meander_agent import init_meander
client = init_meander(
endpoint="https://<host>/api/sources/<source_id>/v1/traces",
source_key="<bearer-token>",
)
# client.tracer -> der verdrahtete OTel-Tracer
# client.shutdown() / client.force_flush() -> Export abschließen
init_meander setzt KEINEN globalen Provider; der Client hält seinen eigenen.
Hast du bereits ein OTel-Setup, lass init_meander weg und übergib deinen Tracer
direkt (siehe Abschnitt 5).
3. Einen Agenten-Run fahren (Claude)
from meander_agent.claude import run_with_plan
result = run_with_plan(
"Bearbeite Bestellung ORD-42. Rufe die üblichen Tools auf, behaupte die "
"gewonnenen Fakten mit ihrer Herkunft und stelle GENAU EINE Frage an die "
"Derivation requires_review.high_value. Triff KEINE Entscheidung.",
vocabulary=vocabulary,
tracer=client.tracer,
)
print("angehängt" if result.attached else f"kein Plan gesetzt: {result.error_state}")
client.shutdown() # Span exportieren
Das Binding öffnet den Root-Span, fährt das Modell mit strukturierter Ausgabe,
sperrt bei Fehler/Abbruch und hängt bei Erfolg den geprüften Plan an. Es gibt ein
RunResult zurück (siehe Abschnitt 4). Braucht das [claude]-Extra und einen
ANTHROPIC_API_KEY.
3b. Derselbe Run über OpenAI
Identischer Aufruf, anderes Binding (Extra [openai], OPENAI_API_KEY):
from meander_agent.openai import run_with_plan
result = run_with_plan(task, vocabulary=vocabulary, tracer=client.tracer)
Beide Bindings gibt es auch async (run_with_plan_async).
4. Was du zurückbekommst
RunResult:
attached: bool— wurdemeander.planan den Span gehängt?plan: dict | None— der angehängte Plan (bei Erfolg).shape_errors: list[ShapeError]— Formfehler mitpath/code/message, falls die Ausgabe die FORM-Prüfung nicht bestand.error_state—Nonebei Erfolg; sonst der Fehlerausgang des Laufs (SDK-Fehler, Abbruch, Typ-Mismatch). Ist er gesetzt, wird NIE angehängt.
Kein meander.plan heißt also immer: entweder ein Fehlerlauf (error_state) oder
eine formfehlerhafte Ausgabe (shape_errors) — beides steht im Ergebnis, nichts
verschwindet still.
5. Eigenes Tracing / eigenes SDK (der Kern)
Willst du dein eigenes SDK anbinden (oder deinen eigenen Tracer nutzen), fährst du den SDK-neutralen Kontextmanager selbst. Er liefert dir den Baustein und das Schema und übernimmt Parsen, Formprüfung, Sperren und Anhängen:
from meander_agent import meander_run
with meander_run(vocabulary=vocabulary, tracer=my_tracer) as run:
# run.prompt_fragment : Format + erlaubtes Vokabular -> als Instruktion ans SDK
# run.output_schema : JSON-Schema, falls dein SDK strukturierte Ausgabe kann
output, error = call_your_llm(task, instructions=run.prompt_fragment,
schema=run.output_schema)
# output: das strukturierte Ergebnis (dict) ODER ein reiner JSON-String.
# error_state: None bei Erfolg, sonst ein beliebiges Detail (=> Sperre).
result = run.finalize(output, error_state=error)
Der Kern übergibt nie selbst Prompts ans SDK und liest nie SDK-Resultate — das ist Sache deines Bindings. Genauso arbeiten die mitgelieferten Claude-/OpenAI-Bindings.
6. Deterministischer Plan ohne LLM
Baust du den Plan selbst (Tests, regelbasierte Agenten, auth-loser Pfad), nutzt du den Emitter direkt:
from meander_agent import attach_plan, validate_plan_shape
plan = {
"plan_version": 1,
"facts": [
{"entity": "Order", "identity": {"order_id": "ORD-42"},
"property": "amount", "value": 900,
"origin": {"kind": "tool", "ref": "lookup_order"}},
],
"relations": [
{"relation": "concerns",
"from": {"entity": "Case", "identity": {"case_id": "C-1"}},
"to": {"entity": "Order", "identity": {"order_id": "ORD-42"}},
"origin": {"kind": "agent"}},
],
"question": {
"derivation_id": "requires_review.high_value",
"subject": {"entity": "Case", "identity": {"case_id": "C-1"}},
},
}
errors = validate_plan_shape(plan) # reine FORM-Prüfung (leer = ok)
with client.tracer.start_as_current_span("agent.run") as span:
res = attach_plan(span, plan) # hängt bei Formgültigkeit an
if not res.ok:
print("nicht angehängt:", [e.as_dict() for e in res.errors])
client.shutdown()
Öffentliche Fläche
| Symbol | Zweck |
|---|---|
init_meander(endpoint, source_key) -> MeanderClient |
Transport (OTel + OTLP) einrichten |
MeanderClient.tracer / .run(vocabulary=…) / .force_flush() / .shutdown() |
Tracer, Kern-Zucker, Export |
meander_agent.claude.run_with_plan[_async](task, *, vocabulary, tracer) |
Claude-Binding |
meander_agent.openai.run_with_plan[_async](task, *, vocabulary, tracer) |
OpenAI-Binding |
meander_run(vocabulary, tracer) -> Run |
SDK-neutraler Kern-Kontextmanager |
Run.prompt_fragment / .output_schema / .finalize(output, error_state) |
Bausteine + Verarbeitung |
attach_plan(span, plan) -> AttachResult |
FORM-Prüfung + Anhang |
validate_plan_shape(plan) -> list[ShapeError] |
reine FORM-Prüfung |
RunResult, AttachResult, ShapeError |
Ergebnis-/Fehlertypen |
PLAN_ATTRIBUTE_KEY, ORIGIN_KINDS, PLAN_VERSION |
Client-Konstanten |
Entwicklung
pixi run test # zufällige Reihenfolge (pytest-randomly)
pixi run -- pytest -p no:randomly # feste Reihenfolge
Die credential-gated Live-LLM-Tests (Claude/OpenAI) laufen dort, wo die Keys gesetzt sind, und werden sonst übersprungen; die deterministische Suite läuft immer und ohne Mocks.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file meander_agent-0.1.0.tar.gz.
File metadata
- Download URL: meander_agent-0.1.0.tar.gz
- Upload date:
- Size: 47.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
50957cbad59bff5a93e524786627eb863d0674ea9e970b022ea330bea124d923
|
|
| MD5 |
131d83db19e0e300acaa929c9ffb0f41
|
|
| BLAKE2b-256 |
d6116365363f4d3cb5bf81b2693e2dee3303068f5d51935de67b44217fe8f1bd
|
Provenance
The following attestation bundles were made for meander_agent-0.1.0.tar.gz:
Publisher:
publish-pypi.yml on Symbolic-Intelligence-Org/meander-agent
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
meander_agent-0.1.0.tar.gz -
Subject digest:
50957cbad59bff5a93e524786627eb863d0674ea9e970b022ea330bea124d923 - Sigstore transparency entry: 2084729517
- Sigstore integration time:
-
Permalink:
Symbolic-Intelligence-Org/meander-agent@6d3a667717657c5146cba52a74f72eda17cf4036 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/Symbolic-Intelligence-Org
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@6d3a667717657c5146cba52a74f72eda17cf4036 -
Trigger Event:
push
-
Statement type:
File details
Details for the file meander_agent-0.1.0-py3-none-any.whl.
File metadata
- Download URL: meander_agent-0.1.0-py3-none-any.whl
- Upload date:
- Size: 20.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f04f3fcd40778c7502be6a674942c2f59d3a4e92497cfc0f95e342272795564d
|
|
| MD5 |
d05948b6361d0b1784e49e09d0904174
|
|
| BLAKE2b-256 |
172e9e0ef35539088b6d05b003557a189438a346beb3da7ec9510b93b431218a
|
Provenance
The following attestation bundles were made for meander_agent-0.1.0-py3-none-any.whl:
Publisher:
publish-pypi.yml on Symbolic-Intelligence-Org/meander-agent
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
meander_agent-0.1.0-py3-none-any.whl -
Subject digest:
f04f3fcd40778c7502be6a674942c2f59d3a4e92497cfc0f95e342272795564d - Sigstore transparency entry: 2084729534
- Sigstore integration time:
-
Permalink:
Symbolic-Intelligence-Org/meander-agent@6d3a667717657c5146cba52a74f72eda17cf4036 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/Symbolic-Intelligence-Org
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@6d3a667717657c5146cba52a74f72eda17cf4036 -
Trigger Event:
push
-
Statement type: