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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b16a89f65901c9eefc5b08e5c2b1503817625c35847304332316ddb01292f8da
4
- data.tar.gz: 7724c71a90c96aa624003663b4e7a7381552a08d0afa857ca19969fdea997875
3
+ metadata.gz: 0506a7cc642b812cabab1bb2840d928e1a9f550bf2486466741a54b6d71ee74f
4
+ data.tar.gz: bf1ff56bf44cf13d78c01dedd26d0bdc6188453dea18ca1975027f2b3a6c7f8d
5
5
  SHA512:
6
- metadata.gz: 3dc1c2f28e0b5b515f89be1bf7eb6714c796deae4ab8382907b0248abcabefc74e2510c372c3a3fc18eb2b8c7d36accd58535d2b9528398b83e6d2b2618e3a8b
7
- data.tar.gz: 5d33acb30a5c7510015d76c7d9d398597988e284e5420752385888e0814b19ba6332528cb701e925e82fb414f7c08bc3a3930ee990065070756701bcd07dc4ee
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
@@ -1,4 +1,4 @@
1
1
  module KairosMcp
2
- VERSION = "3.66.0"
2
+ VERSION = "3.67.0"
3
3
  CHANGELOG_URL = "https://github.com/masaomi/KairosChain_2026/blob/main/CHANGELOG.md"
4
4
  end
@@ -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