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
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,32 @@ All notable changes to this project are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## 0.4.0 - 2026-07-02

### Added

- `AgentKit`, a FastAPI-flavored hub over the registry: keyword-native
`await kit.run("claude", goal=..., permissions="strict", ...)` assembling
the frozen `AgentTask` internally (a prebuilt `task=` still passes through),
short aliases for the built-in kinds via the documented `KIND_ALIASES`
mapping, per-kind runtime caching closed by `aclose()`/`async with`,
`@kit.on(...)` sync/async event handlers that tee alongside a task's own
sink and can never break a run, and `@kit.runtime(...)` decorator
registration for third-party kinds.
- Typed structured output: `kit.run(..., output_type=SomeType)` derives the
wire schema from a dataclass/`TypedDict` (dependency-free, bounded subset,
fail-closed via the new `OutputTypeError`) or from a Pydantic-style model's
own `model_json_schema`, and validates `parsed_output` back into the type.
The result is `ParsedResult[T]` — a runtime-identical `AgentResult`
subclass with a typed `parsed` accessor; `AgentResult` itself stays
non-generic. Non-conforming payloads yield `finish_reason="failed"`, the
adapters' own structured-output convention.
- `PermissionProfile` accepts string literals for `mode`/`filesystem`
("strict", "read-only", ...) and coerces them to real enum members at
construction; unknown values raise `ValueError` listing the vocabulary.
Previously a bare string silently matched none of the adapters' identity
checks and ran at the default posture.

## 0.3.0 - 2026-07-02

### Added
Expand Down
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,41 @@ are added through optional extras.

## Real Providers

`AgentKit` is the keyword-native hub: it registers the built-in runtimes,
caches them per kind, and turns Python types into structured output.

```python
import asyncio
from dataclasses import dataclass

from agent_runtime_kit import AgentKit


@dataclass
class RepoSummary:
name: str
languages: list[str]


async def main() -> None:
async with AgentKit() as kit:
diagnostic = kit.availability_for("claude")
if not diagnostic.available:
raise RuntimeError(diagnostic.message)
result = await kit.run(
"claude",
goal="Summarize this repository",
permissions="strict",
output_type=RepoSummary,
)
print(result.parsed.languages if result.parsed else result.error)


asyncio.run(main())
```

Adapters also work standalone when you need vendor-specific configuration:

```python
import asyncio

Expand Down
8 changes: 8 additions & 0 deletions docs/api-stability.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ import the names from the top-level package instead.
a task field raises `UnsupportedTaskInputError`; the one exception is
vendor-option drift, which is recorded in
`AgentResult.metadata["dropped_options"]` instead.
- **`AgentKit` is sugar, not a second API.** The hub assembles the same frozen
`AgentTask` and returns the same `AgentResult` the runtimes produce
(`ParsedResult` is a runtime-identical subclass adding only the typed
`parsed` accessor). Kind aliases are limited to the documented
`KIND_ALIASES` mapping; exact kind strings always work. `output_type=`
supports a bounded, documented subset of the typing system and raises
`OutputTypeError` on anything outside it (Pydantic-style models are used
through their own `model_json_schema`/`model_validate` when present).

## Vendor SDK version policy

Expand Down
91 changes: 83 additions & 8 deletions docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,98 @@ Claude-only application from installing Codex or Antigravity packages, keep the
core usable when one vendor package is unavailable on a platform, and make
missing-provider errors actionable.

Run a task:
## Run a task

`AgentKit` is the hub: it registers the built-in runtimes, resolves them
lazily, and gives `run()` a keyword-native surface.

```python
import asyncio

from agent_runtime_kit import AgentTask
from agent_runtime_kit.adapters import ClaudeAgentRuntime
from agent_runtime_kit import AgentKit


async def main() -> None:
runtime = ClaudeAgentRuntime(default_model="claude-sonnet-4-6")
result = await runtime.run(AgentTask(goal="Summarize this repository"))
print(result.output)
async with AgentKit() as kit:
result = await kit.run(
"claude", # short alias; "claude-agent-sdk" works too
goal="Summarize this repository",
permissions="strict",
)
print(result.output)


asyncio.run(main())
```

Use `runtime.availability()` before dispatching work in applications that need
clear setup diagnostics.
## Typed structured output

Pass a type instead of hand-writing JSON schema. Dataclasses and `TypedDict`s
work out of the box (no dependency); Pydantic v2 models are used through their
own `model_json_schema`/`model_validate` when you have Pydantic installed.

```python
from dataclasses import dataclass

from agent_runtime_kit import AgentKit


@dataclass
class RepoSummary:
name: str
languages: list[str]
risky_areas: list[str]


async def main() -> None:
async with AgentKit() as kit:
result = await kit.run(
"claude",
goal="Summarize this repository",
output_type=RepoSummary,
)
if result.parsed is not None:
print(result.parsed.languages) # typed: list[str]
else:
print(result.error)
```

A payload that does not conform yields `finish_reason="failed"` with the
mismatch in `result.error` — the same convention the adapters use for
unsatisfied structured output. Unsupported annotations (sets, plain unions,
...) raise `OutputTypeError` at call time rather than sending a half-true
schema.

## Events and custom runtimes

```python
kit = AgentKit()


@kit.on("agent.tool.completed")
def log_tool(event) -> None: # sync or async; exceptions never break a run
print(event["summary"])


@kit.runtime("x-myorg-agent")
def my_runtime(**kwargs): # must stay zero-arg constructible
return MyRuntime(**kwargs)
```

## Lower-level use

The hub is sugar over the same objects you can use directly — construct an
adapter yourself when you need vendor-specific configuration, and pass either
the instance or a prebuilt `AgentTask` to `kit.run` (or call `runtime.run(task)`
without the hub at all):

```python
from agent_runtime_kit import AgentTask
from agent_runtime_kit.adapters import ClaudeAgentRuntime

runtime = ClaudeAgentRuntime(default_model="claude-sonnet-4-6", reuse_process=True)
result = await runtime.run(AgentTask(goal="Summarize this repository"))
```

Use `kit.availability()` (or `runtime.availability()`) before dispatching work
in applications that need clear setup diagnostics.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "agent-runtime-kit"
version = "0.3.0"
version = "0.4.0"
description = "One typed runtime API for Claude, Codex, and Antigravity agent SDKs."
readme = "README.md"
requires-python = ">=3.10"
Expand Down
7 changes: 7 additions & 0 deletions src/agent_runtime_kit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
from agent_runtime_kit._errors import (
AgentRuntimeError,
AgentRuntimeUnavailableError,
OutputTypeError,
RuntimeNotRegisteredError,
UnsupportedTaskInputError,
)
from agent_runtime_kit._kit import KIND_ALIASES, AgentKit
from agent_runtime_kit._runtime import FakeAgentRuntime
from agent_runtime_kit._types import (
AgentCapabilities,
Expand All @@ -19,6 +21,7 @@
FilesystemAccess,
FinishReason,
McpServerConfig,
ParsedResult,
PermissionMode,
PermissionProfile,
RuntimeAvailability,
Expand All @@ -41,6 +44,7 @@

__all__ = [
"AgentCapabilities",
"AgentKit",
"AgentResult",
"AgentRuntime",
"AgentRuntimeError",
Expand All @@ -53,7 +57,10 @@
"FakeAgentRuntime",
"FilesystemAccess",
"FinishReason",
"KIND_ALIASES",
"McpServerConfig",
"OutputTypeError",
"ParsedResult",
"PermissionMode",
"PermissionProfile",
"RuntimeAvailability",
Expand Down
12 changes: 12 additions & 0 deletions src/agent_runtime_kit/_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ def __init__(self, kind: AgentRuntimeKind | str, field: str, message: str) -> No
super().__init__(f"{runtime_kind_value(self.kind)} cannot honor {field}: {message}")


class OutputTypeError(AgentRuntimeError, TypeError):
"""A structured ``output_type`` cannot be honored.

Raised when a Python type cannot be converted to a JSON schema (unsupported
annotation — the stdlib bridge deliberately supports a bounded subset and
fails closed on everything else), or when a returned payload does not
conform to the requested type. Schema-generation failures surface at call
time; payload mismatches are converted by ``AgentKit`` into a failed
``AgentResult`` instead of raising.
"""


class RuntimeNotRegisteredError(AgentRuntimeError, LookupError):
"""No runtime factory is registered for the requested runtime kind."""

Expand Down
Loading
Loading