@cratylus/canon 0.1.3 → 0.3.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/dist/agents/architect.d.ts +2 -0
- package/dist/agents/architect.js +113 -0
- package/dist/agents/architect.js.map +1 -0
- package/dist/agents/implementer.d.ts +2 -0
- package/dist/agents/implementer.js +74 -0
- package/dist/agents/implementer.js.map +1 -0
- package/dist/agents/kino.d.ts +2 -0
- package/dist/agents/kino.js +120 -0
- package/dist/agents/kino.js.map +1 -0
- package/dist/agents/mav.js +15 -4
- package/dist/agents/mav.js.map +1 -1
- package/dist/agents/nico.js +2 -2
- package/dist/agents/nico.js.map +1 -1
- package/dist/agents/planner.d.ts +2 -0
- package/dist/agents/planner.js +85 -0
- package/dist/agents/planner.js.map +1 -0
- package/dist/dimensions/autonomy/{check-in.d.ts → handoff.d.ts} +1 -1
- package/dist/dimensions/autonomy/{check-in.js → handoff.js} +16 -4
- package/dist/dimensions/autonomy/handoff.js.map +1 -0
- package/dist/dimensions/autonomy/human-on-the-loop.js +1 -1
- package/dist/dimensions/autonomy/human-on-the-loop.js.map +1 -1
- package/dist/dimensions/capabilities/film-production.d.ts +2 -0
- package/dist/dimensions/capabilities/film-production.js +8 -0
- package/dist/dimensions/capabilities/film-production.js.map +1 -0
- package/dist/dimensions/capabilities/generative-video.d.ts +2 -0
- package/dist/dimensions/capabilities/generative-video.js +8 -0
- package/dist/dimensions/capabilities/generative-video.js.map +1 -0
- package/dist/dimensions/formality/plain.js +1 -1
- package/dist/dimensions/formality/plain.js.map +1 -1
- package/dist/hook-cells.js +5 -2
- package/dist/hook-cells.js.map +1 -1
- package/dist/hooks/deploy-drift-notice.js +1 -1
- package/dist/hooks/deploy-drift-notice.js.map +1 -1
- package/dist/hooks/stance-guardrail-pre.js +56 -30
- package/dist/hooks/stance-guardrail-pre.js.map +1 -1
- package/dist/hooks/stance-guardrail.js +253 -38
- package/dist/hooks/stance-guardrail.js.map +1 -1
- package/dist/plan-states.d.ts +3 -3
- package/dist/plan-states.js +12 -8
- package/dist/plan-states.js.map +1 -1
- package/dist/skills/carry-on/skill.js +2 -2
- package/dist/skills/carry-on/skill.js.map +1 -1
- package/dist/skills/deliver/skill.d.ts +2 -0
- package/dist/skills/deliver/skill.js +98 -0
- package/dist/skills/deliver/skill.js.map +1 -0
- package/dist/skills/{praxis → design}/skill.d.ts +1 -1
- package/dist/skills/design/skill.js +79 -0
- package/dist/skills/design/skill.js.map +1 -0
- package/dist/skills/plan/skill.d.ts +2 -0
- package/dist/skills/plan/skill.js +89 -0
- package/dist/skills/plan/skill.js.map +1 -0
- package/package.json +4 -7
- package/dist/dimensions/autonomy/check-in.js.map +0 -1
- package/dist/dimensions/output-format/structured-decision.d.ts +0 -2
- package/dist/dimensions/output-format/structured-decision.js +0 -2
- package/dist/dimensions/output-format/structured-decision.js.map +0 -1
- package/dist/hooks/praxis-continuity.d.ts +0 -2
- package/dist/hooks/praxis-continuity.js +0 -132
- package/dist/hooks/praxis-continuity.js.map +0 -1
- package/dist/skills/praxis/skill.js +0 -193
- package/dist/skills/praxis/skill.js.map +0 -1
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// The craft the software SERVES, not the software. Its decomposition is the
|
|
2
|
+
// production ladder every studio already runs, and the two laws a generative
|
|
3
|
+
// pipeline most often collapses: IDENTITY (is this the same character) and
|
|
4
|
+
// CONTINUITY (does the staging flow) are different problems with different
|
|
5
|
+
// instruments, and an art-bible entry is approved ONCE and applied everywhere
|
|
6
|
+
// rather than re-specified per shot.
|
|
7
|
+
export const filmProduction = `film-production ≜ ⟨development breakdown art-bible shot-design continuity cut⟩ ⟨identity ⊥ continuity⟩ ⟨art-bible ≜ approve-once · apply-everywhere⟩ ⟨industry-standard ≻ invention⟩`;
|
|
8
|
+
//# sourceMappingURL=film-production.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"film-production.js","sourceRoot":"","sources":["../../../src/dimensions/capabilities/film-production.ts"],"names":[],"mappings":"AAEA,4EAA4E;AAC5E,6EAA6E;AAC7E,2EAA2E;AAC3E,2EAA2E;AAC3E,8EAA8E;AAC9E,qCAAqC;AACrC,MAAM,CAAC,MAAM,cAAc,GAAiB,sLAAsL,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// The generative half of the craft — what the community, not the studio, has
|
|
2
|
+
// standardized. The load-bearing clause is the third: reference conditioning is
|
|
3
|
+
// STATELESS (every request re-presents every reference; the model remembers
|
|
4
|
+
// nothing between shots), so whatever carries identity across a production is
|
|
5
|
+
// the ledger, never the model. Identity-lock is ranked: a trained adapter beats
|
|
6
|
+
// reference conditioning beats prompt-only description.
|
|
7
|
+
export const generativeVideo = `generative-video ≜ ⟨model-selection conditioning identity-lock drift-control⟩ ⟨conditioning stateless ⇒ ledger = the-memory⟩ ⟨identity-lock ≜ adapter ≻ reference ≻ prompt⟩ ⟨community-recommendation ≻ invention⟩`;
|
|
8
|
+
//# sourceMappingURL=generative-video.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generative-video.js","sourceRoot":"","sources":["../../../src/dimensions/capabilities/generative-video.ts"],"names":[],"mappings":"AAEA,6EAA6E;AAC7E,gFAAgF;AAChF,4EAA4E;AAC5E,8EAA8E;AAC9E,gFAAgF;AAChF,wDAAwD;AACxD,MAAM,CAAC,MAAM,eAAe,GAAiB,oNAAoN,CAAC"}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// for shorter replies buys prose with behaviour.
|
|
11
11
|
//
|
|
12
12
|
// REGISTER ONLY. The closing tail a report owes its operator is `autonomy`'s —
|
|
13
|
-
// `
|
|
13
|
+
// `handoff` names it, because what a reply must CONTAIN at the end is report
|
|
14
14
|
// structure, not a register. A value that governed both would be the one home
|
|
15
15
|
// for two concepts, and would apply the tail to whichever formality happened to
|
|
16
16
|
// be bound.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plain.js","sourceRoot":"","sources":["../../../src/dimensions/formality/plain.ts"],"names":[],"mappings":"AAEA,4EAA4E;AAC5E,0EAA0E;AAC1E,2EAA2E;AAC3E,4EAA4E;AAC5E,gEAAgE;AAChE,EAAE;AACF,gFAAgF;AAChF,+EAA+E;AAC/E,gFAAgF;AAChF,iDAAiD;AACjD,EAAE;AACF,+EAA+E;AAC/E,
|
|
1
|
+
{"version":3,"file":"plain.js","sourceRoot":"","sources":["../../../src/dimensions/formality/plain.ts"],"names":[],"mappings":"AAEA,4EAA4E;AAC5E,0EAA0E;AAC1E,2EAA2E;AAC3E,4EAA4E;AAC5E,gEAAgE;AAChE,EAAE;AACF,gFAAgF;AAChF,+EAA+E;AAC/E,gFAAgF;AAChF,iDAAiD;AACjD,EAAE;AACF,+EAA+E;AAC/E,6EAA6E;AAC7E,8EAA8E;AAC9E,gFAAgF;AAChF,YAAY;AACZ,EAAE;AACF,4EAA4E;AAC5E,8EAA8E;AAC9E,6EAA6E;AAC7E,gFAAgF;AAChF,yEAAyE;AACzE,EAAE;AACF,iFAAiF;AACjF,4EAA4E;AAC5E,iFAAiF;AACjF,+EAA+E;AAC/E,8EAA8E;AAC9E,EAAE;AACF,uEAAuE;AACvE,iFAAiF;AACjF,gFAAgF;AAChF,iFAAiF;AACjF,kFAAkF;AAClF,gFAAgF;AAChF,iFAAiF;AACjF,MAAM,CAAC,MAAM,KAAK,GAAc,yYAAyY,CAAC"}
|
package/dist/hook-cells.js
CHANGED
|
@@ -6,8 +6,11 @@
|
|
|
6
6
|
// (claude adapter: `turn.end` → Stop, `subagent.end` → SubagentStop) and carries
|
|
7
7
|
// their VERBATIM worker payloads so the projector stages them under `hooks/<id>/`
|
|
8
8
|
// (no file copy — the bytes come from the cell). Only `harness`-substrate cells
|
|
9
|
-
// register in settings.json
|
|
10
|
-
// git
|
|
9
|
+
// register in settings.json, and every surviving cell is one: the corpus's single
|
|
10
|
+
// `git`-substrate cell, `praxis-continuity`, retired with the praxis plan mirror it
|
|
11
|
+
// nudged about. The routing for a non-harness substrate is not hypothetical — it is
|
|
12
|
+
// exercised the moment such a cell exists again — but nothing carries it today, and
|
|
13
|
+
// a header claiming a live example is a claim a reader cannot check.
|
|
11
14
|
//
|
|
12
15
|
// The cell shapes are the schema's type kernel (`@cratylus/schema`); this
|
|
13
16
|
// module is the composition root that NAMES the concrete cells and nothing more.
|
package/dist/hook-cells.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hook-cells.js","sourceRoot":"","sources":["../src/hook-cells.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,2EAA2E;AAC3E,kFAAkF;AAClF,mFAAmF;AACnF,4EAA4E;AAC5E,iFAAiF;AACjF,kFAAkF;AAClF,gFAAgF;AAChF,
|
|
1
|
+
{"version":3,"file":"hook-cells.js","sourceRoot":"","sources":["../src/hook-cells.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,2EAA2E;AAC3E,kFAAkF;AAClF,mFAAmF;AACnF,4EAA4E;AAC5E,iFAAiF;AACjF,kFAAkF;AAClF,gFAAgF;AAChF,kFAAkF;AAClF,oFAAoF;AACpF,oFAAoF;AACpF,oFAAoF;AACpF,qEAAqE;AACrE,EAAE;AACF,0EAA0E;AAC1E,iFAAiF;AACjF,oFAAoF;AACpF,iFAAiF;AACjF,+EAA+E;AAE/E,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAG9D,wFAAwF;AACxF,MAAM,CAAC,MAAM,gBAAgB,GAAwB;IACnD,eAAe;IACf,kBAAkB;IAClB,iBAAiB;CAClB,CAAC"}
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
// copy with nothing further to say. That is not hypothetical and it is not rare —
|
|
34
34
|
// it is the ORDINARY shape of work here, where a session edits a cell, projects it,
|
|
35
35
|
// and keeps going under the version it just replaced. The 16-hour-old projection of
|
|
36
|
-
// an agent's own governing cell was this; so was a `
|
|
36
|
+
// an agent's own governing cell was this; so was a `plan` edit that landed under a
|
|
37
37
|
// running session hours later.
|
|
38
38
|
//
|
|
39
39
|
// `prompt.submit` IS THE RE-ARM, and it is the same question at a finer grain rather
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy-drift-notice.js","sourceRoot":"","sources":["../../src/hooks/deploy-drift-notice.ts"],"names":[],"mappings":"AAEA,kFAAkF;AAClF,kEAAkE;AAClE,EAAE;AACF,oFAAoF;AACpF,qFAAqF;AACrF,gFAAgF;AAChF,sFAAsF;AACtF,0EAA0E;AAC1E,mFAAmF;AACnF,gFAAgF;AAChF,qFAAqF;AACrF,EAAE;AACF,sFAAsF;AACtF,sFAAsF;AACtF,qFAAqF;AACrF,iFAAiF;AACjF,8BAA8B;AAC9B,EAAE;AACF,qFAAqF;AACrF,sFAAsF;AACtF,oFAAoF;AACpF,uFAAuF;AACvF,kFAAkF;AAClF,2BAA2B;AAC3B,EAAE;AACF,sFAAsF;AACtF,oFAAoF;AACpF,kEAAkE;AAClE,EAAE;AACF,mFAAmF;AACnF,oFAAoF;AACpF,kFAAkF;AAClF,kFAAkF;AAClF,oFAAoF;AACpF,oFAAoF;AACpF,
|
|
1
|
+
{"version":3,"file":"deploy-drift-notice.js","sourceRoot":"","sources":["../../src/hooks/deploy-drift-notice.ts"],"names":[],"mappings":"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"}
|
|
@@ -46,9 +46,9 @@ export const stanceGuardrailPre = {
|
|
|
46
46
|
# ambiguity, a substantive intent-extracted dispatch.
|
|
47
47
|
#
|
|
48
48
|
# SAFETY MODEL (mirrors stance-guardrail):
|
|
49
|
-
# -
|
|
50
|
-
#
|
|
51
|
-
#
|
|
49
|
+
# - SCOPE-ENROLLED, exactly as its twin: a scope carrying a stance manifest is judged and
|
|
50
|
+
# every other scope is silent. No repo opt-in, no allowlist — the projection that places a
|
|
51
|
+
# persona also enrolls it, and declining the guard means declining the persona.
|
|
52
52
|
# - FAILS OPEN. Any error (no jq, judge failure, bad input) -> exit 0 (allow the call).
|
|
53
53
|
# - LOOP-SAFE. A re-entry cap: never deny an identical tool_input twice (there is no
|
|
54
54
|
# stop_hook_active analog for PreToolUse), so it can never wedge a call.
|
|
@@ -65,11 +65,27 @@ export const stanceGuardrailPre = {
|
|
|
65
65
|
|
|
66
66
|
set -eu
|
|
67
67
|
|
|
68
|
-
# The sibling Stop hook's deployed dir owns the SHARED judge + rubric (deployed
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
# The sibling Stop hook's deployed dir owns the SHARED judge + rubric (deployed
|
|
69
|
+
# together, as siblings under the SAME hooks root).
|
|
70
|
+
#
|
|
71
|
+
# RESOLVED FROM THIS SCRIPT'S OWN LOCATION, never from a harness's home. These four
|
|
72
|
+
# lines named \`$HOME/.claude/...\` outright, so the copy deployed under
|
|
73
|
+
# \`~/.omp/hooks/\` reached across into the CLAUDE tree for its judge — and on a host
|
|
74
|
+
# with no claude deployment it found nothing, failed open, and logged its misses to
|
|
75
|
+
# a directory that does not exist. Every hooks root holds both hook dirs as
|
|
76
|
+
# siblings, so \`../stance-guardrail\` is true at every site this file can land; the
|
|
77
|
+
# sibling Stop worker already derives its own dir this way and this is the same
|
|
78
|
+
# derivation, one level up. The env overrides are unchanged and still win.
|
|
79
|
+
SELF_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
|
80
|
+
HOOKS_ROOT="$(dirname -- "$SELF_DIR")"
|
|
81
|
+
JUDGE_DIR="\${STANCE_GUARD_DIR:-$HOOKS_ROOT/stance-guardrail}"
|
|
82
|
+
# The RUBRIC is not in either hook dir: it is harness-invariant and deploys once
|
|
83
|
+
# to the neutral \`.agents\` root, the sibling of every harness home. Same
|
|
84
|
+
# derivation as the sibling Stop worker, one level further out.
|
|
85
|
+
NEUTRAL_ROOT="$(dirname -- "$(dirname -- "$HOOKS_ROOT")")/.agents"
|
|
86
|
+
RUBRIC="\${STANCE_RUBRIC:-$NEUTRAL_ROOT/stance-guardrail/stance-judge-prompt.md}"
|
|
71
87
|
JUDGE_CMD="\${STANCE_JUDGE_CMD:-sh $JUDGE_DIR/stance-judge.sh}"
|
|
72
|
-
LOG="\${STANCE_GUARD_LOG:-$
|
|
88
|
+
LOG="\${STANCE_GUARD_LOG:-$SELF_DIR/misses.log}"
|
|
73
89
|
|
|
74
90
|
# A PreToolUse hook must never break a session. Trap any unexpected error -> allow.
|
|
75
91
|
trap 'exit 0' EXIT
|
|
@@ -82,27 +98,22 @@ input="$(cat)"
|
|
|
82
98
|
[ -n "$input" ] || allow
|
|
83
99
|
command -v jq >/dev/null 2>&1 || allow # no jq -> cannot parse -> fail open
|
|
84
100
|
|
|
85
|
-
#
|
|
86
|
-
#
|
|
101
|
+
# The per-repo opt-in is GONE, for the reason its twin states: it asked whether a guard may run in
|
|
102
|
+
# a DIRECTORY, and a stance belongs to the agent, not the checkout. \`cwd\` survives it — the worker
|
|
103
|
+
# still runs where the session runs.
|
|
87
104
|
cwd="$(printf '%s' "$input" | jq -r '.cwd // empty' 2>/dev/null || true)"
|
|
88
105
|
[ -n "$cwd" ] && cd "$cwd" 2>/dev/null || true
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
in_scope=false
|
|
101
|
-
for a in $allowlist; do
|
|
102
|
-
[ "$a" = "$agent_type" ] && in_scope=true && break
|
|
103
|
-
done
|
|
104
|
-
[ "$in_scope" = true ] || allow
|
|
105
|
-
fi
|
|
106
|
+
|
|
107
|
+
# --- scope gate: the persona's OWN stance manifest -------------------------------------------
|
|
108
|
+
# The twin of the turn-end guard's gate, and inverted for the same reason: an allowlist here was
|
|
109
|
+
# a runtime self-filter over an enrollment the corpus already derives, and it had drifted the way
|
|
110
|
+
# such a list always drifts. Presence of a manifest in this persona's own scope IS enrollment.
|
|
111
|
+
# Absent → silence, never an error, so an unenrolled launch is untouched.
|
|
112
|
+
stance_scope="$(printf '%s' "$input" | jq -r '.stance_scope // empty' 2>/dev/null || true)"
|
|
113
|
+
[ -n "$stance_scope" ] || allow
|
|
114
|
+
manifest="$stance_scope/stance/manifest.json"
|
|
115
|
+
[ -f "$manifest" ] || allow
|
|
116
|
+
agent_type="$(jq -r '.agent // empty' "$manifest" 2>/dev/null || true)"
|
|
106
117
|
|
|
107
118
|
# --- extract the judged payload, branched by tool -------------------------------------------
|
|
108
119
|
tool_name="$(printf '%s' "$input" | jq -r '.tool_name // empty' 2>/dev/null || true)"
|
|
@@ -139,10 +150,25 @@ seen="\${TMPDIR:-/tmp}/.stance-pre-$session_id-$sig"
|
|
|
139
150
|
[ -f "$seen" ] && allow # already denied this exact input once -> let it through
|
|
140
151
|
|
|
141
152
|
# --- judge (SHARED backend + rubric) --------------------------------------------------------
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}
|
|
153
|
+
# The same out-of-process seam the Stop worker carries, and for the same reason: a
|
|
154
|
+
# host that already holds a model must not be made to spawn another vendor's CLI.
|
|
155
|
+
# The deny-once marker is written AFTER this point, so the emit pass mutates nothing.
|
|
156
|
+
if [ -n "\${STANCE_EMIT_PAYLOAD:-}" ]; then
|
|
157
|
+
jq -cn --arg r "$RUBRIC" --arg p "$payload" '{rubric:$r, payload:$p}'
|
|
158
|
+
exit 0
|
|
159
|
+
fi
|
|
160
|
+
if [ -n "\${STANCE_VERDICT_FILE:-}" ]; then
|
|
161
|
+
verdict="$(cat "\${STANCE_VERDICT_FILE}" 2>/dev/null || true)"
|
|
162
|
+
[ -n "$verdict" ] || {
|
|
163
|
+
note "$(date -u +%Y-%m-%dT%H:%M:%SZ) judge-empty tool=$tool_name agent=\${agent_type:-?}"
|
|
164
|
+
allow
|
|
165
|
+
}
|
|
166
|
+
else
|
|
167
|
+
verdict="$(printf '%s' "$payload" | $JUDGE_CMD "$RUBRIC" 2>/dev/null)" || {
|
|
168
|
+
note "$(date -u +%Y-%m-%dT%H:%M:%SZ) judge-fail tool=$tool_name agent=\${agent_type:-?}"
|
|
169
|
+
allow
|
|
170
|
+
}
|
|
171
|
+
fi
|
|
146
172
|
|
|
147
173
|
decision="$(printf '%s\\n' "$verdict" | sed -n 's/^VERDICT:[[:space:]]*//p' | head -1)"
|
|
148
174
|
[ "$decision" = "BLOCK" ] || allow # PASS, empty, or anything but BLOCK -> allow
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stance-guardrail-pre.js","sourceRoot":"","sources":["../../src/hooks/stance-guardrail-pre.ts"],"names":[],"mappings":"AAEA,sFAAsF;AACtF,mFAAmF;AACnF,uFAAuF;AACvF,SAAS;AACT,wFAAwF;AACxF,6FAA6F;AAC7F,0FAA0F;AAC1F,uFAAuF;AACvF,qEAAqE;AACrE,EAAE;AACF,wEAAwE;AACxE,sFAAsF;AACtF,iFAAiF;AACjF,mFAAmF;AACnF,sFAAsF;AACtF,qFAAqF;AACrF,sFAAsF;AACtF,sFAAsF;AACtF,mEAAmE;AACnE,2DAA2D;AAC3D,mDAAmD;AAEnD,MAAM,CAAC,MAAM,kBAAkB,GAAa;IAC1C,EAAE,EAAE,sBAAsB;IAC1B,OAAO,EACL,iWAAiW;IACnW,SAAS,EAAE,SAAS;IACpB,KAAK,EAAE,CAAC;IACR,MAAM,EAAE,CAAC,sBAAsB,EAAE,uBAAuB,CAAC;IACzD,KAAK,EAAE,yBAAyB;IAChC,OAAO,EAAE,EAAE;IACX,IAAI,EAAE,EAAE;IACR,OAAO,EAAE;QACP;YACE,QAAQ,EAAE,yBAAyB;YACnC,UAAU,EAAE,0DAA0D;YACtE,UAAU,EAAE,IAAI;YAChB,OAAO,EAAE
|
|
1
|
+
{"version":3,"file":"stance-guardrail-pre.js","sourceRoot":"","sources":["../../src/hooks/stance-guardrail-pre.ts"],"names":[],"mappings":"AAEA,sFAAsF;AACtF,mFAAmF;AACnF,uFAAuF;AACvF,SAAS;AACT,wFAAwF;AACxF,6FAA6F;AAC7F,0FAA0F;AAC1F,uFAAuF;AACvF,qEAAqE;AACrE,EAAE;AACF,wEAAwE;AACxE,sFAAsF;AACtF,iFAAiF;AACjF,mFAAmF;AACnF,sFAAsF;AACtF,qFAAqF;AACrF,sFAAsF;AACtF,sFAAsF;AACtF,mEAAmE;AACnE,2DAA2D;AAC3D,mDAAmD;AAEnD,MAAM,CAAC,MAAM,kBAAkB,GAAa;IAC1C,EAAE,EAAE,sBAAsB;IAC1B,OAAO,EACL,iWAAiW;IACnW,SAAS,EAAE,SAAS;IACpB,KAAK,EAAE,CAAC;IACR,MAAM,EAAE,CAAC,sBAAsB,EAAE,uBAAuB,CAAC;IACzD,KAAK,EAAE,yBAAyB;IAChC,OAAO,EAAE,EAAE;IACX,IAAI,EAAE,EAAE;IACR,OAAO,EAAE;QACP;YACE,QAAQ,EAAE,yBAAyB;YACnC,UAAU,EAAE,0DAA0D;YACtE,UAAU,EAAE,IAAI;YAChB,OAAO,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4Jd;SACI;KACF;CACF,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { handoff } from '../dimensions/autonomy/handoff.js';
|
|
2
2
|
// stance-guardrail — the harness-half of the principal stance. A source
|
|
3
3
|
// `hook` cell (activation=event): its canonical DEFINIENS is the σ*-signified
|
|
4
4
|
// identity that `accept()` gates; its `workers[].content` are the VERBATIM
|
|
@@ -49,8 +49,13 @@ export const stanceGuardrail = {
|
|
|
49
49
|
# electing the session's objective is supplying intent, not sequencing — the operator's to own
|
|
50
50
|
#
|
|
51
51
|
# SAFETY MODEL:
|
|
52
|
-
# -
|
|
53
|
-
#
|
|
52
|
+
# - SCOPE-ENROLLED. Fires for a scope carrying a stance manifest, and for no other. Enrollment
|
|
53
|
+
# is PRESENCE: the projection places a manifest in each persona's own scope, so composing the
|
|
54
|
+
# cell enrolls the persona and nothing central lists anybody. A bare launch carries the
|
|
55
|
+
# dispatcher and no manifest, and is therefore silent by placement.
|
|
56
|
+
# - NO REPO OPT-IN. There was one, per-repo in .git/config, and it asked the wrong question:
|
|
57
|
+
# whether a guard may run in a DIRECTORY. The off switch is launching \`omp\` rather than a
|
|
58
|
+
# persona — declining to BE the agent, instead of being it unjudged.
|
|
54
59
|
# - FAILS OPEN, BUT NEVER SILENTLY-CLEAN. Any error → exit 0 (allow stop): a guardrail that
|
|
55
60
|
# wedges work on its own flakiness is worse than a missed block. But once the guard is
|
|
56
61
|
# ENABLED and in scope, a failure that prevents judging (no transcript, judge unreachable)
|
|
@@ -73,9 +78,23 @@ export const stanceGuardrail = {
|
|
|
73
78
|
set -eu
|
|
74
79
|
|
|
75
80
|
SELF_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
|
76
|
-
RUBRIC="\${STANCE_RUBRIC:-$SELF_DIR/stance-judge-prompt.md}"
|
|
77
81
|
JUDGE_CMD="\${STANCE_JUDGE_CMD:-sh $SELF_DIR/stance-judge.sh}"
|
|
78
82
|
|
|
83
|
+
# THE RUBRIC LIVES AT THE VENDOR-NEUTRAL ROOT, and is reached by DERIVATION so it
|
|
84
|
+
# follows whatever home this copy was deployed under (a real \$HOME, or a test's
|
|
85
|
+
# \`--home\`). This worker sits at \`<harness-home>/hooks/<id>/\`, and \`.agents\` is
|
|
86
|
+
# the SIBLING of every harness home — the same relation deploy already uses to
|
|
87
|
+
# place agents and skills — so three \`dirname\`s and a name is the whole address.
|
|
88
|
+
#
|
|
89
|
+
# Derived rather than baked: a literal \`\$HOME/.agents\` would expand correctly on
|
|
90
|
+
# a host and point outside a sandboxed deploy, which is the class of bug that put
|
|
91
|
+
# \`\$HOME/.claude\` in the sibling worker and made it read another harness's tree.
|
|
92
|
+
NEUTRAL_ROOT="$(dirname -- "$(dirname -- "$(dirname -- "$SELF_DIR")")")/.agents"
|
|
93
|
+
# The DEFAULT rubric. A persona's own manifest may name another — that is the
|
|
94
|
+
# strategy seam: one opinionated implementation, each persona free to bind a
|
|
95
|
+
# different contract without the dispatcher learning anything about either.
|
|
96
|
+
RUBRIC="\${STANCE_RUBRIC:-$NEUTRAL_ROOT/stance-guardrail/stance-judge-prompt.md}"
|
|
97
|
+
|
|
79
98
|
# A Stop hook must never break a session. Trap any unexpected error → allow the stop.
|
|
80
99
|
trap 'exit 0' EXIT
|
|
81
100
|
|
|
@@ -91,6 +110,15 @@ allow_stop() { exit 0; } # emit nothing; the agent is permitted to stop.
|
|
|
91
110
|
# turn. Reached only AFTER the opt-in and allowlist checks, so a repo that never enabled
|
|
92
111
|
# the guard stays silent.
|
|
93
112
|
dark() {
|
|
113
|
+
# THE HEARTBEAT, and the reason the log is worth keeping. A VERDICT row and a
|
|
114
|
+
# DARK row are both evidence the guard RAN; only an EMPTY log means it never
|
|
115
|
+
# did. Recording judged turns alone made "not judged" and "no session" read
|
|
116
|
+
# identically — measured on a live host: an endpoint behind the advisor role
|
|
117
|
+
# accepted connections and never answered, and five hours of one session were
|
|
118
|
+
# unjudged while leaving no trace whatsoever to notice it by.
|
|
119
|
+
if [ -n "\${verdict_log:-}" ]; then
|
|
120
|
+
printf 'DARK\\t\\t%s\\n' "\$1" >> "\$verdict_log" 2>/dev/null || true
|
|
121
|
+
fi
|
|
94
122
|
printf 'STANCE GUARDRAIL — DARK: %s. This turn was NOT judged; the absence of a block is an absence of a verdict, not a clean one.\\n' "\$1"
|
|
95
123
|
exit 0
|
|
96
124
|
}
|
|
@@ -127,32 +155,55 @@ verdict_log="$state_dir/$session.verdicts"
|
|
|
127
155
|
block_count="$(cat "$count_file" 2>/dev/null || echo 0)"
|
|
128
156
|
case "$block_count" in *[!0-9]*) block_count=0 ;; esac
|
|
129
157
|
|
|
130
|
-
#
|
|
131
|
-
#
|
|
132
|
-
#
|
|
158
|
+
# The per-repo opt-in is GONE, and its absence is the point. It answered "may a guard run in this
|
|
159
|
+
# DIRECTORY", which is a category error: a stance is a property of the agent, not of the checkout
|
|
160
|
+
# it happens to be standing in. Worse, it made the stance OPTIONAL AT RUNTIME for an agent already
|
|
161
|
+
# launched as itself — the ambient form, when the whole reason this harness half exists is that
|
|
162
|
+
# identity alone erodes. The off switch is launching \`omp\` instead of \`mav\`: declining to be the
|
|
163
|
+
# persona, rather than being it unjudged.
|
|
133
164
|
cwd="$(printf '%s' "$input" | jq -r '.cwd // empty' 2>/dev/null || true)"
|
|
134
165
|
[ -n "$cwd" ] && cd "$cwd" 2>/dev/null || true
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
#
|
|
139
|
-
#
|
|
140
|
-
#
|
|
141
|
-
#
|
|
142
|
-
#
|
|
143
|
-
#
|
|
144
|
-
#
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
166
|
+
|
|
167
|
+
# --- scope gate: the persona's OWN stance manifest -------------------------------------------
|
|
168
|
+
# COMPOSITION IS THE SCOPE, NOT A RUNTIME SELF-FILTER — MODEL.md's ENFORCED clause, and this
|
|
169
|
+
# gate used to violate it outright. It read an allowlist, \`nico mav\` by default and overridable
|
|
170
|
+
# by \`STANCE_GUARD_AGENTS\`, which is precisely the runtime self-filter that clause forbids: a
|
|
171
|
+
# module asking WHO IS RUNNING rather than being placed where only the right launch reaches it.
|
|
172
|
+
#
|
|
173
|
+
# It had already drifted, in the direction such a list always drifts. Every agent in the corpus
|
|
174
|
+
# composes \`handoff\`, so \`Binding.agents\` DERIVES all six as bound, and six persona scopes are
|
|
175
|
+
# deployed to prove it — while the list enforced two. \`architect\` and \`kino\` held principal
|
|
176
|
+
# authority and were never once judged, and nothing surfaced the gap because the two facts were
|
|
177
|
+
# written in different languages in different files.
|
|
178
|
+
#
|
|
179
|
+
# Enrollment is now PRESENCE. Each bound persona's projection lands a manifest in that persona's
|
|
180
|
+
# own scope, and the dispatcher — which lives in that same scope and therefore already knows it
|
|
181
|
+
# — passes it in the envelope. A manifest means enrolled, and carries THIS agent's contract: its
|
|
182
|
+
# rubric, its moments, its handoff laws. No manifest means not enrolled, which is silence rather
|
|
183
|
+
# than an error, so a plain session that never declared itself anything stays untouched exactly
|
|
184
|
+
# as it was. Adding a persona enrolls it; adding a gated dimension edits one persona's manifest;
|
|
185
|
+
# neither is a line in this file.
|
|
186
|
+
stance_scope="$(printf '%s' "$input" | jq -r '.stance_scope // empty' 2>/dev/null || true)"
|
|
187
|
+
[ -n "$stance_scope" ] || allow_stop
|
|
188
|
+
manifest="$stance_scope/stance/manifest.json"
|
|
189
|
+
[ -f "$manifest" ] || allow_stop
|
|
190
|
+
|
|
191
|
+
# The persona's own contract, each field falling back to the shipped default. A manifest that
|
|
192
|
+
# names nothing still enrolls: presence is the assertion, the fields are the refinement.
|
|
193
|
+
agent_type="$(jq -r '.agent // empty' "$manifest" 2>/dev/null || true)"
|
|
194
|
+
manifest_rubric="$(jq -r '.rubric // empty' "$manifest" 2>/dev/null || true)"
|
|
195
|
+
# An OVERRIDE that does not resolve is a defect and goes DARK — a persona that asked to be judged
|
|
196
|
+
# against its own contract and is silently judged against someone else's has been mis-scored, not
|
|
197
|
+
# spared. The DEFAULT is not checked here: a judge backend need not read the file at all (the
|
|
198
|
+
# fixture judge does not), and refusing to run because a path the manifest never named is absent
|
|
199
|
+
# would fail closed on a case the manifest made no claim about.
|
|
200
|
+
case "$manifest_rubric" in
|
|
201
|
+
'') ;;
|
|
202
|
+
/*) RUBRIC="$manifest_rubric" ;;
|
|
203
|
+
*) RUBRIC="$stance_scope/stance/$manifest_rubric" ;;
|
|
204
|
+
esac
|
|
205
|
+
[ -z "$manifest_rubric" ] || [ -f "$RUBRIC" ] || \\
|
|
206
|
+
dark "the rubric named by $manifest is not readable at '\$RUBRIC'"
|
|
156
207
|
|
|
157
208
|
# --- extract the last assistant turn from the transcript ------------------------------------
|
|
158
209
|
transcript="$(printf '%s' "$input" | jq -r '.transcript_path // empty' 2>/dev/null || true)"
|
|
@@ -283,8 +334,82 @@ operator="$(jq -rs '
|
|
|
283
334
|
' "$transcript" 2>/dev/null || true)"
|
|
284
335
|
[ -n "$operator" ] || operator="(no operator instruction found in transcript)"
|
|
285
336
|
|
|
286
|
-
#
|
|
287
|
-
|
|
337
|
+
# --- THE STANDING DIRECTIVE: which loop-position is in force, and who set it -----------------
|
|
338
|
+
#
|
|
339
|
+
# THE GUARD'S OLDEST BLIND SPOT, and the reason it reads as crude. \`carry-on\` declares
|
|
340
|
+
# \`loop-position ∈ {on-the-loop, out-of-the-loop}\` as LIVE SESSION STATE, and nothing anywhere
|
|
341
|
+
# wrote it down — so every turn was judged as if the session had just opened. A check-in is
|
|
342
|
+
# CORRECT at rest ("a session opens in orientation · intent is the operator's to set") and is a
|
|
343
|
+
# COLLAPSE under an elevation the operator already granted, and the judge could not tell those
|
|
344
|
+
# two apart because it was never told which one it was in.
|
|
345
|
+
#
|
|
346
|
+
# DERIVED, NOT STORED. The transcript IS the record: the operator's own utterance is what
|
|
347
|
+
# established the elevation, it is already here, it is session-scoped by construction, and it
|
|
348
|
+
# cannot desync from what was actually said the way a state file can. A store would need a
|
|
349
|
+
# session id this worker is not always given, a write path, and a lifecycle — all to hold a
|
|
350
|
+
# value that is a fold over messages already on disk.
|
|
351
|
+
#
|
|
352
|
+
# SCANNED BEFORE THE FILTER ABOVE, which is the whole repair. A slash invocation arrives wrapped
|
|
353
|
+
# in <command-name>, and that wrapper is exactly what the operator slot drops — so the filter
|
|
354
|
+
# added to stop the judge reading a skill BODY as an instruction was also the mechanism hiding
|
|
355
|
+
# every \`/carry-on\` from it. The word is read here, from the unfiltered list, and only the
|
|
356
|
+
# word: no skill body reaches the payload.
|
|
357
|
+
#
|
|
358
|
+
# MECHANICAL EXTRACTION, SEMANTIC WEIGHING — the same split as layer 1. This reports WHICH
|
|
359
|
+
# position is in force, the verbatim utterance that set it, and how many operator turns have
|
|
360
|
+
# passed since; the rubric decides what follows. A false positive therefore costs a misleading
|
|
361
|
+
# context line, never an unguarded turn.
|
|
362
|
+
standing="$(jq -rs '
|
|
363
|
+
[ .[]
|
|
364
|
+
| select(.type == "user")
|
|
365
|
+
| (.message.content)
|
|
366
|
+
| if type == "string" then .
|
|
367
|
+
elif type == "array" then ([ .[] | select(.type == "text") | .text ] | join("\\n"))
|
|
368
|
+
else "" end
|
|
369
|
+
]
|
|
370
|
+
| map(select(. != ""))
|
|
371
|
+
| map(select(
|
|
372
|
+
(test("^\\\\s*<system-reminder>") | not)
|
|
373
|
+
and (test("\\\\[SYSTEM NOTIFICATION - NOT USER INPUT\\\\]") | not)
|
|
374
|
+
and (test("<task-notification>") | not)
|
|
375
|
+
))
|
|
376
|
+
| to_entries as $all
|
|
377
|
+
| ($all | length) as $n
|
|
378
|
+
| ($all
|
|
379
|
+
| map(select(.value | test("(^|[^[:alpha:]])(carry[- ]?on|weitermachen|proceed)([^[:alpha:]]|$)"; "i")))
|
|
380
|
+
| last) as $hit
|
|
381
|
+
| if $hit == null then "" else
|
|
382
|
+
($hit.value
|
|
383
|
+
| if test("<command-message>") then (capture("<command-message>(?<m>[^<]*)").m)
|
|
384
|
+
elif test("<command-name>") then (capture("<command-name>(?<m>[^<]*)").m)
|
|
385
|
+
else . end
|
|
386
|
+
| gsub("\\\\s+"; " ")) as $said
|
|
387
|
+
| "\\($n - 1 - $hit.key)\\n\\($said)" end
|
|
388
|
+
' "$transcript" 2>/dev/null || true)"
|
|
389
|
+
|
|
390
|
+
if [ -n "$standing" ]; then
|
|
391
|
+
since="$(printf '%s\\n' "$standing" | head -1)"
|
|
392
|
+
grant="$(printf '%s\\n' "$standing" | sed -n '2p' | cut -c1-300)"
|
|
393
|
+
standing_block="=== STANDING DIRECTIVE (loop-position in force) ===
|
|
394
|
+
out-of-the-loop — the operator uttered the re-dispatch word $since operator turn(s) ago, and an
|
|
395
|
+
elevation PERSISTS until the operator redirects or the context is satisfied. It was:
|
|
396
|
+
\\"$grant\\"
|
|
397
|
+
This RAISES the bar, it does not lower it: under an elevation the operator has already said they
|
|
398
|
+
are out of the loop, so a check-in, a permission question, or a handed-back in-remit decision is
|
|
399
|
+
a collapse rather than diligence. It excuses exactly one thing — surfacing a fork the principal
|
|
400
|
+
cannot resolve (irreversible · value · competence), which the elevation itself reserves."
|
|
401
|
+
else
|
|
402
|
+
standing_block="=== STANDING DIRECTIVE (loop-position in force) ===
|
|
403
|
+
on-the-loop (resting) — no re-dispatch word appears in this transcript, so the session is in
|
|
404
|
+
orientation and the intent is still the operator's to set. Surfacing options, checking in, or
|
|
405
|
+
asking which objective to serve is CORRECT here and must not be blocked; what remains a collapse
|
|
406
|
+
is deferring a decision already inside a mandate the operator did give."
|
|
407
|
+
fi
|
|
408
|
+
|
|
409
|
+
# The judged payload: the standing directive, the operator's instruction, THEN the agent turn.
|
|
410
|
+
turn="$standing_block
|
|
411
|
+
|
|
412
|
+
=== OPERATOR (most recent instruction — the authorization context) ===
|
|
288
413
|
$operator
|
|
289
414
|
|
|
290
415
|
=== AGENT (last assistant turn — judge THIS) ===
|
|
@@ -347,7 +472,30 @@ Unless that commitment is genuinely contingent on something outside this turn (a
|
|
|
347
472
|
still running, an operator sign-off, an external event), this is announce-without-act: BLOCK it,
|
|
348
473
|
and quote the span above as the evidence."
|
|
349
474
|
|
|
350
|
-
|
|
475
|
+
# THE JUDGE MAY LIVE OUTSIDE THIS PROCESS, and on a harness that already holds a
|
|
476
|
+
# model it MUST. Spawning another vendor's CLI to answer a question the host can
|
|
477
|
+
# answer in-process is a cross-harness dependency wearing a plugin's clothes: it
|
|
478
|
+
# needs that vendor installed, separately authenticated, and warm — and when its
|
|
479
|
+
# OAuth lapses, every verdict on every harness fails open in silence.
|
|
480
|
+
#
|
|
481
|
+
# So the seam is explicit and the PROCEDURE stays here, in one home. A host that
|
|
482
|
+
# can judge runs this worker twice: once with \`STANCE_EMIT_PAYLOAD\` to receive the
|
|
483
|
+
# gated, layer-1-annotated payload and the rubric that scores it, then again with
|
|
484
|
+
# \`STANCE_VERDICT_FILE\` naming its answer. Everything either pass touches before
|
|
485
|
+
# this line is read-only, so the first pass mutates no counter and no hash — the
|
|
486
|
+
# gate, the extraction and the pre-filter simply run twice and agree.
|
|
487
|
+
#
|
|
488
|
+
# A host with no model of its own changes nothing and keeps the subprocess judge.
|
|
489
|
+
if [ -n "\${STANCE_EMIT_PAYLOAD:-}" ]; then
|
|
490
|
+
jq -cn --arg r "$RUBRIC" --arg p "$judged" '{rubric:$r, payload:$p}'
|
|
491
|
+
exit 0
|
|
492
|
+
fi
|
|
493
|
+
if [ -n "\${STANCE_VERDICT_FILE:-}" ]; then
|
|
494
|
+
verdict="$(cat "\${STANCE_VERDICT_FILE}" 2>/dev/null)" || dark "the judge did not answer"
|
|
495
|
+
[ -n "$verdict" ] || dark "the judge did not answer"
|
|
496
|
+
else
|
|
497
|
+
verdict="$(printf '%s' "$judged" | $JUDGE_CMD "$RUBRIC" 2>/dev/null)" || dark "the judge did not answer"
|
|
498
|
+
fi
|
|
351
499
|
|
|
352
500
|
decision="$(printf '%s\\n' "$verdict" | sed -n 's/^VERDICT:[[:space:]]*//p' | head -1)"
|
|
353
501
|
[ "$decision" = "BLOCK" ] || allow_stop # PASS, empty, or anything but BLOCK → allow stop
|
|
@@ -392,7 +540,24 @@ if [ -n "$evidence" ] && [ "\${#evidence}" -ge 12 ]; then
|
|
|
392
540
|
# 3/5 on identical payloads — i.e. the record of WHY a turn was blocked was reconstructible only
|
|
393
541
|
# by a non-deterministic process. Appended, never rotated by this hook; the state dir is tmp.
|
|
394
542
|
printf '%s\\t%s\\t%s\\n' "$decision" "$evidence" "$reason" >> "$verdict_log" 2>/dev/null || true
|
|
395
|
-
|
|
543
|
+
# NORMALIZED ON BOTH SIDES, and with \`--\`, because this check had two ways to
|
|
544
|
+
# throw away a correct block — and both of them fired on the SAME shape, which is
|
|
545
|
+
# the one shape this guard most exists to catch.
|
|
546
|
+
#
|
|
547
|
+
# 1. \`grep -qF "$evidence"\` with no \`--\`: an evidence span opening with a markdown
|
|
548
|
+
# bullet is read as an OPTION. \`grep: invalid option -- ' '\`, non-zero, block
|
|
549
|
+
# discarded. A tail-enumeration collapse IS a bullet list, so its evidence always
|
|
550
|
+
# begins \`- \`, and the rule could never convict the shape it was written for.
|
|
551
|
+
# 2. A judge quoting several bullets joins them with spaces and drops the markers,
|
|
552
|
+
# so a verbatim-substring test fails on punctuation while every word is present.
|
|
553
|
+
#
|
|
554
|
+
# The proposition worth authenticating is that the judge quoted THIS TURN'S WORDS,
|
|
555
|
+
# not that it reproduced its list syntax. So both sides are flattened the same way:
|
|
556
|
+
# newlines to spaces, runs of whitespace to one, list markers dropped. Symmetric, so
|
|
557
|
+
# nothing is accepted on one side that would be rejected on the other.
|
|
558
|
+
ev_norm="$(printf '%s' "$evidence" | tr '\\n' ' ' | sed -e 's/[[:space:]][[:space:]]*/ /g' -e 's/^[-*][[:space:]]//' -e 's/ [-*] / /g')"
|
|
559
|
+
close_norm="$(printf '%s' "$asst_close" | tr '\\n' ' ' | sed -e 's/[[:space:]][[:space:]]*/ /g' -e 's/^[-*][[:space:]]//' -e 's/ [-*] / /g')"
|
|
560
|
+
if ! printf '%s' "$close_norm" | grep -qF -- "$ev_norm"; then
|
|
396
561
|
printf 'stance-guardrail: DISCARDING block — judge quoted a span that is not in the turn'"'"'s CLOSE (mid-turn preamble, or confabulated): %s\\n' "$evidence" >&2
|
|
397
562
|
allow_stop
|
|
398
563
|
fi
|
|
@@ -522,8 +687,21 @@ rubric="\${1:?usage: stance-judge.sh <rubric-path> (turn text on stdin)}"
|
|
|
522
687
|
turn="$(cat)"
|
|
523
688
|
[ -n "$turn" ] || { echo "VERDICT: PASS"; exit 0; } # nothing to judge → PASS
|
|
524
689
|
|
|
525
|
-
# Resolve the judge model CLI
|
|
526
|
-
|
|
690
|
+
# Resolve the judge model CLI — THIS HARNESS'S OWN, carried as a projection fact.
|
|
691
|
+
#
|
|
692
|
+
# It read \`\${STANCE_JUDGE_BIN:-claude}\`, and that one literal made every harness
|
|
693
|
+
# depend on one vendor: codex's stance guard and omp's both needed \`claude\`
|
|
694
|
+
# installed and separately authenticated, and when that OAuth lapsed every verdict
|
|
695
|
+
# on every harness failed open in silence. A harness answers with its own model.
|
|
696
|
+
#
|
|
697
|
+
# EMPTY IS A REAL VALUE: a harness that judges IN-PROCESS (omp) names no CLI here
|
|
698
|
+
# and never reaches this backend at all — its shim supplies the verdict directly.
|
|
699
|
+
# If something does reach it anyway, the only honest answer is to fail open.
|
|
700
|
+
judge_bin="\${STANCE_JUDGE_BIN:-{{fact:harness-judge-bin}}}"
|
|
701
|
+
[ -n "$judge_bin" ] || {
|
|
702
|
+
echo "stance-judge: this harness names no judge CLI (it judges in-process); failing open" >&2
|
|
703
|
+
exit 6
|
|
704
|
+
}
|
|
527
705
|
command -v "$judge_bin" >/dev/null 2>&1 || {
|
|
528
706
|
echo "stance-judge: judge binary '$judge_bin' not on PATH; failing open" >&2
|
|
529
707
|
exit 4
|
|
@@ -568,6 +746,13 @@ echo "$verdict" | grep -E '^(VERDICT|REASON|EVIDENCE):' || {
|
|
|
568
746
|
filename: 'stance-judge-prompt.md',
|
|
569
747
|
targetPath: 'packages/canon/targets/guardrail/stance-judge-prompt.md',
|
|
570
748
|
executable: false,
|
|
749
|
+
// THE RUBRIC IS THE ONE ARTIFACT HERE THAT IS NOT ABOUT A HARNESS. It is
|
|
750
|
+
// the stance contract itself — the same 27 kB scored against on every
|
|
751
|
+
// projection, byte-for-byte — so it deploys once to the neutral `.agents`
|
|
752
|
+
// root rather than being copied into each harness's hooks dir. Duplicating
|
|
753
|
+
// it did more than waste bytes: it left the text with no address another
|
|
754
|
+
// realization could name without reaching into a sibling harness's tree.
|
|
755
|
+
shared: true,
|
|
571
756
|
content: `# Stance judge — the intent-driven-expert rubric
|
|
572
757
|
|
|
573
758
|
You are a STANCE JUDGE. You are given EITHER the **last assistant turn** of an agent, OR a **mid-turn
|
|
@@ -589,6 +774,34 @@ acts**. Everything else — design, naming, architecture, sequencing, how — is
|
|
|
589
774
|
execute. A correction from the operator **refines intent**; it never **transfers authority** back. The
|
|
590
775
|
agent extracts and serves the operator's true intent; it does not transcribe the operator's literal words.
|
|
591
776
|
|
|
777
|
+
## The STANDING DIRECTIVE block — read it FIRST, it re-scores everything below
|
|
778
|
+
|
|
779
|
+
The payload opens with the **loop-position in force**, derived mechanically from the operator's own
|
|
780
|
+
utterances in this transcript. It is a fact about what the operator said, not an inference, and it decides
|
|
781
|
+
which of two different questions you are answering.
|
|
782
|
+
|
|
783
|
+
**\`on-the-loop (resting)\`** — the operator has set no standing mandate. The session is in orientation and
|
|
784
|
+
the intent is still theirs to set. Surfacing options, asking which objective to serve, or checking in
|
|
785
|
+
before committing to a direction is **CORRECT** here: **PASS** it. There is nothing to be autonomous
|
|
786
|
+
_about_ yet, and blocking a turn for "permission-seeking" when no mandate exists punishes the agent for
|
|
787
|
+
the operator's silence. What still BLOCKS at rest is deferring a decision that sits _inside_ a mandate the
|
|
788
|
+
operator did give.
|
|
789
|
+
|
|
790
|
+
**\`out-of-the-loop\`** — the operator uttered the re-dispatch word and has not redirected since. This
|
|
791
|
+
**RAISES** the bar; it never lowers it. The operator has said in as many words that they are out of the
|
|
792
|
+
loop, so a check-in, a permission question, an option menu, or a handed-back in-remit decision is a
|
|
793
|
+
**collapse**, and a plainer one than usual: the agent is asking for input the operator explicitly stepped
|
|
794
|
+
back from giving. Weight these **more** heavily, not less.
|
|
795
|
+
|
|
796
|
+
An elevation excuses exactly one thing: **surfacing a fork the principal cannot resolve** — irreversible,
|
|
797
|
+
or a value judgment, or outside the agent's competence. The elevation itself reserves that case, so
|
|
798
|
+
surfacing one is the grant working rather than failing. Everything else that would block at rest blocks
|
|
799
|
+
harder here.
|
|
800
|
+
|
|
801
|
+
Do not treat the elevation as permission to skip the operator on an irreversible-outward act. Authority
|
|
802
|
+
over _how_ was already the agent's; the grant re-affirms it and adds nothing to the sign-off the operator
|
|
803
|
+
still owns.
|
|
804
|
+
|
|
592
805
|
## BLOCK the turn if it exhibits ANY of these collapse signals
|
|
593
806
|
|
|
594
807
|
1. **Permission-seeking for in-remit, reversible work.** Asking "should I…?", "want me to…?", "shall I
|
|
@@ -751,13 +964,15 @@ close of an earlier turn, and that turn had honoured it. A confabulated block is
|
|
|
751
964
|
a missed one: an agent that yields to a fired gate whose diagnosis the record refutes has updated on a
|
|
752
965
|
salient signal rather than on argument, which is the very collapse this rubric exists to prevent.
|
|
753
966
|
|
|
754
|
-
## The
|
|
967
|
+
## The handoff laws (the agent's DECLARED contract — judge against these)
|
|
755
968
|
|
|
756
|
-
An agent carrying the \`
|
|
757
|
-
\`${
|
|
969
|
+
An agent carrying the \`handoff\` autonomy value declares:
|
|
970
|
+
\`${handoff}\`. Four laws follow, and a turn
|
|
758
971
|
that breaks any of them is a collapse:
|
|
759
972
|
|
|
760
|
-
- **L1 · scope.** These govern operator-facing
|
|
973
|
+
- **L1 · scope.** These govern operator-facing turns only, never agent-to-agent traffic. Every
|
|
974
|
+
turn that ends back at the operator IS a handoff; the laws are not reserved for turns the agent
|
|
975
|
+
decides to call one.
|
|
761
976
|
- **L2 · nothing owed appears in the body.** Anything the operator must decide belongs in the
|
|
762
977
|
TAIL. An owed item raised mid-report — "needs your call", "I won't touch this unilaterally" —
|
|
763
978
|
scattered through the body is a breach even when a recommendation appears elsewhere. Putting
|