Skip to main content

o11y-one

The Python client for the O11y One API. A thin, hand-written layer over the generated o11y-one-api-agentic package.

Install

uv add o11y-one        # or: pip install o11y-one

Use

import os

from o11y_one.sdk import O11yClient, Disposition, classify
from o11y_one.agentic.v1.evaluation_pb2 import ListEvaluationDefinitionsRequest
from o11y_one.agentic.v1.evaluation_connect import AgenticEvaluationServiceClientSync

o11y = O11yClient(
    base_url="https://grpc.o11y.one",
    credential=os.environ["O11Y_API_KEY"],  # o11y_mach.<selector>.<secret>
    org_id=os.environ["O11Y_ORG_ID"],
)

evals = o11y.service_sync(AgenticEvaluationServiceClientSync)
try:
    defs = evals.list_evaluation_definitions(ListEvaluationDefinitionsRequest())
except Exception as err:  # noqa: BLE001 - classify sorts it out
    failure = classify(err)
    if failure.disposition is Disposition.INSUFFICIENT_SCOPE:
        raise SystemExit(f"credential is missing scope {failure.missing_scope}") from err
    raise

What is here, and what deliberately is not

Three modules and nothing else:

module responsibility
o11y_one.sdk.client transport construction, credential and scoping header injection
o11y_one.sdk.auth the credential wire format and local structural validation
o11y_one.sdk.errors the failure taxonomy: reauthenticate / insufficient-scope / version-skew / retry / unclassified

Service methods are not wrapped. o11y-one-api-agentic already ships a client class per service; a hand-written facade over all of them would be a second API surface to keep in sync with the proto, and it would rot the first time a field is added upstream. Import the generated client class and hand it to O11yClient.service_sync() / O11yClient.service().

Record and replay

An externally executed candidate's runtime routes each model and tool call through a lease tape. Under record, live runs and the call is buffered, then sent to RecordEvaluationCaseSteps within the bounds the server publishes on sdk_record_replay. Under replay, live never runs: each call is answered from the source recording through LookupReplayStep. The harness code is the same in both modes.

evals = AgenticClientSync(client.service_sync(AgenticEvaluationServiceClientSync))
tape = evals.record_lease(evaluation_run_id=run_id, lease=lease)  # or replay_lease
case = tape.case(leased_case)
reply = case.model(request_json, lambda: call_model(request_json))
hits = case.tool("search", args_json, lambda: search(args_json))
case.finish()
tape.flush()  # record, then submit
evals.submit_case_outputs(...)

AgenticClient returns the async twin, whose live is a coroutine function. The sync tape is safe to share across the threads a harness fans its cases out on.

  • ReplayDivergedError names the step, the divergence kind and both request digests. The server has already made it the case's verdict: stop the case and do not submit it.
  • LeaseRefusedError carries a lease refusal (CASE_ALREADY_SUBMITTED, LEASE_EXPIRED, ...). RecordingUnavailableError means this deployment cannot record or replay, and is not retryable.
  • A ConnectError with code UNAVAILABLE is transient; the buffered calls are kept and the next flush() resends them.
  • Steps that never reached a flush are lost with the process, and the case replays as RECORDING_INCOMPLETE.
  • Redaction is the server's, before anything is digested or stored. The SDK redacts nothing and logs no call.

The tape records and replays. o11y-eval run --record|--replay --candidate <key> only asserts, before launching, that the candidate is the kind that mode needs; the runner never executes a harness.

Code scorers

A code scorer is a Python score(input, context) the server runs in its sandbox. A member drafts and previews one; an admin publishes it, and only a published version runs across a dataset. o11y_one.sdk.scorer types the contract.

These verbs are a person's, never a machine credential's, so the client carries no credential and each call carries the person's token:

from connectrpc.request import Headers
from o11y_one.sdk.code_scorer import CodeScorerClientSync

o11y = O11yClient(base_url="https://grpc.o11y.one", org_id=os.environ["O11Y_ORG_ID"])
scorers = CodeScorerClientSync(o11y.service_sync(AgenticEvaluationServiceClientSync))
me = Headers({"authorization": f"Bearer {os.environ['O11Y_USER_TOKEN']}"})
draft = scorers.create_code_scorer(
    config_key="exact-answer",
    name="Exact answer",
    metric_name="quality.exact",
    files={"score.py": open("score.py").read()},
    idempotency_key="exact-answer-v1",
    headers=me,
)
result = scorers.preview_code_scorer(  # waits at most wait_seconds for the sandbox
    score_config_id=draft.score_config_id,
    scorer_input={"candidate_output": "42", "reference_output": "42"},
    idempotency_key="exact-answer-preview-1",
    headers=me,
)
publishing = scorers.publish_code_scorer(  # admin; GetEvaluationScorerConfig reads the outcome
    score_config_id=draft.score_config_id,
    smoke_inputs=[{"candidate_output": "42", "reference_output": "42"}],
    idempotency_key="exact-answer-publish-1",
    headers=me,
)

Each returns a Refusal for a typed rejection (SANDBOX_UNAVAILABLE, CODE_SPEC_INVALID, CODE_INPUT_INVALID, CODE_VERSION_IMMUTABLE, ...), whose message starts with the server's stable code.

A TypeScript scorer runs where the deployment serves the typescript-base runtime. Its bundle is .ts files with the entry score.ts, and it imports only its own files (relative, naming the .ts) and Node's built-ins: no npm packages and no network. scorer-kit holds its types, a seeded PRNG to copy into the bundle and an example score.ts. There, context.seed and context.frozen_evaluation_time_unix_nanos are bigints.

from pathlib import Path

from o11y_one.agentic.v1.evaluation_pb2 import GetAgenticEvaluationCapabilitiesRequest

service = o11y.service_sync(AgenticEvaluationServiceClientSync)
served = service.get_agentic_evaluation_capabilities(
    GetAgenticEvaluationCapabilitiesRequest(), headers=me
).capabilities
kit = Path("packages/sdk-ts/scorer-kit")
draft = scorers.create_code_scorer(
    config_key="sampled-lines",
    name="Sampled lines",
    metric_name="quality.sampled_lines",
    language="typescript",
    files={f: (kit / f).read_text() for f in ("score.ts", "o11y_scorer.ts", "o11y_prng.ts")},
    capabilities=served,  # its served TypeScript runtime; without it, typescript-base v1
    idempotency_key="sampled-lines-v1",
    headers=me,
)

A path that is not identifiers ending in the language's extension, or a TypeScript entry other than score.ts, raises ValidationError before the wire, and so does a language the capabilities serve no runtime for (code_language_unsupported).

The distinction this package exists to preserve

UNAUTHENTICATED and PERMISSION_DENIED are different problems:

  • UNAUTHENTICATED — the credential is absent, malformed, unknown, expired, or revoked. Re-auth. Retrying cannot change the answer.
  • PERMISSION_DENIED — the credential is fine; it lacks a scope (the server names which one) or it was presented to a non-machine surface.

classify() keeps them apart, along with UNAVAILABLE (auth backend down — retry with backoff) and INVALID_ARGUMENT (SDK/server version skew). Collapsing these into a single "auth error" is the failure mode this module exists to prevent.

Credentials

A machine credential is o11y_mach.<selector>.<secret>, presented in the x-o11y-key header. It is:

  • returned exactly once, by CreateMachineCredential. There is no read-back RPC and no recovery path — lose it and you rotate.
  • rotated create-then-revoke, not atomically. A principal may hold several active credentials at once, which is what makes zero-downtime rotation work: create the new one, deploy it, then revoke the old.
  • revoked effective on the next request, not on a TTL boundary.
  • expiring at +365 days by default, capped at three years.

Never send it as authorization: Bearer — that path carries the browser session JWT and a machine credential presented there is rejected.

Metadata

Release files for o11y-one 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for o11y-one 0.2.0
File Size Uploaded
o11y_one-0.2.0.tar.gz 34.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for o11y-one 0.2.0
File Interpreter ABI Platform
o11y_one-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 75.0 kB

Release files / o11y_one-0.2.0.tar.gz

Download URL o11y_one-0.2.0.tar.gz
Size 34.7 kB
Tags Source
SHA-256 checksum
How to use checksums
17e45a90ade034e5eacc562a5d6bbbcf3e9d28e90ed7366f45a2e5b832022d5a
BLAKE2b-256 checksum
How to use checksums
c40483f9e253d5e927da6261645879e501ad6e9ba6144b699a7414ceabda5891
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / o11y_one-0.2.0-py3-none-any.whl

Download URL o11y_one-0.2.0-py3-none-any.whl
Size 40.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
bb9ba974dd6c4741c7915bfaf8183a678a0fc1bd500889e02179a5a7185ec507
BLAKE2b-256 checksum
How to use checksums
aa158044088ef21736d17a45189bd4cab899a621b66ba8f9b50693e1c1ea2cb8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page