Metadata-Version: 2.5
Name: harness-sdk-python
Version: 0.40.3
Summary: RunManager: the harness-sdk runs subsystem for Python Statewire hosts
Project-URL: Repository, https://github.com/assistant-ui/harness-sdk
License-Expression: MIT
Requires-Python: <4.0,>=3.12
Requires-Dist: pinned>=0.17.0
Requires-Dist: statewire<0.24,>=0.23.5
Provides-Extra: deepagents
Requires-Dist: deepagents>=0.6.12; extra == 'deepagents'
Requires-Dist: langchain-core>=0.3; extra == 'deepagents'
Requires-Dist: langchain-openai>=0.3; extra == 'deepagents'
Requires-Dist: langgraph>=1.0.0; extra == 'deepagents'
Requires-Dist: uvicorn[standard]>=0.30.0; extra == 'deepagents'
Provides-Extra: llm
Requires-Dist: langchain-core>=0.3; extra == 'llm'
Provides-Extra: postgres
Requires-Dist: langgraph-checkpoint-postgres>=2.0.0; extra == 'postgres'
Provides-Extra: typesafe
Requires-Dist: typesafe-sdk<0.8,>=0.7.5; extra == 'typesafe'
Provides-Extra: voice
Requires-Dist: httpx>=0.27; extra == 'voice'
Requires-Dist: websockets>=13; extra == 'voice'
Description-Content-Type: text/markdown

# harness-sdk-python

The runs subsystem for Python Statewire hosts: `@run_manager` turns one async method into the run executor and mounts the `run/*` command surface (send, dequeue, edit, reload, stop, continue, input), each run in its own tray. Part of [harness-sdk](https://github.com/assistant-ui/harness-sdk), with `statewire` and `pinned`.

## Install

```sh
uv add harness-sdk-python
```

Extras: `uv add "harness-sdk-python[postgres]"`, `uv add "harness-sdk-python[deepagents]"`, `uv add "harness-sdk-python[llm]"`, `uv add "harness-sdk-python[typesafe]"`.

## Use

```python
from statewire import Statewire, command

from harness_sdk import HARNESS_PROTOCOL, RunManager, run_manager


class RunsHost(Statewire):
    protocols = (HARNESS_PROTOCOL,)

    async def lifespan(self):
        self.state = {"messages": []}
        yield

    @run_manager()
    async def run(self, ctx: RunManager.RunContext):
        for message in ctx.messages:
            self.state["messages"].append({"id": message["id"]})
        ctx.applied()
        return RunManager.Complete()

    @command("run/send")
    async def run_send(self, params, *, ctx, auth_result):
        return await self.run.send(
            params,
            applied=ctx.applied,
            auth_result=auth_result,
            version=ctx.version,
        )
```

## Prioritize

`run/send` takes `behavior`: `"steer"` folds the message into the live run, `"preempt"` stops the live run and runs the message next, `"queue"` runs it after the live run. The default `"auto"` asks `prioritize` while a run is live (no prioritizer, a failure, or an invalid answer steers) and queues otherwise.

```python
from harness_sdk.prioritize import jev_prioritizer, llm_prioritizer

@run_manager(prioritize=jev_prioritizer(min_confidence=0.6))
async def run(self, ctx: RunManager.RunContext): ...

@run_manager(prioritize=llm_prioritizer(ChatOpenAI(model="gpt-5-mini"), queue=True))
async def run(self, ctx: RunManager.RunContext): ...
```

- `prioritize(req: RunManager.PrioritizeRequest) -> "steer" | "preempt" | "queue"`, sync or async; `req` carries `message`, `run` (`run_id`, `status`, `messages`, `steer_queue`), and `queue`.
- Built-ins choose between steer and preempt; `queue=True` adds queue. A timeout steers; `jev_prioritizer` also steers below `min_confidence`.
- Clients on harness-sdk protocols before 2026-11-04 keep `run/enqueue` (behavior `"queue"`) and `run/steer` (behavior `"steer"`).

## Docs

- Get started: https://github.com/assistant-ui/harness-sdk/blob/main/apps/docs/app/docs/get-started/chat-app/page.mdx
- Guide: https://github.com/assistant-ui/harness-sdk/blob/main/apps/docs/app/docs/guides/harness/host/page.mdx
- Reference: https://github.com/assistant-ui/harness-sdk/blob/main/apps/docs/app/docs/reference/harness-sdk-python/page.mdx
- Spec: https://github.com/assistant-ui/harness-sdk/blob/main/apps/docs/app/docs/spec/runs-protocol/page.mdx
