inquirycraft 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.
- inquirycraft-0.6.0/LICENSE +9 -0
- inquirycraft-0.6.0/PKG-INFO +222 -0
- inquirycraft-0.6.0/README.md +189 -0
- inquirycraft-0.6.0/pyproject.toml +70 -0
- inquirycraft-0.6.0/setup.cfg +4 -0
- inquirycraft-0.6.0/src/inquirycraft/__init__.py +76 -0
- inquirycraft-0.6.0/src/inquirycraft/__main__.py +4 -0
- inquirycraft-0.6.0/src/inquirycraft/adapters.py +127 -0
- inquirycraft-0.6.0/src/inquirycraft/agent/__init__.py +5 -0
- inquirycraft-0.6.0/src/inquirycraft/agent/base.py +194 -0
- inquirycraft-0.6.0/src/inquirycraft/cli/__init__.py +13 -0
- inquirycraft-0.6.0/src/inquirycraft/cli/app.py +231 -0
- inquirycraft-0.6.0/src/inquirycraft/cli/contracts.py +46 -0
- inquirycraft-0.6.0/src/inquirycraft/cli/factory.py +33 -0
- inquirycraft-0.6.0/src/inquirycraft/events/__init__.py +25 -0
- inquirycraft-0.6.0/src/inquirycraft/events/models.py +70 -0
- inquirycraft-0.6.0/src/inquirycraft/events/replay.py +307 -0
- inquirycraft-0.6.0/src/inquirycraft/events/sink.py +231 -0
- inquirycraft-0.6.0/src/inquirycraft/executor/__init__.py +15 -0
- inquirycraft-0.6.0/src/inquirycraft/executor/base.py +88 -0
- inquirycraft-0.6.0/src/inquirycraft/executor/dep_utils.py +40 -0
- inquirycraft-0.6.0/src/inquirycraft/executor/output.py +167 -0
- inquirycraft-0.6.0/src/inquirycraft/executor/python.py +129 -0
- inquirycraft-0.6.0/src/inquirycraft/executor/shell.py +65 -0
- inquirycraft-0.6.0/src/inquirycraft/llm/__init__.py +42 -0
- inquirycraft-0.6.0/src/inquirycraft/llm/base.py +288 -0
- inquirycraft-0.6.0/src/inquirycraft/llm/errors.py +16 -0
- inquirycraft-0.6.0/src/inquirycraft/llm/factory.py +95 -0
- inquirycraft-0.6.0/src/inquirycraft/llm/logprob_stream.py +160 -0
- inquirycraft-0.6.0/src/inquirycraft/llm/message_format.py +81 -0
- inquirycraft-0.6.0/src/inquirycraft/llm/models.py +42 -0
- inquirycraft-0.6.0/src/inquirycraft/llm/online.py +71 -0
- inquirycraft-0.6.0/src/inquirycraft/llm/online_support.py +99 -0
- inquirycraft-0.6.0/src/inquirycraft/llm/openai_adapter.py +109 -0
- inquirycraft-0.6.0/src/inquirycraft/llm/pool.py +142 -0
- inquirycraft-0.6.0/src/inquirycraft/llm/pooled.py +344 -0
- inquirycraft-0.6.0/src/inquirycraft/llm/protocol.py +15 -0
- inquirycraft-0.6.0/src/inquirycraft/llm/public_requests.py +123 -0
- inquirycraft-0.6.0/src/inquirycraft/llm/request_client.py +230 -0
- inquirycraft-0.6.0/src/inquirycraft/llm/retry.py +50 -0
- inquirycraft-0.6.0/src/inquirycraft/llm/stream_types.py +123 -0
- inquirycraft-0.6.0/src/inquirycraft/llm/text_stream.py +154 -0
- inquirycraft-0.6.0/src/inquirycraft/llm/tool_args.py +134 -0
- inquirycraft-0.6.0/src/inquirycraft/llm/tool_requests.py +194 -0
- inquirycraft-0.6.0/src/inquirycraft/llm/tool_stream_result.py +90 -0
- inquirycraft-0.6.0/src/inquirycraft/llm/tool_stream_state.py +187 -0
- inquirycraft-0.6.0/src/inquirycraft/llm/usage.py +55 -0
- inquirycraft-0.6.0/src/inquirycraft/llm/utils.py +288 -0
- inquirycraft-0.6.0/src/inquirycraft/mcp/__init__.py +8 -0
- inquirycraft-0.6.0/src/inquirycraft/mcp/client.py +118 -0
- inquirycraft-0.6.0/src/inquirycraft/memory/__init__.py +83 -0
- inquirycraft-0.6.0/src/inquirycraft/memory/context.py +63 -0
- inquirycraft-0.6.0/src/inquirycraft/memory/conversation.py +83 -0
- inquirycraft-0.6.0/src/inquirycraft/memory/history.py +184 -0
- inquirycraft-0.6.0/src/inquirycraft/memory/kv_storage.py +69 -0
- inquirycraft-0.6.0/src/inquirycraft/memory/legacy_records.py +27 -0
- inquirycraft-0.6.0/src/inquirycraft/memory/message.py +260 -0
- inquirycraft-0.6.0/src/inquirycraft/memory/mutation.py +67 -0
- inquirycraft-0.6.0/src/inquirycraft/memory/paths.py +19 -0
- inquirycraft-0.6.0/src/inquirycraft/memory/record_store.py +218 -0
- inquirycraft-0.6.0/src/inquirycraft/memory/records.py +64 -0
- inquirycraft-0.6.0/src/inquirycraft/runtime/__init__.py +42 -0
- inquirycraft-0.6.0/src/inquirycraft/runtime/cancellation.py +24 -0
- inquirycraft-0.6.0/src/inquirycraft/runtime/engine.py +313 -0
- inquirycraft-0.6.0/src/inquirycraft/runtime/hooks.py +97 -0
- inquirycraft-0.6.0/src/inquirycraft/runtime/hosted.py +114 -0
- inquirycraft-0.6.0/src/inquirycraft/runtime/resume.py +172 -0
- inquirycraft-0.6.0/src/inquirycraft/runtime/state.py +15 -0
- inquirycraft-0.6.0/src/inquirycraft/runtime/streaming.py +95 -0
- inquirycraft-0.6.0/src/inquirycraft/runtime/subprocess.py +298 -0
- inquirycraft-0.6.0/src/inquirycraft/runtime/subprocess_sync.py +122 -0
- inquirycraft-0.6.0/src/inquirycraft/runtime/telemetry.py +87 -0
- inquirycraft-0.6.0/src/inquirycraft/tools/__init__.py +136 -0
- inquirycraft-0.6.0/src/inquirycraft/tools/artifacts/__init__.py +11 -0
- inquirycraft-0.6.0/src/inquirycraft/tools/artifacts/reducers.py +41 -0
- inquirycraft-0.6.0/src/inquirycraft/tools/artifacts/store.py +146 -0
- inquirycraft-0.6.0/src/inquirycraft/tools/base.py +188 -0
- inquirycraft-0.6.0/src/inquirycraft/tools/builtin.py +101 -0
- inquirycraft-0.6.0/src/inquirycraft/tools/collection.py +100 -0
- inquirycraft-0.6.0/src/inquirycraft/tools/dispatch.py +75 -0
- inquirycraft-0.6.0/src/inquirycraft/tools/execution/__init__.py +7 -0
- inquirycraft-0.6.0/src/inquirycraft/tools/execution/engine.py +105 -0
- inquirycraft-0.6.0/src/inquirycraft/tools/execution/middleware.py +30 -0
- inquirycraft-0.6.0/src/inquirycraft/tools/execution/models.py +19 -0
- inquirycraft-0.6.0/src/inquirycraft/tools/executor.py +54 -0
- inquirycraft-0.6.0/src/inquirycraft/tools/file_tools.py +269 -0
- inquirycraft-0.6.0/src/inquirycraft/tools/filesystem/__init__.py +65 -0
- inquirycraft-0.6.0/src/inquirycraft/tools/filesystem/edit.py +263 -0
- inquirycraft-0.6.0/src/inquirycraft/tools/filesystem/modify.py +168 -0
- inquirycraft-0.6.0/src/inquirycraft/tools/filesystem/observe.py +323 -0
- inquirycraft-0.6.0/src/inquirycraft/tools/filesystem/search.py +205 -0
- inquirycraft-0.6.0/src/inquirycraft/tools/guards.py +160 -0
- inquirycraft-0.6.0/src/inquirycraft/tools/path_policy.py +277 -0
- inquirycraft-0.6.0/src/inquirycraft/tools/search_tools.py +190 -0
- inquirycraft-0.6.0/src/inquirycraft/tools/shell_feedback.py +65 -0
- inquirycraft-0.6.0/src/inquirycraft/tools/shell_guards.py +51 -0
- inquirycraft-0.6.0/src/inquirycraft/tools/shell_output.py +109 -0
- inquirycraft-0.6.0/src/inquirycraft/tools/shell_reducer.py +198 -0
- inquirycraft-0.6.0/src/inquirycraft/tools/shell_traceback.py +220 -0
- inquirycraft-0.6.0/src/inquirycraft/tools/tool_calls.py +51 -0
- inquirycraft-0.6.0/src/inquirycraft/tools/write_safety.py +90 -0
- inquirycraft-0.6.0/src/inquirycraft.egg-info/PKG-INFO +222 -0
- inquirycraft-0.6.0/src/inquirycraft.egg-info/SOURCES.txt +105 -0
- inquirycraft-0.6.0/src/inquirycraft.egg-info/dependency_links.txt +1 -0
- inquirycraft-0.6.0/src/inquirycraft.egg-info/entry_points.txt +2 -0
- inquirycraft-0.6.0/src/inquirycraft.egg-info/requires.txt +19 -0
- inquirycraft-0.6.0/src/inquirycraft.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 InquiryCraft Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: inquirycraft
|
|
3
|
+
Version: 0.6.0
|
|
4
|
+
Summary: A lightweight, extensible agent runtime with tools, memory, events, and replay
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Repository, https://github.com/EE-PandaMinG/InquiryCraft
|
|
7
|
+
Project-URL: Issues, https://github.com/EE-PandaMinG/InquiryCraft/issues
|
|
8
|
+
Keywords: agent,llm,runtime,tools,mcp
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
14
|
+
Requires-Python: >=3.11
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: click>=8.1
|
|
18
|
+
Provides-Extra: openai
|
|
19
|
+
Requires-Dist: openai>=1.0; extra == "openai"
|
|
20
|
+
Requires-Dist: httpx>=0.27; extra == "openai"
|
|
21
|
+
Requires-Dist: lazy-loader>=0.4; extra == "openai"
|
|
22
|
+
Requires-Dist: requests>=2.31; extra == "openai"
|
|
23
|
+
Requires-Dist: tenacity>=9.0.0; extra == "openai"
|
|
24
|
+
Provides-Extra: tokens
|
|
25
|
+
Requires-Dist: tiktoken>=0.7; extra == "tokens"
|
|
26
|
+
Provides-Extra: mcp
|
|
27
|
+
Requires-Dist: mcp[cli]<1.27,>=1.3.0; extra == "mcp"
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
|
|
31
|
+
Requires-Dist: ruff>=0.15; extra == "dev"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# InquiryCraft
|
|
35
|
+
|
|
36
|
+
[](https://pypi.org/project/inquirycraft/)
|
|
37
|
+
[](https://pypi.org/project/inquirycraft/)
|
|
38
|
+
[](LICENSE)
|
|
39
|
+
|
|
40
|
+
InquiryCraft is a lightweight, extensible harness for building tool-using LLM agents.
|
|
41
|
+
It provides the execution loop and lifecycle primitives needed by standalone agents,
|
|
42
|
+
interactive CLIs, and larger host applications without imposing a domain workflow.
|
|
43
|
+
|
|
44
|
+
## Why InquiryCraft?
|
|
45
|
+
|
|
46
|
+
- **Small core** — the minimal installation depends only on Click.
|
|
47
|
+
- **Real agent loop** — provider request, tool call, tool result, and continuation are
|
|
48
|
+
handled by a reusable `AgentRuntime`.
|
|
49
|
+
- **Provider-neutral contracts** — applications can implement `LLMClient` or use the
|
|
50
|
+
optional OpenAI-compatible adapter.
|
|
51
|
+
- **Composable tools** — typed schemas, execution middleware, path policy, concurrent
|
|
52
|
+
dispatch, output reduction, and raw artifact retention share one contract.
|
|
53
|
+
- **Durable runs** — append-only sessions, lifecycle events, cancellation, resume, and
|
|
54
|
+
telemetry are built in.
|
|
55
|
+
- **Deterministic validation** — record/replay detects drift in messages, model options,
|
|
56
|
+
tool schemas, call ordering, results, and stream mode.
|
|
57
|
+
- **Optional integrations** — provider SDKs, token counting, and MCP stay outside the
|
|
58
|
+
minimal package.
|
|
59
|
+
|
|
60
|
+
## Install
|
|
61
|
+
|
|
62
|
+
Install the minimal runtime and CLI:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pip install inquirycraft
|
|
66
|
+
inquirycraft tools list
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Install an OpenAI-compatible provider client:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install 'inquirycraft[openai]'
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Other optional extras:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
pip install 'inquirycraft[tokens]'
|
|
79
|
+
pip install 'inquirycraft[mcp]'
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Importing `inquirycraft` resolves public symbols lazily. It does not initialize the CLI
|
|
83
|
+
or load provider SDKs.
|
|
84
|
+
|
|
85
|
+
## Quick start
|
|
86
|
+
|
|
87
|
+
Configure any OpenAI-compatible endpoint:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
export INQUIRYCRAFT_API_KEY=...
|
|
91
|
+
export INQUIRYCRAFT_BASE_URL=http://127.0.0.1:8001/v1
|
|
92
|
+
export INQUIRYCRAFT_MODEL=your-model
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Run a single task with the built-in file and shell tools:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
mkdir -p workspace
|
|
99
|
+
inquirycraft run \
|
|
100
|
+
--workspace ./workspace \
|
|
101
|
+
"Inspect the workspace and write a concise summary to result.txt"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Start an interactive session:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
inquirycraft repl --workspace ./workspace
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Python API
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
import asyncio
|
|
114
|
+
import os
|
|
115
|
+
from pathlib import Path
|
|
116
|
+
|
|
117
|
+
from inquirycraft.llm.openai_adapter import OpenAICompatibleClient
|
|
118
|
+
from inquirycraft.runtime import AgentRuntime, RuntimeOptions
|
|
119
|
+
from inquirycraft.tools import default_tools
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
async def main() -> None:
|
|
123
|
+
client = OpenAICompatibleClient(
|
|
124
|
+
api_key=os.environ["INQUIRYCRAFT_API_KEY"],
|
|
125
|
+
base_url=os.environ.get("INQUIRYCRAFT_BASE_URL"),
|
|
126
|
+
)
|
|
127
|
+
runtime = AgentRuntime(
|
|
128
|
+
llm=client,
|
|
129
|
+
options=RuntimeOptions(
|
|
130
|
+
model=os.environ["INQUIRYCRAFT_MODEL"],
|
|
131
|
+
workspace=Path("./workspace"),
|
|
132
|
+
system_prompt="You are a careful coding agent.",
|
|
133
|
+
),
|
|
134
|
+
tools=default_tools(),
|
|
135
|
+
)
|
|
136
|
+
try:
|
|
137
|
+
print(await runtime.run("Inspect the workspace and summarize its contents."))
|
|
138
|
+
finally:
|
|
139
|
+
await client.aclose()
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
asyncio.run(main())
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Implementing the `LLMClient` protocol is enough to connect another provider. Custom
|
|
146
|
+
tools subclass `BaseTool` and return a `ToolResult`; hooks and event sinks can be added
|
|
147
|
+
without modifying the loop.
|
|
148
|
+
|
|
149
|
+
## Sessions, events, and replay
|
|
150
|
+
|
|
151
|
+
Persist a conversation and its lifecycle events:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
inquirycraft run "continue the task" \
|
|
155
|
+
--session-log .inquirycraft/session.jsonl \
|
|
156
|
+
--event-log .inquirycraft/events.jsonl
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Session logs are append-only. If the file already exists, `run` and `repl` restore its
|
|
160
|
+
messages before the next turn.
|
|
161
|
+
|
|
162
|
+
`RecordingLLMClient` stores the exact provider-visible request and completion result or
|
|
163
|
+
stream chunks. `ReplayLLMClient` consumes that JSONL offline and raises
|
|
164
|
+
`ReplayMismatchError` when model parameters, messages, tool schemas, call ordering, or
|
|
165
|
+
stream mode drift. Generated timestamps and event IDs can be normalized without
|
|
166
|
+
loosening prompts, tool payloads, results, or ordering.
|
|
167
|
+
|
|
168
|
+
## Tools and path policy
|
|
169
|
+
|
|
170
|
+
InquiryCraft includes independent read, write, edit, grep, glob, list, and shell tools.
|
|
171
|
+
Filesystem scope is configured separately from tool behavior:
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
from inquirycraft.tools import PathPolicy, ReadTool
|
|
175
|
+
|
|
176
|
+
policy = PathPolicy("./workspace", enabled=True)
|
|
177
|
+
read = ReadTool(path_policy=policy)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
A strict `PathPolicy` contains model-generated paths under a configured root and any
|
|
181
|
+
explicitly allowed extra roots. Hosts that execute untrusted model output should also
|
|
182
|
+
apply process isolation and least-privilege credentials appropriate to their threat
|
|
183
|
+
model.
|
|
184
|
+
|
|
185
|
+
## CLI reference
|
|
186
|
+
|
|
187
|
+
```text
|
|
188
|
+
inquirycraft run Run one tool-using agent task
|
|
189
|
+
inquirycraft repl Start an interactive multi-turn session
|
|
190
|
+
inquirycraft tools list List built-in tools
|
|
191
|
+
inquirycraft tools schema Print tool schemas
|
|
192
|
+
inquirycraft events show Inspect runtime event JSONL
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Use `inquirycraft COMMAND --help` for command-specific options.
|
|
196
|
+
|
|
197
|
+
## Package layout
|
|
198
|
+
|
|
199
|
+
| Package | Responsibility |
|
|
200
|
+
| --- | --- |
|
|
201
|
+
| `inquirycraft.runtime` | Agent loop, hooks, cancellation, telemetry, and resume |
|
|
202
|
+
| `inquirycraft.llm` | Provider protocols, streaming models, retry, and adapters |
|
|
203
|
+
| `inquirycraft.tools` | Tool contracts, execution, file tools, and path policy |
|
|
204
|
+
| `inquirycraft.memory` | Messages, conversations, and persistent records |
|
|
205
|
+
| `inquirycraft.events` | Lifecycle JSONL and deterministic record/replay |
|
|
206
|
+
| `inquirycraft.executor` | Generic shell and Python execution helpers |
|
|
207
|
+
| `inquirycraft.mcp` | Optional MCP transport integration |
|
|
208
|
+
| `inquirycraft.cli` | Standalone commands and CLI composition contracts |
|
|
209
|
+
|
|
210
|
+
See [ARCHITECTURE.md](ARCHITECTURE.md) for design boundaries and validation layers.
|
|
211
|
+
|
|
212
|
+
## Development
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
uv sync --all-extras
|
|
216
|
+
uv run pytest -q
|
|
217
|
+
uv run ruff check .
|
|
218
|
+
uv run ruff format --check .
|
|
219
|
+
uv build
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
InquiryCraft supports Python 3.11 and newer and is distributed under the MIT License.
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# InquiryCraft
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/inquirycraft/)
|
|
4
|
+
[](https://pypi.org/project/inquirycraft/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
InquiryCraft is a lightweight, extensible harness for building tool-using LLM agents.
|
|
8
|
+
It provides the execution loop and lifecycle primitives needed by standalone agents,
|
|
9
|
+
interactive CLIs, and larger host applications without imposing a domain workflow.
|
|
10
|
+
|
|
11
|
+
## Why InquiryCraft?
|
|
12
|
+
|
|
13
|
+
- **Small core** — the minimal installation depends only on Click.
|
|
14
|
+
- **Real agent loop** — provider request, tool call, tool result, and continuation are
|
|
15
|
+
handled by a reusable `AgentRuntime`.
|
|
16
|
+
- **Provider-neutral contracts** — applications can implement `LLMClient` or use the
|
|
17
|
+
optional OpenAI-compatible adapter.
|
|
18
|
+
- **Composable tools** — typed schemas, execution middleware, path policy, concurrent
|
|
19
|
+
dispatch, output reduction, and raw artifact retention share one contract.
|
|
20
|
+
- **Durable runs** — append-only sessions, lifecycle events, cancellation, resume, and
|
|
21
|
+
telemetry are built in.
|
|
22
|
+
- **Deterministic validation** — record/replay detects drift in messages, model options,
|
|
23
|
+
tool schemas, call ordering, results, and stream mode.
|
|
24
|
+
- **Optional integrations** — provider SDKs, token counting, and MCP stay outside the
|
|
25
|
+
minimal package.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
Install the minimal runtime and CLI:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install inquirycraft
|
|
33
|
+
inquirycraft tools list
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Install an OpenAI-compatible provider client:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install 'inquirycraft[openai]'
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Other optional extras:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install 'inquirycraft[tokens]'
|
|
46
|
+
pip install 'inquirycraft[mcp]'
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Importing `inquirycraft` resolves public symbols lazily. It does not initialize the CLI
|
|
50
|
+
or load provider SDKs.
|
|
51
|
+
|
|
52
|
+
## Quick start
|
|
53
|
+
|
|
54
|
+
Configure any OpenAI-compatible endpoint:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
export INQUIRYCRAFT_API_KEY=...
|
|
58
|
+
export INQUIRYCRAFT_BASE_URL=http://127.0.0.1:8001/v1
|
|
59
|
+
export INQUIRYCRAFT_MODEL=your-model
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Run a single task with the built-in file and shell tools:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
mkdir -p workspace
|
|
66
|
+
inquirycraft run \
|
|
67
|
+
--workspace ./workspace \
|
|
68
|
+
"Inspect the workspace and write a concise summary to result.txt"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Start an interactive session:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
inquirycraft repl --workspace ./workspace
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Python API
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
import asyncio
|
|
81
|
+
import os
|
|
82
|
+
from pathlib import Path
|
|
83
|
+
|
|
84
|
+
from inquirycraft.llm.openai_adapter import OpenAICompatibleClient
|
|
85
|
+
from inquirycraft.runtime import AgentRuntime, RuntimeOptions
|
|
86
|
+
from inquirycraft.tools import default_tools
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
async def main() -> None:
|
|
90
|
+
client = OpenAICompatibleClient(
|
|
91
|
+
api_key=os.environ["INQUIRYCRAFT_API_KEY"],
|
|
92
|
+
base_url=os.environ.get("INQUIRYCRAFT_BASE_URL"),
|
|
93
|
+
)
|
|
94
|
+
runtime = AgentRuntime(
|
|
95
|
+
llm=client,
|
|
96
|
+
options=RuntimeOptions(
|
|
97
|
+
model=os.environ["INQUIRYCRAFT_MODEL"],
|
|
98
|
+
workspace=Path("./workspace"),
|
|
99
|
+
system_prompt="You are a careful coding agent.",
|
|
100
|
+
),
|
|
101
|
+
tools=default_tools(),
|
|
102
|
+
)
|
|
103
|
+
try:
|
|
104
|
+
print(await runtime.run("Inspect the workspace and summarize its contents."))
|
|
105
|
+
finally:
|
|
106
|
+
await client.aclose()
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
asyncio.run(main())
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Implementing the `LLMClient` protocol is enough to connect another provider. Custom
|
|
113
|
+
tools subclass `BaseTool` and return a `ToolResult`; hooks and event sinks can be added
|
|
114
|
+
without modifying the loop.
|
|
115
|
+
|
|
116
|
+
## Sessions, events, and replay
|
|
117
|
+
|
|
118
|
+
Persist a conversation and its lifecycle events:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
inquirycraft run "continue the task" \
|
|
122
|
+
--session-log .inquirycraft/session.jsonl \
|
|
123
|
+
--event-log .inquirycraft/events.jsonl
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Session logs are append-only. If the file already exists, `run` and `repl` restore its
|
|
127
|
+
messages before the next turn.
|
|
128
|
+
|
|
129
|
+
`RecordingLLMClient` stores the exact provider-visible request and completion result or
|
|
130
|
+
stream chunks. `ReplayLLMClient` consumes that JSONL offline and raises
|
|
131
|
+
`ReplayMismatchError` when model parameters, messages, tool schemas, call ordering, or
|
|
132
|
+
stream mode drift. Generated timestamps and event IDs can be normalized without
|
|
133
|
+
loosening prompts, tool payloads, results, or ordering.
|
|
134
|
+
|
|
135
|
+
## Tools and path policy
|
|
136
|
+
|
|
137
|
+
InquiryCraft includes independent read, write, edit, grep, glob, list, and shell tools.
|
|
138
|
+
Filesystem scope is configured separately from tool behavior:
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from inquirycraft.tools import PathPolicy, ReadTool
|
|
142
|
+
|
|
143
|
+
policy = PathPolicy("./workspace", enabled=True)
|
|
144
|
+
read = ReadTool(path_policy=policy)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
A strict `PathPolicy` contains model-generated paths under a configured root and any
|
|
148
|
+
explicitly allowed extra roots. Hosts that execute untrusted model output should also
|
|
149
|
+
apply process isolation and least-privilege credentials appropriate to their threat
|
|
150
|
+
model.
|
|
151
|
+
|
|
152
|
+
## CLI reference
|
|
153
|
+
|
|
154
|
+
```text
|
|
155
|
+
inquirycraft run Run one tool-using agent task
|
|
156
|
+
inquirycraft repl Start an interactive multi-turn session
|
|
157
|
+
inquirycraft tools list List built-in tools
|
|
158
|
+
inquirycraft tools schema Print tool schemas
|
|
159
|
+
inquirycraft events show Inspect runtime event JSONL
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Use `inquirycraft COMMAND --help` for command-specific options.
|
|
163
|
+
|
|
164
|
+
## Package layout
|
|
165
|
+
|
|
166
|
+
| Package | Responsibility |
|
|
167
|
+
| --- | --- |
|
|
168
|
+
| `inquirycraft.runtime` | Agent loop, hooks, cancellation, telemetry, and resume |
|
|
169
|
+
| `inquirycraft.llm` | Provider protocols, streaming models, retry, and adapters |
|
|
170
|
+
| `inquirycraft.tools` | Tool contracts, execution, file tools, and path policy |
|
|
171
|
+
| `inquirycraft.memory` | Messages, conversations, and persistent records |
|
|
172
|
+
| `inquirycraft.events` | Lifecycle JSONL and deterministic record/replay |
|
|
173
|
+
| `inquirycraft.executor` | Generic shell and Python execution helpers |
|
|
174
|
+
| `inquirycraft.mcp` | Optional MCP transport integration |
|
|
175
|
+
| `inquirycraft.cli` | Standalone commands and CLI composition contracts |
|
|
176
|
+
|
|
177
|
+
See [ARCHITECTURE.md](ARCHITECTURE.md) for design boundaries and validation layers.
|
|
178
|
+
|
|
179
|
+
## Development
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
uv sync --all-extras
|
|
183
|
+
uv run pytest -q
|
|
184
|
+
uv run ruff check .
|
|
185
|
+
uv run ruff format --check .
|
|
186
|
+
uv build
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
InquiryCraft supports Python 3.11 and newer and is distributed under the MIT License.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "inquirycraft"
|
|
7
|
+
version = "0.6.0"
|
|
8
|
+
description = "A lightweight, extensible agent runtime with tools, memory, events, and replay"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.11"
|
|
13
|
+
keywords = ["agent", "llm", "runtime", "tools", "mcp"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3.11",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"click>=8.1",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.optional-dependencies]
|
|
26
|
+
openai = [
|
|
27
|
+
"openai>=1.0",
|
|
28
|
+
"httpx>=0.27",
|
|
29
|
+
"lazy-loader>=0.4",
|
|
30
|
+
"requests>=2.31",
|
|
31
|
+
"tenacity>=9.0.0",
|
|
32
|
+
]
|
|
33
|
+
tokens = [
|
|
34
|
+
"tiktoken>=0.7",
|
|
35
|
+
]
|
|
36
|
+
mcp = [
|
|
37
|
+
"mcp[cli]>=1.3.0,<1.27",
|
|
38
|
+
]
|
|
39
|
+
dev = [
|
|
40
|
+
"pytest>=7.0",
|
|
41
|
+
"pytest-asyncio>=0.21",
|
|
42
|
+
"ruff>=0.15",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
[project.scripts]
|
|
46
|
+
inquirycraft = "inquirycraft.cli:main"
|
|
47
|
+
|
|
48
|
+
[project.urls]
|
|
49
|
+
Repository = "https://github.com/EE-PandaMinG/InquiryCraft"
|
|
50
|
+
Issues = "https://github.com/EE-PandaMinG/InquiryCraft/issues"
|
|
51
|
+
|
|
52
|
+
[tool.setuptools]
|
|
53
|
+
package-dir = {"" = "src"}
|
|
54
|
+
include-package-data = false
|
|
55
|
+
|
|
56
|
+
[tool.setuptools.packages.find]
|
|
57
|
+
where = ["src"]
|
|
58
|
+
include = ["inquirycraft*"]
|
|
59
|
+
|
|
60
|
+
[tool.pytest.ini_options]
|
|
61
|
+
asyncio_mode = "auto"
|
|
62
|
+
testpaths = ["tests"]
|
|
63
|
+
|
|
64
|
+
[tool.ruff]
|
|
65
|
+
target-version = "py311"
|
|
66
|
+
line-length = 100
|
|
67
|
+
exclude = ["legacy_packages"]
|
|
68
|
+
|
|
69
|
+
[tool.ruff.lint]
|
|
70
|
+
select = ["E4", "E7", "E9", "F", "I"]
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""InquiryCraft's lightweight, domain-independent agent runtime.
|
|
2
|
+
|
|
3
|
+
Every public symbol is resolved lazily. Importing the package does not initialize
|
|
4
|
+
the runtime, CLI, or provider adapters.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from importlib import import_module
|
|
8
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
__version__ = version("inquirycraft")
|
|
13
|
+
except PackageNotFoundError: # source checkout without an installed distribution
|
|
14
|
+
__version__ = "0+unknown"
|
|
15
|
+
|
|
16
|
+
_EXPORTS = {
|
|
17
|
+
"AgentHooks": ("inquirycraft.runtime", "AgentHooks"),
|
|
18
|
+
"AgentRuntime": ("inquirycraft.runtime", "AgentRuntime"),
|
|
19
|
+
"BaseTool": ("inquirycraft.tools", "BaseTool"),
|
|
20
|
+
"CompletionRequest": ("inquirycraft.llm", "CompletionRequest"),
|
|
21
|
+
"CompletionResult": ("inquirycraft.llm", "CompletionResult"),
|
|
22
|
+
"CompositeEventSink": ("inquirycraft.events", "CompositeEventSink"),
|
|
23
|
+
"Conversation": ("inquirycraft.memory", "Conversation"),
|
|
24
|
+
"EventSink": ("inquirycraft.events", "EventSink"),
|
|
25
|
+
"JsonlEventSink": ("inquirycraft.events", "JsonlEventSink"),
|
|
26
|
+
"LLMClient": ("inquirycraft.llm", "LLMClient"),
|
|
27
|
+
"Message": ("inquirycraft.memory", "Message"),
|
|
28
|
+
"RecordingLLMClient": ("inquirycraft.events", "RecordingLLMClient"),
|
|
29
|
+
"ReplayLLMClient": ("inquirycraft.events", "ReplayLLMClient"),
|
|
30
|
+
"ResumeState": ("inquirycraft.runtime", "ResumeState"),
|
|
31
|
+
"RuntimeContext": ("inquirycraft.runtime", "RuntimeContext"),
|
|
32
|
+
"RuntimeEvent": ("inquirycraft.events", "RuntimeEvent"),
|
|
33
|
+
"RuntimeOptions": ("inquirycraft.runtime", "RuntimeOptions"),
|
|
34
|
+
"ToolCollection": ("inquirycraft.tools", "ToolCollection"),
|
|
35
|
+
"ToolContext": ("inquirycraft.tools", "ToolContext"),
|
|
36
|
+
"ToolResult": ("inquirycraft.tools", "ToolResult"),
|
|
37
|
+
"Usage": ("inquirycraft.llm", "Usage"),
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
"__version__",
|
|
42
|
+
"AgentHooks",
|
|
43
|
+
"AgentRuntime",
|
|
44
|
+
"BaseTool",
|
|
45
|
+
"CompletionRequest",
|
|
46
|
+
"CompletionResult",
|
|
47
|
+
"CompositeEventSink",
|
|
48
|
+
"Conversation",
|
|
49
|
+
"EventSink",
|
|
50
|
+
"JsonlEventSink",
|
|
51
|
+
"LLMClient",
|
|
52
|
+
"Message",
|
|
53
|
+
"RecordingLLMClient",
|
|
54
|
+
"ReplayLLMClient",
|
|
55
|
+
"ResumeState",
|
|
56
|
+
"RuntimeContext",
|
|
57
|
+
"RuntimeEvent",
|
|
58
|
+
"RuntimeOptions",
|
|
59
|
+
"ToolCollection",
|
|
60
|
+
"ToolContext",
|
|
61
|
+
"ToolResult",
|
|
62
|
+
"Usage",
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def __getattr__(name: str) -> Any:
|
|
67
|
+
if name not in _EXPORTS:
|
|
68
|
+
raise AttributeError(name)
|
|
69
|
+
module_name, attribute = _EXPORTS[name]
|
|
70
|
+
value = getattr(import_module(module_name), attribute)
|
|
71
|
+
globals()[name] = value
|
|
72
|
+
return value
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def __dir__() -> list[str]:
|
|
76
|
+
return sorted(set(globals()) | set(__all__))
|