Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Do not blur these domains:
- `/tasks`, task RPC, `TaskClaim`, and `TaskUpdate` never control workflow work.
- `MonitorManager` owns process-local monitor state; monitor recovery across Pi death is not implemented.
- `pi-subagents` owns worker execution and global concurrency. pi-loop owns only session-scoped orchestration intent, bounded evidence, local capacity, and recovery decisions.
- Ordinary pending notifications are memory-only. Orchestration wake intent is durable until acknowledgement and remains at-least-once across a crash. Persisted controllers recover on resume, not while Pi is absent.
- Ordinary pending notifications are memory-only. Orchestration wake intent survives buffer clearing/restart until valid acknowledgement, unless cancelled or retired by expiry; delivery remains at-least-once across a crash. Persisted controllers recover on resume, not while Pi is absent.

A feature that requires a cross-store workflow/task transaction violates the architecture.

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
pi install npm:@trevonistrevon/pi-loop
```

pi-loop runs only while Pi is active. Persisted controller intent is not an always-on worker; ordinary pending wakes and monitor handles/output are memory-only. See the [runtime continuity matrix](./docs/RUNTIME_CONTINUITY.md) before relying on restart or unattended operation.

## Quick start

Create scheduled, event-driven, or self-paced loops:
Expand Down
3 changes: 3 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
"includes": [
"src/**/*.ts",
"test/**/*.ts",
"test/controller-routing-evaluation.test.mjs",
"test/e2e/controller-routing-evaluation.mjs",
"test/e2e/controller-routing-conformance.mjs",
"benchmarks/**/*.ts"
]
}
Expand Down
19 changes: 19 additions & 0 deletions docs/CONTROLLER_ROUTING_EVAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@ The fixed checklist is:

A scenario succeeds only when every critical item passes. Accuracy is the fraction of all four items that pass. Reports also record duration, tool-call and retry counts, agent-run count, bounded assistant text, errors, and event traces. A failed argument or single-turn item lowers accuracy even when critical routing succeeds.

## First-attempt schema conformance

Evaluation version 2 reports two fields separately from the fixed four-item checklist:

- `firstAttemptValid`: the first observed controller-creation batch has the expected type/count and every call has an accepted tool result. For the independent-task case, the batch contains three calls, not one.
- `firstAttemptSemantic`: that valid batch also passes the scenario's existing argument checks.

An invalid first call repaired in-turn can still pass routing and score 100% on the original checklist. Both first-attempt fields remain false. Conversely, an accepted definition that omits the requested rework can pass validity but fail semantics. Neither field changes critical-success rules, accuracy, retries, or single-turn judgment.

The observation boundary is `tool_execution_start`/`tool_execution_end`, not hypothetical tool choices or every raw model token. Missing or rejected results do not pass. Response model/provider/API metadata is captured separately from the requested model string; absent metadata remains absent. Reports include Pi/Node versions and explicitly count model observations dropped by the eight-entry bound. Argument checks are structural scenario checks, not proof that an entire workflow executes correctly. A timeout/process/extension failure can still omit in-flight observations and earlier completed scenarios for that model from the failed report; version-probe recovery does not establish complete failed-run retention.

Deterministic tests cover repaired attempts, accepted-but-wrong semantics, missing/error results, competing controllers, multi-task batches, late failed calls, timing, and input non-mutation. The published usage-guide example also runs through the production definition validator and LoopStore; cadence/rework misuse is tested independently. These tests establish measurement and example contracts, not model effectiveness.

## Run across models

```bash
Expand Down Expand Up @@ -69,4 +82,10 @@ Because no critical or normal routing item failed across two consecutive passes

The initial full RPC run with `openai-codex/gpt-5.6-sol:minimal` passed all six scenarios at 100% checklist accuracy. The three workflow scenarios each repaired one invalid first attempt in the same user-request turn after incorrectly treating a state-level `loop` field as rework metadata. Their final workflow calls passed and preserved the intended route; the task and loop scenarios required no retries. This is recorded as schema-repair evidence, not hidden or counted as a controller-selection failure.

## Current bounded baseline

An unchanged-copy run on 2026-10-06 requested `openai/gpt-6.1-sol:minimal` using Pi1.0.4 and Node26.10.0. All six fixtures passed all four checklist items. Each workflow case used one creation call with zero repairs. The older harness did not capture response model metadata, so the model string identifies the requested configuration, not an independently observed response model. The existing three hold-outs were included; they are now seen controls, not fresh unseen validation.

This one-model, one-pass run did not reproduce the historical schema problem. Compact tool copy and examples were left unchanged. No guidance improvement, model-wide failure rate, empirical convergence, or operator benefit is claimed. Proposed multi-model/repeat and operator studies remain unrun. Future tuning requires a reproducible current failure, pinned copy/model provenance, first-attempt semantic comparisons, and fresh frozen hold-outs; provider errors and `SKIP` are not passes.

A 9-second startup window is intentional. Native fallback task tools now register at `session_start`, before the first request, so the window no longer guards tool registration; it is kept so the protocol-v2 `pi-tasks` probe has settled before controller judgment is measured.
10 changes: 5 additions & 5 deletions docs/REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pi-loop separates controller, standalone-task, worker-execution, and monitor aut

Workflow state work is embedded in `LoopStore` as `WorkflowExecutionRecord`. It never appears in TaskStore, `/tasks`, task RPC, `TaskClaim`, or `TaskUpdate`. Standalone tasks never control workflow transitions. Finite orchestration intent, dispatch reservations, local capacity, and evidence are embedded in LoopStore; they never discover or project tasks or workflow executions. `pi-subagents` owns worker execution and its global queue.

Notification buffers and monitor process handles are memory-only. Orchestration wake intent is durable until delivery acknowledgement, but delivery remains at-least-once across a crash. Persisted controllers recover when Pi resumes; they do not execute while Pi is absent.
Notification buffers and monitor process handles are memory-only. Orchestration wake intent survives buffer clearing/restart until valid acknowledgement, unless cancelled or retired by expiry; delivery remains at-least-once across a crash. Persisted controllers recover when Pi resumes; they do not execute while Pi is absent.

## Runtime map

Expand All @@ -37,7 +37,7 @@ File-backed stores use PID locks, unique temporary snapshots, fsync, atomic snap
- `session` (default): isolated by Pi session ID;
- `project`: shared in the working directory.

Project scope shares durable state but does not yet elect one scheduler owner across concurrent Pi runtimes. Workflow execution leases prevent duplicate phase work; they are separate from the planned project scheduler fence. Subagent orchestration rejects memory, project, disabled, and custom-path stores; it supports only default file-backed session scope.
Project scope shares durable state but does not yet elect one scheduler owner across concurrent Pi runtimes. Workflow execution leases prevent duplicate phase work; they are separate from the planned project scheduler fence. Subagent orchestration rejects memory, project, disabled, and custom-path stores; it supports only default file-backed session scope. See [runtime continuity](./RUNTIME_CONTINUITY.md) for the retention/delivery matrix and safe recovery.

## Loop model

Expand All @@ -50,7 +50,7 @@ Project scope shares durable state but does not yet elect one scheduler owner ac

`LoopCreate` creates ordinary controllers. Use dynamic loops for one evolving goal without named phase/outcome routing; use workflows for ordered phases, conditional outcomes, rework, or durable handoff; use standalone tasks for independently completable backlog items. Free-text `/loop <goal>` controllers have no implicit fire-count cap; finite budgets require an explicit `LoopCreate maxFires`. `LoopUpdate` is only for dynamic controllers that are neither workflow nor orchestration owned and must persist `continue` after empty or unchanged iterations while work remains. `LoopDelete` pauses or removes ordinary/workflow controllers and cancellation-fences orchestration before stopping its workers. New loops expire after seven days by default; `PI_LOOP_EXPIRES_IN` changes that default and each creation tool accepts an `expiresIn` override. Durations are positive integer `s`, `m`, `h`, or `d` values and may exceed seven days. `LoopList` exposes the immutable ISO `expiresAt` boundary. Workflow presentation derives `claimed` (live lease, not observed execution), `waiting` (monitor attached), `paused`, `idle`, or ephemeral `stopped` activity from authoritative workflow fields and reports activity duration, wall-clock age, and state age without persisting duplicate UI state. Expiry and stale event/hybrid retirement during session recovery emit `loops:expired` plus a hidden notification with `deleted` or `paused` disposition. Fire limits bound repeated execution.

Wake delivery is idle-driven. A due timer or event mutates loop state, emits `loop:fire`, buffers a generation-tagged notification, and sends a hidden Pi message when delivery is safe. Immediately before a buffered workflow wake is sent, the runtime re-reads `LoopStore` and requires the controller status, definition revision, state, transition sequence, and execution ID to match the queued snapshot; deleted, transitioned, paused, or reissued work is dropped even within the same session generation. Retirement follows the same generation-fenced notification path after the store mutation and emits a typed `loops:expired` payload whose reason distinguishes `expires_at` from `resume_event_stale`. Pending fire and retirement notifications are memory-only; they are not a durable event ledger across process death. Stale extension contexts are probed before fire mutation.
Wake delivery is idle-driven. A due timer or event mutates loop state, emits `loop:fire`, buffers a generation-tagged notification, and sends a hidden Pi message when delivery is safe. Immediately before a buffered workflow wake is sent, the runtime re-reads `LoopStore` and requires the controller status, state, transition sequence, and execution ID to match the queued snapshot; compatible definition revisions refresh the message from current state; deleted, transitioned, paused, or reissued work is dropped even within the same session generation. Retirement follows the same generation-fenced notification path after the store mutation and emits a typed `loops:expired` payload whose reason distinguishes `expires_at` from `resume_event_stale`. Pending fire and retirement notifications are memory-only; they are not a durable event ledger across process death. Stale extension contexts are probed before fire mutation.

## Subagent orchestration model

Expand All @@ -60,7 +60,7 @@ The parent `agent_end`, existing 30-second heartbeat, session recovery, and dire

Lifecycle callbacks CAS loop revision, owner runtime/generation, work ID, dispatch ID, attempt, and agent ID. Normal terminal evidence is bounded and persisted with `provider_owned` completion status; pi-loop does not call `subagents:rpc:consume`, so the provider retains its native completion surface. Protocol-v2 stop replies and `stopped`/`aborted` lifecycle statuses acknowledge cancellation, not quiescence. They become non-retryable, non-consumable `uncertain` dispatches before stop RPCs run. A late spawn reply may attach its exact cleanup identity without restoring execution. Proved failures retry only within `maxAttempts`; a spawn timeout is ambiguous without upstream status/list or idempotent dispatch keys, so it becomes `uncertain` and is never retried automatically.

Completion bursts refill capacity without a pi-loop parent wake. When every terminal dispatch is provider-owned, pi-loop pauses the controller and acknowledges its aggregate wake internally so only the provider's native completion path runs. Uncertain dispatches and failures without a provider-owned terminal result retain the durable hidden wake until `pi.sendMessage` succeeds; a crash before acknowledgement may duplicate that delivery. Completed/attention controllers pause for `OrchestrationGet` inspection and explicit deletion. Presentation labels controller status as `active`, `needs attention`, `complete`, or `cancelled` while preserving reducer vocabulary in persisted state. Progress separates local `reserved` capacity from pending work and last-reported dispatch observations (`starting`, `queued`, `reported running`); none proves current worker liveness. Paused terminal controllers remain visible in the status line until deletion. Provider-native `SubagentWorkflow` UI and execution remain a separate provider-owned surface.
Completion bursts refill capacity without a pi-loop parent wake. When every terminal dispatch is provider-owned, pi-loop pauses the controller and acknowledges its aggregate wake internally so only the provider's native completion path runs. Uncertain dispatches and failures without a provider-owned terminal result retain the durable hidden wake until `pi.sendMessage` succeeds, unless cancelled or retired by expiry; a crash before acknowledgement may duplicate that delivery. Completed/attention controllers pause for `OrchestrationGet` inspection and explicit deletion. Presentation labels controller status as `active`, `needs attention`, `complete`, or `cancelled` while preserving reducer vocabulary in persisted state. Progress separates local `reserved` capacity from pending work and last-reported dispatch observations (`starting`, `queued`, `reported running`); none proves current worker liveness. Paused terminal controllers remain visible in the status line until deletion. Provider-native `SubagentWorkflow` UI and execution remain a separate provider-owned surface.

Session shutdown/switch invalidates lifecycle callbacks, persists unresolved work as uncertain, and best-effort requests cancellation before store rebinding. Any unresolved dispatch history prevents controller deletion under the Store lock; cancelled batches stay paused and inspectable. Late completion events do not clear this safety latch. Protocol v2 provides no safe automatic clearance or deletion route. Legacy `stopped`/`interrupted` records normalize to non-consumable uncertainty; previously launched retries or consumed evidence cannot be undone, and missing historical stop provenance cannot be reconstructed. A new runtime cannot adopt an unexpired foreign owner lease; after expiry it marks active dispatches uncertain rather than risking duplicates. Project scope, dynamic work addition, dependency graphs, cross-session election, and exact-once dispatch are not implemented.

Expand Down Expand Up @@ -162,7 +162,7 @@ External consumers import only `@trevonistrevon/pi-loop/api`. Deep `src/` import
- no lifetime workflow revision-count cap (retained history has growing storage and processing costs)
- 65,536 bytes per workflow definition

Remaining durability work is tracked separately in tasks: durable wake outbox, project scheduler fencing, and recoverable monitor execution. Current recovery is resume-time reconciliation, not unattended continuity or exactly-once delivery.
Daemon execution, an ordinary durable wake outbox, project scheduler fencing, and recoverable monitor execution are not implemented. Scope any future project from concrete unmet workloads and explicit approval, not persistence alone. Current recovery is resume-time reconciliation, not unattended continuity or exactly-once delivery.

## Security boundary

Expand Down
Loading
Loading