Metadata-Version: 2.4
Name: hugpy-harness
Version: 0.1.14
Summary: Optional standalone terminal chat and agent harness for a Hugpy wrapper or fleet
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: hugpy-tools>=0.2.7
Requires-Dist: abstract-toolserver>=0.0.106

# Hugpy Harness

`hugpy-harness` has two roles:

1. **A terminal client.** It provides a full-screen TUI (`hugpy-harness`) and a
   plain chat REPL (`hugpy-chat`) for a standalone Hugpy **wrapper** or a Hugpy
   **fleet** core such as `https://dev.hugpy.ai`. It talks HTTP only. It needs
   no background service, engine, worker enrollment or toolserver.
2. **The agent-loop core library.** Since 0.1.7, hugpy-agent is built on this
   package. Its loop, gateway, adapter, journal, policy, memory and tool
   registry are the harness modules, and hugpy-agent adds its fleet extras
   through hooks. The harness never imports hugpy-agent.

The full reference, covering hooks, chat-loop behavior and the agent layer,
is in [`docs/agent-and-harness.md`](../../docs/agent-and-harness.md).

## Status (2026-10-18)

| where | version |
|---|---|
| this source (`pyproject.toml`) | **0.1.10**, prepared for release |
| installed on ae (deb, `/opt/hugpy-harness/venv`) | 0.1.9 with local source fixes |
| PyPI | 0.1.9; the 0.1.10 endpoint/TUI changes are not published yet |

## Install

**From the apt repo**, on any Debian or Ubuntu box:

```sh
curl -fsSL https://dev.hugpy.ai/apt/setup.sh | sudo bash
sudo apt install hugpy-harness            # hugpy-wrapper is Recommended, not required
sudo apt install --no-install-recommends hugpy-harness   # client only (remote wrapper/fleet)
```

**From locally built debs**, without an apt repo:

```sh
bash packaging/debian/build.sh            # -> build/debian/: harness .deb, wrapper .deb if built, install.sh
cd build/debian
./install.sh                              # missing local wrapper: prompt [Y/n]
./install.sh --yes                        # default yes, no prompts
./install.sh --without-wrapper            # harness only (remote wrapper or fleet)
./install.sh --dry-run                    # show the APT plan only
```

**From source with pip:**

```sh
pip install ../hugpy_tools .              # needs hugpy-tools>=0.2.7
```

How the deb is put together:

- The deb (Architecture `all`) carries the harness wheel and the
  `hugpy-tools` wheel in `/usr/share/hugpy-harness/`. The postinst installs
  both offline into `/opt/hugpy-harness/venv`.
- `/usr/bin/hugpy-harness` and `/usr/bin/hugpy-chat` exec that venv.
- `Recommends: hugpy-wrapper (>= 0.1.62)`. The wrapper keeps its own runtime
  and service, and neither package embeds the other.
- `apt remove hugpy-harness` leaves chat history, the wrapper, models and
  hugpy-agent untouched. A purge removes only this package's runtime.

## Connect

If you do not pass `--base` or a connection `--config`, the CLI reads the local
wrapper endpoint and a fleet or core endpoint from the user config directory
(`XDG_CONFIG_HOME/hugpy`) and `/etc/hugpy`. It checks the wrapper's `/health`
identity against the core's online worker list. A matching worker URL selects
the core, so model choice and resource monitoring cover the fleet. An absent
or unreachable core leaves the standalone wrapper selected. If no wrapper is
installed, a configured fleet or core endpoint is selected directly.

`HUGPY_WRAPPER_URL` and `HUGPY_FLEET_URL` explicitly select their respective
endpoints. `--base` and `--config` override detection. A user-level fleet
setting also selects that fleet directly. View or change it with:

```sh
hugpy-harness endpoint
hugpy-harness endpoint set-fleet https://dev.hugpy.ai
hugpy-harness endpoint clear-fleet
```

The setting is stored in `~/.config/hugpy/fleet.endpoint.json` (or under
`XDG_CONFIG_HOME`). The URL may be a fleet core or its HTTPS proxy.

If nothing is found, the CLI tells you to pass `--base URL` or set
`HUGPY_FLEET_URL`.

**Key.** The CLI sends `HUGPY_WRAPPER_API_KEY` to a wrapper and
`HUGPY_FLEET_API_KEY` to a core.
The fleet key is a `hugpy-core keys create --name <box> --scope use` key, and
you need it only from outside the LAN once the core requires keys for outside
callers. That core key policy is being set up. As of 2026-10-15, the fleet's
`/v1` answered without a key.

**URLs.** A bare host gets `https://`, and `{base}/v1` is probed first.

**`--config FILE`** reads a JSON connection file that may hold only `base`
and `model` (both strings). The option's help text says the file comes from
`fitevict setup`, but the installed wrapper CLI (0.1.122) has no `setup`
command, so write the file yourself.

To connect a remote box to the fleet:

```sh
hugpy-harness endpoint set-fleet https://dev.hugpy.ai
hugpy-chat --list-models
hugpy-harness --model <id from the list>
```

With 0.1.7, which is what apt serves until 0.1.8 is published, pass
`--base https://dev.hugpy.ai` instead. 0.1.7 does not read the fleet
endpoint files or `HUGPY_FLEET_API_KEY`.

## Launch

```sh
hugpy-harness                                   # full-screen TUI (same as `launch` / `tui`)
hugpy-harness launch --workspace /path/to/project --model MODEL_ID
hugpy-harness --base http://HOST:PORT --workspace /path/to/project
hugpy-harness chat --model MODEL_ID             # plain REPL (= hugpy-chat)
hugpy-chat --message "one question" --model MODEL_ID   # one turn, exit code 0 on success
```

**TUI options:** `--config`, `--base`, `--workspace`, `--model`, `--session`,
`--policy {ask,readonly,auto}` (default `ask`), `--optional-tools` and
`--list-models`. The model defaults to `HUGPY_WRAPPER_MODEL`.

**`hugpy-chat` options:** the same as the TUI, plus
`--tools-mode {prompted,constrained,native,auto}`, `--max-steps` (12),
`--max-tokens` (1024), `--message` and `--resume RUN_ID`.

**In the TUI:**

- `/model` opens the model picker. Every picker filters as you type.
- `/resources` toggles telemetry, and `/resources full` opens the compute
  monitor.
- In the monitor, `e` or Delete evicts a model through the wrapper's operator
  action. A fleet core has no eviction route, so there the TUI says so instead
  of failing.
- The display shows fleet resource snapshots per worker, including stale or
  offline state.

**Sessions** are journaled in `<workspace>/.hugpy_wrapper/chat/journal.db`.
Reopen a session with the same `--workspace` and `/session`, or use
`hugpy-chat --resume RUN_ID`. Conversations stay in the same workspace
journal when you change endpoints.

## Chat behavior

Chat runs the agent loop in conversation mode:

- A plain-text reply is a complete answer. Tools are optional.
- Each user message gets its own step budget.
- An empty reply or an engine error ends the turn with that error. The loop
  never demands a tool call.
- `<tool_call>` and `<tool_response>` markup never shows up as reply text.
- A `final_answer` call that has no answer gets one repair prompt.

## Model tools

The harness imports the standalone `hugpy_tools` catalog, so file, search,
text, data, hash, time and web tools work in both the TUI and plain chat.
Without optional tools, a loop has 36 tools, including `fs_read`, `fs_write`,
`fs_glob`, `shell`, `ask_operator` and `final_answer`.

- Tools execute **on the harness machine**, jailed to `--workspace`, even when
  the model runs on a remote wrapper or fleet.
- Writes, network access and host operations go through `--policy`.
- `--optional-tools` adds document, media, rendering, image, desktop and VM
  tools. Their backends load only when called; install the matching
  `hugpy-tools` extras, for example
  `pip install 'hugpy-tools[images,files,render]'`. Rendering also needs
  Playwright Chromium.
- Generated images and screenshots go to `.hugpy_wrapper/tool-artifacts` in
  the workspace.

## Using the core as a library

```python
from hugpy_harness.config import Config
from hugpy_harness.loop import AgentLoop

loop = AgentLoop(Config(base="http://127.0.0.1:52017", model="MODEL", workspace="."),
                 extra_tool_providers=[my_provider])     # provider(ctx) -> [ToolSpec]
print(loop.run("task"))
```

The hooks are:

| hook | purpose |
|---|---|
| `AgentLoop(rag_factory=, comms_factory=, catalog_provider=, extra_tool_providers=)` | constructor hooks |
| `AgentLoop.turn_ctx(run_id)` | spans one drive |
| `Gateway.request_ctx(chat_url, key)` | per-request headers and cancel |
| `Config.state_dir` and `config.state_path()` | per-workspace state; default `.hugpy_wrapper/chat` |
| the `hugpy_harness.tools` entry-point group | each entry is a `provider(ctx) -> [ToolSpec]` |
| `imports.constants.SERVICE_NAME` | the harness's name (declared, not yet read anywhere) |
| the `imports/` layer | `constants`, `functions`, `classes`, `init_imports` |

Details and caveats are in
[`docs/agent-and-harness.md`](../../docs/agent-and-harness.md#the-hooks-a-host-uses).

## Development

```sh
python -m pytest -q tests          # 20 tests, offline
bash packaging/debian/build.sh     # runs plain `python3`; BUILD_PYTHON is not read (pure-Python wheels)
```

Build, release and test conventions for the whole tree are in
[`docs/development.md`](../../docs/development.md).

## Changes

### 0.1.8 (2026-10-15; installed on ae, not yet in the apt pool)

- Fleet connection. Endpoint resolution falls back to `HUGPY_FLEET_URL`, then
  `~/.config/hugpy/fleet.endpoint.json`, then `/etc/hugpy/fleet.endpoint.json`.
  The key falls back to `HUGPY_FLEET_API_KEY`. When no endpoint is found, the
  error now names `--base` and `HUGPY_FLEET_URL`.
- Evicting a model on a fleet core (no `/v1/operator/actions`) reports "this
  endpoint has no eviction" instead of a raw HTTP error.

### 0.1.7 (2026-10-15; apt, bundled in hugpy-agent 0.1.118)

- The harness became the core library for hugpy-agent. 0.1.7 added
  `rag_factory`, `comms_factory`, `catalog_provider` and `extra_tool_providers`
  on `AgentLoop`, the `turn_ctx` method, `Gateway.request_ctx`,
  `Config.state_dir` and `state_path()`, the `hugpy_harness.tools` entry-point
  group, and the `imports/` layer, which includes `SERVICE_NAME`.

### 0.1.6 (2026-10-14; PyPI)

- The right-hand resources pane shows one box per loaded model, and `e` or
  Delete unloads a model.
- The sessions pane lists the loaded models each session used.
- A model is strictly per session.
- Tool markup (`<tool_call>` and `<tool_response>`) is gated out of reply
  text.

### 0.1.5 (2026-10-14)

- Pickers search as you type.
- `/resources full` is the compute panel in the terminal, with eviction.
- A local model's reply is no longer shown twice.

Local Harness controls:

- `/models` offers an engine filter; `/models gguf`, `/models transformers`, or `/models all` selects it directly. Each model shows its size and engine/runner.
- `/settings` chooses the inference endpoint: Fleet or a specific wrapper. `/observe` selects resource telemetry independently of inference routing.
- `/rename NAME` names a session. `/pin` keeps it near the top of the session list. The session retains its model, endpoint and controls across restarts.
- `/session-settings` shows controls; append `max_tokens 1024`, `temperature 0.2`, `no_think false`, or `alloc_mode auto|gpu-only|ram-only|max-gpu|max-ram` to set one. Current controls also appear in the sidebar.
- `/clear` removes this session's transcript and model context. `/clear-model` keeps the transcript and resets only model context.
- Model reasoning is retained in a collapsed thinking card after the reply; Enter/Space/click expands it. The activity line follows the transcript and reports observed load stages; estimates without evidence remain unknown.

Local agents receive the actual registered toolset in their init prompt. When Core is available they get `fleet_list_models`, `fleet_list_workers` and `fleet_call_model` (chat, embeddings, image generation). The shared toolserver client exposes `toolserver_list_categories`, `toolserver_list_tools` and `toolserver_call_tool` through Core's `/ts/*` relay. Core uses its existing management access gate for this relay; inference-only keys do not grant toolserver operator access. Toolserver calls retain target-specific policy checks and the shared privileged-tool allowlist. `HUGPY_AGENT_TOOLSERVER=0` disables the bridge. The shared MCP catalog gives frontier clients the same explicit toolserver names and argument fields; older call names remain accepted as compatibility aliases.
