Metadata-Version: 2.4
Name: ctxmesh
Version: 0.1.0b5
Summary: Python SDK for ctxmesh, the Kubernetes-native control plane for AI agents. Typed clients for memory, tools, knowledge, model calls and feedback, plus OpenTelemetry step tracing. Reads its endpoints from the environment the platform injects, so your code never holds credentials. Source, docs and issues: https://github.com/ctxmesh/ctxmesh
Author: ctxmesh
License: Apache-2.0
Project-URL: Homepage, https://ctxmesh.github.io
Project-URL: Documentation, https://ctxmesh.github.io/sdk/python/
Project-URL: Source, https://github.com/ctxmesh/ctxmesh
Project-URL: Issues, https://github.com/ctxmesh/ctxmesh/issues
Keywords: ctxmesh,llm,agents,ai-agents,kubernetes,observability,opentelemetry,mcp,llmops
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Operating System :: OS Independent
Classifier: Typing :: Typed
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: License :: OSI Approved :: Apache Software License
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: opentelemetry-sdk~=1.27
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc~=1.27
Requires-Dist: openinference-semantic-conventions~=0.1.30
Requires-Dist: jsonschema<5,>=4.20
Provides-Extra: dev
Requires-Dist: ruff==0.6.9; extra == "dev"
Requires-Dist: pytest==8.3.3; extra == "dev"

# ctxmesh — Python SDK

Typed clients for agents running on [ctxmesh](https://ctxmesh.github.io), the
Kubernetes-native control plane for AI agents.

Your agent runs in a pod beside the platform's sidecars. This SDK is the typed way to
reach them — conversation memory, tools and MCP servers, knowledge bases, model calls,
and feedback — plus OpenTelemetry step tracing that produces the same trace tree a
framework agent gets.

**Your code never holds credentials.** Endpoints and identity arrive in the environment
the platform injects, so there is no API key to manage and no base URL to configure.

```bash
pip install ctxmesh
```

The SDK is optional by design: every capability here is also a plain HTTP endpoint the
platform serves, so an agent in any language can call the contract directly.

- **Distribution name:** `ctxmesh` · **import name:** `ctxmesh`
- **Python:** 3.9+ · **runtime deps:** the plane clients are pure stdlib; the
  model + step-tracing helpers add a minimal OTLP/gRPC exporter +
  OpenInference semantic-convention constants, **pinned to the exact versions
  `images/base-python` already bundles** (opentelemetry `1.27.0`,
  openinference-semantic-conventions `0.1.30`) — so `import ctxmesh` adds **zero
  net footprint** to the base image.

## Surface

```python
from ctxmesh import agent

client = agent.from_env()          # in-pod: reads the launcher-injected env

# memory (:2998)
client.memory.get()                       # full context (list)
client.memory.put([{"role": "user", "content": "hi"}])
client.memory.append({"role": "assistant", "content": "hey"})
client.memory.search("hi")
# bind a conversationId for the turn (the agent reads it from the request):
turn = client.with_conversation("conv-42")
turn.memory.get()

# tools / discovery (:2999)
client.tools.list()                       # live manifest as Tool objects
client.tools.call("word-count", text="a b c")   # MCP tools/call

# feedback (:2995)
client.feedback.score("trace-abc", "thumbs-up", 1, comment="great")

# model gateway ($MODEL_GATEWAY_URL) — emits an OpenInference LLM span
resp = client.model.chat("gpt-4o-mini", [{"role": "user", "content": "q"}])
resp.text        # the completion; resp.usage → token counts; resp.raw → full body
```

`agent.from_env()` **fails fast** (`NotInPodError`) when no launcher env is
present — it never silently no-ops. For tests / offline use, build a
`PlaneConfig` explicitly (`PlaneConfig.for_test(...)`) and call
`agent.from_config(config)` against an in-process fake, so tests need no cluster.

## Step-tracing helpers (custom loops)

A framework agent (LangChain/OpenAI/Anthropic) gets its `step → tool → model`
trace tree SDK-free via base-image OpenInference auto-instrumentation. A **custom,
no-framework loop** has no inferable step boundaries, so it emits the tree
explicitly with `client.trace.*` — producing an OpenInference tree
**structurally identical** to a framework one (same `CHAIN`/`TOOL`/`LLM` span
kinds + attribute keys), exported over the same OTLP/gRPC path to the collector
(`:4317`) → Langfuse.

```python
# Bind the inbound /invoke request so the WHOLE tree roots under the launcher's
# `agent.invoke` span (the launcher injected a W3C `traceparent`). Without this
# bind the SDK spans would start a detached trace.
with client.trace.request_context(request.headers):
    with client.trace.step("plan") as step:            # CHAIN span
        step.set_input(user_prompt)
        plan = client.model.chat(model, messages)       # nested LLM span (auto)
        with client.trace.tool("web_search", args) as t:  # TOOL span (child of step)
            t.set_output(client.tools.call("web_search", **args))
        step.set_output(plan.text)
```

`client.trace.loop(name, headers=request.headers)` is a convenience that binds
the request context **and** opens the `AGENT` loop-root span in one `with`.

**Rooting under `agent.invoke` is the invariant** — the SDK extracts the W3C
`traceparent` the launcher proxy injects on every `/invoke` and makes the step
spans children of the launcher's `agent.invoke` root (same trace id, correct
parent span id), not a detached trace.

**Offline / telemetry resilience:** when `OTEL_EXPORTER_OTLP_ENDPOINT` is unset
(offline/tests) the trace client runs in **no-op export** mode — spans are still
created (so nesting/propagation still works and can be asserted) but exported
nowhere. Export/setup failures degrade to no-op and **never crash the loop** (a
telemetry blip is not an error); this is deliberately distinct from the plane
clients, which *surface* endpoint errors (a rejected memory write is a real
error).

## Serving an agent (`ctxmesh.serve`)

You don't hand-roll the HTTP server, the `/invoke` body/​envelope, the
`/healthz`/`/readyz` probes, the `$AGENT_PORT` the launcher proxies to, the
`traceparent` capture, or the SSE token stream — `ctxmesh.serve` encodes the whole
runtime contract, and it binds `request_scope` for you (so a custom loop keeps the
invoking user's run capability instead of silently downgrading to org/public creds):

```python
import ctxmesh

def handle(req: ctxmesh.InvokeRequest) -> str:
    # req.client is scoped to the caller (capability + granted approvals bound) and
    # conversation-aware (req.conversation_id); req.headers roots the trace.
    with req.client.trace.loop("my-agent", headers=req.headers):
        answer = req.client.model.chat(
            "gpt-4o-mini", [{"role": "user", "content": req.input}]
        )
    return answer.text          # or return a ctxmesh.ManagedResult for steps/tools/approval

ctxmesh.serve(handle)           # blocks; serves /invoke + health on $AGENT_PORT
```

- **Streaming** is transparent: call `req.emit_token(delta)` as your loop produces
  content — it emits an SSE `token` frame when the caller sent
  `Accept: text/event-stream`, and is a no-op otherwise (same handler, both modes).
- **The stock managed agent** is just `ctxmesh.serve()` with **no** handler — it runs
  the config-driven tool-calling loop (`run_managed_loop`) with `ManagedConfig.from_env()`.
  That is exactly the managed-agent image's entrypoint.
- `serve(handler, *, client=…, agent_name=…, port=…)` overrides the env defaults
  (`agent.from_env()` / `$AGENT_NAME` / `$AGENT_PORT`) — handy for local runs and tests.

`examples/sdk-custom-agent` shows the same loop with the HTTP handler written out by
hand — the "under the hood" reference for what `serve` collapses into one call.

## Documentation

- [Python SDK guide](https://ctxmesh.github.io/sdk/python/)
- [Compatibility](https://ctxmesh.github.io/reference/compatibility/) — which SDK works with which ctxmesh
- [Source and issues](https://github.com/ctxmesh/ctxmesh)

Apache-2.0. Contributor and toolchain notes live in
[the repository](https://github.com/ctxmesh/ctxmesh/tree/main/sdk/python).
