feat: AgentKit — FastAPI-style ergonomics layer (0.4.0)#39
Merged
Conversation
PermissionProfile(mode="strict") previously stored the bare string: it type-checks against downstream strictness only, and every adapter gates posture with identity checks (mode is PermissionMode.STRICT), so the string silently matched nothing and the run proceeded at DEFAULT posture — a quiet permission loosening. mode/filesystem now coerce to real enum members in __post_init__ (the enums are str subclasses, so lookup-by-value is native); unknown values raise ValueError listing the valid vocabulary. Enum members pass through untouched, preserving all identity comparisons.
…osed json_schema_for(tp) and parse_as(tp, value) cover a bounded subset — scalars, X | None, list[X], dict[str, X], Literal, str-valued Enums, dataclasses (additionalProperties: false, required = fields without defaults), TypedDict — and raise the new OutputTypeError on anything else rather than emitting a half-true schema. Parsing is strict (no cross-type coercion, extra keys rejected, bool never satisfies int) with $-path breadcrumbs in every error. Types shaped like Pydantic v2 models (model_json_schema + model_validate) are delegated to those methods via a structural check: users who have Pydantic get its full coverage, the core gains no dependency. OutputTypeError is exported top-level.
…utput
kit = AgentKit() wraps the registry (fake + vendor adapters registered;
adapters stay lazy so no extra is required) behind a FastAPI-flavored
surface:
- await kit.run("claude", goal=..., permissions="strict") assembles
the frozen AgentTask internally; every task field is a keyword
argument, permissions/filesystem accept string literals (coerced to
real enum members), and a prebuilt task= passes through for advanced
use. Short aliases for the built-in kinds live in one documented
KIND_ALIASES dict; exact kind strings keep working.
- output_type=SomeType derives the wire schema via the stdlib bridge
(or the type's own model_json_schema) and validates parsed_output
back into the type. Non-conforming payloads yield a
finish_reason="failed" result — the adapters' own convention for
unsatisfied structured output — never an exception. The return is
ParsedResult[T], a runtime-identical AgentResult subclass adding the
typed .parsed accessor, so AgentResult itself stays non-generic and
bare annotations survive downstream disallow_any_generics.
- Hub-resolved runtimes are constructed zero-arg, cached per kind, and
closed by aclose()/async with; instances passed directly to run()
remain caller-managed.
…ator
@kit.on("agent.tool.completed") (or bare @kit.on() for everything)
attaches sync or async handlers to every task the hub assembles,
including task= passthroughs, via a tee sink that composes with the
user's own event_sink. Failures are swallowed per the safe_emit
contract — independently per handler, so one bad handler starves
neither the others nor the downstream sink.
@kit.runtime("x-myorg-agent") registers a factory in decorator form;
factories stay zero-arg constructible so availability/capabilities
diagnostics keep working.
Quickstart and README now lead with the AgentKit surface (kwargs-native run, string-literal permissions, output_type=) while keeping the standalone-adapter path documented for vendor-specific configuration. api-stability.md pins the compatibility posture: the hub is sugar over the same task/result objects, aliases are bounded to KIND_ALIASES, and output_type supports a documented subset that fails closed. Everything in 0.4.0 is additive on the 0.3.0 surface.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
A FastAPI-flavored ergonomics layer over the existing runtime API — zero new dependencies, everything additive on the 0.3.0 surface.
Commits (each green: pytest, mypy --strict, ruff)
PermissionProfileliteral coercion —mode="strict"now coerces to the real enum member in__post_init__. Previously the bare string silently matched none of the adapters'is-identity checks and the run proceeded at DEFAULT posture; unknown values now raise with the valid vocabulary._schema.py+OutputTypeError— stdlib bridge between Python types and JSON schema: dataclasses,TypedDict,Literal, str-Enums,X | None,list[X],dict[str, X]. Fail-closed on anything else; strict parsing with$-path breadcrumbs; Pydantic v2 models used via their ownmodel_json_schema/model_validatethrough a structural check (no import, no dependency).AgentKit+ParsedResult[T]— kwargs-nativerun()assembling the frozenAgentTask(prebuilttask=passes through),KIND_ALIASESfor the built-in kinds, per-kind runtime cache closed byaclose()/async with, andoutput_type=returningParsedResult[T](a runtime-identicalAgentResultsubclass;AgentResultstays non-generic so downstreamdisallow_any_genericsusers are unaffected). Non-conforming payloads yieldfinish_reason="failed"— the adapters' own structured-output convention — never an exception.@kit.on(...)+@kit.runtime(...)— sync/async event handlers tee'd alongside the task's own sink (per-handler exception swallowing, thesafe_emitcontract), and decorator registration for third-party kinds.output_typesubset documented and fail-closed).Test delta
232 → 258 tests (
test_schema.py,test_kit.py, coercion tests intest_core.py). No existing test, adapter, or protocol changed.