@sgtbeatdown/pi-tasks 0.1.9 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,12 +1,14 @@
1
1
  # pi-tasks
2
2
 
3
- `pi-tasks` is an endpoint-owned Pi extension for Wolfpack relay v2. It keeps task state in each Pi endpoint's local SQLite store and uses Wolfpack as a content-blind relay. It never writes assignments to terminals. Wolfpack's [relay v2 control-api contract](https://github.com/almogdepaz/wolfpack/blob/main/docs/control-api-schema.md#pi-tasks-relay-v2-boundary) is canonical for the transport boundary.
3
+ Endpoint-owned task coordination for Pi over Wolfpack's memory-only relay. **Both relay mail and endpoint task bookkeeping live in RAM.** Pi's existing session history is the historical record; there is no SQLite database, task-history directory, migration, disk outbox or startup replay.
4
+
5
+ The [relay control API](https://github.com/almogdepaz/wolfpack/blob/main/docs/control-api-schema.md#pi-tasks-relay-v2-boundary) defines the transport boundary.
4
6
 
5
7
  ## endpoint-owned relay
6
8
 
7
- The default extension uses the configured local Wolfpack relay, never the in-memory conformance relay. It registers an opaque endpoint with `POST /api/task-relay/v2/connect`, stores task state at the deterministic per-session path `~/.pi/tasks/v2/sessions/<sha256(WOLFPACK_SESSION_NAME)>/tasks.sqlite`, and exchanges opaque relay envelopes through Wolfpack. The adapter requires a Wolfpack release exposing the stable [relay v2 control-api contract](https://github.com/almogdepaz/wolfpack/blob/main/docs/control-api-schema.md#pi-tasks-relay-v2-boundary).
9
+ The default extension requires a compatible Wolfpack memory-owned server. It negotiates `volatile-v1` at `POST /api/task-relay/volatile-v1`. No transport opt-in flag is needed. The old durable relay adapter is removed, not retained as a compatibility mode.
8
10
 
9
- Set `WOLFPACK_SESSION_NAME` for every Pi process. The adapter uses `WOLFPACK_PORT` when the local control port differs from `18790`; `WOLFPACK_SESSION_NAME` resolves the active Pi process to its relay endpoint. After the target extension registers, run `wolfpack session status <session> --json` and read its `taskEndpoint`. Pass that opaque `{ relay, id }` value unchanged; do not derive it from a session name, broker ID, terminal label, output, or prose.
11
+ Set `WOLFPACK_SESSION_NAME` for each Pi process. `WOLFPACK_PORT` defaults to `18790`; `WOLFPACK_SESSION_NAME` resolves the active Pi process to its relay endpoint. Read `taskEndpoint` from structured session creation/status and pass its opaque `{ relay, id }` unchanged. Never derive it from a name, terminal output or broker ID.
10
12
 
11
13
  ### minimal valid v2 send envelope
12
14
 
@@ -20,134 +22,80 @@ Set `WOLFPACK_SESSION_NAME` for every Pi process. The adapter uses `WOLFPACK_POR
20
22
  }
21
23
  ```
22
24
 
23
- The `agent_task_send` schema is exactly `to`, `task`, and optional `timeoutMs`. Unsupported fields are rejected before persistence: a pre-persistence validation rejection creates no task. If creation status is uncertain after a transport failure, idempotency remains necessary; inspect the structured task ID rather than creating an unrelated replacement.
24
-
25
- Wolfpack owns durable mailbox delivery and peer forwarding; Pi owns lifecycle, logical event order, receipts, and local SQLite state. `agent_task_send` returns after relay acceptance only, not Pi insertion or model execution. The receiver inserts model-visible events as structured `pi-tasks-event` custom messages through Pi's safe `deliverAs: "followUp"` queue and records structured `{ taskId, eventId }` insertion evidence. Replay after Pi has structurally recorded an event cannot create another logical receipt. `createInMemoryTaskRelay` remains exported solely as a deterministic conformance fixture.
26
-
27
- ## retry-content stability (0.1.9)
28
-
29
- New core-originated internal `RelayEnvelope` records persist an immutable ISO
30
- `createdAt` alongside their outbox payload, before relay submission. Assignment,
31
- intent and canonical-event envelopes all use that stored value on every Wolfpack
32
- send, including after adapter/core/store restart. This is **not** a new
33
- `agent_task_send` tool argument or a change to canonical task event authority.
34
- The existing outbox serialization owns the metadata; no new database/schema or
35
- relay spool is introduced. Wolfpack still hashes the full wire envelope, including
36
- its timestamp: genuinely changed timestamp or payload must continue to conflict.
37
-
38
- **Upgrade caveat:** pending envelopes written by older versions have no original
39
- wire timestamp. The old adapter generated it on each attempt, so neither a new
40
- wall-clock value nor an in-memory cache can reconstruct an already accepted
41
- request safely. Missing/invalid metadata now fails before any relay request with
42
- non-retryable `INVALID_RELAY_METADATA`; the core quarantines that pending envelope
43
- unchanged using its existing delivery-blocked mechanism. Already accepted outbox
44
- records and canonical task status are not rewritten. Inspect affected work and
45
- its possibly-accepted outcome before choosing an explicit replacement; do not
46
- blindly mint a new identity or silently backfill timestamps. Existing endpoint
47
- rotation handling remains separate.
48
-
49
- This correction alone does not activate Wolfpack's memory-owned transport. Sparse
50
- cursor negotiation, live-process epoch reset and volatile forwarding outcomes
51
- still require coordinated changes with Wolfpack #351; old forwarding acceptance
52
- and cursor assumptions are not claimed fixed here.
53
-
54
- ### real-relay retry regression
55
-
56
- The ordinary tests use private fixtures. An optional cross-repository test also
57
- runs the real core and adapter against a privately rooted real Wolfpack gateway
58
- over loopback HTTP, loses a response after acceptance, reopens the endpoint store
59
- and recreates the adapter, then verifies duplicate acceptance and genuine content
60
- conflicts. Select a trusted, tracked-clean Wolfpack checkout and exact revision:
25
+ The `agent_task_send` schema is exactly `to`, `task`, and optional `timeoutMs`. Unsupported fields are rejected before admission: a pre-admission validation rejection creates no task. If an outcome is uncertain, idempotency remains necessary within the current lifetime; inspect the returned task ID rather than blindly creating another assignment.
61
26
 
62
- ```bash
63
- PI_TASKS_WOLFPACK_SOURCE=/absolute/wolfpack-checkout \
64
- PI_TASKS_WOLFPACK_REVISION=<full-commit-id> \
65
- bun test tests/wolfpack-real-relay-retry.test.ts
66
- ```
27
+ ## history versus live state
28
+
29
+ - Pi requests task-processing turns through its safe `deliverAs: "followUp"` queue. Pi session messages and tool calls/results preserve historical context. Non-waking receipts, parent ACKs and late-terminal facts are archived as `pi-tasks-event-record` custom entries before transport ACK, without waking the model. Received model-visible task events include the complete structured event in `pi-tasks-event` details, including results—not just a rendered summary.
30
+ - Active tasks, outbox intentions, individual ACK checkpoints, deduplication and delivery-blocked evidence are RAM-owned. Each endpoint has lower-only limits of16,384 records/32MiB encoded state. Admission fails before exceeding those bounds; they are not a process-RSS guarantee.
31
+ - `agent_task_send` confirms relay acceptance, **not execution**. `receiver_recorded` confirms receiver RAM receipt, **not durable persistence**. Pi insertion and wake acceptance are separate evidence, never proof of model execution.
32
+ - Session history is not an operational journal. A new process/lifecycle starts empty with a fresh endpoint/generation; it does not reconstruct tasks, replay envelopes, reuse old ACKs or reopen a worker gate from old history.
33
+ - A relay restart can lose even accepted mail. A Pi restart loses that endpoint's active task state. Prior outcomes can be unknown; do not label them delivered, failed or recovered without evidence.
34
+
35
+ ## memory-owned lifecycle
36
+
37
+ `createConfiguredTaskCore()` owns its transport and `createTaskStore()` RAM state. Startup registers; polling runs every five seconds and at agent settlement. Shutdown fences new work, aborts requests, drains active calls and discards state. Late callbacks cannot mutate a successor lifetime.
38
+
39
+ A live endpoint detecting relay reset stops. `/task-relay-rebind` explains the loss boundary; only `/task-relay-rebind --accept-relay-loss` explicitly discards old state and binds a fresh scope. Automatic polling never invokes rebind. Session history remains untouched, but no historical task becomes active again.
67
40
 
68
- Without that explicit source selection the cross-repository test is skipped.
69
- It does not contact an installed server, live broker or Tailnet peer, and is not
70
- an end-to-end Pi/model execution, two-host, or volatile-profile test.
71
-
72
- ## endpoint-view and delivery-checkpoint corrections (0.1.9)
73
-
74
- Peer relay aliases are local routing names, not globally portable endpoint
75
- identities. On receipt, the adapter projects only protocol-defined assignment and
76
- canonical-event references into the recipient's namespace. The transport header
77
- supplies the authenticated source peer route and local destination; sender-local
78
- source IDs, peer-target IDs, and assignment/event reference agreement must match
79
- before projection. Arbitrary application payloads, event IDs/order, outgoing wire
80
- bytes, and historical task records are unchanged. Core ownership comparisons stay
81
- strict. This relies on Wolfpack enforcing its trusted-peer ingress policy; the
82
- adapter does not authenticate a peer by trusting a payload label.
83
-
84
- Delivery ACKs acknowledge one envelope, **not** a cursor prefix. The core now
85
- tracks observed pending cursor→envelope bindings and a conservative checkpoint in
86
- endpoint-scoped `relay_state` metadata (existing SQLite schema 5, no migration).
87
- A later automatic intent ACK cannot advance the checkpoint past an earlier
88
- unacknowledged assignment. ACK intents are persisted before the request and only
89
- those requested ACKs are retried on connect/receive after a response loss or
90
- reopen. They use the durable envelope ID, not a receive-time RAM cache. Pending
91
- bindings are removed after confirmation; the checkpoint/high-water mark does not
92
- retain an individual completed-ACK history. Retired endpoint bindings are fenced.
93
- Existing potentially unsafe checkpoints are **not** silently rewound or repaired.
94
-
95
- The in-memory conformance fixture now models individual ACKs too. Optional
96
- `tests/wolfpack-real-peer-core.test.ts` uses the same explicit trusted Wolfpack
97
- source/revision variables as the retry test above. It exercises actual adapters,
98
- cores, SQLite and two current v2 gateways over private loopback HTTP, including ACK
99
- response loss/reopen, both-direction task messages and canonical/self fanout.
100
- Broker/topology are synthetic: this is not live Tailnet/auth, compiled-worker,
101
- physical-device or Pi/model execution proof.
102
-
103
- These corrections do **not** activate the memory-owned profile. Negotiated profile/epoch
104
- binding, actual sparse HTTP delivery cursors, live reset/rebind and terminal
105
- forwarding outcomes remain outstanding. Endpoint-scoped checkpoints are not a
106
- substitute for binding the future volatile adapter to its exact relay epoch.
41
+ `createVolatileTaskSession({ url, callerSession, store })` is the lower-level API for a caller-owned RAM store. Transport reconnect within that same lifetime retains pending immutable retries; `rebind()` clears the store, while `close()` stops transport and the caller then closes its state. Persistence/path options are rejected. Old database files are neither read nor deleted.
42
+
43
+ Full immutable content, including the original creation timestamp, is reused for retries. Destination-confirmed acceptance is required. Conflicting content, exhausted delivery and reset cannot become successful acceptance. Individual ACKs preserve sparse bigint cursors and cannot skip earlier pending mail. Bounds/deadlines apply through complete response bodies, including noncooperative injected transports.
44
+
45
+ ## trusted Tailnet and optional authentication
46
+
47
+ The intended deployment trusts its Tailnet machines; Tailscale access control is the network boundary. Do not expose owner APIs through the public internet/Funnel or an untrusted proxy. No additional JWT or peer-signature scheme is required by the task protocol.
48
+
49
+ Existing global Wolfpack JWT enforcement remains **optional**. If the owner configures `WOLFPACK_JWT_SECRET`, the configured Pi factory supports it with short-lived tokens only for HTTP loopback. It never discovers/distributes secrets or automatically forwards them to arbitrary HTTPS URLs. HTTP401 reports `RELAY_AUTH_REQUIRED`, not an epoch reset.
50
+
51
+ Remote task endpoints must be locally resolved aliases. The paired CLI qualifies a selected remote endpoint through the local coordinator; remote lists expose explicit remote metadata. Failed qualification preserves any created session/ID but removes unsafe `taskEndpoint` output and reports `taskEndpointError`.
107
52
 
108
53
  ## delegation workflow
109
54
 
110
- Configure Pi role models with `WOLFPACK_IMPLEMENTER_MODEL` and `WOLFPACK_REVIEWER_MODEL`; they default to `openai-codex/gpt-5.6-terra` and `openai-codex/gpt-5.6-sol`. Explicit user or project choices override those defaults.
55
+ Configure `WOLFPACK_IMPLEMENTER_MODEL` and `WOLFPACK_REVIEWER_MODEL`; defaults are `openai-codex/gpt-5.6-terra` and `openai-codex/gpt-5.6-sol`. Explicit user or project choices override those defaults.
111
56
 
112
57
  ```bash
113
58
  IMPLEMENTER_MODEL="${WOLFPACK_IMPLEMENTER_MODEL:-openai-codex/gpt-5.6-terra}"
114
59
  REVIEWER_MODEL="${WOLFPACK_REVIEWER_MODEL:-openai-codex/gpt-5.6-sol}"
115
60
  ```
116
61
 
117
- 1. create or select a role session. For a new endpoint worker, use its explicit root and resolved role model without a startup assignment: `wolfpack agent spawn --project-dir /absolute/worktree --name <task-role> --model "$IMPLEMENTER_MODEL" --task-worker --readiness-timeout-ms 30000 --json` (or `"$REVIEWER_MODEL"` for review). Put all worker instructions in `agent_task_send.task`. This Pi-only mode rejects prompts/plans and `--notify-parent`.
118
- 2. read the ready `taskEndpoint` from creation success, or verify an existing role's structured liveness, root, harness, and endpoint through `wolfpack session status <stable-session-id> --json`. `TASK_WORKER_PREFLIGHT_FAILED` precedes creation; `TASK_WORKER_NOT_READY` retains `createdSession` and `cleanup`. Inspect an `unconfirmed` cleanup by exact stable ID before retrying. Registration is not model/task execution evidence; report unsupported readiness instead of silently falling back.
119
- 3. call `agent_task_send` with that endpoint and the complete instructions. Continue useful independent coordinator work; when none remains, yield the current turn and let structured task follow-up wake the parent. Do not poll `agent_task_status` or `agent_task_inbox` merely to wait; reserve them for concrete progress evidence or recovery, and call `agent_task_wait` only when explicitly asked to block.
120
- 4. use `agent_task_message` for durable questions, answers, and information. The receiver calls `agent_task_done` as its final action; no completion prose follows.
121
- 5. independently verify the result, call `agent_task_ack({ taskId })` once for that terminal task, then explicitly retain or close only the role sessions the parent spawned.
62
+ 1. Use one assignment mode: full-startup instructions, or an endpoint assignment—not both. For a task worker: `wolfpack agent spawn --project-dir /absolute/worktree --name <task-role> --model "$IMPLEMENTER_MODEL" --task-worker --readiness-timeout-ms 30000 --json` (or `"$REVIEWER_MODEL"`). This mode rejects prompts/plans and `--notify-parent`; put all instructions in `agent_task_send.task`.
63
+ 2. Verify exact session ID/root/Pi harness and registered `taskEndpoint`. `TASK_WORKER_PREFLIGHT_FAILED` precedes creation. `TASK_WORKER_NOT_READY` retains `createdSession`/`cleanup`; inspect `unconfirmed` cleanup by exact ID before retry. Registration is not model execution.
64
+ 3. Submit the assignment and continue useful work. When none remains, yield to structured follow-up. Do not poll `agent_task_status` or `agent_task_inbox` merely to wait; use `agent_task_wait` only when explicitly asked to block.
65
+ 4. Use `agent_task_message` for questions, answers and information. The receiver's last action is `agent_task_done`, not completion prose.
66
+ 5. Independently verify, then acknowledge the terminal task and deliberately retain or clean up the parent-owned role.
122
67
 
123
- Coordinator-capable agents may delegate further when justified, and the spawning coordinator owns each child's lifecycle. Endpoint assignments require terminal completion and one `agent_task_ack`; full-startup children have no task ID, so use their explicit completion/block notification and parent verification instead. Then deliberately retain the child or run `wolfpack kill <stable-session-id> --json`, and verify that exact ID is absent from `wolfpack list --json`. Never use `wolfpack session send`, `/exit`, or `/quit` for cleanup; terminal input is not Wolfpack teardown.
68
+ Endpoint assignments require terminal completion and one `agent_task_ack`; full-startup children have no task ID, so use explicit completion/block notification and parent verification instead. Close only sessions the parent spawned using `wolfpack kill <stable-session-id> --json`, then verify that ID absent from `wolfpack list --json`. Never use `wolfpack session send`, `/exit`, or `/quit` for cleanup.
124
69
 
125
70
  ## worker-only execution gate
126
71
 
127
- Set `PI_TASK_WORKER=1` only when launching a task-only worker. The exact value enables a fail-closed model `tool_call` gate; an absent value or any other value leaves ordinary interactive Pi behavior unchanged. Before assignment, only `agent_task_inbox`, `agent_task_status`, and `agent_task_wait` are allowed. Other current and future model tools are blocked with `PI_TASK_WORKER_ASSIGNMENT_REQUIRED`. Explicit user `!`/`!!` shell commands are outside Pi's model `tool_call` event and are not intercepted.
72
+ The exact value `PI_TASK_WORKER=1` enables a fail-closed model tool gate. Before assignment only inbox/status/wait are allowed; other tools receive `PI_TASK_WORKER_ASSIGNMENT_REQUIRED`. Ordinary interactive Pi and explicit user shell commands are outside this gate.
128
73
 
129
- `PI_TASK_WORKER=1` sessions are leaf roles. Workers cannot call `agent_task_send`, `agent_task_cancel`, or `agent_task_ack`; those attempts are blocked with `PI_TASK_WORKER_COORDINATION_FORBIDDEN`. `agent_task_message` is limited to the eligible incorporated assignment named by its input `taskId`. Generic role-orchestration guidance applies only to non-worker coordinators.
74
+ `PI_TASK_WORKER=1` sessions are leaf roles. Generic role-orchestration guidance applies only to non-worker coordinators. The spawning coordinator owns each child's complete lifecycle.
130
75
 
131
- The gate opens only when the current Pi session contains a structured `pi-tasks-event` entry for `task.created` and the same event belongs to a locally persisted active task targeted to the current endpoint. Rendered prompt text, terminal output, unknown events, foreign tasks, and stale terminal tasks do not authorize execution. On restart, durable session evidence reopens the gate only while the matching local task remains eligible.
76
+ Workers cannot call `agent_task_send`, `agent_task_cancel`, or `agent_task_ack` (`PI_TASK_WORKER_COORDINATION_FORBIDDEN`). `agent_task_message` must name their eligible incorporated assignment. A structured session event alone is insufficient: its task must also be active in this endpoint's current RAM state. Old session history never authorizes work after restart.
132
77
 
133
78
  Preflighting `agent_task_done` marks that task as closing before sibling calls are preflighted. Ordinary tools remain blocked for a closing, pending-terminal, accepted-terminal, or `delivery_blocked` task; an idempotent `agent_task_done` retry for that same assigned task remains allowed. Another independently active assignment can still authorize work.
134
79
 
135
80
  ## terminal delivery and acknowledgment
136
81
 
137
- A receiver task snapshot reports terminal transport separately as `terminalDelivery`: `not_submitted`, `pending`, `accepted`, or `delivery_blocked`. The blocked variant includes stable intent/envelope identities, origin endpoint, timestamp, and structured non-retryable relay error. Canonical task `status` remains origin-owned and is never changed to `delivery_blocked`. Retryable failures keep the same pending terminal intent and envelope; permanent failures remain inspectable and are not rebound to a successor endpoint without a separate authenticated Wolfpack contract.
138
-
139
- `agent_task_ack` is valid only for a terminal origin-owned task. Sequential, concurrent, and restart retries reuse one durable `task.parent_acknowledged` event and the same destination envelope identities while retrying pending physical delivery.
82
+ During the active lifetime `terminalDelivery` reports `not_submitted`, `pending`, `accepted`, or `delivery_blocked`. The blocked variant includes stable intent/envelope identities, origin endpoint, timestamp, and structured non-retryable relay error. Canonical task status is never changed to `delivery_blocked`. Retryable failures reuse the original intent; permanent failures are not rearmed. This evidence is lost on endpoint restart.
140
83
 
141
- Report source modifications in `result.changedFiles`. Artifacts are receiver-project-relative regular files for a parent to inspect, not a changed-file list:
84
+ `agent_task_ack` is terminal-only and origin-owned. Sequential/concurrent retries in one lifetime reuse the logical event and envelope IDs. There is no restart recovery. Report changed source paths in `result.changedFiles`; artifacts are receiver-project-relative regular files, not the changed-file list.
142
85
 
143
86
  ```json
144
87
  { "result": { "changedFiles": ["src/extension.ts"] }, "artifacts": [{ "path": "verification/task-2.md" }] }
145
88
  ```
146
89
 
147
- ## development
90
+ ## development and verification
148
91
 
149
92
  ```bash
150
93
  bun install
151
94
  bun test
152
95
  bun run typecheck
96
+ PI_TASKS_WOLFPACK_SOURCE=/absolute/clean/wolfpack-checkout \
97
+ PI_TASKS_WOLFPACK_REVISION=<full-commit-id> \
98
+ bun test tests/volatile-real-worker.test.ts tests/volatile-extension-worker.test.ts
153
99
  ```
100
+
101
+ Wolfpack worker tests require its supported Bun runtime (currently stable>=1.4.2). Optional source-selected checks use private HTTP/workers, not installed services. They test same-lifetime retry/ACK, explicit reset/rebind, fresh endpoint state and history-only evidence. `createInMemoryTaskRelay` is a deterministic conformance fixture, never the default runtime transport. Physical two-machine qualification, final-path performance, release packaging and live activation remain separate gates.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sgtbeatdown/pi-tasks",
3
- "version": "0.1.9",
4
- "description": "Durable endpoint-owned Pi agent delegation through Wolfpack relay v2.",
3
+ "version": "0.1.10",
4
+ "description": "Memory-only endpoint-owned Pi agent delegation through Wolfpack relay v2.",
5
5
  "module": "src/index.ts",
6
6
  "exports": {
7
7
  ".": "./src/index.ts"
@@ -5,7 +5,7 @@ description: Use when opening, selecting, delegating to, checking, or cleaning u
5
5
 
6
6
  # wolfpack pi task delegation
7
7
 
8
- use `wolfpack-tailnet-control` for session control. this skill covers endpoint-owned v2 task lifecycle, structured handoffs, and parent verification. The package default is the v2 extension described by Wolfpack's [relay v2 control-api contract](https://github.com/almogdepaz/wolfpack/blob/main/docs/control-api-schema.md#pi-tasks-relay-v2-boundary).
8
+ use `wolfpack-tailnet-control` for session control. this skill covers endpoint-owned v2 task lifecycle, structured handoffs, and parent verification. The cutover package default uses the epoch-bound `volatile-v1` transport with the unchanged `pi-tasks/v2` task protocol. It requires a coordinated compatible Wolfpack server; an old server is refused, never silently adopted.
9
9
 
10
10
  ## choose one assignment mode
11
11
 
@@ -74,13 +74,15 @@ Assignment completion ends the assigned task, not the reusable role session. Whe
74
74
  1. create a role session through the readiness path above, or select an existing role by stable ID and verify its structured liveness, canonical project, Pi harness, and registered `taskEndpoint`. Never use terminal output as readiness evidence.
75
75
  2. call `agent_task_send` with the opaque endpoint and concise instructions in `task`. The tool returns after relay acceptance only, not Pi insertion or model execution. If submission reports a retryable delivery error after local persistence, use the structured task ID from the error and inspect it rather than creating an unrelated replacement.
76
76
  3. after `agent_task_send`, continue useful independent coordinator work. When none remains, yield the current turn and let structured task follow-up wake the parent; do not poll `agent_task_status` or `agent_task_inbox` merely to wait. Reserve those tools for concrete progress evidence or recovery, and call `agent_task_wait` only when the user explicitly asks to block.
77
- 4. use `agent_task_message` for durable `question`, `answer`, or `information` flow. Do not use rendered text, terminal output, or logs as lifecycle evidence.
77
+ 4. use `agent_task_message` for `question`, `answer`, or `information` flow within the active endpoint lifetime. Do not use rendered text, terminal output, or logs as lifecycle evidence.
78
78
  5. assignees call `agent_task_done` as their final action with the assigned task ID, terminal status, concise summary, and optional structured result, error, and artifact declarations. Report source modifications in `result.changedFiles`; artifacts are receiver-project-relative regular files for a parent to inspect, not changed-file lists: `{ "result": { "changedFiles": ["src/extension.ts"] }, "artifacts": [{ "path": "verification/task-2.md" }] }`. No prose completion afterward.
79
79
  6. the parent independently verifies files, diff, tests, and artifacts. Then call `agent_task_ack({ taskId })` for that one terminal task and explicitly retain or close the spawned role session.
80
80
 
81
81
  ## v2 delivery and recovery
82
82
 
83
- Wolfpack v2 is a content-blind relay; each Pi endpoint owns task lifecycle, canonical event order, receipts, and SQLite state. Relay acceptance does not prove model execution. The receiver inserts model-visible events as structured `pi-tasks-event` custom messages with Pi's safe `deliverAs: "followUp"` queue, then records insertion through structured `{ taskId, eventId }` evidence. Restart recovery reads durable session entries and local task state; it never parses rendered prompts or terminal prose.
83
+ Wolfpack is a memory-only, content-blind relay; each Pi endpoint keeps active task state, event order, retries and receipts in RAM. There is no SQLite task database. Relay acceptance is not execution; receiver receipt is not durable persistence. Model-visible `pi-tasks-event` entries include the complete structured event in session history; task wakes use Pi's safe `deliverAs: "followUp"` queue. Non-waking receipts, parent ACKs and late-terminal facts are archived as `pi-tasks-event-record` custom entries, not replayed. History is for inspection, never reconstruction/replay or worker authorization after restart. A relay restart can lose even accepted mail; a Pi restart loses its active task state and starts a fresh endpoint.
84
+
85
+ A live endpoint detecting relay reset requires explicit owner action. `/task-relay-rebind` explains the loss boundary; `/task-relay-rebind --accept-relay-loss` discards old RAM state and binds a fresh endpoint. Session history stays untouched, but historical tasks are not adopted. Never invoke rebind as an automatic retry, claim recovery or silently reassign possibly delivered work. Old database files are not read, migrated or deleted; no backward-compatible durable mode is supported.
84
86
 
85
87
  Receiver terminal submission has one task-wide logical identity. Same-status retries reuse it; a conflicting terminal status fails closed. Canonical status remains origin-owned, while `terminalDelivery` separately exposes `not_submitted`, `pending`, `accepted`, or `delivery_blocked`. Parent acknowledgment is terminal-only and retries reuse one logical event and stable envelope identities.
86
88
 
@@ -93,4 +95,4 @@ Set `PI_TASK_WORKER=1` only for task-only workers. Before a valid structured ass
93
95
  - do not steer or interrupt an active Pi turn with task context; use the adapter's follow-up queue.
94
96
  - Do not copy plans, source contents, or transcripts into task context.
95
97
  - do not derive or transform opaque endpoint IDs.
96
- - do not promise JWT federation, artifact byte transfer, exactly-once model execution, or successor endpoint rebinding.
98
+ - do not promise JWT federation, artifact byte transfer, exactly-once model execution, or migration of historical tasks to successor endpoints.
@@ -0,0 +1,63 @@
1
+ import type { TaskCore } from "./task-core";
2
+ import { TaskProtocolError } from "./task-protocol";
3
+ import { createTaskStore } from "./task-store";
4
+ import { createVolatileTaskSession } from "./volatile-task-session";
5
+ import { wolfpackLocalHeaders } from "./wolfpack-local-auth";
6
+
7
+ export interface OwnedTaskCore extends TaskCore {
8
+ /** Fence new work, abort requests, settle active calls, then discard RAM state. */
9
+ close(): Promise<void>;
10
+ }
11
+ export interface ConfiguredTaskCoreOptions {
12
+ readonly sessionName?: string;
13
+ readonly baseUrl?: string;
14
+ readonly fetch?: typeof fetch;
15
+ readonly requestTimeoutMs?: number;
16
+ /** Explicit operator acceptance of loss/unknown outcomes; never set on automatic retry. */
17
+ readonly rebind?: boolean;
18
+ }
19
+
20
+ /** Normal extension transport. Never probe/fallback to the legacy durable relay. */
21
+ export async function createConfiguredTaskCore(options: ConfiguredTaskCoreOptions = {}, signal?: AbortSignal): Promise<OwnedTaskCore> {
22
+ const sessionName = options.sessionName ?? process.env.WOLFPACK_SESSION_NAME;
23
+ if (!sessionName?.trim()) throw new TaskProtocolError("RELAY_UNAVAILABLE", "WOLFPACK_SESSION_NAME is required for the Wolfpack task adapter");
24
+ const port = process.env.WOLFPACK_PORT ?? "18790";
25
+ if (options.baseUrl === undefined && (!/^[0-9]+$/.test(port) || Number(port) < 1 || Number(port) > 65535)) throw new TypeError("invalid WOLFPACK_PORT");
26
+ const base = new URL(options.baseUrl ?? `http://127.0.0.1:${port}`);
27
+ if (base.username || base.password || base.search || base.hash || base.pathname !== "/") throw new TypeError("relay base URL must be an origin");
28
+ const url = new URL("/api/task-relay/volatile-v1", base).href;
29
+ const store = createTaskStore();
30
+ let session: ReturnType<typeof createVolatileTaskSession> | undefined;
31
+ try {
32
+ const requestFetch = options.fetch ?? fetch;
33
+ const authenticated = Object.assign((input: Parameters<typeof fetch>[0], init?: RequestInit) => requestFetch(input, { ...init, headers: wolfpackLocalHeaders(url, init?.headers) }), { preconnect: requestFetch.preconnect }) as typeof fetch;
34
+ session = createVolatileTaskSession({ url, callerSession: sessionName, store, fetch: authenticated, ...(options.requestTimeoutMs !== undefined && { requestTimeoutMs: options.requestTimeoutMs }) });
35
+ const core = await (options.rebind ? session.rebind(signal) : session.connect(signal));
36
+ const active = new Set<Promise<unknown>>();
37
+ let closed = false, closing: Promise<void> | undefined;
38
+ const owned = { ...core } as OwnedTaskCore;
39
+ for (const [key, value] of Object.entries(core)) {
40
+ if (typeof value !== "function") continue;
41
+ Object.defineProperty(owned, key, { enumerable: true, value: (...args: unknown[]) => {
42
+ if (closed) throw new TaskProtocolError("RELAY_CLOSED", "task session is closed", { retryable: false });
43
+ // Core methods may call sibling methods through `this` (notably intent ACK).
44
+ // Keep their original receiver while the outer promise owns the full operation.
45
+ const result: unknown = value.apply(core, args);
46
+ if (!(result instanceof Promise)) return result;
47
+ active.add(result);
48
+ void result.then(() => active.delete(result), () => active.delete(result));
49
+ return result;
50
+ } });
51
+ }
52
+ owned.close = () => {
53
+ if (closing) return closing;
54
+ closed = true;
55
+ session!.close();
56
+ closing = Promise.allSettled([...active]).then(() => { store.close(); });
57
+ return closing;
58
+ };
59
+ return owned;
60
+ } catch (error) {
61
+ session?.close(); store.close(); throw error;
62
+ }
63
+ }