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)

  1. 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.
  2. 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.
  3. .agents/ stays canonical. All harness assets live under .agents/ in the workspace; Yardlet writes promoted assets through src/state.rs.
  4. 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 — description from SKILL.md frontmatter) plus the instruction "read .agents/skills/<name>/SKILL.md before work it applies to". Bodies are never inlined (progressive loading). The planner may set task.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:

causebehavior
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 conflicthuman-gated, unchanged: worktree kept, conflict in the handoff. (A future option is a dedicated "integrate" task; not in this phase.)
recovery/integration errorsame 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, with YARD_TASK_ID, YARD_RUN_DIR, YARD_WORKER env. 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 (+ empty pre-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: false in 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_suggestions of kind "skill" auto-record as .agents/skills/<slug>/SKILL.md (S3) and of kind "rule" as .agents/rules/learned-<slug>.md (src/skills.rs record_run_suggestions/record_run_rules, gated by auto_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 via yardlet harness review. trust::mine also 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 review exposes 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::mine derives the no-result hotspot and high-retry kind observations above from run telemetry without another worker call. The current result is computed when yardlet harness review runs; 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

phasesizedepends on
H1 rules+skills injectionS
H2 partial continuationM— (independent)
H3 hooksM
H4 learning loopLH1 (promotion target), result schema change
H5 central coreLH1–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.skills be 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).