The Java SDK's Maven coordinates are
The main SDK entry point. It creates one-shot queries, multi-turn sessions, and prewarmed launches. It is thread-safe and reusable; closing it cascades to every query and session it created.
Argument errors and local state errors are thrown synchronously, for example a
A one-shot query handle, returned by
The CLI exits after the turn's
A multi-turn session, returned by
Some methods require the CLI to support the corresponding capability. Otherwise they fail with
A pushed catalog always replaces the old value as a whole; an empty list means the CLI explicitly cleared the catalog.
Metadata for a command or skill, in
A CLI that has already finished startup, authentication, and initialization, returned by
Converting again after a conversion or close throws
The message stream of a query or session. It supports two mutually exclusive ways to consume it: pull with
The stage returned by
Each stream can be consumed only once, through either
A pull cursor.
Multi-turn sessions should reuse one cursor across turns: after reading a turn's
The message handler for push mode.
An exception thrown by the handler completes the stage returned by
Immutable options for queries and sessions, used by
The two defaults are also available as constants:
A one-shot query request, containing one
One user input sent to the CLI, with content blocks, a priority, whether it triggers a reply, and optional custom context and skill selection. Immutable.
The content block type
When
A skill explicitly selected for this input.
The name must not be empty or all whitespace.
Passed to
With the list constructor, relative paths are resolved against the session's working directory when the seed is applied, and each file's current mtime is read.
The priority hint for user input. The SDK does not reorder writes by priority; the CLI decides scheduling.
Feature flags passed to
Proxy settings passed to
Set a proxy with
Configuration for the managed CLI, set through
CLI lookup order. At launch, the SDK picks the CLI in this order and stops at the first match:
Authentication strategy passed to
The startup authentication configuration reaches the CLI through a temporary file readable only by the current account. It is never placed in launch arguments or environment variables.
Callback through which the host refreshes short-lived Service Account tokens. The host keeps the long-lived key.
Return a stage that completes with a fresh token. The CLI may call it several times when it needs credentials. The token reaches the CLI over the SDK control channel; do not log it.
Callback that supplies a per-job token. The SDK passes no job ID to the callback; bind your own job identity in the closure.
Return a stage that completes with a token; never return
A secret value managed by the SDK.
The SDK redacts registered credentials that appear in diagnostic events and stderr. This does not cover business content or third-party output, so the application still controls what it logs under its own logging policy.
By default the CLI subprocess inherits only
Tool permission callback, registered with
Return a stage that completes with a
A single tool permission request.
Optional fields that are missing or have the wrong type return an empty
Tool input, previews, and
Explicit approval scope, used through
The CLI saves
The decision of a permission callback: allow (optionally rewriting the tool input) or deny (with a reason).
When the request carries
Permission mode. It is a
For known modes,
Cancellation token passed to callbacks. It is cancelled when the CLI cancels the request, the callback times out, or the session closes. The SDK sends the protocol-level cancellation response; the application only needs to stop the related work.
Cancellation is cooperative: code that ignores the token keeps running. Keep cancellation actions short and non-blocking; exceptions they throw are ignored.
The Plan state can be set independently of the permission mode. Turning Plan on keeps the current permission mode. Package
Rules:
One Hook registration: an event name, an optional matcher, and a handler. Package
The CLI matches event names and matchers; the handler runs inside the Java process.
Hook handler. It is called on the SDK's dedicated Hook thread, not on
The returned JSON goes to the CLI as the Hook output as is. The CLI defines its structure; see Hooks outputs. An empty object means no intervention. Return the following structure in
If the handler throws or the stage completes exceptionally, only this Hook call fails with
A single Hook event.
Common
MCP server configuration, registered with
Calling a chained method on a server type it does not apply to throws
JSON-RPC endpoint of an in-process MCP server, registered with
The SDK forwards the returned JSON to the CLI as the MCP response as is, so the response must carry
Status of one MCP server, returned by
The CLI may echo the server's effective configuration in
Used for MCP OAuth. Flow: start authentication with
The authorization URL and the callback URL can contain a sensitive OAuth state. Do not log them;
MCP elicitation callback, triggered when an MCP server needs to collect more information from the user. Register it with
The callback runs on
Result of an elicitation.
Model types live in the
The model selection strategy passed to
During initialization the SDK only declares the callback to the CLI; it does not call it. The callback runs only when the CLI needs a model decision, so do not rely on it running before
The callback used by
The timeout is set by
The callback used by
If the callback returns
The immutable return value of
Builder methods do not accept
The context of one model request, passed by the SDK to the model callback.
Fields the CLI does not provide are
One entry in the model catalog. Returned by
The catalog may contain models whose
Read options for
The local model catalog snapshot returned by
When
Value type for a model ID.
Memory configuration is passed through
Results also appear in the message stream as
Both
Types for external session storage.
Selects which history storage a single session uses. Set it with
The effective external store cannot be combined with
The interface an external session store implements. The client sets the default store with
Both methods may complete synchronously, but failures must be reported through an exceptionally completed stage, not thrown directly. Do not run blocking I/O on the calling thread; use an async client or your own thread pool. The store should keep each transcript JSON exactly as received (including unknown fields), without wrapping it in another envelope.
Mirroring write failures are reported through
A store that can enumerate sessions. The
A store that supports deleting a whole session.
The implementation should delete the root transcript and all subpaths in the same transaction or critical section as appends, and permanently mark the session as deleted. Later writes to the session are rejected with
The SDK's built-in in-memory implementation of
Resource limits for writes to the external store (mirroring), set with
After a write to one key fails or times out, writes to that key stop and other keys are unaffected. When the session-wide limit is exceeded, mirroring stops for the whole session. These errors are reported through
Reads, continues, forks, or deletes saved sessions. Use
Scope rules:
Value types used in
The result of the initialization handshake, returned by methods such as
If the CLI rejects a capability-gated request as "unsupported" at runtime, the SDK downgrades that capability to
The following methods and settings require the CLI to declare the corresponding capability during the handshake. When the capability state is not
Other
The three states of a CLI capability.
The result of
Success only means the CLI accepted the interrupt request; when the turn ends is determined by
The submission handle returned by
Use
Options and result of
Abstract base class of every message in the message stream, in
Concrete types:
Every dedicated system message extends
Delivered when the assistant produces a reply. Content blocks are output-side types, independent of the input-side
The Java SDK models only
Delivered when a turn ends. Agent-level failures, such as reaching the turn limit, are reported as a
System messages from the CLI.
All sizes in
Notifications about Hook runs. They enter the message stream only with
Each accessor maps to the snake_case field of the same name (such as
Results of Memory generation and Memory loading. Neither ends a turn. The result types live in
Immutable JSON types bundled with the SDK, in
Receives the SDK's structured diagnostic events, such as session initialization, close timeouts, capability downgrades, and managed CLI installation.
An immutable diagnostic event. A
When your application builds its own event with
Later SDK versions may add event types. Keep a default branch when you switch on
All SDK exceptions extend the unchecked
The message and cause are redacted at construction, and
Thrown when:
Apart from the accessors listed below, each exception class has only constructors, and its error code constant is
The following error code is thrown directly as
These error codes are exposed through
com.qoder.sdk:qoder-agent-sdk, and its packages are com.qoder.sdk.*. It requires JRE 8+, and the core artifact has no third-party runtime dependencies.
The SDK runs tasks in a local qodercli child process. When no CLI path is set, the first launch downloads the CLI version pinned by this SDK release, verifies its SHA-256, and caches it for reuse. Managed platforms are macOS arm64/x64, Linux x64/arm64 (glibc and musl), and Windows x64. Windows x64 requires Windows 10 1809 / Windows Server 2019 or later. The SDK does not check the OS version, so older systems need a CLI you specify yourself. You can also point to an installed CLI with qoderCliPath or the QODERCLI_PATH environment variable. On platforms outside the managed set, such as Windows arm64, the SDK looks in ~/.local/bin and PATH. See ManagedRuntimeOptions for the full lookup order.
The declaration blocks below omit method bodies and imports. For installation and a first runnable example, see Quick Start. For capability differences from TypeScript and Python, see Language differences. For built-in tool names, see the built-in tool list.
Entry Points
QoderClient
The main SDK entry point. It creates one-shot queries, multi-turn sessions, and prewarmed launches. It is thread-safe and reusable; closing it cascades to every query and session it created.
| Method | Return type | Description |
|---|---|---|
builder() | QoderClient.Builder | Creates a builder |
query(OneShotQueryRequest) | QoderQuery | Starts a one-shot query; returns the handle synchronously while the CLI starts asynchronously |
openSession(SessionOptions) | CompletionStage<QoderSession> | Opens a multi-turn session; completes after the initialization handshake |
prepare(SessionOptions) | CompletionStage<PreparedQuery> | Starts the CLI ahead of time and completes authentication and initialization without sending input |
sessions() | SessionRepository | Returns a session repository for the client's default storage and project directory |
sessions(SessionOptions) | SessionRepository | Returns a session repository for the given options' sessionStorage and workingDirectory without starting the CLI; null is the same as sessions() |
installRuntime() | CompletionStage<Path> | Only installs or reuses the managed CLI and returns the executable path, without starting the CLI. See ManagedRuntimeOptions |
pruneRuntimes() | CompletionStage<Integer> | Deletes managed CLIs other than the pinned version and returns the number of directories deleted |
closeAsync() | CompletionStage<Void> | Closes asynchronously with cascade; idempotent |
close() | void | Blocking close. For restrictions on calling it from SDK threads or callbacks, see Threading and resource release |
null argument or a failed build() validation. Failures during startup and execution are delivered through the returned CompletionStage or the message stream. All SDK-defined exceptions extend QoderException; standard Java argument and state exceptions keep their original types.
QoderClient.Builder
| Method | Parameter type | Description |
|---|---|---|
auth(Auth) | Auth | Required. The authentication method |
callbackExecutor(Executor) | Executor | Executor for permission, MCP, model policy, auth token, and Memory callbacks; Hooks use a separate SDK thread |
diagnosticsListener(DiagnosticsListener) | DiagnosticsListener | Client-level diagnostics listener |
sessionStore(SessionStore) | SessionStore | The client's default external session store, used by the session repository and all sessions; session options can override it |
projectDirectory(Path) | Path | Project directory for the session repository, and the default working directory for sessions without workingDirectory |
managedRuntime(ManagedRuntimeOptions) | ManagedRuntimeOptions | Cache directory, download policy, mirror, and timeouts for the managed CLI; null restores the defaults |
build() | — | Builds the client; throws InvalidOptionsException when auth is not set |
QoderQuery
A one-shot query handle, returned by QoderClient.query() or PreparedQuery.toQuery().
| Method | Return type | Description |
|---|---|---|
messages() | MessageStream | Returns the message stream |
initialization() | CompletionStage<InitializationResult> | Result of the initialization handshake |
interrupt() | CompletionStage<InterruptResult> | Interrupts the running turn |
flushMemory() | CompletionStage<Void> | Waits for triggered Memory processing to finish; completion does not mean every write succeeded |
refreshMemory() | CompletionStage<Void> | Reloads the Memory consumption configuration |
closeAsync() / close() | CompletionStage<Void> / void | Closes the query and releases resources |
result, but you should still consume the message stream to the end and then close the handle or cursor. With Memory enabled, Memory messages can arrive after ResultMessage. ResultOutcome.ERROR means the turn failed; the absence of an exception does not mean success.
QoderSession
A multi-turn session, returned by QoderClient.openSession() or PreparedQuery.toSession().
| Method | Return type | Description |
|---|---|---|
messages() | MessageStream | Returns the message stream; it can be consumed only once |
initialization() | CompletionStage<InitializationResult> | Result of the initialization handshake, including CLI capabilities and version |
send(UserInput) | CompletionStage<MessageId> | Completes once the input is written to the CLI; does not mean the answer has finished |
submit(UserInput) | MessageSubmission | Returns the assigned MessageId synchronously; observe the write result through written(). Use it when you need the ID before the write |
completeInput() | CompletionStage<Void> | Rejects further input and waits for accepted input to finish writing; does not wait for the answer |
cancelQueuedMessage(MessageId) | CompletionStage<Boolean> | Cancels queued input not yet written to the CLI. Once writing has started, it tries a remote cancel only if the CLI supports the capability; otherwise it returns false |
interrupt() | CompletionStage<InterruptResult> | Interrupts the current turn; the session stays usable |
setPermissionMode(PermissionMode) | CompletionStage<Void> | Switches the permission mode at runtime; switching to bypass requires the session to have set allowDangerouslySkipPermissions(true) |
getPlanMode() / setPlanMode(boolean) | CompletionStage<PlanModeSnapshot> | Queries or switches the standalone Plan state and returns the value the CLI confirmed. See Standalone Plan |
setPermissionModePreservingPlan(PermissionMode) | CompletionStage<Void> | Switches the permission mode and keeps the Plan state; does not accept PLAN |
setProxy(ProxySettings) | CompletionStage<Void> | Sets the CLI's network proxy |
addDirectories(List<Path>) | CompletionStage<Void> | Allows the CLI to access additional directories |
applyFlagSettings(FlagSettings) | CompletionStage<Void> | Applies feature flags |
generateSessionTitle([String]) | CompletionStage<SessionTitle> | Asks the CLI to generate a session title, optionally with a description |
seedReadState(ReadStateSeed) | CompletionStage<Void> | Tells the CLI which files the user has already seen; sent only after every local check passes |
supportedCommands() | CompletionStage<List<String>> | Command names in the current command catalog; sends no request |
commandCatalog() | CompletionStage<List<CommandInfo>> | The current command catalog; sends no request |
modelCatalogSnapshot() | CompletionStage<Optional<ModelCatalogSnapshot>> | The model catalog the CLI provided most recently; empty if none has arrived. Sends no request |
reloadSkills() | CompletionStage<List<CommandInfo>> | Asks the CLI to reload skills and returns the skills list |
getAvailableModels([ModelCatalogOptions]) | CompletionStage<List<ModelInfo>> | Queries the CLI for available models, optionally using the cache. See ModelCatalogOptions |
backgroundTasks([String]) | CompletionStage<Boolean> | Moves the foreground task (or the task for the given tool use) to the background and returns whether it succeeded |
stopTask(TaskId) | CompletionStage<Void> | Stops a background task |
reloadPlugins() | CompletionStage<Void> | Asks the CLI to reload plugins |
mcpServerStatus() | CompletionStage<List<McpServerStatus>> | Status of every MCP server |
beginMcpAuthentication(String[, McpAuthenticationOptions]) | CompletionStage<McpAuthenticationResult> | Starts OAuth for an MCP server and returns the result or an authorization URL the user must open |
mcpSubmitOAuthCallbackUrl(String, String) | CompletionStage<Void> | Hands the full OAuth callback URL to the CLI, which validates it, exchanges it for credentials, and reconnects |
rewindHistory(MessageId) | CompletionStage<Void> | Rewinds the conversation history to the given message |
rewindFiles(MessageId, RewindFilesOptions) | CompletionStage<RewindFilesResult> | Restores checkpoint-tracked files to their state at the given message. See Checkpoint |
flushMemory() / refreshMemory() | CompletionStage<Void> | Same as QoderQuery |
closeAsync() / close() | CompletionStage<Void> / void | Closes the session and releases resources |
UnsupportedCliCapabilityException and send no request. See Capability requirements. When the session closes, queued input not yet written ends with SessionClosedException. To make sure accepted input reaches the CLI, wait for completeInput() first.
Runtime catalogs
| Method | Data source | Sends a request |
|---|---|---|
commandCatalog() / supportedCommands() | The command catalog from initialization; replaced as a whole when the CLI pushes a command change | No |
modelCatalogSnapshot() | The latest complete model catalog from initialization or a CLI push | No |
getAvailableModels(...) | The result of this query; does not update the snapshot above | Yes |
reloadSkills() | The skills list after reloading; does not replace the full command catalog | Yes |
modelCatalogSnapshot() returns Optional.empty() when no catalog has arrived yet. To get the latest model list for the current account, call getAvailableModels(...).
CommandInfo
Metadata for a command or skill, in com.qoder.sdk.command.
name() always has a value. raw() keeps every field the CLI returned, including fields the SDK does not model.
PreparedQuery
A CLI that has already finished startup, authentication, and initialization, returned by QoderClient.prepare(). Use it to remove the startup latency of the first request. It can be used only once: convert it to a query, convert it to a session, or close it.
| Method | Return type | Description |
|---|---|---|
initialization() | CompletionStage<InitializationResult> | Result of the initialization handshake |
toQuery(UserInput) | QoderQuery | Converts to a one-shot query |
toSession() | QoderSession | Converts to a multi-turn session |
closeAsync() / close() | CompletionStage<Void> / void | Discards an unused prewarmed launch |
IllegalStateException. After conversion, the new QoderQuery or QoderSession owns the resources, and closing the PreparedQuery no longer affects them. If the input to toQuery() is rejected, the returned query's message stream ends with that error.
MessageStream
The message stream of a query or session. It supports two mutually exclusive ways to consume it: pull with openCursor(), or let the SDK push with consume().
| Method | Return type | Description |
|---|---|---|
openCursor() | MessageCursor | Opens a pull cursor that reads messages one by one in order |
consume(MessageHandler, Executor) | CompletionStage<Void> | Calls the handler for each message in order on the given executor; the next message is delivered only after the previous one is handled |
consume() completes normally when the stream ends. It completes exceptionally in the following cases, and the owning query or session is closed:
| Case | Exception |
|---|---|
| The handler throws | CallbackExecutionException |
| The executor rejects the task | CallbackRejectedException |
| The consuming thread is interrupted | ConsumerInterruptedException |
| Reading or decoding a message fails | The stream's failure cause; non-SDK exceptions are wrapped with error code MESSAGE_DELIVERY_FAILED |
openCursor() or consume(). A second subscription throws MessageStreamAlreadyConsumedException, and the first subscription counts as consumption even if it fails. Buffered messages remain readable after the CLI stops producing output.
MessageCursor
A pull cursor.
| Method | Return type | Description |
|---|---|---|
hasNext() | boolean | Blocks until the next message; returns false when the stream ends and throws the failure cause when the stream has failed |
next() | SdkMessage | Returns the next message; throws NoSuchElementException when the stream has ended |
close() | void | Closes the cursor and asynchronously closes the owning query or session |
ResultMessage, send the next turn. Do not close and reopen the cursor every turn. Once reading a message fails, the stream terminates, and every later hasNext() / next() throws the same exception. Close the cursor at that point, and create a new session with resume if needed. When the consuming thread is interrupted, ConsumerInterruptedException is thrown.
MessageHandler
The message handler for push mode.
consume() with CallbackExecutionException. With a direct executor (such as Runnable::run), the handler runs on the SDK delivery thread and must return quickly.
Threading and resource release
- Asynchronous methods return
CompletionStage, and each call gives an independent observation. Cancelling or manually completingtoCompletableFuture()affects only that wait and does not cancel the SDK operation. To stop an operation, useinterrupt(),cancelQueuedMessage(), orcloseAsync(). - Handles returned by
openSession()andprepare()belong to the application once delivered. The application must close them even if it never callsget(). If the observing future was cancelled before delivery, the SDK closes the started resources automatically. - Each query, session, and prewarmed launch occupies one CLI child process, so limit concurrency in your application.
QoderClientitself is lightweight and reusable; closing the client cascades to every resource it created. - Permission, MCP, model policy, auth token, and Memory callbacks run on
callbackExecutor; Hooks use a separate SDK thread. Without acallbackExecutor, these callbacks for one session run serially on a single SDK thread, so one blocking callback delays the others and can cause timeouts. Callbacks should return quickly, hand long-running work to the application's own executor, and report completion through the returned stage. - Do not block in SDK callbacks, push handlers, or SDK threads; to close the owning session or client from those places, call
closeAsync()and return. Calling the blockingclose()on an SDK-owned thread or inside a push handler starts an asynchronous close and then throwsIllegalBlockingCloseException. SDK-owned threads include the thread that runs callbacks when nocallbackExecutoris configured, and the threads that run hooks and diagnostics listeners; a session's or query'sclose()checks only its own threads, while the client'sclose()checks those of every session and query it created. A push handler throws even when it runs on an application executor. With a customcallbackExecutor, callbacks run on application threads, whereclose()does not throw this exception and instead blocks until the close completes. - Completion of
closeAsync()means the SDK has stopped accepting input, queued input and control requests are settled, and the CLI process has gone through the close sequence (close stdin, then request termination and force termination after timeouts). It does not wait for application callbacks or continuations already running, and does not guarantee that the CLI's descendant processes have exited or that temporary files have been deleted. With Memory enabled, an idle session finishes Memory wrap-up before closing and waits at mostdrainTimeoutMs(60 seconds by default).
Configuration
SessionOptions
Immutable options for queries and sessions, used by openSession(), prepare(), and OneShotQueryRequest.
SessionOptions.Builder
| Group | Method | Default | Description |
|---|---|---|---|
| Runtime | workingDirectory(Path) | The client's projectDirectory, then the process's current directory | CLI working directory |
| Runtime | qoderCliPath(Path) | Managed CLI | Explicit CLI executable. When set, the managed CLI is not downloaded, and an invalid path fails immediately |
| Runtime | putEnvironment(String, String) | — | Adds one environment variable for the CLI; proxy URL variables are treated as secrets automatically |
| Runtime | putProxyEnvironment(String, SecretValue) | — | Adds a proxy URL variable as a SecretValue. The name must be a recognized proxy variable (such as HTTPS_PROXY); otherwise it throws IllegalArgumentException |
| Runtime | environment(Map<String, String>) | — | Adds environment variables in bulk; appends rather than replaces |
| Prompt | systemPrompt(String) | Not replaced | Replaces the system prompt |
| Prompt | appendSystemPrompt(String) | Nothing appended | Appends to the end of the system prompt |
| Prompt | customContext(Map<String, String>) | Not set | Default custom context for this session's input. See UserInput |
| Prompt | maxTurns(int) | Unlimited | Maximum number of turns; must be positive |
| Prompt | includePartialMessages(boolean) | false | Delivers incremental PartialMessages. See Streaming output |
| Model | modelSelection(ModelSelection) | The CLI's default model | A fixed model, a dynamic callback, or a structured policy. See ModelSelection |
| Tools | tools(List<String>) | CLI default | Base tool list; an empty list is the same as not setting it |
| Tools | allowedTools(List<String>) | — | Tools pre-authorized to run |
| Tools | disallowedTools(List<String>) | — | Blocked tools; takes priority over allowedTools |
| Permission | permissionMode(PermissionMode) | Decided by the CLI | Permission mode. See PermissionMode |
| Permission | planMode(boolean) | Not set | Initial state of standalone Plan; mutually exclusive with permissionMode(PLAN). See Standalone Plan |
| Permission | allowDangerouslySkipPermissions(boolean) | false | The second switch for BYPASS_PERMISSIONS |
| Permission | canUseTool(ToolPermissionHandler) | None | Tool permission callback. See ToolPermissionHandler |
| MCP | mcpServers(List<McpServerConfig>) | — | MCP server configurations; names must be unique. See McpServerConfig |
| MCP | allowedMcpServerNames(List<String>) | — | Names of MCP servers allowed for use |
| MCP | onElicitation(ElicitationHandler) | None | MCP elicitation callback. See ElicitationHandler |
| Hooks | hooks(List<HookRegistration>) | — | Hook registrations. See HookRegistration |
| Hooks | includeHookEvents(boolean) | false | Delivers Hook notification messages in the message stream; does not affect Hook callbacks |
| Session | resume(String) | — | Resumes by session ID; the ID may contain only ASCII letters, digits, _, and - |
| Session | continueSession(boolean) | false | Continues the most recent session; with a listable external store, selects by the timestamps in the store |
| Session | forkSession(boolean) | false | Used with resume or continueSession to fork into a new session |
| Session | sessionStorage(SessionStorage) | SessionStorage.inherit() | Inherit the client setting, use CLI local storage, or use an external store. See SessionStorage |
| Session | sessionMirrorOptions(SessionMirrorOptions) | defaults() | Batch and wait limits for external store mirroring. See SessionMirrorOptions |
| Session | fileCheckpointing(boolean) | false | Enables file checkpoints. See Checkpoint |
| Memory | memory(MemoryOptions) | Disabled | Native or application-managed Memory. See Memory |
| Control | controlRequestTimeoutMs(long) | 60000 | Control request timeout (milliseconds); 0 means unlimited, negative values are invalid |
| Control | resolveModelTimeoutMs(long) | 500 | Timeout for the dynamic model callback (milliseconds); 0 means unlimited, negative values are invalid |
| Control | debug(boolean) | false | Enables CLI debug output |
| Control | diagnosticsListener(DiagnosticsListener) | — | Session-level diagnostics listener |
Builder.DEFAULT_CONTROL_REQUEST_TIMEOUT_MS (60000L) and Builder.DEFAULT_RESOLVE_MODEL_TIMEOUT_MS (500L).
maxTurns() throws IllegalArgumentException immediately for a non-positive value. build() throws InvalidOptionsException in these cases:
BYPASS_PERMISSIONSwithoutallowDangerouslySkipPermissions(true).- An external
sessionStoragetogether withfileCheckpointing(true). planMode(...)together withpermissionMode(PLAN), includingplanMode(false).resumetogether withcontinueSession;forkSessionwithoutresumeorcontinueSession; aresumeID with invalid characters.- An environment variable name that is empty or contains
=or NUL, or a value that contains NUL. mcpServersthat containnullor duplicate server names.
build() does not mean the CLI supports the selected features. Settings that require CLI capabilities, such as planMode, customContext, and forkSession on an external store, are checked at startup or when sending. See Capability requirements.
By default the CLI child process inherits only PATH; pass other variables explicitly. See Subprocess environment.
OneShotQueryRequest
A one-shot query request, containing one UserInput and one set of SessionOptions.
of(prompt) uses a single text input and default options. On the Builder, set only one of prompt() and input(); setting both throws InvalidOptionsException, and build() throws the same exception when neither is set. Without options(), default options are used.
UserInput
One user input sent to the CLI, with content blocks, a priority, whether it triggers a reply, and optional custom context and skill selection. Immutable.
| Method | Return type | Description |
|---|---|---|
text(String) | UserInput | A single text block with priority NOW and shouldQuery set to true |
content() | List<Content> | The content blocks; never empty |
priority() | SendPriority | The priority hint |
shouldQuery() | boolean | Whether it triggers a reply; false only adds context |
customContext() | Optional<Map<String, String>> | Custom context for this input |
clientComposed() | Optional<Boolean> | Whether the client declares that it already handled slash commands |
selectedSkills() | List<SelectedSkill> | Skills explicitly selected for this input; empty by default |
UserInput.Builder
| Method | Description |
|---|---|
addText(String) | Adds a text block |
addImage(String mediaType, String base64Data) | Adds a base64 image block, for example image/png |
addImageUrl(URI) | Adds a URL image block; must be an absolute HTTP(S) URL with a host. The SDK does not download the image |
addToolResult(String toolUseId, String content) | Adds a tool result block that answers an earlier tool call |
customContext(Map<String, String>) | The complete context for this input, overriding the session default; an empty map clears it |
clientComposed(boolean) | true means the client already handled slash commands, so the CLI no longer parses a leading / |
selectedSkills(List<SelectedSkill>) | Skills explicitly selected for this input |
priority(SendPriority) | Priority; NOW by default |
shouldQuery(boolean) | Whether it triggers a reply; true by default |
build() | Builds the input; throws IllegalStateException when there are no content blocks |
UserInput.Content is a closed hierarchy, independent of the output-side ContentBlock:
| Type | Accessors |
|---|---|
UserInput.TextContent | String text() |
UserInput.ImageContent | String mediaType(), String base64Data() |
UserInput.UrlImageContent | URI url() |
UserInput.ToolResultContent | String toolUseId(), String content() |
customContext is not set, it inherits the default from SessionOptions. When set explicitly, it replaces the default completely without merging. An explicitly set customContext (including an empty map) and non-empty selectedSkills require the CLI to support the corresponding capabilities. See Capability requirements. In a session, rejected input is reported by the stage of send(). For queries and PreparedQuery.toQuery(), rejected input ends the message stream with that error.
SelectedSkill
A skill explicitly selected for this input.
arguments is passed to the CLI as is.
ReadStateSeed
Passed to seedReadState() to tell the CLI which files the host application has shown to the user.
of(path, mtime) uses an mtime the host has already observed (epoch milliseconds). Seed only files that were actually shown; the call does not add file contents to the input.
SendPriority
The priority hint for user input. The SDK does not reorder writes by priority; the CLI decides scheduling.
| Value | Description |
|---|---|
NOW | Handle as soon as possible (default) |
NEXT | After the message currently being handled |
LATER | After all queued messages |
FlagSettings
Feature flags passed to applyFlagSettings().
flags maps flag names to booleans and is required. flags() returns an immutable copy.
ProxySettings
Proxy settings passed to setProxy().
ProxySettings.of(SecretValue.of(url)) and clear it with clear(). The proxy URL and any user name and password in it are registered as secrets, and the SDK redacts them in diagnostics. The deprecated String constructor and proxyUrl() expose plaintext; do not use them for logging.
ManagedRuntimeOptions
Configuration for the managed CLI, set through QoderClient.Builder.managedRuntime(). It applies to launches without qoderCliPath and to installRuntime().
| Builder method | Default | Description |
|---|---|---|
cacheDirectory(Path) | ~/.qoder/runtimes/managed | Cache directory; should be on a local disk. null restores the default |
downloadPolicy(DownloadPolicy) | ALLOW | With DISALLOW, never goes online and fails when neither the cache nor a local archive is available |
baseUrl(URI) | Official download URL | Mirror URL; must be an absolute https URL without user info (http is allowed for loopback addresses) |
authorization(SecretValue) | — | Authorization header for the mirror; sent only to the configured host and not forwarded on redirects to other hosts |
localArchive(Path) | — | A local pinned archive, used before downloading |
connectTimeoutMs(long) / readTimeoutMs(long) | 30000 | Connect and per-read timeouts (milliseconds); must be positive |
totalTimeoutMs(long) | 600000 | Timeout for the whole download (milliseconds); must be positive |
build() | — | Throws InvalidOptionsException when a timeout is not positive or baseUrl is invalid |
- The path set by
SessionOptions.Builder.qoderCliPath(); an invalid path fails immediately. - The
QODERCLI_PATHenvironment variable. - The managed CLI: reuse the cache, or install from
localArchive, the classpath resourceMETA-INF/qoder-sdk/runtime/<file name>, or the download URL, in that order. Skipped on platforms outside the managed set. An installation failure is reported directly; the SDK does not fall back to another CLI. - A CLI of the same version left by an older SDK under
~/.qoder/runtimes/. ~/.local/bin/qodercli, thenPATH.
| Scenario | Approach |
|---|---|
| Local development | Use the defaults; the first launch downloads automatically |
| Prewarm while building an image | Run java -cp qoder-agent-sdk-VERSION.jar com.qoder.sdk.RuntimeInstaller [--cache-dir <dir>] (VERSION is the SDK version your application depends on) during the image build so nothing is downloaded at runtime |
| Ship the archive with the application | Put the pinned archive at META-INF/qoder-sdk/runtime/<file name> on the classpath, or point localArchive(Path) at a local file; also set downloadPolicy(DISALLOW) to make sure nothing goes online |
| Enterprise mirror | Point baseUrl(URI) at the mirror, which serves byte-identical files in the <CLI version>/<file name> layout; set authorization(SecretValue) when authentication is required |
| Self-managed CLI | Set qoderCliPath(Path) or QODERCLI_PATH |
RuntimeInstaller is the command-line entry point. It supports --cache-dir, --base-url, --local-archive, --no-download, --prune, and --print-url. On success it prints the executable path and exits with 0; on failure it exits with 1. Mirror authentication can be set only in code; the command line does not accept credentials.
Downloads choose a proxy from the HTTPS_PROXY, ALL_PROXY, and NO_PROXY environment variables; only http:// proxies are supported. When downloading is disallowed and no source is available, or on network failure, timeout, or verification mismatch, RuntimeInstallException is thrown.
installRuntime() is useful for prewarming the cache during deployment or surfacing installation problems early. On platforms outside the managed set, it fails with RuntimeInstallException. pruneRuntimes() deletes other CLI versions and other digest directories for this platform, skipping directories that another process is installing. Before running it, stop every SDK process that shares the cache from starting new sessions. Closing a session does not delete the cache.
Authentication
Auth
Authentication strategy passed to QoderClient.Builder.auth(...). Package com.qoder.sdk.auth. The constructor is not public; get instances from the static factories. Instances are immutable.
| Factory | Credential source | Typical use |
|---|---|---|
qoderCli() | Reuses the login saved by the local CLI | Developer machine |
accessToken(SecretValue) | Access token supplied by the caller | CI, scripts, services acting for a user |
accessTokenEnv() | Reads the QODER_PERSONAL_ACCESS_TOKEN environment variable at startup | CI, container deployments |
accessTokenEnv(String) | Reads the caller-named environment variable at startup | Custom secret injection |
serviceAccountKey(SecretValue) | Service Account Key; the SDK and CLI exchange it for short-lived tokens | Unattended services in an organization |
serviceAccountTokenProvider(ServiceAccountTokenProvider) | Service Account token refreshed by the host on demand; the long-lived key never enters the SDK | Self-hosted token infrastructure |
jobTokenProvider(JobTokenProvider) | Per-job token supplied by the host | Multi-tenant job runners |
java.io.tmpdir and every ancestor directory must not let other accounts replace their entries; otherwise startup fails with UnsupportedSecureSecretTransportException. If needed, pass -Djava.io.tmpdir= to point at a directory owned only by the current account. When the CLI exits because authentication failed, the SDK throws AuthenticationException. Fix the credentials or account permissions first, then create a new Query or Session; retrying with the same credentials does not help.
See SDK Authentication for full usage of each method.
ServiceAccountTokenProvider
Callback through which the host refreshes short-lived Service Account tokens. The host keeps the long-lived key.
JobTokenProvider
Callback that supplies a per-job token. The SDK passes no job ID to the callback; bind your own job identity in the closure.
null. The CLI may call it several times.
SecretValue
A secret value managed by the SDK. toString() always returns [REDACTED], and the running time of equals and hashCode depends only on the length.
| Method | Return type | Description |
|---|---|---|
of(char[]) | SecretValue | Copies the array, then creates the value |
of(String) | SecretValue | Creates the value from a string |
reveal() | char[] | Returns a plaintext copy; the caller clears it after use |
length() | int | Length of the secret |
Subprocess environment
By default the CLI subprocess inherits only PATH from the parent process. On Windows it also inherits system directory variables such as SystemRoot, TEMP, TMP, USERPROFILE, APPDATA, and LOCALAPPDATA. Pass variables the CLI needs, such as HOME, locale, or configuration directories, explicitly with putEnvironment(String, String) or environment(Map) on SessionOptions.Builder. Pass proxy addresses with putProxyEnvironment(String, SecretValue); the SDK redacts credentials in them. Credential variables managed by the SDK are stripped, so environment variables cannot replace Auth.
Permissions
ToolPermissionHandler
Tool permission callback, registered with SessionOptions.Builder.canUseTool(...). Package com.qoder.sdk.permission. The CLI calls it when it needs to ask about a tool call. Without a handler, the CLI handles such calls by its own rules.
| Parameter | Type | Description |
|---|---|---|
request | ToolPermissionRequest | The tool call awaiting a decision |
cancellation | CancellationToken | Cancelled when the CLI abandons the request, the request times out, or the session closes |
PermissionResult; it must not be null. The callback runs on callbackExecutor; see Threading and resource release. If the callback throws or the stage completes exceptionally, only this permission request fails with CALLBACK_EXECUTION_FAILED; the session is unaffected. If it does not complete within controlRequestTimeoutMs, the request fails with CONTROL_REQUEST_TIMEOUT and the token is cancelled. For usage, see Permission Control and Approval and User Input.
ToolPermissionRequest
A single tool permission request.
| Method | Return type | Description |
|---|---|---|
toolName() | String | Tool name |
toolInput() | JsonValue | Tool input arguments |
suggestions() | List<JsonValue> | Permission suggestions from the CLI, such as permission rule updates |
toolUseId() | Optional<String> | ID of this tool call |
agentId() | Optional<String> | Agent that made the call |
title() / displayName() / description() | Optional<String> | Title, name, and description for an approval UI |
blockedPath() | Optional<String> | Path that triggered the prompt |
decisionReason() / decisionReasonType() | Optional<String> | Why the CLI needs to ask, and the reason type |
permissionKind() | Optional<String> | Permission kind, an open string |
classifierApprovable() | Optional<Boolean> | A CLI hint; it does not mean the application approved |
options() | List<PermissionOption> | Approval options offered by the CLI; immutable |
details() | Optional<PermissionDetails> | Additional details |
raw() | JsonObject | The full request |
Optional. The SDK does not map permission kinds, option outcomes, or detail types to closed enums; read unknown values from raw().
PermissionOption and PermissionDetails
PermissionOption is one approval option offered by the CLI.
| Method | Return type | Description |
|---|---|---|
outcome() | Optional<String> | Option outcome; unknown values are kept. Corresponds to PermissionScope.outcome() |
name() | Optional<String> | Display name |
allowed() | boolean | Selectable only when the original field is explicitly true; missing or malformed values count as false |
raw() | JsonObject | The full option |
PermissionDetails is an open details model used for prompts such as repeated_tool_call.
| Method | Return type | Description |
|---|---|---|
type() / title() / toolName() / inputPreview() | Optional<String> | Detail type, title, tool name, input preview |
repeatCount() / threshold() | Optional<Long> | Repeat count and threshold; empty when not an integer or out of range |
raw() | JsonObject | The full object |
raw() can contain business data. Do not log them by default.
PermissionScope
Explicit approval scope, used through PermissionResult.withScope(...).
| Constant | Meaning | outcome() |
|---|---|---|
ONCE | Approve this call only | proceed_once |
SESSION | Always approve within this CLI session | proceed_always |
PERSIST | Ask the CLI to save the approval persistently | proceed_always_and_save |
PERSIST approvals. The SDK keeps no permission store of its own and does not guarantee support for every permission kind.
PermissionResult
The decision of a permission callback: allow (optionally rewriting the tool input) or deny (with a reason).
| Method | Return type | Description |
|---|---|---|
allow() | PermissionResult | Allow with the original tool input |
allow(JsonValue) | PermissionResult | Allow; the CLI uses the given input instead |
deny(String) | PermissionResult | Deny; the reason is passed to the CLI |
withScope(PermissionScope) | PermissionResult | Returns a new allow with an explicit scope; calling it on a deny throws IllegalStateException |
isAllowed() | boolean | Whether the call is allowed |
updatedInput() | Optional<JsonValue> | The rewritten input |
denyReason() | Optional<String> | The deny reason |
scope() | Optional<PermissionScope> | Explicit scope; empty when withScope was not called, and the CLI treats it as ONCE |
options(), an allow decision (including allow() with no scope) must match an option whose allowed() is true. Otherwise this permission request fails and no allow is sent. After the CLI cancels the request, a late decision is not sent.
PermissionMode
Permission mode. It is a final class rather than an enum, and unknown values are kept as is, so modes added by newer CLIs stay usable.
| Constant | value() | Description |
|---|---|---|
DEFAULT | default | Request permissions by CLI rules |
ACCEPT_EDITS | acceptEdits | Accept file edits automatically |
BYPASS_PERMISSIONS | bypassPermissions | Skip all permission checks; requires allowDangerouslySkipPermissions(true) as well |
PLAN | plan | Plan without executing; to combine it with another mode, use standalone Plan |
DONT_ASK | dontAsk | Never ask; operations that would need a prompt are denied |
AUTO | auto | Decide automatically |
of(String) ignores case and any character that is not a letter or digit; "yolo" equals BYPASS_PERMISSIONS. Set the mode at startup with SessionOptions.Builder.permissionMode(...) and switch it at runtime with QoderSession.setPermissionMode(...). Using BYPASS_PERMISSIONS in either place requires the session to set allowDangerouslySkipPermissions(true); otherwise it fails with InvalidOptionsException. For the behavior of each mode, see Permission Control.
CancellationToken
Cancellation token passed to callbacks. It is cancelled when the CLI cancels the request, the callback times out, or the session closes. The SDK sends the protocol-level cancellation response; the application only needs to stop the related work.
| Method | Return type | Description |
|---|---|---|
isCancelled() | boolean | Whether the token is cancelled; once true, it never changes |
onCancel(Runnable) | void | Registers a cancellation action; it also runs if the token is already cancelled. Actions run asynchronously, never inline on the registering thread, and not on callbackExecutor |
registerOnCancel(Runnable) | CancellationRegistration | Same as above, and returns a handle for unregistering |
CancellationRegistration.close() | void | Unregisters the action; idempotent and non-blocking. An action that already started is not interrupted |
onCancel actions are delivered on a best-effort basis, and queued actions may be dropped when the session closes. Work that must stop reliably should check isCancelled() at its own side-effect boundaries. For long-lived work, prefer registerOnCancel and call close() when the work ends to release the action and the objects it captures.
Standalone Plan
The Plan state can be set independently of the permission mode. Turning Plan on keeps the current permission mode. Package com.qoder.sdk.plan.
| Method | Description |
|---|---|
SessionOptions.Builder.planMode(boolean) | Plan state at startup. Without the call, the CLI Plan state is left unchanged; passing false explicitly asks the CLI to leave Plan |
setPlanMode(boolean) | Turns Plan on or off at runtime and returns the state confirmed by the CLI |
getPlanMode() | Queries the current Plan state of the CLI |
setPermissionModePreservingPlan(PermissionMode) | Switches the permission mode and keeps the Plan state; passing PermissionMode.PLAN fails with InvalidOptionsException |
PlanModeSnapshot.active() | Whether Plan is on |
- All of these entry points need CLI support for the corresponding capability; see Capability requirements. Without it they fail with
UnsupportedCliCapabilityExceptionand send no request. - At startup,
planMode(...)andpermissionMode(PermissionMode.PLAN)are mutually exclusive, even withplanMode(false);build()throwsInvalidOptionsException. QoderSession.setPermissionMode(...)does not guarantee that Plan is kept. UsesetPermissionModePreservingPlan(...)when it must be kept.- Trust the
PlanModeSnapshotreturned by the CLI or thePlanModeChangedMessagein the message stream. Do not infer the current state from the last value you set locally.
Hooks Reference
HookRegistration
One Hook registration: an event name, an optional matcher, and a handler. Package com.qoder.sdk.hook. Immutable; build it with builder() and register it with SessionOptions.Builder.hooks(List<HookRegistration>).
| Builder method | Parameter type | Description |
|---|---|---|
event(String) | String | Required. Event name, such as PreToolUse, PostToolUse, or Stop |
matcher(String) | String | Optional. Matched by the CLI, for example the tool name Bash |
handler(HookHandler) | HookHandler | Required. The handler |
timeoutMs(long) | long | How long the Java side waits for this registration (milliseconds). 0 (default) uses controlRequestTimeoutMs; a negative value throws IllegalArgumentException |
build() | — | Builds the registration; throws NullPointerException if event or handler is missing |
timeoutMs limits only the Java-side wait and is not sent to the CLI. To see Hook lifecycle events in the message stream, also set includeHookEvents(true); the messages are described in HookEventMessage. For available events and usage, see Hooks.
HookHandler
Hook handler. It is called on the SDK's dedicated Hook thread, not on callbackExecutor. Hand slow work to an application executor and return a stage that signals completion.
PreToolUse to deny the tool call:
CALLBACK_EXECUTION_FAILED. A timeout fails with CONTROL_REQUEST_TIMEOUT.
HookEvent
A single Hook event.
| Method | Return type | Description |
|---|---|---|
eventName() | String | Event name, taken from hook_event_name in the CLI input |
matcher() | Optional<String> | Matcher echoed by the CLI; usually empty |
payload() | JsonValue | The CLI Hook input object, as raw JSON |
payload() fields: the shared fields session_id, cwd, and hook_event_name; PreToolUse and PostToolUse carry tool_name, tool_input, and tool_use_id, and PostToolUse also carries tool_response; UserPromptSubmit carries prompt; Stop carries stop_hook_active. The field set depends on the CLI version. The SDK neither models nor validates it, so tolerate missing fields when reading.
MCP
McpServerConfig
MCP server configuration, registered with SessionOptions.Builder.mcpServers(List<McpServerConfig>). Package com.qoder.sdk.mcp. Instances are immutable.
| Factory | type() | Description |
|---|---|---|
stdio(name, command, args) | stdio | Server run as a subprocess started by the CLI; null args counts as an empty list |
sse(name, url) | sse | Remote SSE server |
http(name, url) | http | Remote Streamable HTTP server |
inProcess(name, handler) | sdk | Server inside the JVM process. The SDK forwards messages to InProcessMcpHandler; there is no subprocess |
| Chained method | Applies to | Description |
|---|---|---|
withEnv(String, String) | stdio | Adds an environment variable |
withSecretEnv(String, SecretValue) | stdio | Adds a secret environment variable, overriding a plain variable of the same name |
withHeader(String, String) | sse / http | Sets a request header; names are case-insensitive |
withSecretHeader(String, SecretValue) | sse / http | Sets a secret request header, overriding a plain header of the same name |
IllegalStateException. Server names in one list must be unique (case-sensitive); otherwise SessionOptions.Builder.build() throws InvalidOptionsException. The MCP configuration, secrets included, reaches the CLI through a temporary file readable only by the current account, never through launch arguments or environment variables. Do not delete these files while the session runs. For integration details, see MCP Integration.
InProcessMcpHandler
JSON-RPC endpoint of an in-process MCP server, registered with McpServerConfig.inProcess(name, handler).
| Method | Description |
|---|---|
handle(JsonValue) | Handles one JSON-RPC message and returns a stage that completes with the response |
handle(JsonValue, CancellationToken) | The entry point the SDK actually calls; by default it delegates to the one-argument version. Override it to observe cancellation; see CancellationToken |
newConnection() | Called once each time a session registers the server; returns itself by default. A handler with per-connection state should return a separate instance and must not return null |
jsonrpc and the id of the matching request. The returned stage must not be null; completing it with JsonNull.INSTANCE means there is no JSON-RPC response (for example, for a notification). The handler runs on callbackExecutor. If it throws or the stage completes exceptionally, only that message is affected. Within one connection, a request ID stays reserved until the stage returned by handle() settles; reusing it yields DUPLICATE_MCP_REQUEST_ID. When 128 requests are outstanding, new requests get MCP_REQUEST_CAPACITY_EXCEEDED. Cancellation does not undo side effects the handler already produced.
McpServerStatus
Status of one MCP server, returned by QoderSession.mcpServerStatus().
| Method | Return type | Description |
|---|---|---|
name() | String | Server name |
status() | String | Status string, as reported by the CLI |
raw() | JsonValue | Raw status |
raw(), including env and headers. Secrets registered through withSecretEnv, withSecretHeader, and similar methods are replaced with [REDACTED], and the same applies to names and status text.
McpAuthenticationResult and McpAuthenticationOptions
Used for MCP OAuth. Flow: start authentication with QoderSession.beginMcpAuthentication(serverName[, options]). When the result needs user action, the host shows authUrl(), obtains the full callback URL, and calls mcpSubmitOAuthCallbackUrl(serverName, callbackUrl). The SDK does not open a browser; the CLI handles state validation, credential exchange, and reconnection. Finish authentication before sending the first business input.
| Method | Description |
|---|---|
requiresUserAction() | Whether the host must complete user authorization; false means authentication is already done |
authUrl() | Authorization entry point; always present when user action is required |
McpAuthenticationOptions.defaults() | Uses the CLI default callback address |
McpAuthenticationOptions.redirectUri(String) | URI where the host receives the callback; must not be blank |
toString() of both classes omits URLs. For the full flow, see MCP OAuth authentication.
ElicitationHandler
MCP elicitation callback, triggered when an MCP server needs to collect more information from the user. Register it with SessionOptions.Builder.onElicitation(...).
callbackExecutor and is bound by controlRequestTimeoutMs. If it throws or the stage completes exceptionally, only this elicitation fails with CALLBACK_EXECUTION_FAILED.
ElicitationRequest
ElicitationResult
Result of an elicitation.
| Factory | action() | Description |
|---|---|---|
accept(JsonValue) | accept | The user accepted, with content filled in per the schema; null content throws NullPointerException |
decline() | decline | The user declined to answer |
cancel() | cancel | The user cancelled the request |
Models
Model types live in the com.qoder.sdk.model package. For selection modes and callback usage, see Model Selection.
ModelSelection
The model selection strategy passed to SessionOptions.Builder.modelSelection(...). Choose exactly one of the three strategies.
| Factory method | Return type | Description |
|---|---|---|
staticModel(ModelId) | StaticModelSelection | Use one fixed model for the whole session |
dynamic(ModelPolicyProvider) | DynamicModelSelection | The CLI picks the initial model by default; after that, the callback returns a model ID for each model request |
dynamic(ModelId, ModelPolicyProvider) | DynamicModelSelection | Same as above, with an explicit initial model |
policy(ModelDecisionProvider) | PolicyModelSelection | The CLI picks the initial model by default; the callback returns a structured result that keeps the current model or overrides the model and parameters |
policy(ModelId, ModelDecisionProvider) | PolicyModelSelection | Same as above, with an explicit initial model |
openSession() completes.
ModelPolicyProvider
The callback used by dynamic(...). It returns the model ID for the current request.
| Parameter | Type | Description |
|---|---|---|
context | ModelRequestContext | Context of this model request |
SessionOptions.Builder.resolveModelTimeoutMs(long). The default is 500 ms, and 0 means no limit. If the callback throws, returns a failed stage, or times out, this model request fails and does not fall back to the initial model. These failures are not handed to the application as exceptions; neither the message cursor nor the SDK's async methods throw them. The SDK only returns an error code to the CLI (MODEL_RESOLUTION_TIMEOUT for a timeout, CALLBACK_EXECUTION_FAILED for a callback failure) and emits an inbound_control_request_failed diagnostic event whose detail contains error=<error code>. To observe these failures, register a DiagnosticsListener, and handle the turn result through the outcome() of ResultMessage.
ModelDecisionProvider
The callback used by policy(...). It returns a ModelPolicyResult. The context, execution thread, and timeout budget are the same as for ModelPolicyProvider.
null, completes the stage with null, throws, or times out, this model request fails. To keep the model the CLI has currently selected, explicitly return ModelPolicyResult.keepCurrent().
ModelPolicyResult
The immutable return value of ModelDecisionProvider. Only explicitly set fields are sent. If no model is set, the model the CLI has already selected is kept.
| Builder method | Description |
|---|---|
model(ModelId) | Overrides the model for this request |
parameters(JsonObject) | Request parameters passed to the CLI; the SDK does not interpret them |
taskId(String) | Inference task identifier |
subTask(String) | Business sub-task identifier |
build() | Creates the result; with nothing set, it is equivalent to keepCurrent() |
null. parameters is not a channel for secrets; do not put credentials in it.
ModelRequestContext
The context of one model request, passed by the SDK to the model callback.
| Method | Return type | Description |
|---|---|---|
sessionId() | Optional<String> | ID of the owning session |
purpose() | Optional<String> | What the CLI is calling the model for |
turnIndex() | Optional<Integer> | Turn index |
agentId() / agentType() | Optional<String> | ID and type of the subagent that made the request |
resolvedModel() | Optional<ModelId> | The model the CLI preselected before the callback |
availableModels() | List<ModelInfo> | The model catalog attached to this request, immutable; an empty list when not provided |
Optional.empty(). availableModels() is a snapshot for this request only; do not cache it as a fixed catalog for the whole session.
ModelInfo
One entry in the model catalog. Returned by QoderSession.getAvailableModels(), and also present in ModelRequestContext and ModelCatalogSnapshot.
| Method | Return type | Description |
|---|---|---|
modelId() | ModelId | Model ID usable for model selection |
displayName() / description() | Optional<String> | Display name and description |
source() / outerProvider() / descriptionEn() | Optional<String> | Source, outer provider, and English description |
isEnabled() | boolean | Whether the current account can select the model; treated as false when the CLI omits the field |
raw() | JsonValue | The full raw entry, including unmodeled fields such as context window and pricing |
isEnabled() is false. Filter them out when showing selectable models.
ModelCatalogOptions
Read options for QoderSession.getAvailableModels(ModelCatalogOptions).
defaults() and the no-argument getAvailableModels() both use the CLI's default caching strategy. The Builder's fetchStrategy(FetchStrategy) explicitly selects a live read (LIVE) or the cache (CACHE), and uid(String) sets the catalog uid.
ModelCatalogSnapshot
The local model catalog snapshot returned by QoderSession.modelCatalogSnapshot(). It comes from the initialization result or the latest catalog update pushed by the CLI.
modelCatalogSnapshot() returns Optional.empty(), no catalog has been received yet. A snapshot with an empty models() means the CLI explicitly sent an empty catalog. Reading the snapshot sends no request to the CLI; call getAvailableModels(...) for live results for the current account.
ModelId
Value type for a model ID.
Memory
Memory configuration is passed through SessionOptions.Builder.memory(MemoryOptions); the related types live in the com.qoder.sdk.memory package. The CLI generates, writes, and loads memory. This is separate from SessionStore, which stores session transcripts. For enabling memory, application-managed directories, and per-turn generation control, see Memory.
Memory types
| Type | Purpose |
|---|---|
MemoryOptions | Query-level configuration entry point. disabled() is the default; nativeBuilder() uses the CLI's native memory, and customBuilder() lets the application set the generation and loading configuration. You can set user-level and project-level scopes, result callbacks, and drainTimeoutMs (default 60000 ms) |
MemoryGenerationOptions | Generation configuration for custom mode: on/off switch, memory directories (roots), generation prompt, and automatic extraction after each turn |
MemoryTurnCompleteOptions | Configuration for automatic extraction after each turn: on/off switch, the shouldGenerate decision callback, timeout (default 10000 ms), and the decision-failure policy OnGateError (SKIP / REPORT_FAILED) |
MemoryShouldGenerate | Per-turn decision callback; returns CompletionStage<MemoryGenerationGateResult> |
MemoryGenerationGateInput / MemoryGenerationGateResult | Input of the decision callback (session, turn, prompt, answer, and so on), and the run() / skip(reason) decision result |
MemoryConsumptionOptions | Loading configuration for custom mode: on/off switch, file list, maxTokens, overflow handling Overflow (TRUNCATE / FAIL_QUERY), and read-failure handling FailureMode (BEST_EFFORT / FAIL_QUERY) |
MemoryRoot / MemoryConsumptionFile | Memory directory and loaded file, created with builder(String id, Path path). A directory can set access (READ / READ_WRITE) and an index file; a loaded file can be marked as required |
MemoryGenerationHandler / MemoryConsumptionHandler | Notification callbacks for generation and loading results; return CompletionStage<Void> |
EffectiveMemoryConfig | The actual configuration the CLI confirms during initialization, returned by InitializationResult.memory(). It includes the enabled state, mode, each switch, and the id and access of each root, but not the resolved file paths |
MemoryGenerationResult / MemoryConsumptionResult | Generation and loading results: status, per-file details, and raw JSON. Generation statuses are SAVED, PARTIAL, NO_CHANGE, SKIPPED, and FAILED; loading statuses are SUCCESS, PARTIAL, and FAILED. Unrecognized statuses are UNKNOWN |
MemoryUsage | Usage produced by memory generation itself; values the CLI does not provide are Optional.empty() |
MemoryGenerationMessage and MemoryConsumptionMessage. memory(null) throws NullPointerException; to turn memory off, pass MemoryOptions.disabled().
flushMemory() and refreshMemory()
Both QoderQuery and QoderSession provide these two methods. They return CompletionStage<Void> and wait for initialization to complete on their own.
flushMemory(): waits for memory processing the CLI has already triggered to finish. It does not wait for later turns and does not guarantee that every file was saved, so still checkMemoryGenerationResult. The wait is capped bydrainTimeoutMs.refreshMemory(): rereads the memory files with the current loading configuration. Call it after memory files are modified externally. It does not change the configuration. The timeout iscontrolRequestTimeoutMs.
UnsupportedCliCapabilityException and send no request. When a one-shot query enables memory, the message stream keeps delivering memory processing messages after ResultMessage, so consume the stream to the end.
Session Storage
Types for external session storage. SessionStorage and SessionRepository live in the com.qoder.sdk package; the rest live in com.qoder.sdk.session. For usage, see External Session Storage and Session Control.
SessionStorage
Selects which history storage a single session uses. Set it with SessionOptions.Builder.sessionStorage(...).
| Factory method | Description |
|---|---|
inherit() | The default. Uses the client store configured with QoderClient.Builder.sessionStore(...); uses the CLI's local history when the client has none |
cliLocal() | This session uses no external store, only the CLI's local history |
external(SessionStore) | This session uses the given external store; the argument must not be null |
fileCheckpointing(true).
SessionStore
The interface an external session store implements. The client sets the default store with QoderClient.Builder.sessionStore(...), which client.sessions() also uses.
| Method | Return type | Description |
|---|---|---|
append(SessionKey, AppendBatch) | CompletionStage<AppendResult> | Atomically appends one batch |
load(SessionKey) | CompletionStage<Optional<SessionSnapshot>> | Loads the full transcript snapshot; empty when the key does not exist or was deleted |
Implementation contract
| Contract | Requirement |
|---|---|
| Ordering and isolation | load returns entries in append order; data under different SessionKeys is invisible to each other |
| Atomicity | Concurrent appends to the same key must be serialized; a batch is either written entirely or fails entirely |
| Idempotency | An operationId already written under the same key must not be written again. Return the original operation's newVersion and appendedCount, marked as a replay. This check runs before CAS; a deleted key is always rejected with SessionStoreDeletedException |
| CAS | When expectedVersion is not null, it must equal the current version; otherwise throw SessionStoreConflictException carrying the current version. When it is null, append unconditionally. The current version of an empty key is VersionToken.initial() |
| UUID integrity | An entry with the same uuid and the same rawJson is not written twice; the same uuid with a different rawJson must be reported as a conflict. Entries without a uuid are not deduplicated by content |
MirrorErrorMessage and diagnostic events, and the main answer may continue. A successful answer does not mean the transcript was persisted.
ListableSessionStore
A store that can enumerate sessions. The list() of SessionRepository, continuing the most recent session, and subagent restore all depend on it.
listSessions returns every key under the project (including subpath keys), in no guaranteed order. deleteSession deletes only the given key, not the other subpaths of the same session.
SessionDeletionStore
A store that supports deleting a whole session. SessionRepository.delete(...) requires the store to implement this interface.
SessionStoreDeletedException, and an old version cannot rebuild the data. Deleting a session that does not exist or was already deleted should also succeed. A deleted session ID cannot be reused.
InMemorySessionStore
The SDK's built-in in-memory implementation of SessionDeletionStore. Data is lost when the process exits, so it suits tests and short-lived processes.
SessionMirrorOptions
Resource limits for writes to the external store (mirroring), set with SessionOptions.Builder.sessionMirrorOptions(...). Create it with builder(); defaults() returns the defaults. All values must be positive.
| Setting | Default | Description |
|---|---|---|
maxPendingBatches | 256 | Maximum number of batches waiting to be written |
maxPendingBytes | 64 MiB | Maximum raw content bytes of batches waiting to be written |
operationTimeoutMs | 60000 | Total time for each batch, from reading the version to finishing the append (including retries) |
closeDrainTimeoutMs | 5000 | How long closing a session waits for remaining batches to be written |
MirrorErrorMessage and diagnostic events and do not interrupt the model turn.
SessionRepository
Reads, continues, forks, or deletes saved sessions. Use client.sessions() for the repository in the client's default scope, and client.sessions(SessionOptions) for the repository in the scope a given session uses.
| Method | Return type | Description |
|---|---|---|
list() | CompletionStage<List<String>> | IDs of saved sessions in the current scope; requires ListableSessionStore |
get(String) | CompletionStage<Optional<SessionSnapshot>> | Transcript snapshot of one session; requires an external store |
delete(String) | CompletionStage<Void> | Deletes a session and all its subpaths; requires SessionDeletionStore |
continueSession(String, SessionOptions) | CompletionStage<QoderSession> | Continues a session in a new CLI process; passing null as sessionId continues the most recent session |
fork(String, SessionOptions) | CompletionStage<QoderSession> | Creates a new session starting from the given session |
- A repository is bound to one store and one project key. The store is resolved from
sessionStorage. The project key comes fromworkingDirectory, falling back to the client'sprojectDirectory, and isdefaultwhen neither is set. sessions(options)takes only the store and the directory fromoptions. It starts no process and keeps no other configuration, such as the CLI path or environment variables.- When the
optionsofcontinueSessionorforkset no directory or store, the repository's scope is used. An explicit override can open a session in another scope, but it does not changelist,get, ordeleteof the current repository.
UnsupportedCliCapabilityException and does not fall back to deleting key by key. fork on an external store also requires the CLI to support the corresponding capability; see Capability requirements.
Storage value types
Value types used in SessionStore implementations.
| Type | Member | Description |
|---|---|---|
SessionKey | projectKey / sessionId / subpath | Store key. The session root's subpath is an empty string; subagent transcripts use a non-empty subpath. projectKeyFor(Path) derives the project key from the project directory |
AppendBatch | operationId | Idempotency ID; retries with the same ID are applied only once |
expectedVersion | Expected CAS version; null means an unconditional append | |
firstSequence | Ordering hint for the first entry in the batch; it does not replace the CAS version or the idempotency ID | |
entries | Batch entries. The Builder provides operationId, expectedVersion, firstSequence, addEntry, and addEntries | |
AppendResult | applied(...) / replayed(...) | Result of an actual write, or of an idempotent replay; a replay passes the original operation's version and count |
alreadyApplied() | Whether this is a replay | |
SessionSnapshot | entries / version | Entries in append order and the current version |
SessionStoreEntry | rawJson() | Raw JSON line; type, uuid, and timestamp are extracted from it. toString() does not print the content |
OperationId | of / random | Idempotent operation ID |
VersionToken | initial() | Starting version of an empty store; the version must advance after each successful write |
SessionStoreDeletedException | CODE | Rejects writes to a deleted session, with error code SESSION_STORE_DELETED; not retryable. It is not a CAS conflict, so do not retry with the current version |
Results and Identifiers
InitializationResult
The result of the initialization handshake, returned by methods such as QoderQuery.initialization() and QoderSession.initialization(). It is a read-only snapshot taken when the handshake completes and does not change afterward.
| Method | Return type | Description |
|---|---|---|
capabilities() | Map<String, CapabilityState> | Capabilities the CLI declared during the handshake, immutable |
capability(String) | CapabilityState | State of one capability; UNKNOWN when not declared |
cliVersion() | Optional<String> | CLI version |
wireVersion() | Optional<String> | Negotiated wire protocol version |
sessionId() | Optional<String> | Session ID assigned by the CLI |
memory() | Optional<EffectiveMemoryConfig> | Memory configuration confirmed by the CLI; empty when memory is not enabled |
UNSUPPORTED, and later calls in the same session fail locally. InitializationResult does not reflect this change; to observe it, listen for diagnostic events with type capability_downgraded.
Methods and required capabilities
The following methods and settings require the CLI to declare the corresponding capability during the handshake. When the capability state is not SUPPORTED (including an undeclared UNKNOWN), the SDK sends no request and fails with UnsupportedCliCapabilityException.
| Method or setting | Required capability | When unsupported |
|---|---|---|
QoderSession.backgroundTasks() / backgroundTasks(String) | background_tasks_v1 | The returned stage completes exceptionally |
QoderSession.stopTask(TaskId) | background_tasks_v1 | The returned stage completes exceptionally |
QoderSession.rewindHistory(MessageId) | session_rewind_v1 | The returned stage completes exceptionally |
QoderSession.reloadSkills() | reload_skills_v1 | The returned stage completes exceptionally |
getPlanMode() / setPlanMode(boolean) / setPermissionModePreservingPlan(PermissionMode) | plan_mode_v1 | The returned stage completes exceptionally |
SessionOptions.Builder.planMode(boolean) | plan_mode_v1 | Initialization fails |
SessionOptions.Builder.customContext(Map) | custom_context_v1 | Initialization fails |
UserInput.Builder.customContext(Map) (including an empty map) | custom_context_v1 | The write stage of that input completes exceptionally; initialization fails when it is the first input |
UserInput.Builder.selectedSkills(List) (non-empty) | selected_skills_v1 | The write stage of that input completes exceptionally; initialization fails when it is the first input |
Remote cancellation in QoderSession.cancelQueuedMessage(MessageId) | interrupt_cancel_queued_v1 | Only local cancellation; returns false without throwing once the input has started writing |
External SessionStore with forkSession(true), including SessionRepository.fork(...) | session_store_fork_barrier_v1 | Initialization fails |
QoderSession control methods (such as getAvailableModels, rewindFiles, and setPermissionMode) do not check capabilities and send the request directly.
CapabilityState
The three states of a CLI capability.
| Value | Description |
|---|---|
SUPPORTED | The CLI declares support |
UNSUPPORTED | The CLI explicitly declares no support, or rejected a request for this capability at runtime |
UNKNOWN | The CLI did not declare it; methods that depend on the capability fail directly, and the SDK does not probe |
InterruptResult
The result of interrupt().
| Method | Return type | Description |
|---|---|---|
interrupted() | boolean | Whether an interrupt was sent to a running turn; false when no turn is running |
turnState() | String | Turn state at return time: IDLE, RUNNING, or INTERRUPTING |
ResultMessage. After a failed interrupt request (including a timeout), the turn returns to RUNNING and you can retry.
MessageSubmission
The submission handle returned by QoderSession.submit(UserInput).
| Method | Return type | Description |
|---|---|---|
id() | MessageId | Input ID available immediately, which you can pass to cancelQueuedMessage; it does not mean the input was accepted or written |
written() | CompletionStage<MessageId> | Completes after the input is written to the CLI, without waiting for the answer. Completes exceptionally when the input is rejected or the write fails, and with CancellationException when local cancellation succeeds |
send(input) when you only need write confirmation; use submit(input) when you may need to cancel before the write. written() returns an independent stage on each call; cancelling it does not cancel the input itself.
Identifier types
| Type | Description |
|---|---|
MessageId | ID of a user input, from MessageSubmission.id() or the return value of send(...); used by cancelQueuedMessage, rewindHistory, and rewindFiles |
TaskId | Background task ID, passed to stopTask(TaskId) |
SessionTitle | Session title returned by generateSessionTitle() |
RewindFilesOptions / RewindFilesResult
Options and result of QoderSession.rewindFiles(MessageId, RewindFilesOptions). The session must be started with SessionOptions.Builder.fileCheckpointing(true); see File Checkpoint and Rewind.
| Method | Return type | Description |
|---|---|---|
RewindFilesOptions.defaults() | RewindFilesOptions | Actually rewinds files |
RewindFilesOptions.dryRun() | RewindFilesOptions | Only reports which files would change, without writing to disk |
canRewind() | boolean | Whether files can be rewound (dry run) or were rewound |
filesChanged() | List<String> | Affected files |
error() | Optional<String> | Why the files cannot be rewound |
Message Types
SdkMessage
Abstract base class of every message in the message stream, in com.qoder.sdk.message. Use instanceof to check the concrete type.
| Method | Return type | Description |
|---|---|---|
wireType() | String | Top-level type of the message |
uuid() | Optional<String> | Message UUID, present when the CLI provides one |
sessionId() | Optional<String> | ID of the owning session, present when the CLI provides one |
rawJson() | Optional<String> | Raw JSON of the message. Parse it with JsonValue.parse to read fields the SDK does not model |
| Class | wireType() | Description |
|---|---|---|
AssistantMessage | assistant | Assistant reply |
ResultMessage | result | Result at the end of a turn |
UserReplayMessage | user | User message replayed by the CLI |
PartialMessage | partial | Streaming delta event |
SystemMessage and subclasses | system | System messages, split into dedicated classes by subtype(), including Hook notifications and Memory results |
HookEventMessage | hook_event | Legacy Hook event |
MirrorErrorMessage | mirror_error | Session mirroring write failed |
UnknownMessage | Raw value | Unknown top-level type |
SystemMessage. Check the dedicated subclasses before SystemMessage; otherwise the broad branch hides the dedicated types.
AssistantMessage and content blocks
Delivered when the assistant produces a reply. Content blocks are output-side types, independent of the input-side UserInput content blocks.
| Method | Return type | Description |
|---|---|---|
AssistantMessage.content() | List<ContentBlock> | Content blocks |
AssistantMessage.getText() | String | Text of all TextBlocks concatenated |
ContentBlock.type() | String | The block's type, such as text |
TextBlock.text() | String | Text content |
UnknownContentBlock.rawJson() | String | Raw JSON of the block |
text blocks, as TextBlock. tool_use, thinking, and other blocks are kept as UnknownContentBlock: type() returns the raw type, and you parse rawJson() when you need the fields. The TypeScript and Python SDKs provide typed classes for these blocks.
ResultMessage and usage
Delivered when a turn ends. Agent-level failures, such as reaching the turn limit, are reported as a ResultMessage whose outcome() is ERROR. They do not throw.
| Method | Return type | Description |
|---|---|---|
wireSubtype() | String | Raw subtype, such as success or error_max_turns |
wireIsError() | Optional<Boolean> | Raw is_error; empty when the CLI does not provide it |
outcome() | ResultOutcome | Normalized outcome; rules below |
result() | Optional<String> | Final result text |
errors() | List<String> | Error descriptions; may be empty, never null |
usage() | Usage | Token usage for this turn |
Usage.inputTokens() / outputTokens() | long | Input and output token counts |
outcome() rules: ERROR when is_error is true or subtype starts with error; SUCCESS when subtype is success; UNKNOWN otherwise.
Usage has only the input and output counts. It does not include cost, cache tokens, or context usage; read those from rawJson() when you need them. A missing count, or one that cannot be represented exactly as a long, becomes 0, so 0 does not mean the CLI confirmed zero usage.
With Memory enabled, ResultMessage only marks the end of the answer turn. Memory result messages can still arrive afterwards, so read a one-shot query to the end of the stream. Usage for Memory generation is reported separately and is not counted in this message's usage().
SystemMessage and subclasses
System messages from the CLI. SystemMessage itself carries subtypes that have no dedicated class, and it also keeps unknown subtypes.
subtype() | Class | Key accessors | Description |
|---|---|---|---|
| Other or unknown values | SystemMessage | subtype() | Full payload in rawJson() |
plan_mode_changed | PlanModeChangedMessage | planMode() → PlanModeSnapshot | Standalone Plan state changed; see Permissions |
commands_changed | CommandsChangedMessage | commands() → List<CommandInfo> | The complete command catalog; an empty list clears it |
available_models_update | AvailableModelsUpdateMessage | catalog() → ModelCatalogSnapshot | The complete model list and the current model |
agents_md_size_warning | AgentsMdSizeWarningMessage | totalBytes(), files(), and others | AGENTS file size warning |
permission_denied | PermissionDeniedMessage | None; read rawJson() | A tool permission was denied |
background_tasks_changed | BackgroundTasksChangedMessage | None; read rawJson() | The set of background tasks changed |
task_updated | TaskUpdatedMessage | None; read rawJson() | A task's status was updated |
hook_started / hook_progress / hook_response | HookStartedMessage and others | See Hook notification messages | Hook run notifications |
memory_generation / memory_consumption | MemoryGenerationMessage / MemoryConsumptionMessage | result() | Memory generation and loading results |
AgentsMdSizeWarningMessage are UTF-8 byte counts reported by the CLI; the SDK does not recompute them. truncated() reflects the load status the CLI reports. It does not mean the SDK truncated anything.
The WIRE_TYPE constants on PermissionDeniedMessage, BackgroundTasksChangedMessage, and TaskUpdatedMessage are deprecated. Check the class or subtype() instead.
When a command or model catalog update is invalid, the SDK does not build the dedicated message. It keeps the previous catalog snapshot, delivers the original message as a plain SystemMessage, and emits an invalid_catalog_update diagnostic.
PartialMessage and UserReplayMessage
PartialMessage is a streaming delta event, delivered only with SessionOptions.Builder.includePartialMessages(true). Its wireType() is partial, and the CLI's original stream_event frame is in rawJson(). The Java SDK has no typed stream events, so parse both text deltas and tool input deltas from rawJson(). See Streaming output.
UserReplayMessage is a user message (type=user) that the CLI replays into the message stream. It is a different type from UserInput and cannot be sent again as is.
Hook notification messages
Notifications about Hook runs. They enter the message stream only with SessionOptions.Builder.includeHookEvents(true). Turning the option off does not affect Hook callbacks. These messages are notifications only and need no reply; to block or modify execution, use HookHandler.
hook_id or exit_code). outcome() keeps unknown values as is. A missing field or one with the wrong type returns empty; exitCode() returns empty when the value is not an integer in the int range, without truncating. The raw values are still available from rawJson().
HookEventMessage corresponds to the legacy type=hook_event message. The SDK does not convert the three system notifications above into it.
MemoryGenerationMessage and MemoryConsumptionMessage
Results of Memory generation and Memory loading. Neither ends a turn. The result types live in com.qoder.sdk.memory; see Memory for usage.
MemoryGenerationResult contains the generation status, the written and failed files, the duration, and optional separate usage. A successful flush does not mean every file was saved, so check status() and failedFiles(). MemoryConsumptionResult contains the overall status and the load status of each file. Unknown statuses map to UNKNOWN, and the raw value stays in wireStatus(). Registering a result callback does not remove these two messages from the message stream.
MirrorErrorMessage and UnknownMessage
MirrorErrorMessage is delivered when mirroring to external session storage fails. It has no dedicated accessors. The error text is in the error field of rawJson(); it is redacted and its format is not stable, so do not match on the string. To classify it, combine sessionId() with the mirror_append_failed diagnostic type.
UnknownMessage carries top-level message types the SDK does not recognize: wireType() returns the raw value, wireSubtype() returns the raw subtype, and the full payload is in rawJson().
JSON Types
JsonValue and subtypes
Immutable JSON types bundled with the SDK, in com.qoder.sdk.json, with no third-party dependencies. Payloads for Hook, MCP, and other callbacks use these types. A message's rawJson() returns a string; parse it with JsonValue.parse(...) when needed.
| Method | Return type | Description |
|---|---|---|
asXxx() | The matching type | Converts the type; throws IllegalStateException on a type mismatch |
asNumber() / JsonNumber.value() | BigDecimal | Numbers are stored exactly as BigDecimal |
JsonObject.get(String) | JsonValue | Returns null when the member does not exist; use contains to check |
JsonObject.members() | Map<String, JsonValue> | Read-only view in insertion order |
JsonObject.Builder.put(String, JsonValue) | Builder | Adds a member; a later value with the same name wins. Neither the name nor the value may be null |
parsethrowsIllegalArgumentExceptionwhen nesting is deeper than 1000 levels or a single number is longer than 1024 characters.JsonNumber.of(double)rejects NaN and infinity.JsonNumber.of(BigDecimal)throwsIllegalArgumentExceptionwhen the serialized value is longer than 1024 characters.
Diagnostics
DiagnosticsListener
Receives the SDK's structured diagnostic events, such as session initialization, close timeouts, capability downgrades, and managed CLI installation.
- Two registration levels. Register at the client level with
QoderClient.Builder.diagnosticsListener(...)and at the session level withSessionOptions.Builder.diagnosticsListener(...). They do not stack: when a session sets a listener, only the session-level one is used; otherwise the client-level one is used. The managed CLI'smanaged_runtime_*events go only to the client-level listener. - Threading. Each session calls the listener serially on its own SDK diagnostics thread, not on
callbackExecutor. A listener registered with several sessions can be called concurrently and must be thread-safe. - Best-effort delivery. When queued events exceed the limit, new events are dropped. A listener that throws loses only that one notification. On close, the queue gets a bounded time to drain; after that the listener is interrupted and the remaining events are dropped. Do not treat diagnostics as a lossless audit log or as the only source of business state.
- Return quickly. Hand slow work to your application's own async mechanism. Listeners run on SDK threads: to close the session that emitted the event or its client, call
closeAsync()and return; calling their blockingclose()from the listener throwsIllegalBlockingCloseException.
DiagnosticEvent
An immutable diagnostic event. A detail produced by the SDK is redacted: registered credentials are removed and credential-like text is detected on a best-effort basis, but arbitrary business data is not guaranteed to be covered.
| Field | Type | Description |
|---|---|---|
type | String | Event type, required; common values below |
sessionId | Optional<String> | ID of the owning session |
requestType | Optional<String> | Related control request type |
durationMs | Optional<Long> | Operation duration in milliseconds |
queueDepth | Optional<Integer> | Queue depth snapshot |
exitCode | Optional<Integer> | CLI exit code |
capability | Optional<String> | Related capability name |
detail | Optional<String> | Free-form detail |
builder(type), the SDK does not redact detail.
Common event types
type | Main fields | Meaning |
|---|---|---|
session_initialized | sessionId, detail | Initialization handshake completed; detail contains the protocol version |
protocol_handshake_warning | detail | Warning from protocol negotiation during the handshake |
session_failed | sessionId, exitCode, detail | Session ended because of an error |
control_request_completed | requestType, durationMs | A control request received a success response |
inbound_control_request_failed | sessionId, detail | A callback request initiated by the CLI (permission, Hook, MCP, model policy, auth token, and so on) failed or timed out, and the SDK replied to the CLI with an error. detail contains request_id and error=<error code>, for example MODEL_RESOLUTION_TIMEOUT |
capability_downgraded | capability, requestType | The CLI reported a control request that had passed the capability check as unsupported at runtime. Later checks use the downgraded state; an InitializationResult already returned does not change |
invalid_catalog_update | sessionId | A command or model catalog update was invalid; the previous snapshot is kept |
memory_callback_failed | detail | A Memory result callback failed or was rejected by the executor; the message is kept and the session continues |
memory_drain_failed / memory_drain_timeout | detail | Waiting for Memory during automatic wrap-up failed or timed out; the main answer is kept and resources continue to be released |
mirror_append_failed | sessionId, detail | A mirroring write to external session storage failed; a MirrorErrorMessage is delivered as well |
mirror_drain_timeout | sessionId, detail | Mirroring writes did not finish within the time limit during close |
process_tree_cleanup_degraded | capability, detail | The current environment cannot fully clean up the CLI process tree. Initialization is not affected, and this does not mean child processes were reaped |
stdin_writer_stop_timeout | sessionId | The writer thread did not stop in time during close. A message being written may be truncated, and the SDK terminates the CLI process instead |
cli_process_alive_after_close | detail | The CLI process is still running after the close sequence finished; detail lists the termination steps taken |
lane_shutdown_timeout | detail | An SDK internal thread did not stop within the close time limit; callbacks that ignore interrupts may keep running after close |
spool_cleanup_deferred | detail | The message buffer's temporary file was still in use during close; it is deleted after reads and writes return |
private_directory_cleanup_failed | detail | The private temporary directory for auth or MCP configuration was not fully cleaned up. When detail contains credential files may remain, credential files may still be on disk |
session_store_cleanup_failed | detail | Cleanup of the temporary directory used to restore an external session failed |
managed_runtime_reused | detail | The cached managed CLI was used |
managed_runtime_install_started / managed_runtime_installed | detail | Managed CLI installation started / completed |
managed_runtime_stale_install_removed | detail | A damaged or incomplete old installation was removed |
managed_runtime_unsupported_platform | detail | No managed CLI exists for the current platform; the SDK looks for an installed CLI instead |
type.
Exceptions
All SDK exceptions extend the unchecked QoderException. Synchronous methods throw directly. Asynchronous methods fail through the returned CompletionStage; when you wait with join(), the exception is wrapped in CompletionException, and getCause() returns the original. Agent-level failures do not throw; the turn ends with a ResultMessage whose outcome() is ERROR.
QoderException
| Method | Return type | Description |
|---|---|---|
getCode() | String | Stable, machine-readable error code; see the exception list |
isRetryable() | boolean | Whether a retry is worthwhile |
printStackTrace() prints the redacted snapshot. The SDK never retries automatically, and isRetryable() currently returns false for every built-in exception. Your application decides from the error code whether to retry in a new session.
UnsupportedCliCapabilityException
- The method needs a CLI capability that the CLI declares unsupported (
UNSUPPORTED) or never declared (UNKNOWN). The SDK fails locally and sends nothing to the CLI. See capability requirements for the required capabilities. - The CLI rejects a basic control request at runtime.
- A local
SessionRepositoryoperation needs a listable store and the current store does not support it.
capabilityState() returns the CapabilityState that triggered the failure.
Exception list
Apart from the accessors listed below, each exception class has only constructors, and its error code constant is CODE.
| Class | Error code | When it is thrown |
|---|---|---|
AuthenticationException | AUTHENTICATION_FAILED, CLI_AUTH_FATAL | Authentication failed. When the CLI reports a fatal authentication failure with exit code 41, the code is CLI_AUTH_FATAL (constant CODE_CLI_AUTH_FATAL); do not retry it as a process failure |
CliNotFoundException | CLI_NOT_FOUND | The CLI cannot be found: the explicit path is invalid, or automatic lookup found no executable |
RuntimeInstallException | RUNTIME_INSTALL_FAILED | Managed CLI installation failed: downloads are disallowed and there is no local archive, the network failed or timed out, the size or SHA-256 check failed, and so on. The SDK does not switch to another version; see ManagedRuntimeOptions |
CliProcessException | CLI_PROCESS_FAILED | The CLI process exited abnormally (except for authentication exit codes). getExitCode() returns the exit code; getStderrTail() returns a bounded, redacted tail of stderr, or null when unavailable |
ProtocolException | PROTOCOL_ERROR | CLI output violates the protocol in a way not covered by the three subclasses below |
FrameTooLargeException | FRAME_TOO_LARGE | A single message exceeds the size limit |
MalformedUtf8Exception | MALFORMED_UTF8 | CLI output is not valid UTF-8 |
ProtocolVersionMismatchException | PROTOCOL_VERSION_MISMATCH | The CLI's protocol version is incompatible with the SDK |
ControlRequestTimeoutException | CONTROL_REQUEST_TIMEOUT | A control request received no response within controlRequestTimeoutMs (default 60000 ms) |
UnsupportedCliCapabilityException | UNSUPPORTED_CLI_CAPABILITY | See above |
SessionClosedException | SESSION_CLOSED | An operation on a closed client, session, or query. Consumers also receive it when the CLI exits first during a full close |
SessionStoreConflictException | SESSION_STORE_CONFLICT | A session store version conflict (expectedVersion is stale), or the same UUID maps to different content. currentVersion() returns the store's current version |
SessionStoreDeletedException | SESSION_STORE_DELETED | See SessionStoreDeletedException |
SpoolIoException | SPOOL_IO_ERROR | Reading or writing the message buffer's disk file failed |
SpoolCapacityExceededException | SPOOL_CAPACITY_EXCEEDED | Unconsumed messages exceeded the buffer limit and the message stream failed. Consume the message stream promptly to avoid it |
MessageStreamAlreadyConsumedException | MESSAGE_STREAM_ALREADY_CONSUMED | The same MessageStream was consumed a second time; openCursor() and consume() together can be called only once |
ConsumerInterruptedException | CONSUMER_INTERRUPTED | A thread blocked in MessageCursor.hasNext() or next() was interrupted; the thread's interrupt flag is preserved |
CallbackExecutionException | CALLBACK_EXECUTION_FAILED | An application callback (permission, Hook, MCP, message handler) threw |
CallbackRejectedException | CALLBACK_REJECTED | callbackExecutor rejected a callback task, for example because the thread pool is shut down or the queue is full |
IllegalBlockingCloseException | ILLEGAL_BLOCKING_CLOSE | A blocking close() was called on the resource's own SDK thread or in a push handler. Before throwing, the SDK has already started closing as closeAsync() would; see Threading and resource release |
InvalidOptionsException | INVALID_OPTIONS | Invalid configuration, for example QoderClient.Builder without auth, or SessionOptions with mutually exclusive options set |
UnsupportedSecureSecretTransportException | UNSUPPORTED_SECURE_SECRET_TRANSPORT | The private temporary files for auth or MCP configuration cannot be created or written safely, including when java.io.tmpdir or one of its parent directories lets other accounts replace its entries. The SDK refuses to start and does not fall back to plain-text arguments or environment variables |
QoderException:
| Error code | When it is thrown |
|---|---|
CLI_START_FAILED | The CLI process failed to start, for example because of missing permissions, an invalid working directory, or arguments that are too long. On Windows, the SDK also refuses to start a .cmd/.bat CLI when the launch arguments contain cmd.exe special characters (including parentheses, as in Bash(git:*)); point qoderCliPath at the .exe instead |
MODEL_RESOLUTION_TIMEOUT is never thrown to your application. When the dynamic model selection callback times out, the SDK returns this error code to the CLI and emits an inbound_control_request_failed diagnostic event; see ModelPolicyProvider.
Session-terminating error codes
These error codes are exposed through QoderException.getCode() and can appear in exceptions from the message stream or related asynchronous methods; get the original cause with getCause(). Once one occurs, the session cannot be used further. See Errors for how to handle them.
| Error code | Meaning |
|---|---|
QUERY_WRITE_FAILED | The one-shot query's input was not written to the CLI |
CONTROL_WRITE_FAILED | A control request or response was not written to the CLI |
TURN_ACCOUNTING_FAILED | The session's turn state can no longer be processed |
MESSAGE_DELIVERY_FAILED | A buffered message could not be decoded and was lost. The message stream ends here, and the owning Query or Session closes at the same time |