feat(skill-use): Trigger is observable — score it

The door made retrieval possible; this makes it *measured*. A skill that
arrives by retrieval leaves a recorded tool call, and until now the scorer
ignored it entirely — so the one axis the whole door was built for stayed
`NotObservable` even on a mission where three agents demonstrably reached
through it.

Taken from what run 8 actually recorded, not from the shape I imagined:

    ReadMcpResourceTool {"uri":"skill:global/workspace-repo-commit-protocol",
                         "server":"clawmates_skills"}

`retrieved_skills` reads those URIs through `mcp_skills::parse_uri` — the
function that WROTE them — rather than a second matcher, because two
implementations of one format drift and the drift shows up as a skill
silently scoring nothing.

Trigger is now `Pass` for a skill the agent reached for, and
`NotObservable` for one that was inlined — with a reason that names the
fix rather than the transport: being handed a skill is not failing to
reach for one.

`score` also had to stop reading only the prompt. A skill retrieved and
never inlined is invisible to `skills_in_prompt`, and under progressive
disclosure that is EVERY skill — so the scorer would have reported zero
for the delivery model this axis exists to measure.

Listing the catalogue is browsing; reading a body is the reach. Only
`ReadMcpResourceTool` counts.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018i9Ten1LU4jUr5d7TAWda9
This commit is contained in:
Omar Sobh
2026-08-21 12:37:49 -07:00
co-authored by Claude Opus 5
parent 4b160c5a1b
commit b47ae7fa6b
2 changed files with 133 additions and 7 deletions
+1 -1
View File
@@ -114,7 +114,7 @@ fn skill_uri(workspace_id: Option<Uuid>, name: &str) -> String {
}
/// Parse `skill:global/<name>` or `skill:workspace/<ws>/<name>`.
fn parse_uri(uri: &str) -> Option<(Option<Uuid>, String)> {
pub(crate) fn parse_uri(uri: &str) -> Option<(Option<Uuid>, String)> {
if let Some(name) = uri.strip_prefix(URI_PREFIX_GLOBAL) {
return Some((None, name.to_string()));
}