Runtime Architecture
KsADK local runtime has one central job: load a user agent project, adapt the selected framework to a common runner interface, and expose predictable local protocols for terminal, browser, and API clients.
Layer Model
Entry → Boot → HTTP/UI → Conversations Runtime → Runner → Session persistence → 0.6.7 Run capability layer. The important boundary is that the HTTP server and conversation runtime do not need to know whether the user project is ADK, LangGraph, LangChain, or DeepAgents. They call BaseRunner.invoke() or BaseRunner.stream().
Core Packages
| Package | Responsibility |
|---|---|
ksadk.cli | command entry points, local process setup, user-facing errors |
ksadk.detection | project config, entry file, framework, and agent variable detection |
ksadk.runners | common runner contract and framework adapters |
ksadk.server | FastAPI app, local Web UI APIs, OpenAI-compatible endpoints |
ksadk.conversations | message normalization, session turn orchestration, protocol payloads |
ksadk.sessions | local and pluggable session storage |
ksadk_runtime_common.workspace_files | reusable workspace file routes and preview security |
Startup Lifecycle
agentengine run and agentengine web follow the same broad path:
- resolve the project directory.
- re-execute inside the project virtual environment when needed.
- load environment and project settings.
- detect the framework and entry point.
- create the matching runner.
- load the user agent.
- run terminal mode or start the local HTTP server.
If framework detection returns unknown, the CLI stops before importing the user project as a runner.
Startup Boundaries
The local runtime has a strict startup boundary:
- CLI code resolves paths, configuration, environment variables, and the project virtual environment.
- detection code decides which framework adapter should be used.
- the runner factory creates a framework-specific runner.
- the runner loads user code.
- the server layer exposes HTTP protocols and delegates execution to the runner.
Detection happens before user code is imported. That distinction is important for safety and debuggability: a broken module import should be reported as an agent loading problem, while a missing agentengine.yaml, invalid entry point, or unsupported framework should be reported as a project detection problem.
For public examples, keep project setup explicit:
name: support-agent
framework: langgraph
entry_point: agent.py
agent_variable: root_agentExplicit configuration reduces ambiguity in CI and makes the same project work
the same way under agentengine run, agentengine web, local API tests, and
packaging checks.
Framework Detection
Detection uses explicit configuration first, then convention:
Public samples should include agentengine.yaml because explicit config is easier to review and less dependent on source-code heuristics.
The convention-based fallback is still useful for quick experiments. It checks common project layouts such as:
- a root
agent.py,main.py, orapp.py. - a package directory with
agent.py,main.py,app.py, or__init__.py. - a
src/<package>layout. langgraph.jsongraph targets.
When convention detection is used, the runtime still needs two concrete facts: which file should be imported and which variable inside that file is the agent object. If either value is ambiguous, add agentengine.yaml instead of relying on heuristics.
Runner Contract
Every runner implements the same conceptual contract:
| Method | Meaning |
|---|---|
load_agent() | import and prepare the user agent object |
invoke(input_data) | execute one non-streaming turn |
stream(input_data) | execute one streaming turn |
prepare_for_request(model) | apply per-request model override where supported |
close() | release resources when the server shuts down |
This contract is intentionally narrow. Framework-specific behavior belongs in the adapter, not in the FastAPI endpoint.
The narrow contract also defines where custom behavior should live:
| Need | Preferred location |
|---|---|
| custom state mapping for LangGraph | ksadk_prepare_state in the user module |
| custom input mapping for LangChain | ksadk_prepare_input in the user module |
| model override handling | runner prepare_for_request() |
| framework-native session continuity | runner session adapter |
| protocol-specific JSON/SSE formatting | server and conversation runtime |
If you are adding a new framework adapter, start with BaseRunner and implement load_agent(), invoke(), and stream() first. Add session continuity, dynamic model overrides, or protocol-specific event mapping only after the basic invoke and stream paths are stable.
Request Lifecycle
A normal non-streaming /v1/responses request follows this path:
Streaming uses the same preparation path, then serializes internal semantic events into server-sent events. Text deltas, reasoning, tool calls, tool results, approval interrupts, final output, and errors are represented as runtime events before they become protocol-specific SSE payloads.
Streaming Event Model
Streaming output is normalized before it is serialized. Framework adapters may emit very different native events, but the conversation runtime expects a small set of semantic chunk types:
| Chunk type | Meaning | Typical source |
|---|---|---|
text | assistant text delta | model token stream |
thinking | reasoning or thought delta when available | provider or ADK event |
tool_call | tool call started or arguments updated | LangGraph, ADK, MCP, or custom tool event |
tool_result | tool output became available | framework tool result event |
interrupt | run paused for approval or external input | graph interrupt or approval flow |
final | final authoritative output | framework final state |
error | execution failed | runner or protocol exception |
The protocol layer then maps those semantic events to /v1/responses,
/v1/chat/completions, /run_sse, or the local Web UI action format. This is
why application code should not depend on a specific SSE event name unless it is
writing a client for that exact public protocol.
Model Policy And Thinking Semantics
Whether a thinking chunk appears, and how reasoning content is injected, is
decided by the runtime's unified model policy and thinking-disable injection
semantics (unified from 0.6.6, disable injection completed in 0.6.7):
- Unified model policy: hosted deployments inject primary / multimodal /
fallback defaults through
AGENTENGINE_MODEL_POLICY_JSON, shared by Hermes, OpenClaw, and generic agents. Explicit request parameters or explicit environment variables still take precedence over policy defaults. - Fallback retry: the conversation runtime automatically retries once with
the fallback model on recoverable errors such as timeouts, rate limiting, 5xx,
model unavailability, or permission/quota errors. 400 parameter errors,
business errors, and tool errors are not swallowed. When fallback triggers,
the next streaming turn may switch to the fallback backend, and whether a
thinkingchunk appears depends on the target model's capability. - Thinking-disable injection: when
model_options.thinkingis disabled (reasoning.effort=none,thinking.type=disabled, ormax_reasoning_tokens<=0), the runtime automatically injectsenable_thinking=falseandchat_template_kwargs.enable_thinking=falseintoextra_body(compatible with DeepSeek-style models) and filters reasoning output items from the stream, so nothinkingchunk is emitted over SSE.
For the policy JSON structure, catalog fields, and injection details see Environment Variables.
Session And Context Boundary
Every turn builds a PlatformInvocationContext before calling the runner. It
contains the stable identifiers and runtime facts a framework adapter may need:
agent_id,user_id,session_id, and invocation metadata.- normalized input content and message history.
- effective attachments and current-turn attachments.
- model, model metadata, and model options.
- knowledge and memory context when those integrations are enabled.
The runner receives this context as part of the prepared input. Business logic should prefer the documented runner payload fields and framework hooks over reading private server globals. That keeps local terminal runs, Web UI runs, and OpenAI-compatible API calls aligned.
Checkpoint, Resume, And Cooperative Cancel
Added in 0.6.7
KsADK introduces a framework-level checkpoint/resume capability layer and
completes CancelRun cooperative cancellation.
Run-level checkpoint, resume, and cancel sit on top of the session store and
framework checkpoint backend, as a capability layer independent of streaming
chunks. The conversation runtime writes a run_status event on every turn and
exposes recovery and cancel entry points at checkpoint boundaries:
| Capability | Meaning | Trigger |
|---|---|---|
| checkpoint write | persists a resumable snapshot at a turn boundary | runner writes it on turn completion or interrupt |
| resume | resumes a run from a checkpoint | POST /agentengine/api/v1/ResumeRun |
| preview | previews resume content and impact | POST /agentengine/api/v1/GetCheckpointResumePreview |
| list | lists checkpoints under a session | POST /agentengine/api/v1/ListSessionCheckpoints |
| cooperative cancel | requests an in-flight run to stop | POST /agentengine/api/v1/CancelRun |
CancelRun is a cooperative cancel: the runtime observes the cancel request
at the next runner cooperation point (for example the next streaming chunk, a
tool call boundary, or an interrupt checkpoint), stops the run, writes
run_status=cancelled, and preserves already-produced checkpoints and events
for later resume where possible. It does not hard-kill the process or discard
already-persisted turns.
Resume capability is gated by both RuntimeCapabilities.ResumeRun.ResumeMode
and RunLifecycle returned from GetAgentUiBootstrap (time_travel /
forward_only / none); the frontend entry point must satisfy both
RunLifecycle.Checkpoints and RunLifecycle.CheckpointResume before being
shown.
For the full action paths, ResumeMode semantics, SubscribeRunEvents replay,
and the 5-minute server protection timeout see
Sessions And Files.
Local Protocol Entrypoints
| Endpoint | Purpose |
|---|---|
POST /v1/responses | preferred OpenAI-compatible local protocol |
POST /v1/chat/completions | compatibility with Chat Completions clients |
POST /agentengine/api/v1/RunAgent | local Web UI action-style protocol |
POST /run_sse | ADK Web compatible local execution path |
POST /agentengine/api/v1/UploadFile | local file upload for UI flows |
/_ksadk/workspace/v1/* | workspace file list/read/write/delete routes |
External clients should prefer /v1/responses unless they are integrating with the local Web UI itself.
Public Documentation Boundary
This page describes the public local runtime architecture. It intentionally does not publish private gateway behavior, internal cluster deployment details, internal kubeconfig paths, private registry names, or customer-specific runbooks.