nexusagent-runtime 0.4.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 (55) hide show
  1. nexusagent_runtime-0.4.0/LICENSE +21 -0
  2. nexusagent_runtime-0.4.0/PKG-INFO +192 -0
  3. nexusagent_runtime-0.4.0/README.md +160 -0
  4. nexusagent_runtime-0.4.0/pyproject.toml +78 -0
  5. nexusagent_runtime-0.4.0/setup.cfg +4 -0
  6. nexusagent_runtime-0.4.0/src/nexusagent/__init__.py +15 -0
  7. nexusagent_runtime-0.4.0/src/nexusagent/__main__.py +3 -0
  8. nexusagent_runtime-0.4.0/src/nexusagent/agent.py +175 -0
  9. nexusagent_runtime-0.4.0/src/nexusagent/application.py +11 -0
  10. nexusagent_runtime-0.4.0/src/nexusagent/calculator_tool.py +42 -0
  11. nexusagent_runtime-0.4.0/src/nexusagent/config.py +81 -0
  12. nexusagent_runtime-0.4.0/src/nexusagent/echo_tool.py +11 -0
  13. nexusagent_runtime-0.4.0/src/nexusagent/factory.py +13 -0
  14. nexusagent_runtime-0.4.0/src/nexusagent/http_provider.py +189 -0
  15. nexusagent_runtime-0.4.0/src/nexusagent/logging.py +60 -0
  16. nexusagent_runtime-0.4.0/src/nexusagent/main.py +75 -0
  17. nexusagent_runtime-0.4.0/src/nexusagent/provider.py +101 -0
  18. nexusagent_runtime-0.4.0/src/nexusagent/providers.py +58 -0
  19. nexusagent_runtime-0.4.0/src/nexusagent/py.typed +0 -0
  20. nexusagent_runtime-0.4.0/src/nexusagent/runtime.py +54 -0
  21. nexusagent_runtime-0.4.0/src/nexusagent/runtime_factory.py +12 -0
  22. nexusagent_runtime-0.4.0/src/nexusagent/tool.py +24 -0
  23. nexusagent_runtime-0.4.0/src/nexusagent/tool_executor.py +26 -0
  24. nexusagent_runtime-0.4.0/src/nexusagent/tool_factory.py +14 -0
  25. nexusagent_runtime-0.4.0/src/nexusagent/tool_registry.py +49 -0
  26. nexusagent_runtime-0.4.0/src/nexusagent/uppercase_tool.py +11 -0
  27. nexusagent_runtime-0.4.0/src/nexusagent_runtime.egg-info/PKG-INFO +192 -0
  28. nexusagent_runtime-0.4.0/src/nexusagent_runtime.egg-info/SOURCES.txt +53 -0
  29. nexusagent_runtime-0.4.0/src/nexusagent_runtime.egg-info/dependency_links.txt +1 -0
  30. nexusagent_runtime-0.4.0/src/nexusagent_runtime.egg-info/entry_points.txt +2 -0
  31. nexusagent_runtime-0.4.0/src/nexusagent_runtime.egg-info/requires.txt +8 -0
  32. nexusagent_runtime-0.4.0/src/nexusagent_runtime.egg-info/top_level.txt +1 -0
  33. nexusagent_runtime-0.4.0/tests/test_agent.py +731 -0
  34. nexusagent_runtime-0.4.0/tests/test_application.py +239 -0
  35. nexusagent_runtime-0.4.0/tests/test_architecture.py +321 -0
  36. nexusagent_runtime-0.4.0/tests/test_calculator_tool.py +248 -0
  37. nexusagent_runtime-0.4.0/tests/test_config.py +249 -0
  38. nexusagent_runtime-0.4.0/tests/test_echo_tool.py +81 -0
  39. nexusagent_runtime-0.4.0/tests/test_factory.py +74 -0
  40. nexusagent_runtime-0.4.0/tests/test_http_provider.py +329 -0
  41. nexusagent_runtime-0.4.0/tests/test_http_provider_tools.py +100 -0
  42. nexusagent_runtime-0.4.0/tests/test_http_tool_loop_integration.py +70 -0
  43. nexusagent_runtime-0.4.0/tests/test_init.py +123 -0
  44. nexusagent_runtime-0.4.0/tests/test_logging.py +81 -0
  45. nexusagent_runtime-0.4.0/tests/test_main.py +893 -0
  46. nexusagent_runtime-0.4.0/tests/test_provider.py +231 -0
  47. nexusagent_runtime-0.4.0/tests/test_providers.py +78 -0
  48. nexusagent_runtime-0.4.0/tests/test_runtime.py +648 -0
  49. nexusagent_runtime-0.4.0/tests/test_runtime_factory.py +193 -0
  50. nexusagent_runtime-0.4.0/tests/test_tool.py +166 -0
  51. nexusagent_runtime-0.4.0/tests/test_tool_executor.py +349 -0
  52. nexusagent_runtime-0.4.0/tests/test_tool_factory.py +117 -0
  53. nexusagent_runtime-0.4.0/tests/test_tool_loop.py +289 -0
  54. nexusagent_runtime-0.4.0/tests/test_tool_registry.py +422 -0
  55. nexusagent_runtime-0.4.0/tests/test_uppercase_tool.py +111 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Abolfazl RWM
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,192 @@
1
+ Metadata-Version: 2.4
2
+ Name: nexusagent-runtime
3
+ Version: 0.4.0
4
+ Summary: A small, modular AI agent runtime in Python — agents, providers, tools and a readable core.
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/Abolfazlrwm/nexusagent
7
+ Project-URL: Repository, https://github.com/Abolfazlrwm/nexusagent
8
+ Project-URL: Changelog, https://github.com/Abolfazlrwm/nexusagent/blob/main/CHANGELOG.md
9
+ Project-URL: Issues, https://github.com/Abolfazlrwm/nexusagent/issues
10
+ Keywords: ai-agents,agent-runtime,llm,tools,providers
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest>=8.0; extra == "dev"
26
+ Requires-Dist: pytest-cov>=5.0; extra == "dev"
27
+ Requires-Dist: mypy>=1.10; extra == "dev"
28
+ Requires-Dist: ruff>=0.6; extra == "dev"
29
+ Requires-Dist: pre-commit>=3.7; extra == "dev"
30
+ Requires-Dist: build>=1.2; extra == "dev"
31
+ Dynamic: license-file
32
+
33
+ # NexusAgent
34
+
35
+ [![CI](https://github.com/Abolfazlrwm/nexusagent/actions/workflows/ci.yml/badge.svg)](https://github.com/Abolfazlrwm/nexusagent/actions/workflows/ci.yml)
36
+ [![Coverage](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Abolfazlrwm/nexusagent/gh-pages/coverage-badge.json)](https://github.com/Abolfazlrwm/nexusagent/actions/workflows/ci.yml)
37
+ [![PyPI](https://img.shields.io/pypi/v/nexusagent-runtime)](https://pypi.org/project/nexusagent-runtime/)
38
+ [![Python](https://img.shields.io/pypi/pyversions/nexusagent-runtime)](https://pypi.org/project/nexusagent-runtime/)
39
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
40
+
41
+ **A modular runtime for AI agents in Python — zero runtime dependencies, a small readable core, and an honest feature list.**
42
+
43
+ Most agent frameworks hand you a galaxy of abstractions. NexusAgent is the opposite: ~500 lines of typed, contract-tested core that you can read in one sitting — providers, tools, a bounded autonomous tool loop, and a CLI. Nothing hidden, nothing magic.
44
+
45
+ ```python
46
+ from nexusagent import create_application_runtime
47
+
48
+ runtime = create_application_runtime() # offline `fake` provider by default
49
+
50
+ result = runtime.run("Hello NexusAgent")
51
+ print(result.output) # fake response: Hello NexusAgent
52
+ print(result.success) # True
53
+
54
+ print(runtime.execute_tool("calculator", "2 + 3")) # 5
55
+ ```
56
+
57
+ ## Why NexusAgent
58
+
59
+ - **Zero runtime dependencies** — the whole core is the Python standard library. `pip install nexusagent-runtime` pulls nothing else.
60
+ - **Readable by design** — the core is small enough to audit in an afternoon; architecture rules are enforced by tests, not comments.
61
+ - **Honest** — the feature list below says what exists *and what doesn't*. No fake benchmarks, no aspirational docs.
62
+ - **Typed** — ships `py.typed`; the source itself passes `mypy --strict`.
63
+ - **Tested** — 600+ contract tests, ≥95% coverage floor enforced in CI.
64
+
65
+ ## Features
66
+
67
+ | Capability | Status |
68
+ | --- | --- |
69
+ | Provider abstraction (bring any backend) | ✅ |
70
+ | Autonomous tool loop, bounded by `max_iterations` | ✅ |
71
+ | Vendor-neutral HTTP wire protocol | ✅ [docs/protocol.md](docs/protocol.md) |
72
+ | Built-in tools: `echo`, `uppercase`, `calculator` | ✅ |
73
+ | Custom tools / custom providers via small ABCs | ✅ [guides](docs/) |
74
+ | Configuration via `NEXUS_*` environment variables | ✅ |
75
+ | CLI (`nexusagent`, `python -m nexusagent`) | ✅ |
76
+ | Fully typed (`py.typed`), strict mypy clean | ✅ |
77
+ | Async execution / streaming | ❌ |
78
+ | Memory, planning, multi-agent orchestration | ❌ |
79
+ | Vendor adapters (OpenAI, Anthropic, …) | ❌ |
80
+ | Retries / backoff on HTTP failures | ❌ |
81
+
82
+ ## Architecture
83
+
84
+ ```mermaid
85
+ flowchart LR
86
+ U["your code / CLI"] --> R["Runtime"]
87
+ R --> A["Agent"]
88
+ A -- "generate_response()" --> P["Provider<br/>(fake · scripted · http)"]
89
+ P -- "TextResponse" --> A
90
+ P -- "ToolCallRequest" --> A
91
+ A --> X["ToolExecutor"]
92
+ X --> T["ToolRegistry<br/>echo · uppercase · calculator"]
93
+ X -- "ToolResult" --> A
94
+ ```
95
+
96
+ The Provider answers with text or requests a tool; the Agent runs the tool
97
+ and feeds the result back, repeating until a text answer or the iteration
98
+ limit. Deeper details: [docs/architecture.md](docs/architecture.md) ·
99
+ concepts: [docs/concepts.md](docs/concepts.md) · decisions:
100
+ [docs/adr/](docs/adr/).
101
+
102
+ ## Installation
103
+
104
+ ```bash
105
+ pip install nexusagent-runtime
106
+ ```
107
+
108
+ > The **import name** is `nexusagent`; the **PyPI distribution** is
109
+ > `nexusagent-runtime` — the exact name `nexusagent` is blocked on PyPI by an
110
+ > unrelated placeholder package (`nexus-agent`), see
111
+ > [ADR 0004](docs/adr/0004-pypi-distribution-name.md).
112
+
113
+ Requires Python 3.10–3.13. From source:
114
+
115
+ ```bash
116
+ git clone https://github.com/Abolfazlrwm/nexusagent
117
+ cd nexusagent
118
+ pip install -e ".[dev]"
119
+ python scripts/check.py # lint + format + mypy --strict + tests
120
+ ```
121
+
122
+ ## Usage
123
+
124
+ ### Agent with tools
125
+
126
+ ```python
127
+ from nexusagent import create_application_runtime
128
+
129
+ runtime = create_application_runtime()
130
+
131
+ print(runtime.list_tools())
132
+ # [EchoTool, UppercaseTool, CalculatorTool]
133
+
134
+ # The provider decides when to request a tool; the agent runs the loop.
135
+ result = runtime.run("What is 19 + 23?")
136
+ ```
137
+
138
+ ### The tool loop, end to end, offline
139
+
140
+ ```bash
141
+ python examples/tool_loop_demo.py
142
+ ```
143
+
144
+ Starts a local "model backend" speaking the [HTTP protocol](docs/protocol.md),
145
+ requests the calculator, and turns the result into a final answer — no API
146
+ key, no internet.
147
+
148
+ ### CLI
149
+
150
+ ```bash
151
+ nexusagent "your prompt" # or: python -m nexusagent "your prompt"
152
+ nexusagent tool list
153
+ nexusagent tool run calculator "2 + 3"
154
+ ```
155
+
156
+ ### Configuration
157
+
158
+ | Variable | Meaning | Default |
159
+ | --- | --- | --- |
160
+ | `NEXUS_PROVIDER` | `fake` or `http` | `fake` |
161
+ | `NEXUS_ENDPOINT` | HTTP provider URL | — |
162
+ | `NEXUS_MODEL` | model identifier passed to the provider | — |
163
+ | `NEXUS_API_KEY` | sent as `Bearer` token (never logged, masked in reprs) | — |
164
+ | `NEXUS_TIMEOUT` | HTTP timeout in seconds | `30` |
165
+
166
+ ## Examples
167
+
168
+ All examples run offline, with no API keys:
169
+
170
+ - [`examples/basic.py`](examples/basic.py) — smallest possible run
171
+ - [`examples/custom_tool.py`](examples/custom_tool.py) — define and register your own tool
172
+ - [`examples/tool_loop_demo.py`](examples/tool_loop_demo.py) — the autonomous tool loop over local HTTP
173
+ - [`examples/http_provider_local.py`](examples/http_provider_local.py) — `http` provider against a local backend
174
+
175
+ ## Documentation
176
+
177
+ - [Architecture](docs/architecture.md) — layers, module map, dependency rules
178
+ - [Concepts](docs/concepts.md) — Agent, Provider, Tool, Runtime, Settings
179
+ - [Guides](docs/) — write a custom [provider](docs/guides/custom-provider.md) or [tool](docs/guides/custom-tool.md)
180
+ - [HTTP protocol](docs/protocol.md) — wire format for backends
181
+ - [ADRs](docs/adr/) — architecture decision records
182
+ - [Security policy](SECURITY.md) · [Changelog](CHANGELOG.md) · [Contributing](CONTRIBUTING.md)
183
+
184
+ ## Project status
185
+
186
+ Actively developed, pre-1.0. See [PROJECT_STATUS.md](PROJECT_STATUS.md) for
187
+ a candid snapshot of what works and what the next steps are. Breaking public
188
+ API changes require an ADR and are discussed in the open.
189
+
190
+ ## License
191
+
192
+ [MIT](LICENSE)
@@ -0,0 +1,160 @@
1
+ # NexusAgent
2
+
3
+ [![CI](https://github.com/Abolfazlrwm/nexusagent/actions/workflows/ci.yml/badge.svg)](https://github.com/Abolfazlrwm/nexusagent/actions/workflows/ci.yml)
4
+ [![Coverage](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Abolfazlrwm/nexusagent/gh-pages/coverage-badge.json)](https://github.com/Abolfazlrwm/nexusagent/actions/workflows/ci.yml)
5
+ [![PyPI](https://img.shields.io/pypi/v/nexusagent-runtime)](https://pypi.org/project/nexusagent-runtime/)
6
+ [![Python](https://img.shields.io/pypi/pyversions/nexusagent-runtime)](https://pypi.org/project/nexusagent-runtime/)
7
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
8
+
9
+ **A modular runtime for AI agents in Python — zero runtime dependencies, a small readable core, and an honest feature list.**
10
+
11
+ Most agent frameworks hand you a galaxy of abstractions. NexusAgent is the opposite: ~500 lines of typed, contract-tested core that you can read in one sitting — providers, tools, a bounded autonomous tool loop, and a CLI. Nothing hidden, nothing magic.
12
+
13
+ ```python
14
+ from nexusagent import create_application_runtime
15
+
16
+ runtime = create_application_runtime() # offline `fake` provider by default
17
+
18
+ result = runtime.run("Hello NexusAgent")
19
+ print(result.output) # fake response: Hello NexusAgent
20
+ print(result.success) # True
21
+
22
+ print(runtime.execute_tool("calculator", "2 + 3")) # 5
23
+ ```
24
+
25
+ ## Why NexusAgent
26
+
27
+ - **Zero runtime dependencies** — the whole core is the Python standard library. `pip install nexusagent-runtime` pulls nothing else.
28
+ - **Readable by design** — the core is small enough to audit in an afternoon; architecture rules are enforced by tests, not comments.
29
+ - **Honest** — the feature list below says what exists *and what doesn't*. No fake benchmarks, no aspirational docs.
30
+ - **Typed** — ships `py.typed`; the source itself passes `mypy --strict`.
31
+ - **Tested** — 600+ contract tests, ≥95% coverage floor enforced in CI.
32
+
33
+ ## Features
34
+
35
+ | Capability | Status |
36
+ | --- | --- |
37
+ | Provider abstraction (bring any backend) | ✅ |
38
+ | Autonomous tool loop, bounded by `max_iterations` | ✅ |
39
+ | Vendor-neutral HTTP wire protocol | ✅ [docs/protocol.md](docs/protocol.md) |
40
+ | Built-in tools: `echo`, `uppercase`, `calculator` | ✅ |
41
+ | Custom tools / custom providers via small ABCs | ✅ [guides](docs/) |
42
+ | Configuration via `NEXUS_*` environment variables | ✅ |
43
+ | CLI (`nexusagent`, `python -m nexusagent`) | ✅ |
44
+ | Fully typed (`py.typed`), strict mypy clean | ✅ |
45
+ | Async execution / streaming | ❌ |
46
+ | Memory, planning, multi-agent orchestration | ❌ |
47
+ | Vendor adapters (OpenAI, Anthropic, …) | ❌ |
48
+ | Retries / backoff on HTTP failures | ❌ |
49
+
50
+ ## Architecture
51
+
52
+ ```mermaid
53
+ flowchart LR
54
+ U["your code / CLI"] --> R["Runtime"]
55
+ R --> A["Agent"]
56
+ A -- "generate_response()" --> P["Provider<br/>(fake · scripted · http)"]
57
+ P -- "TextResponse" --> A
58
+ P -- "ToolCallRequest" --> A
59
+ A --> X["ToolExecutor"]
60
+ X --> T["ToolRegistry<br/>echo · uppercase · calculator"]
61
+ X -- "ToolResult" --> A
62
+ ```
63
+
64
+ The Provider answers with text or requests a tool; the Agent runs the tool
65
+ and feeds the result back, repeating until a text answer or the iteration
66
+ limit. Deeper details: [docs/architecture.md](docs/architecture.md) ·
67
+ concepts: [docs/concepts.md](docs/concepts.md) · decisions:
68
+ [docs/adr/](docs/adr/).
69
+
70
+ ## Installation
71
+
72
+ ```bash
73
+ pip install nexusagent-runtime
74
+ ```
75
+
76
+ > The **import name** is `nexusagent`; the **PyPI distribution** is
77
+ > `nexusagent-runtime` — the exact name `nexusagent` is blocked on PyPI by an
78
+ > unrelated placeholder package (`nexus-agent`), see
79
+ > [ADR 0004](docs/adr/0004-pypi-distribution-name.md).
80
+
81
+ Requires Python 3.10–3.13. From source:
82
+
83
+ ```bash
84
+ git clone https://github.com/Abolfazlrwm/nexusagent
85
+ cd nexusagent
86
+ pip install -e ".[dev]"
87
+ python scripts/check.py # lint + format + mypy --strict + tests
88
+ ```
89
+
90
+ ## Usage
91
+
92
+ ### Agent with tools
93
+
94
+ ```python
95
+ from nexusagent import create_application_runtime
96
+
97
+ runtime = create_application_runtime()
98
+
99
+ print(runtime.list_tools())
100
+ # [EchoTool, UppercaseTool, CalculatorTool]
101
+
102
+ # The provider decides when to request a tool; the agent runs the loop.
103
+ result = runtime.run("What is 19 + 23?")
104
+ ```
105
+
106
+ ### The tool loop, end to end, offline
107
+
108
+ ```bash
109
+ python examples/tool_loop_demo.py
110
+ ```
111
+
112
+ Starts a local "model backend" speaking the [HTTP protocol](docs/protocol.md),
113
+ requests the calculator, and turns the result into a final answer — no API
114
+ key, no internet.
115
+
116
+ ### CLI
117
+
118
+ ```bash
119
+ nexusagent "your prompt" # or: python -m nexusagent "your prompt"
120
+ nexusagent tool list
121
+ nexusagent tool run calculator "2 + 3"
122
+ ```
123
+
124
+ ### Configuration
125
+
126
+ | Variable | Meaning | Default |
127
+ | --- | --- | --- |
128
+ | `NEXUS_PROVIDER` | `fake` or `http` | `fake` |
129
+ | `NEXUS_ENDPOINT` | HTTP provider URL | — |
130
+ | `NEXUS_MODEL` | model identifier passed to the provider | — |
131
+ | `NEXUS_API_KEY` | sent as `Bearer` token (never logged, masked in reprs) | — |
132
+ | `NEXUS_TIMEOUT` | HTTP timeout in seconds | `30` |
133
+
134
+ ## Examples
135
+
136
+ All examples run offline, with no API keys:
137
+
138
+ - [`examples/basic.py`](examples/basic.py) — smallest possible run
139
+ - [`examples/custom_tool.py`](examples/custom_tool.py) — define and register your own tool
140
+ - [`examples/tool_loop_demo.py`](examples/tool_loop_demo.py) — the autonomous tool loop over local HTTP
141
+ - [`examples/http_provider_local.py`](examples/http_provider_local.py) — `http` provider against a local backend
142
+
143
+ ## Documentation
144
+
145
+ - [Architecture](docs/architecture.md) — layers, module map, dependency rules
146
+ - [Concepts](docs/concepts.md) — Agent, Provider, Tool, Runtime, Settings
147
+ - [Guides](docs/) — write a custom [provider](docs/guides/custom-provider.md) or [tool](docs/guides/custom-tool.md)
148
+ - [HTTP protocol](docs/protocol.md) — wire format for backends
149
+ - [ADRs](docs/adr/) — architecture decision records
150
+ - [Security policy](SECURITY.md) · [Changelog](CHANGELOG.md) · [Contributing](CONTRIBUTING.md)
151
+
152
+ ## Project status
153
+
154
+ Actively developed, pre-1.0. See [PROJECT_STATUS.md](PROJECT_STATUS.md) for
155
+ a candid snapshot of what works and what the next steps are. Breaking public
156
+ API changes require an ADR and are discussed in the open.
157
+
158
+ ## License
159
+
160
+ [MIT](LICENSE)
@@ -0,0 +1,78 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "nexusagent-runtime"
7
+ description = "A small, modular AI agent runtime in Python — agents, providers, tools and a readable core."
8
+ readme = "README.md"
9
+ license = "MIT"
10
+ license-files = ["LICENSE"]
11
+ requires-python = ">=3.10"
12
+ dependencies = []
13
+ dynamic = ["version"]
14
+ keywords = ["ai-agents", "agent-runtime", "llm", "tools", "providers"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: Developers",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Topic :: Software Development :: Libraries :: Python Modules",
25
+ "Typing :: Typed",
26
+ ]
27
+
28
+ [project.scripts]
29
+ nexusagent = "nexusagent.main:main"
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/Abolfazlrwm/nexusagent"
33
+ Repository = "https://github.com/Abolfazlrwm/nexusagent"
34
+ Changelog = "https://github.com/Abolfazlrwm/nexusagent/blob/main/CHANGELOG.md"
35
+ Issues = "https://github.com/Abolfazlrwm/nexusagent/issues"
36
+
37
+ [project.optional-dependencies]
38
+ dev = [
39
+ "pytest>=8.0",
40
+ "pytest-cov>=5.0",
41
+ "mypy>=1.10",
42
+ "ruff>=0.6",
43
+ "pre-commit>=3.7",
44
+ "build>=1.2",
45
+ ]
46
+
47
+ [tool.setuptools.packages.find]
48
+ where = ["src"]
49
+
50
+ [tool.setuptools.package-data]
51
+ nexusagent = ["py.typed"]
52
+
53
+ [tool.setuptools.dynamic]
54
+ version = { attr = "nexusagent.__version__" }
55
+
56
+ [tool.pytest.ini_options]
57
+ testpaths = ["tests"]
58
+ addopts = "--cov=nexusagent --cov-report=term-missing"
59
+
60
+ [tool.coverage.report]
61
+ fail_under = 95
62
+ show_missing = true
63
+
64
+ [tool.mypy]
65
+ strict = true
66
+ files = ["src"]
67
+ python_version = "3.10"
68
+ warn_unused_ignores = true
69
+
70
+ [tool.ruff]
71
+ line-length = 100
72
+ src = ["src", "tests"]
73
+
74
+ [tool.ruff.lint]
75
+ select = ["E", "F", "W", "I", "UP", "B", "BLE", "C4", "SIM", "RUF"]
76
+
77
+ [tool.ruff.lint.isort]
78
+ known-first-party = ["nexusagent"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,15 @@
1
+ from nexusagent.agent import Agent, AgentResult
2
+ from nexusagent.application import create_application_runtime
3
+ from nexusagent.config import Settings
4
+ from nexusagent.runtime import Runtime, create_runtime
5
+
6
+ __version__ = "0.4.0"
7
+
8
+ __all__ = [
9
+ "Agent",
10
+ "AgentResult",
11
+ "Runtime",
12
+ "Settings",
13
+ "create_application_runtime",
14
+ "create_runtime",
15
+ ]
@@ -0,0 +1,3 @@
1
+ from nexusagent.main import main
2
+
3
+ main()
@@ -0,0 +1,175 @@
1
+ """Agent Core for NexusAgent.
2
+
3
+ Defines the contract for running an agent: input text in, an AgentResult out.
4
+ The Agent delegates generation to a Provider. When it is given a ToolRegistry
5
+ and a ToolExecutor it also runs the autonomous tool loop:
6
+
7
+ provider responds -> if it asks for a tool, run it -> feed the result back
8
+ -> provider responds again -> ... until it answers in plain text
9
+
10
+ The loop is bounded by ``max_iterations`` so a misbehaving backend can never
11
+ keep the agent running forever. Explicit ``execute_tool()`` remains available.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from dataclasses import dataclass
17
+
18
+ from nexusagent.logging import get_logger
19
+ from nexusagent.provider import (
20
+ Provider,
21
+ ProviderResponse,
22
+ TextResponse,
23
+ ToolCallRequest,
24
+ ToolResult,
25
+ ToolSpec,
26
+ )
27
+ from nexusagent.tool_executor import ToolExecutionError, ToolExecutor
28
+ from nexusagent.tool_registry import ToolRegistry
29
+
30
+ DEFAULT_MAX_ITERATIONS = 10
31
+
32
+ _logger = get_logger("nexusagent.agent")
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class AgentResult:
37
+ """The result of a single agent run."""
38
+
39
+ output: str
40
+ success: bool
41
+
42
+
43
+ class Agent:
44
+ def __init__(
45
+ self,
46
+ provider: Provider,
47
+ tool_registry: ToolRegistry | None = None,
48
+ tool_executor: ToolExecutor | None = None,
49
+ max_iterations: int = DEFAULT_MAX_ITERATIONS,
50
+ ) -> None:
51
+ if isinstance(max_iterations, bool) or not isinstance(max_iterations, int):
52
+ raise TypeError("max_iterations must be an integer")
53
+
54
+ if max_iterations < 1:
55
+ raise ValueError("max_iterations must be at least 1")
56
+
57
+ self.provider = provider
58
+ self.tool_registry = tool_registry
59
+ self.tool_executor = tool_executor
60
+ self.max_iterations = max_iterations
61
+
62
+ def run(self, input_text: str) -> AgentResult:
63
+ """Run the agent on the given input and return an AgentResult.
64
+
65
+ If the Provider requests tools and this Agent has a ToolRegistry and a
66
+ ToolExecutor, the requested tools are executed and their results are
67
+ fed back to the Provider until it returns a plain text answer or
68
+ ``max_iterations`` provider calls have been made.
69
+ """
70
+ if not isinstance(input_text, str):
71
+ raise TypeError("input_text must be a string")
72
+
73
+ if not input_text.strip():
74
+ raise ValueError("input_text must not be empty or whitespace-only")
75
+
76
+ can_use_tools = self.tool_registry is not None and self.tool_executor is not None
77
+ tools = self._tool_specs() if can_use_tools else ()
78
+ tool_results: list[ToolResult] = []
79
+
80
+ for iteration in range(1, self.max_iterations + 1):
81
+ try:
82
+ response = self._ask_provider(input_text, tools, tuple(tool_results))
83
+ except Exception as exc: # noqa: BLE001
84
+ return AgentResult(output=f"provider error: {exc}", success=False)
85
+
86
+ if isinstance(response, TextResponse):
87
+ _logger.debug("iteration %d: provider answered with text", iteration)
88
+ return AgentResult(output=response.text, success=True)
89
+
90
+ if not isinstance(response, ToolCallRequest):
91
+ return AgentResult(
92
+ output=f"provider error: unexpected response type {type(response).__name__}",
93
+ success=False,
94
+ )
95
+
96
+ if not can_use_tools:
97
+ return AgentResult(
98
+ output=(
99
+ f"provider requested tool {response.tool_name!r} "
100
+ "but the agent has no tools configured"
101
+ ),
102
+ success=False,
103
+ )
104
+
105
+ _logger.info("iteration %d: provider requested tool %r", iteration, response.tool_name)
106
+ tool_results.append(self._run_tool_call(response))
107
+
108
+ return AgentResult(
109
+ output=f"agent stopped: exceeded the maximum of {self.max_iterations} iterations",
110
+ success=False,
111
+ )
112
+
113
+ def execute_tool(self, tool_name: str, input_data: str) -> str:
114
+ """Execute a specific, explicitly named tool and return its output."""
115
+ if self.tool_registry is None:
116
+ raise RuntimeError("Agent requires a ToolRegistry to execute tools")
117
+
118
+ if self.tool_executor is None:
119
+ raise RuntimeError("Agent requires a ToolExecutor to execute tools")
120
+
121
+ tool = self.tool_registry.get(tool_name)
122
+ return self.tool_executor.execute(tool, input_data)
123
+
124
+ def has_tool(self, tool_name: str) -> bool:
125
+ """Return whether a tool is currently available through the Agent."""
126
+ if self.tool_registry is None:
127
+ raise RuntimeError("Agent requires a ToolRegistry to check tool availability")
128
+
129
+ return self.tool_registry.has(tool_name)
130
+
131
+ def _ask_provider(
132
+ self,
133
+ input_text: str,
134
+ tools: tuple[ToolSpec, ...],
135
+ tool_results: tuple[ToolResult, ...],
136
+ ) -> ProviderResponse:
137
+ """Ask the provider for its next response.
138
+
139
+ Any object with a ``generate(prompt) -> str`` method is accepted as a
140
+ provider; only ones that also define ``generate_response`` can request
141
+ tools.
142
+ """
143
+ generate_response = getattr(self.provider, "generate_response", None)
144
+ if generate_response is None:
145
+ return TextResponse(self.provider.generate(input_text))
146
+
147
+ response: ProviderResponse = generate_response(
148
+ input_text, tools=tools, tool_results=tool_results
149
+ )
150
+ return response
151
+
152
+ def _tool_specs(self) -> tuple[ToolSpec, ...]:
153
+ assert self.tool_registry is not None
154
+ return tuple(ToolSpec(t.name, t.description) for t in self.tool_registry.list_tools())
155
+
156
+ def _run_tool_call(self, request: ToolCallRequest) -> ToolResult:
157
+ """Run one requested tool. Failures become error results, never crashes.
158
+
159
+ The Provider (usually a model) is told what went wrong so it can retry
160
+ with better input, pick another tool, or explain the problem.
161
+ """
162
+ try:
163
+ output = self.execute_tool(request.tool_name, request.tool_input)
164
+ except KeyError:
165
+ return self._error_result(request, f"Tool {request.tool_name!r} is not registered")
166
+ except (TypeError, ValueError, ToolExecutionError) as exc:
167
+ return self._error_result(request, str(exc))
168
+
169
+ _logger.debug("tool %r succeeded", request.tool_name)
170
+ return ToolResult(request.tool_name, request.tool_input, output)
171
+
172
+ @staticmethod
173
+ def _error_result(request: ToolCallRequest, message: str) -> ToolResult:
174
+ _logger.warning("tool %r failed: %s", request.tool_name, message)
175
+ return ToolResult(request.tool_name, request.tool_input, message, is_error=True)
@@ -0,0 +1,11 @@
1
+ from __future__ import annotations
2
+
3
+ from nexusagent.config import Settings
4
+ from nexusagent.runtime import Runtime
5
+ from nexusagent.runtime_factory import create_tool_runtime
6
+
7
+ __all__ = ["Runtime", "create_application_runtime"]
8
+
9
+
10
+ def create_application_runtime(settings: Settings | None = None) -> Runtime:
11
+ return create_tool_runtime(settings)