Skip to main content

A provider-neutral harness SDK for agent systems on Claude and Codex.

Project description

Yoke — two oxen, one carved yoke: Claude and Codex pulling one load

Python 3.11+ Typed Claude harness Codex harness License: Apache-2.0

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.

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 natively; Codex has no native equivalent, so Yoke compiles them into provider files — same definition, honest lowering.

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 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.

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

almanac_yoke-0.1.0.tar.gz (111.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

almanac_yoke-0.1.0-py3-none-any.whl (129.8 kB view details)

Uploaded Python 3

File details

Details for the file almanac_yoke-0.1.0.tar.gz.

File metadata

  • Download URL: almanac_yoke-0.1.0.tar.gz
  • Upload date:
  • Size: 111.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for almanac_yoke-0.1.0.tar.gz
Algorithm Hash digest
SHA256 d3e67bd4f995b981e0d76fb9aef934693a4c313964c7727014422a9c81dee873
MD5 16052fa688bdee5d624247de7d3697a7
BLAKE2b-256 c06f4ad9f572285bfae5a63a5215b2b1d5fdc9bb5843a86de4bef8c713695ab5

See more details on using hashes here.

Provenance

The following attestation bundles were made for almanac_yoke-0.1.0.tar.gz:

Publisher: publish.yml on AlmanacCode/Yoke

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file almanac_yoke-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: almanac_yoke-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 129.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for almanac_yoke-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7f76e4abcbb4b55260963b3afd55d4dc289cff856d194660d4479f9d44ff0ba0
MD5 02404dddac61e87b92561a09b431adb5
BLAKE2b-256 93144053961f27e00b764f6f7d1de9d2780ff1bc5123a2db65192ba1e6447b6d

See more details on using hashes here.

Provenance

The following attestation bundles were made for almanac_yoke-0.1.0-py3-none-any.whl:

Publisher: publish.yml on AlmanacCode/Yoke

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page