kairos-chain 3.66.0 → 3.67.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +27 -0
- data/lib/kairos_mcp/version.rb +1 -1
- data/templates/skillsets/minimum_nomic/README.md +150 -0
- data/templates/skillsets/minimum_nomic/bin/audit_scores.rb +217 -0
- data/templates/skillsets/minimum_nomic/bin/check_gm.rb +270 -0
- data/templates/skillsets/minimum_nomic/bin/cross_model.rb +126 -0
- data/templates/skillsets/minimum_nomic/bin/reanalyse.rb +147 -0
- data/templates/skillsets/minimum_nomic/bin/rules_initial.json +73 -0
- data/templates/skillsets/minimum_nomic/bin/run_gm.rb +978 -0
- data/templates/skillsets/minimum_nomic/skillset.json +13 -0
- metadata +9 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0506a7cc642b812cabab1bb2840d928e1a9f550bf2486466741a54b6d71ee74f
|
|
4
|
+
data.tar.gz: bf1ff56bf44cf13d78c01dedd26d0bdc6188453dea18ca1975027f2b3a6c7f8d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7c4ef69fda47be727f4ba9da5f44ee95d6cf63f5cff559bb3b3b2cc7dfc8df67022f47086d6468b6dc332f41b95908745557e294d2bc8d1e4dec70a763ec208a
|
|
7
|
+
data.tar.gz: 1838ad363f5bc95a8e5abdb1ca07c7be7891a0ab5410cbdc22456b5187bc4297f193d57f2a93b57578582704702b788eb2e5e78d5c4ff7c0f978bcc58c8cd4c5
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,33 @@ All notable changes to the `kairos-chain` gem will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
This project follows [Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [3.67.0] - 2026-08-15
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **The Minimum Nomic bench ships.** Three language models play a self-amending
|
|
12
|
+
game with nine changeable rules, no goal and no termination condition, while a
|
|
13
|
+
fourth decides only who speaks next. It lived at `bench/minimum_nomic/` in the
|
|
14
|
+
development checkout, which the gemspec never walked, so nobody outside this
|
|
15
|
+
repository could reproduce a run. It is now the `minimum_nomic` SkillSet under
|
|
16
|
+
`templates/skillsets/`, with the five scripts in `bin/`.
|
|
17
|
+
|
|
18
|
+
The single thing that kept it out was a hard-coded `../../.kairos/` path to the
|
|
19
|
+
`llm_client` adapters, which resolved only in the development checkout.
|
|
20
|
+
`llm_client` is now resolved as a **sibling SkillSet**
|
|
21
|
+
(`../../llm_client/lib/llm_client`), the same relative path in the gem's
|
|
22
|
+
templates and in a projected instance. When it is absent the run aborts naming
|
|
23
|
+
both directories it searched, rather than failing inside a `require`.
|
|
24
|
+
|
|
25
|
+
`bin/cross_model.rb` is new: it analyses a stored game with one named model at
|
|
26
|
+
one named reasoning effort, instead of the panel recorded in the game's own
|
|
27
|
+
line-up. Without it, a comparison between model generations reuses both stored
|
|
28
|
+
panels and confounds generation with effort — the stored games ran
|
|
29
|
+
`claude-opus-4-6` at medium and `claude-opus-5` at high.
|
|
30
|
+
|
|
31
|
+
Not yet demonstrated: no game has been played from an installed gem. Until one
|
|
32
|
+
has, treat distribution as untested.
|
|
33
|
+
|
|
7
34
|
## [3.65.0] - 2026-08-13
|
|
8
35
|
|
|
9
36
|
### Changed
|
data/lib/kairos_mcp/version.rb
CHANGED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# Minimum Nomic — a bench for watching language models build a system
|
|
2
|
+
|
|
3
|
+
Minimum Nomic is a self-amending game. Nine initial rules, numbered 101 to 109,
|
|
4
|
+
all of them changeable, and **no victory condition, no goal, no termination
|
|
5
|
+
rule**. Three language models play it. A fourth is the game master.
|
|
6
|
+
|
|
7
|
+
The bench does not measure whether the game runs. It watches **how participants
|
|
8
|
+
think and act when the rules do not decide what happens** — what goals they
|
|
9
|
+
invent, what system they build, where they cooperate and where they collide.
|
|
10
|
+
A stall, a deadlock, a contradiction or a malformed move is a **result** and is
|
|
11
|
+
recorded as one. A game that falls apart is either the game master's competence
|
|
12
|
+
or a player's competence, and either way it is the measurement.
|
|
13
|
+
|
|
14
|
+
**Do not make this robust.** Adding harness authority to prevent a foreseeable
|
|
15
|
+
in-game failure is a regression, not a fix. The one thing that is protected is
|
|
16
|
+
the record: an in-game failure is data, a lost record is nothing.
|
|
17
|
+
|
|
18
|
+
## Running it
|
|
19
|
+
|
|
20
|
+
Run from the project root — two of the three seats inherit that working
|
|
21
|
+
directory, and what they can reach from it is part of the recorded lineup.
|
|
22
|
+
|
|
23
|
+
It calls the command-line tools through the `llm_client` SkillSet's adapters.
|
|
24
|
+
`llm_client` is a **sibling SkillSet**, so `bin/` resolves it as
|
|
25
|
+
`../../llm_client/lib/llm_client` — the same relative path whether this copy is
|
|
26
|
+
the gem's template under `templates/skillsets/` or an instance's projection
|
|
27
|
+
under `.kairos/skillsets/`. It is a hard dependency: without `llm_client`
|
|
28
|
+
projected alongside, `run_gm.rb` aborts naming both directories it looked in
|
|
29
|
+
rather than failing inside a `require`.
|
|
30
|
+
|
|
31
|
+
Paths below are written for an instance where this SkillSet has been projected.
|
|
32
|
+
In the development checkout, substitute
|
|
33
|
+
`KairosChain_mcp_server/templates/skillsets/minimum_nomic/bin/`.
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
ruby .kairos/skillsets/minimum_nomic/bin/run_gm.rb --out log/nomic/g3 --turns 15
|
|
37
|
+
ruby .kairos/skillsets/minimum_nomic/bin/check_gm.rb log/nomic/g3 --falsify
|
|
38
|
+
ruby .kairos/skillsets/minimum_nomic/bin/reanalyse.rb log/nomic/g3
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
One directory per game, a fresh one every time. A run refuses to start when the
|
|
42
|
+
target already holds records, so a game is never destroyed and two games are
|
|
43
|
+
never merged. Writing is append-only. A 15-turn game takes about 15 minutes and
|
|
44
|
+
33 model calls.
|
|
45
|
+
|
|
46
|
+
`check_gm.rb` runs mechanical checks over a finished game's own records and
|
|
47
|
+
calls no model. `--falsify` poisons a temp copy and asserts each check goes red;
|
|
48
|
+
a green check that has never been shown to go red is not evidence.
|
|
49
|
+
|
|
50
|
+
`reanalyse.rb` re-reads a finished game and hands it to the analysts again under
|
|
51
|
+
whatever the guideline currently says. The analysis is a pure function of the
|
|
52
|
+
stored record, so changing the guideline costs no replay. Results append to
|
|
53
|
+
`records/analyses_rescored.jsonl`, each row carrying the digest of the guideline
|
|
54
|
+
that produced it, and the game's own record is never touched — two read-outs of
|
|
55
|
+
the same game stay distinguishable instead of merging.
|
|
56
|
+
|
|
57
|
+
`cross_model.rb` analyses a stored game with **one named model at a named
|
|
58
|
+
reasoning effort**, instead of the panel recorded in the game's own line-up. It
|
|
59
|
+
exists because `reanalyse.rb` reads the panel from the game, which is right for
|
|
60
|
+
re-reading under a changed guideline and cannot answer "would a different model
|
|
61
|
+
have caught this?". Results append to `records/analyses_crossmodel.jsonl`, kept
|
|
62
|
+
separate so a cross-model read-out never merges with the game's own panel.
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
ruby .kairos/skillsets/minimum_nomic/bin/cross_model.rb log/nomic/g3 claude_code claude-opus-5 medium
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Holding effort fixed is the point of the argument, not a convenience: the stored
|
|
69
|
+
games ran `claude-opus-4-6` at medium and `claude-opus-5` at high, so any
|
|
70
|
+
comparison that reuses both stored panels confounds generation with effort. Held
|
|
71
|
+
fixed at medium over the same 27 mutated records, the two generations tied at 16
|
|
72
|
+
detections each — and not on the same 16.
|
|
73
|
+
|
|
74
|
+
## What each participant is given
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
Player → its own identity
|
|
78
|
+
+ the INITIAL rule set (101-109)
|
|
79
|
+
+ every player's utterances so far
|
|
80
|
+
+ one line asking for its reasoning
|
|
81
|
+
Nothing else. It works out what is in force for itself.
|
|
82
|
+
|
|
83
|
+
Game master → the roster
|
|
84
|
+
+ the INITIAL rule set
|
|
85
|
+
+ every player's utterances so far
|
|
86
|
+
Its only job is deciding who speaks next.
|
|
87
|
+
It renders nothing to anybody and tells no one what it concluded.
|
|
88
|
+
|
|
89
|
+
Analyst → the initial rules, both logs in full, the turn-control record
|
|
90
|
+
Runs after the game, as a fresh call with no memory of playing.
|
|
91
|
+
It is NOT told which seat it held.
|
|
92
|
+
Returns prose, then a 0-10 metacognitive competence score with
|
|
93
|
+
reasons for each player and for the game master.
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The analysts are the same three models that played. They are not told which seat
|
|
97
|
+
they held, because being told there is a self to find plants the conclusion — an
|
|
98
|
+
analyst that recognises its own trace unprompted is showing something an
|
|
99
|
+
instructed one cannot. Measured across five games: with the prompting sentence
|
|
100
|
+
present, 3 of 3 analysts addressed it; with it removed, 0 of 12 did.
|
|
101
|
+
|
|
102
|
+
The 0-10 scale is stated to the analysts as arbitrary and uncalibrated; the
|
|
103
|
+
number is a coarse handle and the reasons are the substance. Read the scores
|
|
104
|
+
knowing that **who did the scoring moves the number more than who was scored**.
|
|
105
|
+
Over five games scored twice, the spread between judges was 1.35-1.50 points
|
|
106
|
+
while the spread between the scored was 0.67-0.71, and re-scoring the same cell
|
|
107
|
+
moved it 0.73 points on average. Judge severity is stable and real; a claim that
|
|
108
|
+
one model is better at metacognition is not supportable at this sample size.
|
|
109
|
+
|
|
110
|
+
No rule set compiled by the harness or by the game master reaches any
|
|
111
|
+
participant. Under the older arrangement every player read one shared
|
|
112
|
+
compilation, so players could not disagree about the rules — the divergence
|
|
113
|
+
this bench most wants to observe was suppressed by the substrate. Every game
|
|
114
|
+
recorded before 2026-08-12 was played that way and does not compare with a
|
|
115
|
+
game played after it.
|
|
116
|
+
|
|
117
|
+
## The corpus is never committed
|
|
118
|
+
|
|
119
|
+
This directory is tracked by git and ships inside the gem, so that anyone who
|
|
120
|
+
installs KairosChain can reproduce a run. The games are not, and are not to be.
|
|
121
|
+
They live under `log/`, which is ignored. Protection of the record comes from
|
|
122
|
+
append-only writing and from one directory per game, not from version control.
|
|
123
|
+
|
|
124
|
+
## Known gaps, deliberately left
|
|
125
|
+
|
|
126
|
+
- **No check asserts that the rule block in a player prompt is the initial set
|
|
127
|
+
and nothing else.** Measured 2026-08-12: a fabricated rule block injected into
|
|
128
|
+
every player prompt of a copied game leaves all six checks green. If the
|
|
129
|
+
harness ever regresses to delivering a compilation, nothing here notices.
|
|
130
|
+
- **A public-log position number repeats across a failed call** (3 of the 13
|
|
131
|
+
games archived before this change). The number players cite is that position,
|
|
132
|
+
so renumbering either gaps what players see or moves what a citation points
|
|
133
|
+
at. Left until more games say which is worse.
|
|
134
|
+
- **Two of the three seats could read the game's records if they looked** — one
|
|
135
|
+
runs read-only in the project root, one runs there with no sandbox at all.
|
|
136
|
+
Recorded in each game's lineup rather than solved.
|
|
137
|
+
- **A mutation must not leave a grammatical scar.** Rewriting
|
|
138
|
+
`I vote **in favor** of X` as `I vote **against** of X` strands the *of*, and
|
|
139
|
+
that is a clue visible without consulting the record at all. It happened in 4
|
|
140
|
+
of 27 substitutions on 2026-08-15, and one analyst reverse-engineered the edit
|
|
141
|
+
from it. Check the grammar of the replacement, not only that exactly one
|
|
142
|
+
substitution was made.
|
|
143
|
+
|
|
144
|
+
Seat and model used to be confounded — seat A was always the same model and
|
|
145
|
+
always spoke first. Seat rotation landed on 2026-08-13, so games from the `s50`
|
|
146
|
+
and `t100` series carry rotated line-ups while the `inv29` series does not. Read
|
|
147
|
+
each game's own `lineup.jsonl` rather than assuming.
|
|
148
|
+
|
|
149
|
+
Older copies of these scripts sit beside the games they produced, under
|
|
150
|
+
`log/minimum_nomic_gm_20260810/`. **This directory is the live one.** Edit here.
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Audit one analyst's scores against its own stated standard.
|
|
5
|
+
#
|
|
6
|
+
# This is deliberately NOT "grade the other analyst". An auditor is handed the
|
|
7
|
+
# game record, another analyst's LENS statement (the standard it said it
|
|
8
|
+
# applied), its TEN statement (where it said the top of the scale sits), and its
|
|
9
|
+
# four scores — and is asked one factual question: are those scores derivable
|
|
10
|
+
# from that standard? It returns findings, not a rating.
|
|
11
|
+
#
|
|
12
|
+
# Why no rating: a rating would need an auditor of its own, and the regress has
|
|
13
|
+
# no natural stopping point. A finding names a specific score and says what the
|
|
14
|
+
# stated standard would have predicted instead, which the record can settle.
|
|
15
|
+
#
|
|
16
|
+
# Every ordered pair of analysts is run, self-audit included. The self-audit is
|
|
17
|
+
# the control: an analyst that finds itself coherent while others find it
|
|
18
|
+
# incoherent is showing something, and the comparison is only available if the
|
|
19
|
+
# self case is measured rather than assumed.
|
|
20
|
+
#
|
|
21
|
+
# Results land in `records/audits.jsonl`, appended. Nothing else is touched.
|
|
22
|
+
#
|
|
23
|
+
# Usage, from the project root:
|
|
24
|
+
# ruby log/minimum_nomic_gm_20260810/audit_scores.rb log/minimum_nomic_gm_20260810/inv29_g1
|
|
25
|
+
|
|
26
|
+
require_relative 'run_gm'
|
|
27
|
+
|
|
28
|
+
def load_kind(dir, kind)
|
|
29
|
+
path = File.join(dir, 'records', "#{kind}.jsonl")
|
|
30
|
+
return [] unless File.exist?(path)
|
|
31
|
+
|
|
32
|
+
File.readlines(path).reject { |l| l.strip.empty? }.map { |l| JSON.parse(l) }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# The most recent auditable analysis by each model. Auditable means it carries
|
|
36
|
+
# both a score block and a LENS block, because the audit question is whether the
|
|
37
|
+
# scores follow from the stated standard and there is nothing to ask without one.
|
|
38
|
+
# Older passes are left alone: an audit of a superseded read-out would be
|
|
39
|
+
# answering a question nobody asked.
|
|
40
|
+
#
|
|
41
|
+
# Selection is by timestamp, not by which guideline appears most often. An
|
|
42
|
+
# earlier version picked the most frequent guideline digest, which on a game
|
|
43
|
+
# carrying four passes of three analyses each is a four-way tie broken
|
|
44
|
+
# arbitrarily — and it silently chose a pre-LENS pass, so every analysis was
|
|
45
|
+
# skipped for having no standard to audit against.
|
|
46
|
+
def latest_analyses(dir)
|
|
47
|
+
rows = load_kind(dir, 'analyses_rescored') + load_kind(dir, 'analyses')
|
|
48
|
+
rows = rows.select do |r|
|
|
49
|
+
r['ok'] && r['text'].to_s.include?("\nSCORES") && r['text'].to_s =~ /^LENS\s*$/
|
|
50
|
+
end
|
|
51
|
+
abort "#{dir}: no analysis carries both a score block and a stated standard" if rows.empty?
|
|
52
|
+
rows.group_by { |r| r['model'] }.transform_values { |v| v.max_by { |r| r['at'].to_s } }.values
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def block(text, word)
|
|
56
|
+
text.to_s[/^#{word}\s*\n(.*?)(?=\n\s*(?:LENS|TEN|SCORES)\b|\z)/m, 1].to_s.strip
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def scores(text)
|
|
60
|
+
text.to_s[/SCORES\s*\n(.*)\z/m, 1].to_s.scan(/^\s*(A|B|C|GM)\s*:\s*(\d+)/).to_h
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
AUDIT_TASK = <<~T.strip
|
|
64
|
+
You are auditing one analyst's scoring of a completed game of Minimum Nomic — a
|
|
65
|
+
self-amending game with nine initial rules, all changeable, and no victory
|
|
66
|
+
condition, no goal and no termination condition. Three language models played it
|
|
67
|
+
and a fourth acted as game master, whose only job was deciding whose turn it was.
|
|
68
|
+
Nobody was given a compiled statement of the rules in force; each participant
|
|
69
|
+
worked that out from the initial set and the log.
|
|
70
|
+
|
|
71
|
+
After the game, that analyst read the whole record and scored each player and the
|
|
72
|
+
game master from 0 to 10 for metacognitive competence. It then stated, in its own
|
|
73
|
+
words, the standard it had applied and what a 10 would have looked like.
|
|
74
|
+
|
|
75
|
+
You are NOT being asked whether the scores are right, whether you would have
|
|
76
|
+
scored the same, or whether the standard is a good one. You are asked one factual
|
|
77
|
+
question:
|
|
78
|
+
|
|
79
|
+
Are these four scores derivable from that stated standard, applied to this
|
|
80
|
+
record?
|
|
81
|
+
|
|
82
|
+
For each score, decide whether the standard plus the record supports it. Where a
|
|
83
|
+
score does not follow, say so plainly: name the score, quote the part of the
|
|
84
|
+
standard it conflicts with, point to what in the record settles it, and say what
|
|
85
|
+
the standard would have predicted instead. Where a score does follow, say that
|
|
86
|
+
too, briefly. Do not soften a finding to be fair and do not manufacture one to
|
|
87
|
+
seem rigorous — "all four follow" is a legitimate result.
|
|
88
|
+
|
|
89
|
+
A note on what counts. A score that reflects a criterion the analyst never
|
|
90
|
+
mentioned is a finding, even if the criterion is reasonable. A score that
|
|
91
|
+
reflects a criterion the analyst explicitly declined to count is a stronger
|
|
92
|
+
finding. Disagreeing with the analyst's reading of an event is NOT a finding
|
|
93
|
+
unless the standard itself rules that reading out.
|
|
94
|
+
|
|
95
|
+
End your reply with this block, exactly, and nothing after it:
|
|
96
|
+
|
|
97
|
+
VERDICT
|
|
98
|
+
coherent: <yes|no>
|
|
99
|
+
contested: <comma-separated list of A, B, C, GM, or none>
|
|
100
|
+
T
|
|
101
|
+
|
|
102
|
+
dir = ARGV[0] or abort 'usage: audit_scores.rb GAME_DIR'
|
|
103
|
+
abort "#{dir}: not a game directory" unless File.directory?(File.join(dir, 'records'))
|
|
104
|
+
|
|
105
|
+
lineup = load_kind(dir, 'lineup').first or abort "#{dir}: no lineup"
|
|
106
|
+
analyses = latest_analyses(dir)
|
|
107
|
+
panel = (lineup['analysts'] || lineup['players']).map do |a|
|
|
108
|
+
{ id: a['id'], adapter: a['adapter'], model: a['model'], effort: a['effort'] }
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
rules = lineup['rules_initial'] || JSON.parse(File.read(RULES_JSON))['rules']
|
|
112
|
+
utterances = load_kind(dir, 'utterances').select { |u| u['in_public_log'] }
|
|
113
|
+
reasonings = load_kind(dir, 'reasonings')
|
|
114
|
+
gm_turns = load_kind(dir, 'turn_control')
|
|
115
|
+
|
|
116
|
+
RECORD = <<~R
|
|
117
|
+
## The initial rule set
|
|
118
|
+
|
|
119
|
+
#{rules.map { |r| "Rule #{r['id']}. #{r['body']}" }.join("\n\n")}
|
|
120
|
+
|
|
121
|
+
## The utterance log (every player saw all of this)
|
|
122
|
+
|
|
123
|
+
#{utterances.map { |u| "[#{u['seq']}] Player #{u['player']}: #{u['text']}" }.join("\n\n")}
|
|
124
|
+
|
|
125
|
+
## The reasoning log (no player ever saw any of this)
|
|
126
|
+
|
|
127
|
+
#{reasonings.map { |r| "[#{r['seq']}] Player #{r['player']} (#{r['form']}): #{r['text'] || '(none recorded)'}" }.join("\n\n")}
|
|
128
|
+
|
|
129
|
+
## The game master's turn-control record
|
|
130
|
+
|
|
131
|
+
#{gm_turns.map { |g| "Turn #{g['turn']}: next=#{g['next_player'] || '(none)'}, continue=#{g['continue']}\n reason: #{g['reason']}\n note: #{g['note']}" }.join("\n\n")}
|
|
132
|
+
R
|
|
133
|
+
|
|
134
|
+
def adapter_for(spec)
|
|
135
|
+
t = TIMEOUTS.fetch('analysis').fetch(spec[:adapter])
|
|
136
|
+
case spec[:adapter]
|
|
137
|
+
when 'claude_code'
|
|
138
|
+
cfg = { 'sandbox_mode' => true, 'timeout_seconds' => t }
|
|
139
|
+
cfg['effort'] = spec[:effort] if spec[:effort]
|
|
140
|
+
LC::ClaudeCodeAdapter.new(cfg)
|
|
141
|
+
when 'codex' then LC::CodexAdapter.new('timeout_seconds' => t)
|
|
142
|
+
when 'cursor' then LC::CursorAdapter.new('timeout_seconds' => t)
|
|
143
|
+
else raise "unknown adapter #{spec[:adapter]}"
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
out = File.join(dir, 'records', 'audits.jsonl')
|
|
148
|
+
task_sha = Digest::SHA256.hexdigest(AUDIT_TASK)
|
|
149
|
+
puts "#{dir}: auditing #{analyses.size} analyses with #{panel.size} auditors " \
|
|
150
|
+
"(#{analyses.size * panel.size} calls), task #{task_sha[0, 12]}"
|
|
151
|
+
|
|
152
|
+
analyses.each do |a|
|
|
153
|
+
lens = block(a['text'], 'LENS')
|
|
154
|
+
ten = block(a['text'], 'TEN')
|
|
155
|
+
s = scores(a['text'])
|
|
156
|
+
if lens.empty? || s.size < 4
|
|
157
|
+
warn " skip #{a['model']}: lens=#{lens.length} chars, scores=#{s.size}"
|
|
158
|
+
next
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
body = <<~B
|
|
162
|
+
#{AUDIT_TASK}
|
|
163
|
+
|
|
164
|
+
## The analyst under audit
|
|
165
|
+
|
|
166
|
+
Model: #{a['model']}
|
|
167
|
+
|
|
168
|
+
### The standard it stated it applied
|
|
169
|
+
|
|
170
|
+
#{lens}
|
|
171
|
+
|
|
172
|
+
### What it stated a 10 would have been in this record
|
|
173
|
+
|
|
174
|
+
#{ten.empty? ? '(not stated — this analysis predates that request)' : ten}
|
|
175
|
+
|
|
176
|
+
### The scores it gave
|
|
177
|
+
|
|
178
|
+
#{s.map { |k, v| "#{k}: #{v}" }.join("\n")}
|
|
179
|
+
|
|
180
|
+
## The record it was scoring
|
|
181
|
+
|
|
182
|
+
#{RECORD}
|
|
183
|
+
B
|
|
184
|
+
|
|
185
|
+
panel.each do |auditor|
|
|
186
|
+
started = Time.now
|
|
187
|
+
reply = nil
|
|
188
|
+
error = nil
|
|
189
|
+
begin
|
|
190
|
+
res = adapter_for(auditor).call(messages: [{ 'role' => 'user', 'content' => body }],
|
|
191
|
+
model: auditor[:model])
|
|
192
|
+
reply = res['content']
|
|
193
|
+
rescue StandardError => e
|
|
194
|
+
error = "#{e.class}: #{e.message}"
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
File.open(out, 'a') do |f|
|
|
198
|
+
f.puts JSON.generate({
|
|
199
|
+
'at' => Time.now.utc.iso8601(3),
|
|
200
|
+
'auditor' => auditor[:model],
|
|
201
|
+
'auditor_seat' => auditor[:id],
|
|
202
|
+
'audited' => a['model'],
|
|
203
|
+
'self_audit' => auditor[:model] == a['model'],
|
|
204
|
+
'audited_guideline_sha256' => a['analysis_guideline_sha256'],
|
|
205
|
+
'audit_task_sha256' => task_sha,
|
|
206
|
+
'prompt_chars' => body.length,
|
|
207
|
+
'seconds' => (Time.now - started).round(1),
|
|
208
|
+
'ok' => !reply.nil?,
|
|
209
|
+
'error' => error,
|
|
210
|
+
'text' => reply
|
|
211
|
+
})
|
|
212
|
+
end
|
|
213
|
+
mark = auditor[:model] == a['model'] ? ' (self)' : ''
|
|
214
|
+
puts " #{auditor[:model]} -> #{a['model']}#{mark}: ok=#{!reply.nil?} " \
|
|
215
|
+
"#{(Time.now - started).round(1)}s #{reply ? "#{reply.length} chars" : error}"
|
|
216
|
+
end
|
|
217
|
+
end
|