Skip to content

Commit a5dab0a

Browse files
authored
Merge pull request #6 from ebarti/fix/v0.1.1-code-review-findings
fix: address v0.1.0 code-review findings, prepare v0.1.1
2 parents b15814d + 75e5e65 commit a5dab0a

21 files changed

Lines changed: 1811 additions & 252 deletions

.github/workflows/ci.yml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches: [main]
7+
8+
permissions:
9+
contents: read
10+
11+
concurrency:
12+
group: ci-${{ github.ref }}
13+
cancel-in-progress: true
14+
15+
jobs:
16+
test:
17+
name: test (py${{ matrix.python-version }}, ${{ matrix.extras }})
18+
runs-on: ubuntu-latest
19+
strategy:
20+
fail-fast: false
21+
matrix:
22+
python-version: ["3.10", "3.11", "3.12", "3.13"]
23+
extras: [core, all]
24+
25+
steps:
26+
- name: Check out repository
27+
uses: actions/checkout@v4
28+
29+
- name: Install uv
30+
uses: astral-sh/setup-uv@v5
31+
with:
32+
python-version: ${{ matrix.python-version }}
33+
34+
- name: Sync dependencies
35+
run: |
36+
if [ "${{ matrix.extras }}" = "all" ]; then
37+
uv sync --all-extras
38+
else
39+
uv sync
40+
fi
41+
42+
- name: Ruff
43+
run: uv run ruff check .
44+
45+
- name: Mypy
46+
run: uv run mypy
47+
48+
- name: Pytest
49+
run: uv run pytest -q

.github/workflows/publish-pypi.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,51 @@ concurrency:
1818
cancel-in-progress: false
1919

2020
jobs:
21+
test:
22+
name: test (py${{ matrix.python-version }}, ${{ matrix.extras }})
23+
runs-on: ubuntu-latest
24+
env:
25+
RELEASE_TAG: ${{ github.event_name == 'release' && github.event.release.tag_name || inputs.tag }}
26+
strategy:
27+
fail-fast: false
28+
matrix:
29+
include:
30+
- python-version: "3.10"
31+
extras: core
32+
- python-version: "3.13"
33+
extras: all
34+
35+
steps:
36+
- name: Check out release tag
37+
uses: actions/checkout@v4
38+
with:
39+
ref: ${{ env.RELEASE_TAG }}
40+
41+
- name: Install uv
42+
uses: astral-sh/setup-uv@v5
43+
with:
44+
python-version: ${{ matrix.python-version }}
45+
46+
- name: Sync dependencies
47+
run: |
48+
if [ "${{ matrix.extras }}" = "all" ]; then
49+
uv sync --all-extras
50+
else
51+
uv sync
52+
fi
53+
54+
- name: Ruff
55+
run: uv run ruff check .
56+
57+
- name: Mypy
58+
run: uv run mypy
59+
60+
- name: Pytest
61+
run: uv run pytest -q
62+
2163
build:
2264
name: Build distributions
65+
needs: test
2366
runs-on: ubuntu-latest
2467
env:
2568
RELEASE_TAG: ${{ github.event_name == 'release' && github.event.release.tag_name || inputs.tag }}

CHANGELOG.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Changelog
2+
3+
All notable changes to this project are documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## 0.1.1 - 2026-06-11
9+
10+
### Fixed
11+
12+
- **Codex approval-mode mapping was inverted** and is now corrected. `STRICT` and
13+
`CAUTIOUS` map to `deny_all` (never escalate beyond the sandbox); `DEFAULT` and
14+
`PERMISSIVE` map to `auto_review` (escalations are auto-adjudicated). Previously a
15+
`STRICT` caller could have escalations auto-approved and a `PERMISSIVE` caller had
16+
them hard-denied — exactly backwards.
17+
- Antigravity MCP stdio server configuration no longer crashes: the required `name`
18+
field is now passed to the SDK model.
19+
- The `mypy` strict gate now passes identically in core-only and all-extras
20+
environments (per-module import overrides replace inline ignores).
21+
- Claude reports `finish_reason="max_turns"` when a turn is truncated by the
22+
max-turns limit instead of a generic failure.
23+
- Codex honors `TurnResult.status`: a `failed` or `interrupted` turn maps to the
24+
matching `finish_reason` and surfaces the structured error message instead of being
25+
reported as success.
26+
27+
### Changed
28+
29+
- **Antigravity DEFAULT permission posture is now safer.** With no `allowed_tools`,
30+
`DEFAULT` (and `CAUTIOUS`) now use the nondestructive toolset (no `run_command`)
31+
instead of granting all tools plus an allow-all policy. `STRICT` (or any read-only
32+
filesystem) uses the read-only toolset with no `allow_all` policy; `PERMISSIVE`
33+
keeps all tools.
34+
- **Unsupported task fields now raise `UnsupportedTaskInputError` instead of being
35+
silently ignored.** Codex rejects `allowed_tools`, `disallowed_tools`, `budget_usd`,
36+
and `permissions.network` (MCP was already rejected); Antigravity rejects
37+
`budget_usd` and `permissions.network`, and rejects combining an allow-list with a
38+
deny-list (the SDK requires them to be mutually exclusive); Claude rejects
39+
`permissions.network`. Only Claude maps `budget_usd`.
40+
- Missing-SDK and missing-credential paths now raise the typed
41+
`AgentRuntimeUnavailableError` instead of a bare `RuntimeError`.
42+
- Antigravity session and app-data storage moved from a world-shared
43+
`/tmp/agent-runtime-kit` path to `$XDG_CACHE_HOME/agent-runtime-kit` (default
44+
`~/.cache/agent-runtime-kit`, created `0o700`), overridable via
45+
`AntigravityAgentRuntime(data_dir=...)`. Sessions now survive reboots and are not
46+
exposed to other users on multi-user machines.
47+
- Antigravity maps `disallowed_tools` to `CapabilitiesConfig.disabled_tools` and
48+
validates tool names against the `BuiltinTools` enum, raising a typed error for an
49+
unknown name instead of leaking a raw validation error.
50+
- Build now requires `hatchling>=1.26` (for the PEP 639 `license = "MIT"` string),
51+
and the deprecated `License :: OSI Approved :: MIT License` classifier was removed.
52+
53+
### Added
54+
55+
- Claude streaming is now real: incremental output deltas and
56+
`tool_requested`/`tool_completed` events are emitted while the SDK runs.
57+
- Codex tool audits are now produced, parsed from `TurnResult.items` (command
58+
executions, MCP tool calls, dynamic tool calls, and web searches).
59+
- Claude records vendor-option kwargs it had to drop due to SDK drift in
60+
`AgentResult.metadata["dropped_options"]`, keeping silent omissions observable.
61+
- A continuous-integration workflow runs `ruff`, `mypy`, and `pytest` across
62+
Python 3.10–3.13 in both the core-only and all-extras dependency lanes, and the
63+
PyPI publish workflow now gates on a test job before building.
64+
- `tests/test_sdk_contract.py` introspects the real vendor SDK surfaces and
65+
auto-skips when the SDKs are not installed.
66+
- An sdist allowlist ensures only `src`, `tests`, `docs`, `examples`, `README.md`,
67+
`LICENSE`, and `pyproject.toml` are packaged for PyPI (internal planning
68+
artifacts, `uv.lock`, and caches are no longer shipped).
69+
70+
## 0.1.0
71+
72+
- Initial release: one typed async runtime API for the Claude, Codex, and
73+
Antigravity agent SDKs, with capability diagnostics, event sinks, and adapters.

README.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,16 +70,18 @@ asyncio.run(main())
7070

7171
`AgentTask` supports goal, system prompt, working directory, permission profile,
7272
MCP stdio servers, session/resume handles, output schema, budget, metadata, and
73-
an async event sink.
73+
an async event sink. Where a runtime cannot honor a field (for example only
74+
Claude maps `budget_usd`; Codex and Antigravity reject it with a typed
75+
`UnsupportedTaskInputError`) the adapter raises rather than silently dropping it.
7476

7577
`AgentResult` returns output, finish reason, parsed structured output, usage,
7678
cost, session id, artifacts, tool-call audits, and provider metadata.
7779

7880
## Docs
7981

80-
- [Quickstart](docs/quickstart.md)
81-
- [Provider diagnostics](docs/providers.md)
82-
- [Capability matrix](docs/capability-matrix.md)
83-
- [Live smoke tests](docs/live-smoke.md)
84-
- [Mestre migration notes](docs/mestre-migration.md)
85-
- [Publish checklist](docs/publish-checklist.md)
82+
- [Quickstart](https://github.com/ebarti/agent-runtime-kit/blob/main/docs/quickstart.md)
83+
- [Provider diagnostics](https://github.com/ebarti/agent-runtime-kit/blob/main/docs/providers.md)
84+
- [Capability matrix](https://github.com/ebarti/agent-runtime-kit/blob/main/docs/capability-matrix.md)
85+
- [Live smoke tests](https://github.com/ebarti/agent-runtime-kit/blob/main/docs/live-smoke.md)
86+
- [Mestre migration notes](https://github.com/ebarti/agent-runtime-kit/blob/main/docs/mestre-migration.md)
87+
- [Publish checklist](https://github.com/ebarti/agent-runtime-kit/blob/main/docs/publish-checklist.md)

docs/capability-matrix.md

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,64 @@
99
| Structured output | Native `output_format` when available | Native output schema / JSON parse fallback | Native response schema / JSON parse fallback |
1010
| MCP stdio servers | Yes | No per-task MCP config | Yes, without per-server env |
1111
| Permission mapping | `permission_mode` | approval mode + sandbox | capabilities + policies |
12-
| Streaming output events | From streamed SDK messages | Not enabled in v1 adapter | From response chunks |
13-
| Tool audit events | Best effort from message content | Best effort from result surface | From tool chunks |
14-
| Missing package diagnostics | Yes | Yes | Yes |
12+
| Streaming output events | Yes — incremental output/tool events while the SDK runs | Not enabled in v1 adapter | Yes — from response chunks |
13+
| Tool audit events | Yes — from streamed message blocks | Yes — parsed from `TurnResult` items | Yes — from tool chunks |
14+
| Missing package diagnostics | Yes (`AgentRuntimeUnavailableError`) | Yes (`AgentRuntimeUnavailableError`) | Yes (`AgentRuntimeUnavailableError`) |
1515
| Missing credential diagnostics | Provider-owned/local auth | Provider-owned/local auth | `GEMINI_API_KEY` or `GOOGLE_API_KEY` |
1616
| Live smoke test | Opt-in | Opt-in | Opt-in |
1717

1818
The matrix is intentionally not a lowest-common-denominator contract. Adapters
19-
reject unsupported inputs when silently dropping them would be misleading.
19+
reject unsupported inputs (see below) when silently dropping them would be
20+
misleading.
21+
22+
## Permission mapping
23+
24+
A single portable `PermissionMode` maps to each vendor's native controls. The
25+
same profile yields different but intentionally aligned postures per runtime;
26+
review this table before assuming a mode is equivalent everywhere.
27+
28+
| `PermissionMode` | Claude `permission_mode` | Codex `approval_mode` + sandbox | Antigravity toolset + policy |
29+
|------------------|--------------------------|---------------------------------|------------------------------|
30+
| `STRICT` | `plan` | `deny_all` (never escalate) | read-only toolset, no `allow_all` policy |
31+
| `CAUTIOUS` | `acceptEdits` | `deny_all` (never escalate) | nondestructive toolset (no `run_command`), `allow_all` policy |
32+
| `DEFAULT` | `default` | `auto_review` (escalations auto-adjudicated) | nondestructive toolset (no `run_command`), `allow_all` policy |
33+
| `PERMISSIVE` | `bypassPermissions` | `auto_review` (escalations auto-adjudicated) | all tools, `allow_all` policy |
34+
35+
Codex sandbox is derived from `FilesystemAccess`, independent of the approval
36+
mode: `READ_ONLY``read_only`, `WORKSPACE_WRITE``workspace_write`,
37+
`FULL_ACCESS``full_access`.
38+
39+
Antigravity toolset notes: the table above describes the default posture when
40+
`allowed_tools` is empty. A `READ_ONLY` filesystem forces the read-only toolset
41+
regardless of mode. User-supplied `allowed_tools`/`disallowed_tools` override the
42+
defaults and are validated against the `BuiltinTools` enum (for example
43+
`"view_file"`, not `"Read"`); an unknown name raises `UnsupportedTaskInputError`.
44+
45+
## Rejected inputs
46+
47+
Each adapter raises `UnsupportedTaskInputError` for task fields it has no SDK
48+
surface to honor, rather than dropping them silently.
49+
50+
| Field | Claude | Codex | Antigravity |
51+
|-------|--------|-------|-------------|
52+
| `budget_usd` | Mapped (`max_budget_usd`) | Rejected | Rejected |
53+
| `permissions.network` | Rejected | Rejected | Rejected |
54+
| `allowed_tools` / `disallowed_tools` | Mapped | Rejected | Mapped (`disallowed_tools``disabled_tools`); allow-list and deny-list are mutually exclusive and rejected if combined |
55+
| `mcp_servers` | Mapped | Rejected (no per-task MCP) | Mapped, without per-server `env` |
56+
57+
Two task fields are informational only and not enforced by any built-in adapter:
58+
`AgentTask.sdk_executions` (carried into events as a hint) and
59+
`SessionResumeState.transcript` (adapters resume by `session_id`).
60+
61+
Claude additionally records any vendor-option kwargs it had to drop due to SDK
62+
drift in `AgentResult.metadata["dropped_options"]`, so silent omission stays
63+
observable.
64+
65+
## Session storage (Antigravity)
66+
67+
Antigravity session and app-data directories are written under
68+
`$XDG_CACHE_HOME/agent-runtime-kit` (default `~/.cache/agent-runtime-kit`),
69+
created with `0o700` permissions. This replaces the previous world-shared
70+
`/tmp` location so transcripts survive reboots and are not exposed to other
71+
users. Override the base directory with
72+
`AntigravityAgentRuntime(data_dir=...)`.

docs/providers.md

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,44 @@
1212

1313
Claude uses the `claude-agent-sdk` package and maps working directory,
1414
permissions, MCP servers, sessions, structured output, tool allow/deny lists,
15-
and budget where supported by the installed SDK.
15+
and budget where supported by the installed SDK. It streams incremental output
16+
and tool events while the SDK runs, and sets `finish_reason="max_turns"` when a
17+
turn is truncated by the max-turns limit. `permissions.network` has no SDK
18+
surface and is rejected with a typed error. Any option kwargs dropped because a
19+
future SDK renamed or removed them are recorded in
20+
`AgentResult.metadata["dropped_options"]` rather than discarded silently.
1621

1722
Codex uses the `openai-codex` package and maps working directory, session
1823
resume, approval mode, sandbox, structured output, model, and reasoning effort.
19-
Codex does not currently support per-task MCP server configuration through this
20-
adapter, so `mcp_servers` is rejected with a typed error.
24+
Approval mode follows `PermissionMode`: `STRICT`/`CAUTIOUS``deny_all` (never
25+
escalate beyond the sandbox), `DEFAULT`/`PERMISSIVE``auto_review`
26+
(escalations auto-adjudicated). Tool audits are parsed from `TurnResult.items`
27+
(command executions, MCP tool calls, dynamic tool calls, web searches), and a
28+
`TurnResult.status` of `failed`/`interrupted` maps to the matching
29+
`finish_reason`. `budget_usd`, `permissions.network`, `allowed_tools`,
30+
`disallowed_tools`, and `mcp_servers` have no per-task SDK surface and are
31+
rejected with typed errors. The constructor defaults to
32+
`config_overrides=("features.plugins=false",)` so headless runs are
33+
deterministic and do not pick up host-local Codex plugin configuration; pass a
34+
different tuple to opt in.
2135

2236
Antigravity uses the `google-antigravity` package and maps API key,
2337
workspace, permission-derived capabilities/policies, MCP stdio servers,
2438
conversation id, structured output, session directories, model, and tool
25-
events. Antigravity MCP server configs do not currently accept per-server env
26-
values through this adapter.
39+
events. `disallowed_tools` maps to `CapabilitiesConfig.disabled_tools`, and an
40+
allow-list and a deny-list are mutually exclusive (the SDK forbids combining
41+
enabled and disabled tool lists), so supplying both is rejected. Tool names are
42+
validated against the `BuiltinTools` enum (`"view_file"`, not `"Read"`).
43+
`budget_usd` and `permissions.network` are rejected with typed errors, and
44+
MCP server configs do not accept per-server env values. The default tool posture
45+
with no `allowed_tools` is:
46+
47+
| `PermissionMode` (or `READ_ONLY` filesystem) | Toolset | Policy |
48+
|----------------------------------------------|---------|--------|
49+
| `STRICT`, or any `READ_ONLY` filesystem | read-only | none (no `allow_all`) |
50+
| `CAUTIOUS`, `DEFAULT` | nondestructive (no `run_command`) | `allow_all` |
51+
| `PERMISSIVE` | all tools | `allow_all` |
52+
53+
Session and app-data directories are written under
54+
`$XDG_CACHE_HOME/agent-runtime-kit` (default `~/.cache/agent-runtime-kit`,
55+
`0o700`), overridable via `AntigravityAgentRuntime(data_dir=...)`.

docs/publish-checklist.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,19 @@ available; any 200 response means it has been claimed.
2424

2525
## Release Gate
2626

27-
- `uv run pytest`
27+
Continuous integration (`.github/workflows/ci.yml`) runs `ruff`, `mypy`, and
28+
`pytest` on every pull request and push to `main` across Python 3.10–3.13 in
29+
both the core-only and all-extras dependency lanes, so gate results no longer
30+
depend on which extras happen to be installed locally. The publish workflow
31+
itself also gates on tests: its `test` job (core on 3.10, all extras on 3.13)
32+
must pass before the `build` job runs, so a release cannot be built or published
33+
unless `ruff`/`mypy`/`pytest` are green against the release tag.
34+
35+
To reproduce the gate locally before tagging:
36+
2837
- `uv run ruff check .`
2938
- `uv run mypy`
39+
- `uv run pytest`
3040
- `uv run python -m build`
3141
- Optional: provider-specific live smoke tests from `docs/live-smoke.md`
3242

0 commit comments

Comments
 (0)