Metadata-Version: 2.4
Name: envpod-coat
Version: 0.1.16
Summary: Agent telemetry SDK for envpod — two functions, full observability
Author-email: "Mark Amo-Boateng, PhD" <mark@envpod.dev>
License: Apache-2.0
Project-URL: Homepage, https://envpod.dev
Project-URL: Documentation, https://envpod.dev/docs/agent-coat
Project-URL: Repository, https://github.com/markamo/envpod-ce
Project-URL: Issues, https://github.com/markamo/envpod-ce/issues
Keywords: ai,agents,telemetry,observability,envpod,aot,otel
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: System :: Monitoring
Classifier: Programming Language :: Python :: 3
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: Operating System :: POSIX :: Linux
Classifier: Operating System :: MacOS :: MacOS X
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
Requires-Dist: pytest-cov>=4.0; extra == "dev"
Provides-Extra: anthropic
Provides-Extra: langchain
Requires-Dist: langchain-core>=0.2; extra == "langchain"
Provides-Extra: claude-agent
Requires-Dist: claude-agent-sdk>=0.1; extra == "claude-agent"
Provides-Extra: all
Requires-Dist: langchain-core>=0.2; extra == "all"
Requires-Dist: claude-agent-sdk>=0.1; extra == "all"
Dynamic: license-file

# envpod-coat

Two functions. Full agent observability. Zero runtime dependencies.

```python
from envpod_coat import coat, tie
```

`@coat` wraps sync and async functions. It emits span lifecycle events with
the function name, input and output previews, timing, and trace/span IDs
that the envpod runtime correlates with pod audit events.

When you turn on `auto_capture=True` for a sync function, the decorator
also captures a sanitized snapshot of the function's local variables on
return. `auto_capture` is off by default, and async wrappers deliberately
skip local-variable capture — use `tie()` for intermediate state in async
code.

`tie()` reports from inside a function when you need intermediate state.
The metaphor: wear the coat, tie up the loose ends inside it.

> `emit` is exported as an alias for `tie` if your fingers prefer the
> standard observability verb. `Op` is exported as an optional
> convenience enum (`@coat(Op.LLM)` ≡ `@coat("llm")`).

Together with envpod's kernel telemetry, you get three-layer observability:
**what the agent thought, what the machine did, and who authorized it.**

## Status

v0.1.16 — telemetry only. The decision plane (Allow / Deny / Modify / Queue
gates) lands in v0.3.0. This release ships the reference producer that emits
canonical AOT (`aotp/0.1`) envelopes to the envpod pod runtime.

New in v0.1.16:

- **`Op` enum** — convenience typing shortcut. `@coat(Op.LLM)` is the
  same as `@coat("llm")`; strings still work for custom domain tags.
- **`auto_capture` is binary + off by default** — turn it on per
  function with `@coat(Op.LLM, auto_capture=True)` or globally via
  `ENVPOD_COAT_CAPTURE=redacted`. Env var wins over decorator.
- **PID-aware correlation** — every coat envelope travels with the
  caller PID. The runtime combines emitted span fields, PID, and time
  windows to join coat spans with DNS, vault, and other pod events.
  Ambiguous kernel events (e.g. overlapping async coroutines on the
  same PID) render with a `[heuristic]` tag in `envpod audit
  --group-by-span`; precise attribution via trace/span propagation
  INTO kernel events ships in v0.1.17.
- **Server-side identity injection** — `subject.agent_id` is
  overwritten by the SO_PEERCRED-resolved claim; a tampered SDK
  cannot forge a registered-agent identity.
- **Anonymous fallback** — callers that haven't claimed yet get
  `envpod.claim_kind = "anonymous"` plus an anonymous fingerprint,
  distinct from named agents in dashboards.
- **Framework adapters** — `envpod-coat[langchain]` and
  `envpod-coat[claude-agent]` extras replace manual wrapping with a
  one-line registration. Four more frameworks follow in v0.1.17.
- **OpenInference OTEL export** — every coat event whose kind maps
  to an OpenInference span kind (LLM / TOOL / AGENT / CHAIN /
  RETRIEVER) emits OpenInference attributes alongside `envpod.*`
  on the same OTLP log record. Phoenix, Arize, and Datadog LLM
  render coat spans natively when you point `pod.yaml`'s
  `otel.endpoint` at them. See `docs/AGENT-COAT.md` → Transport for
  the attribute table.

## Install

```bash
pip install envpod-coat
```

Python 3.10+. Zero runtime dependencies.

## Quickstart

```python
from envpod_coat import coat, tie

@coat("llm")
def ask_claude(messages):
    response = client.messages.create(
        model="claude-sonnet-4-6",
        messages=messages,
    )
    return response

@coat("tool")
def write_file(path, content):
    with open(path, "w") as f:
        f.write(content)

@coat("tool")
def process_batch(rows):
    for i, batch in enumerate(chunk(rows, 100)):
        result = transform(batch)
        tie("batch", {"i": i, "size": len(batch)})
    return result

response = ask_claude([{"role": "user", "content": "refactor main.py"}])
write_file("main.py", response.content[0].text)
```

That's it. Every call emits telemetry to envpod. Add `auto_capture=True`
when you want local variable capture on a specific sync function. The pod
runtime uses the emitted span IDs for coat-to-coat relationships and
combines PID with time windows to line up coat spans with kernel events
(DNS, vault, etc.).

## Identity-aware coat events

Every coat envelope carries a Layer-C identity field set server-side —
the SDK does NOT self-claim. Your in-pod process declares a registered
agent once via the main `envpod` SDK, and subsequent `@coat` events
inherit that claim automatically:

```python
from envpod import InPodClient
with InPodClient() as c:
    c.claim("reviewer", ttl_secs=3600)   # claim first

from envpod_coat import coat, Op
@coat(Op.LLM)                            # then coat events carry
def review(code): ...                    # reviewer's claim_id
```

The claim-binding flow lives in the main `envpod` package because the
kernel trust root is `SO_PEERCRED` on `identity.sock`. **`envpod-coat`
does not expose a `claim()` function** — the decorator consumes
claimed identity; it does not produce it.

Dashboards distinguish claimed vs. anonymous callers by the
`envpod.claim_kind` attribute. Unclaimed processes still get traceable
audit entries under an anonymous fingerprint (`envpod.anonymous.*`)
but stay out of the registered-agent namespace.

## Transport

Inside an envpod pod (`ENVPOD_POD_ID` set, `/run/envpod/queue.sock`
present), events go through the queue socket and become entries in
`audit.jsonl` interleaved with kernel events.

Outside a pod, events fall through to `stderr` as JSON lines prefixed with
`[coat]` so you can develop locally and see your telemetry. When you deploy
inside a pod, kernel correlation activates automatically.

## Failure mode

Telemetry is best-effort. If the queue socket is missing or the write
fails, the SDK logs one warning and falls back to stderr. **The agent is
never blocked on telemetry delivery.** Fail-closed semantics arrive in
v0.3.0 with the decision plane.

## Wire format

Canonical AOT (Agent Operation Taxonomy) envelope. Server-side
identity injection fills in `subject` and `envpod.*` fields on the pod
runtime, so the SDK itself never needs to declare identity.

**Claimed caller** (process has called `InPodClient.claim(...)`):

```json
{
  "spec": "aotp/0.1",
  "kind": "agent.llm.request",
  "ts": "2026-04-14T01:30:00.123Z",
  "ids": {
    "session_id": "sess_abcd1234",
    "run_id": "run_abcd1234",
    "step_id": "step_abcd1234",
    "trace_id": "tr_abcd1234",
    "span_id": "sp_abcd1234",
    "parent_span_id": ""
  },
  "subject": {
    "agent_id": "reviewer",
    "agent_role": "code-review",
    "framework": "envpod-coat",
    "framework_version": "0.1.16"
  },
  "capture": { "mode": "redacted" },
  "payload": {
    "fn": "ask_claude",
    "preview": { "args": [], "kwargs": {} }
  },
  "envpod": {
    "pod_id": "pod_demo",
    "pid": 4242,
    "claim_kind": "claimed",
    "agent_claim_id": "jti_xyz",
    "capabilities": ["read", "commit"],
    "session": "sess_xyz"
  }
}
```

**Anonymous caller** (no claim yet — the registered-agent namespace
stays untouched; a traceable fingerprint rides in `envpod.anonymous`
instead):

```json
{
  "spec": "aotp/0.1",
  "kind": "agent.llm.request",
  "subject": { "agent_id": "", "agent_role": "" },
  "envpod": {
    "pod_id": "pod_demo",
    "pid": 999,
    "uid": 60000,
    "claim_kind": "anonymous",
    "anonymous": {
      "pid": 999,
      "uid": 60000,
      "session": "sess_999",
      "fingerprint": "anon_pid999_uid60000_sesssess_999"
    }
  }
}
```

See `ideas/agent-coat/OPERATION-TAXONOMY.md` in the envpod repo for
the full spec of the 16 core operations (`agent.session.*`,
`agent.run.*`, `agent.step.*`, `agent.llm.*`, `agent.tool.*`,
`agent.memory.*`, `agent.subagent.*`, `agent.human.request`).

## Environment variables

| Variable | Default | Purpose |
|---|---|---|
| `ENVPOD_POD_ID` | unset | When set, transport prefers the queue socket. |
| `ENVPOD_SOCKET` | `/run/envpod/queue.sock` | Override socket path. |
| `ENVPOD_COAT_CAPTURE` | unset (→ `none`) | `none` / `redacted` / `hash` / `summary` / `raw`. Turns capture on globally. Overrides decorator. |
| `ENVPOD_COAT_PREVIEW_LEN` | `200` | Max chars per captured value. |
| `ENVPOD_AGENT_ID` | unset | Populates `envelope.subject.agent_id` on non-envpod transports. Inside a pod, the daemon overrides this from the kernel-authenticated claim. |
| `ENVPOD_AGENT_ROLE` | unset | Populates `envelope.subject.agent_role` on non-envpod transports. |

## Licence

Apache-2.0 with explicit patent grant. The envpod runtime stays Premium
proprietary; this SDK is permissive so any agent framework can adopt it.

## Roadmap

- **v0.1.15** — Two-function Python SDK, queue socket transport,
  canonical AOT emission.
- **v0.1.16** (this release) — `Op` enum, binary `auto_capture`, PID
  correlation, server-side identity injection, anonymous fallback,
  LangChain + Claude Agent SDK adapters, `envpod audit --all
  --group-by-span` CLI.
- **v0.1.17** — Remaining adapters (OpenAI Agents, CrewAI, AutoGen,
  Pydantic AI), OverlayFS fanotify PID coverage, SDK→daemon
  span-context registration (precise kernel event attribution).
- **v0.3.0** — Governance gates: SDK upgrades to mediated mode against
  the new `envpod-coatd` daemon.
- **v0.3.1** — TypeScript SDK.
