Replace os-me HeadlessClaude with a chat-backed decide adapter #1471

Open
opened 2026-10-28 21:22:48 +00:00 by jared · 2 comments
Owner

Problem

OsMe::HeadlessClaude is a decision-model adapter that builds its own prompt, JSON schema, and RawReply.
It runs through OsMe::HeadlessRunner, which already calls LLM.chat on llm-stack (claude_code provider).
OsMe::HeadlessResult is glue that turns the chat reply into Decision::Model::Usage and raises Decision::Model::ResponseError.
cc-os docs/llm-stack/spec.md step 5 says to replace headless_claude.rb with a chat-backed decide adapter.
Today os-me keeps decision-model reply logic that belongs in decision-model.

Blocked by: jared/decision-model#11

Approach

Depends on the decision-model ticket "Move decision-model transport onto llm-stack", which adds a chat-backed adapter to decision-model. UNKNOWN: its class name and keywords, set by the decision-model ADR. The sketch below uses Decision::Model::Adapters::Chat.

Relationship to #1286 (open, "Switch os-me decision-model callers to Jev as the default adapter"):

  • #1286 changes the default adapter to CloudflareJev. It keeps HeadlessClaude as a comparison adapter.
  • This ticket changes how the Claude comparison adapter is built. It does not change the default.
  • Neither ticket blocks the other. Both touch plugins/os-me/lib/os_me/row_labels.rb (RowLabels.build). The ticket that lands second rebases.
  • After this ticket, #1286 must keep a Claude comparison adapter. It builds it from the chat-backed adapter, not from HeadlessClaude.

Scope: headless_claude.rb, headless_result.rb, and the wiring in labeler_client.rb and row_labels.rb.
UNKNOWN: whether headless_runner.rb stays. decision_moment.rb, moment_extractor.rb, and verdict_readback.rb also mention HeadlessClaude or HeadlessResult. Read each before you delete.

Required work:

  1. Delete the HeadlessClaude class — absent class HeadlessClaude in plugins/os-me/lib/os_me/*.rb
  2. Delete the HeadlessResult struct — absent HeadlessResult in plugins/os-me/lib/os_me/*.rb
  3. Build the Claude adapter from the decision-model chat-backed adapter in RowLabels.build — present Adapters::Chat in plugins/os-me/lib/os_me/row_labels.rb
  4. Pin the decision-model revision that has the chat-backed adapter — present decision-model in plugins/os-me/Gemfile.lock
  5. Delete the retired test file — absent HeadlessClaude in plugins/os-me/test/*.rb

Orchestrator work: bump the decision-model pin (ADR-0166) once D is released. Run bin/refresh-plugins.

Acceptance criteria

  1. Given RowLabels.build, when it runs with a fake chat response, then it returns source and wrong-cost labels as before.
  2. Given a chat reply with no structured answers, when the adapter is called, then it raises Decision::Model::ResponseError.
  3. Given a chat reply with token counts and cost, when the adapter is called, then the RawReply carries the same Usage values as before.
  4. Given the os-me suite, when it runs, then no test references OsMe::HeadlessClaude or OsMe::HeadlessResult.
  5. Given eval_adapter.rb, when this ticket closes, then it is unchanged.

Owner sketch

module OsMe
  module RowLabels
    def self.build(text)
      adapter = Decision::Model::Adapters::Chat.new(profile: :os_me_headless, instruction: INSTRUCTION)
      of(Row.new(text: text), client: LabelerClient.for(adapter))
    end
  end
end

The adapter class and its keywords come from the decision-model ADR.

Non-goals

  • plugins/os-me/lib/os_me/eval_adapter.rb. It is eval harness code. It builds SystemOne.ollama, CloudflareJev, and CloudflareAccount.
  • Switching the default adapter to Jev. That is #1286.
  • Moving Wire::QuestionEncoder out of decision-model.

Retired tests

plugins/os-me/test/headless_claude_test.rb

Origin

  • Trigger: cc-os session asked for llm-stack integration work; spec step 5 names headless_claude.rb.
  • Improvised this session: none
  • Chain: os-me adapts the claude CLI by hand ← decision-model has no chat-backed adapter ← DESIGN (cc-os ADR-0303, docs/llm-stack/spec.md step 5).
  • Root candidate: none found; searched HeadlessClaude, HeadlessResult, Jev, decision-model, llm-stack, chat-backed adapter

VERDICT: CREATE

  • Where: plugins/os-me/lib/os_me/headless_claude.rb
  • Session: 4924697c-7a72-4455-a9f0-ba97fc3f1c6d
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/4924697c-7a72-4455-a9f0-ba97fc3f1c6d.jsonl
## Problem `OsMe::HeadlessClaude` is a decision-model adapter that builds its own prompt, JSON schema, and `RawReply`. It runs through `OsMe::HeadlessRunner`, which already calls `LLM.chat` on `llm-stack` (`claude_code` provider). `OsMe::HeadlessResult` is glue that turns the chat reply into `Decision::Model::Usage` and raises `Decision::Model::ResponseError`. cc-os `docs/llm-stack/spec.md` step 5 says to replace `headless_claude.rb` with a chat-backed decide adapter. Today os-me keeps decision-model reply logic that belongs in decision-model. Blocked by: jared/decision-model#11 ## Approach Depends on the decision-model ticket "Move decision-model transport onto llm-stack", which adds a chat-backed adapter to decision-model. UNKNOWN: its class name and keywords, set by the decision-model ADR. The sketch below uses `Decision::Model::Adapters::Chat`. Relationship to #1286 (open, "Switch os-me decision-model callers to Jev as the default adapter"): - #1286 changes the default adapter to `CloudflareJev`. It keeps `HeadlessClaude` as a comparison adapter. - This ticket changes how the Claude comparison adapter is built. It does not change the default. - Neither ticket blocks the other. Both touch `plugins/os-me/lib/os_me/row_labels.rb` (`RowLabels.build`). The ticket that lands second rebases. - After this ticket, #1286 must keep a Claude comparison adapter. It builds it from the chat-backed adapter, not from `HeadlessClaude`. Scope: `headless_claude.rb`, `headless_result.rb`, and the wiring in `labeler_client.rb` and `row_labels.rb`. UNKNOWN: whether `headless_runner.rb` stays. `decision_moment.rb`, `moment_extractor.rb`, and `verdict_readback.rb` also mention `HeadlessClaude` or `HeadlessResult`. Read each before you delete. Required work: 1. Delete the `HeadlessClaude` class — absent `class HeadlessClaude` in `plugins/os-me/lib/os_me/*.rb` 2. Delete the `HeadlessResult` struct — absent `HeadlessResult` in `plugins/os-me/lib/os_me/*.rb` 3. Build the Claude adapter from the decision-model chat-backed adapter in `RowLabels.build` — present `Adapters::Chat` in `plugins/os-me/lib/os_me/row_labels.rb` 4. Pin the decision-model revision that has the chat-backed adapter — present `decision-model` in `plugins/os-me/Gemfile.lock` 5. Delete the retired test file — absent `HeadlessClaude` in `plugins/os-me/test/*.rb` Orchestrator work: bump the decision-model pin (ADR-0166) once D is released. Run `bin/refresh-plugins`. ## Acceptance criteria 1. Given `RowLabels.build`, when it runs with a fake chat response, then it returns source and wrong-cost labels as before. 2. Given a chat reply with no structured answers, when the adapter is called, then it raises `Decision::Model::ResponseError`. 3. Given a chat reply with token counts and cost, when the adapter is called, then the `RawReply` carries the same `Usage` values as before. 4. Given the os-me suite, when it runs, then no test references `OsMe::HeadlessClaude` or `OsMe::HeadlessResult`. 5. Given `eval_adapter.rb`, when this ticket closes, then it is unchanged. ## Owner sketch ```ruby module OsMe module RowLabels def self.build(text) adapter = Decision::Model::Adapters::Chat.new(profile: :os_me_headless, instruction: INSTRUCTION) of(Row.new(text: text), client: LabelerClient.for(adapter)) end end end ``` The adapter class and its keywords come from the decision-model ADR. ## Non-goals - `plugins/os-me/lib/os_me/eval_adapter.rb`. It is eval harness code. It builds `SystemOne.ollama`, `CloudflareJev`, and `CloudflareAccount`. - Switching the default adapter to Jev. That is #1286. - Moving `Wire::QuestionEncoder` out of decision-model. ## Retired tests plugins/os-me/test/headless_claude_test.rb ## Origin - Trigger: cc-os session asked for llm-stack integration work; spec step 5 names `headless_claude.rb`. - Improvised this session: none - Chain: os-me adapts the claude CLI by hand ← decision-model has no chat-backed adapter ← DESIGN (cc-os ADR-0303, `docs/llm-stack/spec.md` step 5). - Root candidate: none found; searched HeadlessClaude, HeadlessResult, Jev, decision-model, llm-stack, chat-backed adapter VERDICT: CREATE - Where: plugins/os-me/lib/os_me/headless_claude.rb - Session: 4924697c-7a72-4455-a9f0-ba97fc3f1c6d - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/4924697c-7a72-4455-a9f0-ba97fc3f1c6d.jsonl
Author
Owner

Note from decision-model#11 planning: the llm-stack claude_code profile, used through LLM::Client, may serve the HeadlessClaude replacement.
This ticket covers only the HeadlessClaude part.
The error-class and Gemfile.lock changes that decision-model#11 forces on os-me are in jared/cc-os#1474.

Note from decision-model#11 planning: the llm-stack `claude_code` profile, used through `LLM::Client`, may serve the HeadlessClaude replacement. This ticket covers only the HeadlessClaude part. The error-class and Gemfile.lock changes that decision-model#11 forces on os-me are in jared/cc-os#1474.
Author
Owner

decision-model#11 landed as 962bfe9 on main; decision-model now calls llm-stack LLM::Client#decide through Adapters::Decider.

decision-model#11 landed as 962bfe9 on main; decision-model now calls llm-stack `LLM::Client#decide` through `Adapters::Decider`.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jared/cc-os#1471
No description provided.