@bjoernrochel/sevro 0.1.0-rc.20 → 0.1.0-rc.22
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/docs/extension-protocol-v1.md +9 -0
- package/docs/installing.md +2 -2
- package/docs/native-hosts.md +48 -1
- package/docs/reading-results.md +20 -0
- package/package.json +1 -1
- package/src/hosts/codex-state.ts +20 -0
- package/src/hosts/codex.ts +3 -0
- package/src/hosts/skill-hooks.ts +70 -0
|
@@ -238,6 +238,15 @@ body exposure and successful Claude `Skill` calls.
|
|
|
238
238
|
This replaces the retired Codex skill-read and Claude nested-skill observations.
|
|
239
239
|
Explicit invocation receipts remain dispatch evidence and do not contribute to
|
|
240
240
|
`observedSkills`; extensions must migrate activation checks to the common ID.
|
|
241
|
+
Codex additionally supplies per-skill `skillExposures` and independent
|
|
242
|
+
`collectionStatus` in that observation. Its optional
|
|
243
|
+
`sevro.host.skill-exposure-claims` capability provides complete evidence only
|
|
244
|
+
for listed positive full or partial body exposures, even when collection is
|
|
245
|
+
partial. Extensions must check the requested skill and acceptable exposure
|
|
246
|
+
status explicitly; this evidence cannot establish absence or primary selection.
|
|
247
|
+
See the [exposure contract](native-hosts.md#skill-activation-evidence) for scope,
|
|
248
|
+
unknown states and host limitations. Required aggregate activation evidence
|
|
249
|
+
still needs a `complete` observation as before.
|
|
241
250
|
Host adapters can also supply bounded evidence artifacts. The engine persists
|
|
242
251
|
their bytes outside the candidate workspace before `evaluate`, then passes
|
|
243
252
|
file URLs and digests through the existing artifact references. A required host
|
package/docs/installing.md
CHANGED
|
@@ -23,10 +23,10 @@ Local installation records the selected version in the project lockfile.
|
|
|
23
23
|
|
|
24
24
|
## Current release
|
|
25
25
|
|
|
26
|
-
The current release is `0.1.0-rc.
|
|
26
|
+
The current release is `0.1.0-rc.22`. Install this exact version:
|
|
27
27
|
|
|
28
28
|
```sh
|
|
29
|
-
bun add --exact @bjoernrochel/sevro@0.1.0-rc.
|
|
29
|
+
bun add --exact @bjoernrochel/sevro@0.1.0-rc.22
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
<!-- sevro-current-release:end -->
|
package/docs/native-hosts.md
CHANGED
|
@@ -90,8 +90,55 @@ Newly created or changed skill bodies are outside the prepared snapshot. A
|
|
|
90
90
|
complete empty observation is limited to this scope and the supported local
|
|
91
91
|
tool hook paths.
|
|
92
92
|
|
|
93
|
+
Codex separates per-skill exposure from collection health in the same
|
|
94
|
+
observation. `skillExposures` contains one `{ skill, status }` entry per prepared
|
|
95
|
+
catalog entry, in catalog order, with qualified skill names:
|
|
96
|
+
|
|
97
|
+
| Status | Meaning |
|
|
98
|
+
| ----------- | ----------------------------------------------------------------------- |
|
|
99
|
+
| `full` | The complete prepared body was exposed, in one result or exact pages. |
|
|
100
|
+
| `partial` | Recognizable fragments were exposed without establishing the full body. |
|
|
101
|
+
| `none` | No exposure was detected within a complete collection's matching scope. |
|
|
102
|
+
| `unknown` | No exposure was detected, but collection gaps prevent an absence claim. |
|
|
103
|
+
| `ambiguous` | Body exposure could not distinguish identically registered bodies. |
|
|
104
|
+
|
|
105
|
+
`collectionStatus` is `complete`, `partial`, or `unavailable`. No parsed hook
|
|
106
|
+
receipts means `unavailable`; otherwise collection gaps mean `partial`.
|
|
107
|
+
Recognized partial bodies and ambiguous identities alone do not indicate a
|
|
108
|
+
collection failure. Their existing `incompleteReasons` still keep the aggregate
|
|
109
|
+
activation observation partial for compatibility. Other reason codes, including
|
|
110
|
+
truncated call diagnostics, keep collection partial. Older observations without
|
|
111
|
+
these new fields have unknown exposure and collection status; do not infer them
|
|
112
|
+
from an empty `observedSkills` list.
|
|
113
|
+
|
|
114
|
+
Exposure statuses use all parsed receipts before the diagnostic `calls` list is
|
|
115
|
+
bounded. A proven `full` or `partial` exposure survives collection gaps, including
|
|
116
|
+
an earlier pending call, missing lifecycle hooks or an invalid receipt tail.
|
|
117
|
+
Full exposure takes precedence over fragments; ambiguous identities cannot
|
|
118
|
+
establish exposure of a particular named skill. `none` does not establish that
|
|
119
|
+
the skill was never used outside the prepared catalog and matching scope.
|
|
120
|
+
Recognizable fragments may be shared by several skill bodies. These statuses
|
|
121
|
+
describe content exposure to a tool caller, not instruction compliance, model
|
|
122
|
+
visibility or selection order.
|
|
123
|
+
|
|
124
|
+
The Codex adapter also advertises `sevro.host.skill-exposure-claims`. When at
|
|
125
|
+
least one skill has `full` or `partial` exposure, it emits that observation with
|
|
126
|
+
`completeness: complete`, `method: tool_result_body_match`,
|
|
127
|
+
`scope: positive_exposure`, the original `collectionStatus`, and `skillExposures`
|
|
128
|
+
containing only those positive entries. It is absent when no unambiguous
|
|
129
|
+
positive exposure is available. Extensions can require this evidence for a
|
|
130
|
+
specific positive assertion: accept only `full` for complete body exposure,
|
|
131
|
+
or explicitly accept `partial` when recognizable exposure suffices. A `partial`
|
|
132
|
+
entry is not a full activation. This receipt cannot establish absence, first
|
|
133
|
+
selection, a complete sequence or successful execution; collection remains
|
|
134
|
+
independently qualified. Existing activation and primary-selection requirements
|
|
135
|
+
are unchanged. Claude's native `Skill` seam does not emit body-exposure statuses
|
|
136
|
+
or these positive body-exposure claims.
|
|
137
|
+
|
|
93
138
|
Codex observations include `incompleteReasons`: an empty array for a complete
|
|
94
|
-
|
|
139
|
+
aggregate activation observation, or all applicable reason codes for a partial
|
|
140
|
+
aggregate observation. The separate `collectionStatus` isolates collection
|
|
141
|
+
health from partial bodies and body ambiguity. Older
|
|
95
142
|
observations without this field have unknown reasons; their missing details
|
|
96
143
|
cannot be reconstructed from balanced native call/return counts.
|
|
97
144
|
|
package/docs/reading-results.md
CHANGED
|
@@ -24,6 +24,26 @@ Preparation/candidate/grading/evidence progress appears on stderr. Stage complet
|
|
|
24
24
|
means the attempt ended; read the three result states to determine its outcome.
|
|
25
25
|
See the [evaluation workflow](evaluation-workflow.md) for the complete progression.
|
|
26
26
|
|
|
27
|
+
## Interpret skill exposure
|
|
28
|
+
|
|
29
|
+
In retained Codex `sevro.host.skill-activations` observations, read
|
|
30
|
+
`data.skillExposures` alongside `data.collectionStatus`. A skill may have `full`
|
|
31
|
+
or `partial` exposure even when collection is incomplete. `none` means no
|
|
32
|
+
exposure was detected within a complete collection's scope; `unknown` means
|
|
33
|
+
collection gaps prevent that conclusion. `ambiguous` means identical bodies
|
|
34
|
+
could not identify a named skill. Missing fields in older evidence remain
|
|
35
|
+
unknown.
|
|
36
|
+
|
|
37
|
+
Full exposure establishes that a tool caller received the complete prepared
|
|
38
|
+
body, not that the model followed it. Partial exposure establishes only
|
|
39
|
+
recognizable fragments. Cases that accept partial exposure must say so in
|
|
40
|
+
their extension policy. The separate `sevro.host.skill-exposure-claims`
|
|
41
|
+
observation supports positive exposure checks without treating gaps as absence
|
|
42
|
+
or first selection. [Native host evidence](native-hosts.md#skill-activation-evidence)
|
|
43
|
+
defines the fields and limits; Claude's native skill-tool evidence has a
|
|
44
|
+
different scope. Task verdicts and existing activation requirements retain their
|
|
45
|
+
current meaning.
|
|
46
|
+
|
|
27
47
|
## Summarize runs
|
|
28
48
|
|
|
29
49
|
```sh
|
package/package.json
CHANGED
package/src/hosts/codex-state.ts
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
candidateTranscriptRoots,
|
|
29
29
|
} from "../native-transcripts";
|
|
30
30
|
import { prepareHookMounts } from "../runtime/hooks";
|
|
31
|
+
import { requireNativeHookRoots } from "../native-transcript-state";
|
|
31
32
|
import {
|
|
32
33
|
requireRuntimeReadRoots,
|
|
33
34
|
runtimeExecutableProtection,
|
|
@@ -102,9 +103,28 @@ export async function codexHookMounts(
|
|
|
102
103
|
await rm(root, { recursive: true });
|
|
103
104
|
await cp(curated, root, { recursive: true });
|
|
104
105
|
}
|
|
106
|
+
await grantCodexHookReads(state, hooks.directories);
|
|
105
107
|
return hooks;
|
|
106
108
|
}
|
|
107
109
|
|
|
110
|
+
/** Hooks advertise paths relative to these curated copies, not the install cache. */
|
|
111
|
+
async function grantCodexHookReads(
|
|
112
|
+
state: CodexState,
|
|
113
|
+
roots: string[],
|
|
114
|
+
): Promise<void> {
|
|
115
|
+
if (roots.length === 0) return;
|
|
116
|
+
await requireNativeHookRoots(roots, state.codexHome);
|
|
117
|
+
const path = join(state.codexHome, "config.toml");
|
|
118
|
+
const config = await readFile(path, "utf8");
|
|
119
|
+
const header = `[permissions.${state.profileId}.filesystem]\n`;
|
|
120
|
+
const grants = roots
|
|
121
|
+
.map((root) => `${JSON.stringify(root)} = "read"\n`)
|
|
122
|
+
.join("");
|
|
123
|
+
await writeFile(path, config.replace(header, header + grants), {
|
|
124
|
+
mode: 0o600,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
108
128
|
async function privateCodexDirectories(stateRoot: string) {
|
|
109
129
|
const codexHome = join(stateRoot, "codex-home"),
|
|
110
130
|
parentHome = join(stateRoot, "home"),
|
package/src/hosts/codex.ts
CHANGED
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
prepareSkillTracking,
|
|
46
46
|
skillTrackingObservation,
|
|
47
47
|
skillActivationClaims,
|
|
48
|
+
skillExposureClaims,
|
|
48
49
|
type SkillTracking,
|
|
49
50
|
} from "./skill-hooks";
|
|
50
51
|
import {
|
|
@@ -141,6 +142,7 @@ function codexCapabilities(
|
|
|
141
142
|
"sevro.host.hooks",
|
|
142
143
|
"sevro.host.skill-activations",
|
|
143
144
|
"sevro.host.skill-activation-claims",
|
|
145
|
+
"sevro.host.skill-exposure-claims",
|
|
144
146
|
...(entrypoint === "app-server" ? ["sevro.host.native-goal"] : []),
|
|
145
147
|
"sevro.codex.plugin-marketplace",
|
|
146
148
|
"sevro.codex.explicit-invocation",
|
|
@@ -761,6 +763,7 @@ async function codexObservations(context: ExecutionContext, turns: TurnResult) {
|
|
|
761
763
|
...(explicit ? [explicit] : []),
|
|
762
764
|
tracking,
|
|
763
765
|
...skillActivationClaims(tracking),
|
|
766
|
+
...skillExposureClaims(tracking),
|
|
764
767
|
];
|
|
765
768
|
}
|
|
766
769
|
|
package/src/hosts/skill-hooks.ts
CHANGED
|
@@ -449,6 +449,66 @@ export function skillActivationClaims(
|
|
|
449
449
|
];
|
|
450
450
|
}
|
|
451
451
|
|
|
452
|
+
export function skillExposureClaims(
|
|
453
|
+
observation: Awaited<ReturnType<typeof skillTrackingObservation>>,
|
|
454
|
+
) {
|
|
455
|
+
const skillExposures = observation.data.skillExposures.filter(
|
|
456
|
+
(exposure) => exposure.status === "full" || exposure.status === "partial",
|
|
457
|
+
);
|
|
458
|
+
if (skillExposures.length === 0) return [];
|
|
459
|
+
return [
|
|
460
|
+
{
|
|
461
|
+
id: "sevro.host.skill-exposure-claims",
|
|
462
|
+
completeness: "complete" as const,
|
|
463
|
+
data: {
|
|
464
|
+
method: "tool_result_body_match",
|
|
465
|
+
scope: "positive_exposure",
|
|
466
|
+
collectionStatus: observation.data.collectionStatus,
|
|
467
|
+
skillExposures,
|
|
468
|
+
},
|
|
469
|
+
},
|
|
470
|
+
];
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function exposureSummary(
|
|
474
|
+
skills: HookSkill[],
|
|
475
|
+
events: SkillHookReceipt[],
|
|
476
|
+
collectionStatus: string,
|
|
477
|
+
) {
|
|
478
|
+
const exposure = {
|
|
479
|
+
full: new Set(events.flatMap((event) => event.invocations)),
|
|
480
|
+
partial: new Set(incompleteSkills(events)),
|
|
481
|
+
ambiguous: new Set(events.flatMap((event) => event.ambiguousInvocations)),
|
|
482
|
+
};
|
|
483
|
+
return skills.map(({ invocation: skill }) => ({
|
|
484
|
+
skill,
|
|
485
|
+
status: skillExposureStatus(skill, exposure, collectionStatus),
|
|
486
|
+
}));
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function skillExposureStatus(
|
|
490
|
+
skill: string,
|
|
491
|
+
exposure: { full: Set<string>; partial: Set<string>; ambiguous: Set<string> },
|
|
492
|
+
collectionStatus: string,
|
|
493
|
+
) {
|
|
494
|
+
if (exposure.full.has(skill)) return "full" as const;
|
|
495
|
+
if (exposure.ambiguous.has(skill)) return "ambiguous" as const;
|
|
496
|
+
if (exposure.partial.has(skill)) return "partial" as const;
|
|
497
|
+
return collectionStatus === "complete"
|
|
498
|
+
? ("none" as const)
|
|
499
|
+
: ("unknown" as const);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function exposureCollectionStatus(reasons: string[], eventCount: number) {
|
|
503
|
+
if (eventCount === 0) return "unavailable" as const;
|
|
504
|
+
return reasons.some(
|
|
505
|
+
(reason) =>
|
|
506
|
+
!["partial_skill_bodies", "ambiguous_skill_bodies"].includes(reason),
|
|
507
|
+
)
|
|
508
|
+
? ("partial" as const)
|
|
509
|
+
: ("complete" as const);
|
|
510
|
+
}
|
|
511
|
+
|
|
452
512
|
export async function skillTrackingObservation(
|
|
453
513
|
tracking: SkillTracking,
|
|
454
514
|
sessionId: string | null,
|
|
@@ -489,6 +549,10 @@ export async function skillTrackingObservation(
|
|
|
489
549
|
...conditions.filter(([, applies]) => applies).map(([reason]) => reason),
|
|
490
550
|
];
|
|
491
551
|
const complete = incompleteReasons.length === 0;
|
|
552
|
+
const collectionStatus = exposureCollectionStatus(
|
|
553
|
+
incompleteReasons,
|
|
554
|
+
events.length,
|
|
555
|
+
);
|
|
492
556
|
return {
|
|
493
557
|
id: "sevro.host.skill-activations",
|
|
494
558
|
completeness: complete ? ("complete" as const) : ("partial" as const),
|
|
@@ -497,6 +561,12 @@ export async function skillTrackingObservation(
|
|
|
497
561
|
visibility: "tool_result",
|
|
498
562
|
scope: "prepared_skill_bodies",
|
|
499
563
|
catalogComplete: tracking.configuration.catalogComplete,
|
|
564
|
+
collectionStatus,
|
|
565
|
+
skillExposures: exposureSummary(
|
|
566
|
+
tracking.configuration.skills,
|
|
567
|
+
events,
|
|
568
|
+
collectionStatus,
|
|
569
|
+
),
|
|
500
570
|
observedSkills: [...new Set(calls.flatMap((call) => call.invocations))],
|
|
501
571
|
...primaryEvidence(events, incompleteReasons),
|
|
502
572
|
partialSkills,
|