@hypercarrier/pi-team-durable 0.0.0-stage → 0.1.0-rc.1
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/LICENSE +21 -0
- package/README.md +118 -2
- package/dist/authority/anchor.d.ts +10 -0
- package/dist/authority/anchor.js +19 -0
- package/dist/authority/card-types.d.ts +152 -0
- package/dist/authority/card-types.js +123 -0
- package/dist/authority/contracts.d.ts +12 -0
- package/dist/authority/contracts.js +29 -0
- package/dist/authority/delivery.d.ts +59 -0
- package/dist/authority/delivery.js +152 -0
- package/dist/authority/graph-card.d.ts +4 -0
- package/dist/authority/graph-card.js +39 -0
- package/dist/authority/graph-control.d.ts +299 -0
- package/dist/authority/graph-control.js +1009 -0
- package/dist/authority/index.d.ts +11 -0
- package/dist/authority/index.js +11 -0
- package/dist/authority/interrupt.d.ts +11 -0
- package/dist/authority/interrupt.js +26 -0
- package/dist/authority/limits.d.ts +7 -0
- package/dist/authority/limits.js +7 -0
- package/dist/authority/notifier.d.ts +32 -0
- package/dist/authority/notifier.js +83 -0
- package/dist/authority/observation.d.ts +55 -0
- package/dist/authority/observation.js +125 -0
- package/dist/authority/projection.d.ts +4 -0
- package/dist/authority/projection.js +15 -0
- package/dist/authority/rc7-contract.json +9334 -0
- package/dist/authority/runtime.d.ts +70 -0
- package/dist/authority/runtime.js +116 -0
- package/dist/authority/state.d.ts +150 -0
- package/dist/authority/state.js +26 -0
- package/dist/authority/sync.d.ts +12 -0
- package/dist/authority/sync.js +77 -0
- package/dist/authority/task-version-ref.d.ts +3 -0
- package/dist/authority/task-version-ref.js +5 -0
- package/dist/authority/tools.d.ts +68 -0
- package/dist/authority/tools.js +326 -0
- package/package.json +57 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 deephbz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,119 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @hypercarrier/pi-team-durable
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Pi Team Bright semantics on [pi-durable](https://www.npmjs.com/package/@earendil-works/pi-durable):
|
|
4
|
+
one Task authority per Team, Workers as durable subthreads, accepted-then-delivered
|
|
5
|
+
notices.
|
|
6
|
+
|
|
7
|
+
## Status
|
|
8
|
+
|
|
9
|
+
- **Prerelease.** The npm `next` tag carries it. This package has its own version
|
|
10
|
+
line, independent of Pi Team Bright. Pi Team Bright 0.25.1 is its contract pin.
|
|
11
|
+
- **No standalone runtime.** The package needs a host that supplies the Team ports:
|
|
12
|
+
model roles or a `catalog`, `observationPort`, `abortRun` and `stop`. The runtime
|
|
13
|
+
builds Worker creation and stop settlement from them. Today the host is the AmPi
|
|
14
|
+
node.
|
|
15
|
+
- **Exact peers.** The peer dependencies pin `@earendil-works/pi-durable`,
|
|
16
|
+
`@earendil-works/pi-ai` and `@earendil-works/chord` to 1.4.0. Other versions give
|
|
17
|
+
a peer conflict.
|
|
18
|
+
- **First version without provenance.** The first version was published by hand.
|
|
19
|
+
Later versions publish from CI with npm provenance.
|
|
20
|
+
- **Protocol value.** The persisted notice value `sender: 'durable-team'` is a
|
|
21
|
+
protocol value from the package's former name. It stays unchanged.
|
|
22
|
+
- **Tests here.** CI runs the unit suites.
|
|
23
|
+
The SIGKILL crash suite and `bench/e2e.ts` stay in HyperCarrier (private).
|
|
24
|
+
- **Store format.** This version stores version-1 documents. The next version
|
|
25
|
+
migrates them to version 2, and a downgrade cannot read a migrated store.
|
|
26
|
+
|
|
27
|
+
Known issues:
|
|
28
|
+
|
|
29
|
+
1. An Alert is lost when the recipient's model errors. It shows as unanswered. Pi
|
|
30
|
+
Team Bright retries it; this package does not.
|
|
31
|
+
2. `team_sync` can replay pages that an earlier call released. Act only on
|
|
32
|
+
`task_read`, and never call `team_sync` from a nested tool call.
|
|
33
|
+
3. Interrupt: a Worker can interrupt the lead, the run to stop is chosen when the
|
|
34
|
+
Alert is accepted, and the abort has no settle bound.
|
|
35
|
+
4. An Alert can be admitted after its recipient is closed or fenced. After an owner
|
|
36
|
+
Close, a busy Worker can still run one tool call.
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
npm install @hypercarrier/pi-team-durable@next
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The package ships compiled JavaScript and type declarations under `dist`.
|
|
45
|
+
Node 22.19 or newer. Install the three pinned peers beside it.
|
|
46
|
+
|
|
47
|
+
## API
|
|
48
|
+
|
|
49
|
+
`./authority` exports the runtime factory, authority documents, observation
|
|
50
|
+
observer, and structural stop ports. A host adapter registers this runtime.
|
|
51
|
+
|
|
52
|
+
- `ptb.team`, `ptb.graph`, `ptb.journal`, `ptb.outbox`, and `ptb.receipts` store
|
|
53
|
+
authority, graph history, recent events, delivery obligations, and replay results.
|
|
54
|
+
Each accepted command and its mechanics commit together.
|
|
55
|
+
- Nine lead tools and three Worker tools use the rc.7 contract. Exact conversation
|
|
56
|
+
and generation bindings guard authority. A fork does not acquire an existing
|
|
57
|
+
Team's lead authority.
|
|
58
|
+
- `ptb.worker-anchor` owns a Worker conversation and renews a durable sleep.
|
|
59
|
+
Lead Esc does not end it. Stops use stored target IDs through the node's single
|
|
60
|
+
mark-before-resume fence. Failed confirmation retains the intent.
|
|
61
|
+
- `ptb.deliver` rechecks an obligation, submits with a stable request ID, records
|
|
62
|
+
admission, and waits for settlement. Notices use `team.notice` entries whose
|
|
63
|
+
data (`TeamNoticeData`) names the delivery kind and its sender: an Alert's
|
|
64
|
+
author, or `team-lead` for Tasks and stops. An interrupt Alert's data also
|
|
65
|
+
has `interrupt: true`. A reader names a notice from this data, never from its
|
|
66
|
+
text.
|
|
67
|
+
Interrupt Alerts persist the recipient run ID at acceptance. A separate phase
|
|
68
|
+
asks the host `abortRun` port to stop that exact run, then checkpoints before
|
|
69
|
+
normal admission. Queue identities stay native; no input is re-queued.
|
|
70
|
+
A failed interruption appears as unproved in the notice. Retry never selects
|
|
71
|
+
a replacement run. The node supplies a cancellable wait outside its admission lock.
|
|
72
|
+
- `ptb.notify` reserves an automatic page before admission. A failed lead input
|
|
73
|
+
pauses notification. Explicit sync or a new owner input resumes it.
|
|
74
|
+
- `team_sync` reserves successive bounded pages. It never waits for consumption.
|
|
75
|
+
The final request hook records exact page visibility. A later observer requires
|
|
76
|
+
a matching persisted native assistant entry and successful input settlement
|
|
77
|
+
before it advances the cursor. Compaction and failed runs release unseen pages.
|
|
78
|
+
- The journal keeps 100 recent events in its document and immutable `ptb.event`
|
|
79
|
+
entries in native storage. This bound does not cover graph history, receipts,
|
|
80
|
+
outbox, observation proofs, or storage history.
|
|
81
|
+
|
|
82
|
+
The host injects model roles: a static `modelRoles` list, or a `catalog(tx,
|
|
83
|
+
lead)` resolver that gives each lead its own roles and default. The AmPi
|
|
84
|
+
node's `default` role captures the authoritative lead model at each Worker
|
|
85
|
+
generation. Unknown roles return a typed refusal.
|
|
86
|
+
The package never reads Pi configuration. The observation commit port resolves
|
|
87
|
+
its store for every hook; a registry can serve multiple stores.
|
|
88
|
+
|
|
89
|
+
The executable contract snapshot comes from Pi Team Bright 0.24.0-rc.7 at
|
|
90
|
+
`382950ce62bd38b6cd9f7fb80b91ef5b9140e15a` (unreleased Alert interrupt support).
|
|
91
|
+
`scripts/rc7-contract.mjs` records source hashes. Pi Team Bright 0.25.1 is the
|
|
92
|
+
contract pin: it ships the same source files, and a test compares their SHA-256
|
|
93
|
+
values with the recorded ones. The tests regenerate the contract from the
|
|
94
|
+
installed devDependency `@hypercarrier/pi-team-bright@0.25.1`.
|
|
95
|
+
|
|
96
|
+
```sh
|
|
97
|
+
npm ci
|
|
98
|
+
npm test
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Fast tests use MemoryStorage, a fixed clock, inert timers, and faux responses.
|
|
102
|
+
The SIGKILL crash-recovery suite and `bench/e2e.ts` stay in HyperCarrier (private).
|
|
103
|
+
They use the AmPi node's subprocess carrier. This repository's tests do not
|
|
104
|
+
cover crash recovery.
|
|
105
|
+
|
|
106
|
+
The C1 delivery hold remains unported. pi-durable places steers and follow-ups at
|
|
107
|
+
turn boundaries. A stale admitted notice cannot grant authority: Task version and
|
|
108
|
+
Worker generation checks still apply.
|
|
109
|
+
|
|
110
|
+
## Development
|
|
111
|
+
|
|
112
|
+
`npm run build` emits `dist`; `npm install` runs it through `prepare`.
|
|
113
|
+
`npm run typecheck`, `npm test` and `npm run verify:package` are the CI checks.
|
|
114
|
+
See [releasing](docs/maintainers/releasing.md) for the release procedure.
|
|
115
|
+
|
|
116
|
+
## Provenance
|
|
117
|
+
|
|
118
|
+
Extracted from HyperCarrier (private) at the certified port, renamed from
|
|
119
|
+
`durable-team`, without its history.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ConversationId, type TaskId, type Tx } from '@earendil-works/pi-durable';
|
|
2
|
+
export declare const ANCHOR_SLEEP_MS: number;
|
|
3
|
+
export declare const WorkerAnchor: import("@earendil-works/pi-durable").Task<null, {
|
|
4
|
+
phase: "hold";
|
|
5
|
+
}, null, object>;
|
|
6
|
+
/** The caller commits this binding with Membership in the same transaction. */
|
|
7
|
+
export declare function createAnchoredConversation(tx: Tx, lead: ConversationId): Promise<{
|
|
8
|
+
conversationId: ConversationId;
|
|
9
|
+
anchorId: TaskId;
|
|
10
|
+
}>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineTask } from '@earendil-works/pi-durable';
|
|
2
|
+
// A live background root owns the Worker conversation. A terminal placeholder
|
|
3
|
+
// cannot be an abort boundary because abortTask returns early for terminal tasks.
|
|
4
|
+
export const ANCHOR_SLEEP_MS = 24 * 60 * 60 * 1000;
|
|
5
|
+
export const WorkerAnchor = defineTask({
|
|
6
|
+
name: 'ptb.worker-anchor', version: 1, initial: () => ({ phase: 'hold' }),
|
|
7
|
+
phases: { hold: async (_task, runtime, context) => {
|
|
8
|
+
await runtime.sleepUntil(runtime.now() + ANCHOR_SLEEP_MS, { phase: 'hold' }, context);
|
|
9
|
+
} },
|
|
10
|
+
abort: async (_task, runtime, context) => {
|
|
11
|
+
await runtime.commit(() => ({ status: 'terminal', outcome: { status: 'aborted' } }), context);
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
/** The caller commits this binding with Membership in the same transaction. */
|
|
15
|
+
export async function createAnchoredConversation(tx, lead) {
|
|
16
|
+
const anchorId = await tx.createTask(WorkerAnchor, null, { conversationId: lead, ownership: { kind: 'conversation' }, background: true, abandonOnRestart: false });
|
|
17
|
+
const conversation = await tx.createConversation({ ownership: { kind: 'task', taskId: anchorId } });
|
|
18
|
+
return { conversationId: conversation.id, anchorId };
|
|
19
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { Type, type Static } from "typebox";
|
|
2
|
+
export declare const GraphVersionRefSchema: Type.TString;
|
|
3
|
+
export declare const GraphTaskTransitionSchema: Type.TEnum<["claim", "block", "resume", "goal_achieved", "goal_failed", "cancel"]>;
|
|
4
|
+
export declare const GraphFailureEdgeSchema: Type.TObject<{
|
|
5
|
+
target: Type.TString;
|
|
6
|
+
max_traversals: Type.TInteger;
|
|
7
|
+
}>;
|
|
8
|
+
export declare const GraphTaskStateSchema: Type.TUnion<[Type.TObject<{
|
|
9
|
+
kind: Type.TLiteral<"dependency_waiting">;
|
|
10
|
+
prerequisite_task_ids: Type.TArray<Type.TString>;
|
|
11
|
+
}>, Type.TObject<{
|
|
12
|
+
kind: Type.TLiteral<"ready">;
|
|
13
|
+
}>, Type.TObject<{
|
|
14
|
+
kind: Type.TLiteral<"in_progress">;
|
|
15
|
+
attempt_id: Type.TString;
|
|
16
|
+
}>, Type.TObject<{
|
|
17
|
+
kind: Type.TLiteral<"blocked">;
|
|
18
|
+
attempt_id: Type.TString;
|
|
19
|
+
evidence: Type.TString;
|
|
20
|
+
}>, Type.TObject<{
|
|
21
|
+
kind: Type.TLiteral<"goal_achieved">;
|
|
22
|
+
attempt_id: Type.TString;
|
|
23
|
+
}>, Type.TObject<{
|
|
24
|
+
kind: Type.TLiteral<"goal_failed">;
|
|
25
|
+
reason: Type.TLiteral<"criterion_failed">;
|
|
26
|
+
attempt_id: Type.TString;
|
|
27
|
+
}>, Type.TObject<{
|
|
28
|
+
kind: Type.TLiteral<"goal_failed">;
|
|
29
|
+
reason: Type.TLiteral<"failure_edge_exhausted">;
|
|
30
|
+
attempt_id: Type.TString;
|
|
31
|
+
target_task_id: Type.TString;
|
|
32
|
+
traversals: Type.TInteger;
|
|
33
|
+
exhaustion_reason: Type.TEnum<["limit_reached", "target_cancelled"]>;
|
|
34
|
+
}>, Type.TObject<{
|
|
35
|
+
kind: Type.TLiteral<"goal_failed">;
|
|
36
|
+
reason: Type.TEnum<["dependency_failed", "dependency_cancelled"]>;
|
|
37
|
+
prerequisite_task_ids: Type.TArray<Type.TString>;
|
|
38
|
+
}>, Type.TObject<{
|
|
39
|
+
kind: Type.TLiteral<"cancelled">;
|
|
40
|
+
reason: Type.TString;
|
|
41
|
+
}>]>;
|
|
42
|
+
export declare const GraphAttemptSummarySchema: Type.TObject<{
|
|
43
|
+
id: Type.TString;
|
|
44
|
+
ordinal: Type.TInteger;
|
|
45
|
+
input_attempt_ids: Type.TRecord<"^.*$", Type.TString>;
|
|
46
|
+
}>;
|
|
47
|
+
/** Canonical graph-native Task card used by tools, delivery, and Coordination. */
|
|
48
|
+
export declare const GraphTaskCardSchema: Type.TObject<{
|
|
49
|
+
id: Type.TString;
|
|
50
|
+
title: Type.TString;
|
|
51
|
+
goal: Type.TString;
|
|
52
|
+
assignee: Type.TString;
|
|
53
|
+
needs: Type.TArray<Type.TString>;
|
|
54
|
+
on_goal_failed: Type.TOptional<Type.TObject<{
|
|
55
|
+
target: Type.TString;
|
|
56
|
+
max_traversals: Type.TInteger;
|
|
57
|
+
}>>;
|
|
58
|
+
status: Type.TEnum<["dependency_waiting", "ready", "in_progress", "blocked", "goal_failed", "goal_achieved", "cancelled"]>;
|
|
59
|
+
state: Type.TUnion<[Type.TObject<{
|
|
60
|
+
kind: Type.TLiteral<"dependency_waiting">;
|
|
61
|
+
prerequisite_task_ids: Type.TArray<Type.TString>;
|
|
62
|
+
}>, Type.TObject<{
|
|
63
|
+
kind: Type.TLiteral<"ready">;
|
|
64
|
+
}>, Type.TObject<{
|
|
65
|
+
kind: Type.TLiteral<"in_progress">;
|
|
66
|
+
attempt_id: Type.TString;
|
|
67
|
+
}>, Type.TObject<{
|
|
68
|
+
kind: Type.TLiteral<"blocked">;
|
|
69
|
+
attempt_id: Type.TString;
|
|
70
|
+
evidence: Type.TString;
|
|
71
|
+
}>, Type.TObject<{
|
|
72
|
+
kind: Type.TLiteral<"goal_achieved">;
|
|
73
|
+
attempt_id: Type.TString;
|
|
74
|
+
}>, Type.TObject<{
|
|
75
|
+
kind: Type.TLiteral<"goal_failed">;
|
|
76
|
+
reason: Type.TLiteral<"criterion_failed">;
|
|
77
|
+
attempt_id: Type.TString;
|
|
78
|
+
}>, Type.TObject<{
|
|
79
|
+
kind: Type.TLiteral<"goal_failed">;
|
|
80
|
+
reason: Type.TLiteral<"failure_edge_exhausted">;
|
|
81
|
+
attempt_id: Type.TString;
|
|
82
|
+
target_task_id: Type.TString;
|
|
83
|
+
traversals: Type.TInteger;
|
|
84
|
+
exhaustion_reason: Type.TEnum<["limit_reached", "target_cancelled"]>;
|
|
85
|
+
}>, Type.TObject<{
|
|
86
|
+
kind: Type.TLiteral<"goal_failed">;
|
|
87
|
+
reason: Type.TEnum<["dependency_failed", "dependency_cancelled"]>;
|
|
88
|
+
prerequisite_task_ids: Type.TArray<Type.TString>;
|
|
89
|
+
}>, Type.TObject<{
|
|
90
|
+
kind: Type.TLiteral<"cancelled">;
|
|
91
|
+
reason: Type.TString;
|
|
92
|
+
}>]>;
|
|
93
|
+
current_context: Type.TString;
|
|
94
|
+
version: Type.TString;
|
|
95
|
+
activation_key: Type.TOptional<Type.TString>;
|
|
96
|
+
accepted_attempt_id: Type.TOptional<Type.TString>;
|
|
97
|
+
current_attempt: Type.TOptional<Type.TObject<{
|
|
98
|
+
id: Type.TString;
|
|
99
|
+
ordinal: Type.TInteger;
|
|
100
|
+
input_attempt_ids: Type.TRecord<"^.*$", Type.TString>;
|
|
101
|
+
}>>;
|
|
102
|
+
attempts_started: Type.TInteger;
|
|
103
|
+
relations: Type.TOptional<Type.TArray<Type.TObject<{
|
|
104
|
+
relation: Type.TEnum<["blocked_by", "parent", "related"]>;
|
|
105
|
+
target_task_id: Type.TString;
|
|
106
|
+
}>>>;
|
|
107
|
+
dependency_state: Type.TOptional<Type.TUnion<[Type.TObject<{
|
|
108
|
+
kind: Type.TLiteral<"ready">;
|
|
109
|
+
active_blocker_ids: Type.TArray<Type.TString>;
|
|
110
|
+
}>, Type.TObject<{
|
|
111
|
+
kind: Type.TLiteral<"waiting">;
|
|
112
|
+
active_blocker_ids: Type.TArray<Type.TString>;
|
|
113
|
+
}>, Type.TObject<{
|
|
114
|
+
kind: Type.TLiteral<"terminal">;
|
|
115
|
+
active_blocker_ids: Type.TArray<Type.TString>;
|
|
116
|
+
}>]>>;
|
|
117
|
+
projection_warnings: Type.TOptional<Type.TArray<Type.TObject<{
|
|
118
|
+
task_id: Type.TString;
|
|
119
|
+
truncated_fields: Type.TArray<Type.TEnum<["title", "goal", "current_context"]>>;
|
|
120
|
+
incomplete_fields: Type.TArray<Type.TEnum<["title", "goal", "current_context"]>>;
|
|
121
|
+
message: Type.TString;
|
|
122
|
+
}>>>;
|
|
123
|
+
}>;
|
|
124
|
+
/** Breaking replacement for task_create. It applies one complete graph revision. */
|
|
125
|
+
export declare const TaskGraphApplyParametersSchema: Type.TObject<{
|
|
126
|
+
operation_id: Type.TString;
|
|
127
|
+
expected_graph_version: Type.TOptional<Type.TString>;
|
|
128
|
+
tasks: Type.TArray<Type.TObject<{
|
|
129
|
+
key: Type.TString;
|
|
130
|
+
title: Type.TString;
|
|
131
|
+
goal: Type.TString;
|
|
132
|
+
assignee: Type.TString;
|
|
133
|
+
needs: Type.TOptional<Type.TArray<Type.TString>>;
|
|
134
|
+
on_goal_failed: Type.TOptional<Type.TObject<{
|
|
135
|
+
target: Type.TString;
|
|
136
|
+
max_traversals: Type.TInteger;
|
|
137
|
+
}>>;
|
|
138
|
+
}>>;
|
|
139
|
+
}>;
|
|
140
|
+
/** One graph-native Task command. Waiting and ready are derived, never authored. */
|
|
141
|
+
export declare const GraphTaskUpdateParametersSchema: Type.TObject<{
|
|
142
|
+
task_id: Type.TString;
|
|
143
|
+
operation_id: Type.TString;
|
|
144
|
+
expected_version: Type.TString;
|
|
145
|
+
transition: Type.TOptional<Type.TEnum<["claim", "block", "resume", "goal_achieved", "goal_failed", "cancel"]>>;
|
|
146
|
+
current_context: Type.TOptional<Type.TString>;
|
|
147
|
+
evidence: Type.TOptional<Type.TString>;
|
|
148
|
+
}>;
|
|
149
|
+
export type GraphTaskCard = Static<typeof GraphTaskCardSchema>;
|
|
150
|
+
export type GraphTaskStateCard = Static<typeof GraphTaskStateSchema>;
|
|
151
|
+
export type TaskGraphApplyParameters = Static<typeof TaskGraphApplyParametersSchema>;
|
|
152
|
+
export type GraphTaskUpdateParameters = Static<typeof GraphTaskUpdateParametersSchema>;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// Ported from Pi Team Bright 0.24.0-rc.4 src/task-authority/graph-control-schemas.ts (MIT). Limits inlined.
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
import { TaskVersionRefSchema } from "./limits.js";
|
|
4
|
+
import { TASK_CARD_GOAL_MAX_LENGTH } from "./limits.js";
|
|
5
|
+
import { TASK_CARD_CONTEXT_MAX_LENGTH, TASK_CARD_TITLE_MAX_LENGTH, TASK_ID_MAX_LENGTH } from "./limits.js";
|
|
6
|
+
const TaskId = Type.String({ minLength: 1, maxLength: TASK_ID_MAX_LENGTH });
|
|
7
|
+
const TaskKey = Type.String({ minLength: 1, maxLength: 64, pattern: "^[A-Za-z0-9_-]+$" });
|
|
8
|
+
const WorkerName = Type.String({ minLength: 1, maxLength: 64 });
|
|
9
|
+
const OperationId = Type.String({ minLength: 1, maxLength: 128 });
|
|
10
|
+
export const GraphVersionRefSchema = Type.String({ pattern: "^g_[0-9a-f]{16}$", minLength: 18, maxLength: 18 });
|
|
11
|
+
export const GraphTaskTransitionSchema = Type.Enum(["claim", "block", "resume", "goal_achieved", "goal_failed", "cancel"]);
|
|
12
|
+
export const GraphFailureEdgeSchema = Type.Object({
|
|
13
|
+
target: TaskKey,
|
|
14
|
+
max_traversals: Type.Integer({ minimum: 1, maximum: 8 }),
|
|
15
|
+
}, { additionalProperties: false });
|
|
16
|
+
export const GraphTaskStateSchema = Type.Union([
|
|
17
|
+
Type.Object({
|
|
18
|
+
kind: Type.Literal("dependency_waiting"),
|
|
19
|
+
prerequisite_task_ids: Type.Array(TaskId, { minItems: 1 }),
|
|
20
|
+
}, { additionalProperties: false }),
|
|
21
|
+
Type.Object({ kind: Type.Literal("ready") }, { additionalProperties: false }),
|
|
22
|
+
Type.Object({ kind: Type.Literal("in_progress"), attempt_id: Type.String({ minLength: 1 }) }, { additionalProperties: false }),
|
|
23
|
+
Type.Object({
|
|
24
|
+
kind: Type.Literal("blocked"),
|
|
25
|
+
attempt_id: Type.String({ minLength: 1 }),
|
|
26
|
+
evidence: Type.String({ minLength: 1 }),
|
|
27
|
+
}, { additionalProperties: false }),
|
|
28
|
+
Type.Object({
|
|
29
|
+
kind: Type.Literal("goal_achieved"),
|
|
30
|
+
attempt_id: Type.String({ minLength: 1 }),
|
|
31
|
+
}, { additionalProperties: false }),
|
|
32
|
+
Type.Object({
|
|
33
|
+
kind: Type.Literal("goal_failed"),
|
|
34
|
+
reason: Type.Literal("criterion_failed"),
|
|
35
|
+
attempt_id: Type.String({ minLength: 1 }),
|
|
36
|
+
}, { additionalProperties: false }),
|
|
37
|
+
Type.Object({
|
|
38
|
+
kind: Type.Literal("goal_failed"),
|
|
39
|
+
reason: Type.Literal("failure_edge_exhausted"),
|
|
40
|
+
attempt_id: Type.String({ minLength: 1 }),
|
|
41
|
+
target_task_id: TaskId,
|
|
42
|
+
traversals: Type.Integer({ minimum: 0 }),
|
|
43
|
+
exhaustion_reason: Type.Enum(["limit_reached", "target_cancelled"]),
|
|
44
|
+
}, { additionalProperties: false }),
|
|
45
|
+
Type.Object({
|
|
46
|
+
kind: Type.Literal("goal_failed"),
|
|
47
|
+
reason: Type.Enum(["dependency_failed", "dependency_cancelled"]),
|
|
48
|
+
prerequisite_task_ids: Type.Array(TaskId, { minItems: 1 }),
|
|
49
|
+
}, { additionalProperties: false }),
|
|
50
|
+
Type.Object({
|
|
51
|
+
kind: Type.Literal("cancelled"),
|
|
52
|
+
reason: Type.String({ minLength: 1 }),
|
|
53
|
+
}, { additionalProperties: false }),
|
|
54
|
+
]);
|
|
55
|
+
export const GraphAttemptSummarySchema = Type.Object({
|
|
56
|
+
id: Type.String({ minLength: 1 }),
|
|
57
|
+
ordinal: Type.Integer({ minimum: 1 }),
|
|
58
|
+
input_attempt_ids: Type.Record(TaskId, Type.String({ minLength: 1 })),
|
|
59
|
+
}, { additionalProperties: false });
|
|
60
|
+
/** Canonical graph-native Task card used by tools, delivery, and Coordination. */
|
|
61
|
+
export const GraphTaskCardSchema = Type.Object({
|
|
62
|
+
id: TaskId,
|
|
63
|
+
title: Type.String({ minLength: 1, maxLength: TASK_CARD_TITLE_MAX_LENGTH }),
|
|
64
|
+
goal: Type.String({ minLength: 1, maxLength: TASK_CARD_GOAL_MAX_LENGTH }),
|
|
65
|
+
assignee: WorkerName,
|
|
66
|
+
needs: Type.Array(TaskId),
|
|
67
|
+
on_goal_failed: Type.Optional(GraphFailureEdgeSchema),
|
|
68
|
+
status: Type.Enum(["dependency_waiting", "ready", "in_progress", "blocked", "goal_failed", "goal_achieved", "cancelled"]),
|
|
69
|
+
state: GraphTaskStateSchema,
|
|
70
|
+
current_context: Type.String({ minLength: 1, maxLength: TASK_CARD_CONTEXT_MAX_LENGTH }),
|
|
71
|
+
version: TaskVersionRefSchema,
|
|
72
|
+
activation_key: Type.Optional(Type.String({ minLength: 1 })),
|
|
73
|
+
accepted_attempt_id: Type.Optional(Type.String({ minLength: 1 })),
|
|
74
|
+
current_attempt: Type.Optional(GraphAttemptSummarySchema),
|
|
75
|
+
attempts_started: Type.Integer({ minimum: 0 }),
|
|
76
|
+
// Optional compatibility coordinates let generic read-only consumers accept
|
|
77
|
+
// both legacy and graph-native cards without making them graph authority.
|
|
78
|
+
relations: Type.Optional(Type.Array(Type.Object({
|
|
79
|
+
relation: Type.Enum(["blocked_by", "parent", "related"]),
|
|
80
|
+
target_task_id: TaskId,
|
|
81
|
+
}, { additionalProperties: false }))),
|
|
82
|
+
dependency_state: Type.Optional(Type.Union([
|
|
83
|
+
Type.Object({ kind: Type.Literal("ready"), active_blocker_ids: Type.Array(TaskId, { maxItems: 0 }) }, { additionalProperties: false }),
|
|
84
|
+
Type.Object({ kind: Type.Literal("waiting"), active_blocker_ids: Type.Array(TaskId, { minItems: 1 }) }, { additionalProperties: false }),
|
|
85
|
+
Type.Object({ kind: Type.Literal("terminal"), active_blocker_ids: Type.Array(TaskId) }, { additionalProperties: false }),
|
|
86
|
+
])),
|
|
87
|
+
projection_warnings: Type.Optional(Type.Array(Type.Object({
|
|
88
|
+
task_id: TaskId,
|
|
89
|
+
truncated_fields: Type.Array(Type.Enum(["title", "goal", "current_context"])),
|
|
90
|
+
incomplete_fields: Type.Array(Type.Enum(["title", "goal", "current_context"])),
|
|
91
|
+
message: Type.String({ minLength: 1 }),
|
|
92
|
+
}, { additionalProperties: false }))),
|
|
93
|
+
}, { additionalProperties: false });
|
|
94
|
+
const GraphTaskItemSchema = Type.Object({
|
|
95
|
+
key: TaskKey,
|
|
96
|
+
title: Type.String({ minLength: 1, maxLength: TASK_CARD_TITLE_MAX_LENGTH }),
|
|
97
|
+
goal: Type.String({ minLength: 1, maxLength: TASK_CARD_GOAL_MAX_LENGTH, description: "Desired outcome and external success signal." }),
|
|
98
|
+
assignee: WorkerName,
|
|
99
|
+
needs: Type.Optional(Type.Array(TaskKey, { uniqueItems: true })),
|
|
100
|
+
on_goal_failed: Type.Optional(GraphFailureEdgeSchema),
|
|
101
|
+
}, { additionalProperties: false });
|
|
102
|
+
/** Breaking replacement for task_create. It applies one complete graph revision. */
|
|
103
|
+
export const TaskGraphApplyParametersSchema = Type.Object({
|
|
104
|
+
operation_id: OperationId,
|
|
105
|
+
expected_graph_version: Type.Optional(GraphVersionRefSchema),
|
|
106
|
+
tasks: Type.Array(GraphTaskItemSchema, { minItems: 1 }),
|
|
107
|
+
}, {
|
|
108
|
+
additionalProperties: false,
|
|
109
|
+
description: "Atomically apply the complete assigned Task graph. Omit expected_graph_version only for its first revision.",
|
|
110
|
+
});
|
|
111
|
+
/** One graph-native Task command. Waiting and ready are derived, never authored. */
|
|
112
|
+
export const GraphTaskUpdateParametersSchema = Type.Object({
|
|
113
|
+
task_id: TaskId,
|
|
114
|
+
operation_id: OperationId,
|
|
115
|
+
expected_version: TaskVersionRefSchema,
|
|
116
|
+
transition: Type.Optional(GraphTaskTransitionSchema),
|
|
117
|
+
current_context: Type.Optional(Type.String({ minLength: 1, maxLength: TASK_CARD_CONTEXT_MAX_LENGTH })),
|
|
118
|
+
evidence: Type.Optional(Type.String({ minLength: 1 })),
|
|
119
|
+
}, {
|
|
120
|
+
additionalProperties: false,
|
|
121
|
+
minProperties: 4,
|
|
122
|
+
description: "Apply one explicit Task transition or record current context. dependency_waiting and ready cannot be authored.",
|
|
123
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { TSchema } from 'typebox';
|
|
2
|
+
import type { Result } from './state.ts';
|
|
3
|
+
export declare const TOOL_NAMES: readonly ["team_create", "team_sync", "ensure_worker", "task_graph_apply", "task_read", "task_update", "worker_stop", "team_shutdown", "alert_send"];
|
|
4
|
+
export type ToolName = typeof TOOL_NAMES[number];
|
|
5
|
+
export type Role = 'lead' | 'worker';
|
|
6
|
+
export interface Contract {
|
|
7
|
+
lead: Record<ToolName, TSchema>;
|
|
8
|
+
worker: Partial<Record<ToolName, TSchema>>;
|
|
9
|
+
results: Record<ToolName, TSchema>;
|
|
10
|
+
}
|
|
11
|
+
export declare function contracts(goalMaxLength?: number): Contract;
|
|
12
|
+
export declare function checkResult(contract: Contract, name: ToolName, value: Result): Result;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { Check } from 'typebox/value';
|
|
3
|
+
export const TOOL_NAMES = ['team_create', 'team_sync', 'ensure_worker', 'task_graph_apply', 'task_read', 'task_update', 'worker_stop', 'team_shutdown', 'alert_send'];
|
|
4
|
+
// Generated from the pinned executable rc.7 contract, not the stale agent surface.
|
|
5
|
+
const source = JSON.parse(readFileSync(new URL('./rc7-contract.json', import.meta.url), 'utf8'));
|
|
6
|
+
export function contracts(goalMaxLength = 1600) {
|
|
7
|
+
if (!Number.isSafeInteger(goalMaxLength) || goalMaxLength < 1)
|
|
8
|
+
throw new Error('goalMaxLength must be a positive safe integer');
|
|
9
|
+
const copy = structuredClone(source);
|
|
10
|
+
function patch(value) {
|
|
11
|
+
if (!value || typeof value !== 'object')
|
|
12
|
+
return;
|
|
13
|
+
const node = value;
|
|
14
|
+
if (node.properties && typeof node.properties === 'object') {
|
|
15
|
+
const goal = node.properties.goal;
|
|
16
|
+
if (goal && typeof goal === 'object')
|
|
17
|
+
goal.maxLength = goalMaxLength;
|
|
18
|
+
}
|
|
19
|
+
for (const child of Object.values(node))
|
|
20
|
+
patch(child);
|
|
21
|
+
}
|
|
22
|
+
patch(copy);
|
|
23
|
+
return copy;
|
|
24
|
+
}
|
|
25
|
+
export function checkResult(contract, name, value) {
|
|
26
|
+
if (!Check(contract.results[name], value))
|
|
27
|
+
throw new Error(`PTB ${name} produced a result outside the rc.7 contract: ${JSON.stringify(value)}`);
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { AbortRun, InterruptTarget } from './interrupt.ts';
|
|
2
|
+
import { type SubmissionId, type Tx } from '@earendil-works/pi-durable';
|
|
3
|
+
import { type State, type Obligation } from './state.ts';
|
|
4
|
+
/** `kind` and `sender` name the delivery in its queue entry; inputs persisted before they existed lack them. */
|
|
5
|
+
export interface DeliveryInput extends InterruptTarget {
|
|
6
|
+
interrupt?: true;
|
|
7
|
+
epoch: string;
|
|
8
|
+
content: string;
|
|
9
|
+
whenBusy: 'steer' | 'followUp';
|
|
10
|
+
kind?: Obligation['kind'];
|
|
11
|
+
sender?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The structured data of a Team input's `team.notice` entry. A reader names the input from these fields, never from
|
|
15
|
+
* its text: `delivery` is the kind (`update` for a notifier page), `sender` the author, at most SENDER_LIMIT characters.
|
|
16
|
+
* `interrupt` marks an Alert sent to interrupt the recipient's run; the interruption outcome stays in the model-facing content.
|
|
17
|
+
* Entries written before `delivery` existed carry only `source` and `deliveryId` or `pageId`.
|
|
18
|
+
*/
|
|
19
|
+
export type TeamNoticeData = {
|
|
20
|
+
source: 'team';
|
|
21
|
+
epoch: string;
|
|
22
|
+
deliveryId?: string;
|
|
23
|
+
pageId?: string;
|
|
24
|
+
delivery?: Obligation['kind'] | 'update';
|
|
25
|
+
sender?: string;
|
|
26
|
+
interrupt?: true;
|
|
27
|
+
};
|
|
28
|
+
export declare const SENDER_LIMIT = 64;
|
|
29
|
+
export declare function eligible(s: State, o: Obligation): boolean;
|
|
30
|
+
export declare function deliveryInput(s: State, o: Obligation): DeliveryInput;
|
|
31
|
+
/** The input a delivery submits. The request ID names the Obligation, so a retry reacquires the same admission. */
|
|
32
|
+
export declare function deliveryDraft(input: DeliveryInput, interruption?: Obligation['interruption']): {
|
|
33
|
+
type: "input";
|
|
34
|
+
content: string;
|
|
35
|
+
whenBusy: "steer" | "followUp";
|
|
36
|
+
requestId: string;
|
|
37
|
+
entry: {
|
|
38
|
+
kind: string;
|
|
39
|
+
data: TeamNoticeData;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
export declare const Deliver: import("@earendil-works/pi-durable").Task<DeliveryInput, {
|
|
43
|
+
phase: "interrupt";
|
|
44
|
+
} | {
|
|
45
|
+
phase: "submit";
|
|
46
|
+
} | {
|
|
47
|
+
phase: "settle";
|
|
48
|
+
submissionId: SubmissionId;
|
|
49
|
+
}, null, object>;
|
|
50
|
+
/** Keep the shared submit/settle mechanics; only the host-owned interruption needs a port. */
|
|
51
|
+
export declare function createDeliver(abortRun: AbortRun): import("@earendil-works/pi-durable").Task<DeliveryInput, {
|
|
52
|
+
phase: "interrupt";
|
|
53
|
+
} | {
|
|
54
|
+
phase: "submit";
|
|
55
|
+
} | {
|
|
56
|
+
phase: "settle";
|
|
57
|
+
submissionId: SubmissionId;
|
|
58
|
+
}, null, object>;
|
|
59
|
+
export declare function scheduleDeliveries(tx: Tx, s: State, delivery: typeof Deliver): Promise<void>;
|