Skip to content

AlmanacCode/Yoke

Repository files navigation

Yoke

yoke (yōk), n. A harness that joins two, that they may pull as one. [akin to Skr. yoga, union.]

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

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 derives temporary custom-agent TOML and the parent selects it with spawn_agent(agent_type=..., fork_turns="none"). Yoke does not silently use a generic child: an incompatible Codex model/backend fails honestly. Codex SDK/CLI surfaces retain their documented lowerings.

Runtime files are derived outside cwd and removed when the session closes. Set Harness(runtime_root=...) to choose their parent cache directory. This is different from agent.bundle(...).write(...), which explicitly exports durable provider files for a project. A later deployment reclaims runtime directories whose owning process exited before normal cleanup.

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.

Apache-2.0. See LICENSE.md.

About

A provider-neutral Python SDK for building agent systems on Claude Code and Codex.

Resources

License

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages