# Python SDK reference

Package-level reference for installation, public interfaces, observation boundaries and delivery behaviour.

## Package and basic usage

Sigillum Finans Python SDK 0.11.0 is a standard wheel for Python 3.10+. opentelemetry-api, opentelemetry-sdk, httpx and cryptography are required dependencies. Application teams do not need to build a helper service. The Sigillum installation provides durable transfer through its product components.

```sh
python -m pip install sigillum_finans_sdk-0.11.0-py3-none-any.whl
```

Place the portal-generated configuration for the target service at a fixed application path and pass an absolute path to start. Configuration is not searched for in the working directory and is read only by the background worker.

```python
from pathlib import Path
from sigillum_finans import start

sdk = start(Path(__file__).resolve().parent / ".sigillum" / "config.json")
app = sdk.wrap_asgi(app)  # FastAPI/Starlette and standard ASGI HTTP
```

Configured HTTP entry points and in-scope HTTPX requests are then captured automatically, including calls through existing HTTPX clients. No separate profile file is required. Native ASGI and HTTPX behaviour is preserved. The supported boundaries are HTTPX 0.27/0.28 and standard ASGI HTTP messages. WebSockets, arbitrary sockets, other HTTP clients and hidden model internals are not automatically captured.

An unread inbound request is considered completely empty only when actual HTTP/1.0 or HTTP/1.1 framing establishes that there is no body: Transfer-Encoding must be absent, and Content-Length must be absent or a single valid zero. No observed bytes, chunks or capture loss may exist at termination. The HTTP method alone is insufficient. Duplicate, conflicting or invalid headers, HTTP/2, HTTP/3 and unknown protocols remain incomplete until natural consumption establishes completion. The SDK does not read, drain or rewind the request to make this determination.

For batch or worker services, use the actual execution name configured in the product:

```python
with sdk.start_execution("daily-calculation"):
    run_existing_work()
```

Without a valid incoming Sigillum carrier, the entry kind, operation name and any HTTP path must exactly match entryPoints. Application ownership is not guessed. Shared-service configuration is not bound to its first application; ownership follows the actual signed root context.

The configuration format is sigillum.sdk-config/2. Each entry definition contains a signed scopeToken with the application’s service coverage and definition revision. A shared service records only within that execution’s fixed scope. Outside scope, it propagates the transaction/trace/parent relationship without creating its own step or record. Connecting another application does not require adding that application to the shared service’s configuration or reinstalling the SDK.

A separate background worker refreshes entry definitions every 30 seconds from the internal Sigillum transfer endpoint. Requests do not wait for refresh. Invalid, older or incomplete responses do not replace a valid profile. In-flight executions retain their signed scope. SDK package and configuration must belong to the same generation; older configuration formats are not silently accepted.

## Optional local transfer agent

If the product installation provides a local Sigillum transfer agent, add the following optional section to the existing sigillum.sdk-config/2 file. Preserve all other identity, signed-scope and trust fields.

```json
"localTransfer": {
  "endpoint": "http://127.0.0.1:29520",
  "token": "PRODUCT_INSTALLER_GENERATES_A_LOCAL_TOKEN"
}
```

The address must have the form http://127.0.0.1:<port>. Remote hosts, localhost, IPv6, paths, queries, fragments and user information are rejected. The installer-generated token contains 32–256 ASCII characters in the range 33–126, without spaces. Proxy environment variables and redirects are not used.

The SDK worker sends prepared observations from the same execution and fixed scope as SFRAW001 to /v1/captures. Metadata carries the original envelope without its content field, the actual root acceptance and attachment definitions. JSON number tokens and attachment bytes are preserved. The product agent generates content hashes/references, SFREC001, encrypted custody and remote delivery.

A normal package contains at most 16 records / 8 MiB. A valid larger single record is sent alone within the 32 MiB package limit. Profiles come only from the same agent’s /v1/profiles endpoint and are verified using the independent scope signature.

The SDK releases its RAM copy only after a sigillum.local-handoff/1 HMAC receipt bound to the delivery identity, raw-package SHA-256 and exact record count. local_durable_records counts agent custody; it does not increment custody_accepted as if remote custody had completed. Agent unavailability or invalid receipts cause the same package and identity to be retried within the bounded memory budget. There is no direct remote fallback and the business request does not wait.

Local retries run in the worker with exponential backoff and small jitter, starting at the configured initial delay. The ceiling is 30 seconds unless the initial delay is higher, in which case that value is the ceiling, up to 300 seconds. Success clears that package’s retry state. Existing direct-remote retry behaviour is unchanged.

Without localTransfer, the direct HTTPS/SFREC path and P-256 custody receipt remain in use. The agent is a Sigillum product component, not a customer-built helper.

## Additional observations and AI calls

The common families are lifecycle, ai, http, otel, context, source and custom. AI JSON objects can describe model parameters and versions, inputs/outputs, classification and scores, retrieval references and ranking, tool selection and results, embedding, inference, evaluation, policy and source-provided explanations. They are not tied to one business-domain class.

```python
from sigillum_finans import Attachment, JsonData

with sdk.start_step("actual-inference") as step:
    step.observe("ai", "inference.features", JsonData.raw(original_features_json))
    result = existing_inference()
    step.observe("ai", "inference.completed", JsonData.raw(original_result_json))
    step.observe("source", "comparison.source", JsonData.raw(original_source_metadata),
                 attachments=(Attachment("original", original_bytes,
                                         purpose="comparison.originalContent"),))
```

JsonData.raw accepts an existing str or immutable bytes containing a JSON object. Source numbers are not rounded, and explicit null is not converted into absence. Attachments preserve original bytes. Arbitrary live objects, getters and iterators are not traversed. Raw-JSON validation and transport encoding run in the worker. Missing, invalid or oversized observations affect loss counters and are not presented as complete.

The supplied call boundary executes an existing operation exactly once:

```python
result = sdk.call_ai("actual-model", JsonData.raw(input_json), existing_call,
                     output=lambda value: JsonData.raw(value.original_json))
# Use await sdk.call_ai_async(...) for asynchronous calls.
```

No output selector is needed when the result is a JSON str/bytes value. Otherwise supply a short selector returning actual raw JSON. Selector failure does not change the real operation’s return value or exception. Streams and lazy results are not pre-consumed. At custom streaming boundaries, call observe during actual consumption. Automatic HTTP stream capture occurs only during normal reads/writes and emits 64 KiB chunks.

## Optional references and portable context

```python
from sigillum_finans import References, ExternalReference

sdk.set_references(References(session_ref="institution-session-reference",
                              person_ref="institution-person-reference",
                              external=(ExternalReference("native.trace", "source-id"),)))
carrier = sdk.export_context()
# An uninstrumented asynchronous transport can preserve this carrier unchanged.
with sdk.start_execution("continuation", carrier=carrier):
    continue_existing_work()
```

Session and person references are optional lookup aids; transaction relationships do not depend on them. Use appropriate institutional references, not login tokens. inject(headers) adds only x-sigillum-context. It does not overwrite an existing Sigillum header or traceparent/tracestate. Conflicts are counted. HTTPX propagates the header automatically for in-scope calls. A missing signed carrier does not cause the old root to be inferred.

If optional references exceed the 16 KiB carrier limit, only the propagated references object is cleared. Actual execution/trace/span identities remain complete under a new MAC. Source context and records retain their full references. referencePropagationPartial reports this partial metadata propagation; no arbitrary subset is presented as complete.

## OpenTelemetry and runtime behaviour

The SDK includes official OTEL with an independent ALWAYS_ON provider and a queue-only span processor. It does not modify the institution’s global provider, sampler, exporter or existing OTEL context. Actual SDK span identities are linked to their process record. Source/tool events are not duplicated as additional business steps.

Resource, instrumentation scope, attributes, events, links, status and actual dropped-item counters are retained. Native OTEL records carry observerContext for product trace/span/parent relationships and nativeContext for official-provider identities. Both fields remain present even when IDs match; different IDs are not represented as equal. Add real technical information through operation.set_otel_attribute(name, primitive), operation.add_otel_event(name, primitive_dict) and operation.add_otel_link(trace_id, span_id, primitive_dict).

start, observe, start_execution, start_step, reference/context methods and close do not wait for observation network calls, disk, acknowledgements or flush. The worker retries the same delivery identity and package bytes. In direct mode, a RAM entry is removed only after a durable custody receipt signed by the separately pinned P-256 key and verified against exact scope/package/record bindings. The SDK does not observe its own transfer HTTP calls.

sdk.status exposes state, accepted, custody_accepted, local_durable_records, lost, pending_records, pending_bytes and last_error. accepted means RAM acceptance, not persistence. Product transfer provides the first durable acceptance. A process exit before that point can lose RAM records. At capacity, business execution continues; loss is counted and reported in a subsequent connected scope.

close() stops new acceptance without waiting. The daemon worker may drain the existing queue while the process remains alive; there is no guaranteed-delivery flush at process exit.

The default shared maximum is 64 MiB / 1,024 records. Pending encoded packages, original snapshots and temporary encoding copies count towards one conservative budget. A prematurely closed stream or exhausted observation budget produces complete=false in the terminal record; truncated content is not silently marked complete.

## Technical verification

Source tests run with python -m pytest sdk/python/tests. consumer/conformance.py CONFIG OUTPUT [INCOMING_CARRIER] is a test consumer, not an institutional service or startup helper. The distributed package does not depend on the source repository or test-vector files.