mechaharness 0.1.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 (79) hide show
  1. mechaharness-0.1.0/LICENSE +21 -0
  2. mechaharness-0.1.0/PKG-INFO +171 -0
  3. mechaharness-0.1.0/README.md +123 -0
  4. mechaharness-0.1.0/pyproject.toml +94 -0
  5. mechaharness-0.1.0/setup.cfg +4 -0
  6. mechaharness-0.1.0/src/mechaharness/__init__.py +54 -0
  7. mechaharness-0.1.0/src/mechaharness/api/__init__.py +5 -0
  8. mechaharness-0.1.0/src/mechaharness/api/app.py +42 -0
  9. mechaharness-0.1.0/src/mechaharness/api_connection.py +160 -0
  10. mechaharness-0.1.0/src/mechaharness/cli/__init__.py +1 -0
  11. mechaharness-0.1.0/src/mechaharness/cli/main.py +131 -0
  12. mechaharness-0.1.0/src/mechaharness/config.py +37 -0
  13. mechaharness-0.1.0/src/mechaharness/connection.py +9 -0
  14. mechaharness-0.1.0/src/mechaharness/context_experiments.py +108 -0
  15. mechaharness-0.1.0/src/mechaharness/core/__init__.py +117 -0
  16. mechaharness-0.1.0/src/mechaharness/core/access.py +464 -0
  17. mechaharness-0.1.0/src/mechaharness/core/completer.py +28 -0
  18. mechaharness-0.1.0/src/mechaharness/core/contract.py +38 -0
  19. mechaharness-0.1.0/src/mechaharness/core/environment.py +115 -0
  20. mechaharness-0.1.0/src/mechaharness/core/events.py +328 -0
  21. mechaharness-0.1.0/src/mechaharness/core/exceptions.py +19 -0
  22. mechaharness-0.1.0/src/mechaharness/core/outcomes.py +48 -0
  23. mechaharness-0.1.0/src/mechaharness/core/types.py +86 -0
  24. mechaharness-0.1.0/src/mechaharness/decision_log.py +107 -0
  25. mechaharness-0.1.0/src/mechaharness/decisions.py +10 -0
  26. mechaharness-0.1.0/src/mechaharness/di.py +324 -0
  27. mechaharness-0.1.0/src/mechaharness/factory.py +65 -0
  28. mechaharness-0.1.0/src/mechaharness/graph.py +148 -0
  29. mechaharness-0.1.0/src/mechaharness/harness/__init__.py +18 -0
  30. mechaharness-0.1.0/src/mechaharness/harness/base.py +407 -0
  31. mechaharness-0.1.0/src/mechaharness/harness/families.py +17 -0
  32. mechaharness-0.1.0/src/mechaharness/harness/pass_through.py +28 -0
  33. mechaharness-0.1.0/src/mechaharness/harness/react.py +90 -0
  34. mechaharness-0.1.0/src/mechaharness/harness/tool_loop.py +23 -0
  35. mechaharness-0.1.0/src/mechaharness/inference/__init__.py +29 -0
  36. mechaharness-0.1.0/src/mechaharness/inference/anthropic.py +176 -0
  37. mechaharness-0.1.0/src/mechaharness/inference/base.py +43 -0
  38. mechaharness-0.1.0/src/mechaharness/inference/judge.py +378 -0
  39. mechaharness-0.1.0/src/mechaharness/inference/mock.py +46 -0
  40. mechaharness-0.1.0/src/mechaharness/inference/openai_compat.py +253 -0
  41. mechaharness-0.1.0/src/mechaharness/inference/openai_wire.py +142 -0
  42. mechaharness-0.1.0/src/mechaharness/inference/systemone.py +232 -0
  43. mechaharness-0.1.0/src/mechaharness/judgement_policy.py +217 -0
  44. mechaharness-0.1.0/src/mechaharness/operation_registry.py +49 -0
  45. mechaharness-0.1.0/src/mechaharness/operations.py +8 -0
  46. mechaharness-0.1.0/src/mechaharness/policy.py +15 -0
  47. mechaharness-0.1.0/src/mechaharness/py.typed +0 -0
  48. mechaharness-0.1.0/src/mechaharness/research.py +129 -0
  49. mechaharness-0.1.0/src/mechaharness/routing.py +121 -0
  50. mechaharness-0.1.0/src/mechaharness/tools/__init__.py +7 -0
  51. mechaharness-0.1.0/src/mechaharness/tools/base.py +117 -0
  52. mechaharness-0.1.0/src/mechaharness/tools/subagents.py +103 -0
  53. mechaharness-0.1.0/src/mechaharness.egg-info/PKG-INFO +171 -0
  54. mechaharness-0.1.0/src/mechaharness.egg-info/SOURCES.txt +77 -0
  55. mechaharness-0.1.0/src/mechaharness.egg-info/dependency_links.txt +1 -0
  56. mechaharness-0.1.0/src/mechaharness.egg-info/entry_points.txt +2 -0
  57. mechaharness-0.1.0/src/mechaharness.egg-info/requires.txt +24 -0
  58. mechaharness-0.1.0/src/mechaharness.egg-info/top_level.txt +1 -0
  59. mechaharness-0.1.0/tests/test_access.py +365 -0
  60. mechaharness-0.1.0/tests/test_api.py +29 -0
  61. mechaharness-0.1.0/tests/test_api_connection.py +67 -0
  62. mechaharness-0.1.0/tests/test_completer.py +58 -0
  63. mechaharness-0.1.0/tests/test_context_experiments.py +39 -0
  64. mechaharness-0.1.0/tests/test_di.py +109 -0
  65. mechaharness-0.1.0/tests/test_environment.py +75 -0
  66. mechaharness-0.1.0/tests/test_events.py +178 -0
  67. mechaharness-0.1.0/tests/test_graph.py +41 -0
  68. mechaharness-0.1.0/tests/test_harness.py +108 -0
  69. mechaharness-0.1.0/tests/test_inference_registry.py +55 -0
  70. mechaharness-0.1.0/tests/test_judge.py +165 -0
  71. mechaharness-0.1.0/tests/test_judgement_policy.py +107 -0
  72. mechaharness-0.1.0/tests/test_openai_wire.py +158 -0
  73. mechaharness-0.1.0/tests/test_operation_registry.py +15 -0
  74. mechaharness-0.1.0/tests/test_outcomes.py +43 -0
  75. mechaharness-0.1.0/tests/test_pass_through.py +100 -0
  76. mechaharness-0.1.0/tests/test_research.py +23 -0
  77. mechaharness-0.1.0/tests/test_routing.py +66 -0
  78. mechaharness-0.1.0/tests/test_subagent_tools.py +59 -0
  79. mechaharness-0.1.0/tests/test_usage_reasoning.py +40 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Richard Lee
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,171 @@
1
+ Metadata-Version: 2.4
2
+ Name: mechaharness
3
+ Version: 0.1.0
4
+ Summary: Agentic harness that abstracts inference backends and model-family harness architectures
5
+ Author-email: Richard Lee <rlee@tokyo3.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://rl337.org/mechaharness/
8
+ Project-URL: Documentation, https://rl337.org/mechaharness/
9
+ Project-URL: Repository, https://github.com/rl337/mechaharness
10
+ Project-URL: Issues, https://github.com/rl337/mechaharness/issues
11
+ Project-URL: Changelog, https://rl337.org/mechaharness/changelog.html
12
+ Keywords: agent,llm,harness,inference,cli
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.9
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: pyiv>=0.3.0
28
+ Requires-Dist: httpx>=0.27
29
+ Requires-Dist: pydantic>=2.7
30
+ Requires-Dist: pydantic-settings>=2.3
31
+ Requires-Dist: typer>=0.12
32
+ Requires-Dist: rich>=13.7
33
+ Requires-Dist: fastapi>=0.111
34
+ Requires-Dist: uvicorn[standard]>=0.30
35
+ Requires-Dist: eval_type_backport>=0.2; python_version < "3.10"
36
+ Provides-Extra: dev
37
+ Requires-Dist: pytest>=8.2; extra == "dev"
38
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
39
+ Requires-Dist: ruff>=0.5; extra == "dev"
40
+ Requires-Dist: mypy>=1.10; extra == "dev"
41
+ Requires-Dist: build>=1.2; extra == "dev"
42
+ Requires-Dist: twine>=5.0; extra == "dev"
43
+ Provides-Extra: docs
44
+ Requires-Dist: sphinx>=5.0.0; extra == "docs"
45
+ Requires-Dist: sphinx-rtd-theme>=1.0.0; extra == "docs"
46
+ Requires-Dist: myst-parser>=2.0.0; extra == "docs"
47
+ Dynamic: license-file
48
+
49
+ # MechaHarness
50
+
51
+ ![MechaHarness](docs/logo.png)
52
+
53
+ Python **agent harness** for hosts that compose inference safely — not a thin
54
+ chat-client wrapper. Founding principles:
55
+
56
+ 1. **Modular via dependency injection** — pyiv `MechaHarnessConfig`; hosts
57
+ subclass and compose (no forked enums / string registries)
58
+ 2. **Multi-model / lanes native** — reason, judge, and media lanes;
59
+ `Completion` / `Judgement` / `Generation` outcomes
60
+ 3. **Cost in the object model** — `CostAccountant` on the harness path so runs
61
+ cannot quietly go AWOL on spend
62
+ 4. **EventLog telemetry built in** — queryable lifecycle, inference, tools,
63
+ cost, and access events on every run
64
+
65
+ Also: deny-by-default grants / `CompoundPolicy`, and host-extendable open
66
+ identity. Docs: [https://rl337.org/mechaharness/](https://rl337.org/mechaharness/)
67
+
68
+ ## Install
69
+
70
+ ```bash
71
+ pip install mechaharness
72
+ ```
73
+
74
+ Requires Python 3.9+. Unreleased `main`:
75
+
76
+ ```bash
77
+ pip install git+https://github.com/rl337/mechaharness.git
78
+ ```
79
+
80
+ ## Quick start
81
+
82
+ ```bash
83
+ # List backends / harness families
84
+ mechaharness backends
85
+ mechaharness families
86
+
87
+ # Local LM Studio (OpenAI-compatible)
88
+ mechaharness run "What is 2+2?" \
89
+ --backend lmstudio \
90
+ --family tool_loop \
91
+ --model local-model
92
+
93
+ # OpenAI
94
+ export MECHA_API_KEY=sk-...
95
+ mechaharness run "Hello" --backend openai --model gpt-4o-mini
96
+
97
+ # HTTP API
98
+ mechaharness serve --port 8080
99
+ curl -s http://127.0.0.1:8080/health
100
+ ```
101
+
102
+ Environment variables use the `MECHA_` prefix (`MECHA_API_KEY`, `MECHA_INFERENCE_BACKEND`, `MECHA_MODEL`, `MECHA_BASE_URL`, …).
103
+
104
+ ## Library usage
105
+
106
+ Non-DI (OpenAPI-shaped):
107
+
108
+ ```python
109
+ import asyncio
110
+ from mechaharness import RunRequest, run
111
+
112
+ async def main() -> None:
113
+ result = await run(RunRequest(prompt="What is 19 + 23?", backend="lmstudio"))
114
+ print(result.final_text)
115
+
116
+ asyncio.run(main())
117
+ ```
118
+
119
+ DI (pyiv Config hooks):
120
+
121
+ ```python
122
+ from pyiv import get_injector
123
+
124
+ from mechaharness.di import MechaHarnessConfig
125
+ from mechaharness.harness.base import AbstractHarness
126
+ from mechaharness.harness.tool_loop import ToolLoopHarness
127
+ from mechaharness.inference.openai_compat import OpenAICompatStrategy
128
+
129
+
130
+ class MyConfig(MechaHarnessConfig):
131
+ def get_inference_class(self):
132
+ return OpenAICompatStrategy
133
+
134
+ def get_harness_class(self):
135
+ return ToolLoopHarness
136
+
137
+
138
+ injector = get_injector(MyConfig)
139
+ harness = injector.inject(AbstractHarness)
140
+ ```
141
+
142
+ See [Dependency injection](https://rl337.org/mechaharness/guides/dependency-injection.html).
143
+
144
+ ## Extending
145
+
146
+ Subclass `InferenceStrategy` (constructor takes `Settings`) and return it from `get_inference_class()`, or merge it into `SettingsConfig.inference_classes()`. Subclass `AbstractHarness` and return it from `get_harness_class()`.
147
+
148
+ ## Development
149
+
150
+ ```bash
151
+ python -m venv .venv
152
+ source .venv/bin/activate
153
+ pip install -e ".[dev,docs]"
154
+ ./run_checks.sh
155
+ ```
156
+
157
+ `run_checks.sh` is what CI runs: ruff, mypy, pytest, Sphinx, and CLI smoke. EventLog + cost without a GPU:
158
+
159
+ ```bash
160
+ mechaharness run "What is 2+2?" --backend mock --family pass_through --model mock --json
161
+ ```
162
+
163
+ ## Design notes
164
+
165
+ | Concern | Pattern | Why |
166
+ |--------|---------|-----|
167
+ | Wiring | pyiv Config hooks | DI-first; hosts inject MechaHarness types |
168
+ | Provider I/O | Strategy | Swap cloud/local without touching agent logic |
169
+ | Agent loop | Template method hierarchy | Share turn accounting; specialize stop/tool rules per model family |
170
+ | Frontends | OpenAPI `RunRequest` / `run()` | Same contract for CLI, HTTP, and non-DI Python |
171
+ | Bindings later | Pydantic + OpenAPI | Types stay serializable; API is the first non-Python client surface |
@@ -0,0 +1,123 @@
1
+ # MechaHarness
2
+
3
+ ![MechaHarness](docs/logo.png)
4
+
5
+ Python **agent harness** for hosts that compose inference safely — not a thin
6
+ chat-client wrapper. Founding principles:
7
+
8
+ 1. **Modular via dependency injection** — pyiv `MechaHarnessConfig`; hosts
9
+ subclass and compose (no forked enums / string registries)
10
+ 2. **Multi-model / lanes native** — reason, judge, and media lanes;
11
+ `Completion` / `Judgement` / `Generation` outcomes
12
+ 3. **Cost in the object model** — `CostAccountant` on the harness path so runs
13
+ cannot quietly go AWOL on spend
14
+ 4. **EventLog telemetry built in** — queryable lifecycle, inference, tools,
15
+ cost, and access events on every run
16
+
17
+ Also: deny-by-default grants / `CompoundPolicy`, and host-extendable open
18
+ identity. Docs: [https://rl337.org/mechaharness/](https://rl337.org/mechaharness/)
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ pip install mechaharness
24
+ ```
25
+
26
+ Requires Python 3.9+. Unreleased `main`:
27
+
28
+ ```bash
29
+ pip install git+https://github.com/rl337/mechaharness.git
30
+ ```
31
+
32
+ ## Quick start
33
+
34
+ ```bash
35
+ # List backends / harness families
36
+ mechaharness backends
37
+ mechaharness families
38
+
39
+ # Local LM Studio (OpenAI-compatible)
40
+ mechaharness run "What is 2+2?" \
41
+ --backend lmstudio \
42
+ --family tool_loop \
43
+ --model local-model
44
+
45
+ # OpenAI
46
+ export MECHA_API_KEY=sk-...
47
+ mechaharness run "Hello" --backend openai --model gpt-4o-mini
48
+
49
+ # HTTP API
50
+ mechaharness serve --port 8080
51
+ curl -s http://127.0.0.1:8080/health
52
+ ```
53
+
54
+ Environment variables use the `MECHA_` prefix (`MECHA_API_KEY`, `MECHA_INFERENCE_BACKEND`, `MECHA_MODEL`, `MECHA_BASE_URL`, …).
55
+
56
+ ## Library usage
57
+
58
+ Non-DI (OpenAPI-shaped):
59
+
60
+ ```python
61
+ import asyncio
62
+ from mechaharness import RunRequest, run
63
+
64
+ async def main() -> None:
65
+ result = await run(RunRequest(prompt="What is 19 + 23?", backend="lmstudio"))
66
+ print(result.final_text)
67
+
68
+ asyncio.run(main())
69
+ ```
70
+
71
+ DI (pyiv Config hooks):
72
+
73
+ ```python
74
+ from pyiv import get_injector
75
+
76
+ from mechaharness.di import MechaHarnessConfig
77
+ from mechaharness.harness.base import AbstractHarness
78
+ from mechaharness.harness.tool_loop import ToolLoopHarness
79
+ from mechaharness.inference.openai_compat import OpenAICompatStrategy
80
+
81
+
82
+ class MyConfig(MechaHarnessConfig):
83
+ def get_inference_class(self):
84
+ return OpenAICompatStrategy
85
+
86
+ def get_harness_class(self):
87
+ return ToolLoopHarness
88
+
89
+
90
+ injector = get_injector(MyConfig)
91
+ harness = injector.inject(AbstractHarness)
92
+ ```
93
+
94
+ See [Dependency injection](https://rl337.org/mechaharness/guides/dependency-injection.html).
95
+
96
+ ## Extending
97
+
98
+ Subclass `InferenceStrategy` (constructor takes `Settings`) and return it from `get_inference_class()`, or merge it into `SettingsConfig.inference_classes()`. Subclass `AbstractHarness` and return it from `get_harness_class()`.
99
+
100
+ ## Development
101
+
102
+ ```bash
103
+ python -m venv .venv
104
+ source .venv/bin/activate
105
+ pip install -e ".[dev,docs]"
106
+ ./run_checks.sh
107
+ ```
108
+
109
+ `run_checks.sh` is what CI runs: ruff, mypy, pytest, Sphinx, and CLI smoke. EventLog + cost without a GPU:
110
+
111
+ ```bash
112
+ mechaharness run "What is 2+2?" --backend mock --family pass_through --model mock --json
113
+ ```
114
+
115
+ ## Design notes
116
+
117
+ | Concern | Pattern | Why |
118
+ |--------|---------|-----|
119
+ | Wiring | pyiv Config hooks | DI-first; hosts inject MechaHarness types |
120
+ | Provider I/O | Strategy | Swap cloud/local without touching agent logic |
121
+ | Agent loop | Template method hierarchy | Share turn accounting; specialize stop/tool rules per model family |
122
+ | Frontends | OpenAPI `RunRequest` / `run()` | Same contract for CLI, HTTP, and non-DI Python |
123
+ | Bindings later | Pydantic + OpenAPI | Types stay serializable; API is the first non-Python client surface |
@@ -0,0 +1,94 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "mechaharness"
7
+ version = "0.1.0"
8
+ description = "Agentic harness that abstracts inference backends and model-family harness architectures"
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.9"
12
+ authors = [
13
+ { name = "Richard Lee", email = "rlee@tokyo3.com" },
14
+ ]
15
+ keywords = ["agent", "llm", "harness", "inference", "cli"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.9",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
27
+ "Typing :: Typed",
28
+ ]
29
+ dependencies = [
30
+ "pyiv>=0.3.0",
31
+ "httpx>=0.27",
32
+ "pydantic>=2.7",
33
+ "pydantic-settings>=2.3",
34
+ "typer>=0.12",
35
+ "rich>=13.7",
36
+ "fastapi>=0.111",
37
+ "uvicorn[standard]>=0.30",
38
+ "eval_type_backport>=0.2; python_version < '3.10'",
39
+ ]
40
+
41
+ [project.optional-dependencies]
42
+ dev = [
43
+ "pytest>=8.2",
44
+ "pytest-asyncio>=0.23",
45
+ "ruff>=0.5",
46
+ "mypy>=1.10",
47
+ "build>=1.2",
48
+ "twine>=5.0",
49
+ ]
50
+ docs = [
51
+ "sphinx>=5.0.0",
52
+ "sphinx-rtd-theme>=1.0.0",
53
+ "myst-parser>=2.0.0",
54
+ ]
55
+
56
+ [project.scripts]
57
+ mechaharness = "mechaharness.cli.main:app"
58
+
59
+ [project.urls]
60
+ Homepage = "https://rl337.org/mechaharness/"
61
+ Documentation = "https://rl337.org/mechaharness/"
62
+ Repository = "https://github.com/rl337/mechaharness"
63
+ Issues = "https://github.com/rl337/mechaharness/issues"
64
+ Changelog = "https://rl337.org/mechaharness/changelog.html"
65
+
66
+ [tool.setuptools.packages.find]
67
+ where = ["src"]
68
+
69
+ [tool.setuptools.package-data]
70
+ mechaharness = ["py.typed"]
71
+
72
+ [tool.pytest.ini_options]
73
+ asyncio_mode = "auto"
74
+ testpaths = ["tests"]
75
+ pythonpath = ["."]
76
+ markers = [
77
+ "story_live: dual-mode user stories against live backends (MECHA_STORY_BACKEND=live)",
78
+ ]
79
+
80
+ [tool.ruff]
81
+ line-length = 100
82
+ target-version = "py39"
83
+
84
+ [tool.ruff.lint]
85
+ select = ["E", "F", "I", "UP", "B"]
86
+
87
+ [tool.mypy]
88
+ python_version = "3.9"
89
+ strict = true
90
+ packages = ["mechaharness"]
91
+
92
+ [[tool.mypy.overrides]]
93
+ module = "pyiv.*"
94
+ ignore_missing_imports = true
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,54 @@
1
+ """MechaHarness: agentic harness with pluggable inference and harness families."""
2
+
3
+ from mechaharness.config import Settings
4
+ from mechaharness.core.access import AccessControl, CostAccountant
5
+ from mechaharness.core.completer import Completer
6
+ from mechaharness.core.contract import RunRequest, RunResponse
7
+ from mechaharness.core.environment import InferenceEnvironment
8
+ from mechaharness.core.events import Event, EventLog, EventType
9
+ from mechaharness.core.types import (
10
+ ChatMessage,
11
+ CompletionRequest,
12
+ CompletionResponse,
13
+ Role,
14
+ ToolCall,
15
+ ToolDefinition,
16
+ ToolResult,
17
+ )
18
+ from mechaharness.di import MechaHarnessConfig, SettingsConfig, list_inference_backends
19
+ from mechaharness.factory import run
20
+ from mechaharness.harness.base import AbstractHarness, HarnessConfig, HarnessResult
21
+ from mechaharness.inference.base import InferenceStrategy
22
+ from mechaharness.tools.base import Tool, ToolRegistry
23
+
24
+ __all__ = [
25
+ "AbstractHarness",
26
+ "AccessControl",
27
+ "ChatMessage",
28
+ "Completer",
29
+ "CompletionRequest",
30
+ "CompletionResponse",
31
+ "CostAccountant",
32
+ "Event",
33
+ "EventLog",
34
+ "EventType",
35
+ "HarnessConfig",
36
+ "HarnessResult",
37
+ "InferenceEnvironment",
38
+ "InferenceStrategy",
39
+ "MechaHarnessConfig",
40
+ "Role",
41
+ "RunRequest",
42
+ "RunResponse",
43
+ "Settings",
44
+ "SettingsConfig",
45
+ "Tool",
46
+ "ToolCall",
47
+ "ToolDefinition",
48
+ "ToolRegistry",
49
+ "ToolResult",
50
+ "list_inference_backends",
51
+ "run",
52
+ ]
53
+
54
+ __version__ = "0.1.0"
@@ -0,0 +1,5 @@
1
+ """HTTP API package."""
2
+
3
+ from mechaharness.api.app import app
4
+
5
+ __all__ = ["app"]
@@ -0,0 +1,42 @@
1
+ """HTTP API surface (stable contract for non-Python clients)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from fastapi import FastAPI, HTTPException
6
+
7
+ from mechaharness import __version__
8
+ from mechaharness.core.contract import RunRequest, RunResponse
9
+ from mechaharness.core.exceptions import MechaHarnessError
10
+ from mechaharness.di import list_harness_families, list_inference_backends
11
+ from mechaharness.factory import run as run_harness
12
+
13
+ app = FastAPI(
14
+ title="MechaHarness",
15
+ version=__version__,
16
+ description="Agentic harness API — pluggable inference backends and harness families.",
17
+ )
18
+
19
+
20
+ @app.get("/health")
21
+ async def health() -> dict[str, str]:
22
+ return {"status": "ok", "version": __version__}
23
+
24
+
25
+ @app.get("/backends")
26
+ async def backends() -> dict[str, list[str]]:
27
+ return {"backends": list_inference_backends()}
28
+
29
+
30
+ @app.get("/families")
31
+ async def families() -> dict[str, list[str]]:
32
+ return {"families": list_harness_families()}
33
+
34
+
35
+ @app.post("/v1/run", response_model=RunResponse)
36
+ async def run(req: RunRequest) -> RunResponse:
37
+ try:
38
+ return await run_harness(req)
39
+ except KeyError as exc:
40
+ raise HTTPException(status_code=400, detail=str(exc)) from exc
41
+ except MechaHarnessError as exc:
42
+ raise HTTPException(status_code=400, detail=str(exc)) from exc
@@ -0,0 +1,160 @@
1
+ """HTTP connection configuration for inference adapters.
2
+
3
+ ``APIConnectionConfig`` is the injectable “how to reach this service” bundle
4
+ (endpoint + credentials + timeout). Wire shape stays on ``JudgeProvider`` /
5
+ ``InferenceStrategy``. Hosts swap implementations (simple HTTP, later OAuth)
6
+ via Config hooks without proliferating URL-only and auth-only types.
7
+
8
+ Judge-lane env knobs (``MECHA_JUDGE_*``) bind here via ``from_env`` — not on
9
+ ``Settings``.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import os
15
+ from abc import ABC, abstractmethod
16
+ from typing import Any
17
+
18
+ DEFAULT_JUDGE_PATH = "/v1/systemone"
19
+
20
+
21
+ class APIConnectionConfig(ABC):
22
+ """How to open an HTTP connection to an inference / judge endpoint."""
23
+
24
+ @abstractmethod
25
+ def endpoint_url(self) -> str:
26
+ """Absolute URL used for the request (no further path joining)."""
27
+
28
+ def headers(self) -> dict[str, str]:
29
+ """Extra HTTP headers (Authorization, etc.)."""
30
+ return {}
31
+
32
+ def timeout_seconds(self) -> float:
33
+ return 60.0
34
+
35
+ def model_id(self) -> str | None:
36
+ """Optional model id for JSON bodies that accept ``model``."""
37
+ return None
38
+
39
+
40
+ class SimpleHttpConnectionConfig(APIConnectionConfig):
41
+ """Base URL + path (or full URL) with optional API key header.
42
+
43
+ Default judge path is ``/v1/systemone``. Set ``url`` to override the full
44
+ endpoint (e.g. a hosted provider with a different scheme).
45
+ """
46
+
47
+ def __init__(
48
+ self,
49
+ *,
50
+ base_url: str | None = None,
51
+ path: str = DEFAULT_JUDGE_PATH,
52
+ url: str | None = None,
53
+ api_key: str | None = None,
54
+ model: str | None = None,
55
+ timeout: float = 60.0,
56
+ header_name: str = "Authorization",
57
+ header_value_template: str = "Bearer {api_key}",
58
+ ) -> None:
59
+ self._base_url = (base_url or "").rstrip("/")
60
+ self._path = path if path.startswith("/") else f"/{path}"
61
+ self._url = url.rstrip("/") if url else None
62
+ self._api_key = api_key
63
+ self._model = model
64
+ self._timeout = timeout
65
+ self._header_name = header_name
66
+ self._header_value_template = header_value_template
67
+
68
+ @classmethod
69
+ def from_env(
70
+ cls,
71
+ *,
72
+ prefix: str = "MECHA_JUDGE",
73
+ legacy_prefix: str | None = "MECHA_DECIDE",
74
+ default_path: str = DEFAULT_JUDGE_PATH,
75
+ fallback_api_key_env: str = "MECHA_API_KEY",
76
+ ) -> SimpleHttpConnectionConfig:
77
+ """Bind connection knobs from the process environment.
78
+
79
+ Reads ``{prefix}_BASE_URL``, ``_PATH``, ``_URL``, ``_MODEL``,
80
+ ``_API_KEY``, ``_TIMEOUT_SECONDS``. When ``legacy_prefix`` is set,
81
+ falls back for base URL / model (judge migration window).
82
+ """
83
+
84
+ def _get(*keys: str) -> str | None:
85
+ for key in keys:
86
+ value = os.environ.get(key)
87
+ if value is not None and value != "":
88
+ return value
89
+ return None
90
+
91
+ base = _get(f"{prefix}_BASE_URL")
92
+ model = _get(f"{prefix}_MODEL")
93
+ if legacy_prefix:
94
+ if base is None:
95
+ base = _get(f"{legacy_prefix}_BASE_URL")
96
+ if model is None:
97
+ model = _get(f"{legacy_prefix}_MODEL")
98
+ path = _get(f"{prefix}_PATH") or default_path
99
+ url = _get(f"{prefix}_URL")
100
+ api_key = _get(f"{prefix}_API_KEY", fallback_api_key_env)
101
+ timeout_raw = _get(f"{prefix}_TIMEOUT_SECONDS")
102
+ timeout = float(timeout_raw) if timeout_raw else 60.0
103
+ return cls(
104
+ base_url=base,
105
+ path=path,
106
+ url=url,
107
+ api_key=api_key,
108
+ model=model,
109
+ timeout=timeout,
110
+ )
111
+
112
+ @classmethod
113
+ def for_judge(cls, **overrides: Any) -> SimpleHttpConnectionConfig:
114
+ """Judge-lane connection from env, with optional constructor overrides."""
115
+ conn = cls.from_env()
116
+ if not overrides:
117
+ return conn
118
+ return cls(
119
+ base_url=overrides.get("base_url", conn._base_url or None),
120
+ path=overrides.get("path", conn._path),
121
+ url=overrides.get("url", conn._url),
122
+ api_key=overrides.get("api_key", conn._api_key),
123
+ model=overrides.get("model", conn._model),
124
+ timeout=float(overrides.get("timeout", conn._timeout)),
125
+ header_name=overrides.get("header_name", conn._header_name),
126
+ header_value_template=overrides.get(
127
+ "header_value_template", conn._header_value_template
128
+ ),
129
+ )
130
+
131
+ def endpoint_url(self) -> str:
132
+ if self._url:
133
+ return self._url
134
+ if not self._base_url:
135
+ raise ValueError(
136
+ "MECHA_JUDGE_BASE_URL is required when MECHA_JUDGE_URL is unset"
137
+ )
138
+ return f"{self._base_url}{self._path}"
139
+
140
+ def headers(self) -> dict[str, str]:
141
+ if not self._api_key:
142
+ return {}
143
+ value = self._header_value_template.format(api_key=self._api_key)
144
+ return {self._header_name: value}
145
+
146
+ def timeout_seconds(self) -> float:
147
+ return self._timeout
148
+
149
+ def model_id(self) -> str | None:
150
+ return self._model
151
+
152
+
153
+ def connection_as_dict(conn: APIConnectionConfig) -> dict[str, Any]:
154
+ """Debug helper — never log secrets from headers."""
155
+ return {
156
+ "endpoint_url": conn.endpoint_url(),
157
+ "timeout_seconds": conn.timeout_seconds(),
158
+ "model_id": conn.model_id(),
159
+ "header_keys": sorted(conn.headers()),
160
+ }
@@ -0,0 +1 @@
1
+ """CLI package."""