@onlooker-community/ecosystem 0.34.0 → 0.43.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.
- package/.claude/skills/writing-tests/SKILL.md +27 -0
- package/.claude-plugin/plugin.json +1 -1
- package/.github/workflows/release.yml +14 -14
- package/.release-please-manifest.json +7 -7
- package/AGENTS.md +116 -0
- package/CHANGELOG.md +175 -0
- package/CLAUDE.md +5 -0
- package/docs/lesson-promotion-pipeline.md +6 -6
- package/docs/superpowers/plans/2026-08-10-lesson-confirmation.md +1273 -0
- package/docs/superpowers/plans/2026-08-11-lesson-judging.md +1232 -0
- package/docs/superpowers/plans/2026-08-11-lesson-unconfirm.md +458 -0
- package/docs/superpowers/plans/2026-08-12-author-key.md +567 -0
- package/docs/superpowers/plans/2026-08-13-approved-pool.md +747 -0
- package/docs/superpowers/plans/2026-08-13-librarian-cluster.md +392 -0
- package/docs/superpowers/plans/2026-08-14-criterion-scores-consumers.md +1374 -0
- package/docs/superpowers/plans/2026-08-14-criterion-scores-schema.md +277 -0
- package/docs/superpowers/plans/2026-08-16-cartographer-undocumented-entity.md +741 -0
- package/docs/superpowers/specs/2026-08-10-lesson-confirmation-design.md +213 -0
- package/docs/superpowers/specs/2026-08-11-lesson-judging-design.md +267 -0
- package/docs/superpowers/specs/2026-08-11-lesson-unconfirm-design.md +152 -0
- package/docs/superpowers/specs/2026-08-12-author-key-design.md +317 -0
- package/docs/superpowers/specs/2026-08-13-approved-pool-design.md +232 -0
- package/docs/superpowers/specs/2026-08-14-criterion-scores-design.md +254 -0
- package/docs/superpowers/specs/2026-08-16-cartographer-undocumented-entity-design.md +249 -0
- package/package.json +2 -2
- package/plugins/assayer/.claude-plugin/plugin.json +1 -1
- package/plugins/assayer/CHANGELOG.md +7 -0
- package/plugins/assayer/scripts/lib/assayer-config.sh +6 -0
- package/plugins/cartographer/.claude-plugin/plugin.json +1 -1
- package/plugins/cartographer/CHANGELOG.md +54 -0
- package/plugins/cartographer/README.md +56 -1
- package/plugins/cartographer/config.json +7 -1
- package/plugins/cartographer/scripts/hooks/cartographer-post-write.sh +4 -4
- package/plugins/cartographer/scripts/hooks/cartographer-session-start.sh +7 -4
- package/plugins/cartographer/scripts/lib/cartographer-collect.sh +8 -1
- package/plugins/cartographer/scripts/lib/cartographer-config.sh +26 -0
- package/plugins/cartographer/scripts/lib/cartographer-events.sh +90 -0
- package/plugins/cartographer/scripts/lib/cartographer-filter.sh +105 -0
- package/plugins/cartographer/scripts/lib/cartographer-omission.sh +130 -0
- package/plugins/cartographer/scripts/lib/cartographer-resolve.sh +132 -0
- package/plugins/cartographer/scripts/run-audit.sh +136 -53
- package/plugins/cartographer/skills/cartographer/SKILL.md +33 -9
- package/plugins/curator/.claude-plugin/plugin.json +1 -1
- package/plugins/curator/CHANGELOG.md +7 -0
- package/plugins/curator/scripts/lib/curator-emit.sh +2 -1
- package/plugins/historian/.claude-plugin/plugin.json +1 -1
- package/plugins/historian/CHANGELOG.md +7 -0
- package/plugins/historian/scripts/lib/historian-emit.sh +2 -1
- package/plugins/librarian/.claude-plugin/plugin.json +1 -1
- package/plugins/librarian/CHANGELOG.md +71 -0
- package/plugins/librarian/config.json +30 -1
- package/plugins/librarian/docs/adr/002-agent-definitions-are-shared-assets.md +82 -0
- package/plugins/librarian/scripts/hooks/librarian-session-end.sh +28 -0
- package/plugins/librarian/scripts/hooks/librarian-session-start.sh +42 -12
- package/plugins/librarian/scripts/lib/librarian-author-key.sh +279 -0
- package/plugins/librarian/scripts/lib/librarian-cli.sh +346 -2
- package/plugins/librarian/scripts/lib/librarian-emit.sh +2 -1
- package/plugins/librarian/scripts/lib/librarian-lesson-judge.sh +349 -0
- package/plugins/librarian/scripts/lib/librarian-lesson-promote.sh +225 -0
- package/plugins/librarian/scripts/lib/librarian-lesson-review.sh +312 -0
- package/plugins/librarian/scripts/lib/librarian-lesson-rubric.sh +56 -0
- package/plugins/librarian/scripts/lib/librarian-lesson-storage.sh +82 -13
- package/plugins/librarian/scripts/lib/librarian-lesson-transform.sh +6 -1
- package/plugins/librarian/scripts/lib/librarian-lesson-validate.sh +131 -62
- package/plugins/librarian/skills/librarian/SKILL.md +98 -1
- package/plugins/tribunal/.claude-plugin/plugin.json +1 -1
- package/plugins/tribunal/CHANGELOG.md +63 -0
- package/plugins/tribunal/agents/tribunal-judge-adversarial.md +34 -0
- package/plugins/tribunal/agents/tribunal-judge-security.md +33 -0
- package/plugins/tribunal/agents/tribunal-judge-standard.md +12 -0
- package/plugins/tribunal/config.json +1 -0
- package/plugins/tribunal/scripts/lib/tribunal-aggregate.sh +154 -9
- package/plugins/tribunal/scripts/lib/tribunal-gate.sh +195 -16
- package/plugins/tribunal/skills/tribunal/SKILL.md +5 -3
- package/scripts/lib/prompt-rules.sh +6 -1
- package/test/bats/archivist-inject.bats +1 -1
- package/test/bats/assayer-extract.bats +2 -2
- package/test/bats/bursar-session-start.bats +3 -3
- package/test/bats/cartographer-config.bats +43 -0
- package/test/bats/cartographer-events.bats +267 -0
- package/test/bats/cartographer-filter.bats +169 -0
- package/test/bats/cartographer-lock.bats +3 -3
- package/test/bats/cartographer-omission.bats +202 -0
- package/test/bats/cartographer-resolve.bats +287 -0
- package/test/bats/cartographer-run-audit.bats +325 -0
- package/test/bats/compass-sanitizer.bats +11 -11
- package/test/bats/compass-transcript.bats +2 -2
- package/test/bats/config.bats +15 -15
- package/test/bats/curator-session-start.bats +10 -3
- package/test/bats/emit-payload-default.bats +52 -0
- package/test/bats/governor-ledger.bats +1 -1
- package/test/bats/historian-prompt-submit.bats +1 -1
- package/test/bats/inspector-post-write-hook.bats +4 -4
- package/test/bats/librarian-author-key.bats +477 -0
- package/test/bats/librarian-cli.bats +16 -16
- package/test/bats/librarian-lesson-judge.bats +884 -0
- package/test/bats/librarian-lesson-promote.bats +552 -0
- package/test/bats/librarian-lesson-review.bats +900 -0
- package/test/bats/librarian-lesson-transform.bats +17 -3
- package/test/bats/librarian-session-end.bats +161 -0
- package/test/bats/librarian-session-start.bats +36 -2
- package/test/bats/lineage-config.bats +1 -1
- package/test/bats/lineage-redact.bats +5 -5
- package/test/bats/session-tracker.bats +4 -4
- package/test/bats/tribunal-aggregate.bats +354 -1
- package/test/bats/tribunal-gate.bats +631 -0
- package/test/bats/tribunal-judge-agents.bats +119 -0
- package/test/bats/tribunal-jury.bats +1 -1
- package/test/bats/turn-tracker.bats +1 -1
- package/test/bats/warden-sanitizer.bats +3 -3
- package/test/bats/worktree-tracker.bats +2 -2
- package/test/node/lesson-validate-agreement.test.mjs +35 -6
|
@@ -57,6 +57,82 @@ librarian_lesson_valid_range() {
|
|
|
57
57
|
[[ "$r" =~ $pattern ]]
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
# Everything both validators check, excluding the scope branch. Kept in one
|
|
61
|
+
# place because it mirrors the vendored sub-schemas line for line; a second
|
|
62
|
+
# copy is how the jq rules and the schema drifted apart once already.
|
|
63
|
+
#
|
|
64
|
+
# Composed into a jq program by each validator, which appends its own scope
|
|
65
|
+
# clause. artifact_ids, session_ids, and observed_at are checked against the
|
|
66
|
+
# same patterns as the vendored lesson-evidence.subschema.json (ULID,
|
|
67
|
+
# non-empty string, RFC3339 date-time) — a provenance-less artifact
|
|
68
|
+
# (session_id/created_at stitched in as "") must fail here, not pass through
|
|
69
|
+
# and get buried permanently once librarian_lesson_seen marks it handled.
|
|
70
|
+
#
|
|
71
|
+
# The `keys - [...] | length == 0` checks mirror `additionalProperties:
|
|
72
|
+
# false` on the vendored `evidence` and `applies_to` sub-schemas, and the
|
|
73
|
+
# `all(type == "string" and length > 0)` checks mirror their array items'
|
|
74
|
+
# `minLength: 1`. Neither is decorative: without them a model that
|
|
75
|
+
# "helpfully" adds an extra field, or emits an empty-string array entry,
|
|
76
|
+
# produces a proposal that passes here but fails ajv against the contract it
|
|
77
|
+
# claims to satisfy — and lessons are meant to be shared with other people.
|
|
78
|
+
# Each `keys` call is guarded by a preceding `type == "object"` check in the
|
|
79
|
+
# same `and` chain: jq's `and` short-circuits left to right, so `keys` on a
|
|
80
|
+
# missing/non-object value is never reached.
|
|
81
|
+
_LIBRARIAN_LESSON_STRUCTURAL='
|
|
82
|
+
(.claim | type) == "string" and (.claim | length) > 0
|
|
83
|
+
and (.rationale | type) == "string" and (.rationale | length) > 0
|
|
84
|
+
and (.evidence | type) == "object"
|
|
85
|
+
and ((.evidence | keys) - ["artifact_ids", "session_ids", "project_key", "observed_at", "resolution"] | length) == 0
|
|
86
|
+
and (.evidence.artifact_ids | type) == "array" and (.evidence.artifact_ids | length) > 0
|
|
87
|
+
and (.evidence.artifact_ids | all(type == "string" and test("^[0-9A-HJKMNP-TV-Z]{26}$")))
|
|
88
|
+
and (.evidence.session_ids | type) == "array" and (.evidence.session_ids | length) > 0
|
|
89
|
+
and (.evidence.session_ids | all(type == "string" and length > 0))
|
|
90
|
+
and (.evidence.project_key | type) == "string"
|
|
91
|
+
and (.evidence.project_key | test("^[0-9a-f]{12}$"))
|
|
92
|
+
and (.evidence.observed_at | type) == "string"
|
|
93
|
+
and (.evidence.observed_at | test("^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"))
|
|
94
|
+
and (.evidence.resolution | type) == "string" and (.evidence.resolution | length) > 0
|
|
95
|
+
and (.applies_to | type) == "object"
|
|
96
|
+
and ((.applies_to | keys) - ["stack", "scope", "file_patterns", "task_kinds"] | length) == 0
|
|
97
|
+
and (.applies_to.stack | type) == "array" and (.applies_to.stack | length) > 0
|
|
98
|
+
and (.applies_to.stack | all(type == "string" and length > 0))
|
|
99
|
+
and (.applies_to.file_patterns | type) == "array"
|
|
100
|
+
and (.applies_to.file_patterns | all(type == "string" and length > 0))
|
|
101
|
+
and (.applies_to.task_kinds | type) == "array"
|
|
102
|
+
and (.applies_to.task_kinds | all(type == "string" and length > 0))
|
|
103
|
+
'
|
|
104
|
+
|
|
105
|
+
# The versioned branch, shared by both validators.
|
|
106
|
+
_LIBRARIAN_LESSON_SCOPE_VERSIONED='
|
|
107
|
+
.applies_to.scope.kind == "versioned"
|
|
108
|
+
and ((.applies_to.scope | keys) - ["kind", "versions"] | length) == 0
|
|
109
|
+
and (.applies_to.scope.versions | type) == "object"
|
|
110
|
+
and (.applies_to.scope.versions | length) > 0
|
|
111
|
+
'
|
|
112
|
+
|
|
113
|
+
# Checks that only apply to a versioned candidate: the cross-field rule JSON
|
|
114
|
+
# Schema cannot express, and the range pattern on each value.
|
|
115
|
+
#
|
|
116
|
+
# NUL-delimited, not newline-delimited: a range value with an embedded newline
|
|
117
|
+
# would otherwise split into two lines that can each pass individually even
|
|
118
|
+
# though the single value they came from is not a valid range. Do not skip
|
|
119
|
+
# empty reads either — jq never emits one for a non-empty object of strings,
|
|
120
|
+
# so an empty read means the range itself is empty, which is invalid.
|
|
121
|
+
_librarian_lesson_check_versions() {
|
|
122
|
+
local candidate="$1"
|
|
123
|
+
|
|
124
|
+
printf '%s' "$candidate" | jq -e '
|
|
125
|
+
(.applies_to.scope.versions | keys) - .applies_to.stack | length == 0
|
|
126
|
+
' >/dev/null 2>&1 || return 1
|
|
127
|
+
|
|
128
|
+
local range
|
|
129
|
+
while IFS= read -r -d '' range; do
|
|
130
|
+
librarian_lesson_valid_range "$range" || return 1
|
|
131
|
+
done < <(printf '%s' "$candidate" | jq --raw-output0 '.applies_to.scope.versions[]' 2>/dev/null)
|
|
132
|
+
|
|
133
|
+
return 0
|
|
134
|
+
}
|
|
135
|
+
|
|
60
136
|
# Validate a full candidate. Prints nothing on success; prints a reason slug
|
|
61
137
|
# to stderr on failure.
|
|
62
138
|
#
|
|
@@ -65,76 +141,69 @@ librarian_lesson_validate_candidate() {
|
|
|
65
141
|
local candidate="${1:-}"
|
|
66
142
|
[[ -z "$candidate" ]] && { printf 'schema_invalid\n' >&2; return 1; }
|
|
67
143
|
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
# The `keys - [...] | length == 0` checks mirror `additionalProperties:
|
|
77
|
-
# false` on the vendored `evidence` and `applies_to` sub-schemas
|
|
78
|
-
# (including the "versioned" scope branch), and the `all(type ==
|
|
79
|
-
# "string" and length > 0)` checks mirror their array items' `minLength:
|
|
80
|
-
# 1`. Neither is decorative: without them a model that "helpfully" adds
|
|
81
|
-
# an extra field, or emits an empty-string array entry, produces a
|
|
82
|
-
# proposal that passes here but fails ajv against the contract it claims
|
|
83
|
-
# to satisfy — and lessons are meant to be shared with other people. Each
|
|
84
|
-
# `keys` call is guarded by a preceding `type == "object"` check in the
|
|
85
|
-
# same `and` chain: jq's `and` short-circuits left to right, so `keys` on
|
|
86
|
-
# a missing/non-object value is never reached.
|
|
87
|
-
if ! printf '%s' "$candidate" | jq -e '
|
|
88
|
-
(.claim | type) == "string" and (.claim | length) > 0
|
|
89
|
-
and (.rationale | type) == "string" and (.rationale | length) > 0
|
|
90
|
-
and (.evidence | type) == "object"
|
|
91
|
-
and ((.evidence | keys) - ["artifact_ids", "session_ids", "project_key", "observed_at", "resolution"] | length) == 0
|
|
92
|
-
and (.evidence.artifact_ids | type) == "array" and (.evidence.artifact_ids | length) > 0
|
|
93
|
-
and (.evidence.artifact_ids | all(type == "string" and test("^[0-9A-HJKMNP-TV-Z]{26}$")))
|
|
94
|
-
and (.evidence.session_ids | type) == "array" and (.evidence.session_ids | length) > 0
|
|
95
|
-
and (.evidence.session_ids | all(type == "string" and length > 0))
|
|
96
|
-
and (.evidence.project_key | type) == "string"
|
|
97
|
-
and (.evidence.project_key | test("^[0-9a-f]{12}$"))
|
|
98
|
-
and (.evidence.observed_at | type) == "string"
|
|
99
|
-
and (.evidence.observed_at | test("^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"))
|
|
100
|
-
and (.evidence.resolution | type) == "string" and (.evidence.resolution | length) > 0
|
|
101
|
-
and (.applies_to | type) == "object"
|
|
102
|
-
and ((.applies_to | keys) - ["stack", "scope", "file_patterns", "task_kinds"] | length) == 0
|
|
103
|
-
and (.applies_to.stack | type) == "array" and (.applies_to.stack | length) > 0
|
|
104
|
-
and (.applies_to.stack | all(type == "string" and length > 0))
|
|
105
|
-
and (.applies_to.file_patterns | type) == "array"
|
|
106
|
-
and (.applies_to.file_patterns | all(type == "string" and length > 0))
|
|
107
|
-
and (.applies_to.task_kinds | type) == "array"
|
|
108
|
-
and (.applies_to.task_kinds | all(type == "string" and length > 0))
|
|
109
|
-
and .applies_to.scope.kind == "versioned"
|
|
110
|
-
and ((.applies_to.scope | keys) - ["kind", "versions"] | length) == 0
|
|
111
|
-
and (.applies_to.scope.versions | type) == "object"
|
|
112
|
-
and (.applies_to.scope.versions | length) > 0
|
|
113
|
-
' >/dev/null 2>&1; then
|
|
144
|
+
# versioned ONLY. This is the guarantee that stops the transform minting
|
|
145
|
+
# lessons that never expire: private lessons run no jury, so nothing
|
|
146
|
+
# downstream would catch a bad version_independent claim. A human may
|
|
147
|
+
# assert that branch — see librarian_lesson_validate_confirmed — because
|
|
148
|
+
# the constraint in the review path forces it to a judged visibility.
|
|
149
|
+
if ! printf '%s' "$candidate" | jq -e \
|
|
150
|
+
"${_LIBRARIAN_LESSON_STRUCTURAL} and ${_LIBRARIAN_LESSON_SCOPE_VERSIONED}" \
|
|
151
|
+
>/dev/null 2>&1; then
|
|
114
152
|
printf 'schema_invalid\n' >&2
|
|
115
153
|
return 1
|
|
116
154
|
fi
|
|
117
155
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
156
|
+
_librarian_lesson_check_versions "$candidate" || {
|
|
157
|
+
printf 'schema_invalid\n' >&2
|
|
158
|
+
return 1
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return 0
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
# Validate a candidate a human has confirmed.
|
|
165
|
+
#
|
|
166
|
+
# Identical to librarian_lesson_validate_candidate except that it also permits
|
|
167
|
+
# the version_independent branch, which requires a non-empty justification.
|
|
168
|
+
#
|
|
169
|
+
# The two are NOT redundant and the difference is not stylistic. They encode
|
|
170
|
+
# different trust: this one bounds what a human may assert AND a jury will then
|
|
171
|
+
# check, because the review path refuses version_independent at private
|
|
172
|
+
# visibility. The transform's validator bounds what a model may assert
|
|
173
|
+
# unsupervised, where nothing downstream would catch a bad claim. Deleting
|
|
174
|
+
# either collapses that distinction.
|
|
175
|
+
#
|
|
176
|
+
# Usage: librarian_lesson_validate_confirmed <candidate_json>
|
|
177
|
+
librarian_lesson_validate_confirmed() {
|
|
178
|
+
local candidate="${1:-}"
|
|
179
|
+
[[ -z "$candidate" ]] && { printf 'schema_invalid\n' >&2; return 1; }
|
|
180
|
+
|
|
181
|
+
local scope_clause='
|
|
182
|
+
(
|
|
183
|
+
('"${_LIBRARIAN_LESSON_SCOPE_VERSIONED}"')
|
|
184
|
+
or (
|
|
185
|
+
.applies_to.scope.kind == "version_independent"
|
|
186
|
+
and ((.applies_to.scope | keys) - ["kind", "justification"] | length) == 0
|
|
187
|
+
and (.applies_to.scope.justification | type) == "string"
|
|
188
|
+
and (.applies_to.scope.justification | length) > 0
|
|
189
|
+
)
|
|
190
|
+
)
|
|
191
|
+
'
|
|
192
|
+
|
|
193
|
+
if ! printf '%s' "$candidate" | jq -e \
|
|
194
|
+
"${_LIBRARIAN_LESSON_STRUCTURAL} and ${scope_clause}" \
|
|
195
|
+
>/dev/null 2>&1; then
|
|
123
196
|
printf 'schema_invalid\n' >&2
|
|
124
197
|
return 1
|
|
125
198
|
fi
|
|
126
199
|
|
|
127
|
-
#
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
local range
|
|
135
|
-
while IFS= read -r -d '' range; do
|
|
136
|
-
librarian_lesson_valid_range "$range" || { printf 'schema_invalid\n' >&2; return 1; }
|
|
137
|
-
done < <(printf '%s' "$candidate" | jq --raw-output0 '.applies_to.scope.versions[]' 2>/dev/null)
|
|
200
|
+
# Range and subset rules apply only to the versioned branch.
|
|
201
|
+
if printf '%s' "$candidate" | jq -e '.applies_to.scope.kind == "versioned"' >/dev/null 2>&1; then
|
|
202
|
+
_librarian_lesson_check_versions "$candidate" || {
|
|
203
|
+
printf 'schema_invalid\n' >&2
|
|
204
|
+
return 1
|
|
205
|
+
}
|
|
206
|
+
fi
|
|
138
207
|
|
|
139
208
|
return 0
|
|
140
209
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: librarian
|
|
3
|
-
description: Review the librarian's pending memory promotion proposals queued from past sessions. Walk pending entries with the user one at a time, surfacing provenance and conflict state, and route each to accept (writes the typed memory file and updates MEMORY.md), reject (writes a body-hash tombstone so the same content won't re-propose), or defer (leave in the queue). Use when the user types `/librarian`, `/librarian review`, `/librarian triage`, `/librarian status`, or `/librarian
|
|
3
|
+
description: Review the librarian's pending memory promotion proposals and lesson candidates queued from past sessions. Walk pending entries with the user one at a time, surfacing provenance and conflict state, and route each to accept (writes the typed memory file and updates MEMORY.md), reject (writes a body-hash tombstone so the same content won't re-propose), or defer (leave in the queue). Lesson candidates route separately to confirm (with a visibility), pass, or defer, with unconfirm to take back a confirmation before the jury sees it. Use when the user types `/librarian`, `/librarian review`, `/librarian triage`, `/librarian status`, `/librarian list`, `/librarian lessons`, `/librarian lessons review`, or `/librarian lessons judge`, or asks to review librarian proposals or lesson candidates.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Librarian: Promotion Queue Review
|
|
@@ -17,6 +17,9 @@ Read the user's argument after `/librarian`:
|
|
|
17
17
|
- `list` → print the pending table and stop
|
|
18
18
|
- `status` → print one-line counts and stop
|
|
19
19
|
- a proposal id (starts with a ULID-shaped string) → jump straight to **show** for that id
|
|
20
|
+
- `lessons`, `lessons review` → **walk the lesson queue** (see below)
|
|
21
|
+
- `lessons list` / `lessons status` → print and stop (`lessons list --confirmed` lists confirmed lessons instead of pending ones)
|
|
22
|
+
- `lessons judge` → **run the jury over confirmed candidates** (see below)
|
|
20
23
|
|
|
21
24
|
If the user passes a free-form intent ("clear out the queue", "what's pending?"), map it to `review` or `list` as appropriate.
|
|
22
25
|
|
|
@@ -26,13 +29,23 @@ Source the plugin helpers and invoke `librarian_cli`. Run this in a single bash
|
|
|
26
29
|
|
|
27
30
|
```bash
|
|
28
31
|
set -uo pipefail
|
|
32
|
+
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
|
|
33
|
+
export CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT"
|
|
29
34
|
source "$CLAUDE_PLUGIN_ROOT/scripts/lib/librarian-config.sh"
|
|
30
35
|
source "$CLAUDE_PLUGIN_ROOT/scripts/lib/librarian-project-key.sh"
|
|
31
36
|
source "$CLAUDE_PLUGIN_ROOT/scripts/lib/librarian-storage.sh"
|
|
32
37
|
source "$CLAUDE_PLUGIN_ROOT/scripts/lib/librarian-emit.sh"
|
|
38
|
+
source "$CLAUDE_PLUGIN_ROOT/scripts/lib/librarian-lesson-storage.sh"
|
|
39
|
+
source "$CLAUDE_PLUGIN_ROOT/scripts/lib/librarian-lesson-validate.sh"
|
|
40
|
+
source "$CLAUDE_PLUGIN_ROOT/scripts/lib/librarian-lesson-review.sh"
|
|
41
|
+
source "$CLAUDE_PLUGIN_ROOT/scripts/lib/librarian-lesson-rubric.sh"
|
|
42
|
+
source "$CLAUDE_PLUGIN_ROOT/scripts/lib/librarian-lesson-judge.sh"
|
|
43
|
+
source "$CLAUDE_PLUGIN_ROOT/scripts/lib/librarian-author-key.sh"
|
|
44
|
+
source "$CLAUDE_PLUGIN_ROOT/scripts/lib/librarian-lesson-promote.sh"
|
|
33
45
|
source "$CLAUDE_PLUGIN_ROOT/scripts/lib/librarian-cli.sh"
|
|
34
46
|
|
|
35
47
|
# action is one of: list | show <id> | accept <id> | reject <id> [reason] | defer <id> | status
|
|
48
|
+
# or: lessons <list [--confirmed]|show <id>|confirm <id> <visibility> [--justification TEXT]|pass <id> [reason]|unconfirm <id>|defer <id>|status>
|
|
36
49
|
librarian_cli "<action>" "<args...>"
|
|
37
50
|
```
|
|
38
51
|
|
|
@@ -55,9 +68,93 @@ For `review` (the default), loop:
|
|
|
55
68
|
|
|
56
69
|
For `list` and `status`, just call `librarian_cli <action>` once and render the output.
|
|
57
70
|
|
|
71
|
+
## The lesson walkthrough
|
|
72
|
+
|
|
73
|
+
Lesson candidates are a separate queue from memory proposals — confirming one is a step toward publishing beyond this machine, not writing a local file, so it gets its own walk rather than folding into the loop above.
|
|
74
|
+
|
|
75
|
+
For `lessons` / `lessons review` (default for the `lessons` route), loop:
|
|
76
|
+
|
|
77
|
+
1. Call `librarian_cli lessons list`. If the output says `No pending lessons.`, tell the user the queue is clear and stop.
|
|
78
|
+
2. Take the first pending id. Call `librarian_cli lessons show <id>` to render it.
|
|
79
|
+
3. Present the candidate to the user in plain English, showing: `claim`, `rationale`, `evidence.resolution`, `applies_to.stack`, `scope.versions` (or the scope kind if unversioned), and the source artifact id.
|
|
80
|
+
4. Ask the user how to route it: **confirm** (with a visibility), **pass** (optionally with a reason), or **defer**.
|
|
81
|
+
5. Route the answer:
|
|
82
|
+
- **confirm** → `librarian_cli lessons confirm <id> <visibility> [--justification TEXT]`. Confirming **requires** a visibility (`private`, `org`, or `public`) — never call confirm without one. `--justification` rewrites the scope to `version_independent` and requires `org` or `public` visibility: a `private` lesson runs no jury, so its justification would go unchecked. If the user wants version-independence at `private`, tell them why it's refused rather than silently dropping the justification.
|
|
83
|
+
- **pass** → `librarian_cli lessons pass <id> "<reason>"`. The reason is optional.
|
|
84
|
+
- **defer** → `librarian_cli lessons defer <id>`. The candidate stays pending; mention it'll resurface next session.
|
|
85
|
+
6. After each routed decision, fetch the next pending id and repeat. When `list` returns no rows, finish with `librarian_cli lessons status` so the user sees the final count.
|
|
86
|
+
|
|
87
|
+
A confirm made at the wrong visibility isn't stuck that way — `librarian_cli lessons unconfirm <id>` takes it back, returning the lesson to pending so the user can confirm it again at the right visibility. It only works on a `confirmed` lesson: a `passed` lesson stays passed, because that decision already has its own record and unconfirm won't touch it.
|
|
88
|
+
|
|
89
|
+
Finding the lesson is its own step, because `lessons list` shows only the pending queue and a confirmed lesson is by definition not in it. The user regretting a confirm is usually back in a later session with the claim in mind and no id, so start from `librarian_cli lessons list --confirmed`, which prints the same `<id> <claim>` rows for everything confirmed and not yet judged. Then `librarian_cli lessons show <id>` — its `visibility` line is what tells you which tier the lesson currently sits at, and whether unconfirming is what the user actually wants.
|
|
90
|
+
|
|
91
|
+
For `lessons list`, `lessons list --confirmed`, and `lessons status`, just call `librarian_cli lessons <action>` once and render the output.
|
|
92
|
+
|
|
93
|
+
For `lessons judge`, run the jury over confirmed candidates:
|
|
94
|
+
|
|
95
|
+
1. Call `librarian_cli lessons list --confirmed --json`. Each row carries `id`,
|
|
96
|
+
`visibility`, and the full `candidate`. If the array is empty, tell the user
|
|
97
|
+
there is nothing awaiting judgment and stop.
|
|
98
|
+
2. **Report the batch before spending anything.** Say how many candidates are
|
|
99
|
+
confirmed and how many are `public`, and ask whether to proceed. This is the
|
|
100
|
+
most expensive step in the pipeline. If the user declines, stop — nothing is
|
|
101
|
+
written and every candidate stays `confirmed`.
|
|
102
|
+
3. For each candidate, in order:
|
|
103
|
+
- **If `visibility` is `private`, dispatch no judges at all.** Call
|
|
104
|
+
`librarian_cli lessons judge <id> '[]'` and move on. Private lessons run no
|
|
105
|
+
jury; that is what makes cost scale with intent rather than artifact volume.
|
|
106
|
+
- Otherwise spawn **both** `tribunal-judge-standard` and
|
|
107
|
+
`tribunal-judge-adversarial` with the Task tool. Give each the candidate's
|
|
108
|
+
`claim`, `rationale`, `evidence.resolution`, and `applies_to`, plus the
|
|
109
|
+
rubric criteria for its visibility: for `org`, grounding / scope_accuracy /
|
|
110
|
+
generality; for `public`, those three plus **disclosure** — does the text
|
|
111
|
+
leak a credential, internal hostname, customer name, or proprietary detail?
|
|
112
|
+
- Each judge returns a JSON object with `score`, `passed`, `judge_type`,
|
|
113
|
+
`feedback_summary`, and `criterion_scores` — a map from **each rubric
|
|
114
|
+
criterion name you gave it** to a score in `[0,1]`. **Tell each judge it
|
|
115
|
+
must score every criterion you listed.** Every criterion in both lesson
|
|
116
|
+
rubrics carries a floor, and a floor no judge scored makes the whole panel
|
|
117
|
+
UNJUDGED — the candidate stays `confirmed` and is re-judged, at full cost,
|
|
118
|
+
on the next run. So an omission here does not soften a verdict; it prevents
|
|
119
|
+
one. That differs from tribunal, which degrades to a plain mean instead of
|
|
120
|
+
refusing, and the shared judge agents describe tribunal's behavior.
|
|
121
|
+
A judge that genuinely cannot assess a criterion should say so in
|
|
122
|
+
`feedback_summary` and score its honest worst case rather than omit the
|
|
123
|
+
key. Collect both verdicts into a JSON array **verbatim** — never summarize
|
|
124
|
+
or reconstruct a judge's verdict.
|
|
125
|
+
- Call `librarian_cli lessons judge <id> '<verdicts-json>'`. Record before
|
|
126
|
+
moving to the next candidate, so an interrupted run costs at most one
|
|
127
|
+
re-judgment. Recording a verdict also promotes the lesson in the same
|
|
128
|
+
call — approved candidates land in the pool and rejected ones in the
|
|
129
|
+
declined ledger, with no separate step. If the output instead says the
|
|
130
|
+
lesson was judged but not promoted, the verdict is already recorded;
|
|
131
|
+
retry once with `librarian_cli lessons promote <id>`, not by re-judging —
|
|
132
|
+
re-judging would spend tokens again for a verdict that already exists.
|
|
133
|
+
**If that retry itself fails, do not loop on it.** Add the id to the
|
|
134
|
+
judged-but-not-promoted bucket and move to the next candidate; report it
|
|
135
|
+
at the end alongside the retry command rather than retrying again here.
|
|
136
|
+
4. **If either judge fails to return parseable JSON, do not invent a verdict and
|
|
137
|
+
do not drop that judge.** The array must still have one entry per empaneled
|
|
138
|
+
judge: put that judge's raw output, as a JSON string, in its slot instead of
|
|
139
|
+
a parsed verdict object. Pass that array to the CLI; it will exit 2, leave
|
|
140
|
+
the candidate `confirmed`, and report that it could not be judged. Collect
|
|
141
|
+
those ids and list them at the end so the user knows to re-run. A broken judge
|
|
142
|
+
must never become a rejection — the artifact's watermark has already moved,
|
|
143
|
+
so a false rejection buries a good lesson permanently.
|
|
144
|
+
5. Finish by reporting counts: approved, rejected, could-not-judge, and
|
|
145
|
+
**judged but not promoted** (verdict recorded, pool write still pending —
|
|
146
|
+
list these ids with `librarian_cli lessons promote <id>` as the next step).
|
|
147
|
+
Don't fold this bucket into "approved": a lesson here has no pool entry yet.
|
|
148
|
+
|
|
149
|
+
`scope_accuracy` is the criterion that matters most on a `version_independent`
|
|
150
|
+
candidate. The schema guarantees such a lesson **carries** a justification; this
|
|
151
|
+
criterion asks whether it is **true**.
|
|
152
|
+
|
|
58
153
|
## Safety rules
|
|
59
154
|
|
|
60
155
|
- **Never accept a proposal on the user's behalf without explicit confirmation.** Accepting writes a file to the user's typed memory store and that memory will be loaded into every future session in this project. Treat each accept like editing a CLAUDE.md.
|
|
61
156
|
- **Do not edit MEMORY.md directly.** `accept` updates the index for you; hand-editing risks duplicate entries or stale links.
|
|
62
157
|
- **Do not delete proposal files manually.** Reject (with a tombstone) is the cleanup path. Direct deletion would let the same body re-propose on the next scan.
|
|
63
158
|
- **Conflict-state proposals deserve a careful read.** When `conflict_state` is `near_duplicate` or `contradicts_existing`, surface the conflict to the user before they decide. Often the right answer is reject (the existing memory is better) or accept-and-then-prune (you can mention that follow-up).
|
|
159
|
+
- **Never confirm a lesson on the user's behalf without an explicit visibility.** Confirming commits a candidate toward leaving this machine — a decision separate from, and heavier than, accepting a memory proposal.
|
|
160
|
+
- **Never dispatch judges without reporting the batch and getting the user's go-ahead first.** Judge dispatch is the most expensive step in the pipeline; report the confirmed and `public` counts and wait before spawning a single judge.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tribunal",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.7",
|
|
4
4
|
"description": "Multi-agent execution with LLM-as-a-Judge quality gates. An Actor performs work; a jury of typed Judges scores it against a project-overridable rubric; a Meta-Judge reviews the jury for bias; the gate decides accept, retry, or exhaust. Grounded in LLM-as-a-Judge (Zheng et al. 2023) and LLM-as-a-Meta-Judge (Wu et al. 2024). Builds on the Onlooker ecosystem plugin.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Onlooker Community",
|
|
@@ -1,5 +1,68 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.2.7](https://github.com/onlooker-community/ecosystem/compare/tribunal-v1.2.6...tribunal-v1.2.7) (2026-08-16)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **tribunal:** let every blocking arm name the floor it tripped over :label: ([#166](https://github.com/onlooker-community/ecosystem/issues/166)) ([1819b5f](https://github.com/onlooker-community/ecosystem/commit/1819b5f7ad0348584bcb0e72e28c9af153ba3a4d))
|
|
9
|
+
|
|
10
|
+
## [1.2.6](https://github.com/onlooker-community/ecosystem/compare/tribunal-v1.2.5...tribunal-v1.2.6) (2026-08-16)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **tribunal:** stop a low criterion from hiding why the panel failed :arrows_counterclockwise: ([#164](https://github.com/onlooker-community/ecosystem/issues/164)) ([a3ec128](https://github.com/onlooker-community/ecosystem/commit/a3ec128f1e2e723e02396580b9ffbcff26f0787f))
|
|
16
|
+
|
|
17
|
+
## [1.2.5](https://github.com/onlooker-community/ecosystem/compare/tribunal-v1.2.4...tribunal-v1.2.5) (2026-08-16)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Bug Fixes
|
|
21
|
+
|
|
22
|
+
* **tribunal:** a floor you cannot read is a floor you cannot clear :lock: ([#162](https://github.com/onlooker-community/ecosystem/issues/162)) ([4ff2ec0](https://github.com/onlooker-community/ecosystem/commit/4ff2ec0f772af51ad4b5ea631ae2d3c68d229499))
|
|
23
|
+
|
|
24
|
+
## [1.2.4](https://github.com/onlooker-community/ecosystem/compare/tribunal-v1.2.3...tribunal-v1.2.4) (2026-08-16)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### Bug Fixes
|
|
28
|
+
|
|
29
|
+
* **tribunal:** refuse a score that is not on the scale it claims :straight_ruler: ([#160](https://github.com/onlooker-community/ecosystem/issues/160)) ([bab2855](https://github.com/onlooker-community/ecosystem/commit/bab28558a68402c1dd67623f469467fd99302ba6))
|
|
30
|
+
|
|
31
|
+
## [1.2.3](https://github.com/onlooker-community/ecosystem/compare/tribunal-v1.2.2...tribunal-v1.2.3) (2026-08-16)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
### Bug Fixes
|
|
35
|
+
|
|
36
|
+
* **tribunal:** stop a verdict nobody scored from voting to approve :no_entry: ([#158](https://github.com/onlooker-community/ecosystem/issues/158)) ([1d6c2dc](https://github.com/onlooker-community/ecosystem/commit/1d6c2dc9c8f5833891732dc7e6637c496ba81f6f))
|
|
37
|
+
|
|
38
|
+
## [1.2.2](https://github.com/onlooker-community/ecosystem/compare/tribunal-v1.2.1...tribunal-v1.2.2) (2026-08-15)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
### Bug Fixes
|
|
42
|
+
|
|
43
|
+
* **tribunal:** drop scoreless verdicts instead of averaging them in :bug: ([#154](https://github.com/onlooker-community/ecosystem/issues/154)) ([f546757](https://github.com/onlooker-community/ecosystem/commit/f5467574f08094b0369f0bd699983cb41ded2cc3))
|
|
44
|
+
|
|
45
|
+
## [1.2.1](https://github.com/onlooker-community/ecosystem/compare/tribunal-v1.2.0...tribunal-v1.2.1) (2026-08-15)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
### Bug Fixes
|
|
49
|
+
|
|
50
|
+
* **tribunal:** close two ways a criterion floor could still be escaped :relieved: ([#152](https://github.com/onlooker-community/ecosystem/issues/152)) ([5b2ddee](https://github.com/onlooker-community/ecosystem/commit/5b2ddee8a787ba0f3e8656a6522b12f348f74859))
|
|
51
|
+
|
|
52
|
+
## [1.2.0](https://github.com/onlooker-community/ecosystem/compare/tribunal-v1.1.3...tribunal-v1.2.0) (2026-08-15)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
### Features
|
|
56
|
+
|
|
57
|
+
* **tribunal,librarian:** make rubric weights and min_pass floors real :straight_ruler: ([#150](https://github.com/onlooker-community/ecosystem/issues/150)) ([f8f8e28](https://github.com/onlooker-community/ecosystem/commit/f8f8e28b60f6d13d5a2a54e26ca284137b77f99a))
|
|
58
|
+
|
|
59
|
+
## [1.1.3](https://github.com/onlooker-community/ecosystem/compare/tribunal-v1.1.2...tribunal-v1.1.3) (2026-08-10)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
### Bug Fixes
|
|
63
|
+
|
|
64
|
+
* make the local bats suite tell the truth :mag: ([#135](https://github.com/onlooker-community/ecosystem/issues/135)) ([f0763e0](https://github.com/onlooker-community/ecosystem/commit/f0763e09f3caf2d39c89f28befd12567af0af845))
|
|
65
|
+
|
|
3
66
|
## [1.1.2](https://github.com/onlooker-community/ecosystem/compare/tribunal-v1.1.1...tribunal-v1.1.2) (2026-08-02)
|
|
4
67
|
|
|
5
68
|
|
|
@@ -31,6 +31,32 @@ You are the **Adversarial Judge** in a Tribunal jury. Your job is to try, in goo
|
|
|
31
31
|
- A single vague "this might fail" is worth `0.0` — name the input or do not raise it.
|
|
32
32
|
- If you genuinely cannot falsify, score `0.85+` and say so. Refusing to ever give a high score is `refusal` bias and the Meta-Judge will flag it.
|
|
33
33
|
|
|
34
|
+
## Scoring against the rubric
|
|
35
|
+
|
|
36
|
+
You are given a rubric with named criteria, each carrying a weight and a
|
|
37
|
+
`min_pass` floor. Your falsification work is how you form a judgment; the
|
|
38
|
+
rubric's criteria are how you report it.
|
|
39
|
+
|
|
40
|
+
Report a score in `[0,1]` for every rubric criterion in `criterion_scores`,
|
|
41
|
+
keyed by the rubric's own names. Your `criteria_evaluated` list stays what it
|
|
42
|
+
has always been — the dimensions you probed (edge cases, concurrency,
|
|
43
|
+
idempotency). The two lists are not expected to match.
|
|
44
|
+
|
|
45
|
+
`safety` in particular is a criterion you are well placed to score and no other
|
|
46
|
+
default judge covers. A crash on malformed input, a non-idempotent migration, a
|
|
47
|
+
race that corrupts state — those are safety findings, and this is where they
|
|
48
|
+
belong.
|
|
49
|
+
|
|
50
|
+
**Do not score `0` for a criterion you did not assess.** A `0` says you assessed
|
|
51
|
+
it and it failed, which on a criterion with a floor blocks the task by itself.
|
|
52
|
+
|
|
53
|
+
Omitting the key instead is handled by the caller's policy, and the policies
|
|
54
|
+
differ: tribunal treats it as a coverage gap and falls back to a plain mean,
|
|
55
|
+
while other callers refuse to judge the panel at all when a floored criterion
|
|
56
|
+
went unscored. So an omission is not the safe middle option — prefer scoring
|
|
57
|
+
your honest worst case and saying what you could not assess in
|
|
58
|
+
`feedback_summary`.
|
|
59
|
+
|
|
34
60
|
## Output format
|
|
35
61
|
|
|
36
62
|
Final message is a single JSON object — no prose, no fence:
|
|
@@ -41,6 +67,12 @@ Final message is a single JSON object — no prose, no fence:
|
|
|
41
67
|
"passed": false,
|
|
42
68
|
"judge_type": "adversarial",
|
|
43
69
|
"criteria_evaluated": ["edge-cases", "concurrency", "idempotency"],
|
|
70
|
+
"criterion_scores": {
|
|
71
|
+
"correctness": 0.5,
|
|
72
|
+
"completeness": 0.6,
|
|
73
|
+
"safety": 0.55,
|
|
74
|
+
"clarity": 0.8
|
|
75
|
+
},
|
|
44
76
|
"strengths_count": 1,
|
|
45
77
|
"weaknesses_count": 2,
|
|
46
78
|
"confidence": 0.8,
|
|
@@ -48,4 +80,6 @@ Final message is a single JSON object — no prose, no fence:
|
|
|
48
80
|
}
|
|
49
81
|
```
|
|
50
82
|
|
|
83
|
+
The `criterion_scores` keys above are the **default rubric's** criteria, shown as an example. **The rubric you are given governs** — score its criterion names, whatever they are. Other callers ship different rubrics; librarian's lesson rubrics, for instance, use `grounding`, `scope_accuracy`, `generality`, and `disclosure`.
|
|
84
|
+
|
|
51
85
|
`feedback_summary` should describe each falsification with enough specificity that the Actor can reproduce it on retry.
|
|
@@ -27,6 +27,31 @@ You are the **Security Judge** in a Tribunal jury. Score the Actor's output excl
|
|
|
27
27
|
- Read the changed files. Do not score from the summary.
|
|
28
28
|
- Do not flag style or hypothetical "could be exploited if…" without a concrete attack chain. The Meta-Judge will mark you as `biased` if you over-report.
|
|
29
29
|
|
|
30
|
+
## Scoring against the rubric
|
|
31
|
+
|
|
32
|
+
You are given a rubric with named criteria, each carrying a weight and a
|
|
33
|
+
`min_pass` floor. Your findings are how you form a judgment; the rubric's
|
|
34
|
+
criteria are how you report it.
|
|
35
|
+
|
|
36
|
+
Report a score in `[0,1]` for every rubric criterion in `criterion_scores`,
|
|
37
|
+
keyed by the rubric's own names. Your `criteria_evaluated` list stays what it
|
|
38
|
+
has always been — the dimensions you swept (injection, secrets, path
|
|
39
|
+
traversal). The two lists are not expected to match.
|
|
40
|
+
|
|
41
|
+
`safety` is where your findings land. It carries the highest floor in the
|
|
42
|
+
default rubric, and a single unresolved injection or leaked credential should
|
|
43
|
+
put your `safety` score below it.
|
|
44
|
+
|
|
45
|
+
**Do not score `0` for a criterion you did not assess.** A `0` says you assessed
|
|
46
|
+
it and it failed, which on a criterion with a floor blocks the task by itself.
|
|
47
|
+
|
|
48
|
+
Omitting the key instead is handled by the caller's policy, and the policies
|
|
49
|
+
differ: tribunal treats it as a coverage gap and falls back to a plain mean,
|
|
50
|
+
while other callers refuse to judge the panel at all when a floored criterion
|
|
51
|
+
went unscored. So an omission is not the safe middle option — prefer scoring
|
|
52
|
+
your honest worst case and saying what you could not assess in
|
|
53
|
+
`feedback_summary`.
|
|
54
|
+
|
|
30
55
|
## Output format
|
|
31
56
|
|
|
32
57
|
Final message is a single JSON object — no prose, no fence:
|
|
@@ -37,6 +62,12 @@ Final message is a single JSON object — no prose, no fence:
|
|
|
37
62
|
"passed": false,
|
|
38
63
|
"judge_type": "security",
|
|
39
64
|
"criteria_evaluated": ["injection", "secrets", "path-traversal"],
|
|
65
|
+
"criterion_scores": {
|
|
66
|
+
"correctness": 0.7,
|
|
67
|
+
"completeness": 0.6,
|
|
68
|
+
"safety": 0.2,
|
|
69
|
+
"clarity": 0.75
|
|
70
|
+
},
|
|
40
71
|
"strengths_count": 1,
|
|
41
72
|
"weaknesses_count": 2,
|
|
42
73
|
"confidence": 0.9,
|
|
@@ -44,4 +75,6 @@ Final message is a single JSON object — no prose, no fence:
|
|
|
44
75
|
}
|
|
45
76
|
```
|
|
46
77
|
|
|
78
|
+
The `criterion_scores` keys above are the **default rubric's** criteria, shown as an example. **The rubric you are given governs** — score its criterion names, whatever they are. Other callers ship different rubrics; librarian's lesson rubrics, for instance, use `grounding`, `scope_accuracy`, `generality`, and `disclosure`.
|
|
79
|
+
|
|
47
80
|
When `passed: false`, every finding in `feedback_summary` must point at a file and (when possible) a line. Vague security objections waste the Actor's retry budget.
|
|
@@ -33,6 +33,12 @@ Your **final message** must be a single JSON object matching `TribunalVerdictPay
|
|
|
33
33
|
"passed": true,
|
|
34
34
|
"judge_type": "standard",
|
|
35
35
|
"criteria_evaluated": ["correctness", "completeness", "clarity"],
|
|
36
|
+
"criterion_scores": {
|
|
37
|
+
"correctness": 0.9,
|
|
38
|
+
"completeness": 0.75,
|
|
39
|
+
"safety": 0.85,
|
|
40
|
+
"clarity": 0.8
|
|
41
|
+
},
|
|
36
42
|
"strengths_count": 3,
|
|
37
43
|
"weaknesses_count": 1,
|
|
38
44
|
"confidence": 0.85,
|
|
@@ -40,8 +46,14 @@ Your **final message** must be a single JSON object matching `TribunalVerdictPay
|
|
|
40
46
|
}
|
|
41
47
|
```
|
|
42
48
|
|
|
49
|
+
The `criterion_scores` keys above are the **default rubric's** criteria, shown as an example. **The rubric you are given governs** — score its criterion names, whatever they are. Other callers ship different rubrics; librarian's lesson rubrics, for instance, use `grounding`, `scope_accuracy`, `generality`, and `disclosure`.
|
|
50
|
+
|
|
43
51
|
Required fields: `score`, `passed`, `judge_type`. `passed` reflects your own judgment based on the rubric thresholds — the orchestrator may still aggregate and override per gate policy.
|
|
44
52
|
|
|
53
|
+
`criterion_scores` maps **each criterion name from the rubric you were given** to your score for it in `[0,1]`. This is separate from `criteria_evaluated`, which lists the dimensions *you* chose to investigate — the rubric's criteria are what the orchestrator weights and floors.
|
|
54
|
+
|
|
55
|
+
Score every rubric criterion you can judge. **Omit any criterion you genuinely cannot assess — do not send `0` for it.** A `0` means "I assessed this and it failed"; an omission means "I did not assess this." The orchestrator treats them very differently: a `0` on a criterion with a floor blocks the task outright, while an omission is handled by the caller's policy. Tribunal reports it as a coverage gap and falls back to a plain mean; other callers refuse to judge a panel at all when a floored criterion went unscored. Do not treat an omission as the safe choice — say what you could not assess in `feedback_summary`.
|
|
56
|
+
|
|
45
57
|
`feedback_summary` should be 1–3 sentences. Name specific files and lines when you can. This is what the Actor sees on retry.
|
|
46
58
|
|
|
47
59
|
The orchestrator will inject `judge_id` and `iteration_id` when persisting your verdict.
|