pi-effect-harness

a harness specifically for writing Effect v4 code

Package details

extensionskill

Install pi-effect-harness from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:pi-effect-harness
Package
pi-effect-harness
Version
0.1.0
Published
May 1, 2026
Downloads
not available
Author
mpsuesser
License
MIT
Types
extension, skill
Size
1,023.3 KB
Dependencies
5 dependencies · 1 peer
Pi manifest JSON
{
  "extensions": [
    "./src/index.ts"
  ],
  "skills": [
    "./skills"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

pi-effect-harness

Table of contents


What it does

When /toggle-effect-harness mode is enabled in the active Pi session:

  • A gold effect badge appears in the Pi footer; mode state persists per-project.
  • The system prompt is augmented every turn with effect-first-development.md (40+ rules covering errors, schemas, layers, services, retries, timeouts, structured concurrency, and observability), a progressive-disclosure agent rules doc, and a "loaded N/7 effect-* skills on this branch" preview.
  • Tool calls that would write Effect code are blocked until at least 7 effect-* skills have been read on the active branch. The check uses a prospective write projection: it looks at the resulting file, so deletion-only changes that leave no Effect code are not blocked.
  • After every successful write, the post-write file is matched against 46 pattern detectors. Matches are sorted by severity and replied back to the agent in-band as a single user message — including the pattern's transformation guidance and a hint to load any suggested skills.
  • A shallow clone of Effect-TS/effect-smol is maintained at .references/effect-v4/, pinned to the tag matching your project's installed effect version. The agent reads from it to verify v4 APIs instead of guessing.

Everything else about your Pi session is unchanged. Toggle /toggle-effect-harness off and the harness disengages cleanly — the system prompt reverts on the next turn, the gate stops firing, and the pattern loop stops emitting feedback.


Install

pi install npm:pi-effect-harness

This registers the extension and all 41 effect-* skills via the package's pi manifest. Pi auto-loads on the next session start.

The first time you enable /toggle-effect-harness in a project, the harness clones effect-smol at the tag matching your installed effect version into .references/effect-v4/ (≈30s, shallow, fail-silent). Add to your project's .gitignore:

.references/

Usage

Toggle /toggle-effect-harness (interactive), or via Pi's mode toggle UI
Status Gold effect badge in the footer
Persistence Project-scoped Pi session state; survives session restart
Activation cost First time per project: shallow clone of effect-smol
Per-turn cost ~3 KB of system-prompt headers + the merged guidance docs

When mode is off, no policy header is injected and no write gate or pattern feedback fires. The harness may still rebuild its command/skill catalog and record successful effect-* skill reads as invisible branch metadata, so the loaded-skill count is ready if you re-enable the mode later.


How it works

The harness is a thin shell around an Effect ManagedRuntime. Pi events are forwarded to a HarnessController, which fans them out to a RuleSet and a HookSet. Rules return Decision values; a DecisionExecutor translates those decisions back into Pi ExtensionAPI calls.

Lifecycle

session_start ─► restore mode state · clear pending skill reads · rebuild SkillCatalog
              · refresh EffectVersion · ensure ReferenceClone if enabled

session_tree  ─► sync mode badge · clear pending skill reads · rebuild SkillCatalog

before_agent_start
  ├─► HookSet
  │     └─► EnsureReferenceClone (if enabled, using the latest refreshed EffectVersion)
  └─► RuleSet
        └─► InjectEffectPolicyHeader
              └─► Decision.InjectSystemPrompt

tool_call
  ├─► HookSet
  │     └─► TrackSkillRead (Read paths matching effect-* skills)
  │           └─► PendingSkillReads.remember(toolCallId, skillName)
  └─► RuleSet
        └─► RequireLoadedSkillsForEffectWrites
              ├─► WriteProjection.prospective(cwd, writeIntent)
              └─► Decision.BlockToolCall  (if Effect code present and skills < 7)

tool_result
  ├─► HookSet
  │     └─► EmitSkillLoadedEntry (successful tracked skill reads)
  │           └─► Decision.AppendCustomEntry
  │                 (customType: "pi-effect-harness:skill-loaded")
  └─► RuleSet
        └─► SendPatternFeedbackAfterWrite
              ├─► WriteProjection.actual(cwd, writeIntent)
              ├─► PatternMatcher × 46 patterns
              └─► Decision.InjectUserMessage

session_shutdown ─► unregister mode badge

Three rules plus session/tool hooks. Session hooks keep the skill catalog, version cache, and reference clone current; tool events record skill reads and run write checks. Everything that touches Pi runs through Decision — there is no direct mutation of session state from rule code, which keeps the rules trivially testable in isolation.

The skill gate

Effect v4 is wide. A model writing Effect cold — without any in-context skill — will reliably produce v3 patterns: Effect.catchAll, Schema.parseJson, Data.TaggedError, OptionFromSelf, compose(...) instead of decodeTo(...), untraced Effect.gen everywhere. The skill gate exists to make the agent stop and read before writing.

What counts as a skill. Each subdirectory under skills/ has a SKILL.md with frontmatter. Pi exposes these as /skill:effect-error-handling commands. The harness watches every Read tool call: when the read path resolves to a known effect-* skill (matched against the live skill catalog), it remembers the pending read keyed by toolCallId. On tool_result, if the read succeeded, it appends an invisible branch-metadata entry shaped like:

{
	customType: "pi-effect-harness:skill-loaded",
	data: {
		name: "effect-error-handling",
		path: "/absolute/path/to/effect-error-handling/SKILL.md"
	}
}

What "loaded" means. Loaded-skill state is derived, not stored. The activeBranchLoadedEffectSkills atom scans the current branch's entries for those custom entries and returns the resulting ReadonlySet<string>. This means:

  • /compact, /fork, and /clone reset the count by default — re-loading skills after a context reset is a feature, not a bug.
  • The count is monotonic within a branch.
  • Counting pending reads in addition to confirmed loads avoids a race where the gate fires between the Read tool call and its result.

The threshold. MIN_EFFECT_SKILLS = 7. Schema, Error Handling, and Layers cover ~70% of any Effect codebase; the remaining four should be task-relevant (AI, SQL, HTTP, CLI, RPC, Workflow, Stream, Testing, Observability, etc.). Seven is calibrated, not arbitrary — fewer and the model still hallucinates; more and the activation friction outweighs the benefit.

Why prospective projection matters. The gate runs on WriteProjection.prospective(cwd, writeIntent), which reconstructs what the file will look like after the write/edit applies. A change whose resulting file no longer matches \bEffect\b|from\s+['"]effect.*['"] is allowed through. A change whose resulting file contains Effect code is gated. This means deletion-only Effect cleanup can proceed without artificially incrementing the skill counter.

The block message quotes the loaded count, the missing count, and a hint to read from .references/effect-v4/ if any API is unclear. The agent retries after loading more skills.

The policy header

Every turn while mode is enabled, InjectEffectPolicyHeader emits a Decision.InjectSystemPrompt whose content is the merged contents of harnesses/effect/guidance/:

File Contents
effect-first-development.md The full Effect-first specification: 40+ numbered laws (EF-1 … EF-40) covering tagged errors, Option, schema, canonical imports, Match, services & layers, Clock, observability, Duration, JSON via Schema, scoped resources, retries, timeouts, structured concurrency, parallel concurrency, Config, Redacted, defects vs. failures, layer memoization isolation, schema-first domain modeling, schema defaults, branded guards, equivalence, transformations, native sort, dual APIs. Followed by copy-paste templates and a 45-item LLM review checklist.
progressive-disclosure-guidance.md Short, imperative agent rules: "load AT LEAST 7 effect-* skills before any Effect work; if anything is unclear, read from .references/effect-v4/."
post__effect-and-the-near-inexpressible-majesty-of-layers.md A long-form essay defending Effect's Layer type. Included for the same reason a system prompt cites a style guide: priors matter.

Followed by a runtime line:

pi-effect-harness policy:
- Before planning or writing Effect code, read at least 7 relevant effect-* skills.
  Loaded on this branch: 3/7 (effect-error-handling, effect-layer-design, effect-schema-v4).
- If any Effect v4 API is unclear, read from the local Effect reference clone instead of guessing.
- Key reference paths: .references/effect-v4/LLMS.md, .references/effect-v4/MIGRATION.md,
  .references/effect-v4/packages/effect/SCHEMA.md, .references/effect-v4/packages/effect/HTTPAPI.md,
  .references/effect-v4/packages/effect/src/.

The skill preview is sorted, capped at 7 names, with (+N more) for overflow. The full guidance is loaded once at layer construction and re-emitted from memory each turn.

The pattern feedback loop

After a successful write, every pattern under patterns/ is matched against the actual post-write file via PatternMatcher. Each pattern is a markdown file with YAML frontmatter:

---
action: context
tool: (edit|write)
event: after
name: avoid-data-tagged-error
description: Use Schema.TaggedErrorClass instead of Data.TaggedError for serialization and RPC compatibility
glob: '**/*.{ts,tsx}'
detector: ast
pattern: Data.TaggedError($$$)
level: warning
suggestSkills:
    - effect-error-handling
---

# Use `Schema.TaggedErrorClass` Instead of `Data.TaggedError`
…

Detectors are either ast-grep rules (single pattern, list of patterns, or full rule object with inside / constraints) or regex with comment-skipping. Severity levels are critical, high, medium, warning, info. Matches are de-duplicated, sorted by severity, and emitted as a single Decision.InjectUserMessage:

pi-effect-harness review request:
File: `src/services/MyThing.ts`

I noticed potential Effect-pattern issues in the write you just completed.
Please inspect this change now.
If the warning is valid, revise the code before continuing.
If you believe it is a false positive or an intentional exception, briefly say so and continue.

Matched patterns:
- avoid-data-tagged-error [warning]: Use Schema.TaggedErrorClass instead of Data.TaggedError…

Relevant guidance:
## avoid-data-tagged-error
…(the full body of the pattern's markdown, plus suggested skills hints)…

The pattern bodies use a Haskell-style transformation diagram convention — type signatures for the bad and good forms, then a one-paragraph rationale. This is harness-internal style, not a requirement; you can fork the patterns and use whatever rationale format you prefer.

The suggestedSkills field is appended to the matched-pattern feedback as: "If you have not loaded the effect-error-handling skill, you should load it before continuing." This closes the feedback loop: a pattern miss surfaces both the rule and the skill that documents it.

The reference clone

Effect v4 is moving fast. Beta releases ship with API renames in nearly every minor (catchAll → catch, parseJson → fromJsonString, Either → Result, compose → decodeTo, the entire *FromSelf suffix removal, etc.). The most reliable way to keep an agent honest is to give it the source.

After /toggle-effect-harness is enabled, and again on enabled session starts / before agent turns, EnsureReferenceClone runs git clone --depth 1 --branch effect@<version> of Effect-TS/effect-smol into .references/effect-v4.cloning/, writes a .pi-effect-harness-version marker file, and atomically renames into place. The version is detected on session start and toggle-on by reading node_modules/effect/package.json (falling back to 4.0.0-beta.59 if absent).

Properties:

  • Atomic: the clone happens in a temp directory and is rename()-d into place. Either .references/effect-v4/ is present and complete, or it is absent.
  • Idempotent: a marker file (.pi-effect-harness-version) records the cloned tag. If the marker matches the installed version, the clone is skipped. If it mismatches, the stale clone is removed and re-cloned.
  • Single-flight: a module-level clonePromise deduplicates concurrent invocations across hooks.
  • Fail-silent: a clone failure (no network, missing tag, git not on PATH) never blocks the agent. The harness continues without the reference; the policy header still tells the agent the paths to look for.

The agent doesn't have to know any of this. It sees .references/effect-v4/LLMS.md and .references/effect-v4/packages/effect/SCHEMA.md mentioned in the policy header, and reads them like any other file.


Skill catalog

41 skills, all loaded into Pi's /skill: namespace.

AI / LLM (6)

Skill Description
effect-ai-chat Stateful AI chat sessions with the Effect Chat module — multi-turn conversations, agentic tool-calling loops, persistence, streaming, structured object generation.
effect-ai-language-model The Effect AI LanguageModel service — text generation, structured output, streaming, tool calling, schema-validated responses.
effect-ai-prompt The complete Prompt API for constructing, merging, and manipulating LLM conversations using messages, parts, and composition operators.
effect-ai-provider @effect/ai provider layers (Anthropic, OpenAI, OpenAI-Compat, OpenRouter) with config management, model abstraction, ExecutionPlan fallback, runtime overrides.
effect-ai-streaming Streaming response patterns: start/delta/end protocol, accumulation strategies, resource-safe consumption, history management with SubscriptionRef.
effect-ai-tool Tool and Toolkit APIs — type-safe tool definitions, parameter validation, handler implementations, user- and provider-defined tools.

Schema & domain modeling (8)

Skill Description
effect-schema-v4 Authoritative reference for Effect Schema v4 API changes and v3 → v4 migration. Find-and-replace tables, breaking changes, idiom shifts.
effect-schema-composition Schema.decodeTo, transformations, filters, multi-stage validation.
effect-domain-modeling Production-ready domain models with Schema.TaggedStruct — ADTs, predicates, orders, guards, match functions.
effect-domain-predicates Comprehensive predicates and orders for domain types using typeclass patterns.
effect-typeclass-design Curried signatures and dual data-first / data-last APIs.
effect-pattern-matching Data.TaggedEnum, $match, $is, Match.typeTags, Effect.match. Avoid manual _tag checks.
effect-context-witness When to use Context.Service witness vs. capability patterns; coupling trade-offs.
effect-optics Iso, Lens, Prism, Optional, Traversal — composable, type-safe access and immutable updates to nested data.

Layers, services, runtime (5)

Skill Description
effect-layer-design Designing and composing layers for clean dependency management.
effect-service-implementation Fine-grained service capabilities; avoiding monolithic designs.
effect-managed-runtime Bridging Effect into non-Effect frameworks (Hono, Express, Fastify, Lambda, Workers) via ManagedRuntime.
effect-platform-abstraction Cross-platform file I/O, process spawning, HTTP clients, terminal — the abstraction itself.
effect-platform-layers Structuring platform-layer provision for cross-platform applications.

Errors, config, observability (4)

Skill Description
effect-error-handling Schema.TaggedErrorClass, catchTag/catchTags, catchReason/catchReasons, Cause, ErrorReporter, recovery patterns.
effect-config Config and ConfigProvider — env vars, structured config, test config, .env, JSON, custom sources.
effect-observability Structured logging, distributed tracing, metrics; OTLP/Prometheus export.
effect-wide-events Wide events (canonical log lines) for observability. Conceptual guide for instrumentation strategy.

Data, IO, concurrency (7)

Skill Description
effect-stream Pull-based streaming pipelines — creation, transformation, consumption, encoding (NDJSON/Msgpack), concurrency, resource safety.
effect-batching Request, RequestResolver, SqlResolver — N+1 elimination, batched data-fetching layers, request caching.
effect-pubsub-event-bus Typed event buses with PubSub and Stream.
effect-filesystem Cross-platform file I/O across Node.js, Bun, browser.
effect-path Cross-platform path operations — joining, resolving, URL conversion.
effect-command-executor ChildProcess — shell commands, captured output, piping, streaming, scoped lifecycle.
effect-concurrency-testing Testing PubSub, Deferred, Latch, Fiber, SubscriptionRef, Stream.

Persistence & networking (4)

Skill Description
effect-sql SqlClient, SqlSchema, SqlModel (CRUD repos), SqlResolver, Migrator.
effect-http-api HttpApi, HttpApiClient, HttpApiBuilder — typed endpoints, security middleware, OpenAPI, derived clients.
effect-rpc-cluster RPC endpoints, cluster routing, workflow patterns with Effect RPC and Cluster.
effect-workflow Durable workflows with Workflow, Activity, DurableClock, DurableDeferred — execution that survives restarts, compensation (saga), distribution via Cluster.

CLI & MCP (2)

Skill Description
effect-cli Type-safe CLI applications — argument parsing, options, commands, dependency injection.
effect-mcp-server MCP servers with McpServer, McpSchema, Tool, Toolkit; stdio and HTTP transports.

Testing & migration (2)

Skill Description
effect-testing @effect/vitest and it.effect(...) — services, layers, time-dependent effects, error handling, property-based testing.
effect-incremental-migration Migrating async/Promise-based modules to Effect services while preserving backward compatibility.

React (3)

Skill Description
effect-atom-state Reactive state management with Effect Atom for React applications.
effect-react-composition Composable React components using Effect Atom; avoiding boolean props; integrating with Effect's reactive state.
effect-react-vm The VM (View Model) pattern for reactive, testable frontend state management.

Pattern catalog

46 patterns run after successful edit/write tool calls when the written path matches the pattern's frontmatter glob. Most target TypeScript/TSX, but some use narrower or negated globs. Detectors are declared per pattern as either ast-grep rules or comment-skipping regex.

avoid-* (21)

Pattern Level Description
avoid-any warning as any and as unknown type assertions.
avoid-data-tagged-error warning Data.TaggedError — use Schema.TaggedErrorClass for serialization and RPC compatibility.
avoid-direct-json info JSON.parse / JSON.stringify — use Schema.fromJsonString or Schema.UnknownFromJsonString.
avoid-direct-tag-checks warning Direct _tag property checks; use exported refinements/predicates.
avoid-expect-in-if warning expect() calls nested inside if blocks in tests.
avoid-fs-promises warning fs/promises direct usage — wrap with Effect.
avoid-mutable-state info let bindings inside Effect services; prefer Ref.
avoid-native-fetch warning Native fetch — use Effect HTTP modules.
avoid-node-imports warning node: imports — use @effect/platform abstractions.
avoid-non-null-assertion warning ! non-null assertion operator.
avoid-object-type warning Object and {} as types.
avoid-option-getorthrow warning Option.getOrThrow — use Option.match or Option.getOrElse.
avoid-platform-coupling warning Binding packages importing platform-specific packages like @effect/platform-bun.
avoid-process-env warning process.env — use Config.*.
avoid-react-hooks high useState/useEffect/useReducer etc. — use VMs with Effect Atom.
avoid-schema-suffix info Schema constants suffixed with Schema; name them after the domain type.
avoid-sync-fs high Synchronous filesystem operations.
avoid-try-catch warning try/catch in Effect code — use Effect.try or typed errors.
avoid-ts-ignore warning @ts-ignore and @ts-expect-error.
avoid-untagged-errors warning instanceof Error and new Error for recoverable failures — use Schema.TaggedErrorClass.
avoid-yield-ref warning Direct yield* Ref/Deferred/Fiber/Latch (removed in v4); use explicit method calls.

prefer-* (7)

Pattern Level Description
prefer-arr-sort warning Arr.sort with explicit Order over native Array.prototype.sort.
prefer-duration-values warning Duration helpers over numeric literals for time.
prefer-effect-fn warning Effect.fn for service methods (automatic tracing) over plain Effect.gen wrappers.
prefer-match-over-switch warning Match over native switch.
prefer-option-over-null info Option over T | null unions.
prefer-redacted-config warning Config.redacted / Schema.Redacted for secrets.
prefer-schema-class warning Schema.Class over Schema.Struct for object/domain schemas.

use-* (7)

Pattern Level Description
use-clock-service warning Clock / DateTime over new Date() and Date.now().
use-console-service warning Console / Effect.log* over console.*.
use-context-service warning Context.Service over legacy ServiceMap.Service APIs.
use-filesystem-service high FileSystem service over direct node:fs imports.
use-path-service warning Path service over direct node:path imports.
use-random-service warning Random service over Math.random().
use-temp-file-scoped warning makeTempFileScoped / makeTempDirectoryScoped over os.tmpdir() or non-scoped variants.

Other (11)

Pattern Level Description
casting-awareness info Type assertions in general — use type-safe alternatives.
context-tag-extends warning class *Tag extends Context.Tag naming — use Context.Service.
effect-catchall-default warning Broad Effect.catch defaults in domain logic — use catchTag unless it's an explicit boundary fallback.
effect-promise-vs-trypromise warning Effect.promise over Effect.tryPromise (loses error handling).
effect-run-in-body warning Effect.runSync / runPromise outside entry points.
imperative-loops warning for / for...of over functional transformations.
require-effect-concurrency warning Effect.forEach / all / validate without explicit concurrency on non-trivial fan-out.
stream-large-files info Whole-file reads when the path looks large or unbounded.
throw-in-effect-gen critical throw inside Effect.gen — use yield* Effect.fail().
vm-in-wrong-file critical View Model definitions outside .vm.ts files.
yield-in-for-loop warning yield* in for loops — use Effect.forEach / STM.forEach.

Each pattern's full markdown body — usually a Haskell-style transformation diagram, rationale, and a hint to load specific effect-* skills — is what gets sent back to the agent on a match.


Configuration

Effect version detection

EffectVersion.refresh(cwd) reads node_modules/effect/package.json and falls back to 4.0.0-beta.59. The detected version is used as the effect@<version> git tag for the reference clone. Refreshing the version happens on session_start and when /toggle-effect-harness is toggled on; the before-turn clone hook then uses the latest cached version. The clone hook compares the marker file to that version and only reclones on mismatch.

Reference clone location

Hardcoded to <cwd>/.references/effect-v4/. The <cwd>/.references/ parent is created if absent. The marker file is <cwd>/.references/effect-v4/.pi-effect-harness-version.

Skill threshold

MIN_EFFECT_SKILLS = 7, defined in harnesses/effect/src/constants.ts. Not currently configurable per-project; if you want a different threshold, fork.

Effect-code regex

\bEffect\b|from\s+['"]effect(?:\/[^'"]*)?['"]

Matches an Effect identifier or any from "effect..." import. The gate is intentionally permissive — false positives on the gate are safe (the agent reads more skills); false negatives are not.

What this extension never does

  • Modifies application source files directly. It may create/update .references/ for the reference clone and Pi's project-scoped session-state file for mode persistence.
  • Blocks Read tool calls. The gate fires on writes only.
  • Persists state across projects. Mode state is project-scoped.
  • Calls the network outside the git clone of the reference repo.
  • Talks to Pi events outside the lifecycle listed above.

Caveats

  • Beta on beta. Effect v4 is itself in beta (pinned to 4.0.0-beta.59), and so is this harness. Pin both deliberately. The reference clone tracks whichever Effect version your project installs, so v4 ABI churn won't break the agent's ability to read accurate sources.
  • The patterns are tripwires, not a linter. They catch the common v3 → v4 confusions and the most expensive-to-debug Effect-specific mistakes. They do not replace bun run check && bun run test. Treat a clean pattern run as "the agent didn't trigger the obvious traps," not as "the code is correct."
  • The skill gate is branch-scoped, not session-scoped. /compact, /fork, and /clone reset the loaded-skill set. This is deliberate: post-compaction, the agent has a smaller working memory, and re-establishing the relevant skill context is cheaper than letting it write Effect code from a partial summary.
  • First activation requires git on PATH and network access. If the clone fails, the harness continues without it; the agent will still be told the paths exist and will get a "file not found" if it tries to read them. Re-toggling /toggle-effect-harness retries.
  • The pattern-feedback loop runs after every successful write. On a large refactor the agent may receive several pattern-feedback messages in a row. This is by design — each one is severity-sorted and de-duplicated, but the rate is determined by the rate of writes.

Development

bun install
bun run check    # dprint format + oxlint + tsgo typecheck
bun run test     # vitest run (all tests)

See AGENTS.md and CONTRIBUTING.md for project structure, code style, and PR guidelines.

The harness is built on a small internal kernel (packages/harness-kit) that wraps Pi's ExtensionAPI in Effect — Decision, HarnessRule, HookSet, RuleEngine, WriteProjection, PatternCatalog, PatternMatcher. The kernel may eventually be lifted out as a standalone library for building other Pi harnesses; for now treat the Effect harness as the product and the kernel as an implementation detail.


License

MIT © Marc Suesser


  • Pi — the coding agent this extension plugs into.
  • Effect — what this extension is opinionated about.
  • Effect-TS/effect-smol — the source the reference clone tracks.
  • Kit Langton (@kitlangton) — primary source for the "near-inexpressible majesty of layers" guidance essay.
  • kriegcloud/beep-effect — the earliest version of the effect-first-development guidance doc was sourced from here.