decision-system S8b: move os-me callers to Decision::Model and delete the port #1266
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#1266
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
After S8, os-me loads the gem
decision-model(moduleDecision::Model) and has the gem adapterOsMe::HeadlessClaude. But the callers still use the in-plugin portOsMe::DecisionModel:ReplyShape,AnswerSource,WrongCost::Assessor,RowLabels, andMomentExtractor. Each caller puts its criteria into one prompt text and asks one question per call. The port cannot send typed questions, it does not apply the criteria rules, and it gives no probabilities or confidence. Two live criteria texts break the gemCriteriaRules, whichQuestionSetapplies by default:ReplyShapeacceptandmodifycontain "not", and theAnswerSourcetastetext names the optionrule. This ticket is the second of two S8 tickets.Approach
Each caller takes a
Decision::Model::Clientand asks gemQuestionobjects. Each caller reads the answer from theReplyby key.askcallsReplyShape#classify(pair){ 'memo' => pair.ai_text, 'reply' => pair.reply_text }Question.choice(:shape, criteria: CRITERIA, instructions: INSTRUCTION)reply.fetch(:shape).choice(a String)AnswerSource#classify(row)row.textQuestion.choice(:source, criteria: CRITERIA, instructions: 'Which source can answer this row?')Result.new(source: reply.fetch(:source).choice)(a Symbol, from the Symbol criteria keys)WrongCost::Assessor#assess(row)row.textQuestion.noul(key, instructions: INSTRUCTIONS[key])forhard_to_undo,compounds,stays_hiddenWrongCost.new(**keys.to_h { [it, reply.fetch(it).choice] })ReplyShape::INSTRUCTION,ReplyShape::CRITERIA,AnswerSource::CRITERIA, andWrongCost::Assessor::INSTRUCTIONSkeep their text unchanged. Ruling (2026-10-23): os-me keeps its measured criteria texts verbatim and builds its Claude-backedDecision::Model::Clientwithrules: Decision::Model::CriteriaRules::Off, one setting for all os-me questions, in the one place os-me builds its Client. The gem rule targets the small local model's literal reading of negated criteria (gem register D-6); the Sonnet labeler passed the S2 gate with these texts (83.3% on the frozen 50). Put this reason in the commit message and the History row, not in a code comment.RowLabels.buildandMomentExtractor.buildmakeDecision::Model::Client.new(OsMe::HeadlessClaude.new(runner: HeadlessRunner.new), rules: Decision::Model::CriteriaRules::Off).MomentExtractor.buildpassesmodel: LABELER_MODEL. Thusos-me-routemakes twoclaudecalls, not four.DecisionMoment::LABELERdoes not change.Delete
plugins/os-me/lib/os_me/decision_model.rband the directoryplugins/os-me/lib/os_me/decision_model/(Question,Answer,Client,CriteriaRules,HeadlessClaude,UnreadableAnswer). Tests that remain useDecision::Model::Adapters::Fixed, or a small recorder that wraps it, in place of the old fake adapters.Required work:
:shapechoice question through the gem inReplyShape— check: presentDecision::Model::Question.choice(:shapeinplugins/os-me/lib/os_me/reply_shape.rb:sourcechoice question through the gem inAnswerSource— check: presentDecision::Model::Question.choice(:sourceinplugins/os-me/lib/os_me/answer_source.rbWrongCost::Assessor— check: presentDecision::Model::Question.noulinplugins/os-me/lib/os_me/wrong_cost/assessor.rbOsMe::HeadlessClaudeinRowLabelsandMomentExtractor— check: presentDecision::Model::Client.newinplugins/os-me/lib/os_me/*.rbDecision::Model::Adapters::Fixedinplugins/os-me/test/*.rbAcceptance criteria
Decision::Model::Adapters::Fixed.new({ shape: { choice: 'modify', probabilities: { 'accept' => 0.1, 'modify' => 0.8, 'override' => 0.05, 'none' => 0.05 } } }), whenReplyShape.new(client: Decision::Model::Client.new(adapter)).classify(pair)runs, then it returns"modify".call(state:, questions:)arguments and returns theFixedreply of criterion 1, and a pair withai_text'MEMO-TEXT'andreply_text'REPLY-TEXT', whenclassifyruns, then the adapter gets one call withstate{ 'memo' => 'MEMO-TEXT', 'reply' => 'REPLY-TEXT' }and one question withkey:shape,type:choice,criteria.keys['accept', 'modify', 'override', 'none'],criteriaequal toReplyShape::CRITERIA, andinstructionsequal toReplyShape::INSTRUCTION.Decision::Model::Adapters::Fixed.new({ source: { choice: 'facts', probabilities: { 'rule' => 0.1, 'precedent' => 0.1, 'facts' => 0.6, 'experiment' => 0.1, 'taste' => 0.1 } } }), whenAnswerSource.new(Decision::Model::Client.new(adapter)).classify(OsMe::Row.new(text: 'row'))runs, then the result hassource:facts,max_rung2, andnovel?false. With the recording adapter and the row textMove Gate::StepOutcome to OsSdlc::StepOutcome.,classifymakes the adapter getstateequal to that text and one question withkey:source,type:choice,criteria.keys[:rule, :precedent, :facts, :experiment, :taste], andinstructionsWhich source can answer this row?.Decision::Model::Adapters::Fixed.new({ hard_to_undo: { noul: 0.9 }, compounds: { noul: 0.2 }, stays_hidden: { noul: 0.5 } }), whenWrongCost::Assessor#assess(OsMe::Row.new(text: 'row'))runs, then it returnsWrongCost.new(hard_to_undo: true, compounds: false, stays_hidden: true).WrongCost::Assessor#assessruns, then the adapter gets one call withstate'row'and three questions with keys[:hard_to_undo, :compounds, :stays_hidden], eachtype:noulandinstructionsWrongCost::Assessor::INSTRUCTIONS[key].RowLabels.buildandMomentExtractor.buildmake, when its rules are read, then they areDecision::Model::CriteriaRules::Off.Decision::Model::Client.new(adapter, rules: Decision::Model::CriteriaRules::Off)with the recording adapter andAdapters::Fixedreply of criterion 1, whenReplyShapeclassifies a pair, then the question set builds withoutDecision::Model::QuestionError, and the criteria sent equalReplyShape::CRITERIAunchanged, including theacceptandmodifytexts that contain "not".claudethat replies first{ answers: { source: { choice: 'facts', probabilities: { rule: 0.1, precedent: 0.1, facts: 0.6, experiment: 0.1, taste: 0.1 } } } }, then{ answers: { hard_to_undo: { noul: 0.1 }, compounds: { noul: 0.9 }, stays_hidden: { noul: 0.1 } } }, whenos-me-route ROW_FILEruns, then stdout is{"source":"facts","max_rung":2,"novel":false,"wrong_cost":{"hard_to_undo":false,"compounds":true,"stays_hidden":false},"level":"medium","required_band":70}plus a newline, the exit status is 0, each of the two prompts includes the row text, and no thirdclaudecall occurs.claudewhose second reply has the text**none**and no structured output, whenMomentExtractor.build(...).runruns, then it raisesDecision::Model::ResponseError, only the first session is marked read, and the firstclaudecall has--model sonnet.grep -rn 'DecisionModel' plugins/os-me/lib plugins/os-me/testruns, then it prints nothing, andplugins/os-me/lib/os_me/decision_model.rbandplugins/os-me/lib/os_me/decision_model/do not exist.Owner sketch
Retired tests
plugins/os-me/test/decision_model_test.rb
plugins/os-me/test/structured_answer_test.rb
Non-goals
OsMe::HeadlessClaude(S8) or to the gem.confidence,probabilities,Reply#usage, orReply#latencyin os-me records. The callers readchoiceonly.DecisionMoment::LABELER.Blocked by: #1250 (gem dependency and
OsMe::HeadlessClaude).Origin
DESIGN(docs/decision-system/build-plan.mdS8; gemdocs/design/core.md).VERDICT: CREATE (user-approved; backlog search only)
Work started on branch ticket-1266 (os-sdlc implement).
Resolution
Done: os-me callers ask typed Decision::Model questions; port OsMe::DecisionModel deleted; OsMe::LabelerClient.for builds every Client with CriteriaRules::Off; ReplyShape eval moved to the gem
Evidence: Hand-finished after escalation 67 (implementation 134, test fixture defect): main
b33ade05,e4464c7e,69ebc806; os-me suite 250 runs 0 failures on main; rubocop clean; opus design audit pass, F1-F3 fixedFollow-ups: #1274 S8 gate adapter comparison; #1275 verifier brief omits staged changes; guard refusing subagent-stop recorded as occurrence on #1096