Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 27 additions & 14 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,37 @@ jobs:
with:
fetch-depth: 0

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
- name: Install uv
uses: astral-sh/setup-uv@v8.3.2
with:
python-version: ${{ matrix.python-version }}
enable-cache: true

- name: Install dependencies
run: uv sync --frozen

- name: OpenAPI schema parity (hand-written models vs vendored schemas)
run: uv run python scripts/check_schema_parity.py

- name: Regenerate OpenAPI models (freshness gate)
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
uv run python scripts/generate_models.py
git diff --exit-code -- src/orloj_sdk/models/_generated || {
echo "::error::Generated models are stale. Run 'uv run python scripts/generate_models.py' and commit."
exit 1
}

- name: Ruff check
run: ruff check src/orloj_sdk tests examples
run: uv run ruff check src/orloj_sdk tests examples

- name: Ruff format
run: ruff format --check src/orloj_sdk tests examples
run: uv run ruff format --check src/orloj_sdk tests examples

- name: Mypy
run: mypy src/orloj_sdk
run: uv run mypy src/orloj_sdk

- name: Pytest
run: pytest --cov=orloj_sdk --cov-report=xml --cov-report=term-missing -q
run: uv run pytest --cov=orloj_sdk --cov-report=xml --cov-report=term-missing -q

publish:
needs: test
Expand All @@ -51,15 +61,18 @@ jobs:
with:
fetch-depth: 0

- uses: actions/setup-python@v5
- name: Install uv
uses: astral-sh/setup-uv@v8.3.2
with:
python-version: "3.12"
- run: pip install build twine
- run: python -m build
- run: twine upload dist/*

- name: Build
run: uv build

- name: Publish to PyPI
run: uv publish
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }}

github-release:
needs: publish
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ ENV/
env/
.env/
.virtualenv/
.uv-tools/

# PyInstaller
*.manifest
Expand Down Expand Up @@ -92,3 +93,6 @@ plan.md

# Profiling
.prof

# OpenAPI bundle is regenerated by scripts/generate_models.py
openapi/openapi.bundle.yaml
56 changes: 46 additions & 10 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,60 @@ Thanks for helping improve the Orloj Python SDK. This document describes how to

## Prerequisites

- **Python 3.10+** (CI runs 3.10 through 3.13)
- A virtual environment is recommended
- **[uv](https://docs.astral.sh/uv/)** (install: `curl -LsSf https://astral.sh/uv/install.sh | sh`)
- **Python 3.10+** (CI runs 3.10 through 3.13; `uv` can install a matching interpreter)

## Local setup

```bash
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e ".[dev]"
uv sync
```

This creates `.venv`, installs the package in editable mode, and installs the `dev` dependency group from `pyproject.toml` (locked in `uv.lock`).

Use `uv run …` for tools (or `source .venv/bin/activate` if you prefer an activated shell).

## Keeping OpenAPI / models in sync

The wire contract is vendored under [`openapi/`](openapi/) and pinned in [`openapi/PIN.json`](openapi/PIN.json)
(commit/tag of [OrlojHQ/orloj](https://github.com/OrlojHQ/orloj)).

1. **Sync** the OpenAPI tree from a local orloj checkout:

```bash
uv run python scripts/sync_openapi.py --orloj-root /path/to/orloj
```

2. **Regenerate** reference models (CI freshness gate):

```bash
uv run python scripts/generate_models.py
```

3. **Check** hand-written `orloj_sdk.models` still cover every OpenAPI schema property:

```bash
uv run python scripts/check_schema_parity.py
```

4. If parity fails, update the corresponding file under `src/orloj_sdk/models/` (and resources if new routes appeared), then re-run the checks.

Public imports stay on the hand-written models (`OrlojModel`, aliases, DX).
`src/orloj_sdk/models/_generated/` is machine-owned — do not edit it.

API-visible changes in orloj must update OpenAPI there first; then sync/regenerate/parity here in the same SDK PR when possible.

## Checks (run before opening a PR)

These mirror [`.github/workflows/ci.yml`](.github/workflows/ci.yml):

```bash
ruff check src/orloj_sdk tests examples
ruff format src/orloj_sdk tests examples # or: ruff format --check ... to verify only
mypy src/orloj_sdk
pytest --cov=orloj_sdk --cov-report=term-missing
uv run python scripts/check_schema_parity.py
uv run python scripts/generate_models.py && git diff --exit-code -- src/orloj_sdk/models/_generated
uv run ruff check src/orloj_sdk tests examples
uv run ruff format src/orloj_sdk tests examples # or: uv run ruff format --check ... to verify only
uv run mypy src/orloj_sdk
uv run pytest --cov=orloj_sdk --cov-report=term-missing
```

Fix any failures locally; CI must pass on the matrix of Python versions.
Expand All @@ -35,13 +69,15 @@ Fix any failures locally; CI must pass on the matrix of Python versions.

## Release version

The published package version is taken from **Git tags** (`hatch-vcs` / setuptools-scm), not from editing `src/orloj_sdk/_version.py`. At release time, create an annotated tag whose name matches the version (for example `v0.2.0` for `0.2.0`) and push it; CI builds and uploads that version to PyPI, then creates a **GitHub Release** for that tag (with auto-generated notes) so it appears under the repo’s Releases page. Runtime `orloj_sdk.__version__` comes from the installed package metadata (after `pip install -e .` or installing from PyPI).
The published package version is taken from **Git tags** (`hatch-vcs` / setuptools-scm), not from editing `src/orloj_sdk/_version.py`. At release time, create an annotated tag whose name matches the version (for example `v0.2.0` for `0.2.0`) and push it; CI builds and uploads that version to PyPI, then creates a **GitHub Release** for that tag (with auto-generated notes) so it appears under the repo’s Releases page. Runtime `orloj_sdk.__version__` comes from the installed package metadata (after `uv sync` or installing from PyPI).

## Pull requests

- Keep changes focused on one concern when possible.
- Update or add tests when behavior changes.
- Match existing style: typing, Pydantic usage, and public API patterns in `src/orloj_sdk`.
- If the change depends on a newer Orloj API, sync `openapi/` and regenerate `_generated` in the same PR.
- If you change dependencies in `pyproject.toml`, run `uv lock` and commit the updated `uv.lock`.

## License

Expand Down
109 changes: 95 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
# orloj — Official Python SDK

Typed Python client for the [Orloj](https://orloj.dev) v1 REST API: synchronous and asynchronous usage (`httpx`), Pydantic v2 models, SSE watch streams, and cursor-based pagination.
Typed Python client for the [Orloj](https://orloj.dev) v1 REST API: synchronous and asynchronous usage (`httpx`), Pydantic v2 models, SSE watch streams, cursor-based pagination, `tasks.run(wait=True)` with typed output, and a small graph-as-code authoring layer.

## Requirements

- Python 3.10+

## Install

From [PyPI](https://pypi.org/project/orloj-sdk/):
From [PyPI](https://pypi.org/project/orloj-sdk/) (with [uv](https://docs.astral.sh/uv/)):

```bash
pip install orloj-sdk
uv add orloj-sdk
```

Install with `pip install orloj-sdk`, then import the `orloj_sdk` package (`from orloj_sdk import OrlojClient`).
Then import the `orloj_sdk` package (`from orloj_sdk import OrlojClient`). `pip install orloj-sdk` works too.

From a git checkout:

```bash
pip install -e ".[dev]"
uv sync
```

## Configuration
Expand All @@ -31,7 +31,7 @@ The client reads defaults from the environment (same order as `orlojctl`):
| API token | `api_token=` | `ORLOJCTL_API_TOKEN`, `ORLOJ_API_TOKEN` |
| Base URL | `base_url=` | `ORLOJCTL_SERVER`, `ORLOJ_SERVER` |

If unset, the base URL defaults to `http://127.0.0.1:8080` and the namespace defaults to `default`.
If unset, the base URL defaults to `http://127.0.0.1:8080` and the namespace defaults to `default`. HTTP `max_retries` defaults to `3` for production-friendly transient failure handling.

## Quickstart

Expand All @@ -46,6 +46,45 @@ for agent in client.agents.list_all():
print(agent.metadata.name)
```

### Run a task and wait

```python
from pydantic import BaseModel
from orloj_sdk import OrlojClient

class Answer(BaseModel):
summary: str

client = OrlojClient(api_token="your-token")
answer = client.tasks.run(
name="job-1",
system="demo-pipeline",
input={"query": "hello"},
wait=True,
output_type=Answer,
output_key="result",
)
print(answer.summary)
```

### Author a system in Python

```python
from orloj_sdk import OrlojClient
from orloj_sdk.authoring import agent, edge, node, system

@agent("researcher", model_ref="default-model")
def researcher() -> None:
"""Research the query and list key facts."""

pipeline = (
system("demo-pipeline")
.add_agent(researcher)
.set_graph({"researcher": node()})
)
pipeline.apply(OrlojClient(api_token="your-token"))
```

### Async

```python
Expand All @@ -55,6 +94,49 @@ async with AsyncOrlojClient(api_token="your-token") as client:
page = await client.agents.list(limit=20)
```

## Durability

Orloj durability (leases, retries, idempotency, dead-letter) lives in the **server**. See [docs/durability.md](docs/durability.md) for guarantees, terminal phases, production server baseline, and SDK wait defaults. Temporal interop is intentionally deferred — Orloj remains the control-plane identity.

## OpenAPI sync

The SDK vendors Orloj’s OpenAPI under [`openapi/`](openapi/) (see [`openapi/PIN.json`](openapi/PIN.json)). CI fails if:

- hand-written models lag schema properties (`scripts/check_schema_parity.py`), or
- generated models under `src/orloj_sdk/models/_generated/` are stale (`scripts/generate_models.py`).

Workflow: sync from orloj → regenerate → fix parity → commit. Details in [CONTRIBUTING.md](CONTRIBUTING.md#keeping-openapi--models-in-sync).

## API surface

Resource accessors on `OrlojClient` / `AsyncOrlojClient`:

| Attribute | Path prefix |
| --------- | ----------- |
| `agents` | `/v1/agents` |
| `agent_systems` | `/v1/agent-systems` |
| `model_endpoints` | `/v1/model-endpoints` |
| `tools` | `/v1/tools` |
| `secrets` | `/v1/secrets` |
| `sealed_secrets` | `/v1/sealed-secrets` (+ `get_public_key`) |
| `memories` | `/v1/memories` |
| `context_adapters` | `/v1/context-adapters` |
| `eval_datasets` | `/v1/eval-datasets` |
| `eval_runs` | `/v1/eval-runs` (start/cancel/export/finalize/annotate/compare) |
| `agent_policies` | `/v1/agent-policies` |
| `agent_roles` | `/v1/agent-roles` |
| `tool_permissions` | `/v1/tool-permissions` |
| `tool_approvals` | `/v1/tool-approvals` |
| `task_approvals` | `/v1/task-approvals` |
| `tasks` | `/v1/tasks` (`run`/`wait`/`output`, logs/messages/metrics/cancel/retry/watch) |
| `task_schedules` | `/v1/task-schedules` |
| `task_webhooks` | `/v1/task-webhooks` |
| `workers` | `/v1/workers` |
| `mcp_servers` | `/v1/mcp-servers` |
| `auth` | auth/tokens/health/capabilities (`cli_token`, …) |
| `events` | `/v1/events/watch` |
| `a2a` | A2A registry, agent cards, JSON-RPC invoke |

## Examples

Runnable scripts (require a running Orloj API):
Expand All @@ -64,25 +146,24 @@ Runnable scripts (require a running Orloj API):
| `examples/quickstart.py` | `whoami` and list agents |
| `examples/watch_tasks.py` | SSE watch on a task until completion |
| `examples/multi_agent_system.py` | Create an `AgentSystem` with a graph |
| `examples/authoring_system.py` | Graph-as-code authoring + `apply` |
| `examples/async_example.py` | `AsyncOrlojClient` with `asyncio.gather` |

```bash
export ORLOJ_API_TOKEN=...
python examples/quickstart.py
uv run python examples/quickstart.py
```

## Development

See [CONTRIBUTING.md](CONTRIBUTING.md) for setup, checks, and pull request expectations.

```bash
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e ".[dev]"
ruff check src/orloj_sdk tests examples
ruff format src/orloj_sdk tests examples
mypy src/orloj_sdk
pytest --cov=orloj_sdk
uv sync
uv run ruff check src/orloj_sdk tests examples
uv run ruff format src/orloj_sdk tests examples
uv run mypy src/orloj_sdk
uv run pytest --cov=orloj_sdk
```

## License
Expand Down
Loading
Loading