Skip to content

Graph: Skill & Program nodes — query-by-skill + tool sub-identity (LLP 0073–0079) - #256

Merged
philcunliffe merged 13 commits into
masterfrom
integration/graph-skill-tool-nodes
Jul 7, 2026
Merged

Graph: Skill & Program nodes — query-by-skill + tool sub-identity (LLP 0073–0079)#256
philcunliffe merged 13 commits into
masterfrom
integration/graph-skill-tool-nodes

Conversation

@philcunliffe

@philcunliffephilcunliffe commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Extends the structural activity graph with two new first-class node types, turning brittle full-text scans of ai_gateway_messages into cheap graph lookups.

  • Session –ran→ Skill (issue Query the activity graph by skill #229): ask the graph skill-shaped questions — "which sessions ran hypaware-ai-improvement-report", "sessions per skill", "which repos a skill runs against" — by joining skill usage to the repos/models/authors already in the graph.
  • Session –invoked→ Program (issue Tool nodes stop at the tool name #230): model what a session did with a tool (git, sed, hyp, npm), not just that it touched exec_command. Distinct edge type so the two never collide under one edge_type filter.

Design & settled decisions

  • Design: LLP 0073. Plan: LLP 0079.
  • LLP 0074 — Claude skill activation = strict three-surface union (Skill tool-call + offset-0 SKILL.md marker + offset-0 slash <command-name>) with a static built-in exclusion list; loose matching rejected on the measured ~23% false-positive rate. (Human-reviewed: the union was chosen over marker-only for coverage, accepting built-in-drift / custom-slash false-positive risk.)
  • LLP 0075 — Codex skill activation = path-pattern on the exec_command read of .codex/skills/<name>/SKILL.md; can't share the Claude rule; availability roster ≠ activation.
  • LLP 0076 — derive at projection time now (zero gateway/adapter/hook changes); a capture-side skill_activated event is the named future fix, out of scope here.
  • LLP 0077 — Program key = validity-gated lowercased basename(argv[0]) of the first command (env-skip, wrapper + bash -lc unwrap, fail-closed); pipeline tails & subcommands deferred to V2.
  • LLP 0078dispatch_source = additive boolean edge-props (dispatch_tool/dispatch_slash/dispatch_marker/dispatch_shell_read) unioned by mergeRow; kit widened backward/forward-compatibly (edge ids unchanged).

Tasks (all verified-merged)

T1 kit edge-props · T2 Program nodes + invoked (#230) · T3 Claude Skill nodes + ran, 3-surface union (#229) · T4 Codex exec-read Skill derivation · T5 e2e projection + the issue-headline SQL/neighbors query proof. PROJECTOR_VERSION 1→2.

Fixes#229
Fixes#230

Change-Set: graph-skill-tool-nodes

philcunliffeand others added 12 commits July 6, 2026 13:42
…, covers #229/#230)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
buildEdge learns optional props, mirroring buildNode's empty->null
handling byte-for-byte, in both context-graph/src/types.d.ts and the
structural twin in ai-gateway-graph/src/types.d.ts. Edge ids hash
(src, type, dst) only, so no committed id changes. Zero behavior
change today: nothing passes props yet. This is the kit widening
LLP 0078 (dispatch_source edge props) and LLP 0073 need, per the
graph-skill-tool-nodes plan (LLP 0079, task T1).
Task-Id: T1
Add a `tool_facets.js` module owning the host-only "what did the shell
actually run" extraction, and wire two additive contract rules into the
ai-gateway-graph connector:
- `commandStringFrom(toolName, toolArgs)` — Bash `command` / exec_command
`cmd` (fallback `command`), null when absent or non-string.
- `programFrom(command)` — deterministic, fail-closed argv[0] extraction:
first-connector cut (quote-blind, head-safe), leading `(` + env-assignment
skip, known-wrapper unwrap (sudo/env/timeout/…), `shell -c` unwrap
(depth-capped at 2 for Codex `bash -lc "…"`), basename + lowercase, then
the `PROGRAM_RE` bound (all-numeric rejected). Any failure mints nothing.
- `Program` node keyed on the gated basename + `Session -invoked-> Program`
edge (distinct from `used`/`ran`), both from Bash/exec_command tool_call
rows; they ride the existing aux-exchange filter for free.
- `PROJECTOR_VERSION` 1 -> 2 (provenance only; ids are content-addressed, no
re-key, no migration — LLP 0073 §additive-no-migration).
Tests: new table-driven `ai-gateway-graph-facets.test.js` (wrappers, env
prefixes, `bash -lc` incl. depth cap, quote-blind head safety, subshell
parens, path basenames, lowercasing, PROGRAM_RE fail-closed) and extended
`ai-gateway-graph-contract.test.js` (node/edge ids, labels, props,
source_keys, SQL filters, aux passthrough, PROJECTOR_VERSION === 2).
@ref LLP 0073, LLP 0077
Task-Id: T2
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…P 0073/0074, #229)
Adds the judgment-heavy Claude half of the skill/program graph facets:
- tool_facets.js: SKILL_NAME_RE gate, static CLAUDE_BUILTIN_COMMANDS
exclusion list, and the three per-surface extraction helpers
(skillFromToolArgs, skillFromMarker, skillFromSlash), each offset-0
anchored and fail-closed.
- graph_contract.js: three Skill node rules + three Session -ran-> Skill
edge rules (Skill tool call / SKILL.md injection marker / slash
command), each under the strict role/part_type/leading-anchor SQL
filter, each stamping only its own dispatch flag via the T1 kit edge
props (dispatch_tool / dispatch_marker / dispatch_slash) so mergeRow
unions flags onto one content-addressed edge. No PROJECTOR_VERSION
bump (T2 owns the 1 -> 2 bump).
- Tests: facet-helper matrix (anchors, trailing slash, SKILL.md
basename, namespaced names, full built-in list, SKILL_NAME_RE bounds)
and contract-rule tests incl. cross-surface node/edge id convergence,
per-surface dispatch-flag exclusivity, the false-positive matrix
(mid-text marker, offset-1 marker, built-in slash, mid-text tag,
grep-of-SKILL.md), and aux-row exclusion. The e2e fixture gains the
role/content_text columns the new rules select.
Task-Id: T3
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…P 0073/0075, #229)
Adds Codex's own skill-activation surface (surface 4), which shares zero
signal with Claude's three (no marker, no `Skill` tool, no `<command-name>`
tag): the activation trace is a plain shell read of `.codex/skills/<name>/SKILL.md`.
- tool_facets.js: `skillFromCodexRead(command)`, matched against the
unanchored `CODEX_SKILL_READ_RE` path pattern; takes the already-resolved
command string (shares `commandStringFrom` with `programFrom`), gated by
the existing `SKILL_NAME_RE`.
- graph_contract.js: a fourth Skill node rule + a fourth Session -ran-> Skill
edge rule, filtered to `exec_command` tool calls, stamping the distinct
`dispatch_shell_read` flag (LLP 0078) so it stays visually weaker than
Claude's richer dispatch signals. No PROJECTOR_VERSION bump (T2 owns the
1 -> 2 bump); the Skill node still converges cross-client with the three
Claude surfaces.
- Tests: match/reject table for the path pattern (quoting variants, ~ prefix,
non-.codex paths, missing SKILL.md suffix, name-with-space), contract-rule
tests extending the existing four-surface assertions (cross-surface node
id convergence, dispatch-flag exclusivity, false-positive matrix, aux-row
exclusion).
Task-Id: T4
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…230 headline SQL (LLP 0073/0079)
Extends context-graph-project-e2e.test.js with a fixture spanning all four
Skill activation surfaces (Claude tool/marker/slash + Codex exec_command
read) and both Program source tools (Bash/exec_command), run through the
real projectGraph engine rather than per-rule unit tests:
- Skill/Program nodes and ran/invoked edges materialize and converge across
clients (Claude + Codex land on one Skill node); re-projection is
idempotent.
- The mergeRow dispatch-flag union: a session sighted via both the marker
and slash surfaces collapses onto one `ran` edge carrying both flags.
- Issue #229/#230's headline SQL (sessions-per-skill ranking,
which-sessions-ran-a-program) run unchanged over the node/edge datasets,
including the json_extract(source_keys) recipe from the issues.
- `queryNeighbors` traversal proves `--edge-type ran`/`--edge-type invoked`
reach the new node types with no CLI change.
Task-Id: T5
…cardinality claim (review round 1 fixes for #256)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@philcunliffe

Copy link
Copy Markdown
ContributorAuthor

🔍 neutral review — round 1 (Codex + Claude) at 6477026 → request_changes → fixed at e6e307c

Core derivation reviewed sound (134/134 green; the strict Claude false-positive filters — SQL prefix-LIKE + offset-0 regex + role=user/part_type=text — verified to reject mid-text markers, built-ins, and grep-of-SKILL.md). Two real mis-key bugs + three minors found and fixed:

  • [major] marker path with a space mis-keyed the Skill nodeBase directory for this skill: /Users/John Smith/.claude/skills/hypaware-query minted Skill John. Fixed: marker regex captures the full line (.+, no /m — preserving the leading anchor) then basenames → hypaware-query.
  • [major] wrapper flag-args mis-keyed as the Programsudo -u root git status → Program root, env -C /tmp gittmp. Fixed: per-wrapper option-with-arg tables (sudo -u, env -C, timeout --signal, stdbuf -o, nice -n, …) consume the value, and any unrecognized -x VALUE shape now fails closed (mints nothing) instead of guessing.
  • [minor] Codex skill over-matchecho/rm naming a SKILL.md minted a spurious activation. Fixed: gated on a read-like argv[0] (cat/sed/head/grep/…).
  • [minor] nested Codex skill dirs — documented the accepted single-segment-only (fail-closed) scope with a test rather than speculatively widening.
  • [minor] cardinality wording — softened LLP 0073/0077 "provably bounded" to "bounded by syntax + observed usage" (hashed basenames like foo.test each mint a node).

After the fix: 150/150 targeted (+16 regression tests) and full npm test 1982/1983 (1 pre-existing skip), typecheck green. Head advanced 6477026 → e6e307c; a round-2 re-review of the delta follows before this is held for merge.

Automated summary of the neutral review (Codex = the independent second family).

… for #256)
The MAJOR-2 fail-closed rewrite treated a bare '--' after a wrapper as an
unrecognized flag, dropping 'sudo -- git status' to no Program node. Consume
'--' as end-of-options so the next token is argv[0]. +2 regression tests.
@philcunliffe

Copy link
Copy Markdown
ContributorAuthor

🔍 neutral review — round 2 (Codex + Claude) → approve

Re-reviewed the round-1 fix delta at e6e307c: all 5 fixes confirmed correct, 150/150 green, no regression to the round-1-verified behavior (strict Claude offset-0 filters, git commitgit, bash -lc unwrap, dispatch-flag union, edge-id stability, PROJECTOR_VERSION). Adversarial pass found no new mis-key — the fail-closed rewrite strictly mints fewer wrong things.

One minor regression caught and fixed (418c458): the fail-closed wrapper rewrite treated a bare -- (end-of-options) as an unrecognized flag, dropping sudo -- git status to no Program node (a safe-direction miss, not a mis-key). Fixed by consuming -- so the next token is argv[0]; +2 regression tests (sudo -- gitgit, env -- duckdbduckdb), 93/93 facets green.

Verdict: approve. Pending CI green at 418c458, this is mergeable ∧ green ∧ reviewed → will flip out of draft and hold for a human merge (closes #229 + #230).

Automated summary of the neutral review.

@philcunliffe
philcunliffe marked this pull request as ready for review July 7, 2026 01:38
@philcunliffe
philcunliffe merged commit d430e88 into masterJul 7, 2026
4 checks passed
@philcunliffe
philcunliffe deleted the integration/graph-skill-tool-nodes branch July 7, 2026 18:22
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tool nodes stop at the tool name Query the activity graph by skill

1 participant

@philcunliffe