feat: initialize Hypothesis Machine Pi extension

This commit is contained in:
Emil
2026-07-31 22:36:16 +03:00
commit 528983ee50
57 changed files with 7790 additions and 0 deletions
+39
View File
@@ -0,0 +1,39 @@
# Architecture
```text
interactive Pi AgentSession (Supervisor)
├─ Hypothesis Machine extension commands/tools
├─ explicit ResearchLoop state machine
└─ AgentTree
├─ child AgentSession + SessionManager + recursive tools
│ └─ grandchild AgentSession + ...
└─ child AgentSession + ... (parallel)
subject results ──> Markdown memory ──> rebuildable SQLite FTS5 index
web tools ──> SearXNG / Firecrawl / Browser adapter ──> sources
test plans ──> Docker-only ExperimentRunner ──> artifacts
└────> independent review verdict
```
The root conversational session is never replaced. A generated child spec is
validated before the tree manifest is changed. The child receives its own Pi
session file and custom tool set. Calling its `spawn_agent` repeats the same
factory path, which makes recursion a capability rather than a special role.
`AgentTree` is model-independent and depends on `AgentRuntimeFactory`. Production
uses `PiAgentRuntimeFactory`; tests use deterministic fake runtimes without paid
API calls. Results are captured from the child's final observable assistant text
and returned by foreground spawn or `agent_control wait/collect`.
The run manifest is small and atomic. On restore, previously running/waiting
agents become `interrupted`; their Pi session path and parent/child relationships
remain intact, and `start()` resumes through `SessionManager.open()`.
The research loop is not an auto-prompt recursion. Each model-driven iteration
must call `record_iteration` with measurable deltas. Code applies termination for
goal completion, methodological failure, external-only questions, user decision,
no information gain, or the absolute iteration cap.
Markdown/source bytes are authoritative. SQLite contains only derived search and
relation data and can be dropped/rebuilt. Pi JSONL remains authoritative for each
agent conversation.
+35
View File
@@ -0,0 +1,35 @@
# Development
Requirements: Node.js 22.19+, npm, Pi credentials for live model tests, Docker with
Compose for local web services and experiments.
```bash
npm install
npm run typecheck
npm test
npm run build
npm run smoke
docker compose -f infra/compose.yaml config
```
The automated suite does not call a model or paid service. Integration tests use
`AgentRuntimeFactory` fakes and cover child/grandchild lineage, parallel work,
steering, cancellation, crash recovery, structured results, and findings.
Manual extension smoke without a model call:
```bash
printf '%s\n' '{"type":"get_commands"}' | \
PI_OFFLINE=1 npx pi --mode rpc --no-session --no-extensions \
--extension ./src/index.ts --approve
```
To inspect web health inside Pi, ask the agent to use a web tool or test the local
endpoints directly. SearXNG JSON must be enabled by `infra/searxng/settings.yml`.
Firecrawl v2 endpoints are used (`/v2/scrape`, `/v2/crawl`). SearXNG binds to
host port 8888 by default; override it with `SEARXNG_PORT` and match `searxng_url`.
To reset only the derived search index, stop Pi and delete
`.hypothesis-machine/index.sqlite*`; `ResearchMemory.rebuildIndex()` reconstructs
it from Markdown. Never delete `runs/`, `sources/`, or `experiments/` unless their
loss is intended.
+80
View File
@@ -0,0 +1,80 @@
# Verified Pi capabilities (0.83.0)
Verified on 2026-07-31 against the published TypeScript declarations for
`@earendil-works/pi-coding-agent@0.83.0`, compatibility-tested against Pi 0.78.0, the current
[Pi documentation](https://pi.dev/docs/latest), and the official
[`earendil-works/pi`](https://github.com/earendil-works/pi) repository.
## Reused from Pi
Hypothesis Machine is an extension package. Pi remains responsible for the TUI,
normal chat, model selection, credentials, streaming, parallel tool calls,
history, compaction, branch summaries, steering/follow-up queues, slash-command
dispatch, lifecycle events, tool rendering, and model/provider execution.
Each child is created by the official SDK `createAgentSession()`. It receives:
- a persistent `SessionManager` in the research run's `sessions/` directory;
- the parent's selected `Model` and thinking level;
- one shared official model/auth service: `ModelRuntime` on Pi 0.83+, or the
parent context's `ModelRegistry` on Pi 0.78;
- a `DefaultResourceLoader` that keeps project context but disables extension
rediscovery for the child (the recursive tools are injected explicitly);
- only its allowed read-only built-ins and Hypothesis Machine custom tools.
`AgentSession.prompt()`, `steer()`, `followUp()`, `abort()`, `subscribe()`, and
`dispose()` provide the actual loop and lifecycle. Hypothesis Machine does not
implement an LLM/tool loop.
The Supervisor extension uses `registerTool`, `registerCommand`, custom tool
rendering, `session_start`, `session_shutdown`, `appendEntry`, `sendUserMessage`,
notifications, and status widgets. The `hypothesis-machine-run` custom entry ties
the root Pi session to a run manifest without copying its conversation.
## Implemented here
Pi does not provide a recursive research registry, domain memory, research stop
policy, web-source gateway, or Docker experiment protocol. This package adds:
- `AgentTree` and `AgentFactory`, including generated/validated Markdown specs;
- coded recursion limits, duplicate-task checks, independent replication flags,
branch cancellation, result collection, and restart recovery;
- Markdown subject memory plus a rebuildable SQLite FTS5 index;
- SearXNG → Firecrawl → optional Browser Use adapter routing;
- URL canonicalization, SSRF checks, source hashing/cache/provenance;
- an explicit bounded iteration state machine;
- frozen test plans and networkless, resource-limited Docker experiments.
## Assumptions corrected after verification
1. `AgentSessionRuntime` owns replacement of the active interactive session
(`newSession`, switch, fork, import). It is not required for independent child
sessions; those use `createAgentSession` directly.
2. In Pi 0.83, `ExtensionContext` exposes `model`, `thinkingLevel`, and a
compatibility `ModelRegistry`, but not the parent `ModelRuntime` instance. The
extension creates one canonical `ModelRuntime` and shares it across children.
In Pi 0.78, `createAgentSession` instead accepts the full `ModelRegistry`, so
the exact registry exposed by the parent context is reused. No key is read or
copied by Hypothesis Machine in either path.
3. The official `subagent/` example currently launches `pi --mode json` child
processes. It is a reference, not a dependency; this project instead uses
persistent in-process `AgentSession`s so steering, follow-up, recursive tools,
and session restoration remain direct SDK operations.
4. Extension factories can run without a session. Background resources must be
created during `session_start` and cleaned up during `session_shutdown`.
5. Custom tool calls are parallel by default. Only `run_experiment` is forced to
sequential execution because it mutates an experiment directory.
6. Plain custom session entries do not enter model context. They are suitable for
the run pointer; findings belong in separate Markdown memory.
7. Current open-source Browser Use agent/MCP operation requires separate model
credentials. Passing Pi credentials to a Python worker would violate the
design, so the MVP exposes a documented local adapter boundary and uses
Firecrawl as the no-extra-model dynamic-page path.
8. Pi 0.78 predates `agent_settled`; autonomous continuation additionally listens
to `agent_end`, guarded by iteration identity and `ctx.isIdle()` so 0.83 does
not schedule duplicate continuation turns.
Primary references: [SDK](https://pi.dev/docs/latest/sdk),
[Extensions](https://pi.dev/docs/latest/extensions),
[session format](https://pi.dev/docs/latest/session-format), and the official
[`subagent` example](https://github.com/earendil-works/pi/tree/main/packages/coding-agent/examples/extensions/subagent).
+31
View File
@@ -0,0 +1,31 @@
# Releasing
This checklist prepares a GitHub release. Publishing to npm is intentionally not
part of the current project workflow.
1. Confirm the worktree contains only intended changes and no runtime state,
fetched sources, datasets, credentials, or package archives.
2. Update the version in `package.json` and `package-lock.json` together.
3. Move release notes into a dated section of `CHANGELOG.md`.
4. Run:
```bash
npm ci
npm run check
docker compose -f infra/compose.yaml config --quiet
npm audit
npm pack --dry-run
```
5. Verify the extension manually with the oldest supported Pi and the recommended
Pi version. A real-model recursive run is a manual release check, not CI.
6. Merge through a reviewed pull request and confirm CI and CodeQL are green.
7. Create an annotated `vX.Y.Z` tag from `main` and a GitHub release using the
matching changelog section.
`npm audit` currently reports the upstream Pi shrinkwrap advisory documented in
[`security.md`](security.md). Confirm that the finding still has exactly that
provenance; any additional high/critical finding blocks a release.
Before the first public release, enable private vulnerability reporting and
branch protection for `main` in the GitHub repository settings.
+43
View File
@@ -0,0 +1,43 @@
# Security model
Hypothesis Machine narrows its own tools, but Pi itself runs with the launching
user's authority. Install only in trusted projects.
- Children receive read-only Pi built-ins (`read`, `grep`, `find`, `ls`) plus an
allowlisted custom set; no child `bash`, `edit`, or `write` is enabled.
- `read_artifact` rejects paths outside the project. Memory writers choose paths
internally and use restrictive file modes.
- Credentials are never requested, serialized, prompted, or passed to Docker.
One official `ModelRuntime` resolves Pi auth in-process.
- Public URLs are normalized and DNS-resolved before use. Loopback, private,
link-local, carrier NAT, multicast/reserved, and metadata targets are blocked.
Every redirect in direct downloads is revalidated. Size and MIME are bounded.
- Firecrawl responses are marked `untrusted`; agents are explicitly instructed
not to execute or obey page instructions. Original source bytes/Markdown,
retrieval time, URL, MIME, SHA-256, and source ID are preserved.
- The default Compose ports bind to `127.0.0.1`. Firecrawl state services are on
an internal network. A hostile public DNS server could still attempt rebinding
between gateway validation and Firecrawl's independent fetch; production
deployments should add an egress proxy/firewall that repeats IP policy.
- Browser Use must use a fresh profile and domain allowlist. The current adapter
is optional because its open-source agent needs separate LLM credentials; the
package never uses the user's main browser profile.
- Experiments run through `docker run`, never host shell: network none, read-only
root, CPU/RAM/PID/time limits, all capabilities dropped, no-new-privileges,
empty minimal environment, and only the experiment directory mounted.
- Dataset acquisition is deliberately outside the experiment container. Download
with `download_source`, verify the data manifest/hash, then mount local bytes.
- If Docker is unavailable, result status is `docker_unavailable`; generated code
is not executed on the host.
Known dependency advisory: `@earendil-works/pi-coding-agent@0.83.0` ships an npm
shrinkwrap that pins `minimatch@10.2.5` / `brace-expansion@5.0.7`, reported by
`npm audit` as GHSA-mh99-v99m-4gvg (resource-exhaustion DoS). A root override
cannot replace a dependency inside that published shrinkwrap. Track the official
Pi release and upgrade when its package moves to `brace-expansion@5.0.8+`; do not
patch Pi's installed files in a postinstall hook.
Known MVP gaps: the read-only Pi built-ins can read any path visible to the Pi
process; use Pi's official sandbox extension or OS/container isolation when a
strict filesystem boundary is required. Compose images are pinned by tag rather
than digest. Browser adapter conformance is operator-controlled.