Skip to content

feat(mcp): route project-qualified paths in the POSIX tools - #1421

Open
phernandez wants to merge 1 commit into
1398-eval-test-fixesfrom
1415-posix-project-routing
Open

feat(mcp): route project-qualified paths in the POSIX tools#1421
phernandez wants to merge 1 commit into
1398-eval-test-fixesfrom
1415-posix-project-routing

Conversation

@phernandez

Copy link
Copy Markdown
Member

Why

Implements #1415: projects become mount points in the POSIX tool namespace. Motivated by measured agent behavior in the #1398 A/B runs — tool outputs return project-prefixed identifiers, inputs demanded a separate project arg, agents faithfully quote outputs and omit the arg (shell affordances prime cwd-thinking), and the stateless default was silently wrong. In run 5, both surfaces' agents wrote a perfect relation into the wrong project.

Stacked on #1416 (1398-eval-test-fixes).

What changed

  • One shared resolver (mcp/project_context.py): first path/identifier segment naming an active project routes there with the remainder — inputs now accept exactly what outputs produce. The CLI verbs inherit it through the shared layer.
  • Explicit param wins only on agreement: project param + path prefix disagreeing refuses naming both; a workspace-qualified explicit param keeps its workspace (review catch — the agree-branch initially discarded it).
  • ls / lists active projects as the root directory — in-band discovery, the mount-point view.
  • Multi-project unqualified refusal: a path matching no project errors with the copyable project list (no project 'x' — active projects: …); single-project configs resolve unqualified paths unchanged.
  • Collision rule documented: first-segment-matches-a-project shadows a same-named folder; reachable only in single-project configs where there's no ambiguity.
  • Known scope gap (review, tracked on Project-qualified paths: projects as mount points in the tool namespace #1415): hosted/factory-mode surfaces with empty local config don't refuse yet.

Validated by the eval it came from (run at-b0442a6f5cd7, Sonnet 5)

run 5 (before) run 6 (after)
posix pass rate 5/12 10/12 — equal to rich
rich pass rate 10/12 10/12
wrong-project failures 2 rich + 6 posix zero, both surfaces

The entire wrong-scope failure class vanished. curate-connect — which agents had "solved" into the wrong project on every prior run — passed on both surfaces. Posix's metadata-search went 0/3 → 3/3 (agents brute-force it at ~2× rich's token cost, which is the remaining efficiency gap: grep/ls/find have no structured frontmatter predicates). Where the surfaces overlap cleanly, posix stays 17–24% cheaper (manual 23.8k vs 31.3k; curate 74.7k vs 89.9k). Aggregate tokens/completed: rich 72.9k, posix 88.0k (+21%, driven by the metadata brute-forcing and navigational chattiness — 13.8 vs 7.8 mean calls).

Remaining failures are genuine task behavior (decoy discrimination on both surfaces, one formatting miss, one incomplete status update, one token-budget stop) — none are scope errors.

Verification

  • ruff / format / ty — clean; tests/mcp 1030 passed; tests/cli 933 passed (21 new resolver tests + 16 routing tests; additive-only test diffs)
  • Live A/B run above; full artifacts with provenance in the run dir

🤖 Generated with Claude Code

https://claude.ai/code/session_014pmKq6bqCi6Zp6BTHuZjrp

Projects become mount points (#1415): every posix verb accepts
<project>/path — exactly the prefixed identifiers tool outputs and
stored permalinks produce — resolved by one shared helper the CLI
inherits. Explicit project params win only on agreement; disagreement
refuses naming both. ls with no project lists active projects as the
root directory. In multi-project configs an unqualified path that
matches no project refuses with the copyable project list; single
project configs keep resolving unqualified paths unchanged.

Motivated by measured agent behavior in the #1398 A/B runs: agents
faithfully quote prefixed output identifiers, omit the project arg
(shell affordances prime cwd thinking), and the stateless default was
silently wrong — both surfaces wrote a perfect relation into the wrong
project. Known scope gap for cloud/factory-mode surfaces noted in
review; follow-up tracked on #1415.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014pmKq6bqCi6Zp6BTHuZjrp
Signed-off-by: phernandez <paul@basicmachines.co>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 31, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-31T22:23:12.696760Z bfd26ba PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bfd26bafa9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1158 to +1160
# Single-project ergonomics unchanged; an empty config (cloud-only client)
# keeps API-side default resolution.
return ProjectPathRoute(project=None, path=path, stripped=False)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Discover cloud projects before default routing

In factory/global-cloud sessions with no local config.projects, this fallback handles both single-segment mount paths and pathless grep/tail calls. Consequently, ls("/") can advertise /research, but ls("/research") cannot recognize that remote project and instead routes through get_project_client(None) to the cloud default; unqualified searches likewise silently query the default even when several cloud projects exist. Discover/count the remotely accessible projects before this fallback so advertised mount roots round-trip and multi-project calls cannot read the wrong project.

AGENTS.md reference: AGENTS.md:L303-L312

Useful? React with 👍 / 👎.

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

Labels

None yet

1 participant