You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add provider-neutral, per-invocation inputs for images, files, and audio while keeping Run and Stream as the only execution verbs.
The current public API accepts only prompt string, and driver.Request carries only Prompt string. An embedding host therefore cannot pass an image, PDF, or audio resource without converting it into an undocumented provider-specific prompt/path convention. That loses modality, prevents truthful capability negotiation, makes resource lifetime ambiguous, and encourages each host to add its own unsafe URL/file handling.
This issue proposes a backward-compatible root API, a resolved Driver SPI, explicit capability modes, typed pre-launch errors, and deterministic cleanup/retry semantics.
Motivation
Our embedding host is a long-running Go process that uses agent-adaptor to expose multiple local Agent CLIs behind one product protocol. The server remains the message/resource authority. Before a run, the host downloads an authorized canonical resource, verifies its MIME type, size and SHA-256, and stages it locally. We then need to give that resource to the configured Driver without:
importing Codex/Claude/Cursor-specific request types into the host;
interpolating a local path into the user's text prompt;
claiming native vision/audio support when a Driver only mentions a workspace path;
passing a remote URL to the SDK and expanding the SDK's SSRF/credential scope;
keeping temporary resources alive forever or deleting them while a Driver still reads them;
changing the Thread compatibility fingerprint for every turn attachment.
This is not only a media feature. A provider-neutral input contract is the boundary that lets hosts apply authorization and resource policy once while Drivers remain responsible for provider protocol encoding.
This proposal follows the immutable-Agent direction in #20:
inputs are invocation content, not stable Agent topology;
WithInputs is call-only and cannot install Agent defaults;
individual input IDs, paths, sizes, and digests do not enter Thread/session compatibility fingerprints;
Run and Stream remain the only execution verbs;
Inspect may report dynamic account/model input limits, while Descriptor retains static transport capabilities;
Prepare does not materialize per-run inputs because it has no invocation content.
If #20 lands as a coordinated breaking v1 revision, the exact DTO names can be frozen there, but the content/lifecycle rules below should remain the same.
Proposed root API
Keep existing text-only calls source-compatible and add a call-only option:
stream:=agent.Stream(
ctx,
"Compare the diagram with the specification.",
adaptor.WithInputs(
input.FromPath(input.KindImage, stagedPNG,
input.WithName("diagram.png"),
input.WithMediaType("image/png"),
input.WithSize(pngSize),
input.WithSHA256(pngDigest),
input.RequireMode(input.ModeNative),
),
input.FromOpener(input.KindFile, "spec.pdf", openPDF,
input.WithMediaType("application/pdf"),
input.WithSize(pdfSize),
input.WithSHA256(pdfDigest),
),
),
)
// WithInputs appends per-invocation non-text inputs in argument order.// It implements CallOption only.funcWithInputs(refs...input.Ref) CallOption
Option semantics
The existing prompt string is the first text part. Inputs are ordered attachments after that text part.
Multiple WithInputs options append in option order. An empty call is a no-op.
There is no Agent-level default input. Reusing user content implicitly across calls would be surprising and unsafe.
Ref metadata is detached when the run begins. Caller mutation after Run/Stream starts cannot change the request.
FromOpener is called at most once for one logical invocation. A safe provider-process retry before prompt delivery reuses the already materialized file.
v1 has no built-in URL source. A host that owns HTTP authorization/fetch policy exposes the verified body through FromOpener or a local staged path.
WithInputs should apply equally to Agent.Run, Agent.Stream, Thread.Run, and Thread.Stream through the existing effective run settings.
The API deliberately does not add RunWithFiles, RunMultimodal, Upload, or a parallel execution handle.
Materialization contract
Core resolves all refs before launching the provider process or allowing the prompt to be delivered:
Validate kind, name, media type, declared size/digest, and requested mode.
Select a delivery mode from the Driver capability. Prefer native; use workspace_reference only when it is declared and the caller did not require native.
Open each source once and stream it into a run-owned directory.
Compute actual size and SHA-256 while copying; compare them with optional expected values.
Pass only resolved, immutable local metadata to the Driver.
Keep the directory alive until Driver completion and all Driver-owned event sources have stopped.
Remove it on success, cancellation, Driver error, stream close, and Agent close.
Zero values should select documented safe defaults, not mean unlimited. Hosts that need larger files can opt in explicitly.
Core should copy path/FS sources rather than let a long-running Driver reopen an arbitrary caller path later. That produces one lifetime model, detects mutation while hashing, and avoids making every Driver implement file safety. Platform-specific exact-inode/symlink/junction hardening can remain an internal materializer concern.
Static capability contract
Extend driver.Descriptor with static transport capabilities:
native means the Driver uses a provider-native typed content/file mechanism and preserves the input modality.
workspace_reference means the Driver intentionally places/references a verified local file in provider-visible workspace context. It is a declared fallback, not native multimodal support.
MediaTypes uses normalized MIME types/patterns. An empty list means the static transport cannot provide a narrower guarantee; the Driver must still validate before provider launch.
A Driver must not advertise native merely because its implementation can append Please inspect /path to a prompt.
The host can use this contract to hide unsupported UI, but root validation remains mandatory because UI capability snapshots can become stale.
Driver SPI
Do not pass raw bytes, remote URLs, fs.FS, or host callbacks through the Driver boundary. Core passes resolved local inputs through the existing sole execution SPI:
LocalPath is absolute, run-owned, read-only input and remains valid until Driver.Run and all event sources return.
Name is a sanitized display basename, never an alternate path.
Size and SHA256 are always the observed values, even when the caller did not declare expectations.
DeliveryMode is selected and validated by core; the Driver must implement exactly that semantic mode or return a contract error before prompt delivery.
Drivers must not mutate, retain, or log LocalPath after the run ends.
Request.Inputs is detached; Driver mutation cannot change root settings or another run.
Driver encoding examples are intentionally provider-specific:
a native Driver maps image/audio/file entries to official typed protocol blocks;
a workspace-reference Driver arranges an accessible workspace path and explicitly references it according to its documented transport;
an unsupported Driver fails before launching the provider process.
No new Driver execution method is required; Driver.Run(ctx, req, sink) remains the only provider execution SPI.
Typed errors
Expose sentinels plus a typed error usable with errors.Is and errors.As:
var (
ErrInputInvalid=errors.New("invalid input")
ErrInputKindUnsupported=errors.New("input kind unsupported")
ErrInputMediaTypeUnsupported=errors.New("input media type unsupported")
ErrInputModeUnsupported=errors.New("input mode unsupported")
ErrInputTooLarge=errors.New("input too large")
ErrInputDigestMismatch=errors.New("input digest mismatch")
ErrInputMaterializationFailed=errors.New("input materialization failed")
)
typeInputErrorstruct {
ErrerrorIndexintKind input.KindMediaTypestringDriverstringLimitint64
}
InputError.Error() and wrapping must not contain source paths, URL credentials, file contents, opener errors containing secrets, or raw provider payloads. Detailed paths may be exposed only through an explicit host-owned diagnostic hook with its own redaction policy.
All validation/materialization/capability errors occur before provider launch and before prompt delivery. That makes them safe for the caller to correct and retry.
Thread, fingerprint, and retry semantics
Inputs are turn content, like Prompt; individual input IDs, names, paths, sizes, digests, and bytes are not part of the Thread compatibility fingerprint.
Driver configuration that changes how inputs are encoded may continue to participate in the existing Driver session/config fingerprint.
On a resumed Thread, inputs are delivered exactly once for that invocation, not reattached to prior turns.
All inputs must be materialized before the prompt may be delivered.
If provider launch fails before prompt delivery, an existing safe fallback may reuse the materialized inputs.
If prompt delivery is indeterminate or may have succeeded, core must not auto-replay the prompt or inputs. Existing no-replay-after-delivery rules remain authoritative.
Prepare never resolves these inputs and does not create their directories.
Events, results, and redaction
Input bytes and local paths never appear in public Events, Result, Raw result, Thread checkpoints, default logs, or error strings.
A Driver may emit a redacted Notice containing input ID, kind, normalized media type, size and digest if the protocol needs auditable attachment acknowledgement.
Result text remains the provider's final result; core does not synthesize a result from input metadata.
Structured output continues to apply to provider output, not to input descriptors.
Built-in Driver requirements
Each built-in Driver should declare truthful support even if the first implementation is unsupported:
Codex
Claude
Cursor
CodeBuddy
For every declared kind/mode, adapter conformance must prove the actual provider request contains the corresponding native block or explicit workspace reference. A fixture that only verifies a prompt contains a path is not proof of native support.
Test requirements
Root API and materialization
text-only existing calls remain source- and behavior-compatible;
repeated WithInputs append in deterministic order and do not alias caller slices;
path, fs.FS, and opener sources produce identical resolved metadata;
actual size/digest are computed and expected mismatch fails before Driver invocation;
max count, per-input bytes, total bytes, invalid kind/name/MIME, and required-mode mismatch are typed pre-launch failures;
cancellation at open/copy/hash/Driver stages closes readers and removes run-owned files;
success, Driver error, Stream close, Agent close, and panic-safe cleanup remove inputs only after event sources stop;
parallel runs cannot observe or delete each other's inputs;
symlink/junction/path replacement and mutation-during-copy tests use real filesystems on supported platforms;
default logs and errors contain no input bytes or secret paths.
Thread and retry
adding different inputs does not split an otherwise compatible Thread;
one invocation delivers its inputs once on a resumed Thread;
a safe pre-prompt launch retry reuses materialized inputs without reopening the source;
an indeterminate post-prompt failure never replays the invocation;
Prepare creates no input materialization.
Driver conformance
every built-in Driver has explicit image/file/audio mode assertions;
unsupported kind/MIME/mode fails before process launch;
native fixtures inspect the real provider wire representation;
workspace_reference fixtures prove the path is provider-visible and the capability is not reported as native;
Driver does not retain a path after run cleanup;
live tests remain opt-in and require existing authenticated local CLIs.
Summary
Add provider-neutral, per-invocation inputs for images, files, and audio while keeping
RunandStreamas the only execution verbs.The current public API accepts only
prompt string, anddriver.Requestcarries onlyPrompt string. An embedding host therefore cannot pass an image, PDF, or audio resource without converting it into an undocumented provider-specific prompt/path convention. That loses modality, prevents truthful capability negotiation, makes resource lifetime ambiguous, and encourages each host to add its own unsafe URL/file handling.This issue proposes a backward-compatible root API, a resolved Driver SPI, explicit capability modes, typed pre-launch errors, and deterministic cleanup/retry semantics.
Motivation
Our embedding host is a long-running Go process that uses agent-adaptor to expose multiple local Agent CLIs behind one product protocol. The server remains the message/resource authority. Before a run, the host downloads an authorized canonical resource, verifies its MIME type, size and SHA-256, and stages it locally. We then need to give that resource to the configured Driver without:
This is not only a media feature. A provider-neutral input contract is the boundary that lets hosts apply authorization and resource policy once while Drivers remain responsible for provider protocol encoding.
Relationship to #20
This proposal follows the immutable-Agent direction in #20:
WithInputsis call-only and cannot install Agent defaults;RunandStreamremain the only execution verbs;Inspectmay report dynamic account/model input limits, whileDescriptorretains static transport capabilities;Preparedoes not materialize per-run inputs because it has no invocation content.If #20 lands as a coordinated breaking v1 revision, the exact DTO names can be frozen there, but the content/lifecycle rules below should remain the same.
Proposed root API
Keep existing text-only calls source-compatible and add a call-only option:
Suggested package and constructors:
Root option:
Option semantics
prompt stringis the first text part. Inputs are ordered attachments after that text part.WithInputsoptions append in option order. An empty call is a no-op.Refmetadata is detached when the run begins. Caller mutation afterRun/Streamstarts cannot change the request.FromOpeneris called at most once for one logical invocation. A safe provider-process retry before prompt delivery reuses the already materialized file.FromOpeneror a local staged path.WithInputsshould apply equally toAgent.Run,Agent.Stream,Thread.Run, andThread.Streamthrough the existing effective run settings.The API deliberately does not add
RunWithFiles,RunMultimodal,Upload, or a parallel execution handle.Materialization contract
Core resolves all refs before launching the provider process or allowing the prompt to be delivered:
native; useworkspace_referenceonly when it is declared and the caller did not require native.Suggested construction-only limits:
Zero values should select documented safe defaults, not mean unlimited. Hosts that need larger files can opt in explicitly.
Core should copy path/FS sources rather than let a long-running Driver reopen an arbitrary caller path later. That produces one lifetime model, detects mutation while hashing, and avoids making every Driver implement file safety. Platform-specific exact-inode/symlink/junction hardening can remain an internal materializer concern.
Static capability contract
Extend
driver.Descriptorwith static transport capabilities:Invariants:
Modesmeans unsupported.nativemeans the Driver uses a provider-native typed content/file mechanism and preserves the input modality.workspace_referencemeans the Driver intentionally places/references a verified local file in provider-visible workspace context. It is a declared fallback, not native multimodal support.MediaTypesuses normalized MIME types/patterns. An empty list means the static transport cannot provide a narrower guarantee; the Driver must still validate before provider launch.nativemerely because its implementation can appendPlease inspect /pathto a prompt.The host can use this contract to hide unsupported UI, but root validation remains mandatory because UI capability snapshots can become stale.
Driver SPI
Do not pass raw bytes, remote URLs,
fs.FS, or host callbacks through the Driver boundary. Core passes resolved local inputs through the existing sole execution SPI:SPI invariants:
LocalPathis absolute, run-owned, read-only input and remains valid untilDriver.Runand all event sources return.Nameis a sanitized display basename, never an alternate path.SizeandSHA256are always the observed values, even when the caller did not declare expectations.DeliveryModeis selected and validated by core; the Driver must implement exactly that semantic mode or return a contract error before prompt delivery.LocalPathafter the run ends.Request.Inputsis detached; Driver mutation cannot change root settings or another run.Driver encoding examples are intentionally provider-specific:
No new Driver execution method is required;
Driver.Run(ctx, req, sink)remains the only provider execution SPI.Typed errors
Expose sentinels plus a typed error usable with
errors.Isanderrors.As:InputError.Error()and wrapping must not contain source paths, URL credentials, file contents, opener errors containing secrets, or raw provider payloads. Detailed paths may be exposed only through an explicit host-owned diagnostic hook with its own redaction policy.All validation/materialization/capability errors occur before provider launch and before prompt delivery. That makes them safe for the caller to correct and retry.
Thread, fingerprint, and retry semantics
Prompt; individual input IDs, names, paths, sizes, digests, and bytes are not part of the Thread compatibility fingerprint.Preparenever resolves these inputs and does not create their directories.Events, results, and redaction
Built-in Driver requirements
Each built-in Driver should declare truthful support even if the first implementation is
unsupported:For every declared kind/mode, adapter conformance must prove the actual provider request contains the corresponding native block or explicit workspace reference. A fixture that only verifies a prompt contains a path is not proof of native support.
Test requirements
Root API and materialization
WithInputsappend in deterministic order and do not alias caller slices;fs.FS, and opener sources produce identical resolved metadata;Thread and retry
Preparecreates no input materialization.Driver conformance
nativefixtures inspect the real provider wire representation;workspace_referencefixtures prove the path is provider-visible and the capability is not reported as native;Acceptance criteria
Run/Streamcode compiles unchanged.WithInputsis call-only and individual inputs never affect Thread compatibility.Request.Inputs.Non-goals