kairos-chain 3.50.0 → 3.52.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 +75 -0
- data/bin/kairos-chain +58 -18
- data/lib/kairos_mcp/version.rb +1 -1
- data/templates/knowledge/multi_llm_review_workflow/multi_llm_review_workflow.md +17 -10
- data/templates/skillsets/agent/bin/agent_step_worker.rb +183 -0
- data/templates/skillsets/agent/lib/agent/advance_gate.rb +357 -0
- data/templates/skillsets/agent/lib/agent/session.rb +12 -3
- data/templates/skillsets/agent/lib/agent/step_delegation.rb +344 -0
- data/templates/skillsets/agent/lib/agent.rb +2 -0
- data/templates/skillsets/agent/skillset.json +2 -1
- data/templates/skillsets/agent/test/test_agent_interruption_resilience.rb +729 -0
- data/templates/skillsets/agent/tools/agent_status.rb +42 -3
- data/templates/skillsets/agent/tools/agent_step.rb +299 -18
- data/templates/skillsets/agent/tools/agent_stop.rb +52 -14
- data/templates/skillsets/agent/tools/agent_wait.rb +191 -0
- data/templates/skillsets/chain_distillation/lib/chain_distillation/carrier_wiring.rb +187 -0
- data/templates/skillsets/chain_distillation/lib/chain_distillation/depositor.rb +463 -0
- data/templates/skillsets/chain_distillation/skillset.json +23 -6
- data/templates/skillsets/chain_distillation/test/test_chain_distillation.rb +656 -0
- data/templates/skillsets/chain_distillation/tools/cd_deposit.rb +69 -0
- data/templates/skillsets/chain_distillation/tools/cd_distill.rb +13 -1
- data/templates/skillsets/chain_distillation/tools/cd_revoke.rb +11 -1
- data/templates/skillsets/confidentiality_guard/config/profile.example.yml +3 -1
- data/templates/skillsets/confidentiality_guard/lib/confidentiality_guard/surfaces.rb +1 -0
- data/templates/skillsets/confidentiality_guard/skillset.json +1 -1
- data/templates/skillsets/confidentiality_guard/test/test_cg_guard.rb +61 -2
- data/templates/skillsets/multi_llm_review/config/multi_llm_review.yml +10 -5
- 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: 33bc325db559dbb4c9bfbd94ea25fb3ebd783edee32cf6cf4fe4a357b0d58bf1
|
|
4
|
+
data.tar.gz: 538dc94bc2eb732cce8233161cff7e972d17bf62894bf88ffc9847fb680f1de8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 916eedc6aaa54819797ee50ca77039a77516cf85c821c71c22e410882d654e06c2f4fcc778bb92fb38631046c7d3bec02c2fee17127fe11a9a4e9a31150d525a
|
|
7
|
+
data.tar.gz: 1bfe55dad79465dc24e9d3d29556d0f6bed30d39675974b1d5833152235206fda25b31b82129a7d5e9754f33feedcbe1d7e8d455b873dabbc52f8f3c3b02a2f8
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,81 @@ 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.51.3] - 2026-07-23
|
|
8
|
+
|
|
9
|
+
### Multi-LLM Review — knowledge template sync (v3.6.1)
|
|
10
|
+
|
|
11
|
+
Completes the 3.51.2 roster update: `templates/knowledge/multi_llm_review_workflow/`
|
|
12
|
+
is synced to v3.6.1 (gpt-5.6-sol roster). 3.51.2 updated the skillset config
|
|
13
|
+
template only, so `kairos-chain upgrade` reported a knowledge conflict against
|
|
14
|
+
instances already carrying the v3.6.1 L1 locally. No code change.
|
|
15
|
+
|
|
16
|
+
## [3.51.2] - 2026-07-23
|
|
17
|
+
|
|
18
|
+
### Multi-LLM Review — Codex roster update (gpt-5.6-sol)
|
|
19
|
+
|
|
20
|
+
Template config only, no code change. In
|
|
21
|
+
`templates/skillsets/multi_llm_review/config/multi_llm_review.yml`, the Codex
|
|
22
|
+
reviewer `gpt-5.4` is retired and replaced by `gpt-5.6-sol`
|
|
23
|
+
(`role_label: codex_gpt5.6-sol`). `gpt-5.5` stays as the calibrated anchor
|
|
24
|
+
for cross-generation comparison. 5.6-sol's reviewer bias profile is not yet
|
|
25
|
+
calibrated — record (a)/(b)/(c) breakdowns per round until a profile
|
|
26
|
+
accumulates. Roster size (6) and convergence rules are unchanged. The
|
|
27
|
+
matching L1 knowledge `multi_llm_review_workflow` was updated to v3.6.1
|
|
28
|
+
(instance-local, chain-recorded).
|
|
29
|
+
|
|
30
|
+
## [3.51.1] - 2026-07-23
|
|
31
|
+
|
|
32
|
+
### CLI — `skillset upgrade` transparency log
|
|
33
|
+
|
|
34
|
+
`kairos-chain skillset upgrade` (both check and `--apply`) now prints a
|
|
35
|
+
structured report mirroring `kairos-chain upgrade --apply`, so a no-op run is
|
|
36
|
+
visibly a completed check rather than a bare `All SkillSets are up to date.`
|
|
37
|
+
one-liner. Every CHECKED SkillSet (an installed one with a gem-template
|
|
38
|
+
counterpart) is listed with its status, followed by a summary:
|
|
39
|
+
|
|
40
|
+
- `[UPGRADE]` / `[UPGRADED]` — version/files changed (changed files listed on
|
|
41
|
+
check), and, on `--apply`, `vFROM -> vTO (N files)`.
|
|
42
|
+
- `[UNCHANGED]` — checked, no difference (the case that was previously invisible).
|
|
43
|
+
- `[NEW-CORE]` — a core SkillSet not yet installed, installed on `--apply`.
|
|
44
|
+
- `[OPTIONAL]` — a non-core new SkillSet, never auto-installed (explicit approval).
|
|
45
|
+
- `## Summary` — counts of upgraded/installed, unchanged, and optional.
|
|
46
|
+
|
|
47
|
+
Display-only: the upgrade decision (`upgrade_check` / `upgrade_apply`) is
|
|
48
|
+
unchanged; `core_only: true` behavior for the plain CLI form is preserved.
|
|
49
|
+
|
|
50
|
+
## [3.51.0] - 2026-07-23
|
|
51
|
+
|
|
52
|
+
### Agent SkillSet — interruption resilience A-1 + A-2
|
|
53
|
+
|
|
54
|
+
Addresses the failure mode where the orchestrator's API stalling leaves an
|
|
55
|
+
auto-mode agent loop waiting on input: the running MCP server is intact, but
|
|
56
|
+
the driver stops, and auto-permission is orthogonal to driver liveness.
|
|
57
|
+
Design v0.3.1 FROZEN (design-by-invariant, multi-LLM review 3R converged).
|
|
58
|
+
|
|
59
|
+
- **A-1 — AdvanceGate** (`lib/agent/advance_gate.rb`): every state-advancing
|
|
60
|
+
`agent_step` call passes through a per-session serialized, atomic, anchored
|
|
61
|
+
gate. INV-A2 (serialized atomic advance via `advance.lock` + tmp-rename
|
|
62
|
+
commit), INV-A3 (anchored at-most-once with a `seq:state:cycle` anchor,
|
|
63
|
+
committed outcomes replay on retry, side-effect intent bracket with
|
|
64
|
+
no-silent-drop), INV-A4 (`next_move` derivable from persisted state),
|
|
65
|
+
INV-A5 (adjudication as a gated judgment). `adjudicate` action added.
|
|
66
|
+
Impl review 4R converged, 49 probes.
|
|
67
|
+
- **A-2 — delegated step executor** (`lib/agent/step_delegation.rb`,
|
|
68
|
+
`bin/agent_step_worker.rb`): INV-A1 driver independence. `agent_step` with
|
|
69
|
+
`execution: "delegated"` returns a resumable handle while a detached
|
|
70
|
+
(setsid) worker re-enters the SAME gated `agent_step` under server-side
|
|
71
|
+
ownership — delegate → wait → collect, transplanted from the review
|
|
72
|
+
SkillSet. All correctness inherited from the A-1 gate; the delegation layer
|
|
73
|
+
is coordination/observability only. New `agent_wait` tool; `agent_status`
|
|
74
|
+
surfaces the handle. Impl review 6R converged, 81 probes total.
|
|
75
|
+
- Attended real-worker run with a live LLM-bearing step validated
|
|
76
|
+
(2026-07-23, isolated harness): the detached worker bootstrapped the full
|
|
77
|
+
ToolRegistry and ran ORIENT+DECIDE via the claude CLI; liveness, collect,
|
|
78
|
+
and collector-owned teardown all held.
|
|
79
|
+
- Native body path unchanged (selectable-off). All prior agent-suite tests
|
|
80
|
+
remain at the main baseline.
|
|
81
|
+
|
|
7
82
|
## [3.50.0] - 2026-07-23
|
|
8
83
|
|
|
9
84
|
### Chain Distillation SkillSet — slice 1 (CD-1..CD-6)
|
data/bin/kairos-chain
CHANGED
|
@@ -141,18 +141,43 @@ when 'skillset'
|
|
|
141
141
|
manual = upgrades.select { |u| u[:new_skillset] && !KairosMcp::SkillSetManager::CORE_SKILLSETS.include?(u[:name]) }
|
|
142
142
|
applicable = upgrades - manual
|
|
143
143
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
144
|
+
# Diagnostic transparency: report the full set of CHECKED SkillSets — not
|
|
145
|
+
# only the ones that changed — so a no-op run is visibly a completed check
|
|
146
|
+
# rather than a bare one-liner, mirroring `kairos-chain upgrade --apply`.
|
|
147
|
+
# This is display-only; the upgrade decision itself (upgrade_check /
|
|
148
|
+
# upgrade_apply) is unchanged. "Checked" = an installed SkillSet that has a
|
|
149
|
+
# gem-template counterpart (exactly what upgrade_check compares).
|
|
150
|
+
templates_dir = File.join(KairosMcp.gem_root, 'templates', 'skillsets')
|
|
151
|
+
checkable = manager.all_skillsets
|
|
152
|
+
.select { |ss| File.directory?(File.join(templates_dir, ss.name)) }
|
|
153
|
+
.sort_by(&:name)
|
|
154
|
+
existing_upgrade_names = applicable.reject { |u| u[:new_skillset] }.map { |u| u[:name] }
|
|
155
|
+
new_core = applicable.select { |u| u[:new_skillset] }
|
|
156
|
+
|
|
157
|
+
if apply
|
|
147
158
|
results = manager.upgrade_apply(core_only: true)
|
|
159
|
+
upgraded_names = results.map { |r| r[:name] }
|
|
160
|
+
puts "# Applying SkillSet Upgrade"
|
|
161
|
+
puts ""
|
|
162
|
+
puts "## SkillSets"
|
|
148
163
|
results.each do |r|
|
|
149
|
-
puts "
|
|
164
|
+
puts " [UPGRADED] #{r[:name].ljust(22)} v#{r[:from]} -> v#{r[:to]} (#{r[:files_updated]} files)"
|
|
165
|
+
end
|
|
166
|
+
checkable.reject { |ss| upgraded_names.include?(ss.name) }.each do |ss|
|
|
167
|
+
puts " [UNCHANGED] #{ss.name.ljust(22)} v#{ss.version}"
|
|
150
168
|
end
|
|
151
|
-
|
|
169
|
+
manual.each do |u|
|
|
170
|
+
puts " [OPTIONAL] #{u[:name].ljust(22)} v#{u[:available_version]} (not installed)"
|
|
171
|
+
end
|
|
172
|
+
puts ""
|
|
173
|
+
puts "## Summary"
|
|
174
|
+
puts " Upgraded/installed: #{results.size}"
|
|
175
|
+
puts " Unchanged: #{checkable.reject { |ss| upgraded_names.include?(ss.name) }.size}"
|
|
176
|
+
puts " Optional (not installed): #{manual.size}"
|
|
152
177
|
|
|
153
178
|
if manual.any?
|
|
154
179
|
puts ""
|
|
155
|
-
puts "
|
|
180
|
+
puts "Optional SkillSets require explicit approval:"
|
|
156
181
|
manual.each { |u| puts " kairos-chain skillset install #{u[:name]}" }
|
|
157
182
|
end
|
|
158
183
|
|
|
@@ -174,24 +199,39 @@ when 'skillset'
|
|
|
174
199
|
end
|
|
175
200
|
end
|
|
176
201
|
else
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
202
|
+
upgrade_by_name = applicable.reject { |u| u[:new_skillset] }.each_with_object({}) { |u, h| h[u[:name]] = u }
|
|
203
|
+
puts "# SkillSet Upgrade Check"
|
|
204
|
+
puts ""
|
|
205
|
+
puts "## SkillSets"
|
|
206
|
+
checkable.each do |ss|
|
|
207
|
+
if (u = upgrade_by_name[ss.name])
|
|
181
208
|
label = u[:version_bump] ? "v#{u[:installed_version]} -> v#{u[:available_version]}" : "files changed"
|
|
182
|
-
puts " #{
|
|
183
|
-
u[:changed_files].each { |f| puts "
|
|
184
|
-
|
|
209
|
+
puts " [UPGRADE] #{ss.name.ljust(22)} #{label}"
|
|
210
|
+
u[:changed_files].each { |f| puts " #{f}" }
|
|
211
|
+
else
|
|
212
|
+
puts " [UNCHANGED] #{ss.name.ljust(22)} v#{ss.version}"
|
|
185
213
|
end
|
|
186
|
-
puts "Run 'kairos-chain skillset upgrade --apply' to apply updates."
|
|
187
214
|
end
|
|
215
|
+
new_core.each do |u|
|
|
216
|
+
puts " [NEW-CORE] #{u[:name].ljust(22)} v#{u[:available_version]} (will install on --apply)"
|
|
217
|
+
end
|
|
218
|
+
manual.each do |u|
|
|
219
|
+
puts " [OPTIONAL] #{u[:name].ljust(22)} v#{u[:available_version]} (not installed)"
|
|
220
|
+
end
|
|
221
|
+
puts ""
|
|
222
|
+
puts "## Summary"
|
|
223
|
+
puts " Available upgrades: #{applicable.size}"
|
|
224
|
+
puts " Unchanged: #{checkable.size - existing_upgrade_names.size}"
|
|
225
|
+
puts " Optional (not installed): #{manual.size}"
|
|
188
226
|
|
|
227
|
+
if applicable.any?
|
|
228
|
+
puts ""
|
|
229
|
+
puts "Run 'kairos-chain skillset upgrade --apply' to apply updates."
|
|
230
|
+
end
|
|
189
231
|
if manual.any?
|
|
190
|
-
puts "" if applicable.any?
|
|
191
|
-
puts "Optional SkillSets available (not auto-installed; require explicit approval):"
|
|
192
|
-
manual.each { |u| puts " #{u[:name]} v#{u[:available_version]} — #{u[:description]}" }
|
|
193
232
|
puts ""
|
|
194
|
-
puts "
|
|
233
|
+
puts "Optional SkillSets (require explicit approval):"
|
|
234
|
+
manual.each { |u| puts " kairos-chain skillset install #{u[:name]} # #{u[:description]}" }
|
|
195
235
|
end
|
|
196
236
|
end
|
|
197
237
|
|
data/lib/kairos_mcp/version.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: multi_llm_review_workflow
|
|
3
3
|
description: "Multi-LLM review methodology and execution — workflow pattern, CLI tooling, consensus analysis, Persona Assembly. Applicable to design, implementation, documentation, or any artifact."
|
|
4
|
-
version: "3.6"
|
|
4
|
+
version: "3.6.1"
|
|
5
5
|
tags:
|
|
6
6
|
- workflow
|
|
7
7
|
- review
|
|
@@ -345,7 +345,7 @@ starting** and verify each against `config/multi_llm_review.yml`:
|
|
|
345
345
|
- [ ] Your model (orchestrator): ___
|
|
346
346
|
- [ ] Agent Team Personas model: = orchestrator model (NOT a different model)
|
|
347
347
|
- [ ] Subprocess CLI models: Opus 4.6 AND Opus 4.8 (both, not either/or)
|
|
348
|
-
- [ ] Codex models: gpt-5.
|
|
348
|
+
- [ ] Codex models: gpt-5.6-sol (default) AND gpt-5.5 (both, not either/or)
|
|
349
349
|
- [ ] Cursor model: default (composer-2.5, no --model flag)
|
|
350
350
|
- [ ] Total reviewer count: 6 (or 5 after orchestrator exclusion from subprocess)
|
|
351
351
|
- [ ] Convergence rule: 4/6 APPROVE (full) or 3/5 APPROVE (after exclusion)
|
|
@@ -356,7 +356,7 @@ starting** and verify each against `config/multi_llm_review.yml`:
|
|
|
356
356
|
| Mistake | Correct behavior | Why it happens |
|
|
357
357
|
|---------|-----------------|----------------|
|
|
358
358
|
| Exclude orchestrator model from Agent Team Personas | Agent Team uses orchestrator model — they provide persona diversity, not epistemic diversity | LLM misreads "do not assign yourself as a reviewer" as applying to Agent Team; it applies only to subprocess CLI |
|
|
359
|
-
| Run only Codex GPT-5.
|
|
359
|
+
| Run only Codex GPT-5.6-sol, skip 5.5 | Run both — cross-generation entries catch different things (5.5 found §5 schema contradiction in Phase 2 Case A that no other reviewer caught) | Cost-saving heuristic; roster has both for a reason |
|
|
360
360
|
| Use a smaller/cheaper model as Agent Team substitute | Use the orchestrator's own model with different personas | Confusing "model diversity" with "persona diversity" — Agent Team is the latter |
|
|
361
361
|
| Run 3 reviewers instead of 6 (or 5 after exclusion) | Use the full roster from config | Ad-hoc "3 is enough" reasoning; config specifies 6 for empirical reasons |
|
|
362
362
|
|
|
@@ -580,7 +580,7 @@ which claude 2>/dev/null && echo "claude: available" || echo "claude: NOT FOUND"
|
|
|
580
580
|
|
|
581
581
|
| Tool | Command | Prompt Input | Output Collection | Model |
|
|
582
582
|
|------|---------|-------------|-------------------|-------|
|
|
583
|
-
| **Codex** | `codex exec -m <model>` | stdin pipe: `cat prompt.md \| codex exec -` | `-o /path/output.md` | GPT-5.
|
|
583
|
+
| **Codex** | `codex exec -m <model>` | stdin pipe: `cat prompt.md \| codex exec -` | `-o /path/output.md` | GPT-5.6-sol + GPT-5.5 (both roster entries, `-m` per entry) |
|
|
584
584
|
| **Cursor Agent** | `agent -p` | File reference (stdin NOT supported) | stdout redirect: `> output.md` | Composer-2.5 (default) |
|
|
585
585
|
| **Claude Code** | Agent tool (internal) | Direct prompt string | Write to workspace file | Fable 5 (session) |
|
|
586
586
|
| **Claude CLI (4.6)** | `claude -p --model claude-opus-4-6 --bare` | stdin pipe: `cat prompt.md \| claude -p --model claude-opus-4-6 --bare` | stdout redirect: `> output.md` | Opus 4.6 |
|
|
@@ -597,7 +597,7 @@ Based on cross-evaluation experiment (7 models × 4 tasks + Nomic, 518 CLI calls
|
|
|
597
597
|
| **Reviewer: Claude CLI** | Opus 4.6 / Opus 4.8 | (default; config `effort: medium`) | Evaluator quality is effort-independent (low≈high: 8.35 vs 8.16) — per 2026-04-29 policy reviewers stay at default |
|
|
598
598
|
| **Coding sub-agent** | Opus 4.7 | `--effort medium` | Cost-effective default; use `high` for complex tasks |
|
|
599
599
|
| **Design sub-agent** | Opus 4.7 | `--effort medium` | Cost-effective default; use `high` for complex tasks |
|
|
600
|
-
| **Codex** | GPT-5.5
|
|
600
|
+
| **Codex** | GPT-5.6-sol / GPT-5.5 | (no flag) | Fixed effort |
|
|
601
601
|
| **Cursor Agent** | Composer-2.5 | (no flag) | Fixed effort |
|
|
602
602
|
|
|
603
603
|
Note (2026-06-10): the effort experiment data is from the Opus 4.6/4.7
|
|
@@ -898,11 +898,11 @@ Step 1: Generate review prompt
|
|
|
898
898
|
Step 2: Detect environment and models
|
|
899
899
|
- Run: which codex && which agent && which claude
|
|
900
900
|
- Detect default models
|
|
901
|
-
- Report: "Auto mode: Codex (gpt-5.
|
|
901
|
+
- Report: "Auto mode: Codex (gpt-5.6-sol, gpt-5.5), Agent (composer-2.5), Claude Team (claude-fable-5), Claude CLI (opus-4.6, opus-4.8)"
|
|
902
902
|
|
|
903
903
|
Step 3: Execute N reviews in parallel (default 6 reviewers)
|
|
904
904
|
- Bash(background): cat prompt.md | codex exec -m gpt-5.5 -C workspace -o log/review_codex_gpt5.5.md -
|
|
905
|
-
- Bash(background): cat prompt.md | codex exec -m gpt-5.
|
|
905
|
+
- Bash(background): cat prompt.md | codex exec -m gpt-5.6-sol -C workspace -o log/review_codex_gpt5.6-sol.md -
|
|
906
906
|
- Bash(background): agent -p --trust "Read prompt and review..." > log/review_cursor.md
|
|
907
907
|
- Agent(background): Claude Team (orchestrator model, Fable 5) → write to log/review_claude_team_fable5.md
|
|
908
908
|
- Bash(background): cat prompt.md | claude -p --model claude-opus-4-6 --bare > log/review_claude_opus4.6.md 2>log/review_claude_opus4.6.stderr.log
|
|
@@ -944,10 +944,10 @@ log/{artifact}_review{N}_consensus_{date}.md # Consensus analysis
|
|
|
944
944
|
```
|
|
945
945
|
|
|
946
946
|
LLM identifiers: `claude_team_fable5`, `claude_cli_opus4.6`, `claude_cli_opus4.8`,
|
|
947
|
-
`codex_gpt5.
|
|
947
|
+
`codex_gpt5.6-sol`, `codex_gpt5.5`, `cursor_composer2.5`, `cursor_gpt5.4`,
|
|
948
948
|
`cursor_premium`
|
|
949
949
|
(legacy, pre-2026-06-10: `claude_opus4.6`, `claude_team_opus4.6`, `claude_team_opus4.7`,
|
|
950
|
-
`claude_cli_opus4.7`, `cursor_composer2`)
|
|
950
|
+
`claude_cli_opus4.7`, `cursor_composer2`; retired 2026-07-23: `codex_gpt5.4`)
|
|
951
951
|
|
|
952
952
|
## Internal Agent Team Review
|
|
953
953
|
|
|
@@ -997,6 +997,13 @@ Compression ratio: parallel agent raw → Assembly ≈ 2:1
|
|
|
997
997
|
epistemic diversity — the personas that authored R1 findings all approved
|
|
998
998
|
their own fixes in R2, while subprocess reviewers caught the seams.
|
|
999
999
|
Design record: `docs/drafts/multi_llm_review_unknowns_pass_v0.3.1_FROZEN.md`
|
|
1000
|
+
- Roster update (v3.6.1, 2026-07-23): Codex gpt-5.6-sol replaces gpt-5.4
|
|
1001
|
+
(retired — its "everyday coding" register is covered by the frontier
|
|
1002
|
+
entries). gpt-5.5 retained as the calibrated anchor for cross-generation
|
|
1003
|
+
comparison. 5.6-sol's reviewer bias profile is uncalibrated — record
|
|
1004
|
+
(a)/(b)/(c) breakdowns per round until a profile accumulates in
|
|
1005
|
+
`multi_llm_reviewer_evaluation`; watch for agentic-coding drift into
|
|
1006
|
+
implementation detail during design-phase reviews ((c) inflation)
|
|
1000
1007
|
|
|
1001
1008
|
**Key insight**: Design reviews and implementation reviews find
|
|
1002
|
-
**categorically different bugs**. Both phases are necessary.
|
|
1009
|
+
**categorically different bugs**. Both phases are necessary.
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
#
|
|
4
|
+
# Interruption resilience Slice A-2 — detached agent step worker (INV-A1).
|
|
5
|
+
#
|
|
6
|
+
# Spawned by StepDelegation#spawn_worker after agent_step was called with
|
|
7
|
+
# execution: "delegated". This worker is deliberately thin: it bootstraps a
|
|
8
|
+
# ToolRegistry (the same construction the MCP server uses) and re-enters the
|
|
9
|
+
# SAME gated agent_step path with the recorded arguments (the delegation-start
|
|
10
|
+
# anchor is already injected into those arguments). Every correctness property
|
|
11
|
+
# — per-session serialization, anchored at-most-once, side-effect intent
|
|
12
|
+
# bracket — is enforced by the AdvanceGate inside that call (Slice A-1), not by
|
|
13
|
+
# this script. If this worker dies, the driver re-issues the recorded call
|
|
14
|
+
# safely; if it double-runs, the gate serializes and replays.
|
|
15
|
+
#
|
|
16
|
+
# argv: <session_id> <session_dir>
|
|
17
|
+
# env: KAIROS_PROJECT_ROOT (chdir target)
|
|
18
|
+
# KAIROS_SERVER_LIB (lib dir to load kairos_mcp from)
|
|
19
|
+
# KAIROS_DATA_DIR (the server's effective data dir; makes the
|
|
20
|
+
# worker resolve the SAME .kairos)
|
|
21
|
+
#
|
|
22
|
+
# Exit codes: 0 success; 1 exception; 125 setsid failed; 130 signal.
|
|
23
|
+
#
|
|
24
|
+
# NB: bootstrap failures (LoadError/ScriptError from require) are caught too,
|
|
25
|
+
# so the driver always sees a result rather than a silently hung handle.
|
|
26
|
+
|
|
27
|
+
require 'json'
|
|
28
|
+
require 'time'
|
|
29
|
+
require 'fileutils'
|
|
30
|
+
|
|
31
|
+
session_id = ARGV[0] or abort 'usage: agent_step_worker.rb <session_id> <session_dir>'
|
|
32
|
+
session_dir = ARGV[1] or abort 'usage: agent_step_worker.rb <session_id> <session_dir>'
|
|
33
|
+
|
|
34
|
+
# Read the handle identity from the raw file first, so even a bootstrap
|
|
35
|
+
# failure can tag its error result with the delegation it belongs to.
|
|
36
|
+
def read_handle_identity(session_dir)
|
|
37
|
+
raw = JSON.parse(File.read(File.join(session_dir, 'delegation.json')))
|
|
38
|
+
{ 'issue_anchor' => raw['issue_anchor'], 'action_key' => raw['action_key'],
|
|
39
|
+
'step_token' => raw['step_token'] }
|
|
40
|
+
rescue StandardError
|
|
41
|
+
{}
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def write_raw_result(session_dir, identity, outcome)
|
|
45
|
+
payload = identity.merge('outcome' => outcome)
|
|
46
|
+
tmp = File.join(session_dir, "delegation_result.json.tmp.#{Process.pid}")
|
|
47
|
+
File.write(tmp, JSON.generate(payload))
|
|
48
|
+
File.rename(tmp, File.join(session_dir, 'delegation_result.json'))
|
|
49
|
+
rescue StandardError
|
|
50
|
+
# nothing more we can do
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
boot_identity = read_handle_identity(session_dir)
|
|
54
|
+
|
|
55
|
+
$LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
|
|
56
|
+
begin
|
|
57
|
+
require 'agent/step_delegation'
|
|
58
|
+
rescue ScriptError, StandardError => e
|
|
59
|
+
# The delegation lib is co-located with this script; if even it cannot load
|
|
60
|
+
# we tag a raw result with the handle identity so status can surface it as
|
|
61
|
+
# 'ready' (an error outcome) rather than the driver waiting out the grace.
|
|
62
|
+
write_raw_result(session_dir, boot_identity,
|
|
63
|
+
{ 'status' => 'error',
|
|
64
|
+
'error' => "worker bootstrap failed: #{e.class}: #{e.message}" })
|
|
65
|
+
exit 1
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
delegation = KairosMcp::SkillSets::Agent::StepDelegation.new(session_dir)
|
|
69
|
+
my_token = boot_identity['step_token']
|
|
70
|
+
|
|
71
|
+
shutdown = { requested: false }
|
|
72
|
+
%w[TERM INT HUP].each do |sig|
|
|
73
|
+
Signal.trap(sig) { shutdown[:requested] = true }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
begin
|
|
77
|
+
Process.setsid
|
|
78
|
+
rescue Errno::EPERM
|
|
79
|
+
# Already a session leader — acceptable; continue.
|
|
80
|
+
rescue StandardError => e
|
|
81
|
+
delegation.write_result({ 'status' => 'error',
|
|
82
|
+
'error' => "worker setsid failed: #{e.message}" },
|
|
83
|
+
identity: boot_identity)
|
|
84
|
+
exit 125
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
heartbeat_thread = Thread.new do
|
|
88
|
+
loop do
|
|
89
|
+
begin
|
|
90
|
+
delegation.touch_heartbeat(my_token)
|
|
91
|
+
rescue StandardError
|
|
92
|
+
# A transient touch failure must not kill the heartbeat thread and
|
|
93
|
+
# make a live worker look crashed; retry on the next tick.
|
|
94
|
+
end
|
|
95
|
+
sleep KairosMcp::SkillSets::Agent::StepDelegation::HEARTBEAT_INTERVAL_SECONDS
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Self-timeout watchdog: a hung gated call would otherwise hold the advance
|
|
100
|
+
# lock forever. Exiting the process releases its flock; the driver then sees
|
|
101
|
+
# 'crashed' and re-issues safely. We deliberately write NO result here so the
|
|
102
|
+
# collector takes the crash path — if the gated advance had already committed,
|
|
103
|
+
# crashed_response recovers its outcome from the gate log; an error result
|
|
104
|
+
# would instead mask that committed advance.
|
|
105
|
+
timeout_s = KairosMcp::SkillSets::Agent::StepDelegation.worker_self_timeout_seconds
|
|
106
|
+
watchdog = Thread.new do
|
|
107
|
+
sleep timeout_s
|
|
108
|
+
exit!(124)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
begin
|
|
112
|
+
Dir.chdir(ENV['KAIROS_PROJECT_ROOT']) if ENV['KAIROS_PROJECT_ROOT'] &&
|
|
113
|
+
Dir.exist?(ENV['KAIROS_PROJECT_ROOT'])
|
|
114
|
+
$LOAD_PATH.unshift(ENV['KAIROS_SERVER_LIB']) if ENV['KAIROS_SERVER_LIB']
|
|
115
|
+
# KAIROS_DATA_DIR was set in the worker env by spawn_worker so ToolRegistry
|
|
116
|
+
# / Session resolve the server's effective .kairos even under --data-dir.
|
|
117
|
+
require 'kairos_mcp/tool_registry'
|
|
118
|
+
|
|
119
|
+
pending = delegation.pending
|
|
120
|
+
raise "no pending delegation in #{session_dir}" unless pending
|
|
121
|
+
|
|
122
|
+
# Supersession guard: run ONLY if the live handle is still ours. If a fresh
|
|
123
|
+
# open_handle replaced it while we were starting up (e.g. we were declared
|
|
124
|
+
# 'crashed' on a stale heartbeat and the driver re-delegated), or we could
|
|
125
|
+
# not read our own identity at boot (my_token nil), do NOT run: the new
|
|
126
|
+
# worker owns this delegation, and the gate would replay/serialize our stale
|
|
127
|
+
# call anyway. Exiting leaves the current handle to its rightful worker.
|
|
128
|
+
if my_token.nil? || pending['step_token'] != my_token
|
|
129
|
+
exit 0
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Past the guard, boot_identity is non-empty and is our own handle identity
|
|
133
|
+
# (also driving the per-token heartbeat); write_result tags with it.
|
|
134
|
+
identity = boot_identity
|
|
135
|
+
|
|
136
|
+
args = (pending['arguments'] || {}).merge('session_id' => session_id)
|
|
137
|
+
args.delete('execution') # never recurse into another delegation
|
|
138
|
+
|
|
139
|
+
exit 130 if shutdown[:requested]
|
|
140
|
+
|
|
141
|
+
registry = KairosMcp::ToolRegistry.new
|
|
142
|
+
raw = registry.call_tool('agent_step', args)
|
|
143
|
+
|
|
144
|
+
# Normalize the MCP content shape to the response hash the inline call
|
|
145
|
+
# would have returned.
|
|
146
|
+
text = if raw.is_a?(Array) && raw.first.is_a?(Hash)
|
|
147
|
+
raw.first[:text] || raw.first['text']
|
|
148
|
+
end
|
|
149
|
+
response = begin
|
|
150
|
+
text ? JSON.parse(text) : { 'status' => 'error', 'error' => 'unrecognized tool result shape' }
|
|
151
|
+
rescue JSON::ParserError
|
|
152
|
+
{ 'status' => 'error', 'error' => 'unparseable tool result', 'raw' => text.to_s[0, 500] }
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Write the result (tagged with our OWN startup identity) and leave the
|
|
156
|
+
# pending handle in place: teardown is the collector's job (agent_wait#collect
|
|
157
|
+
# clears result+handle atomically under delegation.lock), so the worker never
|
|
158
|
+
# races a concurrently-opened fresh delegation by clearing state it may no
|
|
159
|
+
# longer own or by mislabeling its result as a newer delegation's.
|
|
160
|
+
delegation.write_result(response, identity: identity)
|
|
161
|
+
exit 0
|
|
162
|
+
rescue SystemExit, SignalException
|
|
163
|
+
# A deliberate exit (including our own `exit 0`) or a signal is not a
|
|
164
|
+
# failure to report — let it propagate.
|
|
165
|
+
raise
|
|
166
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
167
|
+
# Catch Exception, not just StandardError: LoadError/ScriptError from the
|
|
168
|
+
# bootstrap require are exactly the failure class the driver must see as a
|
|
169
|
+
# result rather than a silently hung handle. Leave teardown to the collector.
|
|
170
|
+
# Tag with our OWN startup identity (boot_identity is always our handle;
|
|
171
|
+
# the in-block `identity` local may be unassigned if we failed early, and
|
|
172
|
+
# a re-read of pending could belong to a superseding delegation).
|
|
173
|
+
begin
|
|
174
|
+
delegation.write_result({ 'status' => 'error', 'error' => "worker: #{e.class}: #{e.message}" },
|
|
175
|
+
identity: (boot_identity.empty? ? nil : boot_identity))
|
|
176
|
+
rescue StandardError
|
|
177
|
+
# best effort
|
|
178
|
+
end
|
|
179
|
+
exit 1
|
|
180
|
+
ensure
|
|
181
|
+
watchdog&.kill
|
|
182
|
+
heartbeat_thread&.kill
|
|
183
|
+
end
|