A provider-neutral harness SDK for agent systems on Claude and Codex.
Project description
Yoke
A Python SDK for building agents on Claude Code and Codex.
Claude Code and Codex have become general-purpose agents: give them
instructions, skills, and subagents, and they can be shaped to any task. Yoke
lets you reuse them from code — one Harness that drives both.
Quickstart · How it compares · Sessions · Skills · Subagents · Workflows · Goals · Surfaces · CLI
Quickstart
pip install almanac-yoke
Install a provider extra when you want Yoke to manage that SDK directly:
pip install 'almanac-yoke[claude]' # or [codex], or [all]
Define an agent, pick a harness, run:
from pathlib import Path
from yoke import Agent, Goal, Harness
agent = Agent(
instructions="You are a careful maintainer. Make small, safe changes.",
goal=Goal("Finish the requested implementation safely."),
)
harness = Harness("codex", agent=agent, cwd=Path.cwd())
result = await harness.run("Implement the bundle loader.")
print(result.output)
Swap "codex" for "claude" and the same agent runs there. Your existing
Claude Code or ChatGPT login is all it needs — no API keys.
Embedding applications can observe a one-shot run while it is happening:
from yoke import RunOptions
seen = []
result = await harness.run(
"Implement the bundle loader.",
RunOptions(on_event=seen.append),
)
assert tuple(seen) == result.events
on_event is a synchronous callback receiving each normalized Event once.
It is runtime-only—it is excluded from serialized options and agent folders.
Live callback delivery is supported by the Claude Python SDK and Codex
app-server surfaces. Passing on_event selects one of those surfaces when the
surface is automatic, and raises UnsupportedFeature when an explicitly
selected surface cannot deliver callbacks. Use harness.stream(...) for a
portable event iterator.
How it compares
The question that places Yoke: who runs the agent?
| The agent runs in | Yoke | |
|---|---|---|
| Claude Agent SDK · Codex SDK | the lab's harness — one provider each | builds on them. They are the surfaces Yoke drives; one definition runs on both. |
| Pydantic AI · LangChain | your process — a loop you assemble over model APIs | starts from a different premise: the harness already is the agent, so there is no loop to assemble. |
| Eve | Eve's own durable runtime, deployed on Vercel | is closest in spirit — an agent is a directory — but compiles that directory onto Claude and Codex instead of shipping a runtime. |
Sessions
session = await harness.start()
plan = await session.run("Draft the migration plan.")
step = await session.run("Apply step one.")
await session.close()
State lives with the provider; Yoke holds the handle. Sessions are native on every surface except the Codex CLI, where they work by resuming threads.
Skills
from yoke import Skill
agent = Agent(
instructions="You are a careful maintainer.",
skills=(Skill(path=Path("skills/source-grounding")),),
)
A skill is a folder with a SKILL.md. Claude loads skills natively, the Codex
app-server mounts them as native skill roots, and other surfaces get them
compiled to files.
Subagents
agent = Agent(
instructions="You are a careful maintainer.",
subagents={
"reviewer": Agent(
description="Find correctness and architecture risks.",
instructions="Review concretely. Prefer file and line evidence.",
),
},
)
A subagent is just another Agent. Claude runs declared subagents through its
native Agent tool. Codex app-server compiles the declaration into explicit
guidance to use native spawn_agent, including requested model overrides, and
normalizes collaboration events when the parent follows that guidance. This is
model-driven orchestration, not deterministic tool enforcement. Codex SDK/CLI
surfaces use clearly labeled compiled instructions or provider files.
Workflows
from yoke import Step, Workflow, WorkflowOptions
workflow = Workflow(
name="review",
steps=(
Step(name="draft", agent="main", prompt="Draft release notes."),
Step(
name="review",
agent="reviewer",
depends_on=("draft",),
prompt="Review this draft: {draft}",
),
),
)
result = await harness.workflow(
workflow,
options=WorkflowOptions(timeout_seconds=120, step_timeout_seconds=60),
)
A workflow is a small dependency graph over the agent and its subagents;
main is the reserved name for the root agent, and WorkflowOptions bounds
the run. Neither provider has a native equivalent yet, so workflows are
portable Yoke constructs — they run the same on every surface.
Goals
from yoke import Goal
session = await session.set_goal(Goal("Land the bundle loader.", token_budget=200_000))
print(await session.get_goal())
A goal is intent that outlives a single prompt. On the Codex app-server it is
real thread state — readable, replaceable, clearable. Everywhere else it
compiles into the provider loop, and explain() tells you which you got.
No pretending
Claude and Codex expose different primitives, and Yoke does not flatten them into a weak common denominator. The capability map is part of the API:
for row in harness.explain().reports:
print(row.feature, row.support, row.lowering) # native, compiled, emulated, unsupported
| Feature | Claude SDK | Codex app-server | Codex SDK | Codex CLI |
|---|---|---|---|---|
| Sessions | native | native | native | resume-based |
| Streaming | native | native | native transport | JSONL/process |
| Skills | native | native skill roots | compiled | files/compiled |
| Subagents | native | compiled → native tool | compiled | files/compiled |
| Goals | provider loop | native state | compiled context | provider loop |
| Workflows | portable Yoke | portable Yoke | portable Yoke | portable/limited |
| Permissions/hooks | native callbacks | native request events | sandbox/approval | flags/config |
Agents are folders
Everything defined in Python can be saved as files, edited by hand, and loaded back:
agent.save("agent")
agent = Agent.from_folder("agent")
agent/
agent.yaml
instructions.md
skills/
source-grounding/SKILL.md
subagents/
reviewer/
workflows/
ship/
Provider files like .claude/ and .codex/ are compiled from this source,
only when you ask:
agent.bundle(provider="codex", surface="codex_cli").write(Path.cwd())
Surfaces
This is the deeper layer:
agent definition -> provider surface -> real harness
Each provider ships more than one way in. Harness("codex") picks the
strongest one for you (the app-server); address a surface directly when you
require an exact one:
| Surface | What it is |
|---|---|
codex:app |
Codex app-server — sessions, native goals, skill roots |
codex:sdk |
Codex Python SDK |
codex:cli |
Codex CLI — codex exec, resumable threads |
claude:sdk |
Claude Agent SDK for Python |
discover reports what this machine already has — surfaces installed, logins
ready, models available — and picks the first ready surface satisfying the
requested features:
from yoke import Feature, discover
found = await discover("codex", Path.cwd(), agent) # reuses your local login
for surface in found.surfaces:
print(surface.surface, [model.id for model in surface.models])
harness = found.harness(Feature.STREAMING)
Claude also accepts runtime-only Credentials (redacted, never serialized);
Codex logins persist provider state, so they go through an explicit
await harness.login(...).
CLI
The same agents and folders, from the shell:
yoke run agents codealmanac "Review this repo"
yoke explain agents codealmanac
yoke status agents codealmanac
yoke install agents codealmanac --provider codex:cli
yoke runs
CLI runs leave inspectable snapshots under .yoke/runs/. SDK users can persist
returned results explicitly with RunStore.at(".yoke/runs").record(result).
Status
Yoke is an early alpha. Everything shown above is built and smoke-tested against live providers. The API may still change before 1.0; durable workflow execution and typed coverage of every provider-specific option remain future work.
- Quickstart
- Reference
- Design notes — every decision, recorded
- Contributing
- Security
Apache-2.0. See LICENSE.md.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file almanac_yoke-0.1.3.tar.gz.
File metadata
- Download URL: almanac_yoke-0.1.3.tar.gz
- Upload date:
- Size: 113.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7fef3c7a9f61725936af06ea0ef1c645a6bd39d87e6d52539c3902f885835dc1
|
|
| MD5 |
6df590e5314a588a9451c4e7b8895ee6
|
|
| BLAKE2b-256 |
93d4a8896faf4a870b049d0e969ce275d65126671f984530f66dfce48200ec61
|
Provenance
The following attestation bundles were made for almanac_yoke-0.1.3.tar.gz:
Publisher:
publish.yml on AlmanacCode/Yoke
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
almanac_yoke-0.1.3.tar.gz -
Subject digest:
7fef3c7a9f61725936af06ea0ef1c645a6bd39d87e6d52539c3902f885835dc1 - Sigstore transparency entry: 2141809751
- Sigstore integration time:
-
Permalink:
AlmanacCode/Yoke@813ddc73e4ce79c6018d4f70a9495e7de7198471 -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/AlmanacCode
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@813ddc73e4ce79c6018d4f70a9495e7de7198471 -
Trigger Event:
release
-
Statement type:
File details
Details for the file almanac_yoke-0.1.3-py3-none-any.whl.
File metadata
- Download URL: almanac_yoke-0.1.3-py3-none-any.whl
- Upload date:
- Size: 132.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f779ef3e109aff544692b384e758c0c9c71e81a836b383bfcf7ea798dd792b78
|
|
| MD5 |
b29562f5803117898c56a7bd9e697566
|
|
| BLAKE2b-256 |
8b246cf80e61fc597a7d181ea86f4f7dcd27880ceb64bebce81e562cf544206d
|
Provenance
The following attestation bundles were made for almanac_yoke-0.1.3-py3-none-any.whl:
Publisher:
publish.yml on AlmanacCode/Yoke
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
almanac_yoke-0.1.3-py3-none-any.whl -
Subject digest:
f779ef3e109aff544692b384e758c0c9c71e81a836b383bfcf7ea798dd792b78 - Sigstore transparency entry: 2141809792
- Sigstore integration time:
-
Permalink:
AlmanacCode/Yoke@813ddc73e4ce79c6018d4f70a9495e7de7198471 -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/AlmanacCode
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@813ddc73e4ce79c6018d4f70a9495e7de7198471 -
Trigger Event:
release
-
Statement type: