Metadata-Version: 2.4
Name: inquirycraft
Version: 0.9.7
Summary: A lightweight, extensible agent runtime with tools, memory, events, and replay
License-Expression: MIT
Project-URL: Repository, https://github.com/EE-PandaMinG/InquiryCraft
Project-URL: Issues, https://github.com/EE-PandaMinG/InquiryCraft/issues
Keywords: agent,llm,runtime,tools,mcp
Classifier: Development Status :: 4 - Beta
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Requires-Python: <3.14,>=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: click>=8.1
Provides-Extra: openai
Requires-Dist: openai>=1.0; extra == "openai"
Requires-Dist: httpx>=0.27; extra == "openai"
Requires-Dist: requests>=2.31; extra == "openai"
Requires-Dist: tenacity>=9.0.0; extra == "openai"
Provides-Extra: tokens
Requires-Dist: tiktoken>=0.7; extra == "tokens"
Provides-Extra: mcp
Requires-Dist: mcp[cli]<1.27,>=1.3.0; extra == "mcp"
Provides-Extra: tui
Requires-Dist: textual<9,>=8.2.0; extra == "tui"
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
Requires-Dist: ruff>=0.15; extra == "dev"
Requires-Dist: textual<9,>=8.2.0; extra == "dev"
Dynamic: license-file

# InquiryCraft

[![PyPI](https://img.shields.io/pypi/v/inquirycraft.svg)](https://pypi.org/project/inquirycraft/)
[![Python](https://img.shields.io/pypi/pyversions/inquirycraft.svg)](https://pypi.org/project/inquirycraft/)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)

InquiryCraft, shortened to **iqcraft** in project discussions and documentation, is a
lightweight, extensible harness for building tool-using LLM agents. It provides the
execution loop and lifecycle primitives needed by standalone agents, interactive CLIs,
and larger host applications without imposing a domain workflow. The published package,
Python import, and CLI command all remain `inquirycraft`; `iqcraft` is a short name, not a
second compatibility entry point.

## Release status

The current release is **0.9.7**, published on
[PyPI](https://pypi.org/project/inquirycraft/). InquiryCraft is currently beta software
and supports Python 3.11–3.13. Release tags use `vMAJOR.MINOR.PATCH`; the `main`
branch may contain changes intended for the next release.

Version **0.9.7** restores folded TUI history, clarifies background shell progress,
keeps detached shell completions in chat, and adds explicit model proxy routing.

Version **0.9.6** adds bounded recovery for interrupted model streams, improves
long-chat continuation and context reporting, and lets TUI shell commands continue
in the background with compact controls and explicit completion results.

Version **0.9.4** hardens truncated-response handling and stream cleanup, preserves
per-request usage and pricing routes, and improves TUI history and scrollbars.

Version **0.9.1** adds transactional chat compaction, incremental operation
projections, and restored tool-state/history fixes. It reads legacy operation logs;
new logs require this release or newer.

For a reproducible installation, pin the published version:

```bash
pip install 'inquirycraft==0.9.7'
```

## Why InquiryCraft?

- **Small core** — the minimal installation depends only on Click.
- **Real agent loop** — provider request, tool call, tool result, and continuation are
  handled by a reusable `AgentRuntime`.
- **Provider-neutral contracts** — applications can implement `LLMClient` or use the
  optional OpenAI-compatible adapter.
- **Composable tools** — typed schemas, execution middleware, path policy, concurrent
  dispatch, output reduction, and raw artifact retention share one contract.
- **Durable runs** — append-only sessions, lifecycle events, cancellation, resume, and
  telemetry are built in.
- **Deterministic validation** — record/replay detects drift in messages, model options,
  tool schemas, call ordering, results, and stream mode.
- **Optional integrations** — provider SDKs, token counting, and MCP stay outside the
  minimal package.

## Install

Install the minimal runtime and CLI:

```bash
pip install inquirycraft
inquirycraft tools list
```

Install an OpenAI-compatible provider client:

```bash
pip install 'inquirycraft[openai]'
```

Other optional extras:

```bash
pip install 'inquirycraft[tokens]'
pip install 'inquirycraft[mcp]'
pip install 'inquirycraft[tui]'
```

Importing `inquirycraft` resolves public symbols lazily. It does not initialize the CLI
or load provider SDKs.

## Quick start

Create the private configuration file, then edit
`~/.config/inquirycraft/models.json`:

```bash
inquirycraft config init
inquirycraft config path
```

```json
{
  "version": 1,
  "models": {
    "model-a": {
      "model": "model-a",
      "use_proxy": false,
      "reasoning_replay": "preserve",
      "pricing": {
        "input_usd_per_1m": 0.15,
        "cached_input_usd_per_1m": 0.003,
        "output_usd_per_1m": 0.60
      },
      "endpoints": {
        "code": [{"url": "https://a.example/v1", "key": "key-a"}],
        "feedback": [{"url": "https://b.example/v1", "key": "key-b"}]
      }
    }
  },
  "defaults": {
    "code_models": ["model-a"],
    "feedback_models": ["model-a"],
    "selection": "auto"
  }
}
```

Each clean model alias may contain shared endpoints or role-specific endpoint lists.
Optional `use_proxy` enables (`true`) or disables (`false`) environment proxies for
that alias's endpoints; `true` forces the proxy even if `NO_PROXY` matches, and
leaving it out defaults to `false` (direct connection).
Optional `pricing` rates are USD per one million tokens; all three fields must be
provided together. The registry is the only pricing source used by the chat monitor.
Optional `reasoning_replay` controls how stored assistant reasoning is projected into
later provider requests: `preserve` (the default) keeps real reasoning, `required`
fills missing historical reasoning with a stable compatibility placeholder, and
`omit` leaves reasoning out of the request. All modes keep canonical memory and JSONL
records unchanged.
CLI, REPL, and TUI use the same
retry, cooldown, and failover pool. A call selects one model first; failover stays
within that model's endpoints. Transient endpoint failures get three attempts with
delay before the pool advances. Use `--models alias-a,alias-b` to select a subset,
or `--model-config /path/models.json` to use another registry.
Embedding hosts can switch the active chat provider pool at runtime without clearing the
conversation. Hosts remain responsible for their own role-specific research configuration.

Run a single task with the built-in file and shell tools:

```bash
mkdir -p workspace
inquirycraft run \
  --workspace ./workspace \
  "Inspect the workspace and write a concise summary to result.txt"
```

Start an interactive session (saved automatically under the workspace):

```bash
inquirycraft repl --workspace ./workspace
```

Start the full-screen InquiryCraft TUI:

```bash
pip install 'inquirycraft[openai,tui]'
inquirycraft tui --workspace ./workspace
```

The TUI uses InquiryCraft's own `IQ · InquiryCraft` terminal wordmark and projects the
same runtime event stream used by embedded hosts. It includes a scrollable transcript,
streaming assistant and reasoning output, tool status cards, pending-message state,
token/model/workspace footer data, and a multiline editor.

Submit with `Enter` (or `Ctrl+S`). Use `Alt+Enter` for a new line;
`Shift+Enter` also works in terminals that distinguish it from Enter. While a run is busy, an ordinary submission is
queued as steering instead of starting a concurrent run. Use `/follow-up TEXT`,
`/next TEXT`, `/cancel`, `/status`, `/clear`, or `/help`; `Ctrl+R` toggles
reasoning, `Ctrl+O` toggles tool details, `Esc` cancels the current interaction,
and pressing `Ctrl+C` twice within two seconds exits (`Ctrl+Q` exits directly).
When a host task list is visible, focus it with Tab, use Up/Down to select a task
or the more-tasks row, and press Enter to open it.
With selected editor or transcript text, `Ctrl+C` copies instead; terminal-managed
`Ctrl+Shift+C/V` can be used for system clipboard copy/paste.
Exiting closes ordinary chat resources; lifecycle
of detached background tasks remains owned by the embedding host. The existing `repl`
command remains available for plain terminals and scripts.

Embedding hosts may use the optional `begin_transcript_section` and
`fold_transcript_section` methods to collapse a preparation exchange into a
clickable summary. Original entries and their nested tool display state remain
available; folding does not change the conversation or its JSONL records.

Completed chat exchanges collapse to two numbered rows (`>` user, `-` assistant) when the
next chat begins. Click or press Enter to restore the original widgets, including
their tool and thinking state. Four recent history summaries stay visible; older
rounds share an expandable History entry. The current exchange stays open.
The footer keeps context/cache/cost values, with only Cache labeled. Resume reconstructs numbering from stored user messages;
hosts may optionally classify their preparation prompts with `is_host_task_prompt`.

Chat messages omit user/assistant headings and decorative side borders;
user text has a display-only `>` prefix and a transparent background.
host notices keep their labels. Tool groups are labeled `TOOLS`.
Hosts may enable `fold_system_notices`: old notices, including query results and
errors, collect in History on subsequent interaction. Active task questions stay
visible until their section closes. This changes only the UI.
Use your terminal's native text selection and copy/paste; Markdown code blocks
have no Copy button.

Applications embedding the InquiryCraft Click group can call
`create_cli(include_tui=False)` when their dependency policy intentionally excludes Textual.
The standalone `inquirycraft` command keeps TUI registration enabled by default.

## Python API

```python
import asyncio
from pathlib import Path

from inquirycraft.adapters import AskLLMAdapter
from inquirycraft.llm import LLMFactoryConfig, ModelRegistry, build_llm
from inquirycraft.runtime import AgentRuntime, RuntimeOptions
from inquirycraft.tools import default_tools


async def main() -> None:
    registry = ModelRegistry.from_json(Path.home() / ".config/inquirycraft/models.json")
    aliases = registry.default_aliases("code_models")
    client = AskLLMAdapter(build_llm(LLMFactoryConfig(
        model_registry=registry,
        model_aliases=aliases,
    )))
    runtime = AgentRuntime(
        llm=client,
        options=RuntimeOptions(
            model=registry.models[aliases[0]].model,
            workspace=Path("./workspace"),
            system_prompt="You are a careful coding agent.",
        ),
        tools=default_tools(),
    )
    try:
        print(await runtime.run("Inspect the workspace and summarize its contents."))
    finally:
        await client.aclose()


asyncio.run(main())
```

Implementing the `LLMClient` protocol is enough to connect another provider. Custom
tools subclass `BaseTool` and return a `ToolResult`; hooks and event sinks can be added
without modifying the loop.

### Streaming, recovery, and runtime control

The compatibility `run() -> str` API and the event-stream API use the same agent loop.
Enable provider streaming separately, and opt into the operation journal when a run must
be recoverable across process restarts:

```python
options = RuntimeOptions(
    model="your-model",
    workspace=Path("./workspace"),
    stream_llm=True,
    tool_execution="parallel",
    operation_log=Path(".inquirycraft/operations.jsonl"),
)
runtime = AgentRuntime(llm=client, options=options, tools=default_tools())

async for event in runtime.run_stream("Inspect the project"):
    print(event.type, event.payload)
```

Tool bundles are fully parsed and schema-validated before execution. Tools default to
`replay_policy = "never"`; declare `"safe"` only when repeating an unsettled call is
known to be safe. Runtime controls can be queued without starting a concurrent run:

```python
await runtime.steer("Focus on the failing integration test")
await runtime.follow_up("Summarize the evidence")
await runtime.next_run("Use concise output on the next explicit run")
```

Hosted applications that execute their own domain policy loops can import process
lifecycle helpers from `inquirycraft.runtime` and shell-output reducers from
`inquirycraft.tools`. These package namespaces are the stable extension surface;
hosts do not need to access the underlying `runtime.subprocess` or reducer modules.
See the maintained [Host Extension API](HOST_EXTENSION_API.md) for signatures,
lifecycle ownership, platform constraints, safe cleanup patterns, and compatibility policy.

`CompactionPolicy` and injected context transformers change only the provider-visible
projection. The append-only source conversation remains intact. When an operation log is
configured, each conversation append also creates a durable projection; terminal records
atomically carry the inactive state and final projection. On resume, settled LLM/tool deltas
missing from the conversation store are reconstructed without repeating a completed provider
request or a `replay_policy="never"` tool.

Context estimates use the highest observed provider token density as a conservative
calibration, including tool-schema overhead. Default chat keeps a compacted provider prefix
and appends new messages without reinserting archived history. Compacted requests persist a versioned,
auditable checkpoint containing the source/provider views, summary boundary, retained tail,
policy, token estimates, and optional provider usage.

### Skills and prompt templates

`ResourceLoader` discovers declarative `SKILL.md` and `*.prompt.md` resources. Project
roots require an explicit `ResourceTrustStore` decision by default. Discovery reads
Markdown data only and never imports project Python. Skill catalogs expose only name and
description; full instructions are read on demand.

## Sessions, events, and replay

Each `run`, `repl`, or `tui` invocation writes a workspace-local session once there is activity, with
conversation, operation, event, and usage logs in
`<workspace>/.inquirycraft/sessions/<ID>/`. Resume an existing chat by choosing
from the session list:

```bash
inquirycraft tui --workspace ./workspace --resume
inquirycraft repl --workspace ./workspace --resume
inquirycraft run --workspace ./workspace --resume "Next task"
```

The list always requires confirmation. For scripts or non-interactive terminals,
add `--session ID` to `--resume`; a unique ID prefix also works. View IDs and
update times with `inquirycraft sessions list --workspace ./workspace`. Resume restores the
entire saved conversation but does not automatically rerun an interrupted step.
Legacy standalone conversation logs remain readable through the Python API; they
are not automatically discovered in the workspace session list. Import one without
changing its original file using `inquirycraft sessions import PATH --workspace ./workspace`;
optional `--operation-log` and `--event-log` paths import matching recovery logs.

`RecordingLLMClient` stores the exact provider-visible request and completion result or
stream chunks. `ReplayLLMClient` consumes that JSONL offline and raises
`ReplayMismatchError` when model parameters, messages, tool schemas, call ordering, or
stream mode drift. Generated timestamps and event IDs can be normalized without
loosening prompts, tool payloads, results, or ordering.

## Tools and path policy

InquiryCraft includes independent read, write, edit, grep, glob, list, and shell tools.
The ordinary chat `shell` tool has no implicit timeout; an explicit `timeout`
still applies. It streams output, retains complete stdout/stderr under
`.logs/shell_outputs/`, and records execution status and artifact references in
tool events and operation settlements. The model-visible result is bounded;
timeout and cancellation retain partial output and clean up the process group.
Failed commands are not automatically rerun. Host-managed research tools keep
their own execution and budget policies.

Filesystem scope is configured separately from tool behavior:

```python
from inquirycraft.tools import PathPolicy, ReadTool

policy = PathPolicy("./workspace", enabled=True)
read = ReadTool(path_policy=policy)
```

A strict `PathPolicy` contains model-generated paths under a configured root and any
explicitly allowed extra roots. Hosts that execute untrusted model output should also
apply process isolation and least-privilege credentials appropriate to their threat
model.

### Tool-output bounds and raw artifacts

The Runtime applies one final output policy after tool hooks and before events,
operation settlement, and Conversation persistence. Results up to 12,000 characters are
unchanged. Larger results become an exact head/tail view; the complete rendered result is
saved under `.logs/tool_outputs/` with a SHA-256 fingerprint and an append-only index.
Shell observation commands use the existing loss-preserving shell summary first.

```python
from inquirycraft.tools import ToolOutputPolicy

runtime = AgentRuntime(
    ...,
    tool_output_policy=ToolOutputPolicy(max_model_chars=16_000),
)

# Explicit compatibility escape hatch; large output will remain model-visible.
unbounded = ToolOutputPolicy(enabled=False)
```

If raw artifact persistence fails, InquiryCraft returns the original unabridged result
instead of silently discarding data. Error results remain errors after reduction.

## CLI reference

```text
inquirycraft run             Run one tool-using agent task
inquirycraft repl            Start an interactive multi-turn session
inquirycraft tui             Start the full-screen streaming terminal interface
inquirycraft tools list      List built-in tools
inquirycraft tools schema    Print tool schemas
inquirycraft events show     Inspect runtime event JSONL
```

Use `inquirycraft COMMAND --help` for command-specific options.

## Package layout

| Package | Responsibility |
| --- | --- |
| `inquirycraft.runtime` | Agent loop, hooks, cancellation, telemetry, and resume |
| `inquirycraft.llm` | Provider protocols, streaming models, retry, and adapters |
| `inquirycraft.tools` | Tool contracts, execution, file tools, and path policy |
| `inquirycraft.memory` | Messages, conversations, and persistent records |
| `inquirycraft.events` | Lifecycle JSONL and deterministic record/replay |
| `inquirycraft.runtime.ProcessExecutionSession` | Typed process execution and cleanup |
| `inquirycraft.mcp` | Optional MCP transport integration |
| `inquirycraft.resources` | Trusted Skills and prompt-template discovery |
| `inquirycraft.cli` | Standalone commands and CLI composition contracts |
| `inquirycraft.tui` | Optional full-screen terminal host and InquiryCraft branding |

See [ARCHITECTURE.md](ARCHITECTURE.md) for design boundaries and validation layers.

## Development

```bash
uv sync --all-extras
uv run pytest -q
uv run ruff check .
uv run ruff format --check .
uv build
```

InquiryCraft supports Python 3.11 and newer and is distributed under the MIT License.

TUI assistant responses render Markdown by default (headings, lists, tables and
syntax-highlighted fenced code). Use `inquirycraft tui --no-markdown` for literal
text, or set `INQUIRYCRAFT_TUI_MARKDOWN=false`. `--markdown` explicitly enables it.
Rendering applies to streamed and restored assistant messages; stored content
remains the original Markdown. User input and tool output remain literal text.

Default CLI runtimes (run, REPL and TUI) enable deterministic context compaction
with a default 256K window (262,144 tokens), an 8,192-token output reserve,
a 2,048-token safety margin and an 80% input trigger. Original conversation records remain intact. This is not
model context-window discovery or an LLM-generated semantic summary. Direct
`AgentRuntime` embedding keeps its existing opt-in policy so host-owned context
transformers are not compacted a second time.

In TUI, type `/` for command completion (Tab or Enter accepts, arrows select,
Escape dismisses). `/context` configures 32K/64K/128K/256K/1M or a custom exact
window (or enter `/context 256K` directly); output reserve, safety margin and trigger ratio are editable. `/usage`
shows session totals. The model footer displays the last completed request's actual input
tokens, token-weighted cache hit rate and cumulative estimated session cost. Before usage is
available, context displays `—` rather than a stored-history estimate. Switching models resets
the current input display, not accumulated cost; resume restores saved totals. Costs use
registry `pricing` at call time; known historical costs are not repriced or cleared. The default capacity
is 256K, and unknown prices remain unset. No model capabilities or
billing rates are inferred. These profiles affect interactive chat factories,
not embedding applications' independent context policies.

`/compact` immediately compacts idle chat context with the existing deterministic
policy, without a model call or API cost. Original conversation, reasoning and tool
records stay intact; the saved projection survives resume. Short context is left
unchanged. It does not compact host-owned research contexts or merely hide UI history.
After automatic or manual compaction, the footer immediately shows `≈` with the compacted
conversation estimate (excluding tool schemas), until new provider usage arrives.
Capacity uses compact units such as `256K` or `1M`; usage keeps one decimal place.

TUI chat has no default turn ceiling: it stops on a final reply or cancellation.
Use `tui --max-turns N` for an explicit per-message limit. Temporary provider
timeouts and transient HTTP errors use bounded recovery (at most three runtime
attempts); tools are not replayed by request recovery. Other runtime defaults
and host-owned research budgets are unchanged.

TUI shell commands still running after 10 seconds automatically become owned
background jobs (at most four concurrent shells). The activity row keeps Thinking
on the left and a collapsed `+N running` control on the right; expand it to inspect
output or stop a command. Completion updates the original tool card and is delivered
to chat without repeating the command. Logs are under `.logs/shell_jobs/` and
`.logs/shell_outputs/`. Exiting TUI stops its owned shells; research workers are
unaffected. After a crash, saved jobs are marked interrupted rather than replayed.
