os-me: prediction records carry triggers, verdict, origin, and autopilot #1081

Closed
opened 2026-10-17 12:31:36 +00:00 by jared · 3 comments
Owner

Problem

The #1074 takedown design (docs/takedown/1074-20261017-082123/final-design.json) stores id, session_id, text, confidence, and depth per ruling. Its grade names the fields that Jared decided on 2026-10-17 and that are still missing: trigger ids from the Why bullets, verdict set by the learner, origin and autopilot supplied by the hook. Without them bin/os-me-report (#1077) cannot compute hit rate per trigger or accept rate by origin, and the learning gate (#1078) cannot tell autopilot accepts from discussed sessions.

Design

/home/jared/dev/cc-os/docs/takedown/1081-20261017-083611/final-design.json

Approach

PredictionLog owns the record field list and null-normalized reads, while PredictionParser extracts triggers and CLI supplies origin, autopilot, verdict and scored_at.

Required work:

  1. Write plugins/os-me/lib/os_me/prediction_log.rb
  2. Write plugins/os-me/lib/os_me/prediction_parser.rb
  3. Write plugins/os-me/lib/os_me/cli.rb
  4. Write test plugins/os-me/test/prediction_fields_test.rb

Acceptance criteria

  • Why: T11 and A5: triggers ["T11","A5"]
  • No Why line: triggers []
  • append without --origin/--autopilot: origin nil, autopilot nil
  • --autopilot false: autopilot false
  • --autopilot maybe: Thor rejects it
  • score-record ID --verdict hit: verdict hit, scored_at ISO8601, depth untouched
  • score-record ID REPLY --accept: depth as before, verdict untouched
  • score-record ID alone: error naming --verdict
  • Old row without the new fields: records returns nil for the five new fields

Owner sketch

Responsibility Owner
Field list, null defaults on read, score updates (depth, verdict, scored_at) PredictionLog
Trigger ids from Why lines PredictionParser
origin and autopilot at append, verdict at score CLI options
scored_at timestamp CLI (Time.now.utc)

plugins/os-me/test/prediction_fields_test.rb

# frozen_string_literal: true

require_relative 'test_helper'

class PredictionFieldsTest < Minitest::Test
  include TranscriptFixtures

  def setup
    @dir = Dir.mktmpdir
    @log = File.join(@dir, 'log.jsonl')
  end

  def teardown
    FileUtils.remove_entry(@dir)
  end

  def run_cli(*args)
    status = 0
    out, err = capture_io do
      OsMe::CLI.start([*args, '--log', @log])
    rescue SystemExit => e
      status = e.status
    end
    [status, out, err]
  end

  def append(text, *flags)
    path = write_transcript(@dir, 'proj', 'sess-1', [typed('q'), assistant(text)])
    run_cli('append', path, *flags)
  end

  def rows
    OsMe::PredictionLog.new(@log).records
  end

  def test_parser_extracts_triggers_from_why_bullets
    rows = OsMe::PredictionParser.new.parse("a. Accept the plan\n   - Why: T11 and A5 apply\nConfidence: 70%")

    assert_equal([%w[T11 A5]], rows.map { |row| row[:triggers] })
  end

  def test_append_stores_triggers
    append("a. Accept\n   - Why: T11 and A5\nb. Split\nConfidence: 70%")

    assert_equal([%w[T11 A5], []], rows.map { |row| row['triggers'] })
  end

  def test_append_stores_origin_and_autopilot
    append('Jared would accept.', '--origin', 'agent', '--autopilot', 'true')

    assert_equal 'agent', rows.first['origin']
    assert_equal true, rows.first['autopilot']
  end

  def test_append_stores_autopilot_false_as_boolean
    append('Jared would accept.', '--autopilot', 'false')

    assert_equal false, rows.first['autopilot']
  end

  def test_score_record_verdict_sets_verdict_and_scored_at
    append('Jared would accept.')
    run_cli('score-record', 'session:1', '--verdict', 'hit')

    assert_equal 'hit', rows.first['verdict']
    refute_nil rows.first['scored_at']
    assert_nil rows.first['depth']
  end

  def test_score_record_without_reply_or_verdict_fails
    append('Jared would accept.')
    status, _out, err = run_cli('score-record', 'session:1')

    refute_equal 0, status
    assert_includes err, '--verdict'
  end

  def test_score_record_rejects_unknown_verdict
    append('Jared would accept.')
    status, = run_cli('score-record', 'session:1', '--verdict', 'maybe')

    refute_equal 0, status
    assert_nil rows.first['verdict']
  end

  def test_record_without_new_fields_reads_back_null
    File.write(@log, "#{JSON.generate(id: 'session:a', session_id: 's', text: 't', confidence: 70, depth: nil)}\n")
    row = rows.first

    %w[triggers origin autopilot verdict scored_at].each { |field| assert_nil row[field], field }
    assert_equal 'session:a', row['id']
  end
end
## Problem The #1074 takedown design (docs/takedown/1074-20261017-082123/final-design.json) stores id, session_id, text, confidence, and depth per ruling. Its grade names the fields that Jared decided on 2026-10-17 and that are still missing: trigger ids from the Why bullets, verdict set by the learner, origin and autopilot supplied by the hook. Without them bin/os-me-report (#1077) cannot compute hit rate per trigger or accept rate by origin, and the learning gate (#1078) cannot tell autopilot accepts from discussed sessions. ## Design /home/jared/dev/cc-os/docs/takedown/1081-20261017-083611/final-design.json ## Approach PredictionLog owns the record field list and null-normalized reads, while PredictionParser extracts triggers and CLI supplies origin, autopilot, verdict and scored_at. Required work: 1. Write `plugins/os-me/lib/os_me/prediction_log.rb` 2. Write `plugins/os-me/lib/os_me/prediction_parser.rb` 3. Write `plugins/os-me/lib/os_me/cli.rb` 4. Write test `plugins/os-me/test/prediction_fields_test.rb` ## Acceptance criteria - Why: T11 and A5: triggers ["T11","A5"] - No Why line: triggers [] - append without --origin/--autopilot: origin nil, autopilot nil - --autopilot false: autopilot false - --autopilot maybe: Thor rejects it - score-record ID --verdict hit: verdict hit, scored_at ISO8601, depth untouched - score-record ID REPLY --accept: depth as before, verdict untouched - score-record ID alone: error naming --verdict - Old row without the new fields: records returns nil for the five new fields ## Owner sketch | Responsibility | Owner | |---|---| | Field list, null defaults on read, score updates (depth, verdict, scored_at) | PredictionLog | | Trigger ids from Why lines | PredictionParser | | origin and autopilot at append, verdict at score | CLI options | | scored_at timestamp | CLI (Time.now.utc) | `plugins/os-me/test/prediction_fields_test.rb` ```ruby # frozen_string_literal: true require_relative 'test_helper' class PredictionFieldsTest < Minitest::Test include TranscriptFixtures def setup @dir = Dir.mktmpdir @log = File.join(@dir, 'log.jsonl') end def teardown FileUtils.remove_entry(@dir) end def run_cli(*args) status = 0 out, err = capture_io do OsMe::CLI.start([*args, '--log', @log]) rescue SystemExit => e status = e.status end [status, out, err] end def append(text, *flags) path = write_transcript(@dir, 'proj', 'sess-1', [typed('q'), assistant(text)]) run_cli('append', path, *flags) end def rows OsMe::PredictionLog.new(@log).records end def test_parser_extracts_triggers_from_why_bullets rows = OsMe::PredictionParser.new.parse("a. Accept the plan\n - Why: T11 and A5 apply\nConfidence: 70%") assert_equal([%w[T11 A5]], rows.map { |row| row[:triggers] }) end def test_append_stores_triggers append("a. Accept\n - Why: T11 and A5\nb. Split\nConfidence: 70%") assert_equal([%w[T11 A5], []], rows.map { |row| row['triggers'] }) end def test_append_stores_origin_and_autopilot append('Jared would accept.', '--origin', 'agent', '--autopilot', 'true') assert_equal 'agent', rows.first['origin'] assert_equal true, rows.first['autopilot'] end def test_append_stores_autopilot_false_as_boolean append('Jared would accept.', '--autopilot', 'false') assert_equal false, rows.first['autopilot'] end def test_score_record_verdict_sets_verdict_and_scored_at append('Jared would accept.') run_cli('score-record', 'session:1', '--verdict', 'hit') assert_equal 'hit', rows.first['verdict'] refute_nil rows.first['scored_at'] assert_nil rows.first['depth'] end def test_score_record_without_reply_or_verdict_fails append('Jared would accept.') status, _out, err = run_cli('score-record', 'session:1') refute_equal 0, status assert_includes err, '--verdict' end def test_score_record_rejects_unknown_verdict append('Jared would accept.') status, = run_cli('score-record', 'session:1', '--verdict', 'maybe') refute_equal 0, status assert_nil rows.first['verdict'] end def test_record_without_new_fields_reads_back_null File.write(@log, "#{JSON.generate(id: 'session:a', session_id: 's', text: 't', confidence: 70, depth: nil)}\n") row = rows.first %w[triggers origin autopilot verdict scored_at].each { |field| assert_nil row[field], field } assert_equal 'session:a', row['id'] end end ```
Author
Owner

Lens challenge on the #1074 merge (A3): the record shape has no owner. PredictionParser builds id/text/confidence with symbol keys, cli.rb merges session_id and depth, PredictionLog reads string keys and decides matches in matching_indexes. When adding triggers, verdict, origin, and autopilot, give the record one owner (an OsMe::Prediction record with matches?(id, session_id) and one key shape) instead of spreading more keys across three files.

Lens challenge on the #1074 merge (A3): the record shape has no owner. PredictionParser builds id/text/confidence with symbol keys, cli.rb merges session_id and depth, PredictionLog reads string keys and decides matches in matching_indexes. When adding triggers, verdict, origin, and autopilot, give the record one owner (an OsMe::Prediction record with matches?(id, session_id) and one key shape) instead of spreading more keys across three files.
Author
Owner

Work started on branch ticket-1081 (os-sdlc takedown-implement).

Work started on branch ticket-1081 (os-sdlc takedown-implement).
Author
Owner

Resolution

Done: Prediction log owns the field list with null-normalized reads; parser extracts triggers; CLI supplies origin, autopilot, verdict, scored_at.

Evidence: Implementation 54 on takedown-implement: design placed, behavior-verifier pass (9/9 criteria), suite pass, contract-auditor pass. Commit aa74938b on ticket-1081.

Follow-ups: none

## Resolution **Done:** Prediction log owns the field list with null-normalized reads; parser extracts triggers; CLI supplies origin, autopilot, verdict, scored_at. **Evidence:** Implementation 54 on takedown-implement: design placed, behavior-verifier pass (9/9 criteria), suite pass, contract-auditor pass. Commit aa74938b on ticket-1081. **Follow-ups:** none
jared closed this issue 2026-10-17 13:19:14 +00:00
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#1081
No description provided.