feat: initialize Hypothesis Machine Pi extension
This commit is contained in:
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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).
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user