This repository was archived by the owner on May 26, 2026. It is now read-only.
feat(KR-3 ST2): iso_link_* typed-edge tool family#10
Merged
Conversation
Three model-facing MCP tools over the relationlink substrate
(ADR-0033 / ADR-0034 typed edges):
* iso_link_create — declare a typed edge. Deferred behind a NEW
RelationLinkWriteNotAvailableError with three concurrent blockers
in substrate main (D-kr3-st2-no-relationlink-write-mcp-tool):
(1) created_by_actor_kind CHECK lacks 'kora' (only 8 actor_kinds
covered; Kora-side INSERT would fail);
(2) no Sea MCP write tool exposes the path;
(3) chain_event_id NOT NULL requires substrate-side SECDEF that
emits + binds the chain event in one transaction.
Direct INSERT bypasses all three guards — do NOT do that.
* iso_link_traverse — walk typed edges from a starting node, bounded
by max_depth (≤3 per Tenet 2) and a list of allowed link_types.
Recursive CTE per direction; 'outgoing' / 'incoming' / 'both'.
Both runs two separate queries and merges via dedup-by-min-depth
(same node reached via shorter path wins).
* iso_link_list_for_node — list all active edges where the node is
either source or target. Single query (OR on from_entity_id /
to_entity_id), capped at 100 rows.
Recon findings (verified against
packages/db/migrations/0058_relationlink.sql on substrate main):
* Table: relationlink (no schema prefix — lives in public).
* PK: link_id UUID. Workspace keying: workspace_id TEXT REFERENCES
workspaces(id) — same pattern as ST2/ST3 reads (not tenant_id).
* No RLS on the table — workspace_id filter in WHERE is the
application-layer isolation. Direct asyncpg reads without GUC.
* link_type column is un-CHECK'd TEXT; V1 vocabulary (21 entries —
11 sea/idea + 10 platform-wide) is informational and lives in
sb1-substrate-shapes/src/relationlink.ts as SEA_IDEA_LINK_TYPES +
PLATFORM_WIDE_LINK_TYPES. The JSON Schema enum on iso_link_create
enforces it client-side; the application-layer per-pair gate
config is the normative source of truth.
* validity_state closed enum (active / superseded / disputed /
tombstoned). All reads filter to 'active'.
Provider wiring:
* get_tool_schemas now returns 7 tools (4 iso_node_* + 3 iso_link_*)
via the new ISO_TYPED_GRAPH_TOOL_SCHEMAS combined export.
* handle_tool_call dispatches iso_link_* by prefix; unknown tool
names still fall through to the ABC default's clear-error path.
Capability checks reuse the iso_node assert_kora_can_perform stub
(D-kr3-st1-capability-check-deferred). The per-tool capability map:
iso_link_create → cap_sea_link_authoring; iso_link_traverse +
iso_link_list_for_node → cap_read_unfiltered_relationlink.
Tests (22 new):
* Schemas: 3 well-formed; V1_LINK_TYPES sanity (11+10=21, no
overlap); ISO_LINK_CREATE_SCHEMA enum syncs with V1_LINK_TYPES;
traverse caps max_depth at 3; list_for_node caps limit at 100;
combined surface = 7 tools.
* read_relationlink_for_node: binds (workspace_id, entity_id, limit);
OR on from/to in the WHERE clause; respects validity_state='active';
caps limit at MAX_LIST_LIMIT=100.
* traverse_relationlink: outgoing one-query / both two-queries;
max_depth cap; rejects empty link_types; dedupes by min-depth;
rejects unknown direction.
* create_relationlink: raises RelationLinkWriteNotAvailableError
with message naming all three substrate-side blockers verbatim
(operators can grep + see the closure conditions inline).
* Provider-level handlers: create envelope shape, link_type +
node_kind validation, traverse routing + V1 vocabulary check,
list_for_node routing + limit cap, provider.handle_tool_call
iso_link_* prefix dispatch.
* Capability stub fires + logs D-kr3-st1-capability-check-deferred
on traverse path (proves the shared stub is invoked by both
families).
Local gates:
* ty check — 7,337 diagnostics, zero-delta vs KR-3 ST1 baseline.
* pytest tests/plugins/memory/ — 294/294 passing (22 new ST2 + 22
ST1 + 250 pre-ST2).
* Full suite via xdist (-n auto): 24,617 / 142 failed / 129 skipped
vs KR-2 ST4 merge baseline (24,570/143/129): +47 passed, -1 fail.
Failures still concentrated in tests/tools/* + tests/skills/* /
tests/tui_gateway/* xdist isolation noise; none touch isokron.
Rule-6:
* BUILD_DEVIATIONS.md adds D-kr3-st2-no-relationlink-write-mcp-tool
under Open with all three blockers + closure condition spelled out.
* No README change needed — KR-3 ST3 owns the operator-facing
tool-surface docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 tasks
rafe-walker
added a commit
that referenced
this pull request
May 22, 2026
Phase 2 Feature 3 frontend. Pairs with CC#1 KR-FEAT-EMAIL (outbound-only after Option-D descope). - Backend /api/email/recent stub (4 messages spanning inbound/outbound/filtered/attachment). - EmailPanel.tsx — stats + filter pills + spoofing chip + HTML-metadata chip + attachment chip + 400-char body preview. - Dashboard card #10 (Mail icon). 4-layer security contract with email-specific guards: no raw addresses (per-field + walk-payload) + message_id stub shape + plain-text rendering (with dangerouslySetInnerHTML ban) + walk-payload sweep for Purelymail-token-hints / HMAC-secret-shapes / bearer-token-shapes. Layout: 10 cards = 3+3+3+1 wrapping to 3+3+4 in lg:grid-cols-3. 241/241 admin-panel tests pass across 21 suites; tsc --noEmit + vite build clean. Flagged pre-existing: 4 tsc -b errors in HeartbeatPanel.tsx + DashboardPage.tsx from HeartbeatStatus enum drift (unknown added) and nullable last_check_at not propagated — confirmed on bare base, NOT introduced by this PR. Recommended cleanup alongside task NousResearch#269.
4 tasks
rafe-walker
added a commit
that referenced
this pull request
May 24, 2026
#161) R3-4 item #10. Per-route cost counter accumulator wired into record_inference. Three windows: process-lifetime, rolling_24h, monthly. RLock concurrency-safe (1000-call/10-thread test verified). Side-channel observer of billing — doesn't mutate cost-ladder accumulation. Route wired: slack_dm (handlers/slack_dm_handler.py:676). Routes reserved (literal accepted, consumers wired in follow-on buckets): email_inbound, email_outbound_compose, mcp_tool, alert_investigation, probe_investigation, tool_loop_iteration, scheduled_task. Follow-ons surfaced: KR-EMAIL-COST-BILL, KR-MCP-TOOL-COST-TAG, KR-REASONING-ITERATION-TAG, KR-PLUGIN-AUDIT-COST-TAG. Snapshot schema_version 1 → 2; compute_snapshot() now includes cost_telemetry section with rolling_24h + monthly windows. /api/snapshot end-to-end verified. 56 new tests + 141/141 focused regression + ruff clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
KR-3 ST2 ships the
iso_link_*typed-edge tool family — 3 model-facing MCP tools over therelationlinksubstrate (ADR-0033/ADR-0034 typed edges).iso_link_createiso_link_traverseiso_link_list_for_nodeCombined typed-graph surface is now 7 tools (4 iso_node_* + 3 iso_link_*) returned by
get_tool_schemas.Rule-3 STOP-gate findings during recon
Verified against
packages/db/migrations/0058_relationlink.sqlon substrate main41ddc208:relationlink(bare, no schema prefix) — lives inpublic. Same workspace_id TEXT pattern as ST2/ST3 reads (not tenant_id).workspace_idfilter in WHERE is the application-layer isolation. Direct asyncpg reads work without GUC.link_typecolumn is un-CHECK'd TEXT — V1 vocabulary (21 entries: 11 sea/idea + 10 platform-wide) is informational and lives insb1-substrate-shapes/src/relationlink.ts. The JSON Schema enum oniso_link_createenforces it client-side; the application-layer per-pair gate config is the normative source of truth.D-kr3-st2-no-relationlink-write-mcp-tool):created_by_actor_kindCHECK lacks'kora'. The CHECK covers 8 entries (operator, oracle, critic, claude_pm, hermes, platform_seal, platform_rollback, platform_session_expiry). Migration 0058 predates'kora''s introduction (added toactor_registryin 0076). A Kora-side INSERT would fail.kora__propose_convention,kora__read_escalation_queue,kora__propose_policy_changeregistered. Nokora__create_relationlinkequivalent.chain_event_id UUID NOT NULLrequires substrate-side SECDEF that emits + binds the chain event in one transaction (same pattern askronicle.compact_scratchpadfrom Plan 02). Direct INSERT either fails (nochain_event_id) or, if filled in client-side, breaks the chain witness invariant.Per ST2 spec § "Read vs write paths": "Writes → through Sea MCP (authorization + audit)". Deferred per the spec's BUILD_DEVIATIONS-or-defer pattern. PM coordinates the substrate-side bucket that addresses all three blockers in one shot.
What landed
plugins/memory/isokron/relationlink.pyRelationLinkRow,ReachableNode,RelationLinkWriteNotAvailableError, V1 link-type vocabulary constants (SEA_IDEA_LINK_TYPES,PLATFORM_WIDE_LINK_TYPES,V1_LINK_TYPES,RELATIONLINK_VALIDITY_STATES), canonical SQL (list + 2x recursive-CTE traverse),read_relationlink_for_node,traverse_relationlink, deferredcreate_relationlink. ~280 LOC.plugins/memory/isokron/tools/iso_link.pyplugins/memory/isokron/tools/__init__.pyISO_TYPED_GRAPH_TOOL_SCHEMAS = ISO_NODE + ISO_LINKexport; re-exports the link dispatcher.plugins/memory/isokron/provider.pyget_tool_schemasreturns 7 tools via the combined export;handle_tool_calladds theiso_link_*prefix branch.BUILD_DEVIATIONS.mdD-kr3-st2-no-relationlink-write-mcp-toolOpen entry — all three blockers spelled out + closure condition.tests/plugins/memory/test_iso_link_tools.pytests/plugins/memory/test_isokron_provider_skeleton.pytest_tool_schemas_exposes_iso_node_family→test_tool_schemas_exposes_iso_typed_graph_family(7 names asserted).Traverse SQL (recursive CTE per direction)
For
outgoing:Mirror for
incomingwalksto_entity_id → from_entity_id.direction='both'issues both queries and the Python side dedupes by(entity_id, entity_kind, via_link_type)keeping MIN depth.Capability checks
Reuses the iso_node
assert_kora_can_performstub (D-kr3-st1-capability-check-deferred). Per-tool capability mapping:iso_link_create→cap_sea_link_authoringiso_link_traverse→cap_read_unfiltered_relationlinkiso_link_list_for_node→cap_read_unfiltered_relationlinkTest plan
22 new tests, all passing:
Schemas (6):
V1_LINK_TYPES= 11 sea/idea + 10 platform-wide = 21, no overlapiso_link_createschema'slink_typeenum matchesV1_LINK_TYPEStupleiso_link_traverseschema capsmax_depthat 3iso_link_list_for_nodeschema capslimitat 100Read SQL (2):
read_relationlink_for_nodebinds(workspace_id, entity_id, limit); OR on from/to; filtersvalidity_state='active'MAX_LIST_LIMIT=100Traverse (5):
from_entity_id)direction='both'runs two queries (outgoing + incoming variants)max_depthcapped at 3link_types(would sprawl)directionvaluesDeferred write (1):
create_relationlinkraisesRelationLinkWriteNotAvailableError; message names all 3 blockers verbatim (operators can grep the message + see exactly what substrate needs to ship)Tool handlers (8):
iso_link_createreturns deferred envelope with the rightdeviation_idiso_link_createrejects invalidlink_typeiso_link_createrejects invalidnode_kindiso_link_traversereturns results from mocked CTEiso_link_traversevalidateslink_typesagainst V1 vocabularyiso_link_list_for_nodereturns rowsiso_link_list_for_nodeenforces server-side limit capprovider.handle_tool_callroutesiso_link_*by prefixCapability check (1):
assert_kora_can_performstub fires + logs D-kr3-st1-capability-check-deferred + the cap name on the iso_link traverse path (proves both families share the gate)Gates
ty check— 7,337 diagnostics, zero-delta vs KR-3 ST1 baseline.pytest tests/plugins/memory/— 294/294 passing (22 new ST2 + 22 ST1 + 250 pre-ST2).-n auto): 24,617 passed / 142 failed / 129 skipped vs KR-2 ST4 merge baseline (24,570/143/129): +47 passed, −1 fail. Sametests/tools/*+tests/skills/*+tests/tui_gateway/*xdist isolation noise; none touchplugins/memory/isokron/.Rule-6 / BUILD_DEVIATIONS / Open asks
New deviation
D-kr3-st2-no-relationlink-write-mcp-tool— 3 substrate blockers spelled out in BUILD_DEVIATIONS.md. PM needs to dispatch the substrate-side bucket that handles all three in one shot (CHECK extension + MCP tool + chain-event SECDEF).Existing deviations carry forward (5 total open now):
D-kr2-st2-capability-matrix-mirrorD-kr2-st3-no-scratchpad-write-mcp-toolD-kr2-st4-no-chain-emit-mcp-toolD-kr3-st1-capability-check-deferredassert_kora_can_performstubD-kr3-st2-no-relationlink-write-mcp-toolPer PM's pipeline-order note: if no substrate buckets are ready to swap to after ST3, KR-6 is next on this lane — it's the cheapest unlock (~50 LOC; closes D-kr3-st1 without needing CC#1 substrate work).
Standing by for KR-3 ST3 (tool registration polish + Hermes
memorydeprecation + system prompt updates).🤖 Generated with Claude Code