M2 — pi SDK session harness + prompt/skill composer #12

Merged
david merged 1 commit from issue-3/pi-session-harness into main 2026-07-24 09:57:20 +00:00
Owner

Summary

Implements M2 — pi SDK session harness + prompt/skill composer (#3): reusable, deterministic helpers to (a) compose a stage system prompt from a thin pipeline wrapper plus an external skill body, and (b) run one pipeline stage as a fresh pi agent session. Adds a new self-contained src/stage/ module.

Changes

  • composeStagePrompt (src/stage/helpers/): concatenates wrapper + skill body (YAML frontmatter stripped via the SDK's stripFrontmatter). Degrades to a built-in default body with degraded = true when the skill path is unset, unreadable, or blank (realizes DESIGN D15/D16). Skill reader is injectable for testing.
  • runStage (src/stage/services/): builds a session via an injectable factory, subscribes to streaming events, aggregates assistant text and the ordered list of tool-call names, sends the user prompt, and always disposes the session in finally (on success and on throw).
  • createPiStageSession (src/stage/services/): the real pi wiring — injects the composed system prompt via DefaultResourceLoader with systemPromptOverride (NOT additionalSkillPaths), uses ModelRuntime.create(), SessionManager.inMemory(), cwd = process.cwd(), resolveCliModel, and a least-privilege tool allowlist.
  • parseThinkingLevel (src/stage/helpers/): coerces a config string into a valid pi ThinkingLevel (or undefined).
  • Types (src/stage/types/stage.ts) + barrel (src/stage/index.ts).

Testing

  • All tests pass — npm test: 132 passed (20 files)
  • tsc --noEmit clean, eslint . clean, prettier --check clean
  • Unit tests: composer (merge + missing/unreadable/blank fallback), runStage orchestration (dispose-on-success, dispose-on-throw, event aggregation, options forwarding), parseThinkingLevel
  • Gated smoke e2e (PILOOP_E2E=1, runStage.e2e.test.ts): read-only ls stage streams output, invokes a tool, disposes — asserts exit conditions, never exact LLM text

Checklist

  • Self-reviewed the diff
  • Code follows project conventions (self-contained module: types/helpers/services/index; Result-style degrade; DI seams)
  • Descriptive naming (no single-character variables)
  • Deterministic non-LLM logic is unit-tested; the LLM stage is validated via the gated e2e only

Closes #3

## Summary Implements **M2 — pi SDK session harness + prompt/skill composer** (#3): reusable, deterministic helpers to (a) compose a stage system prompt from a thin pipeline wrapper plus an external skill body, and (b) run one pipeline stage as a fresh pi agent session. Adds a new self-contained `src/stage/` module. ## Changes - **`composeStagePrompt`** (`src/stage/helpers/`): concatenates `wrapper + skill body` (YAML frontmatter stripped via the SDK's `stripFrontmatter`). Degrades to a built-in default body with `degraded = true` when the skill path is unset, unreadable, or blank (realizes DESIGN D15/D16). Skill reader is injectable for testing. - **`runStage`** (`src/stage/services/`): builds a session via an injectable factory, subscribes to streaming events, aggregates assistant text and the ordered list of tool-call names, sends the user prompt, and **always disposes the session in `finally`** (on success and on throw). - **`createPiStageSession`** (`src/stage/services/`): the real pi wiring — injects the composed system prompt via `DefaultResourceLoader` with `systemPromptOverride` (NOT `additionalSkillPaths`), uses `ModelRuntime.create()`, `SessionManager.inMemory()`, `cwd = process.cwd()`, `resolveCliModel`, and a least-privilege tool allowlist. - **`parseThinkingLevel`** (`src/stage/helpers/`): coerces a config string into a valid pi `ThinkingLevel` (or `undefined`). - **Types** (`src/stage/types/stage.ts`) + barrel (`src/stage/index.ts`). ## Testing - [x] All tests pass — `npm test`: **132 passed** (20 files) - [x] `tsc --noEmit` clean, `eslint .` clean, `prettier --check` clean - [x] Unit tests: composer (merge + missing/unreadable/blank fallback), `runStage` orchestration (dispose-on-success, dispose-on-throw, event aggregation, options forwarding), `parseThinkingLevel` - [x] Gated smoke e2e (`PILOOP_E2E=1`, `runStage.e2e.test.ts`): read-only `ls` stage streams output, invokes a tool, disposes — asserts exit conditions, never exact LLM text ## Checklist - [x] Self-reviewed the diff - [x] Code follows project conventions (self-contained module: types/helpers/services/index; Result-style degrade; DI seams) - [x] Descriptive naming (no single-character variables) - [x] Deterministic non-LLM logic is unit-tested; the LLM stage is validated via the gated e2e only Closes #3
- composeStagePrompt: wrapper + external skill body (frontmatter stripped),
  degrade-to-default with degraded flag when skill path unset/unreadable/blank (D15/D16).
- runStage: runs one stage as a fresh pi session; aggregates streamed assistant
  text and ordered tool calls; disposes the session in finally (success + throw).
- createPiStageSession: real pi wiring via DefaultResourceLoader systemPromptOverride
  (not additionalSkillPaths), ModelRuntime, SessionManager.inMemory, resolveCliModel;
  least-privilege tool allowlist; cwd = process.cwd().
- parseThinkingLevel: coerce config string to a valid ThinkingLevel.
- Unit tests for composer (merge + missing/blank fallback), runStage orchestration
  (dispose-on-success/throw, event aggregation, options forwarding), thinking level.
- Gated smoke e2e (PILOOP_E2E=1): read-only ls stage streams output and disposes.

Closes #3
david merged commit b31e833d9a into main 2026-07-24 09:57:20 +00:00
david deleted branch issue-3/pi-session-harness 2026-07-24 09:57:20 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
david/pi-loop!12
No description provided.