Metadata-Version: 2.4
Name: aiko-monitor
Version: 0.0.23
Summary: AIKO Monitor SDK for Python.
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: httpx<1.0,>=0.27.0
Requires-Dist: pydantic<3.0,>=2.0

# AIKO Python SDK

The SDK starts interpreter and outbound-network observation whenever an enabled `Monitor` is created. There is no separate runtime or egress opt-in. Flask and FastAPI integrations preserve HTTP capture, while generic WSGI/ASGI wrappers add request attribution for Django and other applications using those protocols. Non-web Python programs use the same observer without a framework adapter.

The package requires CPython 3.10 or newer. CPython 3.12+ uses `sys.monitoring`; 3.10/3.11 use profiling on the installing thread and future `threading` threads. The fallback does not attach to existing threads or replace another profiler. Initialization fails explicitly when no monitoring slot is available or profiling is already owned. Use one active Monitor per interpreter.

## Flask and FastAPI

```python
import os

from aiko_monitor import Monitor
from flask import Flask

app = Flask(__name__)
monitor = Monitor(
    app,
    project_key=os.environ["AIKO_PROJECT_KEY"],
    secret_key=os.environ["AIKO_SECRET_KEY"],
    service="web",
    deployment="production",
)
```

For FastAPI, pass a `FastAPI()` application in the same way. Initialize before serving requests. Monitor delivery runs in background workers and does not gate application startup on receiver availability. Invalid local configuration still raises during initialization. Call the synchronous `monitor.destroy()` during the worker's graceful shutdown.

## Django

Initialize inside each serving worker. In `wsgi.py`, after the usual Django settings environment setup:

```python
import os

from aiko_monitor import Monitor
from django.core.wsgi import get_wsgi_application

monitor = Monitor(
    project_key=os.environ["AIKO_PROJECT_KEY"],
    secret_key=os.environ["AIKO_SECRET_KEY"],
    service="django",
    deployment="production",
)
application = monitor.wsgi(get_wsgi_application(), framework="django")
```

For `asgi.py`, use `get_asgi_application()` and `monitor.asgi(..., framework="django")`. An existing Django handler can also be passed to `Monitor(handler, ...)`; serve the returned `monitor.app`, which contains its wrapper. Flask/FastAPI applications are instrumented in place and should not also receive a generic HTTP wrapper.

## Other frameworks and non-web programs

Wrap a standard WSGI callable with `monitor.wsgi(application)` or an ASGI callable with `monitor.asgi(application)`, then serve that returned callable. The observer does not depend on the framework name. HTTP request attribution depends on the wrapper, and compatibility beyond the tested protocol cases must be verified with the application's server and middleware.

For a script or worker, create `Monitor` before the work and call `destroy()` in a `finally` block after the work. For a prefork server, initialize after fork in each worker; inherited exporter threads and identities cannot be reused. Subinterpreters and alternative Python implementations are not verified.

## Outbound connections

Egress observation starts with `Monitor` and works independently of the application framework. It covers standard `http.client`/`urllib` and Requests paths, HTTPX's built-in synchronous and asynchronous transports, standard aiohttp clients, and Python TCP/TLS and asyncio connection boundaries. Standard aiohttp imports after Monitor initialization are instrumented automatically; aiohttp is not a required dependency.

Each observed network hop records its requested protocol, hostname and port separately from the actual socket peer address and port when available. Redirects retain separate hops, and requests using pooled connections remain observable. Operation and hop identities prevent nested HTTP/TCP/TLS wrappers from counting the same call repeatedly. Request context is captured when the operation starts; work outside a wrapped request has process context.

An attempted effect precedes the observed call. HTTP completion means response headers arrived; it does not mean a successful status or a consumed response body. TCP completion means connection establishment, and TLS completion requires a handshake. Errors retain their original application exceptions. Inbound responses and the SDK's own exports do not produce egress effects.

TLS records include encryption, negotiated protocol and connection-creation verification policy when those facts are available. `authorized` remains absent when authorization is unknown, including successful verification-disabled Python connections. A known certificate verification failure is distinct from unknown authorization. Successful configured certificate verification does not independently prove hostname checking was enabled. Egress metadata omits URL paths, queries, credentials, headers, bodies and certificates; existing HTTP capture keeps its own contract.

The events use the same signed `/api/ingest/runtime` endpoint and Monitor outbound aggregation. No additional endpoint or framework-specific egress setup is needed. The collector does not block calls. Custom/native transports, cached original methods, HTTP upgrades and arbitrary socket reuse can bypass these boundaries. Pending raw nonblocking connections remain attempted unless a supported completion boundary is observed. Active operations, redirect hops and TLS policy state are bounded; known collection gaps are included in the runtime manifest and local `network_errors` health counter. Closing Monitor releases owned hooks and reports unfinished operations without inventing outcomes.

## Observation and delivery

The SDK sends these facts through the existing configured endpoint origin at `/api/ingest/runtime`, using the project's credentials and HMAC over the exact gzip batch:

- A runtime manifest, Python version and bounded installed-package inventory.
- Outbound operation boundaries and network effects, with destination, peer and available TLS facts.
- Collector health every 30 seconds and on explicit flush, including known loss and collection errors.

Detailed execution and audit tracing is off by default. Set `runtime_execution=True` when constructing `Monitor` to also collect code-object identity, code name, filename basename and a digest limited to `co_code` bytes; raw execution phases with thread identity, bytecode offset and request context; and selected audit notification names without arguments, locals or source contents. This opt-in collector requires CPython 3.10 or newer and can produce substantial traffic. Inventory, outbound observation and HTTP capture do not require it. The runtime manifest identifies which collectors are enabled.

`monitor.ready()` and `monitor.flush()` are explicit delivery-verification operations. They wait for exact matching receiver acknowledgements for their admitted prefix and raise on known admission or delivery failure. Use them in installation checks, not as application startup gates. They are synchronous; do not await them or run their blocking wait in a busy async event loop. Wizard verification still requires receiver receipts. `monitor.destroy()` detaches the observer, drains with a bounded wait and closes the existing HTTP sender. `monitor.runtime.health()` exposes local counters. HTTP capture and runtime export retain their separate delivery lifecycles.

Each transport logs failed delivery through `aiko.monitor`, without credentials or captured content. HTTP drop warnings are limited to one per second and include a cumulative drop count; runtime delivery warns once per instance. Runtime health counters expose ongoing runtime loss.

The queue retains at most 4,096 events and 4 MiB of losslessly compressed payload, including in-flight events; allocator overhead is separate. Raw events are capped at 60,000 bytes and complete raw batches below 65,536 bytes. Code and thread registries have count limits. A limit or failed receiver can cause loss, which remains explicit locally; even a health event cannot be guaranteed delivery when the receiver is unavailable. Shutdown is best effort, and abrupt termination can lose unsent records.

The interpreter observer does not block application actions. These records do not classify every RCE, reconstruct native-extension memory history, identify unique closures or prove authorization. A syscall is not required to observe a Python body entry, but native execution entirely inside an extension is a different observation boundary. Version-specific missing generator phases are retained as gaps. Copied async request context may outlive its request. The application shares the observer's interpreter and can alter its hooks or SDK state; these events are not tamper-resistant attestation.

FastAPI and Flask reuse the ASGI/WSGI stream adapters. Responses are forwarded as they arrive; capture does not collect and replay the entire response. ASGI non-HTTP scopes and extension messages pass through. WSGI reads, including `readinto`, retain request bytes as the application consumes them.

HTTP capture retains at most 64 MiB per body and 128 MiB of raw body bytes across the process. Parsing, redaction and compression run in sender workers, with two concurrent encoders. Incomplete or over-budget bodies are omitted with `*_body_unavailable` and observed `*_body_size_bytes`; truncated content is never presented as complete. The entire encoded event must fit the receiver's 64 MiB envelope; otherwise metadata is sent with both bodies marked unavailable.

The HTTP sender admits at most 5,000 waiting events and 128 MiB of raw captured body plus encoded metadata bytes across waiting and active work. Events expire after five minutes, each HTTP timeout is 30 seconds, and up to three attempts honor `Retry-After`. HTTP shutdown waits at most ten seconds; an already-running synchronous HTTP call can finish later in its daemon worker. These are logical byte budgets, not a process RSS ceiling: parsed objects, compression, allocator overhead and application-owned bodies are separate. There is no durable local spool, and sustained overload or unavailable receivers can cause explicit loss.

## Verification and research

The [shared research record](research/README.md) contains the five agents' codepath notes, full-read manifests, primary sources, hypotheses, retained negative results and source-bound experiments. Six fresh-process framework paths passed actual Monitor gateway/service/PostgreSQL readback: Flask, FastAPI, Django WSGI, Django ASGI, generic WSGI and generic ASGI. The [receiver report](research/framework-receipt/README.md) distinguishes real runtime persistence from the HTTP fixture receiver.

The [egress research](research/egress/README.md) compares the Node contract with Python behavior, including paired application baselines, redirects, proxies, pooling, TLS policy, async request attribution, and real Monitor receipt and outbound queries.

The [SDK matrix](research/sdk-observation/README.md), [lifecycle review](research/implementation-review/README.md), [transport controls](research/transport-receipt/README.md) and [native-boundary experiments](research/native-boundary/README.md) qualify what those results establish. They are finite observations, not universal application coverage, a performance benchmark or a publication-novelty claim.
