Skip to content

[AIGTWY-4877] Exercise CUJ2 MCP tool use in both agents - #1015

Draft
masonc08 wants to merge 1 commit into
mainfrom
mason-cao_data/masonc08/aigtwy-4877-cuj2-mcp-tool-use
Draft

masonc08 wants to merge 1 commit into
mainfrom
mason-cao_data/masonc08/aigtwy-4877-cuj2-mcp-tool-use

Conversation

@masonc08

@masonc08 masonc08 commented Oct 6, 2026

Copy link
Copy Markdown
Collaborator

What did you change, and why?

Extended the CUJ2 journeys from #993 beyond name-only MCP registration. Both Claude and Codex now inspect their native MCP inventory, require the connected ug_e2e.tools.fixture_reader service and read_fixture tool, call it with a fresh nonce, and verify the nonce-derived SHA-256 receipt plus the actual tool-call record in the parent transcript.

The terminal inventory helper follows the pattern in #990. Its duplicate helper file can disappear when this branch rebases after that PR merges.

How do you know it works?

  • 42 integration-contract tests passed.
  • Ruff check and format check passed.
  • All 15 dedicated-workspace CUJ cases collect.
  • Live workspace execution was not run locally.

Blocker

The CUJ2 workspace currently publishes system.ai.sandbox. This draft expects the read-only workspace fixture to publish mcp_servers.unity_catalog_location = ug_e2e.tools. CI cannot pass until that external workspace configuration is provisioned; the test intentionally does not mutate or repair it.

rohita5l pushed a commit to djcelis/unity-gateway that referenced this pull request Oct 8, 2026
The CUJ plan's skills checks for CUJ 2, 3 and 6 were skipped because
Default Storage rejects skill uploads. With skill fixtures on a schema
backed by customer S3 storage, the journeys can now cover them.

Changes:
- CUJ 2 (`skills.names = ["ug_e2e.skills.fixture-summary"]`):
  - only that bundle downloads, byte-identical to the live fixture;
- both agents list it in `/skills`, and the schema's other skills are
absent;
- both agents invoke it and answer with a token that exists only in the
bundle's reference file.
  - `_assert_cuj2_config` now expects the skills selector.
- CUJ 3 (`skills.unity_catalog_location = "ug_e2e.skills"`):
  - every skill in the schema downloads and is listed;
  - the `ug_e2e.other_skills` decoy stays absent;
  - `ug status` counts the skills per agent;
  - both agents invoke a downloaded skill.
- CUJ 6:
- Phase A publishes the named skill. It downloads once to both skill
roots and is unchanged by a repeat configure.
- Phase A runs no terminal launch: one would make ug own `/etc`, and
phase B's headless configure would then refuse.
- Phases B and C drop the selector, and the skill disappears from disk
and from each agent's `/skills`, including after relaunch.
- The hand-authored user skill must render in the same screen, so the
absence checks can't pass on an unloaded list.
- The user's Claude settings are seeded with the theme the Claude TUI
saves on first launch, so only ug's writes show as a diff.
- Shared helpers live in `tests/e2e_cuj/helpers/skills.py`. The
`tests/README.md` coverage row is updated.
- UC skill names reject underscores, so the plan's `fixture_summary` is
`fixture-summary`.

One-time workspace setup, done outside the tests on 2026-10-07:
- CUJ 6 metastore (ca-central-1):
  - `ug_e2e.skills` sits on S3-backed storage;
- the CUJ SP has `READ_VOLUME` on it, which `skill_authz.jsonnet`
requires for skill reads and downloads;
  - workspace A publishes the Phase A skills selector.
- CUJ 2/3 metastore (us-east-1):
  - the same S3-backed storage credential and an external location;
- `ug_e2e.skills` and `ug_e2e.other_skills` recreated on that storage,
holding the four fixture skills;
- CUJ 2 publishes `skills.names`, and CUJ 3 already published
`unity_catalog_location = ug_e2e.skills`.

This overlaps Mason's open databricks#1014 and databricks#1015 in `test_cuj2_mps_mcp.py`,
where this PR only adds one key to `_assert_cuj2_config`.

This pull request and its description were written by Isaac.

Co-authored-by: Isaac <no-reply@databricks.com>
sunishsheth2009 pushed a commit to erifyc1/unity-gateway that referenced this pull request Oct 8, 2026
…atabricks#1040)

The e2e CUJs grew one copy of each helper per journey. This consolidates
the shared helpers so CUJ 7 (databricks#930) and databricks#1014 / databricks#1015 can build on one
version, and fixes three recurring CUJ flakes found in recent CI runs.

Shared helpers (behavior preserving; every assertion, timeout and
threshold is unchanged unless listed under behavior changes):
- `UserSession.revert_machine_wide` replaces the class teardown revert
and CUJ 3's two per-test copies (same terminal names and messages).
- `base.make_workspace_client` and `base.bearer` replace the per-CUJ
client and bearer code. CUJ 6 now honours `CLIENT_ID_ENV` /
`CLIENT_SECRET_ENV`.
- `Terminal` drives bare `ug` (`agent=`), and `Terminal.wait_until` is
the one permission-rejecting wait for CUJ 2, 3 and 6. CUJ 1 keeps
`AgentTerminal.wait_for_task`, which approves the read-only `find`
prompt Claude emits in CI.
- `evidence.assert_models` / `assert_served` replace CUJ 1's
native-model check and the CUJ 2 / CUJ 3 served-response checks. Per-CUJ
thresholds and matchers stay local.
- `Workspace.agent_configs` splits a published config by agent for CUJ
4, 5 and 6. CUJ 5 reads the config through `workspace.config()` instead
of a raw GET.
- `helpers/poll.py` is the one bounded poll for the CUJ 1 and CUJ 6
trace waits (same budgets) and the `ug mcp list` retry. Trace SQL stays
on `tests/integration/utils/sql.py`.
- `helpers/mcp.py` (`registered_name`), `evidence.claude_file_task`, and
shared model and header constants in `helpers/constants.py`.

Reliability fixes:
- MCP registration (Claude), failing 3 of 13 recent runs: Claude
returned correct receipts but keyed them `mcp__server__tool`, and on
this PR's first run echoed each raw `{"result": "<sha>"}` tool result.
The prompt names the exact keys, and the check accepts exactly those two
native shapes. The receipt values and key set must still match exactly.
- `ug mcp list` health row (`claude:failed`, 1 of 13): Claude's
single-shot probe raced the proxy cold start. The test polls up to 90s
until the rows match, then runs the same exact assertion.
- CUJ 4 Claude tasks use the absolute-path prompt CUJ 3 already used, so
Claude no longer searches with `find` and hits a permission prompt.
- Teardown leaks: a class that leaves `/etc` agent files still fails,
and later classes now name that class and those paths instead of a
generic dirty-runner error.
- The CUJ bearer is refreshed per test (classes such as catalog
discovery can outlive the M2M token). Every bearer a session installs
stays redacted in artifacts.
- ug review: `max_output_tokens` 4,000 to 16,000. High-effort reasoning
consumed the old budget, leaving "returned no output text". The error
now includes `incomplete_details.reason`.

CI:
- E2E CUJs run one runner per file, checked as `E2E CUJs · <CUJ_NAME>`
(for example `E2E CUJs · CUJ 3 · UC model discovery`, names from the CUJ
plan doc). CUJs share machine-wide `/etc` agent settings, so they cannot
run concurrently on one runner. Each file still runs against its own
read-only workspace, with `fail-fast: false`, and `All integration
tests` requires every shard.
- A small plan job discovers every `tests/e2e_cuj/test_*.py` and reads
its module-level `CUJ_NAME`, so adding a CUJ needs no workflow edit (and
no `.github/` owner approval). A file without `CUJ_NAME` fails the plan
job; a unit contract test runs the same discovery script and also
enforces unique names and the `test_cuj<N>_<topic>.py` / `CUJ <N> ·`
convention.
- The CUJ runners set `UV_HTTP_TIMEOUT=120` and `UV_HTTP_RETRIES=6`. All
of them install from PyPI at once, and one shard already failed on a
metadata fetch timeout.
- CUJ files are renamed to `test_cuj<N>_<topic>.py`:
- CUJ 1: `test_cuj1_explicit_models.py` (was
`test_ug_managed_sentinel.py`)
  - CUJ 2: `test_cuj2_mps_mcp.py`, `test_cuj2_skills.py`
- CUJ 3: `test_cuj3_models.py` (was `test_catalog_discovery.py`),
`test_cuj3_mcp.py` (was `test_mcp_registration.py`),
`test_cuj3_skills.py`
- CUJ 4: `test_cuj4_smart_routing.py`; CUJ 5:
`test_cuj5_budget_defaults.py`; CUJ 6: `test_cuj6_repeated_config.py`

Behavior changes worth a look:
- CUJ 2 Claude now rejects "Do you want to proceed?" like every other
CUJ (it previously waited out the timeout).
- The CUJ 6 Codex span poll's last query lands at the 300s deadline
rather than up to 15s after it.

Testing:
- `tests/test_e2e_cuj_helpers.py` adds offline tests for the new
helpers. Terminal tests run the real driver on a PTY against a fake
`ug`.
- Full unit suite and `--collect-only` (78 CUJ tests) pass locally. The
E2E CUJs lane is the live check.

CI timing, measured from job start and end times (main run 37821776097
vs this PR's run 37822258143):
- E2E CUJs: 19.5 min as one job, now 7.8 min wall across 9 parallel
shards. Total runner time is about the same (21.9 min).
- Longest shard: CUJ 6 at 7.8 min. All others take 1.2 to 3.0 min.
- Whole CI run: 20.0 min, now 18.7 min. `Full journeys · Claude` (about
13 to 15 min) is now the critical path, so cutting CUJ time further
won't shorten CI.

Not included: the workspace URL normalization in `src/ucode` from
AIGTWY-4972 (a product behavior change, separate PR).

This pull request and its description were written by Isaac.

Co-authored-by: Isaac <no-reply@databricks.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant