Skip to main content
Reference

SDK References - Java

The Java SDK's Maven coordinates are 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.
public final class QoderClient implements AutoCloseable {
    public static Builder builder();
    public QoderQuery query(OneShotQueryRequest request);
    public CompletionStage<QoderSession> openSession(SessionOptions options);
    public CompletionStage<PreparedQuery> prepare(SessionOptions options);
    public SessionRepository sessions();
    public SessionRepository sessions(SessionOptions options);
    public CompletionStage<Path> installRuntime();
    public CompletionStage<Integer> pruneRuntimes();
    public CompletionStage<Void> closeAsync();
    public void close();
}
MethodReturn typeDescription
builder()QoderClient.BuilderCreates a builder
query(OneShotQueryRequest)QoderQueryStarts 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()SessionRepositoryReturns a session repository for the client's default storage and project directory
sessions(SessionOptions)SessionRepositoryReturns 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()voidBlocking close. For restrictions on calling it from SDK threads or callbacks, see Threading and resource release
Argument errors and local state errors are thrown synchronously, for example a 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

MethodParameter typeDescription
auth(Auth)AuthRequired. The authentication method
callbackExecutor(Executor)ExecutorExecutor for permission, MCP, model policy, auth token, and Memory callbacks; Hooks use a separate SDK thread
diagnosticsListener(DiagnosticsListener)DiagnosticsListenerClient-level diagnostics listener
sessionStore(SessionStore)SessionStoreThe client's default external session store, used by the session repository and all sessions; session options can override it
projectDirectory(Path)PathProject directory for the session repository, and the default working directory for sessions without workingDirectory
managedRuntime(ManagedRuntimeOptions)ManagedRuntimeOptionsCache 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().
public final class QoderQuery implements AutoCloseable {
    public MessageStream messages();
    public CompletionStage<InitializationResult> initialization();
    public CompletionStage<InterruptResult> interrupt();
    public CompletionStage<Void> flushMemory();
    public CompletionStage<Void> refreshMemory();
    public CompletionStage<Void> closeAsync();
    public void close();
}
MethodReturn typeDescription
messages()MessageStreamReturns 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> / voidCloses the query and releases resources
The CLI exits after the turn's 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().
public final class QoderSession implements AutoCloseable {
    public MessageStream messages();
    public CompletionStage<InitializationResult> initialization();
    public CompletionStage<MessageId> send(UserInput input);
    public MessageSubmission submit(UserInput input);
    public CompletionStage<Void> completeInput();
    public CompletionStage<Boolean> cancelQueuedMessage(MessageId id);
    public CompletionStage<InterruptResult> interrupt();
    public CompletionStage<Void> setPermissionMode(PermissionMode mode);
    public CompletionStage<PlanModeSnapshot> getPlanMode();
    public CompletionStage<PlanModeSnapshot> setPlanMode(boolean active);
    public CompletionStage<Void> setPermissionModePreservingPlan(PermissionMode mode);
    public CompletionStage<Void> setProxy(ProxySettings proxy);
    public CompletionStage<Void> addDirectories(List<Path> directories);
    public CompletionStage<Void> applyFlagSettings(FlagSettings settings);
    public CompletionStage<SessionTitle> generateSessionTitle();
    public CompletionStage<SessionTitle> generateSessionTitle(String description);
    public CompletionStage<Void> seedReadState(ReadStateSeed seed);
    public CompletionStage<List<String>> supportedCommands();
    public CompletionStage<List<CommandInfo>> commandCatalog();
    public CompletionStage<Optional<ModelCatalogSnapshot>> modelCatalogSnapshot();
    public CompletionStage<List<CommandInfo>> reloadSkills();
    public CompletionStage<List<ModelInfo>> getAvailableModels();
    public CompletionStage<List<ModelInfo>> getAvailableModels(ModelCatalogOptions options);
    public CompletionStage<Boolean> backgroundTasks();
    public CompletionStage<Boolean> backgroundTasks(String toolUseId);
    public CompletionStage<Void> stopTask(TaskId taskId);
    public CompletionStage<Void> reloadPlugins();
    public CompletionStage<List<McpServerStatus>> mcpServerStatus();
    public CompletionStage<McpAuthenticationResult> beginMcpAuthentication(String serverName);
    public CompletionStage<McpAuthenticationResult> beginMcpAuthentication(String serverName, McpAuthenticationOptions options);
    public CompletionStage<Void> mcpSubmitOAuthCallbackUrl(String serverName, String callbackUrl);
    public CompletionStage<Void> rewindHistory(MessageId toMessageId);
    public CompletionStage<RewindFilesResult> rewindFiles(MessageId messageId, RewindFilesOptions options);
    public CompletionStage<Void> flushMemory();
    public CompletionStage<Void> refreshMemory();
    public CompletionStage<Void> closeAsync();
    public void close();
}
MethodReturn typeDescription
messages()MessageStreamReturns 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)MessageSubmissionReturns 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> / voidCloses the session and releases resources
Some methods require the CLI to support the corresponding capability. Otherwise they fail with 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

MethodData sourceSends a request
commandCatalog() / supportedCommands()The command catalog from initialization; replaced as a whole when the CLI pushes a command changeNo
modelCatalogSnapshot()The latest complete model catalog from initialization or a CLI pushNo
getAvailableModels(...)The result of this query; does not update the snapshot aboveYes
reloadSkills()The skills list after reloading; does not replace the full command catalogYes
A pushed catalog always replaces the old value as a whole; an empty list means the CLI explicitly cleared the catalog. 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.
public final class CommandInfo {
    public static CommandInfo of(String name, JsonObject raw);
    public String name();
    public Optional<String> description();
    public Optional<String> argumentHint();
    public JsonObject raw();
}
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.
public final class PreparedQuery implements AutoCloseable {
    public CompletionStage<InitializationResult> initialization();
    public QoderQuery toQuery(UserInput input);
    public QoderSession toSession();
    public CompletionStage<Void> closeAsync();
    public void close();
}
MethodReturn typeDescription
initialization()CompletionStage<InitializationResult>Result of the initialization handshake
toQuery(UserInput)QoderQueryConverts to a one-shot query
toSession()QoderSessionConverts to a multi-turn session
closeAsync() / close()CompletionStage<Void> / voidDiscards an unused prewarmed launch
Converting again after a conversion or close throws 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().
public final class MessageStream {
    public MessageCursor openCursor();
    public CompletionStage<Void> consume(MessageHandler handler, Executor executor);
}
MethodReturn typeDescription
openCursor()MessageCursorOpens 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
The stage returned by consume() completes normally when the stream ends. It completes exceptionally in the following cases, and the owning query or session is closed:
CaseException
The handler throwsCallbackExecutionException
The executor rejects the taskCallbackRejectedException
The consuming thread is interruptedConsumerInterruptedException
Reading or decoding a message failsThe stream's failure cause; non-SDK exceptions are wrapped with error code MESSAGE_DELIVERY_FAILED
Each stream can be consumed only once, through either 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.
public interface MessageCursor extends AutoCloseable {
    boolean hasNext();
    SdkMessage next();
    void close();
}
MethodReturn typeDescription
hasNext()booleanBlocks until the next message; returns false when the stream ends and throws the failure cause when the stream has failed
next()SdkMessageReturns the next message; throws NoSuchElementException when the stream has ended
close()voidCloses the cursor and asynchronously closes the owning query or session
Multi-turn sessions should reuse one cursor across turns: after reading a turn's 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.
@FunctionalInterface
public interface MessageHandler {
    void onMessage(SdkMessage message) throws Exception;
}
An exception thrown by the handler completes the stage returned by 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

  1. Asynchronous methods return CompletionStage, and each call gives an independent observation. Cancelling or manually completing toCompletableFuture() affects only that wait and does not cancel the SDK operation. To stop an operation, use interrupt(), cancelQueuedMessage(), or closeAsync().
  2. Handles returned by openSession() and prepare() belong to the application once delivered. The application must close them even if it never calls get(). If the observing future was cancelled before delivery, the SDK closes the started resources automatically.
  3. Each query, session, and prewarmed launch occupies one CLI child process, so limit concurrency in your application. QoderClient itself is lightweight and reusable; closing the client cascades to every resource it created.
  4. Permission, MCP, model policy, auth token, and Memory callbacks run on callbackExecutor; Hooks use a separate SDK thread. Without a callbackExecutor, 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.
  5. 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 blocking close() on an SDK-owned thread or inside a push handler starts an asynchronous close and then throws IllegalBlockingCloseException. SDK-owned threads include the thread that runs callbacks when no callbackExecutor is configured, and the threads that run hooks and diagnostics listeners; a session's or query's close() checks only its own threads, while the client's close() checks those of every session and query it created. A push handler throws even when it runs on an application executor. With a custom callbackExecutor, callbacks run on application threads, where close() does not throw this exception and instead blocks until the close completes.
  6. 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 most drainTimeoutMs (60 seconds by default).

Configuration

SessionOptions

Immutable options for queries and sessions, used by openSession(), prepare(), and OneShotQueryRequest.
public final class SessionOptions {
    public static Builder builder();
    public static SessionOptions defaults();   // same as builder().build()
    public Builder toBuilder();                // a new Builder prefilled with the current values
    // Every Builder setting has a getter of the same name, such as workingDirectory() and maxTurns();
    // resume(String) maps to resumeSessionId(). Unset optional values return Optional.empty().
}

SessionOptions.Builder

GroupMethodDefaultDescription
RuntimeworkingDirectory(Path)The client's projectDirectory, then the process's current directoryCLI working directory
RuntimeqoderCliPath(Path)Managed CLIExplicit CLI executable. When set, the managed CLI is not downloaded, and an invalid path fails immediately
RuntimeputEnvironment(String, String)—Adds one environment variable for the CLI; proxy URL variables are treated as secrets automatically
RuntimeputProxyEnvironment(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
Runtimeenvironment(Map<String, String>)—Adds environment variables in bulk; appends rather than replaces
PromptsystemPrompt(String)Not replacedReplaces the system prompt
PromptappendSystemPrompt(String)Nothing appendedAppends to the end of the system prompt
PromptcustomContext(Map<String, String>)Not setDefault custom context for this session's input. See UserInput
PromptmaxTurns(int)UnlimitedMaximum number of turns; must be positive
PromptincludePartialMessages(boolean)falseDelivers incremental PartialMessages. See Streaming output
ModelmodelSelection(ModelSelection)The CLI's default modelA fixed model, a dynamic callback, or a structured policy. See ModelSelection
Toolstools(List<String>)CLI defaultBase tool list; an empty list is the same as not setting it
ToolsallowedTools(List<String>)—Tools pre-authorized to run
ToolsdisallowedTools(List<String>)—Blocked tools; takes priority over allowedTools
PermissionpermissionMode(PermissionMode)Decided by the CLIPermission mode. See PermissionMode
PermissionplanMode(boolean)Not setInitial state of standalone Plan; mutually exclusive with permissionMode(PLAN). See Standalone Plan
PermissionallowDangerouslySkipPermissions(boolean)falseThe second switch for BYPASS_PERMISSIONS
PermissioncanUseTool(ToolPermissionHandler)NoneTool permission callback. See ToolPermissionHandler
MCPmcpServers(List<McpServerConfig>)—MCP server configurations; names must be unique. See McpServerConfig
MCPallowedMcpServerNames(List<String>)—Names of MCP servers allowed for use
MCPonElicitation(ElicitationHandler)NoneMCP elicitation callback. See ElicitationHandler
Hookshooks(List<HookRegistration>)—Hook registrations. See HookRegistration
HooksincludeHookEvents(boolean)falseDelivers Hook notification messages in the message stream; does not affect Hook callbacks
Sessionresume(String)—Resumes by session ID; the ID may contain only ASCII letters, digits, _, and -
SessioncontinueSession(boolean)falseContinues the most recent session; with a listable external store, selects by the timestamps in the store
SessionforkSession(boolean)falseUsed with resume or continueSession to fork into a new session
SessionsessionStorage(SessionStorage)SessionStorage.inherit()Inherit the client setting, use CLI local storage, or use an external store. See SessionStorage
SessionsessionMirrorOptions(SessionMirrorOptions)defaults()Batch and wait limits for external store mirroring. See SessionMirrorOptions
SessionfileCheckpointing(boolean)falseEnables file checkpoints. See Checkpoint
Memorymemory(MemoryOptions)DisabledNative or application-managed Memory. See Memory
ControlcontrolRequestTimeoutMs(long)60000Control request timeout (milliseconds); 0 means unlimited, negative values are invalid
ControlresolveModelTimeoutMs(long)500Timeout for the dynamic model callback (milliseconds); 0 means unlimited, negative values are invalid
Controldebug(boolean)falseEnables CLI debug output
ControldiagnosticsListener(DiagnosticsListener)—Session-level diagnostics listener
The two defaults are also available as constants: 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_PERMISSIONS without allowDangerouslySkipPermissions(true).
  • An external sessionStorage together with fileCheckpointing(true).
  • planMode(...) together with permissionMode(PLAN), including planMode(false).
  • resume together with continueSession; forkSession without resume or continueSession; a resume ID with invalid characters.
  • An environment variable name that is empty or contains = or NUL, or a value that contains NUL.
  • mcpServers that contain null or duplicate server names.
A successful 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.
public final class OneShotQueryRequest {
    public static OneShotQueryRequest of(String prompt);
    public static Builder builder();
    public UserInput input();
    public SessionOptions options();

    public static final class Builder {
        public Builder prompt(String prompt);
        public Builder input(UserInput input);
        public Builder options(SessionOptions options);
        public OneShotQueryRequest build();
    }
}
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.
public final class UserInput {
    public static UserInput text(String text);
    public static Builder builder();
    public List<Content> content();
    public SendPriority priority();
    public boolean shouldQuery();
    public Optional<Map<String, String>> customContext();
    public Optional<Boolean> clientComposed();
    public List<SelectedSkill> selectedSkills();
}
MethodReturn typeDescription
text(String)UserInputA single text block with priority NOW and shouldQuery set to true
content()List<Content>The content blocks; never empty
priority()SendPriorityThe priority hint
shouldQuery()booleanWhether 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

MethodDescription
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
The content block type UserInput.Content is a closed hierarchy, independent of the output-side ContentBlock:
TypeAccessors
UserInput.TextContentString text()
UserInput.ImageContentString mediaType(), String base64Data()
UserInput.UrlImageContentURI url()
UserInput.ToolResultContentString toolUseId(), String content()
When 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.
public final class SelectedSkill {
    public static SelectedSkill of(String name);
    public static SelectedSkill of(String name, String arguments);
    public String name();
    public Optional<String> arguments();
}
The name must not be empty or all whitespace. 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.
public final class ReadStateSeed {
    public ReadStateSeed(List<String> readFiles);
    public static ReadStateSeed of(String path, long mtime);
    public List<String> readFiles();
}
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. 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.
public enum SendPriority { NOW, NEXT, LATER }
ValueDescription
NOWHandle as soon as possible (default)
NEXTAfter the message currently being handled
LATERAfter all queued messages

FlagSettings

Feature flags passed to applyFlagSettings().
public final class FlagSettings {
    public FlagSettings(Map<String, Boolean> flags);
    public Map<String, Boolean> flags();
}
flags maps flag names to booleans and is required. flags() returns an immutable copy.

ProxySettings

Proxy settings passed to setProxy().
public final class ProxySettings {
    public static ProxySettings of(SecretValue proxyUrl);
    public static ProxySettings clear();
    public SecretValue proxyUrlSecret();
    @Deprecated public ProxySettings(String proxyUrl);
    @Deprecated public String proxyUrl();
}
Set a proxy with 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().
public final class ManagedRuntimeOptions {
    public static final long DEFAULT_IO_TIMEOUT_MS = 30_000L;
    public static final long DEFAULT_TOTAL_TIMEOUT_MS = 600_000L;

    public enum DownloadPolicy { ALLOW, DISALLOW }

    public static Builder builder();
    public static ManagedRuntimeOptions defaults();
    public Builder toBuilder();
    public Optional<Path> cacheDirectory();
    public DownloadPolicy downloadPolicy();
    public Optional<URI> baseUrl();
    public Optional<SecretValue> authorization();
    public Optional<Path> localArchive();
    public long connectTimeoutMs();
    public long readTimeoutMs();
    public long totalTimeoutMs();
}
Builder methodDefaultDescription
cacheDirectory(Path)~/.qoder/runtimes/managedCache directory; should be on a local disk. null restores the default
downloadPolicy(DownloadPolicy)ALLOWWith DISALLOW, never goes online and fails when neither the cache nor a local archive is available
baseUrl(URI)Official download URLMirror 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)30000Connect and per-read timeouts (milliseconds); must be positive
totalTimeoutMs(long)600000Timeout for the whole download (milliseconds); must be positive
build()—Throws InvalidOptionsException when a timeout is not positive or baseUrl is invalid
CLI lookup order. At launch, the SDK picks the CLI in this order and stops at the first match:
  1. The path set by SessionOptions.Builder.qoderCliPath(); an invalid path fails immediately.
  2. The QODERCLI_PATH environment variable.
  3. The managed CLI: reuse the cache, or install from localArchive, the classpath resource META-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.
  4. A CLI of the same version left by an older SDK under ~/.qoder/runtimes/.
  5. ~/.local/bin/qodercli, then PATH.
Each SDK version uses only the one CLI version it pins. It does not look up the latest version or upgrade automatically. An archive from any source must match the built-in byte size and SHA-256, or installation fails.
Prewarming and offline use.
ScenarioApproach
Local developmentUse the defaults; the first launch downloads automatically
Prewarm while building an imageRun 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 applicationPut 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 mirrorPoint 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 CLISet 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.
public abstract class Auth {
    public static Auth qoderCli();
    public static Auth accessToken(SecretValue token);
    public static Auth accessTokenEnv();
    public static Auth accessTokenEnv(String environmentVariable);
    public static Auth serviceAccountKey(SecretValue key);
    public static Auth serviceAccountTokenProvider(ServiceAccountTokenProvider provider);
    public static Auth jobTokenProvider(JobTokenProvider provider);
}
FactoryCredential sourceTypical use
qoderCli()Reuses the login saved by the local CLIDeveloper machine
accessToken(SecretValue)Access token supplied by the callerCI, scripts, services acting for a user
accessTokenEnv()Reads the QODER_PERSONAL_ACCESS_TOKEN environment variable at startupCI, container deployments
accessTokenEnv(String)Reads the caller-named environment variable at startupCustom secret injection
serviceAccountKey(SecretValue)Service Account Key; the SDK and CLI exchange it for short-lived tokensUnattended services in an organization
serviceAccountTokenProvider(ServiceAccountTokenProvider)Service Account token refreshed by the host on demand; the long-lived key never enters the SDKSelf-hosted token infrastructure
jobTokenProvider(JobTokenProvider)Per-job token supplied by the hostMulti-tenant job runners
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. 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.
public interface ServiceAccountTokenProvider {
    CompletionStage<SecretValue> fetchServiceAccountToken();
}
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.

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.
public interface JobTokenProvider {
    CompletionStage<SecretValue> fetchJobToken();
}
Return a stage that completes with a token; never return 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.
public final class SecretValue {
    public static SecretValue of(char[] value);
    public static SecretValue of(String value);
    public char[] reveal();
    public int length();
}
MethodReturn typeDescription
of(char[])SecretValueCopies the array, then creates the value
of(String)SecretValueCreates the value from a string
reveal()char[]Returns a plaintext copy; the caller clears it after use
length()intLength of the secret
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.

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.
@FunctionalInterface
public interface ToolPermissionHandler {
    CompletionStage<PermissionResult> canUseTool(ToolPermissionRequest request,
                                                 CancellationToken cancellation)
            throws Exception;
}
ParameterTypeDescription
requestToolPermissionRequestThe tool call awaiting a decision
cancellationCancellationTokenCancelled when the CLI abandons the request, the request times out, or the session closes
Return a stage that completes with a 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.
public final class ToolPermissionRequest {
    public String toolName();
    public JsonValue toolInput();
    public List<JsonValue> suggestions();
    public Optional<String> toolUseId();
    public Optional<String> agentId();
    public Optional<String> title();
    public Optional<String> displayName();
    public Optional<String> description();
    public Optional<String> blockedPath();
    public Optional<String> decisionReason();
    public Optional<String> decisionReasonType();
    public Optional<String> permissionKind();
    public Optional<Boolean> classifierApprovable();
    public List<PermissionOption> options();
    public Optional<PermissionDetails> details();
    public JsonObject raw();
}
MethodReturn typeDescription
toolName()StringTool name
toolInput()JsonValueTool 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()JsonObjectThe full request
Optional fields that are missing or have the wrong type return an empty 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.
MethodReturn typeDescription
outcome()Optional<String>Option outcome; unknown values are kept. Corresponds to PermissionScope.outcome()
name()Optional<String>Display name
allowed()booleanSelectable only when the original field is explicitly true; missing or malformed values count as false
raw()JsonObjectThe full option
PermissionDetails is an open details model used for prompts such as repeated_tool_call.
MethodReturn typeDescription
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()JsonObjectThe full object
Tool input, previews, and raw() can contain business data. Do not log them by default.

PermissionScope

Explicit approval scope, used through PermissionResult.withScope(...).
public enum PermissionScope {
    ONCE, SESSION, PERSIST;
    public String wireValue();
    public String outcome();
}
ConstantMeaningoutcome()
ONCEApprove this call onlyproceed_once
SESSIONAlways approve within this CLI sessionproceed_always
PERSISTAsk the CLI to save the approval persistentlyproceed_always_and_save
The CLI saves 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).
public final class PermissionResult {
    public static PermissionResult allow();
    public static PermissionResult allow(JsonValue updatedInput);
    public static PermissionResult deny(String reason);
    public PermissionResult withScope(PermissionScope scope);
    public boolean isAllowed();
    public Optional<JsonValue> updatedInput();
    public Optional<String> denyReason();
    public Optional<PermissionScope> scope();
}
MethodReturn typeDescription
allow()PermissionResultAllow with the original tool input
allow(JsonValue)PermissionResultAllow; the CLI uses the given input instead
deny(String)PermissionResultDeny; the reason is passed to the CLI
withScope(PermissionScope)PermissionResultReturns a new allow with an explicit scope; calling it on a deny throws IllegalStateException
isAllowed()booleanWhether 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
When the request carries 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.
public final class PermissionMode {
    public static final PermissionMode DEFAULT;
    public static final PermissionMode ACCEPT_EDITS;
    public static final PermissionMode BYPASS_PERMISSIONS;
    public static final PermissionMode PLAN;
    public static final PermissionMode DONT_ASK;
    public static final PermissionMode AUTO;
    public static PermissionMode of(String value);
    public String value();
}
Constantvalue()Description
DEFAULTdefaultRequest permissions by CLI rules
ACCEPT_EDITSacceptEditsAccept file edits automatically
BYPASS_PERMISSIONSbypassPermissionsSkip all permission checks; requires allowDangerouslySkipPermissions(true) as well
PLANplanPlan without executing; to combine it with another mode, use standalone Plan
DONT_ASKdontAskNever ask; operations that would need a prompt are denied
AUTOautoDecide automatically
For known modes, 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.
public interface CancellationToken {
    boolean isCancelled();
    void onCancel(Runnable action);
    default CancellationRegistration registerOnCancel(Runnable action);
}

public interface CancellationRegistration extends AutoCloseable {
    @Override void close();
}
MethodReturn typeDescription
isCancelled()booleanWhether the token is cancelled; once true, it never changes
onCancel(Runnable)voidRegisters 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)CancellationRegistrationSame as above, and returns a handle for unregistering
CancellationRegistration.close()voidUnregisters the action; idempotent and non-blocking. An action that already started is not interrupted
Cancellation is cooperative: code that ignores the token keeps running. Keep cancellation actions short and non-blocking; exceptions they throw are ignored. 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.
public final class PlanModeSnapshot {
    public boolean active();
}

// SessionOptions.Builder
public Builder planMode(boolean active);

// QoderSession
public CompletionStage<PlanModeSnapshot> setPlanMode(boolean active);
public CompletionStage<PlanModeSnapshot> getPlanMode();
public CompletionStage<Void> setPermissionModePreservingPlan(PermissionMode mode);
MethodDescription
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
Rules:
  • All of these entry points need CLI support for the corresponding capability; see Capability requirements. Without it they fail with UnsupportedCliCapabilityException and send no request.
  • At startup, planMode(...) and permissionMode(PermissionMode.PLAN) are mutually exclusive, even with planMode(false); build() throws InvalidOptionsException.
  • QoderSession.setPermissionMode(...) does not guarantee that Plan is kept. Use setPermissionModePreservingPlan(...) when it must be kept.
  • Trust the PlanModeSnapshot returned by the CLI or the PlanModeChangedMessage in 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>).
public final class HookRegistration {
    public static Builder builder();
    public String event();
    public Optional<String> matcher();
    public HookHandler handler();
    public long timeoutMs();
}
Builder methodParameter typeDescription
event(String)StringRequired. Event name, such as PreToolUse, PostToolUse, or Stop
matcher(String)StringOptional. Matched by the CLI, for example the tool name Bash
handler(HookHandler)HookHandlerRequired. The handler
timeoutMs(long)longHow 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
The CLI matches event names and matchers; the handler runs inside the Java process. 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.
@FunctionalInterface
public interface HookHandler {
    CompletionStage<JsonValue> onHookEvent(HookEvent event) throws Exception;
}
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 PreToolUse to deny the tool call:
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "..."
  }
}
If the handler throws or the stage completes exceptionally, only this Hook call fails with CALLBACK_EXECUTION_FAILED. A timeout fails with CONTROL_REQUEST_TIMEOUT.

HookEvent

A single Hook event.
public final class HookEvent {
    public String eventName();
    public Optional<String> matcher();
    public JsonValue payload();
}
MethodReturn typeDescription
eventName()StringEvent name, taken from hook_event_name in the CLI input
matcher()Optional<String>Matcher echoed by the CLI; usually empty
payload()JsonValueThe CLI Hook input object, as raw JSON
Common 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.
public final class McpServerConfig {
    public static McpServerConfig stdio(String name, String command, List<String> args);
    public static McpServerConfig sse(String name, String url);
    public static McpServerConfig http(String name, String url);
    public static McpServerConfig inProcess(String name, InProcessMcpHandler handler);

    // returns a new instance
    public McpServerConfig withEnv(String envName, String value);
    public McpServerConfig withSecretEnv(String envName, SecretValue value);
    public McpServerConfig withHeader(String headerName, String value);
    public McpServerConfig withSecretHeader(String headerName, SecretValue value);

    public String name();
    public String type();
    public Optional<String> command();
    public List<String> args();
    public Map<String, String> env();
    public Map<String, SecretValue> secretEnv();
    public Optional<String> url();
    public Map<String, String> headers();
    public Map<String, SecretValue> secretHeaders();
    public Optional<InProcessMcpHandler> inProcessHandler();
    public boolean hasSecrets();
}
Factorytype()Description
stdio(name, command, args)stdioServer run as a subprocess started by the CLI; null args counts as an empty list
sse(name, url)sseRemote SSE server
http(name, url)httpRemote Streamable HTTP server
inProcess(name, handler)sdkServer inside the JVM process. The SDK forwards messages to InProcessMcpHandler; there is no subprocess
Chained methodApplies toDescription
withEnv(String, String)stdioAdds an environment variable
withSecretEnv(String, SecretValue)stdioAdds a secret environment variable, overriding a plain variable of the same name
withHeader(String, String)sse / httpSets a request header; names are case-insensitive
withSecretHeader(String, SecretValue)sse / httpSets a secret request header, overriding a plain header of the same name
Calling a chained method on a server type it does not apply to throws 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).
@FunctionalInterface
public interface InProcessMcpHandler {
    CompletionStage<JsonValue> handle(JsonValue message) throws Exception;
    default CompletionStage<JsonValue> handle(JsonValue message,
                                              CancellationToken cancellation) throws Exception;
    default InProcessMcpHandler newConnection();
}
MethodDescription
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
The SDK forwards the returned JSON to the CLI as the MCP response as is, so the response must carry 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().
public final class McpServerStatus {
    public String name();
    public String status();
    public JsonValue raw();
}
MethodReturn typeDescription
name()StringServer name
status()StringStatus string, as reported by the CLI
raw()JsonValueRaw status
The CLI may echo the server's effective configuration in 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.
public final class McpAuthenticationResult {
    public boolean requiresUserAction();
    public Optional<String> authUrl();
}

public final class McpAuthenticationOptions {
    public static McpAuthenticationOptions defaults();
    public static McpAuthenticationOptions redirectUri(String redirectUri);
    public Optional<String> redirectUri();
}
MethodDescription
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
The authorization URL and the callback URL can contain a sensitive OAuth state. Do not log them; 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(...).
@FunctionalInterface
public interface ElicitationHandler {
    CompletionStage<ElicitationResult> onElicitation(ElicitationRequest request)
            throws Exception;
}
The callback runs on callbackExecutor and is bound by controlRequestTimeoutMs. If it throws or the stage completes exceptionally, only this elicitation fails with CALLBACK_EXECUTION_FAILED.

ElicitationRequest

public final class ElicitationRequest {
    public String serverName();
    public String message();
    public JsonValue requestedSchema();
    public JsonValue raw();
}
MethodReturn typeDescription
serverName()StringName of the requesting MCP server
message()StringPrompt shown to the user
requestedSchema()JsonValueJSON Schema of the expected answer; an empty object, never null, when the request carries none
raw()JsonValueRaw request

ElicitationResult

Result of an elicitation.
public final class ElicitationResult {
    public static ElicitationResult accept(JsonValue content);
    public static ElicitationResult decline();
    public static ElicitationResult cancel();
    public String action();
    public Optional<JsonValue> content();
}
Factoryaction()Description
accept(JsonValue)acceptThe user accepted, with content filled in per the schema; null content throws NullPointerException
decline()declineThe user declined to answer
cancel()cancelThe 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.
public abstract class ModelSelection {
    public static StaticModelSelection staticModel(ModelId model);
    public static DynamicModelSelection dynamic(ModelPolicyProvider provider);
    public static DynamicModelSelection dynamic(ModelId initialModel, ModelPolicyProvider provider);
    public static PolicyModelSelection policy(ModelDecisionProvider provider);
    public static PolicyModelSelection policy(ModelId initialModel, ModelDecisionProvider provider);
}

public final class StaticModelSelection extends ModelSelection {
    public Optional<ModelId> model();
}

public final class DynamicModelSelection extends ModelSelection {
    public ModelPolicyProvider provider();
    public Optional<ModelId> initialModel();
}

public final class PolicyModelSelection extends ModelSelection {
    public ModelDecisionProvider provider();
    public Optional<ModelId> initialModel();
}
Factory methodReturn typeDescription
staticModel(ModelId)StaticModelSelectionUse one fixed model for the whole session
dynamic(ModelPolicyProvider)DynamicModelSelectionThe CLI picks the initial model by default; after that, the callback returns a model ID for each model request
dynamic(ModelId, ModelPolicyProvider)DynamicModelSelectionSame as above, with an explicit initial model
policy(ModelDecisionProvider)PolicyModelSelectionThe 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)PolicyModelSelectionSame as above, with an explicit initial model
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 openSession() completes.

ModelPolicyProvider

The callback used by dynamic(...). It returns the model ID for the current request.
@FunctionalInterface
public interface ModelPolicyProvider {
    CompletionStage<ModelId> resolveModel(ModelRequestContext context) throws Exception;
}
ParameterTypeDescription
contextModelRequestContextContext of this model request
The timeout is set by 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.
@FunctionalInterface
public interface ModelDecisionProvider {
    CompletionStage<ModelPolicyResult> resolve(ModelRequestContext context) throws Exception;
}
If the callback returns 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.
public final class ModelPolicyResult {
    public static ModelPolicyResult keepCurrent();
    public static ModelPolicyResult select(ModelId model);
    public static Builder builder();
    public Optional<ModelId> model();
    public Optional<JsonObject> parameters();
    public Optional<String> taskId();
    public Optional<String> subTask();
}
Builder methodDescription
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()
Builder methods do not accept 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.
public final class ModelRequestContext {
    public Optional<String> sessionId();
    public Optional<String> purpose();
    public Optional<Integer> turnIndex();
    public Optional<String> agentId();
    public Optional<String> agentType();
    public Optional<ModelId> resolvedModel();
    public List<ModelInfo> availableModels();
}
MethodReturn typeDescription
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
Fields the CLI does not provide are 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.
public final class ModelInfo {
    public ModelId modelId();
    public Optional<String> displayName();
    public Optional<String> description();
    public Optional<String> source();
    public Optional<String> outerProvider();
    public Optional<String> descriptionEn();
    public boolean isEnabled();
    public JsonValue raw();
}
MethodReturn typeDescription
modelId()ModelIdModel 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()booleanWhether the current account can select the model; treated as false when the CLI omits the field
raw()JsonValueThe full raw entry, including unmodeled fields such as context window and pricing
The catalog may contain models whose isEnabled() is false. Filter them out when showing selectable models.

ModelCatalogOptions

Read options for QoderSession.getAvailableModels(ModelCatalogOptions).
public final class ModelCatalogOptions {
    public enum FetchStrategy { LIVE, CACHE }
    public static ModelCatalogOptions defaults();
    public static Builder builder();
    public Optional<FetchStrategy> fetchStrategy();
    public Optional<String> uid();
}
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.
public final class ModelCatalogSnapshot {
    public List<ModelInfo> models();
    public Optional<ModelId> currentModel();
}
When 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.
public final class ModelId {
    public static ModelId of(String value);
    public String value();
}

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

TypePurpose
MemoryOptionsQuery-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)
MemoryGenerationOptionsGeneration configuration for custom mode: on/off switch, memory directories (roots), generation prompt, and automatic extraction after each turn
MemoryTurnCompleteOptionsConfiguration 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)
MemoryShouldGeneratePer-turn decision callback; returns CompletionStage<MemoryGenerationGateResult>
MemoryGenerationGateInput / MemoryGenerationGateResultInput of the decision callback (session, turn, prompt, answer, and so on), and the run() / skip(reason) decision result
MemoryConsumptionOptionsLoading 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 / MemoryConsumptionFileMemory 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 / MemoryConsumptionHandlerNotification callbacks for generation and loading results; return CompletionStage<Void>
EffectiveMemoryConfigThe 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 / MemoryConsumptionResultGeneration 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
MemoryUsageUsage produced by memory generation itself; values the CLI does not provide are Optional.empty()
Results also appear in the message stream as 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 check MemoryGenerationResult. The wait is capped by drainTimeoutMs.
  • 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 is controlRequestTimeoutMs.
When memory is not enabled for the session, both methods fail with 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(...).
public final class SessionStorage {
    public enum Mode { INHERIT, CLI_LOCAL, EXTERNAL }
    public static SessionStorage inherit();
    public static SessionStorage cliLocal();
    public static SessionStorage external(SessionStore store);
    public Mode mode();
    public Optional<SessionStore> externalStore();
}
Factory methodDescription
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
The effective external store cannot be combined with 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.
public interface SessionStore {
    CompletionStage<AppendResult> append(SessionKey key, AppendBatch batch);
    CompletionStage<Optional<SessionSnapshot>> load(SessionKey key);
}
MethodReturn typeDescription
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
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.

Implementation contract

ContractRequirement
Ordering and isolationload returns entries in append order; data under different SessionKeys is invisible to each other
AtomicityConcurrent appends to the same key must be serialized; a batch is either written entirely or fails entirely
IdempotencyAn 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
CASWhen 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 integrityAn 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
Mirroring write failures are reported through 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.
public interface ListableSessionStore extends SessionStore {
    CompletionStage<List<SessionKey>> listSessions(String projectKey);
    CompletionStage<Void> deleteSession(SessionKey key);
}
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.
public interface SessionDeletionStore extends ListableSessionStore {
    CompletionStage<Void> deleteSessionTree(String projectKey, String sessionId);
}
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 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.
public final class InMemorySessionStore implements SessionDeletionStore {
    public InMemorySessionStore();
}

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.
SettingDefaultDescription
maxPendingBatches256Maximum number of batches waiting to be written
maxPendingBytes64 MiBMaximum raw content bytes of batches waiting to be written
operationTimeoutMs60000Total time for each batch, from reading the version to finishing the append (including retries)
closeDrainTimeoutMs5000How long closing a session waits for remaining batches to be written
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 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.
public interface SessionRepository {
    CompletionStage<List<String>> list();
    CompletionStage<Optional<SessionSnapshot>> get(String sessionId);
    CompletionStage<Void> delete(String sessionId);
    CompletionStage<QoderSession> continueSession(String sessionId, SessionOptions options);
    CompletionStage<QoderSession> fork(String sessionId, SessionOptions options);
}
MethodReturn typeDescription
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
Scope rules:
  1. A repository is bound to one store and one project key. The store is resolved from sessionStorage. The project key comes from workingDirectory, falling back to the client's projectDirectory, and is default when neither is set.
  2. sessions(options) takes only the store and the directory from options. It starts no process and keeps no other configuration, such as the CLI path or environment variables.
  3. When the options of continueSession or fork set no directory or store, the repository's scope is used. An explicit override can open a session in another scope, but it does not change list, get, or delete of the current repository.
When the store lacks the required capability, the method fails with 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.
public final class SessionKey {
    public static final String DEFAULT_PROJECT; // "default"
    public static SessionKey of(String projectKey, String sessionId);
    public static SessionKey of(String projectKey, String sessionId, String subpath);
    public static String projectKeyFor(Path workingDirectory);
    public String projectKey();
    public String sessionId();
    public String subpath();
}

public final class AppendBatch {
    public static Builder builder();
    public OperationId operationId();
    public VersionToken expectedVersion();
    public long firstSequence();
    public List<SessionStoreEntry> entries();
}

public final class AppendResult {
    public static AppendResult applied(VersionToken newVersion, int appendedCount);
    public static AppendResult replayed(VersionToken newVersion, int appendedCount);
    public VersionToken newVersion();
    public int appendedCount();
    public boolean alreadyApplied();
}

public final class SessionSnapshot {
    public static SessionSnapshot of(List<SessionStoreEntry> entries, VersionToken version);
    public List<SessionStoreEntry> entries();
    public VersionToken version();
}

public final class SessionStoreEntry {
    public static SessionStoreEntry of(String rawJson);
    public String rawJson();
    public Optional<String> type();
    public Optional<String> uuid();
    public Optional<String> timestamp();
}

public final class OperationId {
    public static OperationId of(String value);
    public static OperationId random();
    public String value();
}

public final class VersionToken {
    public static VersionToken of(String value);
    public static VersionToken initial();
    public String value();
}

public class SessionStoreDeletedException extends QoderException {
    public static final String CODE = "SESSION_STORE_DELETED";
    public SessionStoreDeletedException(String message);
    public SessionStoreDeletedException(String message, Throwable cause);
}
TypeMemberDescription
SessionKeyprojectKey / sessionId / subpathStore 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
AppendBatchoperationIdIdempotency ID; retries with the same ID are applied only once
expectedVersionExpected CAS version; null means an unconditional append
firstSequenceOrdering hint for the first entry in the batch; it does not replace the CAS version or the idempotency ID
entriesBatch entries. The Builder provides operationId, expectedVersion, firstSequence, addEntry, and addEntries
AppendResultapplied(...) / 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
SessionSnapshotentries / versionEntries in append order and the current version
SessionStoreEntryrawJson()Raw JSON line; type, uuid, and timestamp are extracted from it. toString() does not print the content
OperationIdof / randomIdempotent operation ID
VersionTokeninitial()Starting version of an empty store; the version must advance after each successful write
SessionStoreDeletedExceptionCODERejects 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.
public final class InitializationResult {
    public Map<String, CapabilityState> capabilities();
    public CapabilityState capability(String capability);
    public Optional<String> cliVersion();
    public Optional<String> wireVersion();
    public Optional<String> sessionId();
    public Optional<EffectiveMemoryConfig> memory();
}
MethodReturn typeDescription
capabilities()Map<String, CapabilityState>Capabilities the CLI declared during the handshake, immutable
capability(String)CapabilityStateState 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
If the CLI rejects a capability-gated request as "unsupported" at runtime, the SDK downgrades that capability to 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 settingRequired capabilityWhen unsupported
QoderSession.backgroundTasks() / backgroundTasks(String)background_tasks_v1The returned stage completes exceptionally
QoderSession.stopTask(TaskId)background_tasks_v1The returned stage completes exceptionally
QoderSession.rewindHistory(MessageId)session_rewind_v1The returned stage completes exceptionally
QoderSession.reloadSkills()reload_skills_v1The returned stage completes exceptionally
getPlanMode() / setPlanMode(boolean) / setPermissionModePreservingPlan(PermissionMode)plan_mode_v1The returned stage completes exceptionally
SessionOptions.Builder.planMode(boolean)plan_mode_v1Initialization fails
SessionOptions.Builder.customContext(Map)custom_context_v1Initialization fails
UserInput.Builder.customContext(Map) (including an empty map)custom_context_v1The write stage of that input completes exceptionally; initialization fails when it is the first input
UserInput.Builder.selectedSkills(List) (non-empty)selected_skills_v1The write stage of that input completes exceptionally; initialization fails when it is the first input
Remote cancellation in QoderSession.cancelQueuedMessage(MessageId)interrupt_cancel_queued_v1Only local cancellation; returns false without throwing once the input has started writing
External SessionStore with forkSession(true), including SessionRepository.fork(...)session_store_fork_barrier_v1Initialization fails
Other 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.
public enum CapabilityState { SUPPORTED, UNSUPPORTED, UNKNOWN }
ValueDescription
SUPPORTEDThe CLI declares support
UNSUPPORTEDThe CLI explicitly declares no support, or rejected a request for this capability at runtime
UNKNOWNThe CLI did not declare it; methods that depend on the capability fail directly, and the SDK does not probe

InterruptResult

The result of interrupt().
public final class InterruptResult {
    public boolean interrupted();
    public String turnState();
}
MethodReturn typeDescription
interrupted()booleanWhether an interrupt was sent to a running turn; false when no turn is running
turnState()StringTurn state at return time: IDLE, RUNNING, or INTERRUPTING
Success only means the CLI accepted the interrupt request; when the turn ends is determined by 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).
public final class MessageSubmission {
    public MessageId id();
    public CompletionStage<MessageId> written();
}
MethodReturn typeDescription
id()MessageIdInput 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
MessageSubmission submission = session.submit(UserInput.text("Queued input"));
CompletionStage<Boolean> cancelled = session.cancelQueuedMessage(submission.id());
Use 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

public final class MessageId {
    public static MessageId of(String value);
    public String value();
}

public final class TaskId {
    public static TaskId of(String value);
    public String value();
}

public final class SessionTitle {
    public SessionTitle(String title);
    public String title();
}
TypeDescription
MessageIdID of a user input, from MessageSubmission.id() or the return value of send(...); used by cancelQueuedMessage, rewindHistory, and rewindFiles
TaskIdBackground task ID, passed to stopTask(TaskId)
SessionTitleSession 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.
public final class RewindFilesOptions {
    public static RewindFilesOptions defaults();
    public static RewindFilesOptions dryRun();
    public boolean isDryRun();
}

public final class RewindFilesResult {
    public boolean canRewind();
    public List<String> filesChanged();
    public Optional<String> error();
}
MethodReturn typeDescription
RewindFilesOptions.defaults()RewindFilesOptionsActually rewinds files
RewindFilesOptions.dryRun()RewindFilesOptionsOnly reports which files would change, without writing to disk
canRewind()booleanWhether 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.
public abstract class SdkMessage {
    public final String wireType();
    public final Optional<String> uuid();
    public final Optional<String> sessionId();
    public final Optional<String> rawJson();
}
MethodReturn typeDescription
wireType()StringTop-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
Concrete types:
ClasswireType()Description
AssistantMessageassistantAssistant reply
ResultMessageresultResult at the end of a turn
UserReplayMessageuserUser message replayed by the CLI
PartialMessagepartialStreaming delta event
SystemMessage and subclassessystemSystem messages, split into dedicated classes by subtype(), including Hook notifications and Memory results
HookEventMessagehook_eventLegacy Hook event
MirrorErrorMessagemirror_errorSession mirroring write failed
UnknownMessageRaw valueUnknown top-level type
Every dedicated system message extends 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.
public final class AssistantMessage extends SdkMessage {
    public List<ContentBlock> content();
    public String getText();
}

public abstract class ContentBlock {
    public abstract String type();
}

public final class TextBlock extends ContentBlock {
    public static final String TYPE = "text";
    public String text();
}

public final class UnknownContentBlock extends ContentBlock {
    public String rawJson();
}
MethodReturn typeDescription
AssistantMessage.content()List<ContentBlock>Content blocks
AssistantMessage.getText()StringText of all TextBlocks concatenated
ContentBlock.type()StringThe block's type, such as text
TextBlock.text()StringText content
UnknownContentBlock.rawJson()StringRaw JSON of the block
The Java SDK models only 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.
public final class ResultMessage extends SdkMessage implements TurnTerminalMessage {
    public static final String SUBTYPE_SUCCESS = "success";
    public String wireSubtype();
    public Optional<Boolean> wireIsError();
    public ResultOutcome outcome();
    public Optional<String> result();
    public List<String> errors();
    public Usage usage();
    public boolean terminatesTurn();   // always true
}

public enum ResultOutcome { SUCCESS, ERROR, UNKNOWN }

public final class Usage {
    public static Usage empty();
    public long inputTokens();
    public long outputTokens();
}

public interface TurnTerminalMessage {
    boolean terminatesTurn();
}
MethodReturn typeDescription
wireSubtype()StringRaw subtype, such as success or error_max_turns
wireIsError()Optional<Boolean>Raw is_error; empty when the CLI does not provide it
outcome()ResultOutcomeNormalized outcome; rules below
result()Optional<String>Final result text
errors()List<String>Error descriptions; may be empty, never null
usage()UsageToken usage for this turn
Usage.inputTokens() / outputTokens()longInput 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()ClassKey accessorsDescription
Other or unknown valuesSystemMessagesubtype()Full payload in rawJson()
plan_mode_changedPlanModeChangedMessageplanMode() → PlanModeSnapshotStandalone Plan state changed; see Permissions
commands_changedCommandsChangedMessagecommands() → List<CommandInfo>The complete command catalog; an empty list clears it
available_models_updateAvailableModelsUpdateMessagecatalog() → ModelCatalogSnapshotThe complete model list and the current model
agents_md_size_warningAgentsMdSizeWarningMessagetotalBytes(), files(), and othersAGENTS file size warning
permission_deniedPermissionDeniedMessageNone; read rawJson()A tool permission was denied
background_tasks_changedBackgroundTasksChangedMessageNone; read rawJson()The set of background tasks changed
task_updatedTaskUpdatedMessageNone; read rawJson()A task's status was updated
hook_started / hook_progress / hook_responseHookStartedMessage and othersSee Hook notification messagesHook run notifications
memory_generation / memory_consumptionMemoryGenerationMessage / MemoryConsumptionMessageresult()Memory generation and loading results
public class SystemMessage extends SdkMessage {
    public final Optional<String> subtype();
}

public final class AgentsMdSizeWarningMessage extends SystemMessage {
    public static final String SUBTYPE = "agents_md_size_warning";
    public long totalBytes();
    public long thresholdBytes();
    public Optional<String> thresholdSource();
    public Optional<String> loadStatus();
    public Optional<String> severity();
    public Optional<String> code();
    public boolean truncated();
    public List<FileSize> files();

    public static final class FileSize {
        public String path();
        public long bytes();
    }
}

// Accessors of the other subclasses are in the table above; Hook notifications and Memory results follow
All sizes in 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

public final class PartialMessage extends SdkMessage {}

public final class UserReplayMessage extends SdkMessage {
    public List<ContentBlock> content();
}
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.
public final class HookStartedMessage extends SystemMessage {   // subtype=hook_started
    public static final String SUBTYPE = "hook_started";
    public Optional<String> hookId();
    public Optional<String> hookName();
    public Optional<String> hookEvent();
    public Optional<String> stdout();
    public Optional<String> stderr();
    public Optional<String> output();
    public Optional<String> outcome();
    public Optional<Integer> exitCode();
}
// HookProgressMessage (hook_progress) and HookResponseMessage (hook_response) have the same accessors

public final class HookEventMessage extends SdkMessage {        // type=hook_event
    public String eventName();
}
Each accessor maps to the snake_case field of the same name (such as 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.
public final class MemoryGenerationMessage extends SystemMessage {     // subtype=memory_generation
    public MemoryGenerationResult result();
}

public final class MemoryConsumptionMessage extends SystemMessage {    // subtype=memory_consumption
    public MemoryConsumptionResult result();
}
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

public final class MirrorErrorMessage extends SdkMessage {}   // type=mirror_error

public final class UnknownMessage extends SdkMessage {
    public Optional<String> wireSubtype();
}
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.
public abstract class JsonValue {
    public static JsonValue parse(String json);
    public final String toJson();

    public boolean isNull();
    public boolean isBoolean();
    public boolean isNumber();
    public boolean isString();
    public boolean isArray();
    public boolean isObject();

    public boolean asBoolean();
    public BigDecimal asNumber();
    public String asString();
    public JsonArray asArray();
    public JsonObject asObject();
}

public final class JsonNull extends JsonValue {
    public static final JsonNull INSTANCE;
}

public final class JsonBoolean extends JsonValue {
    public static final JsonBoolean TRUE;
    public static final JsonBoolean FALSE;
    public static JsonBoolean of(boolean value);
    public boolean value();
}

public final class JsonNumber extends JsonValue {
    public static JsonNumber of(BigDecimal value);
    public static JsonNumber of(long value);
    public static JsonNumber of(double value);
    public BigDecimal value();
}

public final class JsonString extends JsonValue {
    public static JsonString of(String value);
    public String value();
}

public final class JsonArray extends JsonValue {
    public static JsonArray empty();
    public static JsonArray of(List<? extends JsonValue> values);
    public static JsonArray of(JsonValue... values);
    public List<JsonValue> values();
    public int size();
    public JsonValue get(int index);
}

public final class JsonObject extends JsonValue {
    public static JsonObject empty();
    public static JsonObject of(Map<String, ? extends JsonValue> members);
    public static Builder builder();
    public Map<String, JsonValue> members();
    public JsonValue get(String name);
    public boolean contains(String name);
    public Set<String> names();
    public int size();

    public static final class Builder {
        public Builder put(String name, JsonValue value);
        public JsonObject build();
    }
}
MethodReturn typeDescription
asXxx()The matching typeConverts the type; throws IllegalStateException on a type mismatch
asNumber() / JsonNumber.value()BigDecimalNumbers are stored exactly as BigDecimal
JsonObject.get(String)JsonValueReturns 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)BuilderAdds a member; a later value with the same name wins. Neither the name nor the value may be null
  • parse throws IllegalArgumentException when 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) throws IllegalArgumentException when the serialized value is longer than 1024 characters.
JsonObject payload = JsonObject.builder()
        .put("path", JsonString.of("src/Parser.java"))
        .put("dryRun", JsonBoolean.TRUE)
        .build();

Diagnostics

DiagnosticsListener

Receives the SDK's structured diagnostic events, such as session initialization, close timeouts, capability downgrades, and managed CLI installation.
public interface DiagnosticsListener {
    void onDiagnostic(DiagnosticEvent event);
}
  • Two registration levels. Register at the client level with QoderClient.Builder.diagnosticsListener(...) and at the session level with SessionOptions.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's managed_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 blocking close() from the listener throws IllegalBlockingCloseException.
QoderClient client = QoderClient.builder()
        .auth(Auth.accessTokenEnv())
        .diagnosticsListener(event -> System.err.println(
                "[qoder-diag] type=" + event.type()
                + " requestType=" + event.requestType().orElse("-")
                + " exitCode=" + event.exitCode().map(String::valueOf).orElse("-")))
        .build();

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.
public final class DiagnosticEvent {
    public static Builder builder(String type);
    public String type();
    public Optional<String> sessionId();
    public Optional<String> requestType();
    public Optional<Long> durationMs();
    public Optional<Integer> queueDepth();
    public Optional<Integer> exitCode();
    public Optional<String> capability();
    public Optional<String> detail();
    // The Builder has a setter named after each optional field in the table below; call build() last
}
FieldTypeDescription
typeStringEvent type, required; common values below
sessionIdOptional<String>ID of the owning session
requestTypeOptional<String>Related control request type
durationMsOptional<Long>Operation duration in milliseconds
queueDepthOptional<Integer>Queue depth snapshot
exitCodeOptional<Integer>CLI exit code
capabilityOptional<String>Related capability name
detailOptional<String>Free-form detail
When your application builds its own event with builder(type), the SDK does not redact detail.

Common event types

typeMain fieldsMeaning
session_initializedsessionId, detailInitialization handshake completed; detail contains the protocol version
protocol_handshake_warningdetailWarning from protocol negotiation during the handshake
session_failedsessionId, exitCode, detailSession ended because of an error
control_request_completedrequestType, durationMsA control request received a success response
inbound_control_request_failedsessionId, detailA 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_downgradedcapability, requestTypeThe 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_updatesessionIdA command or model catalog update was invalid; the previous snapshot is kept
memory_callback_faileddetailA Memory result callback failed or was rejected by the executor; the message is kept and the session continues
memory_drain_failed / memory_drain_timeoutdetailWaiting for Memory during automatic wrap-up failed or timed out; the main answer is kept and resources continue to be released
mirror_append_failedsessionId, detailA mirroring write to external session storage failed; a MirrorErrorMessage is delivered as well
mirror_drain_timeoutsessionId, detailMirroring writes did not finish within the time limit during close
process_tree_cleanup_degradedcapability, detailThe 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_timeoutsessionIdThe 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_closedetailThe CLI process is still running after the close sequence finished; detail lists the termination steps taken
lane_shutdown_timeoutdetailAn SDK internal thread did not stop within the close time limit; callbacks that ignore interrupts may keep running after close
spool_cleanup_deferreddetailThe message buffer's temporary file was still in use during close; it is deleted after reads and writes return
private_directory_cleanup_faileddetailThe 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_faileddetailCleanup of the temporary directory used to restore an external session failed
managed_runtime_reuseddetailThe cached managed CLI was used
managed_runtime_install_started / managed_runtime_installeddetailManaged CLI installation started / completed
managed_runtime_stale_install_removeddetailA damaged or incomplete old installation was removed
managed_runtime_unsupported_platformdetailNo managed CLI exists for the current platform; the SDK looks for an installed CLI instead
Later SDK versions may add event types. Keep a default branch when you switch on 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.
RuntimeException
└── QoderException
    ├── AuthenticationException
    ├── CliNotFoundException
    ├── RuntimeInstallException
    ├── CliProcessException
    ├── ProtocolException
    │   ├── FrameTooLargeException
    │   ├── MalformedUtf8Exception
    │   └── ProtocolVersionMismatchException
    ├── ControlRequestTimeoutException
    ├── UnsupportedCliCapabilityException
    ├── SessionClosedException
    ├── SessionStoreConflictException
    ├── SessionStoreDeletedException
    ├── SpoolIoException
    ├── SpoolCapacityExceededException
    ├── MessageStreamAlreadyConsumedException
    ├── ConsumerInterruptedException
    ├── CallbackExecutionException
    ├── CallbackRejectedException
    ├── IllegalBlockingCloseException
    ├── InvalidOptionsException
    └── UnsupportedSecureSecretTransportException

QoderException

public class QoderException extends RuntimeException {
    public QoderException(String code, String message, boolean retryable);
    public QoderException(String code, String message, boolean retryable, Throwable cause);
    public final String getCode();
    public final boolean isRetryable();
}
MethodReturn typeDescription
getCode()StringStable, machine-readable error code; see the exception list
isRetryable()booleanWhether a retry is worthwhile
The message and cause are redacted at construction, and 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

public class UnsupportedCliCapabilityException extends QoderException {
    public static final String CODE = "UNSUPPORTED_CLI_CAPABILITY";
    public CapabilityState capabilityState();
}
Thrown when:
  • 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 SessionRepository operation 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.
ClassError codeWhen it is thrown
AuthenticationExceptionAUTHENTICATION_FAILED, CLI_AUTH_FATALAuthentication 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
CliNotFoundExceptionCLI_NOT_FOUNDThe CLI cannot be found: the explicit path is invalid, or automatic lookup found no executable
RuntimeInstallExceptionRUNTIME_INSTALL_FAILEDManaged 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
CliProcessExceptionCLI_PROCESS_FAILEDThe 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
ProtocolExceptionPROTOCOL_ERRORCLI output violates the protocol in a way not covered by the three subclasses below
FrameTooLargeExceptionFRAME_TOO_LARGEA single message exceeds the size limit
MalformedUtf8ExceptionMALFORMED_UTF8CLI output is not valid UTF-8
ProtocolVersionMismatchExceptionPROTOCOL_VERSION_MISMATCHThe CLI's protocol version is incompatible with the SDK
ControlRequestTimeoutExceptionCONTROL_REQUEST_TIMEOUTA control request received no response within controlRequestTimeoutMs (default 60000 ms)
UnsupportedCliCapabilityExceptionUNSUPPORTED_CLI_CAPABILITYSee above
SessionClosedExceptionSESSION_CLOSEDAn operation on a closed client, session, or query. Consumers also receive it when the CLI exits first during a full close
SessionStoreConflictExceptionSESSION_STORE_CONFLICTA session store version conflict (expectedVersion is stale), or the same UUID maps to different content. currentVersion() returns the store's current version
SessionStoreDeletedExceptionSESSION_STORE_DELETEDSee SessionStoreDeletedException
SpoolIoExceptionSPOOL_IO_ERRORReading or writing the message buffer's disk file failed
SpoolCapacityExceededExceptionSPOOL_CAPACITY_EXCEEDEDUnconsumed messages exceeded the buffer limit and the message stream failed. Consume the message stream promptly to avoid it
MessageStreamAlreadyConsumedExceptionMESSAGE_STREAM_ALREADY_CONSUMEDThe same MessageStream was consumed a second time; openCursor() and consume() together can be called only once
ConsumerInterruptedExceptionCONSUMER_INTERRUPTEDA thread blocked in MessageCursor.hasNext() or next() was interrupted; the thread's interrupt flag is preserved
CallbackExecutionExceptionCALLBACK_EXECUTION_FAILEDAn application callback (permission, Hook, MCP, message handler) threw
CallbackRejectedExceptionCALLBACK_REJECTEDcallbackExecutor rejected a callback task, for example because the thread pool is shut down or the queue is full
IllegalBlockingCloseExceptionILLEGAL_BLOCKING_CLOSEA 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
InvalidOptionsExceptionINVALID_OPTIONSInvalid configuration, for example QoderClient.Builder without auth, or SessionOptions with mutually exclusive options set
UnsupportedSecureSecretTransportExceptionUNSUPPORTED_SECURE_SECRET_TRANSPORTThe 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
The following error code is thrown directly as QoderException:
Error codeWhen it is thrown
CLI_START_FAILEDThe 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 codeMeaning
QUERY_WRITE_FAILEDThe one-shot query's input was not written to the CLI
CONTROL_WRITE_FAILEDA control request or response was not written to the CLI
TURN_ACCOUNTING_FAILEDThe session's turn state can no longer be processed
MESSAGE_DELIVERY_FAILEDA 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