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.
Files changed (90) hide show
  1. pi_durable/__init__.py +320 -0
  2. pi_durable/context.py +328 -0
  3. pi_durable/delta.py +269 -0
  4. pi_durable/documents.py +366 -0
  5. pi_durable/entries.py +66 -0
  6. pi_durable/env/__init__.py +408 -0
  7. pi_durable/env/decode.py +48 -0
  8. pi_durable/env/line_scan.py +190 -0
  9. pi_durable/env/posix.py +1547 -0
  10. pi_durable/errors.py +89 -0
  11. pi_durable/harness/__init__.py +1 -0
  12. pi_durable/harness/agent.py +344 -0
  13. pi_durable/harness/compaction.py +673 -0
  14. pi_durable/harness/context.py +515 -0
  15. pi_durable/harness/define.py +111 -0
  16. pi_durable/harness/events.py +766 -0
  17. pi_durable/harness/generation.py +1079 -0
  18. pi_durable/harness/harness.py +570 -0
  19. pi_durable/harness/inbox.py +255 -0
  20. pi_durable/harness/live.py +374 -0
  21. pi_durable/harness/output.py +467 -0
  22. pi_durable/harness/prompt.py +288 -0
  23. pi_durable/harness/provider.py +78 -0
  24. pi_durable/harness/registry.py +136 -0
  25. pi_durable/harness/scheduler.py +1977 -0
  26. pi_durable/harness/submissions.py +357 -0
  27. pi_durable/harness/task_graph.py +334 -0
  28. pi_durable/harness/tool.py +1365 -0
  29. pi_durable/harness/types.py +772 -0
  30. pi_durable/harness/usage.py +88 -0
  31. pi_durable/harness/util.py +204 -0
  32. pi_durable/harness/validation.py +460 -0
  33. pi_durable/harness/view.py +324 -0
  34. pi_durable/ids.py +63 -0
  35. pi_durable/json.py +186 -0
  36. pi_durable/providers/__init__.py +362 -0
  37. pi_durable/providers/anthropic.py +603 -0
  38. pi_durable/providers/base.py +97 -0
  39. pi_durable/providers/json_parse.py +117 -0
  40. pi_durable/providers/openai.py +574 -0
  41. pi_durable/providers/request.py +35 -0
  42. pi_durable/providers/retry.py +127 -0
  43. pi_durable/providers/stream.py +183 -0
  44. pi_durable/providers/transcript.py +224 -0
  45. pi_durable/providers/usage.py +58 -0
  46. pi_durable/py.typed +0 -0
  47. pi_durable/search.py +115 -0
  48. pi_durable/session/__init__.py +5 -0
  49. pi_durable/session/drafts.py +762 -0
  50. pi_durable/session/forks.py +109 -0
  51. pi_durable/session/observation.py +374 -0
  52. pi_durable/session/session.py +1193 -0
  53. pi_durable/session/transaction.py +1088 -0
  54. pi_durable/storage/__init__.py +5 -0
  55. pi_durable/storage/jsonl/__init__.py +15 -0
  56. pi_durable/storage/jsonl/storage.py +958 -0
  57. pi_durable/storage/memory.py +889 -0
  58. pi_durable/storage/scan.py +57 -0
  59. pi_durable/storage/sqlite/__init__.py +26 -0
  60. pi_durable/storage/sqlite/database.py +62 -0
  61. pi_durable/storage/sqlite/migrations.py +191 -0
  62. pi_durable/storage/sqlite/posix.py +314 -0
  63. pi_durable/storage/sqlite/schema.py +97 -0
  64. pi_durable/storage/sqlite/storage.py +1130 -0
  65. pi_durable/tasks.py +89 -0
  66. pi_durable/testing/__init__.py +41 -0
  67. pi_durable/testing/assertions.py +44 -0
  68. pi_durable/testing/env_conformance.py +828 -0
  69. pi_durable/testing/runner.py +64 -0
  70. pi_durable/testing/storage_conformance.py +2008 -0
  71. pi_durable/testing/types.py +99 -0
  72. pi_durable/tool_search/__init__.py +159 -0
  73. pi_durable/tools/__init__.py +81 -0
  74. pi_durable/tools/_values.py +43 -0
  75. pi_durable/tools/bash.py +250 -0
  76. pi_durable/tools/edit.py +214 -0
  77. pi_durable/tools/edit_diff.py +452 -0
  78. pi_durable/tools/env.py +19 -0
  79. pi_durable/tools/file_mutation_queue.py +68 -0
  80. pi_durable/tools/image.py +248 -0
  81. pi_durable/tools/image_processor.py +77 -0
  82. pi_durable/tools/path_utils.py +111 -0
  83. pi_durable/tools/read.py +506 -0
  84. pi_durable/tools/write.py +71 -0
  85. pi_durable/truncate.py +185 -0
  86. pi_durable/types.py +1297 -0
  87. pi_durable_python-1.3.0.dist-info/METADATA +276 -0
  88. pi_durable_python-1.3.0.dist-info/RECORD +90 -0
  89. pi_durable_python-1.3.0.dist-info/WHEEL +4 -0
  90. 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
+ ]