Skip to main content

Harness Python SDK with instrumentation and plugin architecture

Project description

harness-sdk (otel-python-sdk)

Generic Python agent SDK with OpenTelemetry instrumentation and a plugin architecture.

Local development

bash scripts/fetch-vendor.sh
pip install -e ".[dev,anthropic,openai,litellm]"
./scripts/run-unit-tests.sh

Environment variables for SDK configuration use the HA_ prefix (for example HA_SERVICE_NAME, HA_REPORTING_ENDPOINT).

Integration tests (MySQL / PostgreSQL)

cd test/externalServices && docker compose up -d --wait
cd ../..
RUN_SDK_INTEGRATION_TESTS=1 ./scripts/run-unit-tests.sh

CI

GitHub Actions workflows (public repo, ubuntu-latest):

Workflow Trigger Purpose
pr_build.yaml PR + push to main Build manylinux wheels, lint, pytest (with Docker for DB tests)
publish.yaml Tag v*.*.* Build release artifacts and publish to PyPI / TestPyPI
staticanalysis.yaml PR, main, weekly Trivy filesystem scan

Publishing to PyPI

  1. Configure trusted publishing on PyPI (and TestPyPI for RCs):
    • PyPI: environment pypi, workflow publish.yaml, job publish-pypi
    • TestPyPI: environment testpypi, workflow publish.yaml, job publish-testpypi
  2. Create and push a release tag:
    git tag v0.1.0
    git push origin v0.1.0
    
  3. Release candidates (v1.0.0-rc.1) publish to TestPyPI only; stable tags publish to PyPI.

Version is taken from the tag (v1.2.31.2.3) via scripts/build.sh, which updates src/harness_sdk/version.py and pyproject.toml.

Installing

From PyPI (stable releases)

pip install harness-sdk

Optional instrumentation extras:

pip install "harness-sdk[anthropic,openai,litellm]"

From TestPyPI (release candidates)

Tags containing -rc (for example v1.0.0-rc.1) are published only to TestPyPI. Use TestPyPI as the primary index and PyPI as a fallback so dependencies that are not on TestPyPI still resolve:

pip install \
  --index-url https://test.pypi.org/simple/ \
  --extra-index-url https://pypi.org/simple/ \
  "harness-sdk==1.0.0-rc.1"

With optional extras:

pip install \
  --index-url https://test.pypi.org/simple/ \
  --extra-index-url https://pypi.org/simple/ \
  "harness-sdk[anthropic,openai,litellm]==1.0.0-rc.1"

Pin the version to the RC you want (see published files on TestPyPI). The version string matches the tag without the leading v (tag v1.0.0-rc.11.0.0-rc.1). In a virtualenv or requirements.txt, the same flags apply:

--index-url https://test.pypi.org/simple/
--extra-index-url https://pypi.org/simple/
harness-sdk==1.0.0-rc.1

After install, import and auto-instrument work the same as a PyPI release:

from harness_sdk.agent import Agent
harness-instrument python app.py

Usage

from harness_sdk.agent import Agent

agent = Agent()
agent.instrument()

Auto-instrumentation:

export HA_CONFIG_FILE=/path/to/config.yaml
harness-instrument python app.py

Plugins

The SDK loads extensions via setuptools entry points. Each plugin has a name (the entry-point key). Names are listed in config or environment variables; only installed plugins are loaded, in the order you configure.

Type Entry-point group Config key / env
Control harness_sdk_control_plugin plugins.control / HA_CONTROL_PLUGINS
Observability harness_sdk_observability_plugin plugins.observability / HA_OBSERVABILITY_PLUGINS

Built-in observability plugins (shipped with harness-sdk):

  • builtin_pipeline — OTLP export, sampling, exclusion processors
  • builtin_span_attributes — service name and configured span attributes

Example agent-config.yaml:

service_name: my-service
reporting:
  endpoint: http://localhost:4318
plugins:
  control:
    - my_policy          # order matters: first plugin runs first
  observability:
    - builtin_pipeline
    - builtin_span_attributes
    - my_exporter        # custom plugin after builtins

Or via environment (comma-separated, same order semantics):

export HA_CONTROL_PLUGINS=my_policy
export HA_OBSERVABILITY_PLUGINS=builtin_pipeline,builtin_span_attributes,my_exporter

Create a control plugin

Control plugins evaluate HTTP/gRPC ingress and GenAI spans. They return a ControlResult (block, headers, span attributes, etc.). Plugins run in config order; the chain stops when one returns block=True.

  1. Implement the plugin in your package (see harness_sdk.plugins.control.ControlPlugin):
# my_company_policy/plugin.py
from typing import Any
from opentelemetry.trace import Span
from harness_sdk.plugins.control import ControlResult, ControlPlugin


class MyPolicyPlugin:
    name = "my_policy"
    provides_blocking = True  # set True if this plugin can block requests

    def on_init(self, config: Any) -> None:
        self._config = config

    def evaluate(
        self, span: Span, url: str, headers: dict, body, is_grpc: bool
    ) -> ControlResult:
        result = ControlResult()
        # result.block = True
        # result.response_status_code = 403
        return result

    def evaluate_agent_span(self, span: Span, body: str = "") -> ControlResult:
        return ControlResult()

    def shutdown(self) -> None:
        pass


def factory(config: Any) -> ControlPlugin:
    return MyPolicyPlugin()
  1. Register the entry point in your package pyproject.toml:
[project.entry-points.harness_sdk_control_plugin]
my_policy = "my_company_policy.plugin:factory"

(Equivalent in setup.py: entry_points={'harness_sdk_control_plugin': ['my_policy = ...']}.)

  1. Install your package in the same environment as the app (pip install my-company-policy).

  2. Enable the plugin by name in config or HA_CONTROL_PLUGINS (see above).

For tests or one-off wiring you can also call agent.register_control_plugin(plugin) after Agent() is constructed.

Reference implementation: the Traceable agent ships a control plugin as traceable (traceableai.plugins.traceable_control:factory).

Create an observability plugin

Observability plugins contribute OpenTelemetry SpanProcessor instances to the tracer provider. Processors are registered in config order (each plugin’s create_span_processors may return multiple processors).

  1. Implement the plugin (see harness_sdk.plugins.observability.ObservabilityPlugin):
# my_company_telemetry/plugin.py
from typing import Any, List
from opentelemetry.sdk.trace import SpanProcessor
from opentelemetry.sdk.trace.export import ConsoleSpanExporter, SimpleSpanProcessor


class MyExporterPlugin:
    name = "my_exporter"
    priority = 300  # informational; ordering is driven by config list

    def on_init(self, config: Any) -> None:
        self._config = config

    def create_span_processors(self, config: Any) -> List[SpanProcessor]:
        return [SimpleSpanProcessor(ConsoleSpanExporter())]

    def shutdown(self) -> None:
        pass


def factory(config: Any) -> MyExporterPlugin:
    return MyExporterPlugin()
  1. Register the entry point:
[project.entry-points.harness_sdk_observability_plugin]
my_exporter = "my_company_telemetry.plugin:factory"
  1. Install your package and enable it under plugins.observability or HA_OBSERVABILITY_PLUGINS.

If you omit observability plugins entirely, the SDK defaults to builtin_pipeline and builtin_span_attributes. Custom plugins typically keep those builtins and append your entry after them.

Reference implementations in this repo:

  • harness_sdk.plugins.builtin.pipeline
  • harness_sdk.plugins.builtin.span_attributes

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

harness_sdk-1.0.2.tar.gz (100.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

harness_sdk-1.0.2-py3-none-any.whl (135.8 kB view details)

Uploaded Python 3

File details

Details for the file harness_sdk-1.0.2.tar.gz.

File metadata

  • Download URL: harness_sdk-1.0.2.tar.gz
  • Upload date:
  • Size: 100.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for harness_sdk-1.0.2.tar.gz
Algorithm Hash digest
SHA256 d29f16c0e4a1d8b04b0119586e820b2db8c9f6adfdadeae3169bfeb8ebbb8dd9
MD5 0d0256cc08ab40adb9103efa56689c54
BLAKE2b-256 ebf8d1f044ed60001a519dbef41527359671fb94236acb9d10672b1a3fe651df

See more details on using hashes here.

File details

Details for the file harness_sdk-1.0.2-py3-none-any.whl.

File metadata

  • Download URL: harness_sdk-1.0.2-py3-none-any.whl
  • Upload date:
  • Size: 135.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for harness_sdk-1.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 657d2475c0879891d9f9ad21bbe705c35eeabd4367bdba8455e68ca4e69ce99f
MD5 8e519d53684f50b91af43ad8a7dd4626
BLAKE2b-256 7b5257f471ddd91d8dfc33a9accd82647d8587732465f78d9f24f946255389d7

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page