Skip to content

feat(workspace): tell the model about extension tools a live IDE bridge serves - #1291

Draft
ralphstodomingo wants to merge 1 commit into
mainfrom
feat/awareness-extension-tools
Draft

feat(workspace): tell the model about extension tools a live IDE bridge serves#1291
ralphstodomingo wants to merge 1 commit into
mainfrom
feat/awareness-extension-tools

Conversation

@ralphstodomingo

@ralphstodomingo ralphstodomingo commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Issue for this PR

Closes #1263

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

The ## Workspace integrations system-prompt section projected only the three warehouse capabilities per connection type, so extension-type tools the engine serves through a live VS Code bridge (the dbt project tools, sql-tools) never reached the model — even while they sat in its catalog. #1236 taught the presentation surfaces about them; this is the model-facing half.

  • declared() now also groups the extension keys under their catalog integration (Declared.extensions, optional — the flat keys/extensionKeys lists remain the contract every existing reader was written against), and the attach outcome carries the groups so precedence never makes a second lookup.
  • derive names an extension tool only when two signals agree: its key materialised in the live catalog and a bridge for this project is live at this turn (liveBridge, the same read-only mirror fix: list and count extension-type integrations when a live IDE bridge serves them #1236 uses). Either missing is silence, not a warning — a dormant bridge renders exactly what a workspace with no extension tools renders.
  • The groups ride the nothing-materialised snapshot too, because a workspace can serve extension tools and no warehouse capability at all; that state now speaks when (and only when) it carries them.
  • servedExtensions applies the same reachability filter as servedInventory, at projection time, so an analyst is never told about tools it may not call.
  • The section appends the groups under a fixed intro that names the condition they depend on (the window being open); the extension-only shape keeps the local-tools sentence so nothing is inferred from seeing datamate_* keys listed. When the cap is hit, extension lines drop before any warehouse type.

Claims

  • C1 — Byte-identical when nothing changes. Every snapshot without served extension tools renders the section exactly as before, including all silent disabled states and the unlinked-project case. The disabled-reason table is untouched; EMPTY's shape is untouched unless extension tools are present.
  • C2 — Two signals, or silence. An extension tool is named only when its key is in the live engine catalog and liveBridge(projectDirectory) is true for this turn. A dormant bridge, a catalog key no declared group names, or an outcome without groups (an older engine) all render the no-extension section.
  • C3 — No new lookup, inert names. Grouping and display names come from the catalog read the overlay already caches per attach; nothing is fetched per turn. Integration names pass through inertWorkspaceName before reaching the prompt.
  • C4 — Reachability respected. Tools the caller's ruleset denies are filtered at projection time; the analyst shape renders nothing.
  • C5 — Extension-only workspaces speak. With extension tools served and nothing shadowed, the nothing-materialised snapshot carries the groups (and the bound id) and renders an extension-only section that keeps the local-tools sentence; the same snapshot without a live bridge is silent, as it always was.
  • C6 — Cap discipline. Extension lines are dropped before any warehouse type; an omission line states how many groups were dropped; a single oversized group cannot breach MAX_SECTION_CHARS.

Residuals

  • R1 — Bridge liveness is the presentation mirror (liveBridge), not the engine's own connection state. A window closed after this step's snapshot refresh can leave the block one step stale — the same bound the per-step snapshot already has for everything else in the section.
  • R2liveBridge path matching uses path.relative prefix semantics rather than the engine's platform-aware normalisation (inherited from fix: list and count extension-type integrations when a live IDE bridge serves them #1236's R1); a Windows drive-letter case mismatch can under-report a live bridge, never over-report.
  • R3 — Tool keys are quoted raw from the engine catalog, as the warehouse lines already do; only integration names are sanitised.
  • R4 — The engine discovers the bridge at spawn and does not re-list tools; a window opened after the engine started is not advertised until the engine restarts. Engine behaviour, unchanged here.
  • R5derive rises 17 → 20 cognitive (complexity appendix below). The three added breaks are the extension gate, the outcome-shape spread, and the bound-id spread on the nothing-materialised return; extracting them would move the branches into a helper, not remove them, so the shape is recorded rather than reshuffled.

How did you verify your code works?

  • E2E on the ralphtest rig (see the E2E comment): live bridge → 14 of 16 declared extension keys served and named; real model turn quotes the section verbatim; sidecar-aside control renders no extension block.
  • bun test over the eight workspace suites (awareness, precedence, engine-overlay, engine-probes, engine-types, datamate-list-integrations, plugin/workspace, engine-install-offer): 312 tests, all green; tsgo --noEmit clean.
  • New coverage: named under the integration quoting only materialised keys (declared-but-absent control); dormant bridge byte-identical to a no-extension section; catalog keys without a declared group not claimed; extension-only shape from nothing-materialised and silent again without the bridge; analyst renders nothing; hostile integration name inert; cap drops extension lines first; the attach outcome carries groups only when the allowlist names any; declared() grouping (name from catalog, id fallback, empty groups dropped) and the exact flat-only shape when no extension integration exists.

Screenshots / recordings

Not a UI change. The rendered system-prompt section is asserted verbatim in awareness.test.ts, and the E2E comment on this PR shows the live render from a real VS Code bridge (probe), the model quoting it back verbatim (one real turn), and the negative control with the bridge removed.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

🤖 Generated with Claude Code

https://claude.ai/code/session_01GHBUvb843k1R7UAGi8Ya9b

Appendix — complexity delta (awareness: extension tools via live bridge)

89ddf85656686b2e559c · only functions this diff touches · advisory, not a gate.

Function File Cognitive CCN Status
derive L555 packages/opencode/src/altimate/workspace/precedence.ts 17 → 20 ▲ 16 → 17 ▲ hard to follow
systemSection L146 packages/opencode/src/altimate/workspace/awareness.ts new → 10 new → 8 new — easy
declared L124 packages/opencode/src/altimate/workspace/engine-probes.ts 8 → 9 ▲ 11 → 9 ▼ easy
extensionsServed L287 packages/opencode/src/altimate/workspace/precedence.ts new → 9 new → 12 new — easy

Summary: 13 touched · 3 rose · 1 improved · 6 new (max cognitive 10) · net cognitive Δ +26

ℹ️ How to read these numbers

Cognitive (Sonar spec) counts breaks in linear reading flow — each if/loop/catch/ternary/boolean-operator switch adds 1, and nesting makes every further break cost more. It approximates how much you must hold in your head to follow the function: 0–5 trivial · 6–10 easy · 11–15 moderate (15 = Sonar's recommended per-function cap) · 16–25 hard to follow · >25 needs decomposition.

CCN (cyclomatic) counts independent paths — also the minimum number of test cases for full branch coverage of the function.

Only functions this diff touches are measured, as deltas — pre-existing complexity is not counted against this change. Rising numbers aren't automatically wrong; they're where review attention should go. Test files excluded.

…ge serves

The `## Workspace integrations` section projected only the three warehouse
capabilities per connection type, so extension-type tools the engine serves
through a live VS Code bridge (the dbt project tools, sql-tools) never
reached the model even when they sat in its catalog. The presentation
surfaces learned about them in #1236; this is the model-facing half.

- `declared()` groups the extension keys under their catalog integration
  (`Declared.extensions`, optional so the flat lists stay the contract),
  and the attach outcome carries the groups so precedence never makes a
  second lookup.
- `derive` names an extension tool only when two signals agree: its key
  materialised in the live catalog AND a bridge for this project is live
  now. Either missing is silence, not a warning. The tools ride on the
  `nothing-materialised` snapshot too, since a workspace can serve
  extension tools and no warehouse capability at all.
- `servedExtensions` applies the same reachability filter as the warehouse
  inventory, at projection time, so an `analyst` is never told about tools
  it may not call.
- The section appends the groups under a fixed intro naming the condition
  they depend on; a workspace with extension tools and nothing routed gets
  an extension-only shape that keeps the local-tools sentence. Extension
  lines are dropped before any warehouse type when the cap is hit.

Every snapshot without extension tools renders byte-for-byte what it did
before, including all silent disabled states.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GHBUvb843k1R7UAGi8Ya9b
@ralphstodomingo ralphstodomingo self-assigned this Sep 9, 2026
@coderabbitai

coderabbitai Bot commented Sep 9, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

Thanks for updating your PR! It now meets our contributing guidelines. 👍

@ralphstodomingo

Copy link
Copy Markdown
Contributor Author

E2E — live VS Code bridge, real engine, real model turn (2026-09-10)

Rig: demo-project bound (server-side) to workspace "Test Workspace" (id 15) on the ralphtest tenant; VS Code (WSL) open on the project with the Altimate extension + Power User for dbt, which writes the bridge sidecar; engine 0.7.1 spawned by this branch's overlay with ALTIMATE_WORKSPACE=1. The workspace declares snowflake / altimate / jira plus the extension-type integration Power User for dbt (16 keys).

1. Token-free probe — the real attach, catalog and render, no model

A scratch script runs the CLI's own bootstrap(dir, …), then beforeTurnMCP.tools()Precedence.refreshAwareness.systemSection, and prints what it saw:

  • attach outcome: attached, 35 available / 18 declared (missing: altimate_semantic_layer), and the outcome now carries extensions: [{ id: "power-user-for-dbt", name: "Power User for dbt", keys: [16 keys] }] from the catalog read (C3 — no second lookup).
  • liveBridge(dir): true (sidecar pid alive, workspaceFolders contains the project).
  • engine catalog: 35 datamate_* keys, of which 14 are the extension'sexecute_sql and get_column_values are declared but were not served by the bridge in this session, and the snapshot's extension group lists exactly the 14 that were (C2: catalog-present AND bridge-live; declared-but-absent never claimed).
  • snapshot: enabled: true, shadowed types [snowflake], extensions: [{ integration: "Power User for dbt", tools: [14] }].

Rendered section (verbatim from the probe):

## Workspace integrations

This project is bound to Altimate workspace "Test Workspace" (id 15). For each connection type below, the local tool for a capability that names a workspace tool will NOT execute — it returns a redirect. Call the named workspace tool directly; capabilities not named for a type stay on the local tools:

- snowflake — execute: `datamate_snowflake_execute_database_query`; explain plan: `datamate_snowflake_get_query_explain_plan`; table stats / schema inspection: `datamate_snowflake_get_table_stats`

The VS Code window open on this project serves these extension tools through the workspace. Call them like any other tool; they are unavailable while that window is closed:

- Power User for dbt — `datamate_get_projects`, `datamate_get_children_models`, `datamate_get_parent_models`, `datamate_get_columns_of_model`, `datamate_get_columns_of_source`, `datamate_compile_model`, `datamate_compile_query`, `datamate_run_model`, `datamate_build_model`, `datamate_build_project`, `datamate_run_test`, `datamate_run_model_test`, `datamate_add_dbt_packages`, `datamate_install_deps`

Every other connection type uses the local tools (`sql_execute`, `sql_explain`, `schema_inspect`). Do not use `datamate_*` warehouse tools for connection types that are not listed above.

2. Real model turn — the section reaches the model

bun run dev run --yolo -m altimate-backend/altimate-default --dir demo-project "Do not call any tool. Quote, verbatim and complete, the section of your system prompt whose heading is '## Workspace integrations'. If no such section exists, reply exactly: NO SUCH SECTION."

The model returned the section byte-for-byte identical to the probe's render above (same 14 extension tools, same intro sentence), then DONE. One turn, no tool calls.

3. Negative control — same rig, bridge sidecar moved aside for one run

With the sidecar file temporarily renamed (VS Code left running; file restored afterwards, verified), a fresh probe run under a new session id gave:

  • attach outcome: attached, 21 available / 18 declared — the fresh engine spawn found no bridge, so the 14 extension keys are simply not in the catalog.
  • liveBridge(dir): false; snapshot extensions: [].
  • rendered section: the snowflake line and the converse, no VS Code block — the no-extension render, as C1/C2 claim.

Not exercised live (covered by unit tests only): the "catalog has the keys but the bridge died mid-session" state, which needs a window closed between engine spawn and the turn — R1 describes the one-step staleness bound for it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Workspace awareness prompt says nothing about bridge-served extension tools

1 participant