Make os-me eval harnesses reuse shared eval harness code instead of duplicating it #1413
Labels
No labels
P0
P1
P2
P3
bug
category/code-change
category/cross-repo
category/eval-run
category/investigation
category/no-test-target
category/owner-ruling
category/prose-artifact
category/recurring-chore
category/structure
category/umbrella
crc-refactor
create
delete
enhancement
filed-by/agent
filed-by/user
frozen
lint-rule
needs-info
needs-triage
next
plugin/cc-architect
plugin/os
plugin/os-adr
plugin/os-aidd-lint
plugin/os-backlog
plugin/os-context
plugin/os-doc-hygiene
plugin/os-sdlc
plugin/os-vault
project/cc-os
ready-for-agent
ready-for-human
recurring
review
rule-proposal
update
waiting
wayfinder:grilling
wayfinder:map
wayfinder:map
wayfinder:research
wayfinder:task
wayfinder:task
wontfix
worklist/deviations
worklist/lint-rule
worklist/new-implement-build
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
jared/cc-os#1413
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
The eval harnesses repeat the same work. fast-batch-15 ruling R5 moved the bodies of
plugins/os-me/eval/row_labels/run.rbandconfidence_extractor/run.rbintoOsMe::RowLabels::EvalHarnessandOsMe::ConfidenceExtractor::EvalHarness(commit58fea20a). Each class parses the same kind of flags (lever,labels,split,out,reps,jobs,model,adapter) and runs reps across jobs. os-sdlc has its own eval harness underplugins/os-sdlc/eval/ticket-takedown. Jared (2026-10-26): "The eval harness code should tie into the os-sdlc code wherever reasonable. No sense in duplicating functionality."Reproduce
n/a: investigation. Compare
plugins/os-me/lib/os_me/row_labels/eval_harness.rb,plugins/os-me/lib/os_me/confidence_extractor/eval_harness.rb, and the harness underplugins/os-sdlc/eval/ticket-takedown.Approach
/os-adr:findbefore you choose.Origin
VERDICT: CREATE
Findings (2026-10-27, takedown track; read-only investigation, nothing built, no cross-plugin owner chosen).
Only one capability is duplicated across plugins: the headless
claude -pcall and its reply parsing, in three copies.lib/os_me/headless_runner.rb:11-19; takedownlib/takedown/model/model_request.rb:8-13; os-adrlib/adr/judge/claude_command.rb:24-28. Same core flags (--output-format json,--no-session-persistence,--setting-sources '',--json-schema,--system-prompt).headless_runner.rb:31-36(capture3, no timeout); takedownadapter/process_runner.rb:3-50(stdin, timeout, pgroup kill).headless_runner.rb:48-62; takedownparser/claude_reply.rb:30-70(same keys: usage, total_cost_usd, structured_output, is_error).The other harness duplication is inside os-me only, so there is no os-sdlc eval harness for os-me to reuse there: flag parsing (
row_labels/eval_harness.rb:8-61vseval/reply_shape/run.rb:11-41), reps x jobs pool (lever_run.rb:13-36vsreply_shape/run.rb:64-88), agreement/kappa (agreement_score.rb:17-61vsreply_shape/run.rb:90-124).ADRs found: ADR-0279 (shared behavior crosses the plugin boundary only through the domain owner's gem), ADR-0093 (os-sdlc may depend on os-backlog, not the reverse; nothing lets os-me depend on os-sdlc), ADR-0166 (one boot file per plugin sets BUNDLE_GEMFILE; requiring Takedown from os-me would load two bundles), ADR-0219 (precedent: procman gem for shared behavior).
Options for the claude call (needs Jared's ruling, likely a new ADR):
A. os-me requires os-sdlc code:
require File.expand_path('../../../os-sdlc/eval/ticket-takedown/lib/takedown', __dir__)— conflicts with ADR-0279 and ADR-0166.B. New gem on the procman pattern:
gem 'claude-headless', path: '~/dev/ruby-gems/claude-headless';ClaudeHeadless::Request.new(model:, schema:, system_prompt:, tools:).call(prompt, cwd:)->ClaudeHeadless::Reply. Fits ADR-0279 and ADR-0219.C. Keep the copies across plugins; fix only the os-me internal duplicates.
Proposed tickets, in order:
eval/reply_shape/run.rbonto the os-me harness classes (LeverRun, AgreementScore, one flag-parsing owner); summary lines and TSV columns unchanged. No ruling needed.Filed from proposed ticket 1: #1422 (fold os-me eval/reply_shape/run.rb onto the os-me harness classes).
Ruling and design state for ticket 2 (one owner for the headless claude call), 2026-10-27, session c843bd27.
Jared ruled: option A, widened. Not a narrow
claude-headlessgem. A provider-neutral LLM gem on top of RubyLLM, so that adding a provider means adding one adapter.Proposed layering (not yet an ADR):
ruby_llm2.1.x (external): Anthropic API, OpenAI, Ollama, OpenAI-compatible endpoints such as Cloudflare Workers AI/ai/v1.llm(new, ours): provider profiles, credentials, aclaude_codeprovider (theclaude -pCLI), a price table for model IDs that RubyLLM does not know, and the decision-endpoint wire format. Entry points:LLM.chat,LLM.embed,LLM.decide.decision-model: keeps the meaning (Question, Answer, Reply, Confidence, CriteriaRules, Evaluation). It moves its transport (Http::Connection, CloudflareAccount, Wire::*, the SystemOne and CloudflareJev adapters, Price.jev) ontollm. Its publicClientinterface stays the same, because email-triage also calls it.evals: NOT created yet. decision-model ADR 0001 makes the eval CLI, config format and report.json public contracts, and Evaluation already covers runner, splits, frozen log, kappa and reports. Add reps x jobs, the keep/discard loop and a run ledger there. Extractevalsonly when the first eval that is not a decision eval needs it (likely ticket-takedown), with its own superseding ADR.Spike (scratchpad, throwaway), RubyLLM 2.1.1 on Ruby 4.0.2:
claude_codeprovider: verified. About 44 lines; it overridesProvider#complete, the same way ruby_llm-codex does. It made two real calls (plain, andwith_schemathrough--json-schema). Content must be a String, so structured output is JSON-encoded and read with.parsed.claude-haiku-5-5raises ModelNotFoundError unlessassume_model_exists: trueis set, and then it has no price. Sollmneeds its own price table.RubyLLM.context.Cost note:
claude -pwith a Max login bills against the subscription. The API bills per token, and the account also has a Max-plan API credit balance. Callers choose the provider per call. Do not assume the API is cheaper.Next: ADR through /os-adr:create (layering, ruby_llm dependency, gem names; Jared has not yet confirmed the names
llmandevals). Then thellmtracer bullet: Anthropic profile plus claude_code provider; move os-adrAdr::Judge::ClaudeCommandonto it. Then decision-model onllm.