decision-system S7: more moment kinds #1249
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#1249
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
S2 reads only memo and reply moments. Before S7b can store question-answer, correction, direction, and over-escalation moments, os-me needs two objects that do not exist:
Latency.of(pair)measures the time from the AI message to the reply, and that time includes the time when Jared is AFK. The spike measured a median of 426 s and a p90 of 1,696 s. Because of the AFK time,latency_sis not a valid ping-cost signal (docs/decision-system/learning-loops.md: "only replies within one active stretch").Approach
OsMe::MomentKindfollows the shape ofOsMe::ReplyShape: one typed gem question,Decision::Model::Question.choice(:kind, criteria: CRITERIA, instructions: INSTRUCTION), asked through the client that the caller gives (OsMe::LabelerClient.for(adapter)). The state is{ 'ai' => pair.ai_text, 'reply' => pair.reply_text }.classify(pair)returns the choice as a String. The options are, in this order:question_answer,correction,direction,over_escalation,none.nonemeans "no decision moment".memo_replyis not an option, becauseMemoDetectorfinds memos by text beforeMomentKindruns (docs/decision-system/extraction.md, pipeline diagram).OsMe::ActiveStretchkeeps a latency only when the reply comes in the same active stretch as the AI message:seconds <= IDLE_GAP_SwithIDLE_GAP_S = 900. Out of the stretch,filterreturns the sameLatencywithseconds: nil.reply_charsstays.DecisionMoment.memo_replyapplies the filter, solatency_sis null in each new moment with a gap over 900 s. The record keepsai_atandreply_at, so a later change of the gap can compute the latency again from the record.This ticket does not connect
MomentKindtoMomentExtractor. S7b does that.Required work:
MomentKindwith the five options and the criteria in the owner sketch — check: presentclass MomentKindinplugins/os-me/lib/os_me/moment_kind.rbActiveStretchlatency filter with a 900 s idle gap — check: presentIDLE_GAP_S = 900inplugins/os-me/lib/os_me/active_stretch.rbDecisionMoment.memo_reply— check: presentActiveStretchinplugins/os-me/lib/os_me/decision_moment.rbFixture pairs for the tests (AI text, then reply text):
question_answerShould the hook run on resume as well as on startup?Only on startup.correctionI renamed config.rb to settings.rb and updated the callers.No, revert that rename. config.rb is the name the docs use.directionThe tests pass. Next I will read the cursor code.Before that, add a budget flag to the CLI.over_escalationShould I name the constant IDLE_GAP_S or IDLE_GAP_SECONDS?You decide. Do not ask me about names.noneDone. All 42 tests pass.thanksAcceptance criteria
Decision::Model::Adapters::Fixedadapter (throughOsMe::LabelerClient.for) that answerskindwith that pair's kind, whenOsMe::MomentKind.new(client:).classify(pair)runs, then it returns that kind as a String ('question_answer','correction','direction','over_escalation','none').correctionfixture pair and theRecordingAdapterfromplugins/os-me/test/recording_adapter.rb, whenclassifyruns, then the adapter records exactly one call with exactly one question, whosekeyis:kind, whosetypeis:choice, and whoseoptionsare['question_answer', 'correction', 'direction', 'over_escalation', 'none']in that order.correctionfixture pair and theRecordingAdapter, whenclassifyruns, then the recorded state is{ 'ai' => 'I renamed config.rb to settings.rb and updated the callers.', 'reply' => 'No, revert that rename. config.rb is the name the docs use.' }.OsMe::MomentKind::KINDS, when it is read, then it equals['question_answer', 'correction', 'direction', 'over_escalation'](the options withoutnone).OsMe::Latency.new(seconds: 900, reply_chars: 5)andOsMe::Latency.new(seconds: 901, reply_chars: 5), whenOsMe::ActiveStretch.new.active?runs on each, then it returnstruefor 900 andfalsefor 901.OsMe::Latency.new(seconds: 901, reply_chars: 5), whenOsMe::ActiveStretch.new.filterruns, then it returnsOsMe::Latency.new(seconds: nil, reply_chars: 5); givenseconds: 900, it returns the latency unchanged.Pairwithai_at2026-10-01T10:00:00Z,reply_at2026-10-01T10:14:00Z, and reply textyes, go ahead, whenOsMe::DecisionMoment.memo_replybuilds the moment, thenlatency_sis840andreply_charsis13.reply_at2026-10-01T12:00:00Z, whenOsMe::DecisionMoment.memo_replybuilds the moment, thenlatency_sisnil,reply_charsis13,ai_atis2026-10-01T10:00:00Z, andreply_atis2026-10-01T12:00:00Z.2026-10-01T10:00:00Z, replyyes, go aheadat2026-10-01T12:00:00Z) and a fakeclaudeonPATHthat answers shapeaccept, whenos-me-extract --store <store> --transcript-root <root> --budget 20runs, then it exits 0 andmoments.jsonlholds one row withkindmemo_reply,latency_snull, andreply_chars13.Owner sketch
Non-goals
MomentKindtoMomentExtractor, the moment consumers, and budget counts for the new calls: S7b.MomentKindcriteria. The S7 gate (an agent checks a sample of each kind; 80% agreement) is orchestrator work after S7b.moments.jsonl.Origin
DESIGN(docs/decision-system/build-plan.mdS7).VERDICT: CREATE (user-approved; backlog search only)
Work started on branch ticket-1249 (os-sdlc implement, S7a).
Resolution
Done: S7a: OsMe::MomentKind (gem choice question, four kinds plus none) and OsMe::ActiveStretch (900 s idle gap); DecisionMoment.memo_reply nulls latency_s outside an active stretch and keeps ai_at and reply_at
Evidence: implementation 137 merge-ready, 10 dispatches (one test-review fix round); suite and lint gates pass; commit on ticket-1249 touches moment_kind.rb, active_stretch.rb, decision_moment.rb and four tests
Follow-ups: S7b (wire MomentKind into the extractor, write the four kinds) filed after merge with a per-call budget ruling; S7 gate (80% agreement per kind) runs after S7b