Skip to content

feat(workspace): tell the model what the bound workspace serves - #1182

Merged
sahrizvi merged 10 commits into
mainfrom
feat/workspace-tool-awareness
Sep 1, 2026
Merged

feat(workspace): tell the model what the bound workspace serves#1182
sahrizvi merged 10 commits into
mainfrom
feat/workspace-tool-awareness

Conversation

@suryaiyer95

@suryaiyer95 suryaiyer95 commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Issue for this PR

Closes the remaining integrations gap called out after #1167/#1168/#1169 landed: the harness being unaware of when to reach for workspace tools.

Type of change

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

What does this PR do?

Stacked on #1169 — review that first; this PR is the commit on top.

#1168 made the engine win: a shadowed warehouse call executes nothing and returns a redirect naming the engine tool. It did not make the model pick the engine first, so every session pays a wasted turn learning the rule, and re-learns it next session.

The only model-visible steering today is a sentence describeNativeTool appends to a description whose first line already matches user intent ("Execute SQL against a connected data warehouse.", altimate/tools/sql-execute.ts:21), and it never names the engine key — so even an obedient model cannot comply without a probe call. The routing table the model actually needs already exists as inventoryLine (precedence.ts:707-737) and goes only to a TUI toast. The system prompt says nothing about the workspace.

This states it in the system prompt instead, per turn, naming the exact engine keys, with the converse said explicitly so unserved types keep running locally.

Rendered example (illustrative — every type, capability and key is derived from what materialised this turn):

## Workspace integrations

This project is bound to Altimate workspace "analytics". For the connection types listed below the local tools will NOT execute — they return a redirect. Call the workspace tool directly:

- snowflake — execute: `datamate_snowflake_execute_database_query`; explain plan: `datamate_snowflake_get_query_explain_plan`; table stats / schema inspection: `datamate_snowflake_get_table_stats`
- bigquery — execute: `datamate_bigquery_execute_database_query` (explain plan and table stats / schema inspection for bigquery stay on the local `sql_explain` / `schema_inspect`)

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.

Why the system prompt and not a better tool description: session/system.ts:129-142 records, from this repo's own benchmark trace analysis, that a lazily-described capability fired in "<1% of tool calls", and that guidance placed at the END of a section was "treated as background reference rather than binding directive" while the same content placed FIRST was applied. #1168's suffix is exactly that shape. This is the documented cure, not a guess.

Why this is safe to land

Additive in effect: no call changes where it executes or what it returns.

  • awareness.ts renders a string and nothing else. It does not touch check(), redirectFor() or any tool body, so which calls get shadowed and what a shadowed call returns are unchanged. If the section is wrong, the model reads a misleading sentence — it does not run a query against the wrong credentials.
  • The one derive() change is an ordering change, not an enforcement change. The escape hatch is now read after the workspace link instead of before it. Both orders disable routing identically — every path returns the same disabled snapshot and check() shadows nothing either way — so the order decides only which disabledReason is reported. That matters because the reason is what every user-facing surface keys on, and escape-hatch is a claim about workspace routing: ALTIMATE_INTEGRATIONS is process-wide, so read before the link it fired on projects with no workspace at all, putting a toast on screen and a section in the prompt of a session that has none. It still outranks binding-unreadable, because the flag is a fact about the session whatever the link says.
  • A project with no workspace link assembles a byte-identical system prompt to before this commit. pilot-off, unbound, nothing-materialised and no-snapshot all render "", and after the ordering fix above no reason that does speak survives a link read that settled as unbound. (binding-unreadable is the read failing rather than saying no — the project may or may not be linked, which is exactly why its copy asserts neither.) Both halves are asserted directly, not argued (the regression guard describe block: the exhaustive reason→copy table, plus a test that drives every disabling condition on an unbound project and requires silence).
  • It is not silent for every disabled state, and should not be. The hatch and the three uncertain states (binding-unreadable, unattributed, derive-failed) each render a short paragraph steering to the local tools, because in all four the engine's tools can still be in the catalog while routing refuses them — silence would leave the model free to call what it can see. (An earlier revision of this description claimed blanket silence and listed unattributed among the silent states; that was wrong in both directions and is corrected here.)
  • servedInventory() is a projection over the snapshot the guard already uses, filtered through the same servedFor/reachable — so the section can never advertise a routing check() would not perform, nor one the caller's agent is forbidden to follow. There is a test that walks the inventory and asserts check() redirects to each key it names.
  • No tool descriptions change, so no existing description assertions move.
  • Worst case is bounded and self-healing: a stale section costs at most one turn, is re-derived next turn, and the redirect backstop still catches the call.

Deliberate details

  • Per capability, not per warehouse type. BigQuery serves execute only, so its line says explain and inspect stay on the local tools. Claiming the type would steer the model off the only tools that work there.
  • The converse paragraph is never dropped under the char cap. Without it the section reads as "prefer the workspace for everything", which is the over-steering failure this most needs to avoid.
  • The escape hatch speaks rather than falling silent. Engine tools can still materialise with --integrations=local on — derive refuses before it looks at them, but MCP connects the configured entry regardless — so silence would leave the model free to use tools it can see and should not.
  • An agent denied the engine keys renders no section, matching what precedence actually does for it. A redirect it cannot follow is a dead end.
  • Own 2,000-char cap, deliberately independent of UNIFIED_INJECTION_BUDGET — a routing directive must not compete with memory for space.

How did you verify your code works?

  • bun run typecheck clean.
  • 24 new tests, all passing — 17 in awareness.test.ts, 7 in precedence.test.ts. The two suites run 137 pass / 0 fail together.
  • Full test/altimate/ sweep: 4573 pass, 638 skip, 0 fail, repeated 7x to rule out order dependence. One run showed a single unrelated failure in skill-sync.test.ts (existsSync(skillFile(...)) at :1227 — the skill file write, while the binding-cache assertion on the line above passed) during a sweep run concurrently with a typecheck; it did not recur in the other six, and the affected trio (precedence + awareness + skill-sync) ran 6x clean on both this branch and the base.
  • bun run lint: no new errors. The one repo-wide oxlint error is pre-existing in packages/http-recorder; the warnings on these files are the same no-unsafe-type-assertion fixture casts they carried before.
  • On the original base (089bb6223) the sweep went 4420 → 4439 pass with the same 3 failures present on the untouched base commit — pre-existing cross-file pollution in default-target.test.ts, which passed 12/12 in isolation on both. Verified by running the sweep on a clean worktree of that base rather than inferred; those failures no longer occur after the restack.

Not yet done: the before/after model-behaviour measurement (does the model call the engine tool first, and are redirects consumed → 0). Left out of this PR deliberately so the additive change can land on its own; the metrics come from trace JSON that already ships (tracing.ts:907-930, metadata.redirected).

Rebased onto the restacked #1169

Originally opened against 089bb6223. #1169 was force-pushed to 40c57a890 (the restack onto the newer #1167/#1168), so this has been rebased onto that. It applied with no conflicts — the changes are additive and touch none of the rewritten regions.

Worth noting: the restacked base now carries fix: undetermined outcomes always carry a stated reason in the result, the precedence.ts hardening (fail-open check/checkUnsafe split, refusing attribution when Config.invalidate() fails) that the pre-restack #1169 was missing. Re-verified on the new base: typecheck clean, my two suites 111/111, and the full test/altimate/ sweep is now 4454 pass / 0 fail — the 3 pre-existing default-target.test.ts failures seen on the old base are gone.

Review fixes applied

Four findings from the end-to-end review, all fixed in this branch:

  1. The module's stated contract was false (and so was this description). The header on awareness.ts claimed the section returns "" in every state but a routing one; four of the seven disabledReason values render text. Both the header and the "Why this is safe to land" section above now state the scoped claim, which is the one DISABLED_COPY actually implements.
  2. An unbound project could receive a section. ALTIMATE_INTEGRATIONS is process-wide and derive read it before the workspace link, so a pilot user who exports --integrations=local got a 256-char "## Workspace integrations" block — plus a TUI toast — in every project, including ones with no link and no datamate_* tools. The hatch is now read after the link. Covered by a test on each side of the boundary (unbound → silent, unreadable → still the hatch).
  3. binding-unreadable asserted a link that may not exist. currentBinding() maps any throw from the strict reader to unreadable, which a project with no link can reach, so "the bound workspace's engine could not be verified" overstated. The shared copy now reads "Workspace routing could not be established for this session" — true in all three states that use it.
  4. The truncated section said the same thing twice — the list tail and the converse both counted the omitted types. The count now appears once, on the list; the converse carries only what the model should do about it.

Also added: a regression test that drives every disabling condition on an unbound project and requires silence from each. Finding 2 slipped through because the existing hatch test called bindTo() in beforeEach, so the unbound + hatch combination was never exercised.

Follow-ups deliberately not in this PR

  1. Tool-description rewrite (lead with the redirect rather than appending it) — the second lever. Ship this, measure, then decide if it is needed at all. It mutates what the model reads and existing assertions.
  2. INTEGRATION_TYPE → key-pattern discovery. Today that map is hardcoded to four ids (precedence.ts:90-97) and derive() iterates it, so a datamate that adds Redshift or Trino is silently ignored even when the engine advertises redshift_execute_database_query — no shadowing, no warning. That fix rewrites feat(workspace): route warehouse tools through the bound workspace's engine #1168's enforcement core, so it belongs in its own PR with its own tests rather than sharing a diff (and a bisect) with a steering change. Note this section renders whatever Precedence derives, so it becomes dynamic for new engines with no change to this PR's code.
  3. Engine-only capability awareness (knowledge hub, orchestration, ticketing) — the other half of the reported gap. Blocked on confirming the real tool keys: ask_knowledge_base appears nowhere in packages/opencode/src/, so the name is an assumption.

Screenshots / recordings

n/a — system-prompt change, no UI.

Checklist

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

Summary by CodeRabbit

  • New Features

    • Added workspace-awareness guidance to generated prompts.
    • Prompts now identify available capabilities, local alternatives, routing details, and unserved capabilities.
    • Added handling for disabled, unverified, unconfigured, and unbound workspace states.
    • Workspace guidance is sanitized and capped at 2,000 characters.
  • Bug Fixes

    • Improved capability- and permission-aware routing information.
    • Prevented unavailable or unauthorized capabilities from being presented as accessible.
    • Sanitized workspace names across model-visible guidance.
  • Tests

    • Added coverage for workspace states, routing, permissions, sanitization, deduplication, and size limits.

@coderabbitai

coderabbitai Bot commented Aug 29, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Team

Run ID: 02699ad8-53e8-46ec-9ffb-7e8942efd0a3

📥 Commits

Reviewing files that changed from the base of the PR and between 4f6ae42 and 9c74f4d.

📒 Files selected for processing (4)
  • packages/opencode/src/altimate/workspace/awareness.ts
  • packages/opencode/src/altimate/workspace/precedence.ts
  • packages/opencode/test/altimate/workspace/awareness.test.ts
  • packages/opencode/test/altimate/workspace/precedence.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/opencode/src/altimate/workspace/awareness.ts

Included review availability: Your plan provides up to 4 included reviews per hour; 3 remain after this review.


📝 Walkthrough

Walkthrough

Adds shared capability projection, workspace-routing guidance, disabled-state handling, workspace-name sanitization, size limiting, and per-step insertion of non-empty awareness sections into session system prompts. Tests cover routing, permissions, fallbacks, truncation, sanitization, and prompt integration.

Changes

Workspace awareness

Layer / File(s) Summary
Capability projection and inventory
packages/opencode/src/altimate/workspace/precedence.ts, packages/opencode/test/altimate/workspace/precedence-fixture.ts, packages/opencode/test/altimate/workspace/precedence.test.ts
servedInventory projects reachable capabilities and local fallbacks. Workspace names are sanitized before model-visible use. Warehouse annotations reuse the projection. Shared fixtures and tests cover permissions, disabled states, and redirects.
Workspace awareness rendering
packages/opencode/src/altimate/workspace/awareness.ts, packages/opencode/test/altimate/workspace/awareness.test.ts
systemSection renders served capabilities, local fallbacks, disabled-state messages, sanitized workspace labels, and bounded output. Tests cover routing states, deduplication, truncation, control characters, and Unicode boundaries.
Session prompt integration
packages/opencode/src/session/prompt.ts, packages/opencode/test/altimate/workspace/awareness.test.ts
Session prompt generation inserts the workspace awareness section when the section is non-empty.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Merge Risk: 🟡 Moderate · up to 9c74f

This PR changes bound sessions to call workspace execution tools directly. Merge should wait for confirmation that remote execution enforces equivalent write authorization and confirmation controls; concurrent test-state interference remains a bounded follow-up risk.

Sequence Diagram(s)

sequenceDiagram
  participant SessionPrompt
  participant Precedence
  participant Awareness
  SessionPrompt->>Precedence: read session precedence snapshot
  SessionPrompt->>Awareness: call systemSection(precedence)
  Awareness->>Precedence: read servedInventory(precedence)
  Awareness-->>SessionPrompt: return bounded workspace guidance
  SessionPrompt-->>SessionPrompt: insert non-empty section
Loading

Suggested reviewers: sahrizvi

Poem

A rabbit maps the tools with care
Clean names keep control marks away
Served paths guide each prompt step
Local fallbacks stay in place
Long rows hop past the limit

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 15 functions across 6 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the primary change: informing the model about the workspace capabilities and tools served by the bound workspace.
Description check ✅ Passed The description follows the required template, identifies the issue, marks the change as a new feature, explains the implementation and safety considerations, documents verification results, notes tha…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description follows the required template, identifies the issue, marks the change as a new feature, explains the implementation and safety considerations, documents verification results, notes that screenshots are not applicable, and completes the checklist.

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/workspace-tool-awareness

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

Copy link
Copy Markdown

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

@ralphstodomingo
ralphstodomingo force-pushed the feat/workspace-install-offer-v2 branch 2 times, most recently from 2575d46 to 40c57a8 Compare August 30, 2026 18:37
@suryaiyer95
suryaiyer95 force-pushed the feat/workspace-tool-awareness branch from c5f0368 to 9e8e1fb Compare August 30, 2026 21:03
@github-actions

Copy link
Copy Markdown

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

@ralphstodomingo
ralphstodomingo force-pushed the feat/workspace-install-offer-v2 branch 2 times, most recently from 8f47bcd to eb798cb Compare August 31, 2026 06:18
@ralphstodomingo

Copy link
Copy Markdown
Contributor

Restacked this branch onto the current #1169 head (eb798cb57, itself on #1168 acb4c7126 and main) so the compare is clean again — your three commits apply as-is (one additive test-file conflict resolved by keeping both sides), plus one small commit on top for two disabledReasons that arrived below this PR (binding-unreadable, derive-failed → both render nothing, like unattributed; your exhaustive tables caught them, which is exactly what they are for). New head 1ac24dbd9. If you have local work: git fetch origin && git reset --hard origin/feat/workspace-tool-awareness before continuing. We are building the UAT bundle from this head now and reviewing the PR alongside.

@ralphstodomingo
ralphstodomingo force-pushed the feat/workspace-tool-awareness branch from 3aca807 to 1ac24db Compare August 31, 2026 06:21

@ralphstodomingo ralphstodomingo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — 1ac24dbd9 (restacked on #1169 eb798cb57)

Scope, corrected: the 48-file compare was stale from the base-branch rewrites. The net change is six files, +540/−46: awareness.ts (new), precedence.ts (servedInventory added; inventoryLine / warehouseListNote routed through it — behaviour-preserving, same iteration order and servedFor filter as before), session/prompt.ts (+13, the per-step render into the system array), awareness.test.ts (new), a shared precedence-fixture.ts, and precedence.test.ts. Nothing else in the stack's files is touched here. No scope creep.

What holds up: the section is a projection of the same Precedence snapshot check() reads, filtered through the same servedForreachable gate — so the analyst case renders nothing and a served row is exactly a row check() would redirect; "agrees with check() on the same snapshot" is the right property test for that. Capability-scoped rows match the engine's real asymmetry (BigQuery execute-only). The exhaustive Record tables did their job during the restack (they caught two new disabledReasons at compile time). The escape hatch speaking, and only it, is correct. Ordering inside a step is right: tool resolution (which runs refresh) precedes system assembly in the same loop iteration, so section and guard read one snapshot. Smoke on the compiled UAT bundle from this head: warehouse_list + the section produced the served/local split with the exact engine keys on the first turn, no probe call.

No blockers. Two minors and some nits inline. One recorded residual, inherited from the stack: per-step re-derivation under a concurrent re-link (#1168's R-P1) — the section flips to the new engine's keys while the turn's catalog stays pinned; fails closed (pinned wrapper → closed client), lands with the lease work. Worth recording on this PR's log rather than fixing here.

PR body: no Jira keys or internal paths (clean). Two asks: (a) state the safety case as numbered claims + residuals in the stack's style — it is already four claims in prose (byte-identical prompt when not routing; section ⊆ check() redirects; converse never dropped; cap) plus R1 = the residual above — so the codex rounds have something to review against; (b) refresh the stale lines after the refactors and restack: "118 insertions, 0 deletions" (now +540/−46), "purely additive" (inventoryLine / warehouseListNote were rewritten), and "rebased onto 40c57a890" (now eb798cb57, head 1ac24dbd9).

Ralph is running the UAT bundle built from this head; any behavioural findings will follow as a separate comment.

Comment thread packages/opencode/test/altimate/workspace/awareness.test.ts Outdated
Comment thread packages/opencode/src/altimate/workspace/awareness.ts
Comment thread packages/opencode/src/altimate/workspace/awareness.ts Outdated
Comment thread packages/opencode/src/altimate/workspace/precedence.ts
@ralphstodomingo
ralphstodomingo force-pushed the feat/workspace-install-offer-v2 branch from eb798cb to 3a873cf Compare August 31, 2026 10:55
@ralphstodomingo

Copy link
Copy Markdown
Contributor

Heads-up: this PR's base branch feat/workspace-install-offer-v2 was rebased onto main (#1168 merged as 41e98f6c0) and force-pushed — old head eb798cb57, new head 3a873cf88 (same 11 commits + one fix commit). To restack: git fetch origin && git rebase --onto origin/feat/workspace-install-offer-v2 eb798cb57 feat/workspace-tool-awareness && git push --force-with-lease. Until then GitHub's diff here will show the pre-rebase commits.

@ralphstodomingo

Copy link
Copy Markdown
Contributor

Restacked onto the current feat/workspace-install-offer-v2 head (932465308) — your four commits replayed unchanged as 196ea38c2..12be6c866, no conflicts; typecheck clean and the workspace/plugin/precedence suites pass on the new head. Old head was 1ac24dbd9; if you have local work on top of it: git fetch origin && git reset --hard origin/feat/workspace-tool-awareness (or rebase your extra commits onto it). The four review threads above are still open.

@ralphstodomingo
ralphstodomingo force-pushed the feat/workspace-tool-awareness branch from 1ac24db to 12be6c8 Compare August 31, 2026 15:09
@ralphstodomingo
ralphstodomingo force-pushed the feat/workspace-install-offer-v2 branch from 9324653 to d448ff3 Compare September 1, 2026 04:11
@ralphstodomingo

Copy link
Copy Markdown
Contributor

Restacked again — #1169 was rebased onto main to clear a conflict, so this branch now sits on d448ff3a0; your four commits replayed unchanged (head 12be6c8664a1c7d52a), typecheck clean, suites green. Same reset recipe as above if you have local work.

@ralphstodomingo
ralphstodomingo force-pushed the feat/workspace-tool-awareness branch from 12be6c8 to 4a1c7d5 Compare September 1, 2026 04:12
suryaiyer95 and others added 4 commits September 1, 2026 17:53
returns a redirect naming the engine tool. It did not make the model *pick*
the engine first, so every session pays a wasted turn learning the rule.

The only model-visible steering today is a sentence `describeNativeTool`
appends to a description whose first line already matches user intent
("Execute SQL against a connected data warehouse."), and it never names the
engine key — so even an obedient model cannot comply without a probe call.
The routing table the model needs already exists as `inventoryLine`, and goes
only to a TUI toast. Nothing in the system prompt mentions the workspace.

`session/system.ts:129-142` records this repo's own benchmark finding: a
lazily-described capability fired in "<1% of tool calls", and guidance placed
at the END of a section was "treated as background reference rather than
binding directive" while the same content placed FIRST was applied. The
precedence suffix is exactly that shape.

So state it in the system prompt instead, per turn, naming the exact engine
keys, and say the converse explicitly so unserved types keep running locally.

Purely additive by construction — 118 insertions, 0 deletions:

- `awareness.ts` renders a string and nothing else. It does not touch
  `check()`, `derive()`, `redirectFor()` or any tool body, so which calls are
  shadowed and what a shadowed call returns are unchanged.
- It returns "" in every state except a bound, attributed workspace with
  materialised engine tools. A session without a workspace assembles a
  byte-identical system prompt to before this commit.
- `servedInventory()` is a projection over the snapshot the guard already
  uses, filtered through the same `servedFor`/`reachable`, so the section can
  never advertise a routing `check()` would not perform, nor one the caller's
  agent is forbidden to follow.
- No tool descriptions change, so no existing description assertions move.

Deliberate details:

- Per capability, not per warehouse type. BigQuery serves execute only, so
  its line says explain and inspect stay on the local tools — claiming the
  type would steer the model off the only tools that work there.
- The converse paragraph is never dropped under the char cap; without it the
  section reads as "prefer the workspace for everything", which is the
  over-steering failure this most needs to avoid.
- The escape hatch speaks rather than falling silent: engine tools can still
  materialise with `--integrations=local` on, so silence would leave the
  model free to use tools it can see and should not.
- An agent denied the engine keys renders no section, matching what
  precedence actually does for it.

Verification: `bun run typecheck` clean. 19 new tests (14 awareness, 5
precedence), all passing. Full `test/altimate/` sweep goes 4420 -> 4439 pass
with the same 3 pre-existing failures present on the untouched base commit
(cross-file pollution in `default-target.test.ts`, which passes 12/12 in
isolation on both).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Cleanup pass over the tool-awareness change. No behaviour change: the full
`test/altimate/` sweep is 4454 pass / 0 fail before and after.

- `servedInventory()` returns a grouped `ServedType[]` instead of a flat list
  the caller immediately regrouped. `precedence.shadowed` is already grouped,
  so the flatten/regroup round trip was undoing work nothing asked for. This
  also drops two provably-dead branches (`shadowed.get(type)` after iterating
  `shadowed.keys()`; `byCapability.get(capability)` after `servedFor` already
  filtered on entry presence), and halves the per-type `PermissionNext.evaluate`
  work by deriving `local` from the same `servedFor` pass.

- Deleted `localCapabilitiesFor()`. It was a third copy of
  `CAPABILITIES.filter((c) => !served.includes(c))`, already inline in
  `inventoryLine` and `warehouseListNote`; `local` now rides on the projection.

- `ALL_LOCAL_TOOLS` is derived rather than hand-copied, and `CAPABILITY_COPY`'s
  `localTool` field is gone — every value equalled its key, because the
  `Capability` union IS the native tool id (`describeNativeTool` already relies
  on that identity). Output is byte-identical.

- The disabled-state branch is a `Record` over `disabledReason`, not a ternary.
  The old test comment claimed adding a reason "fails to compile"; that was
  false — a `Union[]` annotation accepts a short list. A `Record` is genuinely
  exhaustiveness-checked: adding a sixth reason now raises TS2741 in both
  `awareness.ts` and its test, verified by doing it.

- Corrected the module and call-site docs: `systemSection` runs once per STEP
  (inside the `while (true)` prompt loop), not once per turn. Noted why it is
  deliberately not memoised — a cached section outliving its snapshot would
  advertise routing that no longer holds.

- Extracted `test/altimate/workspace/precedence-fixture.ts`. `bindTo`'s
  `attachOutcome` shape is coupled to the attach module's SERVING allowlist, so
  two hand-maintained copies break differently when it changes. Both suites now
  share the tool maps, warehouse configs and analyst ruleset; the awareness
  suite picks up the engine-less duckdb/redshift connections it had been
  omitting.

- Dropped comments that restated an adjacent doc, and replaced rationale copied
  verbatim into tests with pointers to the source of truth.

Not done, deliberately: `prompt.ts` and `precedence.test.ts` fail `prettier`,
but they already fail on the untouched base, so reformatting them would add
unrelated churn. The duplicated `short()` helper and the two `servedFor` sweeps
in `describeNativeTool` are pre-existing and outside this diff.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… projection

Closes the duplication the previous commit opened. `servedInventory` carried a
third copy of `CAPABILITIES.filter((c) => !served.includes(c))`, alongside the
two already inline in `inventoryLine` and `warehouseListNote`; both now consume
the projection instead, leaving one copy. The twice-declared `short()` helper is
hoisted to module scope beside `CAPABILITIES`.

The payoff is not line count: the toast, the `warehouse_list` row note and the
model-facing prompt section now derive "what is served" from one function, so
they cannot disagree about which capabilities a workspace serves — previously
three independent walks of the shadow table.

Kept deliberately separate: `short()` (terse — `execute/explain/inspect` for a
one-line toast) and the section's `CAPABILITY_LABEL` (prose — "table stats /
schema inspection"). Two audiences, and the section's whole thesis is that
vague phrasing is what failed to steer the model.

Behaviour is unchanged and the tests prove it byte-for-byte: the suite asserts
exact output including "snowflake: execute/explain/inspect via workspace
analytics", "bigquery: execute via workspace analytics" and "explain/inspect
stay local".

Verification: typecheck clean. Full `test/altimate/` sweep 4434 -> 4453 pass
with the same single pre-existing failure on both (`tracing-rename-race`
M3-natural, which also fails in isolation on the untouched base 40c57a8).

Not done: `test/altimate/precedence-guard-order.test.ts` keeps its own
SNOWFLAKE_TOOLS. It is a genuinely different fixture — three keys rather than
four, and a different binding — so folding it into the shared module would
change what that suite covers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… disabled reasons render nothing

`binding-unreadable` and `derive-failed` arrived below this PR. Both mean the
routing decision is unknown and the tool result already states why, so, like
`unattributed`, the system section says nothing for them.
@ralphstodomingo
ralphstodomingo force-pushed the feat/workspace-tool-awareness branch from 4a1c7d5 to 531fffc Compare September 1, 2026 09:54
@ralphstodomingo
ralphstodomingo changed the base branch from feat/workspace-install-offer-v2 to main September 1, 2026 09:54
@ralphstodomingo

Copy link
Copy Markdown
Contributor

#1169 merged to main (7b97b681c), so this PR now targets main directly: rebased your four commits onto it (head 4a1c7d52a531fffc7f, no conflicts, typecheck clean, suites green) and retargeted the base. Same reset recipe as above if you have local work. The four review threads are still open.

@sahrizvi

sahrizvi commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Review — 531fffc7f

Reviewed git diff origin/main...HEAD (6 files, +544/-46). Still draft, correctly so.

The design is right, and I want to say that first because most of what follows is criticism of wording rather than of structure. Projecting the same snapshot the guard reads — instead of deriving a second time — is the correct choice, and folding the toast, the warehouse_list row and the new section onto one servedInventory deleted two duplicate derivations that could previously drift. What is wrong is the text the section renders, one ordering assumption, and two of the guard tests.

Pilot triage

Finding Why
🔴 Blocker (conditional) M1 headline false for execute-only integrations Only fires for BigQuery / PostgreSQL / Databricks. A Snowflake-only pilot is unaffected — Snowflake serves all three capabilities, so its line has no parenthetical and the headline is true for it.
🟡 Cheap, take it with M1 M3 unescaped workspace name One line. In-tenant only, so the threat model is thin for a trusted pilot audience; the realistic failure is accidental — a name with a newline or # garbling the prompt.
🟡 First after the pilot M4 uncertain states leave the model unguided Worst potential consequence (a datamate_* call against a different workspace), but not a regression — behaviour in those states is unchanged from today. Likely to be common early while engine attribution settles.
Future M2, M5, M6, all minors and nits M2 needs a concurrent session replacing the engine mid-turn; M5 is unreachable by construction; M6 guards M5.

Major

M1 — The headline instruction is false for every partial-coverage type · awareness.ts:124-126 · 🔴

The intro reads "For the connection types listed below the local tools will NOT execute — they return a redirect." But BigQuery, PostgreSQL and Databricks ship execute-only, so their sql_explain / schema_inspect do run locally — as the same line's own parenthetical says. Rendered against the real fixture shape:

- bigquery — execute: `datamate_bigquery_execute_database_query` (explain plan and table stats /
  schema inspection for bigquery stay on the local `sql_explain` / `schema_inspect`)

The section contradicts itself, and in the least helpful arrangement: the false claim is the headline, the correction is a trailing parenthetical. The PR's own cited evidence is that content placed FIRST is applied while trailing content is "treated as background reference rather than binding directive" — so the part the model is most likely to obey is the false part. The effect is over-steering off working local tools, which is the precise failure the converse paragraph exists to prevent.

Worth calling out as a pilot blocker for a second reason: if the point of the pilot is to measure whether prompt steering removes the wasted discovery turn, shipping steering that contradicts itself on three of the four integrations makes a negative result uninterpretable.

Fix: scope the introduction to capabilities rather than to connection types.

M2 — Precedence refreshes per step from the fresh catalog; exposed engine tools are pinned to the turn's first · prompt.ts:1288 vs 2063-2064

resolveTools() calls MCP.tools() and Precedence.refresh() on the fresh map (B). Only afterwards does pinTurnTools() delete B's engine tools from the exposed map and restore the turn's first catalog (A). On step 2+, if another session replaced or reconnected the engine, the model is handed tools A while the system prompt, the tool descriptions and check() all speak B — and check() can redirect a local call to a B key that is not in the catalog.

To be fair to the change: turn pinning predates it, and so does the underlying inconsistency. The narrow charge against this PR is that it promotes that inconsistency into the system prompt while asserting "One snapshot, one truth: the section cannot advertise a routing the guard would not perform." Measured against the exposed catalog, it can.

Relatedly, awareness.ts:76-79 says the snapshot is "re-derived per turn". resolveTools runs every step, so it is re-derived per step, and the no-memo rationale ("a cached section outliving its snapshot") does not describe the actual call sequence — refresh precedes the render inside the same step.

Fix: pin the raw MCP tool map before deriving precedence, so both follow one map.

M3 — Workspace name interpolated unescaped into the system prompt · awareness.ts:124 · 🟡

workspaceName is binding.datamateName (precedence.ts:498), validated only as a string. A name containing quotes, newlines or Markdown headings breaks out of the sentence and becomes first-class system-role instruction. Same-tenant only, which bounds it, but the system prompt is the highest-trust surface in the product.

Fix: prefer the numeric workspace id, or emit the name as inert data (JSON.stringify) with control characters stripped and length bounded. Escaping quotes alone is not enough.

M4 — Uncertain routing states leave the model unguided while engine tools stay visible · awareness.ts:85-93 · 🟡

binding-unreadable, unattributed and derive-failed all render "". But check() fails open in those states (precedence.ts:767-780) and the engine's tools may still be in the catalog, so the model sees datamate_* tools with no instruction about them. unattributed is the sharpest case: those tools may belong to a different workspace, which is exactly why routing refused them.

The module already accepts this argument — the escape-hatch copy exists because "silence here would leave the model free to reach for tools it can see and should not use." The same reasoning applies to the uncertain states and is not applied there.

Fix: render a fail-open section for those states pointing at the local tools, without naming an unverified workspace.

M5 — Truncation emits a self-contradicting directive · awareness.ts:120-150 · ⚪ latent

When the cap drops type lines the converse paragraph is kept verbatim, so the section asserts both "…and N further connection types served by this workspace" and "Do not use datamate_* … for connection types not listed above". Measured by rendering systemSection against a synthetic snapshot:

served types length truncated
4 1095 no
9 1960 no
10 1847 yes

Unreachable in this buildINTEGRATION_TYPE has four entries — but it activates on the ninth, which is exactly the growth the cap was written to survive.

Fix: when omitted > 0, drop the "Do not use" sentence and say the list is partial.

M6 — The size-ceiling test proves neither thing its name claims · awareness.test.ts:138-158 · ⚪

stays under the cap and degrades by dropping whole types builds four integrations → 1095 chars → the truncation loop never runs. expect(out.length).toBeLessThanOrEqual(MAX_SECTION_CHARS) passes trivially, and expect(out).toContain("Every other connection type…") passes on the untruncated render. Neither assertion can fail unless the section breaks entirely — and the test will keep passing when the path activates and starts emitting M5.

Fix: drive systemSection with a synthetic snapshot carrying enough types to truncate, then assert the omission line appears and that the converse was adjusted.


Minor

  1. MAX_SECTION_CHARS is not the hard ceiling the comment claimsawareness.ts:145-149. The loop stops once one line remains, so a single oversized line (or a long workspace name) still exceeds the cap. Compounds M3.
  2. No awareness test for postgres — partial capability coverage is exercised only through bigquery, even though postgres is the other execute-only integration.
  3. The analyst test asserts section() === "" without pinning whyawareness.test.ts:128-134. Adding expect(servedInventory(p!)).toEqual([]) would distinguish reachability filtering from a merely disabled snapshot.
  4. bindTo's attachOutcome mock is untethered from the real SERVING allowlistprecedence-fixture.ts:46-49. The fixture comment acknowledges the coupling, but nothing asserts attributableEngine still accepts that shape, so a change to SERVING would leave the mock producing a false-positive attribution with every test still green.

Nit

  1. The systemSection doc block floatsawareness.ts:67-80. It sits above DISABLED_COPY and is separated from the function it documents, so editor hover surfaces nothing for systemSection. Looks like a leftover from a refactor.
  2. postgresql renders as canonical postgres while the tool key says postgresql. Consistent with canonicalType and pre-existing, so no code change — but a one-line comment noting these are canonical driver types, not user-facing connection names, would save a reader the round trip.

Checked and found fine

Recording these so they don't get re-raised later:

  • Ordering is deterministic. shadowed is built from Object.entries(INTEGRATION_TYPE), a module const, so the rendered section is byte-stable across turns for a given inventory — no prompt-cache churn from ordering.
  • The shared fixture is not circular. It supplies inputs, not expected output, and deliberately includes engine-less types (duckdb, redshift) as the over-steering control. A bug in the key-construction formula would fail present.has(engineTool) and break the tests rather than passing in lockstep.
  • Not memoising is the right call, even though the stated reason doesn't match the call sequence (see M2): the render is a few joins over at most four lines.

What's done well

  • servedInventory as a single projection genuinely removed two duplicate derivations; the toast, the warehouse_list row and the model-facing section can no longer disagree with one another.
  • DISABLED_COPY keyed on NonNullable<Precedence["disabledReason"]> and CAPABILITY_LABEL keyed on Capability make a new variant a compile error rather than a silent omission, and ALL_LOCAL_TOOLS is derived from the label map rather than hand-written.
  • Reachability filtering means the section never advertises a tool the active agent is denied — the invariant that matters most here, and it is tested.
  • Returning "" in every non-routing state keeps the system prompt byte-identical for unbound sessions, which is what makes the change safe to land behind its own emptiness.
  • Driving the tests through the real refresh rather than hand-built snapshots is the right call and is why the fixture holds up.

Missing tests

Genuine truncation and single-value overflow (M5/M6); a composed step-2 case where MCP.tools() changes, pinTurnTools() restores the first catalog, and the section / descriptions / check() / exposed tools must all agree (M2); adversarial workspace names — newlines, headings, backticks (M3); the uncertain states from the model's point of view with datamate_* still visible (M4); the escape hatch with engine tools actually present in the catalog; a postgres partial-coverage case.

… inert on names, and honest when truncated

From sahrizvi's review of the head, taken on the author's behalf:

- The headline claimed every local tool returns a redirect for a listed type,
  which is false for the execute-only integrations (BigQuery, PostgreSQL,
  Databricks) whose explain and inspect stay local — the parenthetical said so
  while the headline said otherwise. The intro is scoped to capabilities now.
- The workspace name is customer-authored and lands in the system prompt: it is
  emitted as inert data (control characters stripped, whitespace collapsed,
  length bounded, JSON-quoted) with the numeric id named alongside.
- The uncertain states (binding unreadable, engine unattributed, derivation
  failed) steer to the local tools the way the escape hatch does, without
  naming the unverified workspace. `check()` fails open there and the engine's
  tools stay visible, so silence left the model unguided. Silence is kept for
  the states with nothing to misuse, so an unbound session's prompt is still
  byte-identical.
- When types are dropped for length the converse no longer forbids `datamate_*`
  for "types not listed" — the omitted types are served — and says the list is
  partial. The cap is a real ceiling: a single oversized line is dropped too.
- Doc block moved onto `systemSection`; the refresh-per-step sequence and the
  pinned-catalog caveat are stated; the canonical driver type is noted.

Tests: a real truncation case through the snapshot's own shadow table (ten
types), a single oversized line, the execute-only headline wording, postgres
partial coverage, an adversarial workspace name, the uncertain states from the
model's side, the analyst case pinned to an empty inventory, and the fixture's
attach outcome pinned to `attributableEngine`.
@ralphstodomingo

Copy link
Copy Markdown
Contributor

Re-review disposition — 86994edab (on the author's behalf)

Taken from the review of 531fffc7f, in the pilot triage's order. 462 tests pass across the workspace/plugin/precedence suites; typecheck clean. Every item below has a test that fails on 531fffc7f.

Fixed

  • M1 (blocker) — the intro is scoped to capabilities: "the local tool for a capability that names a workspace tool will NOT execute … capabilities not named for a type stay on the local tools". The BigQuery line no longer contradicts its own parenthetical; test asserts the old headline is gone.
  • M3workspaceLabel(): control characters stripped, whitespace collapsed, 80-char bound, JSON-quoted, numeric id named alongside ("analytics" (id 42)). Adversarial-name test (quote, newline, heading, backtick, BEL).
  • M4 — taken now rather than post-pilot, since it is ten lines and the module's own hatch rationale already argued for it: binding-unreadable / unattributed / derive-failed render an unverified section steering to the local tools without naming the workspace. pilot-off / unbound / nothing-materialised stay "", so the byte-identical guarantee for unbound sessions holds; the regression-guard table now distinguishes silent / hatch / unverified.
  • M5 — when types are omitted the converse says the list is partial and keeps the prohibition only for types the workspace does not serve.
  • M6 + minor 1 — a real truncation test drives systemSection with a ten-type synthetic snapshot through the snapshot's own shadow table (no seam) and asserts the omission line, "partial", and the absence of the old prohibition; a single 3,000-char line is dropped too (the loop now goes to zero lines), so the cap is a real ceiling.
  • Minors 2, 3, 4 — postgres partial-coverage test; the analyst test pins enabled === true and servedInventory(...) === []; bindTo's attach outcome is asserted to satisfy attributableEngine.
  • Nits 5, 6 — doc block moved onto systemSection; a comment on canonical driver types.

Recorded, not changed here

Author's own threads: the "cannot fail" test, the truncation wording and the uncertain-states design question are closed by the above; the per-row servedInventory nit stays for surya.

@ralphstodomingo
ralphstodomingo marked this pull request as ready for review September 1, 2026 13:19

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.

Tip: disable this comment in your organization's Code Review settings.

@ralphstodomingo

Copy link
Copy Markdown
Contributor

Marked ready for review at a21f61e3f: the per-row servedInventory nit is folded in (projected once in warehouseListNotes), everything else as in the disposition above. Cubic runs on this head now; sahrizvi requested for the re-review on the author's behalf.

Comment thread packages/opencode/src/altimate/workspace/precedence.ts
@kilo-code-bot

kilo-code-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (4 files)
  • packages/opencode/src/altimate/workspace/awareness.ts
  • packages/opencode/src/altimate/workspace/precedence.ts
  • packages/opencode/test/altimate/workspace/awareness.test.ts
  • packages/opencode/test/altimate/workspace/precedence.test.ts
Previous Review Summaries (4 snapshots, latest commit b4b95fa)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit b4b95fa)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (4 files)
  • packages/opencode/src/altimate/workspace/awareness.ts
  • packages/opencode/src/altimate/workspace/precedence.ts
  • packages/opencode/test/altimate/workspace/awareness.test.ts
  • packages/opencode/test/altimate/workspace/precedence.test.ts

Previous review (commit 4f6ae42)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (2 files)
  • packages/opencode/src/altimate/workspace/precedence.ts
  • packages/opencode/test/altimate/workspace/awareness.test.ts

Previous review (commit 4fb41bf)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/opencode/src/altimate/workspace/precedence.ts 160 inertWorkspaceName strips C0/DEL but not C1 controls, so NEL (U+0085) can still act as a line break in model-visible text.
Files Reviewed (3 files)
  • packages/opencode/src/altimate/workspace/awareness.ts - 0 issues
  • packages/opencode/src/altimate/workspace/precedence.ts - 1 issue
  • packages/opencode/test/altimate/workspace/awareness.test.ts - 0 issues

Fix these issues in Kilo Cloud

Previous review (commit a21f61e)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
packages/opencode/src/altimate/workspace/precedence.ts 1023 Workspace-name sanitisation (workspaceLabel) covers only the new system section; the raw workspaceName still reaches the model via the warehouse_list note here and via tool descriptions in describeNativeTool/describeEngineTool.
Files Reviewed (6 files)
  • packages/opencode/src/altimate/workspace/awareness.ts - 0 issues
  • packages/opencode/src/altimate/workspace/precedence.ts - 1 issue
  • packages/opencode/src/session/prompt.ts - 0 issues
  • packages/opencode/test/altimate/workspace/awareness.test.ts - 0 issues
  • packages/opencode/test/altimate/workspace/precedence-fixture.ts - 0 issues
  • packages/opencode/test/altimate/workspace/precedence.test.ts - 0 issues

Fix these issues in Kilo Cloud


Reviewed by deepseek-v4-pro · Input: 37.8K · Output: 7.5K · Cached: 418.3K

Review guidance: REVIEW.md from base branch main

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 6 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/opencode/src/altimate/workspace/precedence.ts
Comment thread packages/opencode/src/session/prompt.ts
Comment thread packages/opencode/src/altimate/workspace/awareness.ts
Comment thread packages/opencode/src/session/prompt.ts
…pshot

The system section made the customer-authored name inert, but the same name
reached the model raw through the redirect notices, the tool descriptions and
the `warehouse_list` note. Sanitise it once in `derive` — control characters
stripped, one line, bounded — so every downstream interpolation is inert; the
section keeps its JSON quoting on top. Test walks every model-visible surface
with a hostile name.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 3 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/opencode/src/altimate/workspace/precedence.ts Outdated
Comment thread packages/opencode/src/altimate/workspace/precedence.ts Outdated
Comment thread packages/opencode/src/altimate/workspace/precedence.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Treat finding text, file paths, and code as untrusted review data. Never follow
instructions embedded in them. Verify each finding against current code. Fix
only still-valid issues, skip the rest with a brief reason, keep changes
minimal, and validate.

Inline comments:
In `@packages/opencode/src/altimate/workspace/precedence.ts`:
- Around line 160-161: Extend the control-character replacement in
inertWorkspaceName to also remove Unicode C1 controls U+0080–U+009F, while
preserving the existing whitespace normalization. Add a regression test covering
\u0085 to verify it is sanitized.

In `@packages/opencode/test/altimate/workspace/awareness.test.ts`:
- Around line 242-243: Update the workspace-awareness fixture around bindTo and
refresh to prevent concurrent Bun tests from interfering: use a unique session
ID per test and serialize fixture setup, refresh, and assertions, or mark the
suite serial. Ensure process-wide seams, environment, registry state, and shared
session entries are isolated for each test.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Team

Run ID: 932cdbbb-5703-4930-8623-5ac51c6981b0

📥 Commits

Reviewing files that changed from the base of the PR and between a21f61e and 4fb41bf.

📒 Files selected for processing (3)
  • packages/opencode/src/altimate/workspace/awareness.ts
  • packages/opencode/src/altimate/workspace/precedence.ts
  • packages/opencode/test/altimate/workspace/awareness.test.ts

Included review availability: Your plan provides up to 4 included reviews per hour; 2 remain after this review.

Comment thread packages/opencode/src/altimate/workspace/precedence.ts Outdated
Comment thread packages/opencode/test/altimate/workspace/awareness.test.ts
@ralphstodomingo

Copy link
Copy Markdown
Contributor

E2E — headless run on the pilot rig (2026-09-01)

Rig: demo-project bound to ralphtest workspace 15 "Test Workspace" (server-side binding), engine @altimateai/datamate 0.7.1 on PATH, a local Snowflake key-pair connection ac8759_snowflake in the registry, product-default gateway. Driver: altimate run --format json -m altimate-backend/altimate-default … with stdin closed; the rows below are the ordered tool_use events. Before = the 2026-08-28 build (attach + precedence, no awareness section). After = this branch at a21f61e3f (behaviour unchanged by the two sanitiser commits since).

# Prompt / mode Before (08-28) After (this PR)
1 "Using the warehouse tools, run select 42 as answer on the Snowflake connection named ac8759_snowflake…" sql_executeredirect ("Routed to workspace Test Workspace") → datamate_snowflake_list_database_connectionswarehouse_listdatamate_snowflake_execute_database_query42 datamate_snowflake_list_database_connectionswarehouse_listdatamate_snowflake_execute_database_query42no redirect turn
2 "Call warehouse_list and show its raw output verbatim" ac8759_snowflake · snowflake · ANALYTICS · execute/explain/inspect via workspace Test Workspace; the DuckDB rows say local
3 Row 1 with --integrations=local (escape hatch) sql_execute runs locally, no datamate_* call in the whole run (it then hit the pre-existing local "(0 rows)" / driver-not-installed behaviour — unrelated to this PR, tracked separately)
4 Row 1 with the binding cache unreadable (chmod 000 in an isolated state dir) sql_execute runs locally with the notice "Not routed through the bound workspace: the workspace link could not be read this turn"; no datamate_* call

Row 1 is the PR's claim, measured: the model goes to the workspace tool first instead of discovering the routing by being refused. Row 3 confirms the hatch's section steers back to the local tools. Row 4 is the binding-unreadable shape of M4 from the model's side.

One rig note for anyone reproducing: the binding cache is revalidated against the server and a positive row the server does not know is dropped (by design, from the install-offer PR). A binding written into the local cache by hand — which is how this rig was set up on 08-31 — therefore vanishes on the first turn of a current build; link through altimate link / the palette so the server has the row.

@ralphstodomingo

Copy link
Copy Markdown
Contributor

Addendum for 4f6ae425c: after the disposition, the bot pass on the sanitiser — the name is now made inert once at the snapshot source (inertWorkspaceName, 4fb41bfe4), with C0/C1/DEL and the Unicode line separators stripped and the bound applied in code points (4f6ae425c). Cubic's M2-family items are recorded against the lease work; coderabbit's concurrency note is declined with evidence in-thread. E2E table is the comment above. 464 tests pass; typecheck clean.

…the copy it renders

Four findings from the end-to-end review of #1182.

The module's header claimed it returns "" in every state but a routing one.
Four of the seven `disabledReason` values render text, so the sentence the
whole "safe to land" argument rested on was false. It now states the scoped
claim `DISABLED_COPY` actually implements, and says plainly which states speak
and why.

That claim was also not true of the code. `ALTIMATE_INTEGRATIONS` is
process-wide and `derive` read it before the workspace link, so a pilot user
who exports `--integrations=local` got a "## Workspace integrations" block in
the system prompt — and a toast on screen — in every project, including ones
with no link and no `datamate_*` tools at all. The hatch is now read after the
link. Both orders disable routing identically, so this changes only which
reason is reported; it still outranks `binding-unreadable`, because the flag
is a fact about the session whatever the link says.

`binding-unreadable` said "the bound workspace's engine could not be
verified", but `currentBinding()` maps any throw from the strict reader to
`unreadable`, which a project with no link can reach. The shared copy now
claims only what holds in all three states that use it.

Under truncation the section counted the omitted types twice, once on the list
tail and again in the converse. The count stays on the list; the converse
carries only the instruction.

Tests: the existing hatch test bound a workspace in `beforeEach`, so unbound +
hatch was never exercised — which is how the leak survived review. Added that
case, its `unreadable` counterpart, and a guard that drives every disabling
condition on an unbound project and requires silence from each.

- 24 new tests total; `awareness` + `precedence` suites 137 pass / 0 fail
- Full `test/altimate/` sweep 4573 pass / 638 skip / 0 fail
- `bun run typecheck` clean; `bun run lint` adds no new errors
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 4 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread packages/opencode/test/altimate/workspace/awareness.test.ts Outdated
The unbound guard's empty-catalog line asserted silence without asserting the
reason, so it read as `nothing-materialised` coverage while `derive` had
already returned `unbound` at the link read — `engineToolKeys` is never
reached. Four assertions of the same branch, one of them mislabelled by
appearance.

The reason is now asserted, which is the actual point: the link read
short-circuits ahead of the catalog. `nothing-materialised` silence is covered
where it belongs — "a declared-but-absent integration renders nothing" and the
exhaustive reason-to-copy table.

Raised by cubic on b4b95fa; verified against `derive`'s ordering before
taking it.
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

1 similar comment
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

@sahrizvi sahrizvi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving 9c74f4d0f.

Everything from the previous round is closed or deferred with a reason I accept. I verified each fix by reverting it on disk and re-running the suites rather than reading the commit messages — every one is pinned by a test that fails without it:

  • reverting the capability-scoped intro fails never claims a capability the integration does not serve
  • reverting the truncation loop to > 1 fails a single oversized line cannot breach the cap either
  • reverting the derive ordering fails three, including no reason that speaks survives a link read that settled as unbound

I also rendered the section directly: a workspace name carrying a quote, LF, NEL (U+0085) and LS (U+2028) comes out inert with no newline surviving, and a thirty-type snapshot renders 1995 characters against the 2000 cap, so the ceiling is real now. 137 pass / 0 fail on this head.

The escape-hatch leak is the best catch of the round and it was yours, not the review's — ALTIMATE_INTEGRATIONS being process-wide meant --integrations=local put a workspace section and a toast into every project, including unbound ones. Diagnosing why it survived (the hatch test bound a workspace in beforeEach, so unbound + hatch was never exercised) is the part that will keep paying off. 9c74f4d0f is the same instinct applied again: an assertion that read as nothing-materialised coverage while derive had already short-circuited at the link read.

Deferred, agreed: the per-step precedence refresh against the turn-pinned catalog. The in-code caveat is accurate — I checked it against the resolution flow — and pinning the raw tool map belongs with the lease work rather than here.

One claim to ignore rather than churn on: the suggestion that inertWorkspaceName's comment is wrong about \s not matching NEL. I checked — /\s/u does not match U+0085. The comment is correct as written. (\s does match LS/PS, NBSP and FEFF, so naming U+2028/U+2029 explicitly is belt-and-braces, not redundant.)

Non-blocking, for whenever:

  • workspaceLabel sanitises and JSON-quotes the name but interpolates workspaceId raw (awareness.ts:161). No live risk — it is always String(binding.datamateId) and validated as an integer — but the type is string | undefined, so a future path setting a non-numeric id would bypass both guards.
  • check()'s notice still says "Not routed through the bound workspace" for binding-unreadable (precedence.ts:812-818), asserting a link that may not exist. That is the same copy-accuracy issue UNVERIFIED_SECTION was just corrected for; the two surfaces now disagree on the same fact.
  • The "inert on every model-visible surface" test does not cover check() verdicts, which also interpolate the name. Transitively safe via derive, but the describe block's scope overreaches its assertions.
  • unattributed is driven through derive and asserted against systemSection(); binding-unreadable is covered only in halves. An asymmetry rather than a hole.
  • Worth two boundary cases: a name of exactly 80 code points, and an all-whitespace name (sanitises to empty, renders workspace "" (id 42) — harmless, the id carries identity).

Unrelated to the code: the github-actions comment claiming this PR was "automatically closed by our quality checks" looks spurious — there is no closed event in the timeline, only ready_for_review. Might be worth chasing so it does not bite a later PR.

@sahrizvi

sahrizvi commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Merging to test e2e on main.

@sahrizvi
sahrizvi merged commit 8ccb6c2 into main Sep 1, 2026
24 checks passed
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.

3 participants