Skip to content

Commit 7eb5da6

Browse files
Remove dual-mode tools and mode from MCP agent surface (#1143, #1148)
Split case.partition.finite into separate producer and checker IDs (case.partition.finite.verify), ban dual-mode descriptors at install time, and retire agent-facing mode from the MCP boundary. Product surface is now math.find (discover/inspect) and math.run (execute one tool -> value or verdict). Checkers are separate catalog IDs, not a mode switch on producers. Tool identity owns role; client mode is ignored and the sole advertised mode is stamped at dispatch. - Forbid len(modes) > 1 on CapabilityDescriptor registration - Split finite partition: producer materializes cases, checker replays - Default CapabilityRequest.mode to None (sole-mode inference at dispatch) - Remove mode param from capability_describe and capability_invoke - Remove --allow-mode/--deny-mode CLI flags - Remove mode from discovery request/result, projections, telemetry - Math-first text projection: output first, demote empty semantic fields - Rewrite guidance: search/execute, producers vs checkers, no mode workflow Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent e443138 commit 7eb5da6

23 files changed

Lines changed: 585 additions & 521 deletions

src/jacobian/adapters/mcp/cli.py

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77

88
from jacobian import __version__
99

10-
_CAPABILITY_MODES = ("EXPLORE", "VERIFY")
11-
1210

1311
def _parser() -> argparse.ArgumentParser:
1412
parser = argparse.ArgumentParser(
@@ -98,20 +96,6 @@ def _parser() -> argparse.ArgumentParser:
9896
default=[],
9997
help=help_text,
10098
)
101-
parser.add_argument(
102-
"--allow-mode",
103-
action="append",
104-
default=[],
105-
choices=_CAPABILITY_MODES,
106-
help="allow only this capability mode; repeatable",
107-
)
108-
parser.add_argument(
109-
"--deny-mode",
110-
action="append",
111-
default=[],
112-
choices=_CAPABILITY_MODES,
113-
help="deny capabilities with this mode; repeatable",
114-
)
11599
parser.add_argument(
116100
"--max-tenant-runtimes",
117101
type=int,
@@ -145,7 +129,6 @@ def main() -> None:
145129

146130
from jacobian.adapters.mcp.server import create_server
147131
from jacobian.capability_service import CapabilityPolicy
148-
from jacobian.contracts.capabilities import CapabilityMode
149132

150133
capability_policy = CapabilityPolicy(
151134
profile=args.capability_policy_profile,
@@ -155,8 +138,6 @@ def main() -> None:
155138
denied_domains=frozenset(args.denied_domains),
156139
allowed_tags=frozenset(args.allowed_tags),
157140
denied_tags=frozenset(args.denied_tags),
158-
allowed_modes=frozenset(CapabilityMode(value) for value in args.allow_mode),
159-
denied_modes=frozenset(CapabilityMode(value) for value in args.deny_mode),
160141
)
161142
if args.transport == "stdio":
162143
if (

src/jacobian/adapters/mcp/guidance.py

Lines changed: 35 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -32,66 +32,37 @@
3232
)
3333

3434
MATH_FIND_DESCRIPTION = """\
35-
Use this when a task may benefit from a specialized exact mathematical operation, even
36-
if shell code could also calculate the answer. Relevant outcomes include matrix
37-
determinants, polynomial or symbolic computation, structural analysis, examples or
38-
counterexamples, bounded search, formal-environment inspection, and requested
39-
independent verification. Search Jacobian by desired local mathematical outcome, or
40-
inspect one exact operation contract. A capability ID is not required for search or
41-
browse.
42-
43-
Available forms:
44-
- Pass `query` as a plain-language description of the desired local mathematical
45-
outcome. The response contains compact operation cards with accepted inputs, output
46-
summary, availability, scope, assurance ceiling, factual relationships, and one
47-
size-bounded validated invocation example when available.
48-
- Optionally filter with `domain` and `mode`. `limit` is between 1 and 20 and defaults
49-
to 5; a smaller requested limit returns less model context.
50-
- Omit all arguments to browse a compact installed catalog.
51-
- When `next_cursor` is present, pass it back with the same filters and limit to
52-
continue without loading the complete catalog.
53-
- Ranking is deterministic retrieval over published IDs, titles, descriptions, and
54-
tags. Match fields and terms are returned; candidates are not recommendations.
55-
- Pass `capability_id` to inspect the exact operation. SUMMARY is compact, CONTRACT
56-
adds the validation-equivalent input schema and validated invocation examples, and
57-
FULL adds complete provider and audit metadata.
58-
59-
Weak or empty results do not imply impossibility. They include unranked recovery paths
60-
for query reformulation, filter removal, browsing, and catalog inspection. Every exact
61-
response states the operation's scope rule.
35+
Search or inspect installed math tools by desired outcome or exact ID. Use when a
36+
task may benefit from exact computation, search, structural analysis, or a separate
37+
checker tool—even if shell code could also calculate the answer.
38+
39+
Forms:
40+
- `query`: plain-language mathematical outcome (compact tool cards).
41+
- Optional `domain` filter; `limit` 1–20 (default 5).
42+
- Omit arguments to browse; follow `next_cursor` with the same filters to continue.
43+
- Ranking is deterministic lexical retrieval; matches are not recommendations.
44+
- `capability_id`: exact inspect (SUMMARY / CONTRACT / FULL views).
45+
46+
Checker tools are separate IDs (often `*.verify`), not a mode switch on producers.
6247
6348
Examples:
64-
- `{"query":"compute an exact matrix determinant","domain":"matrix","mode":"EXPLORE","limit":3}`
65-
- `{"query":"find a counterexample to associativity","domain":"universal_algebra","mode":"EXPLORE","limit":3}`
66-
- `{"query":"eliminate this denominator using the defining relations"}`
67-
- `{}`
68-
- `{"capability_id":"polynomial.compute.gcd"}`
49+
- `{"query":"compute an exact matrix determinant","domain":"matrix","limit":3}`
50+
- `{"query":"find a counterexample to associativity","domain":"universal_algebra"}`
6951
- `{"capability_id":"polynomial.compute.gcd","view":"CONTRACT"}`
7052
"""
7153

7254
MATH_RUN_DESCRIPTION = """\
73-
Use this to run one selected Jacobian operation with its typed payload. If the payload
74-
shape is unfamiliar, math.find can return the exact CONTRACT. EXPLORE returns proposed,
75-
heuristic, or computed evidence; VERIFY is valid only for an installed checker-backed
76-
contract.
77-
78-
The typed `CapabilityResult` keeps execution, scope, completeness, mathematical
79-
conclusion, assurance, obligations, diagnostics, relationships, and artifacts distinct.
55+
Run one installed math tool by ID with its typed `payload`. Read the mathematical
56+
value in `output` first, then execution status. If the payload shape is unknown,
57+
use math.find with view CONTRACT.
8058
81-
COMPLETED does not by itself establish a mathematical conclusion. One invocation
82-
covers only its exact supplied input or claim, and repeated finite or bounded calls do
83-
not widen that scope. Follow returned `artifact://` references when durable evidence
84-
or a size-separated result is provided.
85-
86-
A verification record is claim-bound. Verification of an input, premise,
87-
factorization, or related artifact does not promote a conclusion derived by the model
88-
to `VERIFIED`; the checker record must bind the exact final claim.
59+
Ordinary tools return calculations. Independent checking uses a separate checker
60+
tool ID (for example `polynomial.identity.verify` or `case.partition.finite.verify`),
61+
not a mode on the producer. Failed or incomplete runs are not mathematical conclusions.
8962
9063
Examples:
91-
- `{"capability_id":"integer.compute.gcd","mode":"EXPLORE","payload":{"left":"84","right":"30"}}`
92-
- `{"capability_id":"polynomial.identity.verify","mode":"VERIFY","payload":{"variables":["x"],"left":{"terms":[]},"right":{"terms":[]}}}`
93-
94-
These are valid envelopes, not a required research strategy.
64+
- `{"capability_id":"integer.compute.gcd","payload":{"left":"84","right":"30"}}`
65+
- `{"capability_id":"polynomial.identity.verify","payload":{"variables":["x"],"left":{"terms":[]},"right":{"terms":[]}}}`
9566
"""
9667

9768
OPERATING_GUIDE = """\
@@ -113,11 +84,11 @@
11384
11485
## Search, browse, inspect, and run
11586
116-
Search with `math.find(query=...)`, optionally filtered by `domain` and
117-
`mode`. Results are compact candidates ranked by deterministic matches against
118-
published descriptor metadata; `matched_on` and `matched_terms` make that retrieval
119-
visible. Ranking is not a recommendation. Follow `next_cursor` with unchanged filters
120-
and limit when a discovery result is truncated. Omit all arguments to browse.
87+
Search with `math.find(query=...)`, optionally filtered by `domain`. Results are
88+
compact candidates ranked by deterministic matches against published descriptor
89+
metadata; `matched_on` and `matched_terms` make that retrieval visible. Ranking is
90+
not a recommendation. Follow `next_cursor` with unchanged filters and limit when a
91+
discovery result is truncated. Omit all arguments to browse.
12192
12293
The same tool accepts `capability_id` for exact inspection. SUMMARY is the compact
12394
projection, CONTRACT adds the validation-equivalent input schema and examples, and
@@ -137,15 +108,15 @@
137108
and catalog-inspection paths. They do not establish operation absence or mathematical
138109
impossibility.
139110
140-
## Exploration and verification
111+
## Producers and checkers
141112
142-
`EXPLORE` returns proposed, heuristic, or computed evidence. Search, generation,
143-
evaluation, solver output, and retrieved memory are not proof.
113+
Ordinary producer tools return proposed, heuristic, or computed evidence. Search,
114+
generation, evaluation, solver output, and retrieved memory are not proof.
144115
145-
`VERIFY` may return `VERIFIED` only when an operator-authorized independent checker
146-
accepts evidence bound to the exact claim, semantics, candidate, scope, certificate
147-
format, and checker identity. Only assurance level `VERIFIED` with a local
148-
verification record is verified.
116+
A separate checker tool (often a `*.verify` ID) may return `VERIFIED` only when an
117+
operator-authorized independent checker accepts evidence bound to the exact claim,
118+
semantics, candidate, scope, certificate format, and checker identity. Only assurance
119+
level `VERIFIED` with a local verification record is verified.
149120
150121
Verification does not transfer across model-authored deductions. A record accepting
151122
premises, inputs, factorizations, or related artifacts does not verify a derived
@@ -218,7 +189,7 @@ def evidence_check_prompt(claim: str, artifact_uri: str | None = None) -> str:
218189
{claim}
219190
</claim>
220191
{artifact_context}
221-
1. Search with `math.find(query=..., mode="VERIFY")`.
192+
1. Search with `math.find(query=...)` for a checker tool (often a `*.verify` ID).
222193
2. Treat an empty result as checker unavailability, not evidence for or against the
223194
claim.
224195
3. Describe the selected exact capability. Confirm that its semantics, scope,

src/jacobian/adapters/mcp/projections.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,6 @@ def _discovery_operation_card(
200200
if descriptor.invocation_examples:
201201
example = descriptor.invocation_examples[0]
202202
candidate = {
203-
"mode": example.mode.value,
204203
"payload": example.input,
205204
}
206205
if (
@@ -301,7 +300,6 @@ def _discovery_recovery_paths(
301300
value is not None
302301
for value in (
303302
request.domain,
304-
request.mode,
305303
request.input_kind,
306304
request.artifact_type,
307305
)
@@ -347,7 +345,6 @@ def _capability_descriptor_view(
347345
"description": descriptor.description,
348346
"provider": descriptor.provider,
349347
"provider_runtime": runtime_summary,
350-
"modes": [mode.value for mode in descriptor.modes],
351348
"tags": list(descriptor.tags),
352349
"accepted_input_kinds": [
353350
kind.value for kind in descriptor.accepted_input_kinds
@@ -380,7 +377,6 @@ def _capability_descriptor_view(
380377
"description": descriptor.description,
381378
"provider": descriptor.provider,
382379
"provider_runtime": runtime_summary,
383-
"modes": [mode.value for mode in descriptor.modes],
384380
"accepted_input_kinds": [
385381
kind.value for kind in descriptor.accepted_input_kinds
386382
],
@@ -409,7 +405,6 @@ def _capability_discovery_response(
409405
*,
410406
query: str | None,
411407
domain: str | None,
412-
mode: CapabilityMode | None,
413408
input_kind: CapabilityInputKind | None,
414409
artifact_type: str | None,
415410
limit: int | None,
@@ -419,7 +414,6 @@ def _capability_discovery_response(
419414
discovery_request = CapabilityDiscoveryRequest(
420415
query=query,
421416
domain=domain,
422-
mode=mode,
423417
input_kind=input_kind,
424418
artifact_type=artifact_type,
425419
limit=limit if limit is not None else 5,
@@ -435,7 +429,7 @@ def _capability_discovery_response(
435429
"message": "The capability discovery cursor is not in this result set.",
436430
"hint": (
437431
"Restart discovery without a cursor, or reuse the same query, "
438-
"domain, mode, input_kind, artifact_type, and limit that produced "
432+
"domain, input_kind, artifact_type, and limit that produced "
439433
"next_cursor."
440434
),
441435
}

src/jacobian/adapters/mcp/tooling.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -412,38 +412,44 @@ async def _invoke_capability_attempt(
412412
*,
413413
capability_id: str,
414414
payload: dict[str, Any],
415-
mode: CapabilityMode,
415+
mode: CapabilityMode | None = None,
416416
ctx: Any | None,
417417
) -> CapabilityResult:
418418
started = time.monotonic()
419419
argument_digest = _argument_digest(
420420
{
421421
"capability_id": capability_id,
422-
"mode": mode.value,
423422
"payload": payload,
424423
}
425424
)
426425
trace_digest, trace_source = _request_trace_digest(ctx)
427426
request_digest = _request_id_digest(ctx)
428427
cancellation_event = threading.Event()
428+
# mode is stamped from the tool descriptor at dispatch; clients do not choose it.
429+
request = CapabilityRequest(
430+
capability_id=capability_id,
431+
mode=mode,
432+
input=payload,
433+
)
429434
try:
430435
result = await _run_blocking(
431436
_invoke_capability_with_cancellation,
432437
runtime,
433-
CapabilityRequest(
434-
capability_id=capability_id,
435-
mode=mode,
436-
input=payload,
437-
),
438+
request,
438439
cancellation_event,
439440
on_cancel=cancellation_event.set,
440441
)
441442
except asyncio.CancelledError as exc:
442443
drained = getattr(exc, "drained_result", None)
444+
log_mode = (
445+
drained.mode
446+
if isinstance(drained, CapabilityResult)
447+
else CapabilityMode.EXPLORE
448+
)
443449
if isinstance(drained, CapabilityResult):
444450
_log_capability_attempt(
445451
capability_id=capability_id,
446-
mode=mode,
452+
mode=log_mode,
447453
started=started,
448454
argument_digest=argument_digest,
449455
request_digest=request_digest,
@@ -454,7 +460,7 @@ async def _invoke_capability_attempt(
454460
else:
455461
_log_capability_attempt(
456462
capability_id=capability_id,
457-
mode=mode,
463+
mode=log_mode,
458464
started=started,
459465
argument_digest=argument_digest,
460466
request_digest=request_digest,
@@ -467,7 +473,7 @@ async def _invoke_capability_attempt(
467473
except Exception:
468474
_log_capability_attempt(
469475
capability_id=capability_id,
470-
mode=mode,
476+
mode=CapabilityMode.EXPLORE,
471477
started=started,
472478
argument_digest=argument_digest,
473479
request_digest=request_digest,
@@ -479,7 +485,7 @@ async def _invoke_capability_attempt(
479485
raise
480486
_log_capability_attempt(
481487
capability_id=capability_id,
482-
mode=mode,
488+
mode=result.mode,
483489
started=started,
484490
argument_digest=argument_digest,
485491
request_digest=request_digest,

0 commit comments

Comments
 (0)