pi-durable-python 1.3.0__py3-none-any.whl
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.
- pi_durable/__init__.py +320 -0
- pi_durable/context.py +328 -0
- pi_durable/delta.py +269 -0
- pi_durable/documents.py +366 -0
- pi_durable/entries.py +66 -0
- pi_durable/env/__init__.py +408 -0
- pi_durable/env/decode.py +48 -0
- pi_durable/env/line_scan.py +190 -0
- pi_durable/env/posix.py +1547 -0
- pi_durable/errors.py +89 -0
- pi_durable/harness/__init__.py +1 -0
- pi_durable/harness/agent.py +344 -0
- pi_durable/harness/compaction.py +673 -0
- pi_durable/harness/context.py +515 -0
- pi_durable/harness/define.py +111 -0
- pi_durable/harness/events.py +766 -0
- pi_durable/harness/generation.py +1079 -0
- pi_durable/harness/harness.py +570 -0
- pi_durable/harness/inbox.py +255 -0
- pi_durable/harness/live.py +374 -0
- pi_durable/harness/output.py +467 -0
- pi_durable/harness/prompt.py +288 -0
- pi_durable/harness/provider.py +78 -0
- pi_durable/harness/registry.py +136 -0
- pi_durable/harness/scheduler.py +1977 -0
- pi_durable/harness/submissions.py +357 -0
- pi_durable/harness/task_graph.py +334 -0
- pi_durable/harness/tool.py +1365 -0
- pi_durable/harness/types.py +772 -0
- pi_durable/harness/usage.py +88 -0
- pi_durable/harness/util.py +204 -0
- pi_durable/harness/validation.py +460 -0
- pi_durable/harness/view.py +324 -0
- pi_durable/ids.py +63 -0
- pi_durable/json.py +186 -0
- pi_durable/providers/__init__.py +362 -0
- pi_durable/providers/anthropic.py +603 -0
- pi_durable/providers/base.py +97 -0
- pi_durable/providers/json_parse.py +117 -0
- pi_durable/providers/openai.py +574 -0
- pi_durable/providers/request.py +35 -0
- pi_durable/providers/retry.py +127 -0
- pi_durable/providers/stream.py +183 -0
- pi_durable/providers/transcript.py +224 -0
- pi_durable/providers/usage.py +58 -0
- pi_durable/py.typed +0 -0
- pi_durable/search.py +115 -0
- pi_durable/session/__init__.py +5 -0
- pi_durable/session/drafts.py +762 -0
- pi_durable/session/forks.py +109 -0
- pi_durable/session/observation.py +374 -0
- pi_durable/session/session.py +1193 -0
- pi_durable/session/transaction.py +1088 -0
- pi_durable/storage/__init__.py +5 -0
- pi_durable/storage/jsonl/__init__.py +15 -0
- pi_durable/storage/jsonl/storage.py +958 -0
- pi_durable/storage/memory.py +889 -0
- pi_durable/storage/scan.py +57 -0
- pi_durable/storage/sqlite/__init__.py +26 -0
- pi_durable/storage/sqlite/database.py +62 -0
- pi_durable/storage/sqlite/migrations.py +191 -0
- pi_durable/storage/sqlite/posix.py +314 -0
- pi_durable/storage/sqlite/schema.py +97 -0
- pi_durable/storage/sqlite/storage.py +1130 -0
- pi_durable/tasks.py +89 -0
- pi_durable/testing/__init__.py +41 -0
- pi_durable/testing/assertions.py +44 -0
- pi_durable/testing/env_conformance.py +828 -0
- pi_durable/testing/runner.py +64 -0
- pi_durable/testing/storage_conformance.py +2008 -0
- pi_durable/testing/types.py +99 -0
- pi_durable/tool_search/__init__.py +159 -0
- pi_durable/tools/__init__.py +81 -0
- pi_durable/tools/_values.py +43 -0
- pi_durable/tools/bash.py +250 -0
- pi_durable/tools/edit.py +214 -0
- pi_durable/tools/edit_diff.py +452 -0
- pi_durable/tools/env.py +19 -0
- pi_durable/tools/file_mutation_queue.py +68 -0
- pi_durable/tools/image.py +248 -0
- pi_durable/tools/image_processor.py +77 -0
- pi_durable/tools/path_utils.py +111 -0
- pi_durable/tools/read.py +506 -0
- pi_durable/tools/write.py +71 -0
- pi_durable/truncate.py +185 -0
- pi_durable/types.py +1297 -0
- pi_durable_python-1.3.0.dist-info/METADATA +276 -0
- pi_durable_python-1.3.0.dist-info/RECORD +90 -0
- pi_durable_python-1.3.0.dist-info/WHEEL +4 -0
- pi_durable_python-1.3.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,772 @@
|
|
|
1
|
+
"""Public Harness types.
|
|
2
|
+
|
|
3
|
+
The Harness layer grows in later tickets. This module holds the value shapes the Session produces
|
|
4
|
+
for it and the scheduler's registry, agent, settings, and task-runtime contracts.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from collections.abc import Awaitable, Callable, Mapping, Sequence
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from typing import TYPE_CHECKING, Any, Literal, Never, NotRequired, Protocol, TypeAlias, TypedDict, TypeVar
|
|
12
|
+
|
|
13
|
+
from ..context import AbortSignal, Context
|
|
14
|
+
from ..documents import (
|
|
15
|
+
AnyDocToken,
|
|
16
|
+
CommonDocDefinition,
|
|
17
|
+
ConversationDocFamilyToken,
|
|
18
|
+
ConversationDocToken,
|
|
19
|
+
DocFamilyToken,
|
|
20
|
+
DocToken,
|
|
21
|
+
RewindableConversationDocFamilyToken,
|
|
22
|
+
RewindableConversationDocToken,
|
|
23
|
+
SessionDocFamilyToken,
|
|
24
|
+
SessionDocToken,
|
|
25
|
+
TaskDocFamilyToken,
|
|
26
|
+
TaskDocToken,
|
|
27
|
+
)
|
|
28
|
+
from ..ids import ConversationId, EntryId, SubmissionId, TaskId
|
|
29
|
+
from ..json import JsonObject, JsonValue
|
|
30
|
+
from ..types import (
|
|
31
|
+
ConversationOwnership,
|
|
32
|
+
Cursor,
|
|
33
|
+
DocumentReader,
|
|
34
|
+
EntryDraft,
|
|
35
|
+
EntryQuery,
|
|
36
|
+
EntryRecord,
|
|
37
|
+
EntryWithHead,
|
|
38
|
+
InputDoneRecord,
|
|
39
|
+
InputUnansweredRecord,
|
|
40
|
+
MessageRecord,
|
|
41
|
+
NextTaskState,
|
|
42
|
+
Page,
|
|
43
|
+
SubmissionRecord,
|
|
44
|
+
TaskOutcome,
|
|
45
|
+
TaskRecord,
|
|
46
|
+
TaskState,
|
|
47
|
+
WatchHandle,
|
|
48
|
+
WriteDoneRecord,
|
|
49
|
+
WriteUnansweredRecord,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
if TYPE_CHECKING:
|
|
53
|
+
from ..session.transaction import Transaction
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class ContextView(TypedDict):
|
|
57
|
+
"""One conversation's committed transcript and the model context derived from it.
|
|
58
|
+
|
|
59
|
+
``head`` is the newest visible head marker, if any. ``entries`` is the raw active transcript,
|
|
60
|
+
``contributions`` holds each of their model messages after edits and excluded stop reasons, and
|
|
61
|
+
``messages`` is the provider request context: tool results ordered after their call, missing
|
|
62
|
+
ones synthesized, and the baseline system message first.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
head: EntryWithHead | None
|
|
66
|
+
entries: list[EntryRecord]
|
|
67
|
+
contributions: list[list[MessageRecord]]
|
|
68
|
+
messages: list[MessageRecord]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# ── Registry and extensions ─────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
ThinkingLevel = Literal["off", "minimal", "low", "medium", "high", "xhigh", "max"]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
ToolExecutionMode = Literal["parallel", "sequential"]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class ToolControl(TypedDict, total=False):
|
|
80
|
+
"""What a tool result asks the tool round to do."""
|
|
81
|
+
|
|
82
|
+
addTools: list[str]
|
|
83
|
+
terminate: Literal[True]
|
|
84
|
+
handoff: str
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class ToolDiagnostic(TypedDict, total=False):
|
|
88
|
+
"""Remark about a call for the model and the UI; never part of the tool's data."""
|
|
89
|
+
|
|
90
|
+
severity: Literal["info", "warn", "error"]
|
|
91
|
+
message: str
|
|
92
|
+
code: str
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class ToolExecutionResult(TypedDict, total=False):
|
|
96
|
+
"""Three channels: ``output`` for the model, ``structuredOutput`` for programs, ``details`` for UIs."""
|
|
97
|
+
|
|
98
|
+
output: list[JsonValue]
|
|
99
|
+
structuredOutput: JsonValue
|
|
100
|
+
isError: bool
|
|
101
|
+
details: JsonValue
|
|
102
|
+
diagnostics: list[ToolDiagnostic]
|
|
103
|
+
usage: dict[str, JsonValue]
|
|
104
|
+
control: ToolControl
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class NestedToolExecutionResult(TypedDict, total=False):
|
|
108
|
+
"""A nested call's result, for programs: no output for the model, and ``control`` does not apply."""
|
|
109
|
+
|
|
110
|
+
taskId: TaskId
|
|
111
|
+
structuredOutput: JsonValue
|
|
112
|
+
isError: bool
|
|
113
|
+
details: JsonValue
|
|
114
|
+
diagnostics: list[ToolDiagnostic]
|
|
115
|
+
usage: dict[str, JsonValue]
|
|
116
|
+
durationMs: int
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class ToolHookCall(TypedDict, total=False):
|
|
120
|
+
"""A tool call as hooks see it; a nested call adds ``parent``."""
|
|
121
|
+
|
|
122
|
+
type: Literal["toolCall"]
|
|
123
|
+
id: str
|
|
124
|
+
name: str
|
|
125
|
+
arguments: dict[str, JsonValue]
|
|
126
|
+
parent: dict[str, Any]
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class ToolHooks(TypedDict, total=False):
|
|
130
|
+
"""Hooks of the built-in tool task."""
|
|
131
|
+
|
|
132
|
+
beforeTool: Callable[..., Any]
|
|
133
|
+
afterTool: Callable[..., Any]
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class ToolExecutionApi(Protocol):
|
|
137
|
+
"""What a running tool may use: bounded output, durable commits, and Harness reads."""
|
|
138
|
+
|
|
139
|
+
task_id: TaskId
|
|
140
|
+
conversation_id: ConversationId
|
|
141
|
+
call_id: str
|
|
142
|
+
registry: RegistrySnapshot
|
|
143
|
+
models: Any
|
|
144
|
+
env: Any
|
|
145
|
+
output_window: Mapping[str, Any] | None
|
|
146
|
+
|
|
147
|
+
def agent(self, context: Context) -> Awaitable[Agent]: ...
|
|
148
|
+
|
|
149
|
+
def output(self, chunk: str | bytes, skipped: Mapping[str, Any] | None = None) -> None: ...
|
|
150
|
+
|
|
151
|
+
def retained_output(self) -> Mapping[str, Any]: ...
|
|
152
|
+
|
|
153
|
+
def diagnostic(self, diagnostic: Mapping[str, Any]) -> None: ...
|
|
154
|
+
|
|
155
|
+
def details(self, value: JsonValue, context: Context) -> Awaitable[None]: ...
|
|
156
|
+
|
|
157
|
+
def commit(self, change: Callable[..., Any], context: Context) -> Awaitable[Any]: ...
|
|
158
|
+
|
|
159
|
+
def memo(self, name: str, *args: Any) -> Awaitable[JsonValue | None]: ...
|
|
160
|
+
|
|
161
|
+
def create_task(
|
|
162
|
+
self, task: Any, input: JsonValue, options: Mapping[str, Any], context: Context
|
|
163
|
+
) -> Awaitable[TaskId]: ...
|
|
164
|
+
|
|
165
|
+
def get_task(self, id: TaskId, context: Context) -> Awaitable[TaskRecord | None]: ...
|
|
166
|
+
|
|
167
|
+
def wait_for_task(self, id: TaskId, context: Context) -> Awaitable[SettledTask]: ...
|
|
168
|
+
|
|
169
|
+
def conversation(self, id: ConversationId, context: Context) -> Awaitable[Any]: ...
|
|
170
|
+
|
|
171
|
+
def snapshot(self, token: Any, *args: Any) -> Awaitable[JsonObject | None]: ...
|
|
172
|
+
|
|
173
|
+
def snapshot_as_of(self, token: Any, *args: Any) -> Awaitable[JsonObject | None]: ...
|
|
174
|
+
|
|
175
|
+
def watch_doc(self, token: Any, *args: Any) -> Awaitable[Any]: ...
|
|
176
|
+
|
|
177
|
+
def execute_tool(
|
|
178
|
+
self, name: str, args: Mapping[str, Any], context: Context, options: Mapping[str, Any] | None = None
|
|
179
|
+
) -> Awaitable[NestedToolExecutionResult]: ...
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@dataclass(frozen=True, slots=True)
|
|
183
|
+
class ToolRegistration:
|
|
184
|
+
"""Executable tool registered in a registry, with the fields resolution and planning read."""
|
|
185
|
+
|
|
186
|
+
name: str
|
|
187
|
+
description: str = ""
|
|
188
|
+
parameters: Any = None
|
|
189
|
+
execute: Callable[..., Any] | None = None
|
|
190
|
+
replay: Literal["safe", "unsafe"] | None = None
|
|
191
|
+
execution_mode: ToolExecutionMode | None = None
|
|
192
|
+
structured_output_schema: Any = None
|
|
193
|
+
callers: Sequence[Literal["model", "tools"]] | None = None
|
|
194
|
+
deferred: bool = False
|
|
195
|
+
output_limits: Mapping[str, Any] | None = None
|
|
196
|
+
constrained_sampling: Any = None
|
|
197
|
+
prepare_arguments: Callable[..., Any] | None = None
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@dataclass(frozen=True, slots=True)
|
|
201
|
+
class PromptSection:
|
|
202
|
+
"""One system prompt section."""
|
|
203
|
+
|
|
204
|
+
key: str
|
|
205
|
+
render: Callable[..., Any]
|
|
206
|
+
tag: bool | None = None
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
HooksOf: TypeAlias = Any
|
|
210
|
+
"""Hook handler map a task definition declares; erased to ``Any`` in Python's runtime typing."""
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@dataclass(frozen=True, slots=True)
|
|
214
|
+
class HookRegistration:
|
|
215
|
+
"""Hook handlers matching tasks by name."""
|
|
216
|
+
|
|
217
|
+
task: str
|
|
218
|
+
handlers: Mapping[str, Any]
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@dataclass(frozen=True, slots=True)
|
|
222
|
+
class Wrap:
|
|
223
|
+
"""A pure wrapper of one tool or section name."""
|
|
224
|
+
|
|
225
|
+
tool: str | None = None
|
|
226
|
+
section: str | None = None
|
|
227
|
+
wrap: Callable[..., Any] | None = None
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
@dataclass(frozen=True, slots=True)
|
|
231
|
+
class Extension:
|
|
232
|
+
"""Named bundle of code installed in a registry and selected by conversations."""
|
|
233
|
+
|
|
234
|
+
name: str
|
|
235
|
+
tools: Sequence[ToolRegistration] = ()
|
|
236
|
+
sections: Sequence[PromptSection] = ()
|
|
237
|
+
hooks: Sequence[HookRegistration] = ()
|
|
238
|
+
wraps: Sequence[Wrap] = ()
|
|
239
|
+
tasks: Sequence[Any] = ()
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
class RegistrySnapshot(Protocol):
|
|
243
|
+
"""Immutable view of one published registry state."""
|
|
244
|
+
|
|
245
|
+
def installed(self) -> Sequence[Extension]: ...
|
|
246
|
+
|
|
247
|
+
def extension(self, name: str) -> Extension | None: ...
|
|
248
|
+
|
|
249
|
+
def tools(self) -> Sequence[Mapping[str, Any]]: ...
|
|
250
|
+
|
|
251
|
+
def sections(self) -> Sequence[Mapping[str, Any]]: ...
|
|
252
|
+
|
|
253
|
+
def tasks(self) -> Sequence[Any]: ...
|
|
254
|
+
|
|
255
|
+
def task(self, name: str) -> Any | None: ...
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class RegistryReader(Protocol):
|
|
259
|
+
"""Read side of a registry consumed by a Harness."""
|
|
260
|
+
|
|
261
|
+
def snapshot(self) -> RegistrySnapshot: ...
|
|
262
|
+
|
|
263
|
+
def subscribe(self, listener: Callable[[], Any]) -> Callable[[], None]: ...
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
class Registry(RegistryReader, Protocol):
|
|
267
|
+
"""Application-owned registry of extensions."""
|
|
268
|
+
|
|
269
|
+
def install(self, extension: Extension) -> None: ...
|
|
270
|
+
|
|
271
|
+
def uninstall(self, extension: Extension) -> None: ...
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
# ── Agent and settings ──────────────────────────────────────────────────────
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
class ModelRef(TypedDict):
|
|
278
|
+
provider: str
|
|
279
|
+
modelId: str
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
class AgentState(TypedDict, total=False):
|
|
283
|
+
"""Stored choices of one conversation; names, not objects."""
|
|
284
|
+
|
|
285
|
+
model: ModelRef
|
|
286
|
+
thinkingLevel: ThinkingLevel
|
|
287
|
+
extensions: list[str] | dict[str, list[str]]
|
|
288
|
+
tools: list[str] | dict[str, list[str]]
|
|
289
|
+
modelTools: list[str] | dict[str, list[str]]
|
|
290
|
+
instructions: str
|
|
291
|
+
cwd: str
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
class AgentChange(TypedDict, total=False):
|
|
295
|
+
"""A change to ``pi.agent``: a given field replaces the stored one, ``None`` clears it."""
|
|
296
|
+
|
|
297
|
+
model: ModelRef | None
|
|
298
|
+
thinkingLevel: ThinkingLevel | None
|
|
299
|
+
extensions: Any
|
|
300
|
+
tools: Any
|
|
301
|
+
modelTools: Any
|
|
302
|
+
instructions: str | None
|
|
303
|
+
cwd: str | None
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
@dataclass(slots=True)
|
|
307
|
+
class Agent:
|
|
308
|
+
"""A conversation's agent resolved against a registry snapshot and the settings."""
|
|
309
|
+
|
|
310
|
+
model: ModelRef | None = None
|
|
311
|
+
thinking_level: ThinkingLevel = "off"
|
|
312
|
+
extensions: Sequence[Extension] = ()
|
|
313
|
+
tools: Sequence[ToolRegistration] = ()
|
|
314
|
+
callable: Sequence[ToolRegistration] = ()
|
|
315
|
+
deferred: Sequence[ToolRegistration] = ()
|
|
316
|
+
sections: Sequence[PromptSection] = ()
|
|
317
|
+
instructions: str | None = None
|
|
318
|
+
cwd: str | None = None
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
@dataclass(frozen=True, slots=True)
|
|
322
|
+
class PromptInput:
|
|
323
|
+
"""Input to system prompt section rendering for one request preparation."""
|
|
324
|
+
|
|
325
|
+
conversation_id: ConversationId
|
|
326
|
+
agent: Agent
|
|
327
|
+
env: Any
|
|
328
|
+
shown: Mapping[str, str]
|
|
329
|
+
read: DocumentReader
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
class ConversationStreamOptions(TypedDict, total=False):
|
|
333
|
+
"""Curated pi-ai request options; absent fields use the provider defaults."""
|
|
334
|
+
|
|
335
|
+
transport: str
|
|
336
|
+
timeout_ms: int
|
|
337
|
+
max_retries: int
|
|
338
|
+
max_retry_delay_ms: int
|
|
339
|
+
headers: dict[str, str]
|
|
340
|
+
metadata: JsonObject
|
|
341
|
+
cache_retention: str
|
|
342
|
+
deferred: bool | dict[str, str]
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
class ConversationRetryPolicy(TypedDict, total=False):
|
|
346
|
+
"""Durable generation attempt retries."""
|
|
347
|
+
|
|
348
|
+
enabled: bool
|
|
349
|
+
max_retries: int
|
|
350
|
+
base_delay_ms: int
|
|
351
|
+
max_agent_delay_ms: int
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
class CompactionPolicy(TypedDict, total=False):
|
|
355
|
+
"""Automatic compaction thresholds; manual compaction ignores ``enabled``."""
|
|
356
|
+
|
|
357
|
+
enabled: bool
|
|
358
|
+
reserve_tokens: int
|
|
359
|
+
keep_recent_tokens: int
|
|
360
|
+
background_tokens: int
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
CompactionReason = Literal["manual", "threshold", "overflow"]
|
|
364
|
+
"""What started a compaction task."""
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
class CompactionResult(TypedDict, total=False):
|
|
368
|
+
"""A blocking summary's ``entryId``, or a conversation-owned summary's ``submissionId``."""
|
|
369
|
+
|
|
370
|
+
entryId: EntryId
|
|
371
|
+
submissionId: SubmissionId
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
class ProgressPolicy(TypedDict, total=False):
|
|
375
|
+
"""How often running progress is committed."""
|
|
376
|
+
|
|
377
|
+
partial_interval_ms: int
|
|
378
|
+
output_interval_ms: int
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
class GenerationHooks(TypedDict, total=False):
|
|
382
|
+
"""Hooks of the built-in generation task."""
|
|
383
|
+
|
|
384
|
+
beforeRequest: Callable[..., Any]
|
|
385
|
+
afterResponse: Callable[..., Any]
|
|
386
|
+
onYield: Callable[..., Any]
|
|
387
|
+
afterTools: Callable[..., Any]
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
class CompactionHooks(TypedDict, total=False):
|
|
391
|
+
"""Hooks of the built-in compaction task."""
|
|
392
|
+
|
|
393
|
+
beforeCompact: Callable[..., Any]
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
class EnvTarget(TypedDict):
|
|
397
|
+
"""What ``HarnessOptions.env`` builds an environment for."""
|
|
398
|
+
|
|
399
|
+
conversation_id: ConversationId
|
|
400
|
+
cwd: NotRequired[str]
|
|
401
|
+
read: DocumentReader
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
class HookApi(Protocol):
|
|
405
|
+
"""What a hook may use: committed reads, the task ID, and the task's memos."""
|
|
406
|
+
|
|
407
|
+
task_id: TaskId
|
|
408
|
+
conversation_id: ConversationId
|
|
409
|
+
models: Any
|
|
410
|
+
|
|
411
|
+
async def snapshot(self, token: AnyDocToken, *args: Any) -> JsonObject | None: ...
|
|
412
|
+
|
|
413
|
+
async def snapshot_as_of(self, token: AnyDocToken, *args: Any) -> JsonObject | None: ...
|
|
414
|
+
|
|
415
|
+
async def memo(self, name: str, *args: Any) -> JsonValue | None: ...
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
HookResult = Any
|
|
419
|
+
"""A hook's value, absent when it declines, or an awaitable of it."""
|
|
420
|
+
|
|
421
|
+
ConversationInit = Callable[..., Any]
|
|
422
|
+
"""Runs inside the creating commit after the creation hook and the agent change."""
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
class ConversationCreateOptions(TypedDict):
|
|
426
|
+
"""Options of ``createConversation`` and ``fork``."""
|
|
427
|
+
|
|
428
|
+
ownership: ConversationOwnership
|
|
429
|
+
agent: NotRequired[AgentChange]
|
|
430
|
+
init: NotRequired[ConversationInit]
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
class HarnessSettings(TypedDict, total=False):
|
|
434
|
+
"""Harness-wide run policy, read at every resolution and never copied."""
|
|
435
|
+
|
|
436
|
+
extensions: Sequence[Extension]
|
|
437
|
+
stream: ConversationStreamOptions
|
|
438
|
+
retry: ConversationRetryPolicy
|
|
439
|
+
compaction: CompactionPolicy
|
|
440
|
+
progress: ProgressPolicy
|
|
441
|
+
tool_execution: Literal["parallel", "sequential"]
|
|
442
|
+
steering_mode: QueueMode
|
|
443
|
+
follow_up_mode: QueueMode
|
|
444
|
+
context_retention_ms: int
|
|
445
|
+
document_retention_ms: int | float
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
@dataclass(slots=True)
|
|
449
|
+
class Settings:
|
|
450
|
+
"""Resolved Harness settings: every field over its built-in default."""
|
|
451
|
+
|
|
452
|
+
extensions: Sequence[Extension] | None = None
|
|
453
|
+
stream: dict[str, Any] = field(default_factory=dict)
|
|
454
|
+
retry: dict[str, Any] = field(default_factory=dict)
|
|
455
|
+
compaction: dict[str, Any] = field(default_factory=dict)
|
|
456
|
+
progress: dict[str, Any] = field(default_factory=dict)
|
|
457
|
+
tool_execution: Literal["parallel", "sequential"] = "parallel"
|
|
458
|
+
steering_mode: Literal["all", "one-at-a-time"] = "one-at-a-time"
|
|
459
|
+
follow_up_mode: Literal["all", "one-at-a-time"] = "one-at-a-time"
|
|
460
|
+
context_retention_ms: int = 600_000
|
|
461
|
+
document_retention_ms: int | float = 600_000
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
# ── Task runtime and inspection ─────────────────────────────────────────────
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
class SettledTask(TypedDict):
|
|
468
|
+
"""A task record that has reached a terminal state."""
|
|
469
|
+
|
|
470
|
+
id: TaskId
|
|
471
|
+
conversationId: ConversationId
|
|
472
|
+
kind: str
|
|
473
|
+
version: int
|
|
474
|
+
input: JsonValue
|
|
475
|
+
owner: NotRequired[TaskId]
|
|
476
|
+
background: bool
|
|
477
|
+
abortRequested: bool
|
|
478
|
+
abortReason: NotRequired[Literal["restart"]]
|
|
479
|
+
abandonOnRestart: NotRequired[Literal[True]]
|
|
480
|
+
startedAt: NotRequired[int]
|
|
481
|
+
endedAt: NotRequired[int]
|
|
482
|
+
state: TaskState
|
|
483
|
+
memos: NotRequired[Never]
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
AnyTask = Any
|
|
487
|
+
"""Erased executable task definition stored in the registry."""
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
class HookRunner(Protocol):
|
|
491
|
+
"""Runs every selected extension's handler for one hook name, in extension order."""
|
|
492
|
+
|
|
493
|
+
async def each(self, name: str, invoke: Callable[[Any], Awaitable[None] | None]) -> None: ...
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
class TaskRuntime(Protocol):
|
|
497
|
+
"""What a phase handler may do: committed reads, gated commits, waits, and the Harness clock."""
|
|
498
|
+
|
|
499
|
+
task_id: TaskId
|
|
500
|
+
conversation_id: ConversationId
|
|
501
|
+
signal: AbortSignal | None
|
|
502
|
+
registry: RegistrySnapshot
|
|
503
|
+
settings: Settings
|
|
504
|
+
models: Any
|
|
505
|
+
hooks: HookRunner
|
|
506
|
+
|
|
507
|
+
async def agent(self, context: Context) -> Agent: ...
|
|
508
|
+
|
|
509
|
+
async def env(self, context: Context) -> Any: ...
|
|
510
|
+
|
|
511
|
+
async def commit(
|
|
512
|
+
self,
|
|
513
|
+
change: Callable[..., NextTaskState | None | Awaitable[NextTaskState | None]],
|
|
514
|
+
context: Context,
|
|
515
|
+
) -> None: ...
|
|
516
|
+
|
|
517
|
+
async def memo(self, name: str, *args: Any) -> JsonValue | None: ...
|
|
518
|
+
|
|
519
|
+
async def conversation(self, id: ConversationId, context: Context) -> Any: ...
|
|
520
|
+
|
|
521
|
+
async def get_task(self, id: TaskId, context: Context) -> TaskRecord | None: ...
|
|
522
|
+
|
|
523
|
+
async def wait_for_task(self, id: TaskId, context: Context) -> SettledTask: ...
|
|
524
|
+
|
|
525
|
+
async def abort_owned(self, id: TaskId, context: Context) -> None: ...
|
|
526
|
+
|
|
527
|
+
async def owned_tasks(self, context: Context) -> list[TaskRecord]: ...
|
|
528
|
+
|
|
529
|
+
async def outcomes(self, ids: Sequence[TaskId], context: Context) -> list[TaskOutcome]: ...
|
|
530
|
+
|
|
531
|
+
async def entry(self, *args: Any) -> EntryRecord | None: ...
|
|
532
|
+
|
|
533
|
+
async def context(self, conversation_id: ConversationId, context: Context, *args: Any) -> ContextView: ...
|
|
534
|
+
|
|
535
|
+
def now(self) -> int: ...
|
|
536
|
+
|
|
537
|
+
def report(self, error: BaseException) -> None: ...
|
|
538
|
+
|
|
539
|
+
async def sleep(self, until: int, context: Context) -> None: ...
|
|
540
|
+
|
|
541
|
+
async def watch_doc(self, token: Any, *args: Any) -> Any: ...
|
|
542
|
+
|
|
543
|
+
async def snapshot(self, token: Any, *args: Any) -> JsonObject | None: ...
|
|
544
|
+
|
|
545
|
+
async def snapshot_as_of(self, token: Any, *args: Any) -> JsonObject | None: ...
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
class TaskInspection(TypedDict):
|
|
549
|
+
"""A live task and what the scheduler would do with it under the current registry."""
|
|
550
|
+
|
|
551
|
+
record: TaskRecord
|
|
552
|
+
state: dict[str, Any]
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
class HarnessInspection(TypedDict):
|
|
556
|
+
"""Point-in-time view of live work."""
|
|
557
|
+
|
|
558
|
+
scheduling: Literal["paused", "running", "closing"]
|
|
559
|
+
tasks: list[TaskInspection]
|
|
560
|
+
submissions: list[SubmissionRecord]
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
# ── Submissions ─────────────────────────────────────────────────────────────
|
|
564
|
+
|
|
565
|
+
QueueMode = Literal["all", "one-at-a-time"]
|
|
566
|
+
"""How many queued items of one mode a boundary selects."""
|
|
567
|
+
|
|
568
|
+
UserInput = JsonValue
|
|
569
|
+
"""Content of a user message: a string or an ordered content list."""
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
class InputEntry(TypedDict):
|
|
573
|
+
"""The kind and data an input is placed as, instead of a ``pi.user`` entry."""
|
|
574
|
+
|
|
575
|
+
kind: str
|
|
576
|
+
data: NotRequired[JsonValue]
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
class InputSubmissionDraft(TypedDict, total=False):
|
|
580
|
+
"""An input submission: admitted as one user-message entry and answered by a run."""
|
|
581
|
+
|
|
582
|
+
type: Literal["input"]
|
|
583
|
+
content: UserInput
|
|
584
|
+
entry: InputEntry
|
|
585
|
+
request_id: str
|
|
586
|
+
requestId: str
|
|
587
|
+
when_busy: Literal["steer", "followUp", "reject"]
|
|
588
|
+
whenBusy: Literal["steer", "followUp", "reject"]
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
class WriteSubmissionDraft(TypedDict, total=False):
|
|
592
|
+
"""A passive entry write: appended when a boundary selects it, or at once when display-only."""
|
|
593
|
+
|
|
594
|
+
type: Literal["write"]
|
|
595
|
+
entry: EntryDraft
|
|
596
|
+
request_id: str
|
|
597
|
+
requestId: str
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
SubmissionDraft: TypeAlias = InputSubmissionDraft | WriteSubmissionDraft
|
|
601
|
+
"""Either an input or a passive entry write."""
|
|
602
|
+
|
|
603
|
+
SettledSubmissionRecord: TypeAlias = (
|
|
604
|
+
InputDoneRecord | InputUnansweredRecord | WriteDoneRecord | WriteUnansweredRecord
|
|
605
|
+
)
|
|
606
|
+
"""A submission that reached ``done`` or ``unanswered``."""
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
# ── Conversations ───────────────────────────────────────────────────────────
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
class ConversationAbortOptions(TypedDict, total=False):
|
|
613
|
+
"""Options of ``Conversation.abort()``: ``background`` also aborts background work."""
|
|
614
|
+
|
|
615
|
+
background: bool
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
class ConversationHandle(Protocol):
|
|
619
|
+
"""Invocation-bound conversation operations for tasks and tools."""
|
|
620
|
+
|
|
621
|
+
id: ConversationId
|
|
622
|
+
|
|
623
|
+
async def submit(self, submission: Any, context: Context) -> Any: ...
|
|
624
|
+
|
|
625
|
+
async def abort(self, context: Context, options: Mapping[str, Any] | None = None) -> None: ...
|
|
626
|
+
|
|
627
|
+
async def wait_for_idle(self, context: Context) -> None: ...
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
class Conversation(Protocol):
|
|
631
|
+
"""Stateless handle for one conversation, bound to the Harness that returned it."""
|
|
632
|
+
|
|
633
|
+
id: ConversationId
|
|
634
|
+
|
|
635
|
+
async def submit(self, submission: SubmissionDraft, context: Context) -> Submission: ...
|
|
636
|
+
|
|
637
|
+
async def agent(self, context: Context) -> Agent: ...
|
|
638
|
+
|
|
639
|
+
async def configure(self, change: AgentChange, context: Context) -> None: ...
|
|
640
|
+
|
|
641
|
+
async def commit(self, change: Callable[[Transaction], Any], context: Context) -> Any: ...
|
|
642
|
+
|
|
643
|
+
async def context(
|
|
644
|
+
self, context: Context, options: Mapping[str, Any] | None = None
|
|
645
|
+
) -> ContextView: ...
|
|
646
|
+
|
|
647
|
+
async def entries(
|
|
648
|
+
self,
|
|
649
|
+
query: EntryQuery,
|
|
650
|
+
limit: int,
|
|
651
|
+
cursor: Cursor | None,
|
|
652
|
+
context: Context,
|
|
653
|
+
) -> Page[EntryRecord, Cursor]: ...
|
|
654
|
+
|
|
655
|
+
async def fork(
|
|
656
|
+
self, at: EntryId, options: ConversationCreateOptions, context: Context
|
|
657
|
+
) -> Conversation: ...
|
|
658
|
+
|
|
659
|
+
async def compact(self, instructions: str | None, context: Context) -> TaskId: ...
|
|
660
|
+
|
|
661
|
+
async def reset(self, handoff: str | None, context: Context) -> None: ...
|
|
662
|
+
|
|
663
|
+
async def abort(
|
|
664
|
+
self, context: Context, options: ConversationAbortOptions | None = None
|
|
665
|
+
) -> None: ...
|
|
666
|
+
|
|
667
|
+
async def wait_for_idle(self, context: Context) -> None: ...
|
|
668
|
+
|
|
669
|
+
async def view_state(self, context: Context) -> Any: ...
|
|
670
|
+
|
|
671
|
+
async def watch(self, context: Context) -> Any: ...
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
ConversationWatch: TypeAlias = WatchHandle[Any]
|
|
675
|
+
"""A serialized exact-frame watch of one conversation's view."""
|
|
676
|
+
|
|
677
|
+
|
|
678
|
+
class Submission(Protocol):
|
|
679
|
+
"""An admitted submission, awaitable until it settles and abortable while queued."""
|
|
680
|
+
|
|
681
|
+
id: SubmissionId
|
|
682
|
+
|
|
683
|
+
async def status(self, context: Context) -> SubmissionRecord: ...
|
|
684
|
+
|
|
685
|
+
async def wait(self, context: Context) -> SettledSubmissionRecord: ...
|
|
686
|
+
|
|
687
|
+
async def abort(self, context: Context) -> Literal["aborted", "already_placed", "settled"]: ...
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
class HarnessOptions(TypedDict, total=False):
|
|
691
|
+
"""Options of ``Harness.open()``."""
|
|
692
|
+
|
|
693
|
+
models: Any
|
|
694
|
+
registry: RegistryReader
|
|
695
|
+
settings: Any
|
|
696
|
+
env: Callable[..., Any]
|
|
697
|
+
conversation_created: Callable[..., Any]
|
|
698
|
+
now: Callable[[], int]
|
|
699
|
+
on_report: Callable[[BaseException], Any]
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
T = TypeVar("T")
|
|
703
|
+
|
|
704
|
+
__all__ = [
|
|
705
|
+
"Agent",
|
|
706
|
+
"AgentChange",
|
|
707
|
+
"AgentState",
|
|
708
|
+
"AnyTask",
|
|
709
|
+
"CommonDocDefinition",
|
|
710
|
+
"CompactionHooks",
|
|
711
|
+
"CompactionPolicy",
|
|
712
|
+
"CompactionReason",
|
|
713
|
+
"CompactionResult",
|
|
714
|
+
"ContextView",
|
|
715
|
+
"Conversation",
|
|
716
|
+
"ConversationAbortOptions",
|
|
717
|
+
"ConversationCreateOptions",
|
|
718
|
+
"ConversationDocFamilyToken",
|
|
719
|
+
"ConversationDocToken",
|
|
720
|
+
"ConversationHandle",
|
|
721
|
+
"ConversationInit",
|
|
722
|
+
"ConversationRetryPolicy",
|
|
723
|
+
"ConversationStreamOptions",
|
|
724
|
+
"DocFamilyToken",
|
|
725
|
+
"DocToken",
|
|
726
|
+
"EnvTarget",
|
|
727
|
+
"Extension",
|
|
728
|
+
"GenerationHooks",
|
|
729
|
+
"HarnessInspection",
|
|
730
|
+
"HarnessOptions",
|
|
731
|
+
"HarnessSettings",
|
|
732
|
+
"HookApi",
|
|
733
|
+
"HookRegistration",
|
|
734
|
+
"HookResult",
|
|
735
|
+
"HookRunner",
|
|
736
|
+
"HooksOf",
|
|
737
|
+
"InputEntry",
|
|
738
|
+
"InputSubmissionDraft",
|
|
739
|
+
"ModelRef",
|
|
740
|
+
"NestedToolExecutionResult",
|
|
741
|
+
"ProgressPolicy",
|
|
742
|
+
"PromptInput",
|
|
743
|
+
"PromptSection",
|
|
744
|
+
"QueueMode",
|
|
745
|
+
"Registry",
|
|
746
|
+
"RegistryReader",
|
|
747
|
+
"RegistrySnapshot",
|
|
748
|
+
"RewindableConversationDocFamilyToken",
|
|
749
|
+
"RewindableConversationDocToken",
|
|
750
|
+
"SessionDocFamilyToken",
|
|
751
|
+
"SessionDocToken",
|
|
752
|
+
"Settings",
|
|
753
|
+
"SettledSubmissionRecord",
|
|
754
|
+
"SettledTask",
|
|
755
|
+
"Submission",
|
|
756
|
+
"SubmissionDraft",
|
|
757
|
+
"TaskDocFamilyToken",
|
|
758
|
+
"TaskDocToken",
|
|
759
|
+
"TaskInspection",
|
|
760
|
+
"TaskRuntime",
|
|
761
|
+
"ThinkingLevel",
|
|
762
|
+
"ToolControl",
|
|
763
|
+
"ToolDiagnostic",
|
|
764
|
+
"ToolExecutionApi",
|
|
765
|
+
"ToolExecutionMode",
|
|
766
|
+
"ToolExecutionResult",
|
|
767
|
+
"ToolHookCall",
|
|
768
|
+
"ToolHooks",
|
|
769
|
+
"ToolRegistration",
|
|
770
|
+
"UserInput",
|
|
771
|
+
"Wrap",
|
|
772
|
+
]
|