asm-protocol 0.5.2__tar.gz → 0.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. {asm_protocol-0.5.2 → asm_protocol-0.6.0}/PKG-INFO +38 -10
  2. {asm_protocol-0.5.2 → asm_protocol-0.6.0}/README.md +37 -9
  3. {asm_protocol-0.5.2 → asm_protocol-0.6.0}/asm_cli.py +14 -3
  4. {asm_protocol-0.5.2 → asm_protocol-0.6.0}/asm_protocol.egg-info/PKG-INFO +38 -10
  5. {asm_protocol-0.5.2 → asm_protocol-0.6.0}/asm_protocol.egg-info/SOURCES.txt +11 -1
  6. {asm_protocol-0.5.2 → asm_protocol-0.6.0}/asm_protocol.egg-info/top_level.txt +1 -0
  7. {asm_protocol-0.5.2 → asm_protocol-0.6.0}/asm_select_api.py +25 -15
  8. {asm_protocol-0.5.2 → asm_protocol-0.6.0}/asm_selector_mcp.py +29 -14
  9. asm_protocol-0.6.0/library_select.py +11 -0
  10. {asm_protocol-0.5.2 → asm_protocol-0.6.0}/pyproject.toml +13 -2
  11. asm_protocol-0.6.0/schema/selection-receipt-v0.1.schema.json +182 -0
  12. {asm_protocol-0.5.2 → asm_protocol-0.6.0}/scorer/__init__.py +1 -1
  13. asm_protocol-0.6.0/scorer/data/__init__.py +1 -0
  14. {asm_protocol-0.5.2 → asm_protocol-0.6.0}/scorer/scorer.py +10 -2
  15. {asm_protocol-0.5.2 → asm_protocol-0.6.0}/scorer/test_asm_lint.py +14 -0
  16. {asm_protocol-0.5.2 → asm_protocol-0.6.0}/scorer/test_asm_selector_mcp.py +1 -0
  17. asm_protocol-0.6.0/scorer/test_cost_estimation.py +118 -0
  18. asm_protocol-0.6.0/scorer/test_langchain_adapter.py +114 -0
  19. {asm_protocol-0.5.2 → asm_protocol-0.6.0}/scorer/test_library_select.py +106 -11
  20. asm_protocol-0.6.0/scorer/test_version_contract.py +35 -0
  21. asm_protocol-0.6.0/src/asm_protocol/__init__.py +15 -0
  22. asm_protocol-0.6.0/src/asm_protocol/cost.py +202 -0
  23. asm_protocol-0.6.0/src/asm_protocol/integrations/__init__.py +2 -0
  24. asm_protocol-0.6.0/src/asm_protocol/integrations/langchain.py +258 -0
  25. asm_protocol-0.6.0/src/asm_protocol/selection.py +487 -0
  26. asm_protocol-0.6.0/src/asm_protocol/version.py +7 -0
  27. asm_protocol-0.5.2/library_select.py +0 -218
  28. asm_protocol-0.5.2/scorer/test_langchain_adapter.py +0 -54
  29. {asm_protocol-0.5.2 → asm_protocol-0.6.0}/LICENSE +0 -0
  30. {asm_protocol-0.5.2 → asm_protocol-0.6.0}/_asm_library_data.py +0 -0
  31. {asm_protocol-0.5.2 → asm_protocol-0.6.0}/asm_lint.py +0 -0
  32. {asm_protocol-0.5.2 → asm_protocol-0.6.0}/asm_protocol.egg-info/dependency_links.txt +0 -0
  33. {asm_protocol-0.5.2 → asm_protocol-0.6.0}/asm_protocol.egg-info/entry_points.txt +0 -0
  34. {asm_protocol-0.5.2 → asm_protocol-0.6.0}/asm_protocol.egg-info/requires.txt +0 -0
  35. {asm_protocol-0.5.2 → asm_protocol-0.6.0}/mcp_server_json_asm.py +0 -0
  36. {asm_protocol-0.5.2 → asm_protocol-0.6.0}/openrouter_adapter.py +0 -0
  37. {asm_protocol-0.5.2 → asm_protocol-0.6.0}/schema/__init__.py +0 -0
  38. {asm_protocol-0.5.2 → asm_protocol-0.6.0}/schema/asm-receipt-envelope-v0.1.schema.json +0 -0
  39. {asm_protocol-0.5.2 → asm_protocol-0.6.0}/schema/asm-v0.2.schema.json +0 -0
  40. {asm_protocol-0.5.2 → asm_protocol-0.6.0}/schema/asm-v0.3.schema.json +0 -0
  41. {asm_protocol-0.5.2 → asm_protocol-0.6.0}/scorer/data/elo_snapshot.json +0 -0
  42. {asm_protocol-0.5.2 → asm_protocol-0.6.0}/scorer/test_data_quality_audit.py +0 -0
  43. {asm_protocol-0.5.2 → asm_protocol-0.6.0}/scorer/test_manifests_schema.py +0 -0
  44. {asm_protocol-0.5.2 → asm_protocol-0.6.0}/scorer/test_mcp_server_json_asm.py +0 -0
  45. {asm_protocol-0.5.2 → asm_protocol-0.6.0}/scorer/test_openrouter_adapter.py +0 -0
  46. {asm_protocol-0.5.2 → asm_protocol-0.6.0}/scorer/test_scorer.py +0 -0
  47. {asm_protocol-0.5.2 → asm_protocol-0.6.0}/scorer/test_x402_bridge.py +0 -0
  48. {asm_protocol-0.5.2 → asm_protocol-0.6.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: asm-protocol
3
- Version: 0.5.2
3
+ Version: 0.6.0
4
4
  Summary: Agent Service Manifest: value metadata and scoring for autonomous service selection
5
5
  Author: Yi Guo
6
6
  License-Expression: MIT
@@ -41,7 +41,7 @@ It accepts either a standalone ASM manifest or an MCP Registry `server.json`
41
41
  with publisher-provided ASM metadata:
42
42
 
43
43
  ```bash
44
- python -m pip install "asm-protocol==0.5.2"
44
+ python -m pip install "asm-protocol==0.6.0"
45
45
  asm-lint server.json --format markdown --output asm-lint-report.md
46
46
  ```
47
47
 
@@ -58,7 +58,7 @@ steps:
58
58
  - uses: actions/setup-python@v5
59
59
  with:
60
60
  python-version: "3.12"
61
- - uses: YE-YI7/asm-spec/.github/actions/asm-lint@v0.5.2
61
+ - uses: YE-YI7/asm-spec/.github/actions/asm-lint@v0.6.0
62
62
  with:
63
63
  path: server.json
64
64
  fail-on: invalid
@@ -67,6 +67,8 @@ steps:
67
67
  The Action adds the full Markdown report to the job summary. It does not call
68
68
  an ASM-hosted API or upload the inspected manifest. See the
69
69
  [lint and CI guide](docs/adoption/asm-lint.md) for status semantics.
70
+ For a bounded producer-side change, use the
71
+ [10-minute adoption package](docs/adoption/ten-minute.md).
70
72
 
71
73
  ## Try it: pick a tool for a task
72
74
 
@@ -75,7 +77,13 @@ git clone https://github.com/YE-YI7/asm-spec.git && cd asm-spec
75
77
  python library/select_demo.py
76
78
  ```
77
79
 
78
- For *"make a study plan and remind me daily"* with a cloud agent on Windows, the selector drops the tools it can't drive (Apple Reminders, Things 3 — local-device only) and the ones it can't call directly (Any.do — Zapier only), then ranks the rest. Ask for a built-in pomodoro and the pick changes to TickTick. Ask to *"edit an image and lay out a poster"* and it picks free, scriptable **Photopea** over paid Photoshop — and filters **Affinity Designer**, which exposes no automation API at all.
80
+ The deterministic core does **not** pretend to understand the task sentence. The
81
+ caller supplies structured facts such as `taxonomy`, `required_functions`,
82
+ platform, reach, and expected workload; `task` remains audit/display text. If
83
+ taxonomy and required functions are both absent, selection returns
84
+ `under_specified` instead of choosing an unrelated globally cheap tool.
85
+
86
+ For *"make a study plan and remind me daily"* with a cloud agent on Windows, the selector drops the tools it can't drive (Apple Reminders, Things 3 — local-device only) and the ones it can't call directly (Any.do — Zapier only), then ranks the rest from those explicit constraints. Ask for a built-in pomodoro and the pick changes to TickTick. Ask to *"edit an image and lay out a poster"* and it filters **Affinity Designer**, which exposes no automation API at all.
79
87
 
80
88
  The library it selects over is in [`library/`](library/) — 30 real tools across task management, creative design, research, communication, developer tools, booking, and real-estate data today, each carrying:
81
89
 
@@ -105,31 +113,50 @@ ASM is MCP-compatible: publish a standalone `.well-known/asm`, or embed ASM in M
105
113
  ASM ships an MCP server so any MCP client (Claude Desktop, Cursor, an agent host) can call the selector as a tool — no schema adoption required:
106
114
 
107
115
  ```bash
108
- python3 -m pip install "asm-protocol[mcp]==0.5.2"
116
+ python3 -m pip install "asm-protocol[mcp]==0.6.0"
109
117
  asm-selector # stdio MCP server (MCP SDK 2.x)
110
118
  ```
111
119
 
112
120
  It exposes three tools: **`select_tool`** (pick a tool for a task and return its risk/approval policy), **`list_library_tools`**, and **`get_tool_manifest`**. Point your client's MCP config at `asm-selector`, or at `python3 /path/to/asm-spec/asm_selector_mcp.py`; the selector reads `library/` (override with `ASM_LIBRARY_DIR`). The Python server uses the stable MCP SDK 2.x line and supports the modern `2026-07-28` protocol era. The same selector is importable directly: `from library_select import select`.
113
121
 
122
+ Cost output has an explicit `known`, `partial`, or `unknown` status. Metered
123
+ prices need expected monthly usage; one-time licenses need an amortization
124
+ period; prose-only free tiers remain unknown because their allowance and reset
125
+ rules are not machine-readable. If every eligible candidate does not have a
126
+ known cost in the same currency, the selector returns `needs_cost_facts` rather
127
+ than guessing. A caller may explicitly request the `capability_breadth` fallback;
128
+ it is never applied implicitly.
129
+
114
130
  The same engine is also available as:
115
131
 
116
132
  ```bash
117
133
  # CLI (human or scripted)
118
134
  asm select "find and book a refundable flight" --taxonomy tool.booking.travel \
119
- --requires flight_search,flight_order_create --json
135
+ --requires flight_search,flight_order_create --fallback-policy capability_breadth --json
120
136
 
121
137
  # Hosted HTTP API (stdlib-only; deploy anywhere that runs Python)
122
138
  python asm_select_api.py # POST /select, GET /tools, GET /healthz on :8787
123
139
  ```
124
140
 
125
- LangChain / LangGraph builders get the same selector as a drop-in tool (`pip install langchain-core`):
141
+ DeepSeek Harness developer-preview users can install the native
142
+ [`asm_select` tool adapter](integrations/deepseek-harness/README.md). It uses
143
+ the same HTTP contract and returns the current structured decision, but never
144
+ invokes or authorizes the selected service. The frozen v0.1 receipt is available
145
+ only through an explicit legacy compatibility profile. The adapter defaults to
146
+ a local selector so task text is not sent to a hosted endpoint implicitly.
147
+
148
+ LangChain / LangGraph builders get the same selector as a packaged drop-in tool:
126
149
 
127
150
  ```python
128
- import sys; sys.path += ["asm-spec", "asm-spec/integrations/langchain"]
129
- from asm_tools import ASMToolSelectorTool
151
+ from asm_protocol.integrations.langchain import ASMToolSelectorTool
130
152
  agent_tools = [ASMToolSelectorTool()] # name: asm_tool_selector
131
153
  ```
132
154
 
155
+ Install it with `python -m pip install "asm-protocol[langchain]==0.6.0"`. LangChain
156
+ hosts receive the full structured decision in `ToolMessage.artifact`; the
157
+ model-facing content remains a short summary. The adapter never executes or
158
+ authorizes the selected service.
159
+
133
160
  A public reference instance runs at **https://asm-spec.onrender.com**. It also dogfoods ASM's own publishing convention: `GET /.well-known/asm` serves the library catalog (one re-stampable `generated_at`, per-manifest links), and `GET /manifest/{service_id}` serves each full manifest — ASM is its own first publisher.
134
161
 
135
162
  ```bash
@@ -374,9 +401,10 @@ Schema: [`schema/asm-v0.3.schema.json`](schema/asm-v0.3.schema.json).
374
401
 
375
402
  ```text
376
403
  schema/ ASM JSON Schema
404
+ src/asm_protocol/ Canonical Python SDK: selection, cost, version
377
405
  library/ Tool-value library (agent tool selection) + select_demo.py
378
406
  manifests/ 75 source-linked manifests
379
- scorer/ Python TOPSIS scorer and tests
407
+ scorer/ Legacy experimental per-unit TOPSIS scorer and tests
380
408
  registry/ MCP registry server exposing ASM tools
381
409
  examples/mcp-server-json/ MCP Registry server.json examples
382
410
  docs/integrations/ MCP Registry and aggregator integration docs
@@ -25,7 +25,7 @@ It accepts either a standalone ASM manifest or an MCP Registry `server.json`
25
25
  with publisher-provided ASM metadata:
26
26
 
27
27
  ```bash
28
- python -m pip install "asm-protocol==0.5.2"
28
+ python -m pip install "asm-protocol==0.6.0"
29
29
  asm-lint server.json --format markdown --output asm-lint-report.md
30
30
  ```
31
31
 
@@ -42,7 +42,7 @@ steps:
42
42
  - uses: actions/setup-python@v5
43
43
  with:
44
44
  python-version: "3.12"
45
- - uses: YE-YI7/asm-spec/.github/actions/asm-lint@v0.5.2
45
+ - uses: YE-YI7/asm-spec/.github/actions/asm-lint@v0.6.0
46
46
  with:
47
47
  path: server.json
48
48
  fail-on: invalid
@@ -51,6 +51,8 @@ steps:
51
51
  The Action adds the full Markdown report to the job summary. It does not call
52
52
  an ASM-hosted API or upload the inspected manifest. See the
53
53
  [lint and CI guide](docs/adoption/asm-lint.md) for status semantics.
54
+ For a bounded producer-side change, use the
55
+ [10-minute adoption package](docs/adoption/ten-minute.md).
54
56
 
55
57
  ## Try it: pick a tool for a task
56
58
 
@@ -59,7 +61,13 @@ git clone https://github.com/YE-YI7/asm-spec.git && cd asm-spec
59
61
  python library/select_demo.py
60
62
  ```
61
63
 
62
- For *"make a study plan and remind me daily"* with a cloud agent on Windows, the selector drops the tools it can't drive (Apple Reminders, Things 3 — local-device only) and the ones it can't call directly (Any.do — Zapier only), then ranks the rest. Ask for a built-in pomodoro and the pick changes to TickTick. Ask to *"edit an image and lay out a poster"* and it picks free, scriptable **Photopea** over paid Photoshop — and filters **Affinity Designer**, which exposes no automation API at all.
64
+ The deterministic core does **not** pretend to understand the task sentence. The
65
+ caller supplies structured facts such as `taxonomy`, `required_functions`,
66
+ platform, reach, and expected workload; `task` remains audit/display text. If
67
+ taxonomy and required functions are both absent, selection returns
68
+ `under_specified` instead of choosing an unrelated globally cheap tool.
69
+
70
+ For *"make a study plan and remind me daily"* with a cloud agent on Windows, the selector drops the tools it can't drive (Apple Reminders, Things 3 — local-device only) and the ones it can't call directly (Any.do — Zapier only), then ranks the rest from those explicit constraints. Ask for a built-in pomodoro and the pick changes to TickTick. Ask to *"edit an image and lay out a poster"* and it filters **Affinity Designer**, which exposes no automation API at all.
63
71
 
64
72
  The library it selects over is in [`library/`](library/) — 30 real tools across task management, creative design, research, communication, developer tools, booking, and real-estate data today, each carrying:
65
73
 
@@ -89,31 +97,50 @@ ASM is MCP-compatible: publish a standalone `.well-known/asm`, or embed ASM in M
89
97
  ASM ships an MCP server so any MCP client (Claude Desktop, Cursor, an agent host) can call the selector as a tool — no schema adoption required:
90
98
 
91
99
  ```bash
92
- python3 -m pip install "asm-protocol[mcp]==0.5.2"
100
+ python3 -m pip install "asm-protocol[mcp]==0.6.0"
93
101
  asm-selector # stdio MCP server (MCP SDK 2.x)
94
102
  ```
95
103
 
96
104
  It exposes three tools: **`select_tool`** (pick a tool for a task and return its risk/approval policy), **`list_library_tools`**, and **`get_tool_manifest`**. Point your client's MCP config at `asm-selector`, or at `python3 /path/to/asm-spec/asm_selector_mcp.py`; the selector reads `library/` (override with `ASM_LIBRARY_DIR`). The Python server uses the stable MCP SDK 2.x line and supports the modern `2026-07-28` protocol era. The same selector is importable directly: `from library_select import select`.
97
105
 
106
+ Cost output has an explicit `known`, `partial`, or `unknown` status. Metered
107
+ prices need expected monthly usage; one-time licenses need an amortization
108
+ period; prose-only free tiers remain unknown because their allowance and reset
109
+ rules are not machine-readable. If every eligible candidate does not have a
110
+ known cost in the same currency, the selector returns `needs_cost_facts` rather
111
+ than guessing. A caller may explicitly request the `capability_breadth` fallback;
112
+ it is never applied implicitly.
113
+
98
114
  The same engine is also available as:
99
115
 
100
116
  ```bash
101
117
  # CLI (human or scripted)
102
118
  asm select "find and book a refundable flight" --taxonomy tool.booking.travel \
103
- --requires flight_search,flight_order_create --json
119
+ --requires flight_search,flight_order_create --fallback-policy capability_breadth --json
104
120
 
105
121
  # Hosted HTTP API (stdlib-only; deploy anywhere that runs Python)
106
122
  python asm_select_api.py # POST /select, GET /tools, GET /healthz on :8787
107
123
  ```
108
124
 
109
- LangChain / LangGraph builders get the same selector as a drop-in tool (`pip install langchain-core`):
125
+ DeepSeek Harness developer-preview users can install the native
126
+ [`asm_select` tool adapter](integrations/deepseek-harness/README.md). It uses
127
+ the same HTTP contract and returns the current structured decision, but never
128
+ invokes or authorizes the selected service. The frozen v0.1 receipt is available
129
+ only through an explicit legacy compatibility profile. The adapter defaults to
130
+ a local selector so task text is not sent to a hosted endpoint implicitly.
131
+
132
+ LangChain / LangGraph builders get the same selector as a packaged drop-in tool:
110
133
 
111
134
  ```python
112
- import sys; sys.path += ["asm-spec", "asm-spec/integrations/langchain"]
113
- from asm_tools import ASMToolSelectorTool
135
+ from asm_protocol.integrations.langchain import ASMToolSelectorTool
114
136
  agent_tools = [ASMToolSelectorTool()] # name: asm_tool_selector
115
137
  ```
116
138
 
139
+ Install it with `python -m pip install "asm-protocol[langchain]==0.6.0"`. LangChain
140
+ hosts receive the full structured decision in `ToolMessage.artifact`; the
141
+ model-facing content remains a short summary. The adapter never executes or
142
+ authorizes the selected service.
143
+
117
144
  A public reference instance runs at **https://asm-spec.onrender.com**. It also dogfoods ASM's own publishing convention: `GET /.well-known/asm` serves the library catalog (one re-stampable `generated_at`, per-manifest links), and `GET /manifest/{service_id}` serves each full manifest — ASM is its own first publisher.
118
145
 
119
146
  ```bash
@@ -358,9 +385,10 @@ Schema: [`schema/asm-v0.3.schema.json`](schema/asm-v0.3.schema.json).
358
385
 
359
386
  ```text
360
387
  schema/ ASM JSON Schema
388
+ src/asm_protocol/ Canonical Python SDK: selection, cost, version
361
389
  library/ Tool-value library (agent tool selection) + select_demo.py
362
390
  manifests/ 75 source-linked manifests
363
- scorer/ Python TOPSIS scorer and tests
391
+ scorer/ Legacy experimental per-unit TOPSIS scorer and tests
364
392
  registry/ MCP registry server exposing ASM tools
365
393
  examples/mcp-server-json/ MCP Registry server.json examples
366
394
  docs/integrations/ MCP Registry and aggregator integration docs
@@ -515,6 +515,7 @@ def cmd_select(args: argparse.Namespace) -> int:
515
515
  required_functions=[f for f in (args.requires or "").split(",") if f],
516
516
  require_approval_for=[s for s in (args.approval_for or "").split(",") if s],
517
517
  require_agent_completable_setup=args.agent_setup_only,
518
+ fallback_policy=args.fallback_policy,
518
519
  )
519
520
  if args.json:
520
521
  print(json.dumps(result, indent=2, ensure_ascii=False))
@@ -522,18 +523,26 @@ def cmd_select(args: argparse.Namespace) -> int:
522
523
 
523
524
  sel = result["selected"]
524
525
  if not sel:
525
- print("No eligible tool.")
526
+ print(f"No selection ({result['selection_status']}): {result['reason']}")
527
+ for candidate in result["alternatives"][: args.limit]:
528
+ print(f" candidate: {candidate['display_name']} ({candidate['cost_estimate']['status']} cost)")
526
529
  for r in result["rejected"][: args.limit]:
527
530
  print(f" - {r['service']}: {r['reason']}")
528
531
  return 2
529
532
  print(f"Selected: {sel['display_name']} ({sel['service_id']})")
530
- print(f" cost=${sel['monthly_cost_usd']}/mo, interface={sel['interface']}, reach={sel['reach']}")
533
+ estimate = sel["cost_estimate"]
534
+ cost = (f"${sel['monthly_cost_usd']}/mo" if sel["monthly_cost_usd"] is not None
535
+ else f"{estimate['status']} (workload or allowance facts required)")
536
+ print(f" cost={cost}, interface={sel['interface']}, reach={sel['reach']}")
531
537
  if sel.get("agent_completable_setup") is not None:
532
538
  print(f" setup: agent_completable={sel['agent_completable_setup']}, requires={sel.get('setup_requires', [])}")
533
539
  print(f" risk={result['risk_class']}, approval_required={result['approval_required']}, side_effects={result['side_effects']}")
534
540
  print(f" reason: {result['reason']}")
535
541
  for alt in result["alternatives"][: args.limit]:
536
- print(f" alt: {alt['display_name']} (${alt['monthly_cost_usd']}/mo)")
542
+ alt_cost = (f"${alt['monthly_cost_usd']}/mo"
543
+ if alt["monthly_cost_usd"] is not None
544
+ else alt["cost_estimate"]["status"])
545
+ print(f" alt: {alt['display_name']} ({alt_cost})")
537
546
  if result["rejected"]:
538
547
  print(" filtered out:")
539
548
  for r in result["rejected"][: args.limit]:
@@ -556,6 +565,8 @@ def build_parser() -> argparse.ArgumentParser:
556
565
  help="Comma-separated side-effects that force approval, e.g. financial_charge,sends_message")
557
566
  sel.add_argument("--agent-setup-only", action="store_true",
558
567
  help="Drop tools needing human-in-the-loop setup (paid signup, OAuth consent, approval)")
568
+ sel.add_argument("--fallback-policy", choices=["capability_breadth"],
569
+ help="Explicit fallback when eligible candidate costs are not comparable")
559
570
  sel.add_argument("--json", action="store_true", help="Emit the structured decision as JSON")
560
571
  sel.add_argument("--limit", type=int, default=5, help="Maximum alternatives/rejections to print")
561
572
  sel.set_defaults(func=cmd_select)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: asm-protocol
3
- Version: 0.5.2
3
+ Version: 0.6.0
4
4
  Summary: Agent Service Manifest: value metadata and scoring for autonomous service selection
5
5
  Author: Yi Guo
6
6
  License-Expression: MIT
@@ -41,7 +41,7 @@ It accepts either a standalone ASM manifest or an MCP Registry `server.json`
41
41
  with publisher-provided ASM metadata:
42
42
 
43
43
  ```bash
44
- python -m pip install "asm-protocol==0.5.2"
44
+ python -m pip install "asm-protocol==0.6.0"
45
45
  asm-lint server.json --format markdown --output asm-lint-report.md
46
46
  ```
47
47
 
@@ -58,7 +58,7 @@ steps:
58
58
  - uses: actions/setup-python@v5
59
59
  with:
60
60
  python-version: "3.12"
61
- - uses: YE-YI7/asm-spec/.github/actions/asm-lint@v0.5.2
61
+ - uses: YE-YI7/asm-spec/.github/actions/asm-lint@v0.6.0
62
62
  with:
63
63
  path: server.json
64
64
  fail-on: invalid
@@ -67,6 +67,8 @@ steps:
67
67
  The Action adds the full Markdown report to the job summary. It does not call
68
68
  an ASM-hosted API or upload the inspected manifest. See the
69
69
  [lint and CI guide](docs/adoption/asm-lint.md) for status semantics.
70
+ For a bounded producer-side change, use the
71
+ [10-minute adoption package](docs/adoption/ten-minute.md).
70
72
 
71
73
  ## Try it: pick a tool for a task
72
74
 
@@ -75,7 +77,13 @@ git clone https://github.com/YE-YI7/asm-spec.git && cd asm-spec
75
77
  python library/select_demo.py
76
78
  ```
77
79
 
78
- For *"make a study plan and remind me daily"* with a cloud agent on Windows, the selector drops the tools it can't drive (Apple Reminders, Things 3 — local-device only) and the ones it can't call directly (Any.do — Zapier only), then ranks the rest. Ask for a built-in pomodoro and the pick changes to TickTick. Ask to *"edit an image and lay out a poster"* and it picks free, scriptable **Photopea** over paid Photoshop — and filters **Affinity Designer**, which exposes no automation API at all.
80
+ The deterministic core does **not** pretend to understand the task sentence. The
81
+ caller supplies structured facts such as `taxonomy`, `required_functions`,
82
+ platform, reach, and expected workload; `task` remains audit/display text. If
83
+ taxonomy and required functions are both absent, selection returns
84
+ `under_specified` instead of choosing an unrelated globally cheap tool.
85
+
86
+ For *"make a study plan and remind me daily"* with a cloud agent on Windows, the selector drops the tools it can't drive (Apple Reminders, Things 3 — local-device only) and the ones it can't call directly (Any.do — Zapier only), then ranks the rest from those explicit constraints. Ask for a built-in pomodoro and the pick changes to TickTick. Ask to *"edit an image and lay out a poster"* and it filters **Affinity Designer**, which exposes no automation API at all.
79
87
 
80
88
  The library it selects over is in [`library/`](library/) — 30 real tools across task management, creative design, research, communication, developer tools, booking, and real-estate data today, each carrying:
81
89
 
@@ -105,31 +113,50 @@ ASM is MCP-compatible: publish a standalone `.well-known/asm`, or embed ASM in M
105
113
  ASM ships an MCP server so any MCP client (Claude Desktop, Cursor, an agent host) can call the selector as a tool — no schema adoption required:
106
114
 
107
115
  ```bash
108
- python3 -m pip install "asm-protocol[mcp]==0.5.2"
116
+ python3 -m pip install "asm-protocol[mcp]==0.6.0"
109
117
  asm-selector # stdio MCP server (MCP SDK 2.x)
110
118
  ```
111
119
 
112
120
  It exposes three tools: **`select_tool`** (pick a tool for a task and return its risk/approval policy), **`list_library_tools`**, and **`get_tool_manifest`**. Point your client's MCP config at `asm-selector`, or at `python3 /path/to/asm-spec/asm_selector_mcp.py`; the selector reads `library/` (override with `ASM_LIBRARY_DIR`). The Python server uses the stable MCP SDK 2.x line and supports the modern `2026-07-28` protocol era. The same selector is importable directly: `from library_select import select`.
113
121
 
122
+ Cost output has an explicit `known`, `partial`, or `unknown` status. Metered
123
+ prices need expected monthly usage; one-time licenses need an amortization
124
+ period; prose-only free tiers remain unknown because their allowance and reset
125
+ rules are not machine-readable. If every eligible candidate does not have a
126
+ known cost in the same currency, the selector returns `needs_cost_facts` rather
127
+ than guessing. A caller may explicitly request the `capability_breadth` fallback;
128
+ it is never applied implicitly.
129
+
114
130
  The same engine is also available as:
115
131
 
116
132
  ```bash
117
133
  # CLI (human or scripted)
118
134
  asm select "find and book a refundable flight" --taxonomy tool.booking.travel \
119
- --requires flight_search,flight_order_create --json
135
+ --requires flight_search,flight_order_create --fallback-policy capability_breadth --json
120
136
 
121
137
  # Hosted HTTP API (stdlib-only; deploy anywhere that runs Python)
122
138
  python asm_select_api.py # POST /select, GET /tools, GET /healthz on :8787
123
139
  ```
124
140
 
125
- LangChain / LangGraph builders get the same selector as a drop-in tool (`pip install langchain-core`):
141
+ DeepSeek Harness developer-preview users can install the native
142
+ [`asm_select` tool adapter](integrations/deepseek-harness/README.md). It uses
143
+ the same HTTP contract and returns the current structured decision, but never
144
+ invokes or authorizes the selected service. The frozen v0.1 receipt is available
145
+ only through an explicit legacy compatibility profile. The adapter defaults to
146
+ a local selector so task text is not sent to a hosted endpoint implicitly.
147
+
148
+ LangChain / LangGraph builders get the same selector as a packaged drop-in tool:
126
149
 
127
150
  ```python
128
- import sys; sys.path += ["asm-spec", "asm-spec/integrations/langchain"]
129
- from asm_tools import ASMToolSelectorTool
151
+ from asm_protocol.integrations.langchain import ASMToolSelectorTool
130
152
  agent_tools = [ASMToolSelectorTool()] # name: asm_tool_selector
131
153
  ```
132
154
 
155
+ Install it with `python -m pip install "asm-protocol[langchain]==0.6.0"`. LangChain
156
+ hosts receive the full structured decision in `ToolMessage.artifact`; the
157
+ model-facing content remains a short summary. The adapter never executes or
158
+ authorizes the selected service.
159
+
133
160
  A public reference instance runs at **https://asm-spec.onrender.com**. It also dogfoods ASM's own publishing convention: `GET /.well-known/asm` serves the library catalog (one re-stampable `generated_at`, per-manifest links), and `GET /manifest/{service_id}` serves each full manifest — ASM is its own first publisher.
134
161
 
135
162
  ```bash
@@ -374,9 +401,10 @@ Schema: [`schema/asm-v0.3.schema.json`](schema/asm-v0.3.schema.json).
374
401
 
375
402
  ```text
376
403
  schema/ ASM JSON Schema
404
+ src/asm_protocol/ Canonical Python SDK: selection, cost, version
377
405
  library/ Tool-value library (agent tool selection) + select_demo.py
378
406
  manifests/ 75 source-linked manifests
379
- scorer/ Python TOPSIS scorer and tests
407
+ scorer/ Legacy experimental per-unit TOPSIS scorer and tests
380
408
  registry/ MCP registry server exposing ASM tools
381
409
  examples/mcp-server-json/ MCP Registry server.json examples
382
410
  docs/integrations/ MCP Registry and aggregator integration docs
@@ -19,10 +19,12 @@ schema/__init__.py
19
19
  schema/asm-receipt-envelope-v0.1.schema.json
20
20
  schema/asm-v0.2.schema.json
21
21
  schema/asm-v0.3.schema.json
22
+ schema/selection-receipt-v0.1.schema.json
22
23
  scorer/__init__.py
23
24
  scorer/scorer.py
24
25
  scorer/test_asm_lint.py
25
26
  scorer/test_asm_selector_mcp.py
27
+ scorer/test_cost_estimation.py
26
28
  scorer/test_data_quality_audit.py
27
29
  scorer/test_langchain_adapter.py
28
30
  scorer/test_library_select.py
@@ -30,5 +32,13 @@ scorer/test_manifests_schema.py
30
32
  scorer/test_mcp_server_json_asm.py
31
33
  scorer/test_openrouter_adapter.py
32
34
  scorer/test_scorer.py
35
+ scorer/test_version_contract.py
33
36
  scorer/test_x402_bridge.py
34
- scorer/data/elo_snapshot.json
37
+ scorer/data/__init__.py
38
+ scorer/data/elo_snapshot.json
39
+ src/asm_protocol/__init__.py
40
+ src/asm_protocol/cost.py
41
+ src/asm_protocol/selection.py
42
+ src/asm_protocol/version.py
43
+ src/asm_protocol/integrations/__init__.py
44
+ src/asm_protocol/integrations/langchain.py
@@ -1,6 +1,7 @@
1
1
  _asm_library_data
2
2
  asm_cli
3
3
  asm_lint
4
+ asm_protocol
4
5
  asm_schema
5
6
  asm_select_api
6
7
  asm_selector_mcp
@@ -4,7 +4,8 @@
4
4
  Endpoints:
5
5
  POST /select body: {task, taxonomy?, agent_reach?, user_platform?,
6
6
  required_functions?, require_approval_for?,
7
- require_agent_completable_setup?}
7
+ require_agent_completable_setup?, workload?: {
8
+ monthly_units?, amortization_months?}}
8
9
  -> structured selection decision (same shape as library_select.select)
9
10
  GET /tools ?taxonomy=... -> list of selectable tools
10
11
  GET /.well-known/asm -> ASM catalog: one re-stampable generated_at + per-manifest
@@ -29,7 +30,7 @@ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
29
30
  from pathlib import Path
30
31
  from urllib.parse import parse_qs, unquote, urlparse
31
32
 
32
- from library_select import load_library, monthly_cost, select
33
+ from library_select import estimate_monthly_cost, load_library, select
33
34
 
34
35
  _LIBRARY = load_library()
35
36
  _BY_ID = {m.get("service_id"): m for m in _LIBRARY}
@@ -138,6 +139,7 @@ def _tools_listing(taxonomy: str | None) -> list[dict]:
138
139
  if taxonomy and m.get("taxonomy") != taxonomy:
139
140
  continue
140
141
  inv = m.get("invocation") or {}
142
+ estimate = estimate_monthly_cost(m)
141
143
  out.append({
142
144
  "service_id": m.get("service_id"),
143
145
  "display_name": m.get("display_name"),
@@ -146,7 +148,8 @@ def _tools_listing(taxonomy: str | None) -> list[dict]:
146
148
  "reach": inv.get("reach"),
147
149
  "agent_operable": inv.get("agent_operable"),
148
150
  "agent_completable_setup": inv.get("agent_completable_setup"),
149
- "monthly_cost_usd": round(monthly_cost(m), 2),
151
+ "monthly_cost_usd": estimate.monthly_total,
152
+ "cost_estimate": estimate.to_dict(),
150
153
  })
151
154
  return out
152
155
 
@@ -248,18 +251,25 @@ class Handler(BaseHTTPRequestHandler):
248
251
  if not task:
249
252
  self._send(400, {"error": "'task' is required"})
250
253
  return
251
- result = select(
252
- task,
253
- taxonomy=req.get("taxonomy"),
254
- agent_reach=req.get("agent_reach", "cloud"),
255
- user_platform=req.get("user_platform", "any"),
256
- required_functions=req.get("required_functions") or [],
257
- require_approval_for=req.get("require_approval_for") or [],
258
- require_agent_completable_setup=bool(req.get("require_agent_completable_setup", False)),
259
- library=_LIBRARY,
260
- receipt=bool(req.get("receipt", False)),
261
- )
262
- self._send(200, result)
254
+ try:
255
+ result = select(
256
+ task,
257
+ taxonomy=req.get("taxonomy"),
258
+ agent_reach=req.get("agent_reach", "cloud"),
259
+ user_platform=req.get("user_platform", "any"),
260
+ required_functions=req.get("required_functions") or [],
261
+ require_approval_for=req.get("require_approval_for") or [],
262
+ require_agent_completable_setup=bool(req.get("require_agent_completable_setup", False)),
263
+ workload=req.get("workload"),
264
+ fallback_policy=req.get("fallback_policy"),
265
+ selection_profile=req.get("selection_profile", "current"),
266
+ library=_LIBRARY,
267
+ receipt=bool(req.get("receipt", False)),
268
+ )
269
+ except (TypeError, ValueError) as error:
270
+ self._send(400, {"error": str(error)})
271
+ return
272
+ self._send(422 if result["selection_status"] == "under_specified" else 200, result)
263
273
 
264
274
  def log_message(self, fmt, *args): # quiet default logging
265
275
  pass
@@ -9,15 +9,16 @@ policy (risk / approval / side-effects) to gate the action.
9
9
  Run (stdio): python asm_selector_mcp.py (or `asm-selector` once installed)
10
10
  Requires: pip install "asm-protocol[mcp]"
11
11
  """
12
+
12
13
  from __future__ import annotations
13
14
 
14
15
  from mcp.server import MCPServer
15
16
 
16
- from library_select import load_library, monthly_cost, select
17
+ from library_select import SELECTOR_VERSION, estimate_monthly_cost, load_library, select
17
18
 
18
19
  mcp = MCPServer(
19
20
  "asm-selector",
20
- version="0.5.1",
21
+ version=SELECTOR_VERSION.removeprefix("asm-protocol/"),
21
22
  description="Select agent-operable tools using ASM value and policy metadata.",
22
23
  )
23
24
 
@@ -31,6 +32,9 @@ def select_tool(
31
32
  required_functions: list[str] | None = None,
32
33
  require_approval_for: list[str] | None = None,
33
34
  require_agent_completable_setup: bool = False,
35
+ monthly_usage: dict[str, float] | None = None,
36
+ amortization_months: int | None = None,
37
+ fallback_policy: str | None = None,
34
38
  ) -> dict:
35
39
  """Pick the best tool from the ASM library for a task.
36
40
 
@@ -40,7 +44,7 @@ def select_tool(
40
44
  policy so the caller can gate high-stakes actions before invoking.
41
45
 
42
46
  Args:
43
- task: natural-language description of what the agent must do.
47
+ task: audit/display text; the deterministic core does not interpret it.
44
48
  taxonomy: optional ASM taxonomy to scope candidates (e.g. tool.booking.travel).
45
49
  agent_reach: 'cloud' (remote/headless) or 'local_device' (runs on user's machine).
46
50
  user_platform: e.g. windows, macos, ios, android, web, any.
@@ -48,6 +52,9 @@ def select_tool(
48
52
  require_approval_for: side-effects that should force approval (e.g. ["financial_charge","sends_message"]).
49
53
  require_agent_completable_setup: if true, drop tools that need a human-in-the-loop
50
54
  step (paid signup, OAuth consent, manual approval) the agent cannot complete unattended.
55
+ monthly_usage: expected monthly units keyed by billing dimension.
56
+ amortization_months: period for normalizing a declared one-time purchase.
57
+ fallback_policy: optional 'capability_breadth'; never applied implicitly.
51
58
  """
52
59
  return select(
53
60
  task,
@@ -57,27 +64,35 @@ def select_tool(
57
64
  required_functions=required_functions or [],
58
65
  require_approval_for=require_approval_for or [],
59
66
  require_agent_completable_setup=require_agent_completable_setup,
67
+ workload={
68
+ "monthly_units": monthly_usage,
69
+ "amortization_months": amortization_months,
70
+ },
71
+ fallback_policy=fallback_policy,
60
72
  )
61
73
 
62
74
 
63
75
  @mcp.tool()
64
76
  def list_library_tools(taxonomy: str | None = None) -> list[dict]:
65
- """List tools in the ASM library (optionally filtered by taxonomy), with
66
- interface/reach and monthly cost so a client can browse what's selectable."""
77
+ """List tools with invocation facts and honest cost-estimate status."""
67
78
  out = []
68
79
  for m in load_library():
69
80
  if taxonomy and m.get("taxonomy") != taxonomy:
70
81
  continue
71
82
  inv = m.get("invocation") or {}
72
- out.append({
73
- "service_id": m.get("service_id"),
74
- "display_name": m.get("display_name"),
75
- "taxonomy": m.get("taxonomy"),
76
- "interface": inv.get("interface"),
77
- "reach": inv.get("reach"),
78
- "agent_operable": inv.get("agent_operable"),
79
- "monthly_cost_usd": round(monthly_cost(m), 2),
80
- })
83
+ estimate = estimate_monthly_cost(m)
84
+ out.append(
85
+ {
86
+ "service_id": m.get("service_id"),
87
+ "display_name": m.get("display_name"),
88
+ "taxonomy": m.get("taxonomy"),
89
+ "interface": inv.get("interface"),
90
+ "reach": inv.get("reach"),
91
+ "agent_operable": inv.get("agent_operable"),
92
+ "monthly_cost_usd": estimate.monthly_total,
93
+ "cost_estimate": estimate.to_dict(),
94
+ }
95
+ )
81
96
  return out
82
97
 
83
98
 
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env python3
2
+ """Backward-compatible facade for the canonical :mod:`asm_protocol` SDK."""
3
+
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ _SRC = Path(__file__).resolve().parent / "src"
8
+ if str(_SRC) not in sys.path:
9
+ sys.path.insert(0, str(_SRC))
10
+
11
+ from asm_protocol.selection import *
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "asm-protocol"
7
- version = "0.5.2"
7
+ dynamic = ["version"]
8
8
  description = "Agent Service Manifest: value metadata and scoring for autonomous service selection"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -27,11 +27,21 @@ asm-selector = "asm_selector_mcp:main"
27
27
 
28
28
  [tool.setuptools]
29
29
  py-modules = ["asm_cli", "asm_lint", "mcp_server_json_asm", "openrouter_adapter", "library_select", "asm_selector_mcp", "asm_select_api", "_asm_library_data"]
30
- packages = ["scorer", "asm_schema"]
30
+ packages = [
31
+ "asm_protocol",
32
+ "asm_protocol.integrations",
33
+ "scorer",
34
+ "scorer.data",
35
+ "asm_schema",
36
+ ]
31
37
 
32
38
  [tool.setuptools.package-dir]
39
+ asm_protocol = "src/asm_protocol"
33
40
  asm_schema = "schema"
34
41
 
42
+ [tool.setuptools.dynamic]
43
+ version = {attr = "asm_protocol.version.__version__"}
44
+
35
45
  [tool.setuptools.package-data]
36
46
  scorer = ["data/*.json"]
37
47
  asm_schema = ["*.json"]
@@ -39,3 +49,4 @@ asm_schema = ["*.json"]
39
49
  [tool.pytest.ini_options]
40
50
  testpaths = ["scorer", "tools/asm-gen"]
41
51
  python_files = ["test_*.py"]
52
+ pythonpath = [".", "src"]