312 lines
18 KiB
Markdown
312 lines
18 KiB
Markdown
# Execution Controls For Coordinated Turns
|
|
|
|
Status: all four design decisions accepted; ready for API and staged
|
|
implementation planning from v0.22.0. No code changes are authorized by this
|
|
roadmap. Public names and signatures are not yet selected.
|
|
|
|
## Purpose
|
|
|
|
Enable applications using `Engine.Coordinate` to select a fresh timeout for each
|
|
continuation and retain the exact prepared request before generation. These are
|
|
execution capabilities useful for bounded operations and optional diagnostics,
|
|
without assumptions about prompt content or application workflows.
|
|
|
|
[Audita's request](audita-request.md) supplies the motivating requirements.
|
|
Audita already uses one child context across explicit preparation and execution,
|
|
and retains prepared details for diagnostics. The goal is to make those controls
|
|
available during coordinator-owned execution without requiring a downstream
|
|
replacement for PromptKit's turn loop.
|
|
|
|
PromptKit owns history assembly, execution, cancellation propagation, and
|
|
observations. Applications select budgets, interpret results, run validators,
|
|
schedule branches, and own diagnostic destinations, disclosure policy, and write
|
|
failure handling. This work adds no artifact store or reporting framework.
|
|
|
|
## Current Baseline
|
|
|
|
[PromptKit v0.22.0](../releases/v0.22.0.md) supplies captured exchanges,
|
|
continuation, sequential coordination, and explicit execution groups.
|
|
|
|
- The [coordinator](../../coordinate.go) attaches the current exchange and composes
|
|
request/coordinator observers, then calls `Engine.Run` with the parent context.
|
|
Its callback returns a request, not an execution context or prepared handle.
|
|
- Ordinary [run execution](../../internal/usecase/runner.go) resolves the target,
|
|
obtains logical admission, completes preparation, and generates and validates
|
|
the response. Admission is released before the next-turn callback.
|
|
- Explicit [prepared execution](../../prepared_execution.go) already supplies
|
|
detached `Details()` and executes the retained snapshot. Preparation and
|
|
execution are separate calls; preparation does not reserve backend capacity.
|
|
- Ordinary [observations](../internal/observation.md) are metadata-only. Ordinary
|
|
runs observe preparation failures; split preparation itself emits no execution
|
|
events. A claimed prepared handle starts its observation lifecycle at execution.
|
|
|
|
A timeout around the entire coordinator includes all turns and application
|
|
callbacks. Provider timeouts cover a different scope. Neither provides a fresh
|
|
whole-operation budget for each selected turn. Separately calling `Prepare`
|
|
would duplicate preparation and would not bind the inspected details to the
|
|
request actually executed. An exchange cannot supply a failed turn's prepared
|
|
request when execution returns no result.
|
|
|
|
## Target End State
|
|
|
|
The selected approach is optional per-request controls honored by ordinary `Run`
|
|
and therefore by `Coordinate`. The accepted decisions below specify their
|
|
lifecycle; the coordinator does not delegate execution to an application executor.
|
|
|
|
### Optional Operation Timeout
|
|
|
|
An application can select a duration for each requested continuation. When that
|
|
continuation starts, establish a fresh child deadline before preparation and use
|
|
it for the entire operation: preparation, admission waiting, retries, generation,
|
|
structural validation, and structural repair, including the prepared-details
|
|
notification. Do not restart the budget between phases or attempts.
|
|
|
|
The selected timeout covers work according to the entry point:
|
|
|
|
| Entry point | Selected timeout behavior |
|
|
| --- | --- |
|
|
| `Run`, including coordinated turns | Starts before preparation and covers preparation and execution. |
|
|
| `PrepareExecution` | Retains the duration for later execution; preparation uses its supplied context. |
|
|
| `RunPrepared` | Starts the retained duration when invoked, covering the remaining execution. |
|
|
|
|
Idle time between split preparation and execution does not consume the retained
|
|
execution budget. Applications wanting one budget across both calls can pass the
|
|
same caller-created deadline context through them. `Prepare` and `ValidateRequest`
|
|
continue using their supplied contexts without starting the execution timeout.
|
|
|
|
Respect parent cancellation and earlier parent deadlines. A local timeout ends
|
|
the affected operation and coordination under the existing operational-error
|
|
rule; it does not cancel the parent or siblings. Release child-context resources
|
|
before returning to the application's next-turn callback. Assessment calls from
|
|
that callback retain their own application-selected contexts and budgets.
|
|
|
|
Determine the execution result and any local-timeout identity before publishing
|
|
the terminal observation, so the returned error and observed outcome agree.
|
|
A slow terminal observer or subsequent child-context cleanup must not
|
|
retroactively turn a completed operation into a timeout. Preserve the existing
|
|
coordinator behavior for completed responses, parent cancellation before
|
|
next-turn callback delivery, and the single undelivered response. No automatic
|
|
replay or new recovery policy is added.
|
|
|
|
No selected timeout means existing context behavior. This adds no framework-wide
|
|
recommended duration or mandatory cap. Additional-turn, transport-retry,
|
|
structural-repair, and provider-generation budgets keep their existing meanings.
|
|
Cancellation remains cooperative; a deadline does not forcibly terminate
|
|
application callbacks or injected collaborators that ignore their context, or
|
|
guarantee that the public call returns by that deadline.
|
|
|
|
### Opt-In Prepared Details Before Generation
|
|
|
|
Provide explicitly selected access to caller-owned details of the logical request
|
|
selected for execution, after successful preparation and before its first
|
|
generation attempt. Reuse the existing prepared-details representation and
|
|
credential redaction where practical. Include assembled captured history, rendered
|
|
follow-up messages, caller-appended messages, and ordinary resolved execution
|
|
metadata.
|
|
|
|
Use the execution's prepared snapshot rather than rendering or reading inputs
|
|
again. Retaining or modifying delivered details must not mutate the execution.
|
|
Once delivered, details remain available to the application after provider failure
|
|
or cancellation even when no ordinary result or exchange is returned. Diagnostic
|
|
access is available only when execution reaches the prepared-notification point,
|
|
not for every failed operation. Preparation failure requires no complete snapshot;
|
|
ordinary admission failure or timeout may occur before preparation completes.
|
|
Do not add rendering or rearrange admission solely to manufacture diagnostics for
|
|
those failures. Cancellation before notification does not promise delivery;
|
|
follow the context checks in [Decision 3](#3-what-is-the-prepared-details-callback-contract).
|
|
|
|
This access exposes logical prepared content, not raw HTTP envelopes or internal
|
|
structural-repair requests. Keep it separate from metadata-only observers and
|
|
execution-group summaries. It is not another next-turn callback: ordinary
|
|
execution may already hold backend admission at the notification point.
|
|
|
|
### Compatibility And Composition
|
|
|
|
Applications selecting neither capability retain existing behavior. Preserve
|
|
conversation assembly, capture, final-turn delivery, branching, and grouping
|
|
contracts. Request observers, coordinator observers, and explicitly selected
|
|
groups continue receiving ordinary execution facts without duplicate lifecycle
|
|
events or counting attempts twice.
|
|
|
|
Preserve ordinary admission, preparation-failure observation, and ownership
|
|
behavior. A simple replacement of ordinary `Run` with the public
|
|
`PrepareExecution`/`RunPrepared` sequence would change those behaviors. This does
|
|
not freeze the internal call structure: consolidation or refactoring is welcome
|
|
where it preserves the selected lifecycle.
|
|
|
|
Document which controls each entry point applies or retains. Execution-only
|
|
controls need not take effect during `Prepare` or `ValidateRequest`; accepting
|
|
request-shaped input there does not itself justify rejecting such controls or
|
|
starting a timer. Follow the accepted split-preparation and callback semantics.
|
|
|
|
## Work From The Baseline
|
|
|
|
1. **Design the public API.** Write small ordinary-run, coordinated-turn, and
|
|
split-preparation usage sketches under the accepted lifecycle below. Specify
|
|
omitted/zero/negative timeout handling using ordinary duration conventions,
|
|
and express the selected callback timing and cleanup in the public contract.
|
|
Document the entry-point distinctions and conditional diagnostic availability.
|
|
Choose names and signatures without adding a broader lifecycle-hook framework.
|
|
2. **Add operation-budget handling.** Establish and clean up the child scope at
|
|
the selected execution boundary. Preserve earlier parent deadlines and public
|
|
error identity through facade mapping and terminal observation classification.
|
|
Determine the outcome before terminal delivery and prevent observer latency
|
|
or child-context cleanup from changing it afterward.
|
|
Ensure cleanup does not affect the coordinator parent or subsequent callbacks.
|
|
3. **Expose the prepared snapshot.** Deliver detached public details from the
|
|
existing prepared state before generation. Cover both ordinary execution and
|
|
the selected prepared-execution lifecycle without a second preparation pass.
|
|
Preserve credential handling and release resources on cancellation or panic.
|
|
Avoid snapshot-copy work when the capability is not selected.
|
|
4. **Integrate and verify coordination.** Have ordinary execution honor the
|
|
selected controls on the callback-returned request under the accepted approach.
|
|
Preserve capture and observer composition; verify the next-turn callback runs
|
|
with the parent context after admission and child-budget cleanup.
|
|
5. **Document and demonstrate.** Update public GoDoc, the
|
|
[consumer guide](../consumers/pkg-promptkit.md), relevant internal lifecycle
|
|
documentation, and a focused offline example when implementation lands.
|
|
Show how applications retain diagnostics on failure and bound independent
|
|
assessment calls. Reconcile Audita's scenarios against the final API.
|
|
|
|
This is an additive runtime feature; no collection-format or ConfigKit changes
|
|
are needed. Keep implementation proportional to the two requested controls.
|
|
|
|
## Validation Targets
|
|
|
|
Use offline public-boundary tests and existing execution collaborators, with
|
|
controlled cancellation and synchronization rather than fragile timing margins.
|
|
Cover the consumer request's [acceptance scenarios](audita-request.md#acceptance-scenarios)
|
|
and the accepted lifecycle choices:
|
|
|
|
- Successive turns receive fresh budgets: their combined duration may exceed one
|
|
operation budget without exhausting either individual operation.
|
|
- For ordinary and coordinated runs, preparation, admission waiting, retries,
|
|
validation, and repair share the same operation deadline. Local expiry stops
|
|
coordination without canceling parent or sibling work; earlier parent
|
|
cancellation/deadlines remain effective.
|
|
- Next-turn assessment uses the parent context after release, including another
|
|
call to a backend with capacity one. Timeout identity survives public wrapping.
|
|
- A slow terminal observer or child-context cleanup does not change a completed
|
|
outcome into a local timeout; the returned error and terminal observation agree.
|
|
- Prepared details precede generation, contain the assembled continuation, and
|
|
remain available after later failure. Mutation of the delivered copy or source
|
|
changes after preparation do not change the executed snapshot.
|
|
- Diagnostic access introduces no additional preparation or inference calls,
|
|
notification per internal repair, or duplicate observer lifecycle. Ordinary
|
|
retries and structural repair remain allowed. Credential redaction remains
|
|
intact.
|
|
- Admission or preparation failure before the notification point does not require
|
|
diagnostic delivery or additional rendering to produce a snapshot.
|
|
- Preparation failure, cancellation around notification, and callback panic follow
|
|
the selected delivery rules and do not strand admission or retained resources.
|
|
- Split preparation uses its supplied context and retains the selected duration
|
|
for `RunPrepared`; idle time does not consume that execution budget. A shared
|
|
caller deadline can still bound both calls. Neither discarded nor unsuccessfully
|
|
claimed handles start execution notifications.
|
|
- The prepared notification receives detached details and the operation context,
|
|
has no error return, and runs synchronously within the operation budget.
|
|
- Local-budget expiry has a narrow inspectable identity while preserving standard
|
|
context matching; parent and provider deadlines are not mislabeled as local.
|
|
- Observers/groups retain ordinary facts on success and failure. No-option use,
|
|
completed-result delivery, and final-turn behavior remain compatible.
|
|
|
|
## Accepted Decisions
|
|
|
|
All four decisions below are accepted. No significant product decisions remain
|
|
open. Exact public names, signatures, timeout-value conventions, and the Go
|
|
representation of timeout identity remain for API and implementation planning.
|
|
|
|
### 1. Narrow Execution Controls Or Caller-Delegated Execution?
|
|
|
|
**Accepted approach:** add optional per-request execution controls honored by
|
|
ordinary `Run` and thus by `Coordinate`, including a timeout and
|
|
prepared-details callback. This keeps history, capture, admission, errors, and
|
|
observers in their existing owners and makes the capability useful outside
|
|
coordination. A caller can select different controls on each returned request;
|
|
an additional coordinator-wide configuration layer is not needed for the
|
|
demonstrated use case.
|
|
|
|
**Alternative considered, not selected:** let the coordinator delegate a turn
|
|
to a caller-provided executor or prepared-execution adapter. This would reuse
|
|
Audita's existing adapter and accommodate other execution customizations. It
|
|
also requires a larger contract for exchange assembly, capture, observer
|
|
composition, handle cleanup, and admission release. The narrow controls meet
|
|
the demonstrated need with fewer ownership questions; caller-delegated
|
|
execution is not part of this feature.
|
|
|
|
### 2. What Does A Timeout Mean For Split Preparation And Execution?
|
|
|
|
**Accepted approach:** bound each `Run` invocation from before preparation. For
|
|
`PrepareExecution`, retain the selected duration as an execution control and
|
|
apply it when `RunPrepared` starts; preparation itself continues using its
|
|
supplied context. Do not retain a live timer across an idle prepared handle.
|
|
Explicitly explain that this bounds the remaining execution, not preparation
|
|
already done. Callers needing one total budget across both calls can continue
|
|
passing the same caller-created deadline context, as Audita does today.
|
|
|
|
This preserves the distinction between executing now and preparing for later,
|
|
while letting a prepared handle carry the same selected execution controls. It
|
|
requires clear documentation: the duration alone does not retroactively cover
|
|
split preparation. `Prepare` and `ValidateRequest` likewise do not become timed
|
|
executions merely because they accept request-shaped input.
|
|
|
|
**Alternative considered, not selected:** start a budget during
|
|
`PrepareExecution` and carry its remaining deadline into `RunPrepared`. This
|
|
directly bounds preparation and execution together without caller context
|
|
plumbing. It also counts idle time, requires timer/context ownership and
|
|
discard semantics, and changes what a prepared handle retains. This is useful
|
|
if a consumer needs a whole prepared operation lifetime, but exceeds the
|
|
current coordinated-turn requirement.
|
|
|
|
### 3. What Is The Prepared-Details Callback Contract?
|
|
|
|
**Accepted approach:** a synchronous notification receiving the operation
|
|
context and a detached prepared value, with no error return. Invoke it once
|
|
after the snapshot is ready and before generation, only when execution proceeds
|
|
to that point. For prepared handles, retain the callback until execution and
|
|
clear it on claim/discard like the existing observer; inspection remains
|
|
available earlier through `Details()` without triggering the notification.
|
|
|
|
Check context before notification and again before generation. Include callback
|
|
time in the operation budget and retain ordinary cooperative cancellation and
|
|
panic propagation, with resource cleanup on unwind. Do not add background delivery
|
|
or stronger terminal-event guarantees. The application can retain details and
|
|
perform optional writes under its own policy; it need not turn a diagnostic write
|
|
failure into an inference failure.
|
|
|
|
Under the current ordinary run path this notification executes while logical
|
|
admission is held. Document that fact rather than promise the next-turn callback's
|
|
capacity-one nested-execution behavior. Do not prohibit arbitrary callback work;
|
|
applications must account for the held capacity and operation budget. Releasing
|
|
and reacquiring admission solely for notification is not necessary for this scope.
|
|
|
|
**Alternative considered, not selected:** let the callback return an error to
|
|
veto generation. This supports mandatory audit persistence or an application
|
|
pre-generation check, with ordinary cleanup and error propagation. It also
|
|
makes the callback an execution gate and requires failure classification
|
|
semantics absent from the optional-diagnostics request. The selected
|
|
notification satisfies that request; a generation veto would require a separate
|
|
demonstrated need.
|
|
|
|
### 4. How Precisely Should Local Timeout Errors Be Identified?
|
|
|
|
**Accepted approach:** preserve standard context matching and provide a narrow,
|
|
inspectable operation-timeout identity when this newly selected budget causes
|
|
termination. Keep parent cancellation/deadlines and provider-local failures
|
|
under their existing identities. The marker must survive public wrapping and
|
|
must not be inferred from any arbitrary `DeadlineExceeded` returned by a
|
|
collaborator. Choose its Go representation during API design; no new
|
|
error-report hierarchy or terminal-event category is implied.
|
|
|
|
This lets callers distinguish the requested local budget from an earlier parent
|
|
or provider deadline without retaining the internal child context. Define cause
|
|
selection around races using the child context's recorded cause; do not promise
|
|
perfect ordering of independently simultaneous cancellations.
|
|
|
|
**Alternative considered, not selected:** return only existing context errors
|
|
and let callers inspect their parent context. This is smaller and often
|
|
sufficient when the parent remains active. However, `DeadlineExceeded` alone
|
|
may also originate in a provider timeout, and a parent may become canceled
|
|
before the caller inspects it. That weaker attribution does not meet the
|
|
selected distinction as directly as a narrow operation-timeout identity.
|