Shared Worker Harness & Learning Loop
July 11, 2026 · View on GitHub
Status: H1 implemented (+A1 discovery of existing repo assets), H2 implemented (partial continuation), H3 implemented (workspace hooks), H4 implemented (learning loop: skill auto-learn + S4 score/prune, rule auto-learn,
yardlet harness review). Remaining within H4: deterministic-observation candidate mining (failure themes → candidates). H5 deferred. Companion docs: parallel-queue.md, routing-and-telemetry.md.
Problem
Yardlet drives interchangeable workers (Codex, Claude Code, any CLI via the generic adapter), but the harness around them — repo rules, reusable procedures, deterministic guards, accumulated lessons — barely exists and is not shared:
.agents/rules/is only read by Claude Code, by accident (via CLAUDE.md); Codex and custom workers never see it.- There is no skill library, no catalog, no per-task skill loading.
- Deterministic guards exist only as built-ins (billing env scrub, packet danger list, evaluator forbidden paths); a workspace cannot add its own.
- Nothing learned in one run ever improves the next run. The handoffs and evaluations pile up as history, not as harness.
This absorbs the remaining patterns the spec calls for (§13.2 Hermes skill lifecycle, §13.4 oh-my/OMC hooks and permission matrix), in Yardlet's shape.
Principles (inherited, non-negotiable)
- The packet is the only shared injection point. Anything that must
reach every adapter-connected worker goes through the compiled packet
(inline or as a read anchor) — never through a CLI-specific mechanism
like
.claude/skills/. One harness, all workers. - Policy vs mechanism. Mechanisms collect and suggest on every cycle; only a human promotes a suggestion into the harness. Same discipline as routing/telemetry: lessons never enter packets without explicit promotion.
.agents/stays canonical. All harness assets live under.agents/in the workspace; Yardlet writes promoted assets throughsrc/state.rs.- Token economy. Inline only what is small and always relevant; anchor the rest and let the worker read on demand (progressive loading).
Asset model
.agents/
rules/ always-on constraints, small .md files → inlined in every packet
skills/<name>/SKILL.md reusable procedures → catalog line in packet;
(frontmatter: name, description) body read on demand
agents/<role>.md role extensions (exists today) → appended to that role's packets
hooks/
pre-run.d/* executable guards, run BEFORE spawn (non-zero exit = abort run)
post-run.d/* executable checks, run at evaluation (non-zero exit = fail check)
telemetry/harness.jsonl suggestion candidates (mechanism-owned)
Phase H1 — shared injection (rules + skill catalog) — implemented
Execution packets gain two sections, compiled identically for every worker:
- Workspace rules: the concatenated contents of
.agents/rules/*.md, inlined, capped at ~4 KB total (over the cap: inline the newest, anchor the rest with a note). Rules are constraints, so they must not depend on the worker choosing to read them. - Skills: one catalog line per skill (
name — descriptionfrom SKILL.md frontmatter) plus the instruction "read.agents/skills/<name>/SKILL.mdbefore work it applies to". Bodies are never inlined (progressive loading). The planner may settask.skills: [name]to mark a skill as required for a task; required skills become explicit read anchors.
Planning packets get the same rules section (planning must respect repo
rules too) and the catalog, so the planner can assign task.skills.
Phase H2 — Partial handling (continuation, not redo)
Partial today halts the auto-drain and waits for a human. That wastes the
work already done. Partial has three distinct causes and gets three
behaviors:
| cause | behavior |
|---|---|
worker self-reported incomplete (status: partial) | auto-continue: next run of the task is a continuation packet — it injects the previous run's checkpoint.md, the worker's compact_summary, and the unmet acceptance criteria, with the instruction "continue from this checkpoint; do not redo finished work". Bounded by the existing per-drain attempts cap (2), then halt to NeedsUser. |
| parallel merge conflict | human-gated, unchanged: worktree kept, conflict in the handoff. (A future option is a dedicated "integrate" task; not in this phase.) |
| recovery/integration error | same as merge conflict — surfaced, human decides. |
Mechanically: run_next gains continuation inputs (like the existing
answer-resume path) sourced from the latest Partial run of the task;
run_auto routes Partial retries through it instead of halting on first
sight. A partial_reason recorded at evaluation time distinguishes
self-reported from conflict so the drain knows which to auto-continue.
Phase H3 — hooks (workspace-owned deterministic guards) — implemented
pre-run.d/*: executed by Yardlet before spawning a worker, in the workspace root, withYARD_TASK_ID,YARD_RUN_DIR,YARD_WORKERenv. Non-zero exit aborts the run with the hook's reason in the report — the task fails (drain stops on it; fix the cause and re-run) instead of spawning a worker (e.g. detect-secrets, lint gates, "don't run while CI is red").post-run.d/*: executed during evaluation with the same env. Non-zero exit folds a failed fatal check into the evaluation (the task cannot be Done past it).- Hooks are the workspace's own code; Yardlet never ships enabled hooks, only a
documented
.agents/hooks/README.md(+ emptypre-run.d/post-run.d). Only executable files run, in sorted filename order. A 30s wall-clock timeout (longer = killed + failed) and captured stdout/stderr go to<run_dir>/hooks/<phase>/.hooks: falsein yardlet.yaml turns them off.
This gives the workspace's hooks/ a home where they bind all workers, not
just one CLI. Implementation: src/hooks.rs, wired into src/run.rs.
Phase H4 — the learning loop (every cycle strengthens the harness) — implemented
Lifecycle (spec §13.2): observation → candidate → review → promotion → deprecation.
Implemented: a run's
harness_suggestionsof kind "skill" auto-record as.agents/skills/<slug>/SKILL.md(S3) and of kind "rule" as.agents/rules/learned-<slug>.md(src/skills.rsrecord_run_suggestions/record_run_rules, gated byauto_skill/auto_rule). Learned skills are scored and auto-pruned (S4); learned rules are always-on (no per-task attribution to score) so they are kept until removed — reversible via git, visible viayardlet harness review.trust::minealso mines two deterministic telemetry signals on demand: a worker is a no-result hotspot after at least 6 runs when at least 10% have no parseable result, and a task kind is high-retry after at least 3 tasks reach Done with an average of at least 2.5 attempts.yardlet harness reviewexposes both under Mined observations. They are suggestions only; Yardlet does not auto-apply them.
Observe (mechanism, every run, no extra tokens).
- The result contract gains an optional field:
harness_suggestions: [{kind: "rule"|"skill", title, content}]. The execution packet instructs the worker: "if you learned something reusable about this repo (a convention, a pitfall, a procedure), propose it here — short and imperative." The worker that just did the work is the cheapest possible observer. trust::minederives the no-result hotspot and high-retry kind observations above from run telemetry without another worker call. The current result is computed whenyardlet harness reviewruns; it is not persisted as a candidate.- Mining validation-failure themes, drift/forbidden-path/merge-conflict themes, and NeedsUser question themes is explicitly deferred. Resume that work only when telemetry carries a stable, typed identity for the relevant signal (rather than free-form text alone), and a deterministic fixture can prove a repeat threshold, deduplication, and traceability back to the evidence runs.
Collect + apply (mechanism, auto by default). With auto_rule or
auto_skill enabled, worker-proposed harness_suggestions are written directly
as .agents/rules/learned-<slug>.md or .agents/skills/<slug>/SKILL.md through
state.rs (the worker proposed; the deterministic core writes — I1/I3). From
the next packet on, every worker shares the asset. Mined observations follow a
separate review-only path and are not promoted automatically. What keeps
auto-applied assets from poisoning the loop is the eval feedback loop for
skills plus reversibility in git; learned rules have no per-task attribution
and therefore remain until removed.
Self-correct (eval, the safety mechanism). Each learned asset carries a
source: learned marker and accrues a score from the runs that used it
(review-task pass-through, first-try Done, retry tax — see docs/skills.md).
A learned asset that scores poorly across N intents is auto-deprecated
(unequipped, kept in git). The H1 inline cap bounds packet growth.
Human override (always available, never required). yardlet harness review
shows learned rules, learned-skill scores, and the two classes of mined
observation. Its mined suggestions must be applied separately as a
rule/skill/scope change; the command does not promote or reject them. Learned
assets remain removable or restorable through git. With auto_skill: false,
auto_rule: false, or auto_prune: false, cautious workspaces can disable the
corresponding automatic action (I4: minimize intervention, don't mandate it).
Phase H5 (deferred) — central core & presets
the workspace's remaining role: one shared library wired into many repos
(init tooling, presets, catalog.tsv). Yardlet equivalent would be
yardlet init --core <path> symlinking shared rules/skills into .agents/.
Deferred until H1–H4 prove the in-repo loop; a central core multiplies
whatever the loop produces, including its mistakes.
Order & sizing
| phase | size | depends on |
|---|---|---|
| H1 rules+skills injection | S | — |
| H2 partial continuation | M | — (independent) |
| H3 hooks | M | — |
| H4 learning loop | L | H1 (promotion target), result schema change |
| H5 central core | L | H1–H4 proven |
H1 and H2 first (H2 fixes a real daily pain; H1 is the foundation H4 needs).
Open questions (do not block H1/H2)
- Should
task.skillsbe planner-assigned only, or user-editable in the TUI task view? (start: planner-assigned, hand-edit the yaml if needed) - Suggestion spam control: per-run cap (start: 3) and a minimum-evidence threshold for deterministic candidates (start: 2 occurrences).
- Whether promoted skills should auto-attach to matching task kinds (start: no — catalog + planner assignment only).