@hecer/yoke 1.12.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +3 -3
- package/.codex-plugin/plugin.json +2 -2
- package/CHANGELOG.md +19 -0
- package/README.md +39 -27
- package/canon/loop/prd.schema.md +2 -2
- package/canon/manifest.yaml +2 -2
- package/canon/skills/authoring-prd/SKILL.md +1 -1
- package/canon/skills/yoke-retrofit/SKILL.md +2 -2
- package/canon/skills/yoke-workflow/SKILL.md +1 -1
- package/canon/tools/graphify.md +1 -1
- package/canon/tools/playwright-mcp.md +1 -1
- package/canon/tools/serena.md +1 -1
- package/dist/agents/catalog.js +7 -0
- package/dist/agents/contracts.js +3 -1
- package/dist/agents/host.js +4 -0
- package/dist/agents/process-streams.js +62 -0
- package/dist/agents/process.js +43 -3
- package/dist/agents/providers.js +45 -3
- package/dist/agents/telemetry.js +99 -2
- package/dist/canon/manifest.js +2 -1
- package/dist/change/inbox.js +1 -1
- package/dist/cli.js +22 -24
- package/dist/dashboard/panels.js +2 -2
- package/dist/goals/command.js +3 -2
- package/dist/loop/claims.js +2 -1
- package/dist/loop/decision.js +3 -2
- package/dist/loop/parallel-command.js +4 -2
- package/dist/loop/prd.js +2 -1
- package/dist/loop/reporter.js +1 -0
- package/dist/loop/run-command.js +31 -10
- package/dist/prd/command.js +3 -3
- package/dist/quality/candidate-comparison.js +6 -1
- package/dist/quality/command.js +17 -2
- package/dist/quality/types.js +6 -1
- package/dist/retrofit/apply.js +87 -1
- package/dist/retrofit/config.js +8 -0
- package/dist/retrofit/detect.js +6 -0
- package/dist/retrofit/plan.js +6 -0
- package/dist/retrofit/planners/kilo.js +44 -0
- package/dist/retrofit/planners/opencode.js +44 -0
- package/dist/retrofit/planners/pi.js +24 -0
- package/dist/retrofit/skill-actions.js +3 -0
- package/dist/retrofit/tools.js +8 -0
- package/dist/review/command.js +3 -2
- package/dist/review/verdict.js +1 -1
- package/dist/routing/capability.js +2 -2
- package/dist/routing/planning.js +2 -0
- package/dist/routing/registry.js +3 -1
- package/dist/routing/router.js +7 -3
- package/dist/setup/command.js +13 -3
- package/docs/HARNESSES.md +81 -0
- package/gemini-extension.json +2 -2
- package/package.json +6 -2
package/dist/routing/planning.js
CHANGED
|
@@ -4,8 +4,10 @@ export function resolvePlanner(config, start, selection = {}, override) {
|
|
|
4
4
|
const inherited = agent === start ? selection : {};
|
|
5
5
|
const planning = !override || override === (config?.planning?.agent ?? start) ? config?.planning : undefined;
|
|
6
6
|
return { agent, selection: {
|
|
7
|
+
provider: planning?.provider ?? inherited.provider,
|
|
7
8
|
model: planning?.model ?? inherited.model,
|
|
8
9
|
reasoningEffort: planning?.reasoningEffort ?? inherited.reasoningEffort,
|
|
10
|
+
variant: planning?.variant ?? inherited.variant,
|
|
9
11
|
bare: inherited.bare,
|
|
10
12
|
nativeMultiAgent: false,
|
|
11
13
|
} };
|
package/dist/routing/registry.js
CHANGED
|
@@ -68,8 +68,10 @@ export function historyForWorkers(workers) {
|
|
|
68
68
|
// Capability evidence belongs to the provider/model that produced it. This
|
|
69
69
|
// prevents a reused worker id from inheriting scores from a retired model.
|
|
70
70
|
if (event.provider !== worker.agent
|
|
71
|
+
|| event.requestedProvider !== worker.provider
|
|
71
72
|
|| event.requestedModel !== worker.model
|
|
72
|
-
|| event.requestedReasoningEffort !== worker.reasoningEffort
|
|
73
|
+
|| event.requestedReasoningEffort !== worker.reasoningEffort
|
|
74
|
+
|| event.requestedVariant !== worker.variant)
|
|
73
75
|
continue;
|
|
74
76
|
if (typeof event.verificationSuccess !== 'boolean')
|
|
75
77
|
continue;
|
package/dist/routing/router.js
CHANGED
|
@@ -101,8 +101,10 @@ function callUsage(role, provider, selection, tokens, durationMs, profile) {
|
|
|
101
101
|
role,
|
|
102
102
|
provider,
|
|
103
103
|
...(profile ? { profile } : {}),
|
|
104
|
+
...(selection.provider ? { requestedProvider: selection.provider } : {}),
|
|
104
105
|
...(selection.model ? { requestedModel: selection.model } : {}),
|
|
105
106
|
...(selection.reasoningEffort ? { requestedReasoningEffort: selection.reasoningEffort } : {}),
|
|
107
|
+
...(selection.variant ? { requestedVariant: selection.variant } : {}),
|
|
106
108
|
...(tokens?.model ? { actualModel: tokens.model } : {}),
|
|
107
109
|
inputTokens: tokens?.inputTokens ?? 0,
|
|
108
110
|
...(tokens?.cachedInputTokens !== undefined ? { cachedInputTokens: tokens.cachedInputTokens } : {}),
|
|
@@ -191,7 +193,7 @@ function routingSteps(options) {
|
|
|
191
193
|
if (!assessment)
|
|
192
194
|
return { success: false, summary: 'Routing assessment unavailable or invalid; implementation was not started', tokens: aggregateCalls(calls), routing: { recordOutcome: () => undefined, blocked: true } };
|
|
193
195
|
const choice = chooseCapability({ root, story: ctx.story, assessment, workers: eligibleWorkers, parent: options.parent, parentSelection: options.parentSelection, maxAttempts: options.maxAttempts, fallback: options.fallback, maxTier: options.maxTier });
|
|
194
|
-
options.onDecision?.(ctx.story.id, { profile: choice.worker?.id ?? 'SELF', provider: choice.provider, model: choice.selection.model, reasoningEffort: choice.selection.reasoningEffort, reason: choice.reason, next: choice.next, assessment });
|
|
196
|
+
options.onDecision?.(ctx.story.id, { profile: choice.worker?.id ?? 'SELF', provider: choice.provider, model: choice.selection.model, reasoningEffort: choice.selection.reasoningEffort, variant: choice.selection.variant, providerModel: choice.selection.provider, reason: choice.reason, next: choice.next, assessment });
|
|
195
197
|
if (choice.blocked)
|
|
196
198
|
return { ...blocked(choice.reason), tokens: aggregateCalls(calls) };
|
|
197
199
|
if (choice.exhausted)
|
|
@@ -209,7 +211,7 @@ function routingSteps(options) {
|
|
|
209
211
|
return;
|
|
210
212
|
recorded = true;
|
|
211
213
|
recordRoutingObservation({ projectHash: projectHash(root), storyHash: storyHash(projectHash(root), ctx.story.id), assessmentKey: routingAssessmentKey(root, ctx.story), taskClass: assessment.taskClass, requiredTier: choice.requiredTier,
|
|
212
|
-
role: 'implementation', strategy: 'capability', selected: choice.worker?.id ?? 'SELF', provider: choice.provider, requestedModel: choice.selection.model, requestedReasoningEffort: choice.selection.reasoningEffort,
|
|
214
|
+
role: 'implementation', strategy: 'capability', selected: choice.worker?.id ?? 'SELF', provider: choice.provider, requestedProvider: choice.selection.provider, requestedModel: choice.selection.model, requestedReasoningEffort: choice.selection.reasoningEffort, requestedVariant: choice.selection.variant,
|
|
213
215
|
actualModel: result.tokens?.model, orchestratorProvider: options.planner?.agent ?? options.parent, orchestratorModel: (options.planner?.selection ?? options.parentSelection)?.model, orchestratorDurationMs: calls.filter(c => c.role === 'orchestrator').reduce((s, c) => s + c.durationMs, 0), workerDurationMs: calls[calls.length - 1].durationMs,
|
|
214
216
|
processSuccess: result.success, verificationSuccess: infrastructureFailure ? false : verified, failureKind: infrastructureFailure ? 'infrastructure' : failureKind ?? 'implementation', usageAvailable: result.tokens !== undefined && result.tokens.measurementComplete !== false,
|
|
215
217
|
inputTokens: result.tokens?.inputTokens ?? 0, outputTokens: result.tokens?.outputTokens ?? 0, totalCostUsd: result.tokens?.totalCostUsd });
|
|
@@ -247,7 +249,7 @@ function routingSteps(options) {
|
|
|
247
249
|
return blocked('Selected routing profile exceeds configured limits; execution blocked');
|
|
248
250
|
const provider = worker?.agent ?? options.parent;
|
|
249
251
|
const selection = worker
|
|
250
|
-
? { model: worker.model, reasoningEffort: worker.reasoningEffort, nativeMultiAgent: false, ...(provider !== 'gemini' && provider !== 'qwen' ? { bare: options.parentSelection?.bare } : {}) }
|
|
252
|
+
? { provider: worker.provider, model: worker.model, reasoningEffort: worker.reasoningEffort, variant: worker.variant, nativeMultiAgent: false, ...(provider !== 'gemini' && provider !== 'qwen' && provider !== 'pi' ? { bare: options.parentSelection?.bare } : {}) }
|
|
251
253
|
: { ...(options.parentSelection ?? {}), nativeMultiAgent: false };
|
|
252
254
|
const workerStarted = now();
|
|
253
255
|
const result = yield () => makeWorker(provider, selection)(ctx);
|
|
@@ -296,6 +298,8 @@ function routingSteps(options) {
|
|
|
296
298
|
provider,
|
|
297
299
|
...(selection.model ? { requestedModel: selection.model } : {}),
|
|
298
300
|
...(selection.reasoningEffort ? { requestedReasoningEffort: selection.reasoningEffort } : {}),
|
|
301
|
+
...(selection.provider ? { requestedProvider: selection.provider } : {}),
|
|
302
|
+
...(selection.variant ? { requestedVariant: selection.variant } : {}),
|
|
299
303
|
...(result.tokens?.model ? { actualModel: result.tokens.model } : {}),
|
|
300
304
|
orchestratorProvider: options.parent,
|
|
301
305
|
...(orchestratorSelection.model ? { orchestratorModel: orchestratorSelection.model } : {}),
|
package/dist/setup/command.js
CHANGED
|
@@ -7,7 +7,8 @@ import { applyActions } from '../retrofit/apply.js';
|
|
|
7
7
|
import { join } from 'node:path';
|
|
8
8
|
import { modelPresetWorkers, planModelPresets } from './model-presets.js';
|
|
9
9
|
import { runRetrofit } from '../retrofit/command.js';
|
|
10
|
-
|
|
10
|
+
import { SUPPORTED_AGENTS } from '../agents/catalog.js';
|
|
11
|
+
const ALL_AGENTS = [...SUPPORTED_AGENTS];
|
|
11
12
|
export function defaultRoutingWorkers(agents) {
|
|
12
13
|
const workers = {
|
|
13
14
|
claude: [
|
|
@@ -32,6 +33,15 @@ export function defaultRoutingWorkers(agents) {
|
|
|
32
33
|
// Respect the user's Qwen Code account/model. API presets are opt-in.
|
|
33
34
|
{ id: 'qwen-standard', agent: 'qwen', tier: 'standard', costTier: 'medium', capabilities: ['implementation'] },
|
|
34
35
|
],
|
|
36
|
+
opencode: [
|
|
37
|
+
{ id: 'opencode-standard', agent: 'opencode', tier: 'standard', costTier: 'medium', capabilities: ['implementation'] },
|
|
38
|
+
],
|
|
39
|
+
kilo: [
|
|
40
|
+
{ id: 'kilo-standard', agent: 'kilo', tier: 'standard', costTier: 'medium', capabilities: ['implementation'] },
|
|
41
|
+
],
|
|
42
|
+
pi: [
|
|
43
|
+
{ id: 'pi-standard', agent: 'pi', tier: 'standard', costTier: 'medium', capabilities: ['implementation'] },
|
|
44
|
+
],
|
|
35
45
|
};
|
|
36
46
|
return agents.flatMap(agent => workers[agent]);
|
|
37
47
|
}
|
|
@@ -85,12 +95,12 @@ export async function runSetup(targetDir, opts = {}) {
|
|
|
85
95
|
let decisionPolicy = defaultPolicy;
|
|
86
96
|
let routing = defaultRouting;
|
|
87
97
|
if (interactive && ask) {
|
|
88
|
-
agents = parseAgents(await ask(`Agents [${defaultAgents.join(',')}] (
|
|
98
|
+
agents = parseAgents(await ask(`Agents [${defaultAgents.join(',')}] (${SUPPORTED_AGENTS.join(',')}|all): `), defaultAgents);
|
|
89
99
|
const graphAnswer = (await ask(`Code graph [${defaultGraph}] (graphify|serena): `)).trim().toLowerCase();
|
|
90
100
|
if (graphAnswer === 'graphify' || graphAnswer === 'serena')
|
|
91
101
|
codeGraph = graphAnswer;
|
|
92
102
|
loop = yes(await ask(`Enable autonomous loop? [${defaultLoop ? 'yes' : 'no'}]: `), defaultLoop);
|
|
93
|
-
const runnerAnswer = (await ask(`Default runner [${runner}] (
|
|
103
|
+
const runnerAnswer = (await ask(`Default runner [${runner}] (${SUPPORTED_AGENTS.join('|')}): `)).trim().toLowerCase();
|
|
94
104
|
if (ALL_AGENTS.includes(runnerAnswer))
|
|
95
105
|
runner = runnerAnswer;
|
|
96
106
|
const policyAnswer = (await ask(`Decision mode [${decisionPolicy}] (auto|critical): `)).trim().toLowerCase();
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# OpenCode, Kilo and Pi
|
|
2
|
+
|
|
3
|
+
Yoke 1.13.0 adds first-class adapters for OpenCode, Kilo and Pi coding agent. The adapters share Yoke's invocation, routing, review, quality, telemetry and retrofit contracts, while preserving the controls each harness actually provides.
|
|
4
|
+
|
|
5
|
+
This is a CLI integration, not an authentication bundle. Install the selected harness, log in or configure its API provider, and verify it independently before starting a Yoke loop.
|
|
6
|
+
|
|
7
|
+
## Setup and retrofit
|
|
8
|
+
|
|
9
|
+
Select one of the new harnesses explicitly:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
yoke setup . --yes --agent=opencode --runner=opencode
|
|
13
|
+
yoke setup . --yes --agent=kilo --runner=kilo
|
|
14
|
+
yoke setup . --yes --agent=pi --runner=pi
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
To add one to an existing project without changing the other generated artifacts:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
yoke retrofit . --agent=opencode
|
|
21
|
+
yoke retrofit . --agent=kilo
|
|
22
|
+
yoke retrofit . --agent=pi
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`--agent=all` now includes all seven supported harnesses. Retrofit is merge-aware for the native config files and backs up Yoke-managed overwrites under `.yoke/backup/`.
|
|
26
|
+
|
|
27
|
+
## Native artifacts
|
|
28
|
+
|
|
29
|
+
| Harness | Generated project artifacts | Native capabilities used by Yoke |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| OpenCode | `AGENTS.md`, `.opencode/skills/`, `opencode.json`, `.opencode/agents/yoke-reviewer.md` | `run --format json`, provider/model selection, variants, plan agent, local MCP servers |
|
|
32
|
+
| Kilo | `AGENTS.md`, `.kilo/skills/`, `kilo.jsonc`, `.kilo/agents/yoke-reviewer.md` | OpenCode-compatible `run --format json`, provider/model selection, variants, plan agent, local MCP servers |
|
|
33
|
+
| Pi | `AGENTS.md`, `.pi/skills/`, `.pi/settings.json` | JSONL mode, provider/model selection, thinking level, explicit tool allowlists |
|
|
34
|
+
|
|
35
|
+
All three consume the shared `AGENTS.md` and `.yoke/context/*.md` context. OpenCode and Kilo also receive the configured local MCP servers from the Yoke code-graph choice. Pi has no native MCP or sub-agent layer, so its integration intentionally uses the portable skills and Yoke's own loop rather than pretending those features exist.
|
|
36
|
+
|
|
37
|
+
## Provider, model and variant selection
|
|
38
|
+
|
|
39
|
+
Yoke keeps the harness (`agent`) separate from the model provider (`provider`) and model identifier (`model`):
|
|
40
|
+
|
|
41
|
+
```yaml
|
|
42
|
+
runner:
|
|
43
|
+
agent: opencode
|
|
44
|
+
provider: openrouter
|
|
45
|
+
model: openai/gpt-5.6
|
|
46
|
+
variant: high
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
For OpenCode and Kilo this becomes `--model openrouter/openai/gpt-5.6 --variant high`. If no explicit provider is configured, a model string already in the harness's `provider/model` form is passed through unchanged. For Pi the equivalent is:
|
|
50
|
+
|
|
51
|
+
```yaml
|
|
52
|
+
runner:
|
|
53
|
+
agent: pi
|
|
54
|
+
provider: openai
|
|
55
|
+
model: gpt-5.6
|
|
56
|
+
reasoningEffort: high
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
This becomes `--provider openai --model gpt-5.6 --thinking high`. Pi calls `variant` and `reasoningEffort` the same underlying thinking-level selection; configuring both with different values is rejected.
|
|
60
|
+
|
|
61
|
+
The same fields are available on routing workers and quality critic/repair roles. Routing evidence is keyed by harness, provider, model, reasoning effort and variant, so a model profile does not inherit another profile's success history.
|
|
62
|
+
|
|
63
|
+
## Permission profiles and honest limits
|
|
64
|
+
|
|
65
|
+
| Yoke profile | OpenCode / Kilo | Pi |
|
|
66
|
+
|---|---|---|
|
|
67
|
+
| `safe` | Headless `--auto` run; Yoke still verifies the resulting tree and gates the commit | `read,bash,edit,write` tool allowlist so the implementer can test and modify the project |
|
|
68
|
+
| `read-only` | `--agent plan` plus JSON output | `read,grep,find,ls` only |
|
|
69
|
+
| `unsafe` | Explicit `--dangerously-skip-permissions` | Harness default tool set; no Yoke-added allowlist |
|
|
70
|
+
|
|
71
|
+
OpenCode, Kilo and Pi do not provide the same OS-level sandbox boundary as Codex or Gemini. The Yoke `safe` label therefore describes the selected harness controls and Yoke's mechanical gates, not a universal filesystem sandbox. Use `read-only` for review and do not use `unsafe` unless the project owner accepts the boundary.
|
|
72
|
+
|
|
73
|
+
The Yoke loop disables native delegation where the harness exposes it, so Yoke's worker budget remains the authority. OpenCode and Kilo native agents are installed as a read-only reviewer artifact; Yoke's review runner still validates the structured verdict itself. Pi has no native sub-agent or plan mode by design.
|
|
74
|
+
|
|
75
|
+
## Telemetry and validation limits
|
|
76
|
+
|
|
77
|
+
Yoke parses OpenCode/Kilo JSON text events and `step_finish` token/cost events, and Pi `message_end` plus `message_update.usage` events. Missing provider events produce partial or unknown measurements; they are never reported as zero-cost success. Provider-reported model and usage remain provider claims.
|
|
78
|
+
|
|
79
|
+
The release test suite covers argument construction, provider/variant propagation, routing and quality configuration, retrofit plans, host detection, JSON result parsing, and representative telemetry fixtures. It does not authenticate against every provider or claim equal model quality. Run a small project-specific smoke task after installing a harness and configuring credentials.
|
|
80
|
+
|
|
81
|
+
Official CLI references: [OpenCode CLI](https://dev.opencode.ai/docs/cli), [OpenCode configuration](https://opencode.ai/docs/config), [OpenCode skills](https://opencode.ai/docs/skills), [Kilo CLI reference](https://github.com/Kilo-Org/kilocode/blob/main/packages/kilo-docs/pages/code-with-ai/platforms/cli-reference.md), and [Pi coding agent](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/README.md).
|
package/gemini-extension.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yoke",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Cross-agent coding harness: curated skill canon, mechanical safety gates, autonomous loop with proof artifacts. CLI: npm i -g @hecer/yoke",
|
|
3
|
+
"version": "1.13.0",
|
|
4
|
+
"description": "Cross-agent coding harness for seven supported CLIs: curated skill canon, mechanical safety gates, autonomous loop with proof artifacts. CLI: npm i -g @hecer/yoke",
|
|
5
5
|
"contextFileName": "GEMINI-EXTENSION.md"
|
|
6
6
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hecer/yoke",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "One harness,
|
|
3
|
+
"version": "1.13.0",
|
|
4
|
+
"description": "One harness, seven agents, zero trust in \"done\" — cross-agent coding harness for Claude Code, Codex CLI, Gemini CLI, Qwen Code, OpenCode, Kilo and Pi: one skill canon, mechanical safety gates, an autonomous loop with screenshot/video proofs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"yoke": "dist/cli.js"
|
|
@@ -45,6 +45,10 @@
|
|
|
45
45
|
"claude-code",
|
|
46
46
|
"codex",
|
|
47
47
|
"gemini-cli",
|
|
48
|
+
"qwen-code",
|
|
49
|
+
"opencode",
|
|
50
|
+
"kilo",
|
|
51
|
+
"pi-coding-agent",
|
|
48
52
|
"agents",
|
|
49
53
|
"agentic-coding",
|
|
50
54
|
"harness",
|