{"id":50999878,"url":"https://github.com/cablehead/yoke","last_synced_at":"2026-06-20T13:30:51.797Z","repository":{"id":348866164,"uuid":"1199880839","full_name":"cablehead/yoke","owner":"cablehead","description":"A single agent turn as a unix pipe.","archived":false,"fork":false,"pushed_at":"2026-05-04T14:58:37.000Z","size":734,"stargazers_count":14,"open_issues_count":0,"forks_count":3,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-06-18T21:31:44.801Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cablehead.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-02T20:05:22.000Z","updated_at":"2026-06-06T23:55:34.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/cablehead/yoke","commit_stats":null,"previous_names":["cablehead/yoke"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/cablehead/yoke","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cablehead%2Fyoke","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cablehead%2Fyoke/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cablehead%2Fyoke/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cablehead%2Fyoke/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cablehead","download_url":"https://codeload.github.com/cablehead/yoke/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cablehead%2Fyoke/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34572423,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-20T02:00:06.407Z","response_time":98,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-20T13:30:48.643Z","updated_at":"2026-06-20T13:30:51.787Z","avatar_url":"https://github.com/cablehead.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1\u003e\n\u003cp align=\"center\"\u003e\n  yoke\n  \u003cbr\u003e\u003cbr\u003e\n  \u003csup\u003eA single agent turn as a unix pipe.\u003c/sup\u003e\n\u003c/p\u003e\n\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/cablehead/yoke/actions/workflows/release-binaries.yml\"\u003e\n    \u003cimg src=\"https://github.com/cablehead/yoke/actions/workflows/release-binaries.yml/badge.svg\" alt=\"CI\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://discord.com/invite/YNbScHBHrh\"\u003e\n    \u003cimg src=\"https://img.shields.io/discord/1182364431435436042?logo=discord\" alt=\"Discord\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n---\n\nyoke is a static binary that drives one LLM agent turn to completion. It runs\ntool calls in a loop until the model is satisfied, then exits. Context window\nin as JSONL on stdin, new context + live stream out as JSONL on stdout.\n\n```\ncontext.jsonl ──\u003e yoke ──\u003e tee ──\u003e store context for follow-ups\n                               └─\u003e real-time view\n```\n\nNo TUI, no REPL, no daemon, no persistence. Just a JSONL-in / JSONL-out\nprimitive you compose with shell tools. Particularly\n[Nushell](https://www.nushell.sh), which is purpose-built for orchestrating\nstructured data streams.\n\n```nushell\n# one-shot\nyoke --provider gemini --model gemini-2.5-flash \"what files are here?\"\n\n# pipe context in, tee the stream to a file for follow-ups\nyoke --provider anthropic --model claude-sonnet-4-20250514 \"refactor main.rs\"\n  | tee { save -f session.jsonl }\n\n# continue the conversation\ncat session.jsonl\n  | yoke --provider anthropic --model claude-sonnet-4-20250514 \"now add tests\"\n\n# replay the same context against a different model\ncat session.jsonl\n  | yoke --provider openai --model gpt-5.4-mini \"summarize what happened\"\n```\n\nBuilt on [yoagent](https://github.com/yologdev/yoagent).\n\n- [Install](#install)\n- [Providers](#providers)\n- [Tools](#tools)\n- [Input / Output](#input--output)\n- [Round-tripping](#round-tripping)\n- [Skills](#skills)\n- [Web UI](#web-ui)\n\n## Install\n\n### [eget](https://github.com/zyedidia/eget)\n\n```nushell\neget cablehead/yoke\n```\n\n### Homebrew (macOS)\n\n```nushell\nbrew install cablehead/tap/yoke\n```\n\n### cargo\n\n```nushell\ncargo install --git https://github.com/cablehead/yoke\n```\n\n### Build from source\n\n```nushell\ngit clone https://github.com/cablehead/yoke\ncd yoke\ncargo build --release\n```\n\n## Providers\n\nRun with no arguments to see available providers:\n\n```nushell\n$ yoke\navailable providers:\n\n  anthropic\n    env: ANTHROPIC_API_KEY\n    key: https://console.anthropic.com/settings/keys\n\n  openai\n    env: OPENAI_API_KEY\n    key: https://platform.openai.com/api-keys\n\n  gemini\n    env: GEMINI_API_KEY\n    key: https://aistudio.google.com/apikey\n\n  ollama\n    local, no API key required\n    default: http://localhost:11434\n```\n\nRun with a provider and no model to list available models:\n\n```nushell\n$ yoke --provider anthropic\nclaude-3-5-haiku-20241022\nclaude-3-5-sonnet-20241022\nclaude-sonnet-4-20250514\n...\n```\n\n| Provider | Env var | API |\n|----------|---------|-----|\n| anthropic | `ANTHROPIC_API_KEY` | Anthropic Messages |\n| openai | `OPENAI_API_KEY` | OpenAI Chat Completions |\n| gemini | `GEMINI_API_KEY` | Google Generative AI |\n| ollama | -- | Local, OpenAI-compatible |\n\n### Ollama\n\nRun models locally with [Ollama](https://ollama.com). No API key required.\n\n```nushell\nyoke --provider ollama\nyoke --provider ollama --model gemma4 \"hello\"\nyoke --provider ollama --base_url http://192.168.1.100:11434 --model llama3 \"hello\"\n```\n\n## Tools\n\nControl which tools the agent has access to with `--tools`:\n\n```nushell\n# all tools including web search (default)\nyoke --provider gemini --model gemini-2.5-flash --tools all \"find recent rust news\"\n\n# code tools only\nyoke --provider anthropic --model claude-sonnet-4-20250514 --tools code \"refactor main.rs\"\n\n# nushell instead of bash\nyoke --provider gemini --model gemini-2.5-flash --tools nu,read_file \"check the logs\"\n\n# no tools\nyoke --provider anthropic --model claude-sonnet-4-20250514 --tools none \"explain ownership in rust\"\n```\n\n| Tool | Description |\n|------|-------------|\n| `bash` | Shell command execution |\n| `nu` | Nushell script execution (embedded engine) |\n| `read_file` | Read files with line numbers |\n| `write_file` | Create or overwrite files |\n| `edit_file` | Search/replace editing |\n| `list_files` | Directory listing |\n| `search` | Grep/ripgrep pattern search |\n| `web_search` | Provider-side web search |\n\n### The nu tool\n\nThe builtin `nu` tool runs Nushell scripts in an embedded engine -- no\nsubprocess, no shell. Output is automatically converted to\n[nuon](https://www.nushell.sh/lang-guide/chapters/types/nuon.html) so\nstructured data round-trips cleanly.\n\nAn optional `input` parameter accepts JSON data that gets piped as `$in` to\nthe command. This lets the LLM pass structured data as native JSON without\nworrying about string quoting:\n\n```json\n{\"command\": \"$in | sort-by price -r\", \"input\": [{\"name\": \"Widget A\", \"price\": 25.50}]}\n```\n\n#### Plugins and modules\n\nLoad Nushell plugins with `--plugin` and module search paths with `-I`:\n\n```nushell\n# load the polars plugin\nyoke --provider gemini --model gemini-2.5-flash --tools nu \\\n  --plugin /usr/local/bin/nu_plugin_polars \\\n  \"open data.csv and find the top 5 rows by price\"\n\n# multiple plugins and an include path\nyoke --provider gemini --model gemini-2.5-flash --tools nu \\\n  --plugin /usr/local/bin/nu_plugin_polars \\\n  --plugin /usr/local/bin/nu_plugin_formats \\\n  -I ./lib \\\n  \"use mymod.nu; analyze the data\"\n```\n\nPlugin names are included in the tool description so the LLM knows\nthey're available and can discover subcommands via `help`.\n\nUse `--config \u003cfile.nu\u003e` to run a Nushell script once at startup. The script\nruns against the shared engine state, so any `use`, `def`, `hide`, or env\nmutations persist across every nu tool call:\n\n```nushell\n# init.nu\nuse mymod\ndef my-favorite-number [] { 42 }\nhide rm\n\nyoke --provider gemini --model gemini-2.5-flash --tools nu \\\n  -I ./lib --config ./init.nu \\\n  \"what is my favorite number?\"\n```\n\nParse and eval errors in the config are fatal and reported at startup with\nfile path and span, so misconfigurations are caught immediately.\n\n### Web search\n\nWeb search is a provider-side capability:\n\n| Provider | How it works | With function tools? |\n|----------|-------------|---------------------|\n| Anthropic | Server tool, model invokes mid-turn | Yes |\n| OpenAI | Dedicated search models (e.g. `gpt-5-search-api`) | No |\n| Gemini | Google Search grounding tool | Yes |\n\n## Input / Output\n\n### Input\n\nJSONL on stdin. Lines with `role` are context messages. Everything else is\nsilently skipped.\n\n```nushell\n# simple prompt\n{role: \"user\", content: \"list files\"} | to json -r\n  | yoke --provider anthropic --model claude-sonnet-4-20250514\n\n# system prompt + user message\n[\n  ({role: \"system\", content: \"You are a helpful assistant.\"} | to json -r)\n  ({role: \"user\", content: \"list files\"} | to json -r)\n] | str join \"\\n\"\n  | yoke --provider anthropic --model claude-sonnet-4-20250514\n```\n\n### Output\n\nJSONL on stdout. Two kinds of lines:\n\n**Context lines** have `role`. User messages, assistant responses, tool\nresults. These round-trip as input to the next turn.\n\n**Observation lines** have `type`. Streaming deltas, tool execution, lifecycle\nevents. Skipped on input.\n\n```jsonl\n{\"type\":\"agent_start\"}\n{\"role\":\"system\",\"content\":\"...\"}\n{\"type\":\"turn_start\"}\n{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"what files are here?\"}],\"timestamp\":1234}\n{\"type\":\"delta\",\"kind\":\"text\",\"delta\":\"I'll check\"}\n{\"type\":\"tool_execution_start\",\"tool_call_id\":\"...\",\"tool_name\":\"list_files\",\"args\":{}}\n{\"type\":\"tool_execution_end\",\"tool_call_id\":\"...\",\"tool_name\":\"list_files\",\"result\":{...}}\n{\"role\":\"toolResult\",\"toolCallId\":\"...\",\"toolName\":\"list_files\",\"content\":[...]}\n{\"role\":\"assistant\",\"content\":[...],\"stopReason\":\"stop\",\"model\":\"...\",\"usage\":{...}}\n{\"type\":\"turn_end\"}\n{\"type\":\"agent_end\"}\n```\n\nThe observation lines are the live stream -- tee them to a renderer for\nreal-time display. The context lines are the durable state -- save them for\nfollow-ups.\n\n## Round-tripping\n\nSave a run:\n\n```nushell\nyoke --provider anthropic --model claude-sonnet-4-20250514 \"what files are here?\"\n  | tee { save -f session.jsonl }\n```\n\nContinue the conversation:\n\n```nushell\ncat session.jsonl\n  | yoke --provider anthropic --model claude-sonnet-4-20250514 \"now count them\"\n```\n\nReplay context against a different model:\n\n```nushell\ncat session.jsonl\n  | yoke --provider openai --model gpt-5.4-mini \"summarize what happened\"\n```\n\n## Skills\n\nLoad [AgentSkills](https://agentskills.io)-compatible skill directories with\n`--skills`. Skill metadata is injected into the system prompt. The agent reads\nfull SKILL.md instructions via `read_file` when it activates a skill.\n\n```nushell\nyoke --provider gemini --model gemini-2.5-flash --skills ./skills --tools read_file \"use the greet skill\"\n```\n\nA skill directory:\n\n```\nskills/\n  greet/\n    SKILL.md\n  weather/\n    SKILL.md\n    scripts/\n```\n\nSKILL.md uses YAML frontmatter with `name` and `description` fields. The body\ncontains full instructions the agent reads on demand.\n\n## Web UI\n\nyoke includes a browser-based UI powered by\n[http-nu](https://github.com/cablehead/http-nu) and\n[Datastar](https://data-star.dev). It streams responses in real time with\nrendered markdown, syntax highlighting, and grounding sources.\n\n```nushell\nhttp-nu --datastar --store ./store :3001 ux/serve.nu\n```\n\nEach yoke run streams JSONL through a render pipeline. The browser morphs HTML\ninto place as the turn progresses. Completed runs are persisted to the\n[cross.stream](https://cross.stream) store for replay.\n\n## Tool eval\n\n`tests/tools/` contains eval cases for iterating on builtin tool descriptions\nand behavior. Each case is a markdown file with a prompt and evaluation\ncriteria. `perform.nu` runs the case through yoke and checks the output.\n\n```nushell\ncd tests/tools/nu\n$env.GEMINI_API_KEY = \"your-key-here\"\nnu perform.nu case1.md\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcablehead%2Fyoke","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcablehead%2Fyoke","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcablehead%2Fyoke/lists"}