diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 837761a5..a55a6a92 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -6,7 +6,7 @@
- [ ] `uv run --extra dev pytest -q`
- [ ] `make public-audit`
-- [ ] `make public-docs-build`
+- [ ] `make docs-site-build`
- [ ] `make open-source-audit-dist` if package artifacts changed.
- [ ] `uv build`
- [ ] `uv run --extra dev python -m twine check dist/*`
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e12216e1..5176454b 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -12,7 +12,7 @@ jobs:
test:
runs-on: ubuntu-latest
env:
- KSADK_WEB_VERSION: "0.2.16"
+ KSADK_WEB_VERSION: "0.2.18"
steps:
- uses: actions/checkout@v4
@@ -46,24 +46,13 @@ jobs:
run: uv build
- name: Run public release gate tests
- run: |
- uv run --extra dev pytest \
- tests/test_open_source_audit.py \
- tests/test_runtime_common_packaging.py \
- tests/test_public_release_positioning.py \
- tests/test_tracing_setup_otlp.py \
- tests/test_check_publication_state.py \
- tests/test_check_approval_record.py \
- tests/test_markdown_repair.py \
- tests/test_conversation_runtime.py \
- tests/test_server_session_app.py \
- -q
+ run: make public-test
- name: Audit public repository candidate
run: uv run --extra dev python scripts/open_source_audit.py --target public-repo
- name: Build and audit public docs
- run: make public-docs-build
+ run: make docs-site-build
- name: Check package metadata
run: uv run --extra dev python -m twine check dist/*
diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml
index bc2464d8..f9137159 100644
--- a/.github/workflows/publish-pypi.yml
+++ b/.github/workflows/publish-pypi.yml
@@ -9,7 +9,10 @@ on:
ksadk_web_version:
description: KsADK Web npm version to bundle
required: false
- default: "0.2.16"
+ default: "0.2.18"
+ approved_source_commit:
+ description: Reviewed source commit SHA recorded in docs/maintainer-approval-record.md
+ required: false
permissions:
contents: read
@@ -26,7 +29,8 @@ jobs:
environment:
name: pypi
env:
- KSADK_WEB_VERSION: ${{ github.event.inputs.ksadk_web_version || '0.2.16' }}
+ KSADK_WEB_VERSION: ${{ github.event.inputs.ksadk_web_version || '0.2.18' }}
+ KSADK_APPROVED_SOURCE_COMMIT: ${{ github.event.inputs.approved_source_commit || vars.KSADK_APPROVED_SOURCE_COMMIT }}
permissions:
contents: read
id-token: write
diff --git a/AGENTS.md b/AGENTS.md
index 4af9da38..74fa3c70 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -37,7 +37,7 @@
- `ksadk.skills` / `ksadk.skills.runtime` 是 Skill Runtime 上层应用,负责 Skill Center 消费、包校验、安全解压、loader、工具定义和 `execute_skills` 编排。
- E2B backend 是当前优先实现路径;后续可扩展 KOP / 平台私有 backend,但业务逻辑不要写死到 E2B 特定对象。
- ADK Runner 可做自动工具注入;LangGraph / DeepAgents 等已编译 graph 默认提供 helper 或显式接入,不强行魔改用户 graph。
-- 沙箱镜像内最小 agent 交付物以 `deploy/skill-runtime/` 为准。
+- 沙箱镜像内最小 agent 交付物以 `ksadk/skills/runtime/agent.py` 为准;顶层 `deploy/` 已迁出本仓。
- Skill Service 管注册、CRUD、版本治理;KsADK 只消费运行时必要接口,例如 `ListSkillsBySpaceId`、`GetSkillDownloadUrl`。
## 5. 跨仓边界
@@ -83,13 +83,14 @@
- 未经用户明确批准,不得改 `pyproject.toml` / `ksadk/version.py` 版本号,不得新增或改写 CHANGELOG 发版条目。
- 用户批准发布后,正式 PyPI 发布优先走 GitHub Release / `workflow_dispatch` 触发的 Trusted Publishing;本地 `make publish` / `make publish-test` 仅作为明确批准的应急路径,不绕过 Makefile 手写上传命令。
- 不得在同一轮协作中擅自连续发布多个版本承载中间修复。
-- `master` 是内部开发主干;GitHub `main` 是公开主干。不得直接 `merge master -> main`,公开同步必须走 `release/public-x.y.z` 或等价候选分支。
-- 公开候选必须先推内部 ezone 审核,再推 GitHub、发 GitHub Release、上传 PyPI 或发布 Pages。
+- `master` 是内部开发主干;GitHub `main` 是公开主干。不得直接 `merge master -> main`,公开同步必须走 clean export candidate、GitHub PR 或等价的受审核公开候选流程。
+- GitHub 侧不得存在可写的 `master` 公开分支,也不得把内部 `master` 直接 push 到 GitHub;如果误推到了 `github/master`,第一时间删除远端分支并清理本地跟踪引用,再重新走公开候选流程。
+- 公开候选必须先通过 `make public-preflight` 和 review,再合入 GitHub `main`;npm、PyPI、GitHub Pages 都必须由可信 GitHub workflow 发布,不走本地 publish/upload。
- 公开发布前必须运行 `make public-preflight`。如果只做发布状态核对,运行 `make public-publish-check`。失败时不得发布。
- 每次公开 GitHub Release 对应的公开提交都必须打 tag 留痕,优先使用 `make public-release-tag V=x.y.z`。
- 公开分支长期工作树可以保留,但只能作为公开同步/发布工作区,不做日常内部开发。
- 不得把 `.pypirc`、私有 registry 凭证、kubeconfig、真实 API Key 或临时 token 放入仓库根目录;正式 PyPI 发布默认使用 Trusted Publishing,只有应急本地发布才允许 PyPI 凭证来自 `~/.pypirc`、环境变量或 CI Secret。
-- 完整公开同步流程见 `docs/release/public-release-workflow.md`;该文档优先于口头约定。
+- 完整公开同步流程见 `docs/public-release-workflow.md`;该文档优先于口头约定。
发布前必须检查:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 792f10d6..6d0fd8e4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,46 @@
格式参考 [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
版本遵循 [Semantic Versioning](https://semver.org/spec/v2.0.0.html)。
+## [0.6.9] - 2026-07-07
+
+### 亮点
+
+- **run 状态双维度字段**:新增 `run_mode`(background/foreground/unknown)和 `run_trigger`(new_run/checkpoint_resume/approval_resume/unknown)两个独立维度字段,区分"怎么跑"和"怎么开始",替代单字段 `run_kind` 的语义错误。后台长任务从 checkpoint 恢复时不再丢失"这是后台任务"的信息,前端可直接消费 `ActiveRunMode` / `ActiveRunTrigger` 判断长任务会话,无需从事件流推断。
+- **checkpoint 可恢复性聚合字段**:`ListSessionCheckpoints` 响应新增 `ResumableTotal` / `HasResumableCheckpoint`,解决 `Total > 0` 不能代表"可恢复"的误判(终态/过期/memory_local checkpoint 会让 Total 非空但不可恢复)。恢复按钮可用性应看 `HasResumableCheckpoint`。
+- **state_delta.active_run 对齐**:ksadk 与 agentengine-server 现都把 `run_mode` / `run_trigger` 写入 `state_delta.active_run`,Session 对象的 `ActiveRunMode` / `ActiveRunTrigger` 由 state 重建,刷新/分享链接/切 session 都能恢复一致状态。
+- **会话事件续订与历史分页收敛**:runtime `ListSessionEvents` / `SubscribeRunEvents` 支持 `AfterSeqId` 增量续订,session backends 支持 `BeforeSeqId` 向前翻页,控制台可从最新窗口进入历史并稳定重连,不再依赖全量扫事件。
+- **真实 token usage 契约补齐**:ADK、LangChain、LangGraph runner 在单轮内累计多次 LLM 调用 usage,同时保留 `last_usage`,让服务端可以同时得到会话累计 token 消耗和最后一轮上下文窗口占用,避免用累计值误当窗口占用。
+
+### 新增
+
+- 新增 `ksadk/conversations/run_kinds.py`:`run_mode` / `run_trigger` 枚举常量 + `validate_run_mode` / `validate_run_trigger` + `trigger_from_resume_input`(从 resume_input 推导 trigger)。
+- `append_run_status_event` 新增 `run_mode` / `run_trigger` 参数,写入事件 `metadata` 与 `state_delta.active_run`。
+- `PreparedConversationTurn` 新增 `run_mode` / `run_trigger` 字段,`build_run_input` 按 checkpoint_resume / approval_resume / new_run 分支回填。
+- `invoke_conversation_once` / `_iter_conversation_turn_events` / `stream_responses_conversation_turn` / `stream_conversation_turn` 透传 `run_mode`;18+ 处 `append_run_status_event` 调用点按 endpoint 语义传值。
+- 各 endpoint 按产品语义标记 run_mode:`RunAgent Background:true` 与 `ResumeRun Stream:true` 标 `background`;普通 `RunAgent Stream:true`、`ResumeRun Stream:false`、`/v1/responses`、`/v1/chat/completions`、`/run_sse` 标 `foreground`。
+- `_DetachedSSEStream` 构造与终态 fallback 写入 `run_mode` / `run_trigger`。
+- 新增 `_latest_session_run_metadata` helper(不改原 `_latest_session_run_status`,保护现有契约),`_session_to_action_payload` 顶层新增 `ActiveRunMode` / `ActiveRunTrigger`。
+- `_list_checkpoints_payload` 新增 `ResumableTotal` / `HasResumableCheckpoint` 聚合字段。规则:`IsResumable===true && ReplayAllowed!==false && IsTerminal!==true && CheckpointStatus not in {expired, disabled}`,不排除 `resumed`(已恢复过的仍计入,符合存档点可反复读)。
+- agentengine-server 侧新建 `app/services/run_kinds.py`(独立维护,不 import ksadk,用测试约束一致性),`_append_run_status` 与 `_serialize_session` 同步写入/读取新字段。
+- 新增 `ksadk/runners/usage_accumulator.py`,统一归一化并累加 OpenAI/ADK/LangChain/LangGraph usage 字段,覆盖 `input_tokens`、`output_tokens`、`total_tokens` 及 token details。
+- runtime `ListSessionEvents` 新增 `AfterSeqId` / `BeforeSeqId` 过滤能力;`SubscribeRunEvents` 支持 `AfterSeqId`,用于断线后只推送已读序号之后的新事件。
+- `SessionService.get_events()` 在 in-memory、local SQLite、Postgres 后端补齐 `after_seq_id` / `before_seq_id` 过滤,保持最新窗口、向后增量、向前翻页三类语义一致。
+
+### 变更
+
+- `run_status` 事件 `metadata` 与 `state_delta.active_run` 扩展为含 `run_mode` / `run_trigger`;旧 session 缺字段降级 `unknown`,不破坏现有 `ActiveInvocationId` / `ActiveRunStatus` 契约。
+- approval 续跑的 `run_mode` 跟随原 run(不写死 foreground),需从原 run 上下文透传。
+- server 侧 `run_status` 事件 `content` 仍为 `{status, detail}`,`run_mode` / `run_trigger` 只写进 `state_delta`,避免破坏现有消费方。
+- runner 返回 `metadata.usage` 继续表示单次响应的累计真实消耗;新增 `metadata.last_usage` 表示最后一次模型调用的 usage,供上层计算 `ContextUsage` 等窗口占用指标。
+- LangChain / LangGraph 的 final chunk metadata 增加 `usage` 与 `last_usage`,保留原响应内容结构,避免只取最后一个 chunk 或最后一次 LLM 调用造成 token 少算。
+
+### 修复
+
+- 修复 ADK runner 单轮内多次 LLM 调用时只保留最后一次 usage,导致会话累计 token 消耗少算的问题。
+- 修复 BaseRunner 从响应列表尾部反向取 usage,遇到多段模型调用时无法聚合的问题。
+- 修复 runtime 事件分页只支持 offset/limit,前端重连和历史向上翻页需要额外扫全量事件的问题。
+- 修复 `SubscribeRunEvents` hosted/runtime 双链路续订语义不一致,断线重连可能重复消费旧事件的问题。
+
## [0.6.8] - 2026-07-03
### 亮点
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index cc689158..33c43271 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -17,14 +17,16 @@ Run focused checks before sending a change:
```bash
uv run --extra dev pytest -q
make open-source-audit
-make public-docs-audit
+make docs-site-build
+make public-audit
uv build
uv run --extra dev python -m twine check dist/*
```
-`public-docs-audit` builds the curated GitHub Pages candidate from
-`public-docs/`. It must not publish `.zread/wiki`, `.zread/site`, internal
-deployment notes, or private generated snapshots.
+`docs-site-build` builds the Fumadocs GitHub Pages candidate from
+`docs-site/`. `public-audit` checks that public repository candidates do not
+publish `.zread/wiki`, `.zread/site`, internal deployment notes, or private
+generated snapshots.
`open-source-audit` checks the current public repository candidate for files
that should not enter the open-source surface.
diff --git a/Makefile b/Makefile
index 45aa0aa1..aaba196b 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
# AgentEngine Makefile
# 用于同步 KsADK Web static 和管理项目
-.PHONY: help install clean clean-cache clean-dist clean-static clean-offline dev test publish publish-test public-status public-init-worktree public-worktree-status public-sync-check public-secret-audit public-audit public-version-gate public-docs-build public-docs-site-build public-test public-build-check public-preflight public-publish-check public-release-approval-check public-publish-gate public-release-tag public-review public-sync-ksadk-web-static open-source-audit-dist openclaw-build openclaw-push openclaw-size hermes-build hermes-push hermes-size docs-check-wiki docs-prepare-source docs-docker-build docs-docker-push docs-helm-lint docs-helm-template docs-deploy docs-deploy-all docs-status docs-logs sync-ksadk-web-static sync-hosted-ui build-frontend build-webui sync-static webui build-wheel build-all clean-frontend
+.PHONY: help install clean clean-cache clean-dist clean-static clean-offline dev test publish publish-test public-status public-init-worktree public-worktree-status public-sync-check public-secret-audit public-audit public-version-gate docs-site-build docs-site-dev public-test public-build-check public-preflight public-publish-check public-release-approval-check public-publish-gate public-release-tag public-review public-sync-ksadk-web-static open-source-audit-dist openclaw-build openclaw-push openclaw-size hermes-build hermes-push hermes-size sync-ksadk-web-static sync-hosted-ui build-frontend build-webui sync-static webui build-wheel build-all clean-frontend
# 默认目标
help:
@@ -39,6 +39,8 @@ help:
@echo " make public-release-tag V=x.y.z 创建公开 release 留痕 tag"
@echo " make public-review 公开候选审核入口"
@echo " make public-publish-check 发布状态核对"
+ @echo " make docs-site-build 本地构建 Fumadocs 静态站点"
+ @echo " make docs-site-dev 本地预览 Fumadocs 文档站"
@echo ""
@echo " \033[1;32m离线打包:\033[0m"
@echo " make offline-current 当前平台离线包"
@@ -52,11 +54,6 @@ help:
@echo " Hermes / OpenClaw / Skill Runtime 镜像已迁移到内部 agentengine-images 仓库"
@echo " 可设置 AGENTENGINE_IMAGES_DIR=../agentengine-images 后继续使用兼容入口"
@echo ""
- @echo " \033[1;32mzread 文档站:\033[0m"
- @echo " make docs-deploy-all 构建原生 zread 文档镜像 + 推送 + 部署到预发"
- @echo " make docs-status 查看预发文档站状态"
- @echo " make docs-deploy-all ENV=online DOCS_VERSION=x # 部署线上"
- @echo ""
@echo " \033[1;32m清理:\033[0m"
@echo " make clean 清理构建产物和本地测试缓存"
@echo " make clean-cache 仅清理 Python/测试/类型检查缓存"
@@ -350,18 +347,22 @@ public-audit: public-secret-audit
@python3 scripts/open_source_audit.py --target public-repo
@echo "✅ public path audit passed"
-public-docs-build: public-docs-site-build
- @echo "==> docs build (Fumadocs docs-site)"
-
-# Fumadocs 文档站 (docs-site/) 构建 + 类型检查, 公开发布前验证
-public-docs-site-build:
+docs-site-build:
@echo "==> docs-site (Fumadocs) build"
@if [ -d "docs-site" ] && [ -f "docs-site/package.json" ]; then \
- cd docs-site && pnpm install --frozen-lockfile && pnpm build; \
+ cd docs-site && pnpm install --frozen-lockfile && NEXT_PUBLIC_BASE_PATH=/ksadk-python pnpm build:static; \
else \
echo "⚠️ docs-site 不存在,跳过 Fumadocs build"; \
fi
+docs-site-dev:
+ @echo "==> docs-site (Fumadocs) dev server"
+ @if [ -d "docs-site" ] && [ -f "docs-site/package.json" ]; then \
+ cd docs-site && pnpm install --frozen-lockfile && pnpm dev; \
+ else \
+ echo "⚠️ docs-site 不存在,无法启动 Fumadocs dev server"; \
+ fi
+
public-test:
@echo "==> test"
@uv sync --extra dev
@@ -389,7 +390,7 @@ public-version-gate:
@echo "==> release version gate (prevent downgrade/re-publish)"
uv run python scripts/check_release_version.py
-public-preflight: public-version-gate public-audit sync-ksadk-web-static public-test public-docs-build public-build-check
+public-preflight: public-version-gate public-audit sync-ksadk-web-static public-test docs-site-build public-build-check
@echo "✅ public preflight passed"
public-publish-check:
@@ -403,7 +404,12 @@ public-publish-check:
public-release-approval-check:
@echo "==> release approval record check"
- @uv run python scripts/check_approval_record.py --expected-current-commit "$${KSADK_APPROVED_SOURCE_COMMIT:-}"
+ @if [ -z "$${KSADK_APPROVED_SOURCE_COMMIT:-}" ]; then \
+ echo "❌ KSADK_APPROVED_SOURCE_COMMIT is required before external release writes"; \
+ echo " Set it to the reviewed source commit recorded in docs/maintainer-approval-record.md"; \
+ exit 1; \
+ fi
+ @uv run python scripts/check_approval_record.py --expected-current-commit "$$KSADK_APPROVED_SOURCE_COMMIT"
public-publish-gate: public-release-approval-check
@echo "✅ public publish gate passed"
@@ -530,123 +536,6 @@ openclaw-build openclaw-push openclaw-size hermes-build hermes-push hermes-size:
@$(MAKE) -C "$(AGENTENGINE_IMAGES_DIR)" $@
-# ============================================================
-# zread 文档站发布
-# ============================================================
-#
-# 依赖本地 .zread/wiki/current 指向的完整 wiki 版本。发布镜像会运行
-# zread browse 原生 UI,保留 zread 样式、前端交互和 Mermaid 渲染。
-#
-
-DOCS_PROJECT_NAME ?= ksadk-docs
-DOCS_DOCKER_REGISTRY ?= hub.kce.ksyun.com
-DOCS_DOCKER_NAMESPACE ?= bigdata-ai
-DOCS_WIKI_VERSION ?= $(shell test -f .zread/wiki/current && sed 's|^versions/||' .zread/wiki/current || echo missing-wiki)
-DOCS_VERSION ?= zread-$(DOCS_WIKI_VERSION)
-ENV ?= pre
-DOCS_FORCE_UPDATE ?= 0
-DOCS_FORCE_UPDATE_NONCE ?= $(shell date '+%Y%m%d%H%M%S')
-
-ifeq ($(ENV),online)
- DOCS_KUBECONFIG_PATH := $(HOME)/.kube/agentengine-online
- DOCS_VALUES_FILE := deploy/helm/ksadk-docs/values-online.yaml
-else
- DOCS_KUBECONFIG_PATH := $(HOME)/.kube/agentengine-pre
- DOCS_VALUES_FILE := deploy/helm/ksadk-docs/values-pre.yaml
-endif
-
-DOCS_IMAGE := $(DOCS_DOCKER_REGISTRY)/$(DOCS_DOCKER_NAMESPACE)/$(DOCS_PROJECT_NAME):$(DOCS_VERSION)
-DOCS_NAMESPACE ?= agentengine
-DOCS_HELM_RELEASE ?= ksadk-docs
-DOCS_HELM_CHART := deploy/helm/ksadk-docs
-DOCS_HELM_TIMEOUT ?= 600s
-DOCS_BASE_PATH ?= /ksadk-docs
-DOCS_BASE_IMAGE ?= hub.kce.ksyun.com/bigdata-ai/agentengine-server-base:v0.4.1
-DOCS_ZREAD_VERSION ?= 0.2.12
-DOCS_ZREAD_SHA256 ?= faf5ef7f2f8edc24d41b84fd838322882846f4bab10f1a9210de29cba2a53a10
-DOCS_HELM_SET_FLAGS := --set image.tag=$(DOCS_VERSION) --set docs.basePath=$(DOCS_BASE_PATH)
-
-ifeq ($(DOCS_FORCE_UPDATE),1)
- DOCS_HELM_SET_FLAGS += --set-string podAnnotations.force-redeploy=$(DOCS_FORCE_UPDATE_NONCE)
-endif
-
-docs-check-wiki:
- @if [ ! -f ".zread/wiki/current" ]; then \
- echo "❌ 缺少 .zread/wiki/current,请先运行 zread generate -y --stdio"; \
- exit 1; \
- fi
- @if [ ! -f ".zread/wiki/versions/$(DOCS_WIKI_VERSION)/wiki.json" ]; then \
- echo "❌ 缺少 .zread/wiki/versions/$(DOCS_WIKI_VERSION)/wiki.json"; \
- exit 1; \
- fi
- @python3 -c 'import json; from pathlib import Path; version = Path(".zread/wiki/current").read_text().strip().removeprefix("versions/"); root = Path(".zread/wiki/versions", version); wiki = json.loads((root / "wiki.json").read_text()); pages = wiki.get("pages") or []; assert pages, "wiki.json 中没有页面,拒绝发布"; missing = [p.get("file") for p in pages if not (root / p.get("file", "")).exists()]; print(f"✅ zread wiki: {version}, pages={len(pages)}, missing={len(missing)}"); [print(f"❌ 缺失页面文件: {name}") for name in missing]; raise SystemExit(1 if missing else 0)'
- @if [ -f ".zread/wiki/drafts/wiki.json" ]; then \
- echo "⚠️ 检测到 .zread/wiki/drafts/wiki.json,本次仍发布 current 完整版本: $(DOCS_WIKI_VERSION)"; \
- fi
-
-docs-prepare-source: docs-check-wiki
- @python3 scripts/prepare_zread_source_snapshot.py
-
-docs-docker-build: docs-check-wiki docs-prepare-source
- @echo "🐳 构建 KsADK 原生 zread 文档镜像: $(DOCS_IMAGE)"
- @DOCKER_BUILDKIT=1 docker build --pull=false --platform linux/amd64 \
- -f Dockerfile.docs \
- --build-arg DOCS_BASE_IMAGE=$(DOCS_BASE_IMAGE) \
- --build-arg ZREAD_VERSION=$(DOCS_ZREAD_VERSION) \
- --build-arg ZREAD_SHA256=$(DOCS_ZREAD_SHA256) \
- -t $(DOCS_IMAGE) \
- .
-
-docs-docker-push: docs-docker-build
- @echo "📤 推送 KsADK 文档镜像: $(DOCS_IMAGE)"
- @docker push $(DOCS_IMAGE)
-
-docs-helm-lint:
- @echo "==> helm lint $(DOCS_HELM_CHART)"
- @helm lint $(DOCS_HELM_CHART)
-
-docs-helm-template:
- @echo "==> helm template $(DOCS_HELM_RELEASE) ($(ENV))"
- @helm template $(DOCS_HELM_RELEASE) $(DOCS_HELM_CHART) \
- --namespace $(DOCS_NAMESPACE) \
- --values $(DOCS_VALUES_FILE) \
- $(DOCS_HELM_SET_FLAGS)
-
-docs-deploy: docs-helm-lint
- @echo "==> helm upgrade --install $(DOCS_HELM_RELEASE) ($(ENV))"
- @echo " namespace=$(DOCS_NAMESPACE) image=$(DOCS_IMAGE) timeout=$(DOCS_HELM_TIMEOUT) force_update=$(DOCS_FORCE_UPDATE)"
- @set -e; \
- if helm upgrade --install $(DOCS_HELM_RELEASE) $(DOCS_HELM_CHART) \
- --kubeconfig $(DOCS_KUBECONFIG_PATH) \
- --namespace $(DOCS_NAMESPACE) \
- --create-namespace \
- --values $(DOCS_VALUES_FILE) \
- $(DOCS_HELM_SET_FLAGS) \
- --wait \
- --timeout $(DOCS_HELM_TIMEOUT); then \
- echo "==> deployment ready"; \
- echo "==> url: http://$$(helm get values $(DOCS_HELM_RELEASE) --kubeconfig $(DOCS_KUBECONFIG_PATH) -n $(DOCS_NAMESPACE) -a -o json | python3 -c 'import json,sys; print(json.load(sys.stdin)["ingress"]["host"])')$(DOCS_BASE_PATH)/"; \
- else \
- status=$$?; \
- echo "==> deployment failed, collecting diagnostics..."; \
- kubectl --kubeconfig $(DOCS_KUBECONFIG_PATH) get deploy,pods,svc,ingress -n $(DOCS_NAMESPACE) -l app.kubernetes.io/name=$(DOCS_PROJECT_NAME) -o wide || true; \
- latest_pod=$$(kubectl --kubeconfig $(DOCS_KUBECONFIG_PATH) get pods -n $(DOCS_NAMESPACE) -l app.kubernetes.io/name=$(DOCS_PROJECT_NAME) --sort-by=.metadata.creationTimestamp -o name 2>/dev/null | tail -n 1 | cut -d/ -f2); \
- if [ -n "$$latest_pod" ]; then \
- echo "==> latest pod: $$latest_pod"; \
- kubectl --kubeconfig $(DOCS_KUBECONFIG_PATH) describe pod -n $(DOCS_NAMESPACE) "$$latest_pod" | sed -n '/Events:/,$$p' || true; \
- fi; \
- exit $$status; \
- fi
-
-docs-deploy-all: docs-docker-push docs-deploy
-
-docs-status:
- @kubectl --kubeconfig $(DOCS_KUBECONFIG_PATH) get pods,svc,ingress -n $(DOCS_NAMESPACE) -l app.kubernetes.io/name=$(DOCS_PROJECT_NAME)
-
-docs-logs:
- @kubectl --kubeconfig $(DOCS_KUBECONFIG_PATH) logs -f -n $(DOCS_NAMESPACE) deployment/$(DOCS_HELM_RELEASE)
-
-
# ============================================================
# KsADK Web static 同步
diff --git a/README.en.md b/README.en.md
index 513594fa..4f150f3b 100644
--- a/README.en.md
+++ b/README.en.md
@@ -16,7 +16,7 @@
-
+
## 30 Seconds Quick Start
@@ -37,9 +37,9 @@ Start the local debugging Web UI:
agentengine web . --no-open
```
-
+
-
+
## Why KsADK
@@ -53,16 +53,16 @@ Most agent frameworks solve how to build agents. KsADK solves how to run, debug,
## Architecture
-
+
## Docs And Examples
- Documentation:
-- Quick Start:
-- Why KsADK:
-- Architecture:
-- Ecosystem Positioning:
-- Observability:
+- Quick Start:
+- Why KsADK:
+- Architecture:
+- Ecosystem Positioning:
+- Observability:
- Samples:
## Related Projects
diff --git a/README.md b/README.md
index f4baf508..23a8d39b 100644
--- a/README.md
+++ b/README.md
@@ -58,11 +58,11 @@ agentengine web . --no-open
## 文档与样例
- 文档:
-- 快速开始:
-- 为什么需要 KsADK:
-- 架构:
-- 生态定位对比:
-- 可观测:
+- 快速开始:
+- 为什么需要 KsADK:
+- 架构:
+- 生态定位对比:
+- 可观测:
- 样例仓库:
## 相关项目
diff --git a/README.zh-CN.md b/README.zh-CN.md
index 4ec558f3..c212f115 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -16,7 +16,7 @@
-
+
## 30 秒快速体验
@@ -37,9 +37,9 @@ agentengine run -i
agentengine web . --no-open
```
-
+
-
+
## 为什么需要 KsADK
@@ -53,16 +53,16 @@ agentengine web . --no-open
## 架构
-
+
## 文档与样例
- 文档:
-- 快速开始:
-- 为什么需要 KsADK:
-- 架构:
-- 生态定位对比:
-- 可观测:
+- 快速开始:
+- 为什么需要 KsADK:
+- 架构:
+- 生态定位对比:
+- 可观测:
- 样例仓库:
## 相关项目
diff --git a/docs-site/content/docs/framework/guides/build-and-package.en.mdx b/docs-site/content/docs/framework/guides/build-and-package.en.mdx
index ef407f78..c2b28071 100644
--- a/docs-site/content/docs/framework/guides/build-and-package.en.mdx
+++ b/docs-site/content/docs/framework/guides/build-and-package.en.mdx
@@ -9,7 +9,7 @@ itself.
```bash
uv run --extra dev python -m twine check dist/*
-make open-source-review
+make public-review
```
## Web UI Artifacts
@@ -69,7 +69,7 @@ The assertions guarantee:
`make public-preflight` layers `public-audit`, `public-test`, and
-`public-docs-build` on top of `public-build-check`. It is the mandatory local
+`docs-site-build` on top of `public-build-check`. It is the mandatory local
gate before pushing to GitHub, PyPI, or a Release.
diff --git a/docs-site/content/docs/framework/guides/build-and-package.mdx b/docs-site/content/docs/framework/guides/build-and-package.mdx
index 95644bdd..8c20b7a2 100644
--- a/docs-site/content/docs/framework/guides/build-and-package.mdx
+++ b/docs-site/content/docs/framework/guides/build-and-package.mdx
@@ -8,7 +8,7 @@ title: "构建与打包"
```bash
uv run --extra dev python -m twine check dist/*
-make open-source-review
+make public-review
```
## Web UI 产物
@@ -58,7 +58,7 @@ agentengine --dry-run deploy .
- wheel 含同步后的 `ksadk/server/static/index.html` 及 `assets/` 入口,保证安装即可打开本地 UI。
-`make public-preflight` 在 `public-build-check` 之上追加 `public-audit`、`public-test`、`public-docs-build`,是推 GitHub/PyPI/Release 前必须通过的完整本地门禁。
+`make public-preflight` 在 `public-build-check` 之上追加 `public-audit`、`public-test`、`docs-site-build`,是推 GitHub/PyPI/Release 前必须通过的完整本地门禁。
## Artifact 规则
diff --git a/docs-site/content/docs/references/contributing/index.en.mdx b/docs-site/content/docs/references/contributing/index.en.mdx
index e1303f6b..30f74a5d 100644
--- a/docs-site/content/docs/references/contributing/index.en.mdx
+++ b/docs-site/content/docs/references/contributing/index.en.mdx
@@ -18,22 +18,21 @@ pytest
Build docs locally:
```bash
-make public-docs-build
-make public-docs-serve
+make docs-site-build
```
Run open-source checks:
```bash
make open-source-audit
-make public-docs-audit
+make public-audit
```
For changes that touch packaging, public docs, release metadata, or repository
layout, run the broader review target before asking for release approval:
```bash
-make open-source-review
+make public-preflight
```
## Public CI Expectations
@@ -43,7 +42,7 @@ Public CI must not require internal kubeconfig files, internal registries, inter
Before submitting a public PR:
- run focused tests for the changed area.
-- run docs build when editing `public-docs/` or `mkdocs.yml`.
+- run `make docs-site-build` when editing `docs-site/`; use `make docs-site-dev` for local preview.
- update CLI docs when command behavior changes.
- update release notes when changing packaging or public API behavior.
- keep examples local-first unless a hosted feature is explicitly approved.
@@ -59,7 +58,7 @@ KsADK keeps public confidence through layered tests:
| ASGI service tests | validate FastAPI routes and session events without a real network server | service/session pytest files |
| HTTP protocol E2E | validate `/v1/responses`, `/v1/chat/completions`, upload, and local Web UI action payloads | OpenAI protocol E2E tests |
| browser E2E | validate built UI behavior when Chromium is available | browser-tagged E2E tests |
-| open-source audits | verify public tree, docs, Pages artifact, sdist, wheel, and clean export boundaries | `make open-source-review` |
+| open-source audits | verify public tree, docs, Pages artifact, sdist, wheel, and clean export boundaries | `make public-preflight` |
When a change affects protocol shape, attachment handling, session events, or
the local Web UI payload, prefer a test that crosses the same boundary a real
@@ -98,7 +97,7 @@ Avoid:
- references to generated `.zread/` output as the published source.
Local zread wiki output can be useful as an engineering note source, but public
-documentation should be curated Markdown under `public-docs/`. Do not publish
+documentation should be curated Markdown under `docs-site/`. Do not publish
the generated wiki directory or depend on it during public CI.
## Open-Source Review Boundary
diff --git a/docs-site/content/docs/references/contributing/index.mdx b/docs-site/content/docs/references/contributing/index.mdx
index ae1ae76d..00e1d4de 100644
--- a/docs-site/content/docs/references/contributing/index.mdx
+++ b/docs-site/content/docs/references/contributing/index.mdx
@@ -17,22 +17,21 @@ pytest
本地构建文档:
```bash
-make public-docs-build
-make public-docs-serve
+make docs-site-build
```
运行开源检查:
```bash
make open-source-audit
-make public-docs-audit
+make public-audit
```
如果变更影响 packaging、公开文档、release metadata 或仓库布局,请在请求 release
approval 前运行更完整的审核目标:
```bash
-make open-source-review
+make public-preflight
```
## 公开 CI 期望
@@ -42,7 +41,7 @@ make open-source-review
提交公开 PR 前:
- 运行变更区域的 focused tests。
-- 修改 `public-docs/` 或 `mkdocs.yml` 时运行文档构建。
+- 修改 `docs-site/` 时运行 `make docs-site-build`,需要本地预览时运行 `make docs-site-dev`。
- 命令行为变化时更新 CLI 文档。
- packaging 或公开 API 变化时更新 release notes。
- 示例应保持本地优先,除非某个 hosted feature 已明确批准公开。
@@ -56,7 +55,7 @@ make open-source-review
| ASGI service tests | 不启动真实网络 server 验证 FastAPI routes 和 session events | service/session pytest 文件 |
| HTTP protocol E2E | 验证 `/v1/responses`、`/v1/chat/completions`、upload 和本地 Web UI action payload | OpenAI protocol E2E tests |
| browser E2E | Chromium 可用时验证构建后的 UI 行为 | browser-tagged E2E tests |
-| open-source audits | 验证公开 tree、docs、Pages artifact、sdist、wheel 和 clean export 边界 | `make open-source-review` |
+| open-source audits | 验证公开 tree、docs、Pages artifact、sdist、wheel 和 clean export 边界 | `make public-preflight` |
当变更影响协议形态、附件处理、session event 或本地 Web UI payload 时,优先写一个
跨越真实客户端边界的测试。
@@ -78,7 +77,7 @@ make open-source-review
- 真实 token 或客户数据。
- 把生成的 `.zread/` 输出当作发布源引用。
-本地 zread wiki 输出可以作为工程笔记来源,但公开文档应是 `public-docs/` 下经过整理的
+本地 zread wiki 输出可以作为工程笔记来源,但公开文档应是 `docs-site/` 下经过整理的
Markdown。不要发布生成的 wiki 目录,也不要让公开 CI 依赖它。
## 开源审核边界
diff --git a/docs-site/content/docs/references/contributing/release.en.mdx b/docs-site/content/docs/references/contributing/release.en.mdx
index 765c4147..ccef15a7 100644
--- a/docs-site/content/docs/references/contributing/release.en.mdx
+++ b/docs-site/content/docs/references/contributing/release.en.mdx
@@ -38,8 +38,7 @@ publication strategy, and maintainer sign-offs.
## Local Commands
```bash
-make open-source-review
-make open-source-review-bundle
+make public-review
python3 scripts/audit_public_history_paths.py --json --allow-violations
git diff --check
```
@@ -93,7 +92,7 @@ The workflow runs the following steps:
it into the packaging tree. When triggered via `workflow_dispatch`, the
`ksadk_web_version` input pins a specific npm version (for example `1.2.3`).
2. **Release preflight**: `make public-preflight` chains `public-audit`,
- `public-test`, `public-docs-build`, and `public-build-check`.
+ `public-test`, `docs-site-build`, and `public-build-check`.
3. **Build and content check**: inside `public-build-check`, `uv build`
produces the artifacts and `twine check dist/*` validates wheel and sdist
metadata.
diff --git a/docs-site/content/docs/references/contributing/release.mdx b/docs-site/content/docs/references/contributing/release.mdx
index dbb5bd37..378561ce 100644
--- a/docs-site/content/docs/references/contributing/release.mdx
+++ b/docs-site/content/docs/references/contributing/release.mdx
@@ -7,8 +7,7 @@ title: 发布流程
## 发布前
```bash
-make open-source-review
-make open-source-review-bundle
+make public-review
make public-publish-check PUBLIC_PUBLISH_PHASE=pre-publish V=0.6.7
```
@@ -48,7 +47,7 @@ workflow 执行步骤如下:
通过 `workflow_dispatch` 触发时可传 `ksadk_web_version` 输入指定版本
(如 `1.2.3`)。
2. **发布前预检**:`make public-preflight`,串起 `public-audit`、
- `public-test`、`public-docs-build` 以及 `public-build-check`。
+ `public-test`、`docs-site-build` 以及 `public-build-check`。
3. **构建与内容检查**:`public-build-check` 内执行 `uv build` 并
`twine check dist/*`,校验 wheel 与 sdist 元数据。
4. **OIDC 上传**:用 `pypa/gh-action-pypi-publish` 通过 Trusted Publishing
diff --git a/docs-site/content/docs/references/contributing/testing.en.mdx b/docs-site/content/docs/references/contributing/testing.en.mdx
index daeda5c7..4e26e59c 100644
--- a/docs-site/content/docs/references/contributing/testing.en.mdx
+++ b/docs-site/content/docs/references/contributing/testing.en.mdx
@@ -16,7 +16,7 @@ publishable artifacts.
| ASGI service tests | FastAPI routes without opening a real port | service and session pytest files |
| HTTP protocol E2E | `/v1/responses`, `/v1/chat/completions`, upload, session events | protocol E2E tests |
| browser E2E | local Web UI request construction and upload behavior | browser-capable E2E tests |
-| open-source audits | public tree, clean exports, Pages, sdist, wheel | `make open-source-review` |
+| open-source audits | public tree, clean exports, Pages, sdist, wheel | `make public-preflight` |
Use the narrowest test that proves the change, then run the broader gate when a
change touches public behavior, packaging, docs, or release boundaries.
@@ -31,15 +31,14 @@ uv run --extra dev pytest tests/ -q
For documentation changes:
```bash
-make public-docs-build
-make public-docs-audit
+make docs-site-build
+make public-audit
```
For release or open-source boundary changes:
```bash
-make open-source-review
-make open-source-review-bundle
+make public-review
```
## Snapshot Tests
@@ -88,7 +87,7 @@ Use browser E2E for:
## Open-Source Review Gate
-`make open-source-review` is the local gate for the public release candidate. It
+`make public-review` is the local gate for the public release candidate. It
checks:
- open-source contract tests.
@@ -111,7 +110,7 @@ publication.
| CLI help, options, or error text | focused CLI tests and affected snapshots |
| runtime request/response behavior | focused runtime tests plus protocol E2E |
| attachments or workspace files | protocol tests and workspace security tests |
-| public docs | `make public-docs-build` and `make public-docs-audit` |
+| public docs | `make docs-site-build` and `make public-audit` |
| package metadata or release scripts | `uv build`, `twine check`, artifact audit |
| open-source export policy | export tests, open-source audit, review bundle |
diff --git a/docs-site/content/docs/references/contributing/testing.mdx b/docs-site/content/docs/references/contributing/testing.mdx
index 81b6e13b..900f0940 100644
--- a/docs-site/content/docs/references/contributing/testing.mdx
+++ b/docs-site/content/docs/references/contributing/testing.mdx
@@ -8,8 +8,8 @@ title: 测试策略
```bash
uv run --extra dev pytest
-uv run --extra dev python -m mkdocs build --strict
-make open-source-review
+make docs-site-build
+make public-review
```
## 覆盖重点
diff --git a/docs-site/content/docs/references/environment-variables.en.mdx b/docs-site/content/docs/references/environment-variables.en.mdx
index f2400ca7..558b8a14 100644
--- a/docs-site/content/docs/references/environment-variables.en.mdx
+++ b/docs-site/content/docs/references/environment-variables.en.mdx
@@ -57,6 +57,21 @@ Hosted deployments can inject a shared policy through `AGENTENGINE_MODEL_POLICY_
| `KSADK_UI_BUNDLE_PATH` | custom UI static bundle path relative to project; local auto-detects `research-ui/dist` (0.6.7) |
| `KSYUN_REGION` | region used by cloud actions and some SDK clients |
+## Cloud Account Identity Resolution
+
+| Variable | Purpose |
+| --- | --- |
+| `KSYUN_IAM_ENDPOINT` | Optional public IAM endpoint override; defaults to `iam.api.ksyun.com` when unset |
+| `KSYUN_IAM_INTRANET_URL` | Optional IAM intranet endpoint fallback override; defaults to `iam.inner.api.ksyun.com` when unset |
+| `IAM_INTRANET_URL` | Compatibility alias for `KSYUN_IAM_INTRANET_URL` |
+
+
+ When public IAM returns an error such as “inner account can only access through
+ intranet”, the runtime retries the intranet endpoint. Public environments
+ normally do not hit this branch; internal environments can override the address
+ with `KSYUN_IAM_INTRANET_URL` or `IAM_INTRANET_URL`.
+
+
## Session Storage
| Variable | Purpose |
@@ -269,6 +284,7 @@ KSADK_MCP_SERVERS='[{"name":"docs","url":"http://127.0.0.1:9000/mcp"}]'
| `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | traces-specific OTLP HTTP endpoint; takes precedence over the generic endpoint |
| `OTEL_EXPORTER_OTLP_TRACES_PROTOCOL` | traces-specific OTLP protocol; takes precedence over the generic protocol |
| `OTEL_EXPORTER_OTLP_TRACES_HEADERS` | traces-specific OTLP HTTP headers; takes precedence over generic headers and may contain auth data |
+| `KSADK_OTLP_MAX_EXPORT_BATCH_SIZE` | maximum spans exported per OTLP batch, default `64`, to avoid oversized collector requests |
| `LANGFUSE_PUBLIC_KEY` | compatibility path for older Langfuse tracing auto-configuration |
| `LANGFUSE_SECRET_KEY` | Langfuse secret |
| `LANGFUSE_BASE_URL` | Langfuse base URL |
diff --git a/docs-site/content/docs/references/environment-variables.mdx b/docs-site/content/docs/references/environment-variables.mdx
index c0eef32c..e024105f 100644
--- a/docs-site/content/docs/references/environment-variables.mdx
+++ b/docs-site/content/docs/references/environment-variables.mdx
@@ -57,6 +57,20 @@ OPENAI_MODEL_NAME=my-model
| `KSADK_UI_BUNDLE_PATH` | 自定义 UI 静态 bundle 相对项目路径;本地默认自动探测 `research-ui/dist`(0.6.7) |
| `KSYUN_REGION` | 云端 action 和部分 SDK client 使用的区域 |
+## 云账号身份解析
+
+| 变量 | 用途 |
+| --- | --- |
+| `KSYUN_IAM_ENDPOINT` | 可选:覆盖 IAM 公网 endpoint;未配置时使用 `iam.api.ksyun.com` |
+| `KSYUN_IAM_INTRANET_URL` | 可选:覆盖 IAM 内网 endpoint fallback;未配置时使用默认 `iam.inner.api.ksyun.com` |
+| `IAM_INTRANET_URL` | `KSYUN_IAM_INTRANET_URL` 的兼容别名 |
+
+
+ 当公网 IAM 返回“inner account can only access through intranet”这类错误时,
+ 运行时会尝试内网 endpoint。外部环境通常不会命中该分支;内部环境如需覆盖地址,
+ 可设置 `KSYUN_IAM_INTRANET_URL` 或 `IAM_INTRANET_URL`。
+
+
## 会话存储
| 变量 | 用途 |
@@ -269,6 +283,7 @@ KSADK_MCP_SERVERS='[{"name":"docs","url":"http://127.0.0.1:9000/mcp"}]'
| `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | traces 专用 OTLP HTTP endpoint,优先于通用 endpoint |
| `OTEL_EXPORTER_OTLP_TRACES_PROTOCOL` | traces 专用 OTLP protocol,优先于通用 protocol |
| `OTEL_EXPORTER_OTLP_TRACES_HEADERS` | traces 专用 OTLP HTTP headers,优先于通用 headers,可能包含鉴权信息 |
+| `KSADK_OTLP_MAX_EXPORT_BATCH_SIZE` | OTLP 单次 export 最大 span 数,默认 `64`,用于避免 collector 请求过大 |
| `LANGFUSE_PUBLIC_KEY` | 兼容旧 Langfuse tracing 自动配置 |
| `LANGFUSE_SECRET_KEY` | Langfuse secret |
| `LANGFUSE_BASE_URL` | Langfuse base URL |
diff --git a/docs-site/content/docs/references/openai-compatible-api.en.mdx b/docs-site/content/docs/references/openai-compatible-api.en.mdx
index e1777131..b1ca8036 100644
--- a/docs-site/content/docs/references/openai-compatible-api.en.mdx
+++ b/docs-site/content/docs/references/openai-compatible-api.en.mdx
@@ -118,8 +118,8 @@ of KsADK extensions:
| `status` | compatible | `completed` / `failed` / `incomplete` |
| `model` | compatible | model or agent used for the request |
| `output` | compatible | output item, usually an assistant message |
-| `metadata` | compatible | caller metadata |
-| `usage` | compatible-shaped | usage info when available |
+| `metadata` | compatible | caller metadata; KsADK 0.6.9+ adds `metadata.last_usage` when available |
+| `usage` | compatible-shaped | per-response accumulated usage when available, used for the real cost of this response |
| `output_text` | KsADK extension | convenient concatenated text |
| `session_id` | KsADK extension | local session id |
@@ -139,6 +139,20 @@ of KsADK extensions:
]
}
],
+ "usage": {
+ "input_tokens": 1250,
+ "output_tokens": 180,
+ "total_tokens": 1430,
+ "input_token_details": {"cached": 900}
+ },
+ "metadata": {
+ "last_usage": {
+ "input_tokens": 980,
+ "output_tokens": 180,
+ "total_tokens": 1160,
+ "input_token_details": {"cached": 700}
+ }
+ },
"output_text": "This agent can...",
"session_id": "local-demo-session"
}
@@ -147,6 +161,14 @@ of KsADK extensions:
Consumers aiming for broad compatibility should read `output` first and treat
`output_text` as a convenience field.
+
+ `usage` keeps the OpenAI-style accumulated usage for this response; if an
+ agent loop calls the model multiple times, those calls are summed. The
+ KsADK-specific `metadata.last_usage` is the final model-call usage snapshot,
+ used by higher layers to compute current context-window occupancy. It is not a
+ cumulative session total.
+
+
diff --git a/docs-site/content/docs/references/openai-compatible-api.mdx b/docs-site/content/docs/references/openai-compatible-api.mdx
index ccb6ee07..358fb1e1 100644
--- a/docs-site/content/docs/references/openai-compatible-api.mdx
+++ b/docs-site/content/docs/references/openai-compatible-api.mdx
@@ -113,8 +113,8 @@ checkpoint resume 与 approval payload。
| `status` | compatible | `completed` / `failed` / `incomplete` |
| `model` | compatible | 本次请求使用的模型或 Agent |
| `output` | compatible | output item,通常是 assistant message |
-| `metadata` | compatible | 调用方 metadata |
-| `usage` | compatible-shaped | 可用时的 usage 信息 |
+| `metadata` | compatible | 调用方 metadata;KsADK 0.6.9+ 会在可用时追加 `metadata.last_usage` |
+| `usage` | compatible-shaped | 可用时的本轮累计 usage,用于统计本次响应真实消耗 |
| `output_text` | KsADK 扩展 | 拼接后的便捷文本 |
| `session_id` | KsADK 扩展 | 本地 session id |
@@ -134,6 +134,20 @@ checkpoint resume 与 approval payload。
]
}
],
+ "usage": {
+ "input_tokens": 1250,
+ "output_tokens": 180,
+ "total_tokens": 1430,
+ "input_token_details": {"cached": 900}
+ },
+ "metadata": {
+ "last_usage": {
+ "input_tokens": 980,
+ "output_tokens": 180,
+ "total_tokens": 1160,
+ "input_token_details": {"cached": 700}
+ }
+ },
"output_text": "This agent can...",
"session_id": "local-demo-session"
}
@@ -141,6 +155,12 @@ checkpoint resume 与 approval payload。
追求广泛兼容的消费者应优先读取 `output`,把 `output_text` 视为便捷字段。
+
+ `usage` 保持 OpenAI 风格的本轮累计 usage(agent loop 内多次 LLM 调用会累加)。
+ `metadata.last_usage` 是 KsADK 扩展,表示最后一次 LLM 调用的 usage 快照,用于
+ 上层计算当前上下文窗口占用;它不是会话累计值。
+
+
diff --git a/docs-site/content/docs/references/remote-runtime-api.en.mdx b/docs-site/content/docs/references/remote-runtime-api.en.mdx
index b4273c42..b4a01897 100644
--- a/docs-site/content/docs/references/remote-runtime-api.en.mdx
+++ b/docs-site/content/docs/references/remote-runtime-api.en.mdx
@@ -117,9 +117,9 @@ All action paths live under `POST /agentengine/api/v1/*` (download surfaces are
| Group | Purpose | Actions |
| --- | --- | --- |
-| Sessions | Create, get, list, delete sessions | `CreateSession`, `GetSession`, `ListSessions`, `DeleteSession` |
+| Sessions | Create, get, list, delete sessions, read projected chat messages | `CreateSession`, `GetSession`, `ListSessions`, `ListSessionMessages`, `DeleteSession` |
| Runs | Invoke or cancel a run, subscribe to events | `RunAgent`, `SubscribeRunEvents`, `CancelRun` |
-| Events | List historical session events | `ListSessionEvents` |
+| Events | List historical session events with incremental resume and older-page cursors | `ListSessionEvents` |
| Files | Upload, download attachments, workspace files, export archive | `UploadFile`, `AttachmentContent`, `ListWorkspaceFiles`, `AddWorkspaceFile`, `DeleteWorkspaceFile`, `GetWorkspaceFileContent`, `ExportWorkspaceZip` |
| Models | List the available model catalog | `ListAgentModels` |
| UI Bootstrap | Bootstrap metadata for UI rendering and capability discovery | `GetAgentUiBootstrap` |
@@ -136,7 +136,7 @@ All action paths live under `POST /agentengine/api/v1/*` (download surfaces are
1. **Probe capabilities**: call `GetAgentUiBootstrap` first to check `Capabilities.WorkspaceFiles`, `Capabilities.ResumeRun`, etc.
2. **Create a session**: `CreateSession`, take `Session.SessionId`.
-3. **Pull history**: `ListSessions` / `ListSessionEvents` to render the sidebar and timeline.
+3. **Pull history**: `ListSessions` for the sidebar and `ListSessionMessages` for chat history; use `ListSessionEvents` directly only for debugging or raw timelines.
4. **Start a run**: `RunAgent` (foreground or background), subscribe to progress via `SubscribeRunEvents`.
5. **Reclaim a session**: `DeleteSession` to release persisted records.
@@ -185,6 +185,12 @@ Creates (or reuses) a session. When `SessionId` is omitted, the runtime generate
"LastPrompt": "",
"ActiveInvocationId": "",
"ActiveRunStatus": "",
+ "ActiveRunMode": "unknown",
+ "ActiveRunTrigger": "unknown",
+ "ActiveRunUpdatedAt": "",
+ "Model": null,
+ "ContextUsage": null,
+ "TokenUsage": null,
"State": {},
"CreatedAt": 1719900000.0,
"UpdatedAt": 1719900000.0,
@@ -202,7 +208,12 @@ Creates (or reuses) a session. When `SessionId` is omitted, the runtime generate
| `Title` / `TitleSource` | Session title and its source (`fallback_first_prompt`, `heuristic`, etc.) |
| `Summary` | Runtime-maintained session summary |
| `FirstPrompt` / `LastPrompt` | Truncated first/last user message preview |
-| `ActiveInvocationId` / `ActiveRunStatus` | Active run invocation and status for this session |
+| `ActiveInvocationId` / `ActiveRunStatus` | Active run invocation and status for this session; stale orphaned active runs are read as `interrupted` |
+| `ActiveRunMode` / `ActiveRunTrigger` | 0.6.9+ two independent run dimensions: `background` / `foreground` / `unknown` and `new_run` / `checkpoint_resume` / `approval_resume` / `unknown` |
+| `ActiveRunUpdatedAt` | 0.6.9+ timestamp from `active_run` itself, mainly for diagnostics; orphan detection uses the session `UpdatedAt` heartbeat |
+| `Model` | Model metadata from the most recent run when available |
+| `ContextUsage` | 0.6.9+ latest-turn context-window usage: `used_tokens`, `cached_tokens`, `context_window_tokens`, `percent` |
+| `TokenUsage` | 0.6.9+ cumulative session token usage: `input_tokens`, `output_tokens`, `total_tokens`, `turns`, `last_response_id`, and detail fields |
| `State` | Session state dict (sanitized) |
| `CreatedAt` / `UpdatedAt` / `Version` | Timestamps and version |
| `Continuity` | Optional: continuity info from the runner adapter |
@@ -632,7 +643,9 @@ Request cancellation of an in-flight run (both detached stream and runner channe
#### ListSessionEvents
-Paginated list of historical events for a session, offset-based pagination.
+Paginated list of historical events for a session. Without a seq cursor,
+`Offset` / `Limit` page backward from the latest event window. `AfterSeqId` is
+for reconnect/incremental reads; `BeforeSeqId` is for loading older history.
@@ -644,6 +657,8 @@ Paginated list of historical events for a session, offset-based pagination.
| `SessionId` | string | yes | Target session id |
| `Offset` | int | no | 0-based offset, default `0` |
| `Limit` | int | no | Page size, min `1` |
+| `AfterSeqId` | int | no | 0.6.9+ return events with `SeqId > AfterSeqId` for reconnect catch-up |
+| `BeforeSeqId` | int | no | 0.6.9+ return events with `SeqId < BeforeSeqId` for older pages |
```json title="request.json"
{ "SessionId": "local-demo-session", "Offset": 0, "Limit": 50 }
@@ -662,7 +677,9 @@ Paginated list of historical events for a session, offset-based pagination.
"Events": [ /* event payload, same as SubscribeRunEvents */ ],
"Total": 128,
"Offset": 0,
- "Limit": 50
+ "Limit": 50,
+ "AfterSeqId": null,
+ "BeforeSeqId": null
}
}
```
@@ -674,6 +691,103 @@ Pagination fields:
| `Total` | Total event count for this session |
| `Offset` | Current offset (defaults to `0`) |
| `Limit` | Current page size (falls back to the returned count when not sent) |
+| `AfterSeqId` / `BeforeSeqId` | Echoed seq cursors; they move in opposite directions and should not be mixed |
+
+
+
+
+#### ListSessionMessages
+
+Project the raw event log into chat messages that the Web UI can render
+directly. The server handles assistant snapshot de-duplication, reasoning
+merging, tool / approval pairing, and attachment normalization so clients do not
+need to reconstruct messages from `ListSessionEvents`.
+
+
+
+
+`POST /agentengine/api/v1/ListSessionMessages`:
+
+| Field | Type | Required | Notes |
+| --- | --- | --- | --- |
+| `AgentId` | string | no | When present, the server fetches runtime `ListSessionEvents` and projects them in one place; when omitted, hosted reads the local session store |
+| `SessionId` | string | yes | Target session id |
+| `Limit` | int | no | Maximum messages to return, default `50`, range `1..200` |
+| `AfterSeqId` | int | no | Return incremental messages with `SeqId > AfterSeqId`; used for reconnect catch-up and not truncated to `Limit` |
+| `BeforeSeqId` | int | no | Return the latest page before `SeqId < BeforeSeqId`; used for loading older history |
+| `IncludeReasoning` | bool | no | Include `Reasoning[]` inside assistant messages |
+| `IncludeToolEvents` | bool | no | Include `ToolEvents[]` inside assistant messages |
+| `IncludeAttachments` | bool | no | Include `Attachments[]` on user messages, default `true` |
+
+```json title="request.json"
+{
+ "SessionId": "local-demo-session",
+ "Limit": 50,
+ "IncludeAttachments": true
+}
+```
+
+
+
+
+```json title="response.json"
+{
+ "Code": 0,
+ "Message": "Success",
+ "RequestId": "req_abc123",
+ "Action": "ListSessionMessages",
+ "Data": {
+ "SessionId": "local-demo-session",
+ "Messages": [
+ {
+ "MessageId": "evt_user_1",
+ "Role": "user",
+ "Content": {"text": "Summarize this file"},
+ "SeqId": 21,
+ "InvocationId": "inv_demo_001",
+ "Timestamp": 1719900001.0,
+ "Attachments": [
+ {
+ "file_uri": "ae-upload://abc123_report.pdf",
+ "name": "report.pdf",
+ "mime": "application/pdf",
+ "size": 204800,
+ "url": "/agentengine/api/v1/AttachmentContent?FileUri=ae-upload%3A%2F%2Fabc123_report.pdf",
+ "is_image": false
+ }
+ ]
+ },
+ {
+ "MessageId": "evt_assistant_1",
+ "Role": "assistant",
+ "Content": {"text": "Here is the summary..."},
+ "SeqId": 24,
+ "InvocationId": "inv_demo_001",
+ "ResponseId": "resp_a1b2c3",
+ "TraceId": "trace_abc",
+ "RootSpanId": "span_root"
+ }
+ ],
+ "LatestSeqId": 24,
+ "HasMore": true,
+ "NextCursor": 20
+ }
+}
+```
+
+Pagination fields:
+
+| Field | Notes |
+| --- | --- |
+| `LatestSeqId` | `SeqId` of the last message in the page; pass it to `SubscribeRunEvents(AfterSeqId=...)` as the reconnect starting point |
+| `HasMore` | Whether older messages remain |
+| `NextCursor` | Older-page cursor; pass it as `BeforeSeqId=NextCursor` on the next request |
+
+
+ `ListSessionMessages` only projects historical messages. To decide whether an
+ SSE stream should reconnect, read `GetSession.ActiveRunStatus` /
+ `ActiveInvocationId`, then call `SubscribeRunEvents(AfterSeqId=LatestSeqId)`.
+
diff --git a/docs-site/content/docs/references/remote-runtime-api.mdx b/docs-site/content/docs/references/remote-runtime-api.mdx
index 3281b653..2f5e1304 100644
--- a/docs-site/content/docs/references/remote-runtime-api.mdx
+++ b/docs-site/content/docs/references/remote-runtime-api.mdx
@@ -112,9 +112,9 @@ cancel 和 model listing。公开 API 客户端优先使用 OpenAI 兼容的 `/v
| 分组 | 用途 | Actions |
| --- | --- | --- |
-| 会话 Sessions | 创建、获取、列出、删除会话 | `CreateSession`、`GetSession`、`ListSessions`、`DeleteSession` |
+| 会话 Sessions | 创建、获取、列出、删除会话,读取投影后的聊天消息 | `CreateSession`、`GetSession`、`ListSessions`、`ListSessionMessages`、`DeleteSession` |
| 运行 Runs | 调用或取消 Agent run、订阅事件 | `RunAgent`、`SubscribeRunEvents`、`CancelRun` |
-| 事件 Events | 列出 session 历史 events | `ListSessionEvents` |
+| 事件 Events | 列出 session 历史 events,支持增量续订与向前翻页 | `ListSessionEvents` |
| 文件 Files | 上传、下载附件、workspace 文件、导出 archive | `UploadFile`、`AttachmentContent`、`ListWorkspaceFiles`、`AddWorkspaceFile`、`DeleteWorkspaceFile`、`GetWorkspaceFileContent`、`ExportWorkspaceZip` |
| 模型 Models | 列出可用模型目录 | `ListAgentModels` |
| UI Bootstrap | 获取 UI 渲染与能力探测所需 metadata | `GetAgentUiBootstrap` |
@@ -130,7 +130,7 @@ cancel 和 model listing。公开 API 客户端优先使用 OpenAI 兼容的 `/v
1. **探测能力**:先调用 `GetAgentUiBootstrap`,确认 `Capabilities.WorkspaceFiles`、`Capabilities.ResumeRun` 等。
2. **创建会话**:`CreateSession`,拿到 `Session.SessionId`。
-3. **拉取历史**:`ListSessions` / `ListSessionEvents` 渲染侧栏与时间线。
+3. **拉取历史**:`ListSessions` 渲染侧栏,`ListSessionMessages` 渲染聊天历史;只有调试或原始时间线才直接读 `ListSessionEvents`。
4. **发起运行**:`RunAgent`(前台或后台),用 `SubscribeRunEvents` 续订进度。
5. **回收会话**:`DeleteSession` 释放持久化记录。
@@ -179,6 +179,12 @@ cancel 和 model listing。公开 API 客户端优先使用 OpenAI 兼容的 `/v
"LastPrompt": "",
"ActiveInvocationId": "",
"ActiveRunStatus": "",
+ "ActiveRunMode": "unknown",
+ "ActiveRunTrigger": "unknown",
+ "ActiveRunUpdatedAt": "",
+ "Model": null,
+ "ContextUsage": null,
+ "TokenUsage": null,
"State": {},
"CreatedAt": 1719900000.0,
"UpdatedAt": 1719900000.0,
@@ -196,7 +202,12 @@ cancel 和 model listing。公开 API 客户端优先使用 OpenAI 兼容的 `/v
| `Title` / `TitleSource` | 会话标题及其来源(`fallback_first_prompt`、`heuristic` 等) |
| `Summary` | 运行时维护的会话摘要 |
| `FirstPrompt` / `LastPrompt` | 截断后的首/末用户消息预览 |
-| `ActiveInvocationId` / `ActiveRunStatus` | 当前会话活跃 run 的 invocation 与状态 |
+| `ActiveInvocationId` / `ActiveRunStatus` | 当前会话活跃 run 的 invocation 与状态;读侧会把超时孤儿活跃态兜底显示为 `interrupted` |
+| `ActiveRunMode` / `ActiveRunTrigger` | 0.6.9+ run 状态双维度:`background` / `foreground` / `unknown` 与 `new_run` / `checkpoint_resume` / `approval_resume` / `unknown` |
+| `ActiveRunUpdatedAt` | 0.6.9+ active_run 自身更新时间,主要用于诊断;孤儿判定使用 session `UpdatedAt` 心跳 |
+| `Model` | 最近一次 run 的模型 metadata(如可用) |
+| `ContextUsage` | 0.6.9+ 最近一轮上下文窗口占用:`used_tokens`、`cached_tokens`、`context_window_tokens`、`percent` |
+| `TokenUsage` | 0.6.9+ 会话累计 token 消耗:`input_tokens`、`output_tokens`、`total_tokens`、`turns`、`last_response_id` 与明细字段 |
| `State` | 会话状态 dict(已脱敏) |
| `CreatedAt` / `UpdatedAt` / `Version` | 时间戳与版本号 |
| `Continuity` | 可选:runner 适配器描述的连续性信息 |
@@ -619,7 +630,8 @@ data: [DONE]
#### ListSessionEvents
-分页列出某 session 的历史 events,offset-based 分页。
+分页列出某 session 的历史 events。无游标时 `Offset` / `Limit` 表示从最新事件窗口向前分页;
+`AfterSeqId` 用于断线后增量读取,`BeforeSeqId` 用于向上翻更早历史。
@@ -631,6 +643,8 @@ data: [DONE]
| `SessionId` | string | 是 | 目标会话 id |
| `Offset` | int | 否 | 0-based 偏移,缺省 `0` |
| `Limit` | int | 否 | 每页大小,最小 `1` |
+| `AfterSeqId` | int | 否 | 0.6.9+ 返回 `SeqId > AfterSeqId` 的事件,用于重连补齐 |
+| `BeforeSeqId` | int | 否 | 0.6.9+ 返回 `SeqId < BeforeSeqId` 的事件,用于向前翻页 |
```json title="request.json"
{ "SessionId": "local-demo-session", "Offset": 0, "Limit": 50 }
@@ -649,7 +663,9 @@ data: [DONE]
"Events": [ /* event payload,同 SubscribeRunEvents */ ],
"Total": 128,
"Offset": 0,
- "Limit": 50
+ "Limit": 50,
+ "AfterSeqId": null,
+ "BeforeSeqId": null
}
}
```
@@ -661,6 +677,102 @@ data: [DONE]
| `Total` | 该 session 的事件总数 |
| `Offset` | 当前偏移(缺省回填 `0`) |
| `Limit` | 当前页大小(未传时回填为实际返回数) |
+| `AfterSeqId` / `BeforeSeqId` | 请求中的 seq 游标原样回填;二者语义相反,不应混用 |
+
+
+
+
+#### ListSessionMessages
+
+把原始 event log 投影为前端可直接渲染的聊天消息列表。它会在服务端完成
+assistant snapshot 去重、reasoning 归并、tool / approval 配对和附件规范化,避免客户端
+从 `ListSessionEvents` 自行筛消息。
+
+
+
+
+`POST /agentengine/api/v1/ListSessionMessages`:
+
+| 字段 | 类型 | 必填 | 说明 |
+| --- | --- | --- | --- |
+| `AgentId` | string | 否 | 传入时 server 调 runtime `ListSessionEvents` 再统一投影;不传则 hosted 直连 session store |
+| `SessionId` | string | 是 | 目标会话 id |
+| `Limit` | int | 否 | 返回消息条数上限,默认 `50`,范围 `1..200` |
+| `AfterSeqId` | int | 否 | 返回 `SeqId > AfterSeqId` 的增量消息;用于重连补齐,不截断到 `Limit` |
+| `BeforeSeqId` | int | 否 | 返回 `SeqId < BeforeSeqId` 之前的最新一页消息;用于向上加载更早历史 |
+| `IncludeReasoning` | bool | 否 | 是否在 assistant 消息中返回 `Reasoning[]` |
+| `IncludeToolEvents` | bool | 否 | 是否在 assistant 消息中返回 `ToolEvents[]` |
+| `IncludeAttachments` | bool | 否 | 是否在 user 消息中返回 `Attachments[]`,默认 `true` |
+
+```json title="request.json"
+{
+ "SessionId": "local-demo-session",
+ "Limit": 50,
+ "IncludeAttachments": true
+}
+```
+
+
+
+
+```json title="response.json"
+{
+ "Code": 0,
+ "Message": "Success",
+ "RequestId": "req_abc123",
+ "Action": "ListSessionMessages",
+ "Data": {
+ "SessionId": "local-demo-session",
+ "Messages": [
+ {
+ "MessageId": "evt_user_1",
+ "Role": "user",
+ "Content": {"text": "总结这个文件"},
+ "SeqId": 21,
+ "InvocationId": "inv_demo_001",
+ "Timestamp": 1719900001.0,
+ "Attachments": [
+ {
+ "file_uri": "ae-upload://abc123_report.pdf",
+ "name": "report.pdf",
+ "mime": "application/pdf",
+ "size": 204800,
+ "url": "/agentengine/api/v1/AttachmentContent?FileUri=ae-upload%3A%2F%2Fabc123_report.pdf",
+ "is_image": false
+ }
+ ]
+ },
+ {
+ "MessageId": "evt_assistant_1",
+ "Role": "assistant",
+ "Content": {"text": "文件摘要如下..."},
+ "SeqId": 24,
+ "InvocationId": "inv_demo_001",
+ "ResponseId": "resp_a1b2c3",
+ "TraceId": "trace_abc",
+ "RootSpanId": "span_root"
+ }
+ ],
+ "LatestSeqId": 24,
+ "HasMore": true,
+ "NextCursor": 20
+ }
+}
+```
+
+分页语义:
+
+| 字段 | 说明 |
+| --- | --- |
+| `LatestSeqId` | 本页最后一条消息的 `SeqId`;可作为 `SubscribeRunEvents(AfterSeqId=...)` 的续订起点 |
+| `HasMore` | 是否还有更早消息可取 |
+| `NextCursor` | 向前翻页游标;下一次请求传 `BeforeSeqId=NextCursor` |
+
+
+ `ListSessionMessages` 只负责历史消息投影。是否需要重连运行中的 SSE,应读取
+ `GetSession.ActiveRunStatus` / `ActiveInvocationId`,再用
+ `SubscribeRunEvents(AfterSeqId=LatestSeqId)` 续订。
+
diff --git a/docs-site/content/docs/references/security-boundaries.en.mdx b/docs-site/content/docs/references/security-boundaries.en.mdx
index 15a2200c..9b25d1b1 100644
--- a/docs-site/content/docs/references/security-boundaries.en.mdx
+++ b/docs-site/content/docs/references/security-boundaries.en.mdx
@@ -14,7 +14,7 @@ The public repository is expected to contain:
- Python SDK and CLI source.
- local runtime adapters.
- generated static assets required by `agentengine web`.
-- curated public docs under `public-docs/`.
+- curated public docs under `docs-site/`.
- public CI, release checks, and contribution policy.
It must not contain:
diff --git a/docs-site/content/docs/references/troubleshooting.en.mdx b/docs-site/content/docs/references/troubleshooting.en.mdx
index 3cc3bac2..74364718 100644
--- a/docs-site/content/docs/references/troubleshooting.en.mdx
+++ b/docs-site/content/docs/references/troubleshooting.en.mdx
@@ -234,14 +234,14 @@ short:
Run:
```bash
-uv run --extra dev python -m mkdocs build --strict
+make docs-site-build
```
Common causes:
- broken relative links.
-- page added to `nav` but not created.
-- duplicate Markdown extension entries.
+- page added to the docs tree but not linked correctly.
+- invalid MDX or duplicated frontmatter fields.
- generated files under `site/` accidentally committed.
- docs referencing private files excluded from the public repository.
diff --git a/docs-site/next.config.mjs b/docs-site/next.config.mjs
index 1999fe94..d332fb89 100644
--- a/docs-site/next.config.mjs
+++ b/docs-site/next.config.mjs
@@ -2,7 +2,7 @@ import { createMDX } from 'fumadocs-mdx/next';
const withMDX = createMDX();
-// For GitHub Pages project sites, set NEXT_PUBLIC_BASE_PATH=/veadk-python at
+// For GitHub Pages project sites, set NEXT_PUBLIC_BASE_PATH=/ksadk-python at
// build time. Left empty for local dev so the site is served from `/`.
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || '';
diff --git a/docs-site/scripts/build-static.mjs b/docs-site/scripts/build-static.mjs
index 5b92dd66..9c6af6a6 100644
--- a/docs-site/scripts/build-static.mjs
+++ b/docs-site/scripts/build-static.mjs
@@ -6,7 +6,7 @@
// export build, restore it, and write a `.nojekyll` file so GitHub Pages serves
// the `_next/` directory.
//
-// Usage: NEXT_PUBLIC_BASE_PATH=/veadk-python node scripts/build-static.mjs
+// Usage: NEXT_PUBLIC_BASE_PATH=/ksadk-python node scripts/build-static.mjs
import { execSync } from 'node:child_process';
import { existsSync, renameSync, writeFileSync, mkdirSync } from 'node:fs';
diff --git a/docs-site/source.config.ts b/docs-site/source.config.ts
index 886f822a..d20a2164 100644
--- a/docs-site/source.config.ts
+++ b/docs-site/source.config.ts
@@ -20,7 +20,7 @@ export const docs = defineDocs({
},
});
-// GitHub Pages serves under a base path (e.g. /veadk-python). Next prefixes
+// GitHub Pages serves under a base path (e.g. /ksadk-python). Next prefixes
// `_next/` assets and next/link hrefs automatically, but NOT raw
// from markdown. Prepend the base path to absolute image sources at build time.
const BASE_PATH = process.env.NEXT_PUBLIC_BASE_PATH || '';
diff --git "a/docs/guides/Agent \345\274\200\345\217\221\350\200\205\344\270\212\344\270\213\346\226\207\346\216\245\345\205\245\346\214\207\345\215\227.md" "b/docs/guides/Agent \345\274\200\345\217\221\350\200\205\344\270\212\344\270\213\346\226\207\346\216\245\345\205\245\346\214\207\345\215\227.md"
deleted file mode 100644
index 1910394f..00000000
--- "a/docs/guides/Agent \345\274\200\345\217\221\350\200\205\344\270\212\344\270\213\346\226\207\346\216\245\345\205\245\346\214\207\345\215\227.md"
+++ /dev/null
@@ -1,567 +0,0 @@
-# Agent 开发者上下文接入指南
-
-本文档面向使用 `ksadk-python` 开发 Agent 业务逻辑的开发者,重点说明:
-
-- 通过 `/v1/responses`、Hosted UI 或 `RunAgent` 调用时,运行时会把哪些上下文喂给 Agent
-- LangGraph、LangChain、ADK 这三类主路径里,开发者应该从哪里拿:
- - 当前输入
- - 多轮历史
- - 图片 / 附件上下文
- - OCR / 文档抽取结果
- - 平台上下文
- - 知识库与长期记忆上下文
- - 模型能力元数据
-- 什么时候应该解析 `HumanMessage`,什么时候不应该
-
-本文档不讨论云端部署、权限治理或外部产品逻辑;只聚焦 agent 业务代码如何接上下文。
-
-## 1. 先给结论
-
-如果你是 Agent 业务开发者,**最推荐的接入方式不是直接拆 `messages[-1]`,而是使用 `ksadk_prepare_state()` / `ksadk_prepare_input()` 明确接收平台传入的标准上下文。**
-
-优先级建议:
-
-1. `LangGraph`
- - 自定义 `ksadk_prepare_state(payload, session_context)`
- - 具体项目结构、interrupt / resume、Responses approval 写法见 [LangGraph开发最佳实践](./LangGraph开发最佳实践.md)
-2. `LangChain`
- - 自定义 `ksadk_prepare_input(payload, session_context)`
-3. `ADK`
- - 使用 runner 已构造好的 `Part` / session 能力
-4. 只有在你确实做 messages-native agent 时,再直接解析 `HumanMessage`
-
-## 2. 运行时到底会给 Agent 什么
-
-进入 runner 前,`ksadk` 会把一次请求整理成标准运行输入。核心字段包括:
-
-- `input`
-- `history`
-- `input_content`
-- `input_messages`
-- `input_parts`
-- `attachments`
-- `attachment_results`
-- `current_attachments`
-- `current_attachment_results`
-- `has_current_files`
-- `model`
-- `model_metadata`
-- `platform_context`
-- `kb_context`
-- `memory_context`
-- `instructions`
-
-这些字段不是每个 framework 都以同样方式消费,但它们是当前平台提供给 Agent 的标准上下文来源。
-`input_content` / `input_messages` 是 runner 默认 canonical 输入,使用 OpenAI Responses 风格 content blocks;`input_parts` 是 legacy/internal normalized parts,用于兼容已有 runner。`attachments`、`current_attachments`、`has_current_files` 等是 KsADK runner payload 扩展上下文,不属于 OpenAI 官方请求或响应字段。
-
-对外协议不混写:`/v1/responses` 接收 OpenAI Responses 风格 `input_text / input_image / input_file`;`/v1/chat/completions` 保持 Chat Completions 风格 `messages`,官方多模态块优先使用 `text / image_url`。进入 runner 前,两条入口都会投影到同一套 `input_content / input_messages`,并额外生成兼容用 `input_parts`。KsADK 兼容扩展 `inlineData / fileData` 仍可用于老客户端,但不把它们声明成 OpenAI Chat 官方字段。
-
-### 2.1 字段说明
-
-| 字段 | 类型 | 说明 |
-| --- | --- | --- |
-| `input` | `str` | 当前这一轮的标准文本输入 |
-| `history` | `list[dict]` | 当前多轮会话历史,已经过 transcript 投影 / compaction |
-| `input_content` | `list[dict]` | 当前 user turn 的 OpenAI Responses content blocks,例如 `input_text / input_image / input_file` |
-| `input_messages` | `list[dict]` | OpenAI Responses 风格 message/input items;需要完整 role/content 结构的 runner 优先读这里 |
-| `input_parts` | `list[dict]` | legacy/internal 归一化片段,保留 `text / inlineData / fileData`;用于兼容已有 runner,不作为 OpenAI 官方协议字段暴露 |
-| `invocation_id` | `str` | 当前运行 ID。0.6.5 起写入 runner payload,用于 transcript 分组、`SubscribeRunEvents` 续订、`CancelRun` 以及 trace 串联 |
-| `attachments` | `list[dict]` | 当前会话最近有效附件上下文,兼容历史 fallback,不应用来判断本轮是否传文件 |
-| `attachment_results` | `list[dict]` | 最近有效附件理解结果,例如 OCR / 文本提取 |
-| `current_attachments` | `list[dict]` | 当前最新 user turn 解析出的附件列表,不包含历史 fallback |
-| `current_attachment_results` | `list[dict]` | 当前最新 user turn 的附件理解结果 |
-| `has_current_files` | `bool` | 当前最新 user turn 是否包含归一化后的 `inlineData` 或 `fileData`,包括 OpenAI `input_image / input_file` |
-| `model` | `str` | 当前请求显式使用的模型名 |
-| `model_metadata` | `dict` | 模型元数据,可能来自请求显式传入,也可能来自上游 `/v1/models` 自动解析 |
-| `platform_context` | `dict` | 平台上下文,含 `agent_id / user_id / session_id / account_id` |
-| `kb_context` | `dict` | 知识库召回构造的上下文 |
-| `memory_context` | `dict` | 长期记忆构造的上下文 |
-| `instructions` | `str` | 本轮额外系统/开发者指令 |
-
-## 3. 多轮会话历史是怎么来的
-
-平台不是要求前端每次把完整对话都重传回来,而是通过:
-
-- `session_id`
-- conversation event store
-- transcript 投影
-- 必要时 compaction
-
-来恢复当前会话历史。
-
-你在 Agent 业务里看到的 `history`,通常已经不是“原始数据库所有消息”,而是:
-
-- 更早历史被压缩成 summary checkpoint
-- 最近若干轮保持原始 user / assistant 消息
-- 特殊事件(tool call / tool result / approval / attachment)保留成可解释文本占位
-
-所以:
-
-- 如果你只需要“语义历史”,用 `history`
-- 如果你需要 OpenAI Responses 风格当前输入结构,优先看 `input_content / input_messages`
-- 如果你需要兼容老 runner 的内部结构,再看 `input_parts`
-- 如果你需要附件理解结果,看 `current_attachments / current_attachment_results / attachments / attachment_results`
-
-## 4. 图片 / 附件上下文怎么来的
-
-### 4.1 `attachments`
-
-`attachments` 是当前会话最近有效附件上下文。它可能来自当前轮,也可能来自同一 session 中最近一次带附件的 user turn,主要用于“继续围绕上个附件追问”的兼容场景。
-
-如果业务需要判断“本次问答是否带文件”,不要看 `attachments`,直接看 `has_current_files`;如果要当前轮附件列表,看 `current_attachments`。
-
-典型字段:
-
-```python
-{
- "display_name": "diagram.png",
- "mime_type": "image/png",
- "transport": "reference", # 或 "inline"
- "file_uri": "ksadk-upload://...",
- "data": "", # 仅 transport="inline" 时存在
- "size_bytes": 1356,
- "storage_path": "/tmp/.../diagram.png",
- "is_text": False,
-}
-```
-
-说明:
-
-- `transport="inline"`:调用方直接传了 `inlineData`
-- `transport="reference"`:调用方先 `UploadFile`,再传 `fileData.fileUri`
-
-### 4.2 当前轮文件判断
-
-推荐判断方式:
-
-```python
-has_file = bool(payload.get("has_current_files"))
-current_files = payload.get("current_attachments", [])
-```
-
-如果需要兼容旧版本 KsADK,可以退回检查 `input_parts`:
-
-```python
-has_file = any(
- isinstance(part, dict)
- and (part.get("inlineData") is not None or part.get("fileData") is not None)
- for part in payload.get("input_parts") or []
-)
-```
-
-### 4.3 `attachment_results`
-
-这是平台附件理解管线产出的结果,比 `attachments` 更适合业务逻辑消费。典型字段:
-
-```python
-{
- "display_name": "diagram.png",
- "mime_type": "image/png",
- "transport": "reference",
- "file_uri": "ksadk-upload://...",
- "size_bytes": 1356,
- "kind": "image",
- "status": "ok",
- "warnings": [],
- "extraction_method": "image_ocr",
- "text_excerpt": "KIMI E2E",
- "text": "KIMI E2E",
- "image": {"ocr_engine": "rapidocr_onnxruntime"}
-}
-```
-
-推荐使用方式:
-
-- 想拿当前轮 OCR 文本:读 `current_attachment_results[*]["text"]`
-- 想支持“围绕上次附件继续追问”:读 `attachment_results[*]["text"]`
-- 想区分图片 / 文档 / 压缩包:读 `kind`
-- 想看平台有没有降级或失败:读 `status / warnings / extraction_method`
-
-## 5. 模型能力元数据怎么来的
-
-`model_metadata` 的来源优先级是:
-
-1. 请求里显式传入的 `model_metadata`
-2. runtime 用 `OPENAI_BASE_URL / OPENAI_API_KEY` 查询上游 `/v1/models`
-3. 本地默认兜底
-
-当前最值得关注的字段是:
-
-```python
-{
- "id": "kimi-k2.7-code",
- "architecture": {
- "input_modalities": ["文字", "图片", "视频"],
- "output_modalities": ["文字"]
- },
- "capabilities": {
- "multimodal_input_image": True,
- "multimodal_input_video": True,
- "multimodal_input_file": False,
- "function_calling": True,
- "structured_output": True,
- "context_caching": True
- },
- "limits": {...},
- "pricing": {...}
-}
-```
-
-!!! info "多模态默认模型来源"
- 自策略 v1 起,运行时在请求未显式指定模型时,会按平台多模态默认模型策略挑选默认模型(含图片/视频输入能力的优先模型)。`model_metadata` 仍按下方来源优先级填充,业务侧不需要自己判断"默认模型是否多模态",直接读 `capabilities.multimodal_input_*` 即可。
-
-业务代码里最常用的判断是:
-
-```python
-supports_image = bool(
- (((model_metadata or {}).get("capabilities") or {}).get("multimodal_input_image"))
-)
-```
-
-## 6. LangGraph 怎么拿上下文
-
-LangGraph 的完整开发写法已经内化到框架专属文档:
-
-- [LangGraph开发最佳实践](./LangGraph开发最佳实践.md)
-
-这里只保留平台上下文接入的核心边界:
-
-- 默认 messages-based 图可以不写 hook,运行时会自动构造 `messages` state
-- 自定义 state 图推荐显式暴露 `ksadk_prepare_state(payload, session_context)`
-- `ksadk_prepare_state` 必须在 `agentengine.yaml` 的 `entry_point` 对应模块顶层可见
-- 附件、OCR、原始输入片段优先从 `payload` 读取
-- 会话历史、平台身份、知识库、长期记忆优先从 `session_context` 读取
-- LangGraph `interrupt()` 的恢复由平台协议层判断,业务代码不需要自己猜下一轮是否要 `Command(resume=...)`
-
-最小推荐写法:
-
-```python
-def ksadk_prepare_state(payload: dict, session_context: dict) -> dict:
- if session_context.get("is_resume"):
- return payload.get("input")
-
- return {
- "query": payload["input"],
- "history": session_context["history"],
- "attachments": payload.get("attachments", []),
- "attachment_results": payload.get("attachment_results", []),
- "current_attachments": payload.get("current_attachments", []),
- "current_attachment_results": payload.get("current_attachment_results", []),
- "has_current_files": payload.get("has_current_files", False),
- "platform_context": session_context.get("platform_context"),
- "kb_context": session_context.get("kb_context"),
- "memory_context": session_context.get("memory_context"),
- "model_metadata": payload.get("model_metadata", {}),
- }
-```
-
-如果涉及 human-in-the-loop / MCP 工具审批 / `interrupt()` 断点恢复,请优先阅读 [LangGraph开发最佳实践](./LangGraph开发最佳实践.md) 的 interrupt 与 Responses approval 章节。
-
-## 7. LangChain 怎么拿上下文
-
-推荐定义:
-
-```python
-def ksadk_prepare_input(payload: dict, session_context: dict) -> dict:
- return {
- "question": payload["input"],
- "history": session_context["history"],
- "attachments": payload.get("attachments", []),
- "attachment_results": payload.get("attachment_results", []),
- "current_attachments": payload.get("current_attachments", []),
- "current_attachment_results": payload.get("current_attachment_results", []),
- "has_current_files": payload.get("has_current_files", False),
- "input_content": payload.get("input_content", []),
- "input_messages": payload.get("input_messages", []),
- "input_parts": payload.get("input_parts", []),
- "model_metadata": payload.get("model_metadata"),
- }
-```
-
-然后业务链或 runnable 自己决定:
-
-- 要不要把 `history` 变成 prompt
-- 要不要优先消费 `attachment_results[*]["text"]`
-- 要不要在支持多模态的模型上直接消费 `input_content / input_messages`
-- 是否需要兼容旧 runner 的 `input_parts`
-
-说明:
-
-- LangChain 当前不保证所有 agent 自动原生图片直通
-- 所以如果你要做稳定多模态 LangChain agent,建议自己在 hook 里显式处理
-
-## 8. ADK 怎么拿上下文
-
-ADK 路径下,平台会优先把附件转成底层 SDK 的 `Part`:
-
-- 文本 -> `types.Part(text=...)`
-- 图片 / 其他附件 -> `types.Part.from_bytes(...)`
-
-所以对支持原生多模态的 ADK 模型,图片会优先按 bytes part 进入底层 SDK。
-
-ADK 侧开发者通常更应该依赖:
-
-- ADK 自己的 session 机制
-- 当前传进来的 `new_message.parts`
-- 平台附加的 state delta
-
-## 9. 通用运行时上下文:`get_current_invocation_context()`
-
-如果你在 tool、helper 或平台公共逻辑里,希望不通过 state/hook 也能拿到当前调用上下文,可以用:
-
-```python
-from ksadk.runtime_context import get_current_invocation_context
-
-ctx = get_current_invocation_context()
-if ctx:
- print(ctx.agent_id)
- print(ctx.user_id)
- print(ctx.account_id)
- print(ctx.session_id)
- print(ctx.invocation_id)
- print(ctx.model)
- print(ctx.input_content)
- print(ctx.input_messages)
- print(ctx.attachments)
- print(ctx.attachment_results)
- print(ctx.current_attachments)
- print(ctx.has_current_files)
- print(ctx.kb_context)
- print(ctx.memory_context)
- print(ctx.model_metadata)
-```
-
-`PlatformInvocationContext` 当前包含:
-
-- `agent_id`
-- `user_id`
-- `account_id`(默认空串 `""`,未携带平台身份时为空)
-- `session_id`
-- `invocation_id`
-- `history`
-- `input_content`
-- `input_messages`
-- `input_parts`
-- `attachments`
-- `attachment_results`
-- `current_attachments`
-- `current_attachment_results`
-- `has_current_files`
-- `runner_type`
-- `model`
-- `kb_context`
-- `memory_context`
-- `model_metadata`
-
-### 9.1 不抛异常的安全读取入口(0.6.5 新增)
-
-`get_current_invocation_context()` 在没有上下文时返回 `None`,调用方仍需自己判空。如果你只想要某个具体字段、且希望在没有上下文时拿到一个显式默认值而不是 `None`,可以用下面三个安全入口:
-
-```python
-from ksadk.runtime_context import (
- get_current_invocation_context_or_default,
- get_current_user_id,
- get_current_account_id,
-)
-
-# 无上下文时返回一个空 PlatformInvocationContext 实例,不抛异常
-ctx = get_current_invocation_context_or_default()
-
-# 无上下文或字段缺失时返回传入的 default,不抛异常
-user_id = get_current_user_id(default="")
-account_id = get_current_account_id(default="")
-```
-
-!!! tip "什么时候用安全入口"
- - 在 tool / 回调 / 后台任务里取身份字段,不希望因为没有运行上下文就中断流程
- - 写库、打日志、埋点等"best-effort"消费场景,缺失身份时用空串兜底即可
- - 需要拿到完整 ctx 做多字段消费时,优先用 `get_current_invocation_context_or_default()` 拿到一个非空实例,再按字段读
-
-!!! warning "不要用这些入口做权限决策"
- 这三个入口返回的是"尽力而为"的运行上下文,适合观测、埋点、默认参数;涉及鉴权或租户隔离的判断仍应走平台协议层显式传入的 `platform_context`,不要只依赖 `get_current_account_id()` 的默认值。
-
-### 9.2 Hosted 附件统一解析:`ae-upload://`
-
-Hosted 部署下,用户上传的附件会以 `ae-upload://` 协议的引用地址下发到 runner payload。运行时会按当前部署形态把这类引用统一解析成可消费的 `AttachmentContent`,业务代码不需要自己处理 `ae-upload://` 前缀:
-
-- 调用方先走平台 `UploadFile`,得到 `ae-upload://...` 引用
-- runner 进入前,平台把引用解析为 `AttachmentContent`(含 bytes 或本地可读路径)
-- 业务代码统一从 `current_attachments / attachments` 消费,`transport` 字段标记是 `inline` 还是 `reference`
-
-如果业务代码需要主动下载某个附件引用(例如从 `attachment_results` 拿到 `file_uri` 后再取原始字节),使用 `AttachmentContent` 下载入口:
-
-```python
-from ksadk.attachments import resolve_attachment_content
-
-# file_uri 可以是 ae-upload://... / ksadk-upload://... / https://...
-content = resolve_attachment_content(file_uri)
-# content.bytes: 原始字节
-# content.mime_type: 归一化后的 mime
-# content.display_name: 展示名
-```
-
-!!! info "公开口径"
- `ae-upload://` 是平台 Hosted 附件引用协议,具体 Host 端点不对外暴露;示例里统一用 `example.com` 占位,业务代码只认协议前缀和 `AttachmentContent` 入口。
-
-### 9.3 `ModelMetadata` 透传与多模态能力判断
-
-`model_metadata` 会在 runner payload 和 `PlatformInvocationContext` 之间透传:请求显式传入的 `model_metadata` 优先,未传入时由 runtime 查询上游 `/v1/models` 自动解析并填充。因此无论 LangGraph / LangChain / ADK 哪条路径,业务代码都可以用同一种方式判断模型多模态能力:
-
-```python
-from ksadk.runtime_context import get_current_invocation_context_or_default
-
-ctx = get_current_invocation_context_or_default()
-capabilities = (ctx.model_metadata or {}).get("capabilities") or {}
-supports_image = bool(capabilities.get("multimodal_input_image"))
-supports_video = bool(capabilities.get("multimodal_input_video"))
-supports_file = bool(capabilities.get("multimodal_input_file"))
-```
-
-多模态分流建议:
-
-- 支持图片/视频输入:优先消费 `input_content / input_messages` 中的原生多模态块
-- 不支持原生多模态:退回 `current_attachment_results[*]["text"]` 走 OCR / 文本提取降级
-- 需要判断当前轮是否真的带了文件:读 `has_current_files`,不要用 `attachments` 判断
-
-## 10. `HumanMessage` 什么时候需要自己解析
-
-只有在你明确做的是 messages-native graph / prompt pipeline 时,才建议自己拆 `HumanMessage`。
-
-### 10.1 纯文本模型
-
-```python
-HumanMessage(content="请分析这张图片\\n\\n[上传文件引用: ...]")
-```
-
-### 10.2 原生多模态模型
-
-```python
-HumanMessage(
- content=[
- {"type": "text", "text": "请分析这张图片"},
- {"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}}
- ]
-)
-```
-
-### 10.3 推荐解析方式
-
-```python
-def parse_human_message_content(content):
- result = {"texts": [], "images": []}
-
- if isinstance(content, str):
- result["texts"].append(content)
- return result
-
- if isinstance(content, list):
- for block in content:
- if not isinstance(block, dict):
- continue
- if block.get("type") == "text":
- result["texts"].append(str(block.get("text") or ""))
- elif block.get("type") == "image_url":
- result["images"].append(str((block.get("image_url") or {}).get("url") or ""))
- return result
-```
-
-但再次强调:
-
-- 如果你只是想拿图片 OCR 文本、附件摘要、平台上下文
-- 不推荐优先拆 `HumanMessage`
-- 更推荐直接用 `has_current_files / current_attachments / attachment_results / session_context`
-
-## 11. 常见接入模式
-
-### 模式 A:只关心最终文本输入
-
-适合简单问答 agent:
-
-```python
-def ksadk_prepare_state(payload: dict, session_context: dict) -> dict:
- return {"query": payload["input"]}
-```
-
-### 模式 B:同时关心附件 OCR
-
-适合简历、票据、截图理解类 agent:
-
-```python
-def ksadk_prepare_state(payload: dict, session_context: dict) -> dict:
- results = payload.get("current_attachment_results") or payload.get("attachment_results") or []
- attachment_texts = [
- item.get("text", "")
- for item in results
- if isinstance(item, dict) and item.get("text")
- ]
- return {
- "query": payload["input"],
- "attachment_texts": attachment_texts,
- }
-```
-
-### 模式 C:按模型能力分支
-
-适合既支持多模态模型、又要兼容纯文本模型的 agent:
-
-```python
-def ksadk_prepare_state(payload: dict, session_context: dict) -> dict:
- model_metadata = payload.get("model_metadata") or {}
- capabilities = model_metadata.get("capabilities") or {}
- return {
- "query": payload["input"],
- "supports_image": bool(capabilities.get("multimodal_input_image")),
- "attachments": payload.get("attachments", []),
- "attachment_results": payload.get("attachment_results", []),
- "current_attachments": payload.get("current_attachments", []),
- "has_current_files": payload.get("has_current_files", False),
- }
-```
-
-## 12. 常见坑
-
-### 12.1 把 `HumanMessage.content` 当成永远是字符串
-
-这是最常见坑。多模态模型下,它可能是 `list[block]`。
-
-### 12.2 用 `attachments` 判断当前轮是否传文件
-
-`attachments` 是最近有效附件上下文,可能来自历史 fallback。判断当前轮是否传文件用 `has_current_files`,当前轮附件列表用 `current_attachments`。
-
-### 12.3 想拿业务上下文,却只盯着 `messages[-1]`
-
-更稳的做法是:
-
-- `history` 看多轮语义
-- `has_current_files / current_attachments` 看当前轮文件
-- `attachments / attachment_results` 看最近有效文件上下文和理解结果
-- `platform_context` 看平台身份
-- `kb_context / memory_context` 看召回上下文
-
-### 12.4 以为客户端每轮都会重传完整历史
-
-不会。多轮历史恢复主要靠 `session_id + server 侧 event store`。
-
-### 12.5 以为图片一定会原生直通
-
-不会。是否走原生图片输入取决于:
-
-- 请求显式 `model_metadata`
-- 或 runtime 自动查到的模型目录能力
-
-纯文本模型会走附件/OCR/文本降级。
-
-## 13. 推荐实践
-
-1. 对 LangGraph / LangChain,优先写 `ksadk_prepare_state()` / `ksadk_prepare_input()`
-2. 只有 messages-native agent 才去直接拆 `HumanMessage`
-3. 想理解图片内容时,优先读 `attachment_results`
-4. 想做多模态分流时,读 `model_metadata.capabilities`
-5. 想依赖多轮历史时,确保前端持续复用 `session_id`
-
-## 14. 相关文档
-
-- [LangGraph开发最佳实践](./LangGraph开发最佳实践.md)
-- [远程Agent运行时接口说明](../reference/远程Agent运行时接口说明.md)
-- [ksadk使用文档](./ksadk使用文档.md)
-- [ksadk技术设计](../reference/ksadk技术设计.md)
diff --git "a/docs/guides/DeepAgents\350\257\264\346\230\216.md" "b/docs/guides/DeepAgents\350\257\264\346\230\216.md"
deleted file mode 100644
index d18f4d44..00000000
--- "a/docs/guides/DeepAgents\350\257\264\346\230\216.md"
+++ /dev/null
@@ -1,54 +0,0 @@
-# DeepAgents说明
-
-本文档说明 `ksadk` 当前对 `deepagents` 框架的接入方式。
-
-## 1. 设计原则
-
-- 最小适配:尽量复用现有 LangGraph 运行时路径
-- 统一入口:CLI、框架识别、构建依赖和部署参数都走 `deepagents`
-
-## 2. 当前实现
-
-### 2.1 框架识别
-
-当前支持:
-
-- 显式配置 `framework: deepagents`
-- 代码特征识别:
- - `from deepagents import ...`
- - `import deepagents`
- - `create_deep_agent(...)`
-
-### 2.2 运行时
-
-当前 `DeepAgentsRunner` 沿用 LangGraph 路径,原因是 `create_deep_agent()` 返回 LangGraph 图对象,天然兼容现有 invoke / stream 语义。
-
-### 2.3 平台能力
-
-`deepagents` 当前与 `langgraph` 一样可以复用:
-
-- KB 检索能力
-- LTM 环境变量注入
-- 默认 storage 挂载基座 `/home/node/.agentengine`
-- Hosted WorkspaceFiles capability
-
-## 3. 初始化示例
-
-```bash
-agentengine init my-agent -f deepagents
-```
-
-生成项目示意:
-
-```python
-from deepagents import create_deep_agent
-from langchain_openai import ChatOpenAI
-
-llm = ChatOpenAI(...)
-root_agent = create_deep_agent(model=llm)
-```
-
-## 4. 相关文档
-
-- [ksadk使用文档](./ksadk使用文档.md)
-- [ksadk技术设计](../reference/ksadk技术设计.md)
diff --git "a/docs/guides/LangGraph\345\274\200\345\217\221\346\234\200\344\275\263\345\256\236\350\267\265.md" "b/docs/guides/LangGraph\345\274\200\345\217\221\346\234\200\344\275\263\345\256\236\350\267\265.md"
deleted file mode 100644
index 2b6943d9..00000000
--- "a/docs/guides/LangGraph\345\274\200\345\217\221\346\234\200\344\275\263\345\256\236\350\267\265.md"
+++ /dev/null
@@ -1,682 +0,0 @@
-# LangGraph 开发最佳实践
-
-本文档面向使用 `ksadk-python` 开发 LangGraph Agent 的业务开发者,重点说明:
-
-- LangGraph 工程应该如何暴露 `root_agent`
-- `ksadk_prepare_state(payload, session_context)` 应该放在哪里、怎么写
-- 平台上下文、附件、OCR、知识库、长期记忆如何进入 LangGraph state
-- `interrupt()` / `Command(resume=...)` 在 AgentEngine 运行时里的职责边界
-- `/v1/responses` 下 MCP approval 与通用 human-in-the-loop 的恢复写法
-
-本文档只讨论业务代码接入方式,不展开远程部署、网关鉴权和托管 UI 协议。接口字段 contract 见 [远程Agent运行时接口说明](../reference/远程Agent运行时接口说明.md)。
-
-## 1. 推荐结论
-
-LangGraph Agent 推荐按以下原则接入:
-
-1. `agentengine.yaml` 的 `framework` 写 `langgraph`
-2. `entry_point` 指向真正加载 Agent 的 Python 模块
-3. 在 `entry_point` 模块顶层暴露 `root_agent`
-4. 自定义 state 图优先写 `ksadk_prepare_state(payload, session_context)`
-5. 业务节点只消费自己定义的 state 字段,不直接解析平台 event store
-6. 如果使用 `interrupt()`,业务代码只定义暂停点和 resume payload 的业务含义
-7. 是否把下一次请求转成 `Command(resume=...)` 由平台协议层决定,不由业务代码猜测
-
-`LangGraphRunner` 的设计目标是薄适配:尽量透传 LangGraph 原生能力,只在 `resume=True` 时把输入包装成 `langgraph.types.Command(resume=...)`。
-
-## 2. 推荐目录结构
-
-一个最小但清晰的项目可以这样组织:
-
-```text
-my_agent/
- agent.py
- state.py
- nodes.py
- prompts.py
-agentengine.yaml
-requirements.txt
-```
-
-其中:
-
-- `agent.py`:组装 `StateGraph`,暴露 `root_agent`,并 re-export `ksadk_prepare_state`
-- `state.py`:定义 `TypedDict` / reducer
-- `nodes.py`:放 LangGraph 节点逻辑
-- `prompts.py`:放 prompt 模板或系统指令
-
-简单项目也可以只保留一个 `agent.py`。关键不是文件数量,而是 `entry_point` 模块必须能被 `ksadk` 直接加载。
-
-## 3. agentengine.yaml
-
-示例:
-
-```yaml
-name: langgraph-demo
-framework: langgraph
-entry_point: my_agent/agent.py
-agent_variable: root_agent
-```
-
-字段说明:
-
-| 字段 | 说明 |
-| --- | --- |
-| `framework` | 必须为 `langgraph` |
-| `entry_point` | Python 模块文件路径,运行时会加载这个模块 |
-| `agent_variable` | 模块里的 LangGraph compiled graph 变量名,通常是 `root_agent` |
-
-## 4. root_agent 暴露方式
-
-`root_agent` 应该是 LangGraph 编译后的图:
-
-```python
-from langgraph.graph import END, StateGraph
-
-from .state import AgentState
-from .nodes import answer
-from .state_adapter import ksadk_prepare_state
-
-
-workflow = StateGraph(AgentState)
-workflow.add_node("answer", answer)
-workflow.set_entry_point("answer")
-workflow.add_edge("answer", END)
-
-root_agent = workflow.compile()
-```
-
-如果你把 `ksadk_prepare_state` 放在别的文件里,必须在 `entry_point` 模块 re-export:
-
-```python
-from .state_adapter import ksadk_prepare_state
-```
-
-`ksadk` 不会全项目扫描这个函数。它只会在 `entry_point` 对应模块上执行类似下面的查找:
-
-```python
-getattr(module, "ksadk_prepare_state", None)
-```
-
-所以以下写法不推荐:
-
-- 放在别的文件里,但没有从 `entry_point` 模块导入
-- 写成类方法
-- 写在函数内部
-- 运行时动态创建,但模块导入完成后顶层属性上拿不到
-
-## 5. 平台给 LangGraph 的标准输入
-
-进入 LangGraphRunner 前,平台会把一次请求整理成标准运行输入。常见字段如下:
-
-| 字段 | 类型 | 说明 |
-| --- | --- | --- |
-| `input` | `str` 或 `dict` | 当前输入;普通请求是文本,resume 请求是结构化恢复 payload |
-| `history` | `list[dict]` | 多轮历史,已经过 transcript 投影和必要的 compaction |
-| `input_content` | `list[dict]` | 当前 user turn 的 OpenAI Responses content blocks,例如 `input_text / input_image / input_file` |
-| `input_messages` | `list[dict]` | OpenAI Responses 风格 message/input items;需要完整 role/content 结构时优先读这里 |
-| `input_parts` | `list[dict]` | legacy/internal 归一化片段,保留 `text / inlineData / fileData`;用于兼容已有 runner |
-| `attachments` | `list[dict]` | 当前会话最近有效附件上下文,兼容历史 fallback |
-| `attachment_results` | `list[dict]` | 最近有效 OCR / 文本抽取 / 附件理解结果 |
-| `current_attachments` | `list[dict]` | 当前最新 user turn 的附件列表,不包含历史 fallback |
-| `current_attachment_results` | `list[dict]` | 当前最新 user turn 的附件理解结果 |
-| `has_current_files` | `bool` | 当前最新 user turn 是否包含归一化后的 `inlineData` 或 `fileData`,包括 OpenAI `input_image / input_file` |
-| `model` | `str` | 本轮显式模型名 |
-| `model_metadata` | `dict` | 模型能力元数据 |
-| `platform_context` | `dict` | `agent_id / user_id / account_id / session_id` 等平台身份(`PlatformInvocationContext.to_payload()`) |
-| `kb_context` | `dict` | 知识库召回上下文 |
-| `memory_context` | `dict` | 长期记忆上下文 |
-| `instructions` | `str` | 请求级系统/开发者指令 |
-| `resume` | `bool` | 平台判断本轮是否为断点恢复 |
-| `invocation_id` | `str` | 0.6.5 新增。本次 invocation 的唯一标识,对应 `ToolExecutionContext.invocation_id`,可用于日志/trace 关联;透传给 `ksadk_prepare_state(payload, ...)` 的 `payload["invocation_id"]` |
-
-`input_content` / `input_messages` 是 runner 默认 canonical 输入,沿用 OpenAI Responses content block 形态;`input_parts` 是 legacy/internal normalized parts。`attachments / current_attachments / has_current_files` 等字段是 KsADK 提供给 LangGraph runner 的运行时上下文扩展,不属于 OpenAI 官方请求或响应字段。
-
-对外协议不混写:`/v1/responses` 按 Responses 语义接收 `input_text / input_image / input_file`;`/v1/chat/completions` 保持 Chat Completions 语义,官方图片块使用 `text / image_url`。进入 runner 前,两条入口都会转换为 `input_content / input_messages`,同时生成兼容用 `input_parts`。KsADK 仍兼容 `inlineData / fileData` 老输入,但不要把它们当成 OpenAI Chat 官方字段。
-
-## 6. 默认 messages-based 图
-
-如果没有定义 `ksadk_prepare_state()`,运行时会自动把请求转换成 LangGraph 常见的 messages state:
-
-```python
-{
- "attachments": [...],
- "attachment_results": [...],
- "current_attachments": [...],
- "current_attachment_results": [...],
- "has_current_files": True,
- "input_content": [...],
- "input_messages": [...],
- "input_parts": [...],
- "model_metadata": {...},
- "messages": [
- SystemMessage(...),
- HumanMessage(...),
- AIMessage(...),
- HumanMessage(...),
- ],
-}
-```
-
-说明:
-
-- `messages` 是默认主上下文
-- `input_content / input_messages / input_parts / attachments / attachment_results / current_attachments / current_attachment_results / has_current_files` 保留在 state 顶层
-- 如果模型支持原生图片输入,最后一条 `HumanMessage.content` 可能是多模态 block 列表
-- 如果模型不支持原生图片输入,最后一条 `HumanMessage.content` 通常是字符串
-
-messages-based 图适合快速迁移。但如果你的业务需要稳定消费附件、OCR、平台身份、知识库或长期记忆,推荐显式写 `ksadk_prepare_state()`。
-
-## 7. 推荐:自定义 ksadk_prepare_state
-
-推荐在 `ksadk_prepare_state(payload, session_context)` 里把平台输入投影成业务 state。
-
-```python
-def ksadk_prepare_state(payload: dict, session_context: dict) -> dict:
- if session_context.get("is_resume"):
- return payload.get("input")
-
- return {
- "query": payload["input"],
- "history": session_context["history"],
- "attachments": payload.get("attachments", []),
- "attachment_results": payload.get("attachment_results", []),
- "current_attachments": payload.get("current_attachments", []),
- "current_attachment_results": payload.get("current_attachment_results", []),
- "has_current_files": payload.get("has_current_files", False),
- "input_content": payload.get("input_content", []),
- "input_messages": payload.get("input_messages", []),
- "input_parts": payload.get("input_parts", []),
- "platform_context": session_context.get("platform_context"),
- "kb_context": session_context.get("kb_context"),
- "memory_context": session_context.get("memory_context"),
- "model_metadata": payload.get("model_metadata", {}),
- }
-```
-
-字段来源建议:
-
-| 你想拿什么 | 推荐来源 |
-| --- | --- |
-| 当前用户输入 | `payload["input"]` |
-| 当前输入 OpenAI canonical content | `payload["input_content"]` |
-| 当前输入 OpenAI canonical messages | `payload["input_messages"]` |
-| 当前输入 legacy/internal parts | `payload["input_parts"]` |
-| 当前轮是否带文件 | `payload["has_current_files"]` |
-| 当前轮附件引用 | `payload["current_attachments"]` |
-| 当前轮 OCR / 文档抽取结果 | `payload["current_attachment_results"]` |
-| 最近有效附件上下文 | `payload["attachments"]` |
-| 最近有效 OCR / 文档抽取结果 | `payload["attachment_results"]` |
-| 会话历史 | `session_context["history"]` |
-| 平台身份 | `session_context["platform_context"]` |
-| account_id(计费/租户隔离) | `session_context["platform_context"]["account_id"]`,等价于 `PlatformInvocationContext.account_id` |
-| invocation_id(本次调用标识) | `payload["invocation_id"]`,对应 `ToolExecutionContext.invocation_id` |
-| 知识库上下文 | `session_context["kb_context"]` |
-| 长期记忆上下文 | `session_context["memory_context"]` |
-| 是否断点恢复 | `session_context["is_resume"]`,只建议在 adapter 中判断,用来返回 resume payload |
-
-不要在业务代码里读取平台内部 event store 来拼 history。平台已经把可喂给模型的历史投影成 `history`。
-
-如果你的图使用 LangGraph `interrupt()`,`session_context["is_resume"]` 为 `True` 时,`ksadk_prepare_state` 的返回值会作为 `Command(resume=...)` 的值传回 interrupt 调用点,而不是作为新的 graph state 注入。因此推荐在 resume 分支直接返回 `payload["input"]`,不要继续返回完整业务 state。
-
-!!! info "0.6.5 / 0.6.7 平台上下文字段"
- `session_context["platform_context"]` 来自平台 `PlatformInvocationContext.to_payload()`,业务代码可通过它稳定拿到:
-
- - `agent_id` / `user_id` / `session_id`:运行时身份,用于多租户隔离、日志关联
- - `account_id`(`PlatformInvocationContext.account_id`):计费与租户归属,0.6.5 起在托管 runtime 里始终填充;本地裸跑可能为空字符串,消费时请用 `or ""` 兜底
- - `runner_type`:当前 runner 类型,例如 `langgraph`
-
- 本次 invocation 的唯一标识 `invocation_id` 不在 `platform_context` 内,而是作为 `payload["invocation_id"]` 透传给 `ksadk_prepare_state`,对应平台 `ToolExecutionContext.invocation_id`。需要把业务日志、trace span 或外部副作用(写库、调用下游)与单次请求关联时,优先读这个字段而不是自己生成 ID。
-
- ```python
- def ksadk_prepare_state(payload: dict, session_context: dict) -> dict:
- platform_ctx = session_context.get("platform_context") or {}
- return {
- "query": payload.get("input", ""),
- "account_id": str(platform_ctx.get("account_id") or ""),
- "invocation_id": str(payload.get("invocation_id") or ""),
- # ...其他字段
- }
- ```
-
-!!! tip "0.6.5 / 0.6.7 LangGraph checkpoint resume 保留 checkpoint_ns"
- 当通过 `ResumeRun` 从历史 checkpoint 恢复运行时,LangGraph runner 会把 `framework_ref.langgraph.checkpoint_ns` 写回 `configurable.checkpoint_ns`:
-
- - **0.6.5**:runner 首次在 checkpoint resume 配置里保留 `checkpoint_ns`,此前多命名空间(subgraph)图回档会丢失命名空间上下文
- - **0.6.7**:runner 无条件保留 `checkpoint_ns`(即便上游未传也会补空串占位),并在 `run_checkpoint` 事件的 `framework_ref.langgraph.checkpoint_ns` 中回写,保证列表 / 预览 / 恢复三段链路一致
-
- 业务代码不需要直接读 `checkpoint_ns`。如果你的图使用了 subgraph 并依赖命名空间隔离,恢复后 LangGraph 会自动定位到正确的 subgraph checkpoint。仅当你在自定义节点里手动调用 `agent.get_state(config)` / `agent.aget_state(config)` 做诊断时,才需要把 `checkpoint_ns` 一并带上。
-
-!!! tip "0.6.5 / 0.6.7 time_travel ResumeMode 使用指导"
- LangGraph runner 的 `RuntimeCapabilities.ResumeRun.ResumeMode` 声明为 `time_travel`,表示控制台可以选择任意历史 checkpoint 回档重放,而不是只能沿最新 invocation 续跑。使用要点:
-
- - 前端入口门控以 `GetAgentUiBootstrap.Capabilities.RuntimeCapabilities.Checkpoint` 与 `CheckpointResumeCapability` 为准,不要仅凭 `RunLifecycle.Resume` 判断
- - `ResumeMode=time_travel` 时优先展示 checkpoint 列表(`ListSessionCheckpoints`)让用户选点,再用 `GetCheckpointResumePreview` 预览、`ResumeRun` 恢复
- - 恢复语义是“同一 run 续跑”(`RunId` 不变),不是新建 run;终态 checkpoint 返回 `200 noop`,前端按正常完成态收敛
- - `time_travel` 依赖持久化 checkpointer(`postgres` / `sqlite`);`memory` 后端 `Scope=process_local` 不可恢复,控制台应隐藏恢复入口
-
-## 8. 附件、OCR 和图片
-
-`attachments` 更接近原始引用,但语义是最近有效附件上下文;如果只判断当前轮是否传了文件,请使用 `has_current_files` 或 `current_attachments`。
-
-典型字段:
-
-```python
-{
- "display_name": "diagram.png",
- "mime_type": "image/png",
- "transport": "reference",
- "file_uri": "ksadk-upload://abc123.png",
- "data": "", # 仅 transport="inline" 时存在
- "size_bytes": 1356,
- "storage_path": "/tmp/.../diagram.png",
- "is_text": False,
-}
-```
-
-`attachment_results` 更适合业务逻辑消费,典型字段:
-
-```python
-{
- "display_name": "diagram.png",
- "mime_type": "image/png",
- "kind": "image",
- "status": "ok",
- "extraction_method": "image_ocr",
- "text": "KIMI E2E",
- "text_excerpt": "KIMI E2E",
-}
-```
-
-推荐模式:
-
-```python
-def collect_attachment_texts(state: dict) -> list[str]:
- results = state.get("current_attachment_results") or state.get("attachment_results") or []
- return [
- item.get("text", "")
- for item in results
- if isinstance(item, dict) and item.get("text")
- ]
-```
-
-如果你要根据模型能力决定是否走原生多模态:
-
-```python
-def supports_image_input(state: dict) -> bool:
- model_metadata = state.get("model_metadata") or {}
- capabilities = model_metadata.get("capabilities") or {}
- return bool(capabilities.get("multimodal_input_image"))
-```
-
-如果只是需要当前轮图片 OCR 文本,不建议拆 `HumanMessage.content`,直接用 `current_attachment_results[*]["text"]` 更稳定;需要支持“继续分析上次附件”时再 fallback 到 `attachment_results`。
-
-## 9. 知识库和长期记忆
-
-平台可能按策略把知识库和长期记忆上下文注入:
-
-```python
-kb_context = state.get("kb_context") or {}
-memory_context = state.get("memory_context") or {}
-
-kb_text = kb_context.get("formatted_text", "")
-memory_text = memory_context.get("formatted_text", "")
-```
-
-建议把它们当作外部上下文材料,而不是持久化状态源。业务图如果要写自己的记忆,应明确区分:
-
-- 平台长期记忆召回:`memory_context`
-- LangGraph 图内部状态:你的 `AgentState`
-- 业务数据库:你自己的外部存储
-
-## 10. interrupt 与断点恢复
-
-LangGraph 原生支持在图节点中调用 `interrupt()` 暂停,并在下一次 `invoke` / `stream` 时用 `Command(resume=...)` 恢复。
-
-在 AgentEngine 运行时里,职责边界是:
-
-| 层级 | 职责 |
-| --- | --- |
-| LangGraph 业务代码 | 调用 `interrupt()`,定义暂停信息和 resume payload 的业务语义 |
-| Responses / Hosted UI / API 层 | 接收用户审批或恢复输入,判断这是一次 resume |
-| conversation runtime | 记录 `approval_request / approval_response`,向 runner 传 `resume=True` |
-| LangGraphRunner | 薄适配:把 `resume=True` 转成 `Command(resume=...)` |
-
-如果你定义了 `ksadk_prepare_state`,resume 请求也会经过这个 hook。此时 hook 的返回值就是 `Command(resume=...)` 里的 `resume` 值。推荐写法是:
-
-```python
-def ksadk_prepare_state(payload: dict, session_context: dict) -> dict:
- if session_context.get("is_resume"):
- return payload.get("input")
- return build_normal_state(payload, session_context)
-```
-
-业务代码不应该:
-
-- 自己判断“上一轮是不是暂停了”
-- 自己构造平台 event store 查询
-- 依赖 LangGraph 内部 `state.tasks[*].interrupts` 结构
-- 要求客户端直接传 Python `Command`
-
-业务代码应该:
-
-- 在节点中用 `interrupt(value)` 暂停
-- 让 `value` 包含前端或调用方需要展示的信息
-- 在恢复后从 `Command(resume=...)` 传回的值继续执行业务逻辑
-
-## 11. MCP 工具审批:Responses 标准语义
-
-当 interrupt 表示 MCP/tool approval 时,运行时会按 OpenAI Responses 风格输出 `mcp_approval_request`,并以 `response.incomplete` 结束本轮。
-
-客户端恢复时,应调用 `/v1/responses`,传入同一个 `session_id`,并把 `input` 写成 `mcp_approval_response`:
-
-```json
-{
- "session_id": "sess_xxx",
- "previous_response_id": "resp_xxx",
- "input": [
- {
- "type": "mcp_approval_response",
- "id": "mcprsp_xxx",
- "approval_request_id": "appr_xxx",
- "approve": true,
- "reason": "approved by user"
- }
- ],
- "stream": true
-}
-```
-
-运行时会把它转换成 runner 输入:
-
-```python
-{
- "session_id": "sess_xxx",
- "resume": True,
- "input": {
- "type": "mcp_approval_response",
- "id": "mcprsp_xxx",
- "approval_request_id": "appr_xxx",
- "approve": True,
- "reason": "approved by user",
- },
-}
-```
-
-LangGraphRunner 随后调用:
-
-```python
-Command(resume={
- "type": "mcp_approval_response",
- "id": "mcprsp_xxx",
- "approval_request_id": "appr_xxx",
- "approve": True,
- "reason": "approved by user",
-})
-```
-
-注意:
-
-- `session_id` 是当前运行时定位 LangGraph thread 的关键字段
-- `previous_response_id` 按 Responses 语义保留到 metadata,但当前不能替代 `session_id`
-- 客户端不需要知道 Python `Command`
-- 业务代码只关心 resume payload 的业务含义
-
-## 12. 泛化 human-in-the-loop:ksadk_resume
-
-如果 interrupt 不是 MCP/tool approval,而是普通人工确认、补充信息或业务分支选择,运行时会使用平台扩展事件:
-
-- 流式事件:`response.ksadk.approval_request`
-- 结束事件:`response.incomplete`
-- `incomplete_details.reason`: `approval_required`
-- `incomplete_details.ksadk_interrupt`: 原始 interrupt 信息
-
-恢复请求可以使用 `ksadk_resume`:
-
-```json
-{
- "session_id": "sess_xxx",
- "input": [
- {
- "type": "ksadk_resume",
- "interrupt_id": "intr_xxx",
- "value": {
- "approved": true,
- "answer": "继续"
- }
- }
- ],
- "stream": true
-}
-```
-
-runner 收到的输入会是:
-
-```python
-{
- "session_id": "sess_xxx",
- "resume": True,
- "input": {
- "type": "ksadk_resume",
- "interrupt_id": "intr_xxx",
- "value": {
- "approved": True,
- "answer": "继续",
- },
- },
-}
-```
-
-业务节点恢复后应按自己约定解析 `value`。
-
-## 13. checkpoint resume 用户向使用流程
-
-前面几节讲的是 `interrupt()` / approval 的运行内恢复(同一 invocation 内续跑)。0.6.5 起 ksadk 还支持把整条 run 回档到某个历史 checkpoint 重新执行,这是面向控制台 / Hosted UI 的用户向操作,业务代码不需要写任何 resume 逻辑。
-
-适用场景:
-
-- 用户想“回到第 3 步重新选一个分支”
-- 某一步工具调用结果不对,想从那一步之前重跑
-- 长流程中途想换模型重试某一段
-
-!!! info "0.6.5 / 0.6.7 checkpoint resume 能力门控"
- 是否可回档以 `GetAgentUiBootstrap.Capabilities.RuntimeCapabilities` 为准:
-
- - `Checkpoint.Supported=true` 才有 checkpoint 列表 / 预览入口
- - `ResumeRun.Supported=true` 且 `ResumeRun.ResumeMode=time_travel` 才能选任意历史 checkpoint 回档
- - `CheckpointResumeCapability.Supported / Checkpoint / ResumeRun` 是整体能力总开关,前端应同时校验
-
- LangGraph runner 在配置了持久化 checkpointer(`postgres` / `sqlite`)且 `ResumeMode=time_travel` 时点亮该链路;`memory` 后端 `Scope=process_local` 不支持跨进程恢复,恢复入口应隐藏。
-
-用户向操作流程(控制台 / Hosted UI 视角):
-
-```mermaid
-flowchart LR
- A[ListSessionCheckpoints] --> B{IsResumable?}
- B -- 是 --> C[GetCheckpointResumePreview]
- C --> D[ResumeRun]
- D --> E[SubscribeRunEvents]
- B -- 否 --> F[引导选其他 checkpoint]
- D -. 409 .-> F
-```
-
-步骤说明:
-
-1. **列表**:调 `ListSessionCheckpoints(AgentId, SessionId, OnlyResumable=true)` 拿到可恢复 checkpoint 列表。每个 descriptor 含 `CheckpointId / RunId / FrameworkRef / IsResumable / IsTerminal / NextNode / Backend / Scope`
-2. **预览**:选中一个 checkpoint 后调 `GetCheckpointResumePreview(AgentId, SessionId, RunId, CheckpointId)`,展示“将从哪个节点继续、涉及哪些 tool receipt”,避免误恢复
-3. **恢复**:确认后调 `ResumeRun(AgentId, SessionId, RunId, CheckpointId, Stream=true)`。恢复语义是同一 `RunId` 续跑,不是新建 run;可带 `InvocationId` 便于 `SubscribeRunEvents` / `CancelRun`
-4. **订阅**:`Stream=true` 返回 SSE,事件流与普通 run 一致;终态 checkpoint 返回 `200 noop`,前端按正常完成态收敛 UI
-
-```bash hl_lines="6"
-# 恢复调用示例(占位符 endpoint / token)
-curl -X POST https://example.com/agentengine/api/v1/ResumeRun \
- -H "Authorization: Bearer sk-test" \
- -H "Content-Type: application/json" \
- -d '{
- "AgentId": "agent_xxx",
- "SessionId": "sess_xxx",
- "RunId": "run_xxx",
- "CheckpointId": "ckpt_xxx",
- "Stream": true
- }'
-```
-
-业务代码注意事项:
-
-- checkpoint resume **不会** 经过 `ksadk_prepare_state` 的 resume 分支。`session_context["is_resume"]` 描述的是 `interrupt()` 运行内恢复,与 checkpoint 回档是两条独立链路
-- LangGraph runner 会把 `framework_ref.langgraph.checkpoint_ns` 写回 `configurable.checkpoint_ns`(0.6.5 首次保留,0.6.7 无条件保留),subgraph 命名空间上下文自动恢复,业务节点不需要感知
-- 终态 checkpoint(`IsTerminal=true`)不可恢复,`ResumeRun` 返回 `200 noop`;非终态且 `IsResumable=false` 返回 `409 checkpoint_not_resumable`,前端应引导用户选其他 checkpoint 而不是无限重试
-- runtime 只信任服务端已保存的 `run_checkpoint` 事件解析 `framework_ref`,客户端不能自行伪造 checkpoint 状态
-
-## 14. 完整可跑示例
-
-下面示例演示:
-
-- 自定义 `AgentState`
-- 使用 `ksadk_prepare_state`
-- 消费附件 OCR、平台上下文和模型能力
-- 支持人工确认 interrupt / resume
-
-```python
-from __future__ import annotations
-
-from typing import Annotated, Any, TypedDict
-import operator
-
-from langgraph.graph import END, StateGraph
-from langgraph.types import interrupt
-
-
-class AgentState(TypedDict):
- query: str | dict[str, Any]
- history: list[dict[str, Any]]
- attachments: list[dict[str, Any]]
- attachment_results: list[dict[str, Any]]
- current_attachments: list[dict[str, Any]]
- current_attachment_results: list[dict[str, Any]]
- has_current_files: bool
- input_content: list[dict[str, Any]]
- input_messages: list[dict[str, Any]]
- input_parts: list[dict[str, Any]]
- platform_context: dict[str, Any] | None
- kb_context: dict[str, Any] | None
- memory_context: dict[str, Any] | None
- model_metadata: dict[str, Any]
- messages: Annotated[list[dict[str, str]], operator.add]
-
-
-def _attachment_texts(state: AgentState) -> list[str]:
- return [
- item.get("text", "")
- for item in state.get("attachment_results", [])
- if isinstance(item, dict) and item.get("text")
- ]
-
-
-def _is_approved(resume_value: Any) -> bool:
- if not isinstance(resume_value, dict):
- return False
- if resume_value.get("type") == "mcp_approval_response":
- return bool(resume_value.get("approve"))
- if resume_value.get("type") == "ksadk_resume":
- value = resume_value.get("value") or {}
- return isinstance(value, dict) and bool(value.get("approved"))
- return bool(resume_value.get("approved"))
-
-
-def answer(state: AgentState) -> AgentState:
- query = state["query"]
- attachment_texts = _attachment_texts(state)
- model_metadata = state.get("model_metadata") or {}
- supports_image = bool(
- ((model_metadata.get("capabilities") or {}).get("multimodal_input_image"))
- )
-
- if "删除" in str(query):
- resume_value = interrupt(
- {
- "id": "confirm-delete",
- "message": "检测到删除操作,请确认是否继续。",
- "operation": "delete",
- }
- )
- if not _is_approved(resume_value):
- return {"messages": [{"role": "assistant", "content": "已取消删除操作。"}]}
-
- content = (
- f"query={query}; "
- f"supports_image={supports_image}; "
- f"attachment_texts={attachment_texts}"
- )
- return {"messages": [{"role": "assistant", "content": content}]}
-
-
-def ksadk_prepare_state(payload: dict, session_context: dict) -> dict:
- if session_context.get("is_resume"):
- return payload.get("input")
-
- return {
- "query": payload.get("input", ""),
- "history": session_context.get("history", []),
- "attachments": payload.get("attachments", []),
- "attachment_results": payload.get("attachment_results", []),
- "current_attachments": payload.get("current_attachments", []),
- "current_attachment_results": payload.get("current_attachment_results", []),
- "has_current_files": payload.get("has_current_files", False),
- "input_content": payload.get("input_content", []),
- "input_messages": payload.get("input_messages", []),
- "input_parts": payload.get("input_parts", []),
- "platform_context": session_context.get("platform_context"),
- "kb_context": session_context.get("kb_context"),
- "memory_context": session_context.get("memory_context"),
- "model_metadata": payload.get("model_metadata", {}),
- "messages": [],
- }
-
-
-workflow = StateGraph(AgentState)
-workflow.add_node("answer", answer)
-workflow.set_entry_point("answer")
-workflow.add_edge("answer", END)
-
-root_agent = workflow.compile()
-```
-
-## 15. 常见反模式
-
-### 15.1 在业务代码里猜是否 resume
-
-不要通过读取数据库、检查上一轮输出文本、解析 event store 来判断是否恢复。平台会把恢复请求转成 `resume=True`。
-
-### 15.2 让客户端直接传 LangGraph Command
-
-外部协议应该是 JSON。`Command(resume=...)` 是 Python / LangGraph runner 内部调用形态,不应该暴露给客户端。
-
-### 15.3 依赖 LangGraph 内部状态结构
-
-不要依赖 `state.tasks[*].interrupts` 这类内部结构做业务判断。LangGraph 版本升级后这些结构可能变化。
-
-### 15.4 把 `HumanMessage.content` 当成永远是字符串
-
-多模态模型下它可能是 content block 列表。除非你明确在做 messages-native agent,否则优先使用 `payload / session_context`。
-
-### 15.5 用 attachments 判断当前轮是否传文件
-
-`attachments` 是最近有效附件上下文,可能来自历史 fallback。当前轮是否传文件看 `has_current_files`,当前轮附件列表看 `current_attachments`;OCR、文档抽取、压缩包摘要仍优先看对应的 `current_attachment_results` 或 `attachment_results`。
-
-## 16. 检查清单
-
-上线前建议确认:
-
-- `agentengine.yaml` 的 `framework` 是 `langgraph`
-- `entry_point` 指向包含 `root_agent` 的模块
-- `root_agent` 是 compiled graph
-- `ksadk_prepare_state` 在 `entry_point` 模块顶层可见
-- 自定义 state 明确包含业务需要的上下文字段
-- 当前轮文件判断使用 `has_current_files / current_attachments`
-- 附件理解优先读取 `current_attachment_results / attachment_results`
-- 多模态分支读取 `model_metadata.capabilities`
-- interrupt 恢复只依赖 resume payload,不依赖平台内部事件结构
-- `/v1/responses` 恢复调用传同一个 `session_id`
-- checkpoint 回档依赖持久化 checkpointer(`postgres` / `sqlite`),`memory` 后端隐藏恢复入口
-- checkpoint resume 与 `interrupt()` 运行内恢复是两条独立链路,不要在 `ksadk_prepare_state` 里混判
diff --git "a/docs/guides/ksadk\344\275\277\347\224\250\346\226\207\346\241\243.md" "b/docs/guides/ksadk\344\275\277\347\224\250\346\226\207\346\241\243.md"
deleted file mode 100644
index 642cdfb3..00000000
--- "a/docs/guides/ksadk\344\275\277\347\224\250\346\226\207\346\241\243.md"
+++ /dev/null
@@ -1,1036 +0,0 @@
-# ksadk使用文档
-
-本文档面向使用 `agentengine` / `ksadk` 的开发者、SA 与交付同学,口径以当前仓库代码、CLI 帮助、测试断言和 Docker/Makefile 默认值为准。
-
-## 1. 适用范围
-
-当前文档覆盖这些主线能力:
-
-- 本地初始化、配置、运行与调试
-- `build / deploy / launch` 的构建与部署参数
-- `agentengine files` 的完整工作区文件管理链路
-- Hermes 与 OpenClaw 的部署和常用验证路径
-- PVC 默认值、默认挂载目录、容量约束
-- `agentengine agent invoke` 的 Hermes 远端 native 调用与本地目录同步
-- 统一模型策略与 fallback(`0.6.6` 引入,`0.6.7` 补齐 reasoning 与 thinking 兼容)
-- Hosted 附件 `ae-upload://` scheme 与 `AttachmentContent` action
-- `ListSessions` / `ListSessionEvents` 分页字段
-- Custom UI bundle 与 `RuntimeCapabilities` 能力位
-- `--env` / `--env-file` 运行时环境变量与 `.env` 构建上下文边界
-- KCR 企业版 / 个人版 / 第三方镜像仓库凭证收敛
-- 长任务恢复与 `CancelRun` / `ResumeRun` 用户向流程
-
-## 2. 安装与入口
-
-```bash
-pip install -U ksadk
-```
-
-可选 extras:
-
-```bash
-pip install "ksadk[langgraph]"
-pip install "ksadk[langchain]"
-pip install "ksadk[deepagents]"
-pip install "ksadk[adk]"
-pip install "ksadk[skills]"
-```
-
-知识库和长期记忆使用的 `kingsoftcloud-sdk-python` 已包含在默认依赖中,不需要额外安装 `ksadk[kb]`。
-
-命令入口等价:
-
-```bash
-agentengine --help
-ksadk --help
-```
-
-## 3. CLI 全景
-
-当前主线命令组包括:
-
-- `init`
-- `config`
-- `run`
-- `web`
-- `build`
-- `deploy`
-- `launch`
-- `agent`
-- `files`
-- `dashboard`
-- `hermes`
-- `openclaw`
-- `mcp`
-- `a2a`
-
-全局选项包括:
-
-- `--output pretty|json`
-- `--dry-run`
-- `--no-color`
-
-```mermaid
-flowchart LR
- classDef client fill:#dbeafe,stroke:#1d4ed8,stroke-width:2px,color:#1e3a8a;
- classDef control fill:#ede9fe,stroke:#7c3aed,stroke-width:2px,color:#581c87;
- classDef data fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#166534;
- classDef runtime fill:#e2e8f0,stroke:#475569,stroke-width:2px,color:#1e293b;
-
- Init["init / config / run / web"]:::client --> Local["本地开发链路"]:::data
- Build["build / deploy / launch"]:::client --> Server["agentengine-server"]:::control
- Files["files / agent invoke"]:::client --> Runtime["远端 runtime / Hosted Action"]:::data
- Hermes["hermes *"]:::client --> HermesRT["Hermes Runtime"]:::runtime
- OpenClaw["openclaw *"]:::client --> OpenClawRT["OpenClaw Runtime"]:::runtime
-```
-
-## 4. 最短成功路径
-
-### 4.1 本地项目初始化
-
-```bash
-agentengine init my-agent -f langgraph
-cd my-agent
-agentengine config
-agentengine run -i
-```
-
-本地 Web UI:
-
-```bash
-agentengine web --port 8080
-```
-
-### 4.2 云端一键部署
-
-```bash
-agentengine launch . --target serverless
-```
-
-### 4.3 Hermes 云端部署
-
-```bash
-agentengine init my-hermes -f hermes
-cd my-hermes
-agentengine hermes deploy --name my-hermes
-agentengine hermes status
-```
-
-### 4.4 OpenClaw 云端部署
-
-```bash
-agentengine init my-openclaw -f openclaw
-cd my-openclaw
-agentengine openclaw deploy
-agentengine openclaw status
-```
-
-### 4.5 Skill Runtime 与内置工具接入
-
-`0.6.2` 新增 `ksadk.toolsets`,开发者可以在 LangGraph、LangChain、DeepAgents 或自定义 runner 中显式绑定 AgentEngine 内置工具。推荐默认使用渐进式披露,避免把所有低频或高风险工具直接塞进模型上下文:
-
-```python
-from ksadk.toolsets import describe_agentengine_tools, get_agentengine_tools
-
-tools = get_agentengine_tools(include=["focused", "agentengine_tool_dispatcher"])
-tool_specs = describe_agentengine_tools(include=["focused", "agentengine_tool_dispatcher"])
-```
-
-`focused` 默认直接暴露这些高频工具:
-
-- Skill Space:`list_skills`、`search_skills`、`load_skill`
-- Workspace:`workspace_status`、`search_workspace_files`、`edit_workspace_file`、`lint_workspace_file`
-- Platform:`component_status`
-- Sandbox:`sandbox_status`
-
-低频、高风险或上下文较重的工具通过 `agentengine_tool_dispatcher` 按需 `list` / `describe` / `call`:
-
-```python
-from ksadk.toolsets import agentengine_tool_dispatcher
-
-agentengine_tool_dispatcher("describe", tool_name="run_code")
-agentengine_tool_dispatcher(
- "call",
- tool_name="run_code",
- arguments={"code": "print(42)", "language": "python"},
-)
-```
-
-`get_agentengine_tools()` 无参仍返回全量工具,兼容旧项目;新项目建议显式写 `include=["focused", "agentengine_tool_dispatcher"]`。如果只需要某个分组,也可以写 `include=["skill"]`、`include=["workspace"]`、`include=["platform"]` 或 `include=["sandbox"]`。
-
-Skill Runtime 执行入口是 `execute_skills`。它只用于 workflow 型任务,普通 instruction-first Skill 推荐先 `load_skill` 读取 `SKILL.md`,再由外层 agent 按指令完成。隔离执行 backend 由环境变量决定:
-
-- `KSADK_SKILL_RUNTIME_BACKEND=local_process`:走本地 agent 进程
-- `KSADK_SKILL_RUNTIME_BACKEND=e2b`:走远程 sandbox / E2B backend
-- 未设置 backend 但存在 `KSADK_SANDBOX_TEMPLATE_ID`:自动走 E2B
-- 显式 `KSADK_SKILL_RUNTIME_BACKEND=disabled`:禁用隔离执行
-
-Workspace 内置工具只访问 AgentEngine UI workspace,不访问任意宿主机路径。`edit_workspace_file` 是 exact snippet replacement;匹配不到返回 `snippet_not_found`,匹配次数不符合预期返回 `ambiguous_edit`。`lint_workspace_file` 提供 Python AST、JSON parse 和通用文本轻量检查。
-
-Sandbox direct tools 只通过 configured isolated sandbox backend 执行。`run_command` / `run_code` 不会退化为宿主机 shell;未配置 sandbox 时会返回诊断。`execute_skills`、Workspace 写入/删除、sandbox command/code 等中高风险工具会经过 Tool Gateway;strict 模式下会返回 `approval_required`,由 UI 或调用方回传批准后继续。
-
-## 5. `/v1/responses` OpenAI 兼容接口
-
-本地 `agentengine run -i` 启动后,AgentEngine 暴露 `/v1/responses`。这一接口优先兼容 OpenAI Responses 的返回结构和 SSE 生命周期,同时保留少量 `ksadk` 扩展字段,方便会话和本地 CLI 继续工作。
-
-### 5.1 非流式调用
-
-```bash
-curl http://127.0.0.1:8000/v1/responses \
- -H "Content-Type: application/json" \
- -d '{
- "model": "glm-5.2",
- "input": "请用一句话介绍 AgentEngine",
- "instructions": "只用中文回答,语气简洁",
- "metadata": {"source": "local-doc"}
- }'
-```
-
-当前支持的常用请求字段:
-
-- `input`:字符串或 OpenAI message/input item 列表。
-- `model`:本轮请求使用的模型,会同步到运行时环境。
-- `instructions`:本轮系统/开发者指令,不写入用户消息正文;LangGraph 会转为 system message,字符串输入类 runner 会作为 prompt 前缀。
-- `metadata`:请求元数据,会回显到 response object,并记录到本轮事件 metadata;不参与模型生成。
-- `stream`:`true` 时返回 SSE。
-- `session_id`:复用已有会话;未传时自动创建。
-
-非流式响应包含官方风格字段:`id`、`object`、`created_at`、`status`、`model`、`output`、`metadata`、`usage`、`error`、`incomplete_details`。同时保留 `output_text` 和 `session_id` 作为 `ksadk` 扩展,兼容现有调用方。
-
-### 5.2 流式调用
-
-```bash
-curl -N http://127.0.0.1:8000/v1/responses \
- -H "Content-Type: application/json" \
- -d '{
- "model": "glm-5.2",
- "session_id": "sess-demo-001",
- "input": [{"role": "user", "content": [{"type": "input_text", "text": "分析这个任务"}]}],
- "stream": true
- }'
-```
-
-`model` 和 `session_id` 在流式与非流式调用中都可传;`session_id` 用来复用同一会话,未传时自动创建。
-
-流式事件按 Responses 生命周期输出:
-
-- `response.created` / `response.in_progress`:响应创建与开始运行。
-- `response.output_item.added` / `response.content_part.added`:开始输出 message、reasoning、function call 或 MCP approval request。
-- `response.output_text.delta` / `response.output_text.done`:正文增量与正文完成。
-- `response.reasoning.delta`:思考内容增量,前端可选择单独渲染。
-- `response.function_call_arguments.delta` / `response.function_call_arguments.done`:工具调用参数;底层一次性拿到参数时也会按一次 delta + done 输出。
-- `response.output_item.done` / `response.content_part.done`:输出项或内容块完成。
-- `response.completed`:本轮完成。
-- `response.failed`:运行失败。
-- `response.incomplete`:需要人工审核或中断恢复。
-
-工具结果和人工审核的兼容策略:
-
-- `response.ksadk.tool_result`:工具执行结果。
-- 能明确识别为工具审批的 interrupt 会渲染为官方风格 `mcp_approval_request` output item。
-- 其他通用 interrupt 使用 `response.ksadk.approval_request` 扩展事件;最终 response 会以 `status: "incomplete"` 返回,并在 `incomplete_details.ksadk_interrupt` 中包含中断信息。
-
-### 5.3 图片与附件输入
-
-`/v1/responses` 是默认主维护协议,按 OpenAI Responses 语义接收 `input_text` / `input_image` / `input_file`。运行时会把这些输入块原样投影到 runner 的 `input_content` / `input_messages`,同时生成 legacy/internal 的 `input_parts` 兼容旧 runner。
-
-当前 `/v1/responses` 推荐输入块:
-
-- `input_text`
-- `input_image`
-- `input_file`
-
-旧客户端仍可传 KOP 风格 part 数组,运行时会兼容:
-
-- `text`
-- `inlineData`
-- `fileData`
-
-推荐图片传法:
-
-1. 按 OpenAI Responses 官方形态传 `input_image.image_url`,其中 `image_url` 可以是远程图片 URL,也可以是 `data:image/...;base64,...`
-2. 老客户端可先调用 `UploadFile` 上传图片,再通过兼容扩展 `fileData.fileUri` 引用
-3. 老客户端可直接把图片 base64 放进兼容扩展 `inlineData.data`
-
-OpenAI 风格 data URL 示例:
-
-```json
-{
- "input": [
- {
- "role": "user",
- "content": [
- { "type": "input_text", "text": "请分析这张图片" },
- {
- "type": "input_image",
- "image_url": "data:image/png;base64,"
- }
- ]
- }
- ],
- "stream": false
-}
-```
-
-运行时会保留这个官方输入块到 `input_content`,并额外归一化为内部附件上下文,因此业务代码可以继续通过 `has_current_files` / `current_attachments` 判断本轮是否带图。远程图片 URL 会作为引用保留,并可在支持原生图片输入的 LangGraph 路径下继续传给模型;KsADK 不会主动拉取远程图片做 OCR。需要平台提取、OCR 或本地附件内容时,请使用 data URL、`inlineData` 或 `fileData`。
-
-多模态模型“看图”和平台 OCR 是两条不同链路:推荐让支持图片的模型直接消费 `input_image` / `input_content`,这样不需要在代码包里安装本地 OCR 依赖。平台本地 OCR 只用于需要把图片预先转成 `current_attachment_results[*].text` 的场景;源码构建默认不打包 OCR 二进制栈,如需启用请在构建环境设置 `KSADK_BUILD_ENABLE_ATTACHMENT_OCR=true`,或在项目 `requirements.txt` 中显式加入 OCR 相关依赖。
-
-OpenAI 风格文件示例:
-
-```json
-{
- "input": [
- {
- "role": "user",
- "content": [
- { "type": "input_text", "text": "请总结这个文件" },
- {
- "type": "input_file",
- "filename": "resume.txt",
- "file_data": ""
- }
- ]
- }
- ]
-}
-```
-
-`input_file.file_data` 会保留在 `input_content`,并归一化为内部 `inlineData`;`input_file.file_url` / `input_file.file_id` 会保留为引用,并归一化为内部 `fileData`。KsADK 不会主动拉取远程 `file_url` 内容;需要平台提取或 OCR 时,请使用 `file_data`、`inlineData` 或先上传后用 `fileData.fileUri`。
-
-旧客户端先上传再引用的兼容示例:
-
-```json
-{
- "input": [
- {
- "role": "user",
- "content": [
- { "text": "请分析这张图片" },
- {
- "fileData": {
- "fileUri": "ksadk-upload://abc123.png",
- "displayName": "diagram.png",
- "mimeType": "image/png"
- }
- }
- ]
- }
- ],
- "stream": false
-}
-```
-
-旧客户端直接内联的兼容示例:
-
-```json
-{
- "input": [
- {
- "role": "user",
- "content": [
- { "text": "请分析这张图片" },
- {
- "inlineData": {
- "data": "",
- "displayName": "diagram.png",
- "mimeType": "image/png"
- }
- }
- ]
- }
- ]
-}
-```
-
-当前附件类型支持矩阵:
-
-| 类型 | 典型扩展名 / MIME | 传输支持 | 平台内容提取 | 原生多模态直通 |
-| --- | --- | --- | --- | --- |
-| 文本 | `.txt` `.md` `.json` `.yaml` `.yml` `.csv` `.tsv` `.log` | 支持 | 支持 | 不适用 |
-| 文档 | `.pdf` `.docx` `.pptx` `.xlsx` `.html` `.htm` | 支持 | 部分支持:文本提取 / OCR | 不适用 |
-| 图片 | `.png` `.jpg` `.jpeg` `.webp` / `image/*` | 支持 | 元信息提取默认支持;OCR 需构建时显式启用 | 部分支持,见下方 |
-| 压缩包 | `.zip` | 支持 | 支持:目录/可读文件抽样提取 | 不适用 |
-| 其他二进制 | 其他后缀或 `application/octet-stream` | 支持 | 通常仅保留为附件引用 | 不支持 |
-
-框架差异:
-
-- `ADK`
- - 图片会优先按原生 bytes part 传给底层 SDK
- - 如果模型支持原生多模态,可直接吃图
-- `LangGraph`
- - 若模型支持图片输入,默认消息构造会把图片附件转换成多模态 `HumanMessage.content` blocks
-- `LangChain`
- - 当前不保证所有 agent 自动原生吃图
- - 如需原生多模态,建议在 `ksadk_prepare_input(payload, session_context)` 中优先消费 `input_content / input_messages`,必要时再兼容 `input_parts / current_attachments / attachments`
- - 判断当前轮是否传文件用 KsADK runner payload 扩展字段 `has_current_files`;该字段不是 OpenAI Responses API 官方字段
-
-模型能力判断优先级:
-
-1. 请求里显式传入的 `model_metadata`
-2. runtime 通过 `OPENAI_BASE_URL` / `OPENAI_API_KEY` 查询上游 `/v1/models` 返回的 `architecture.input_modalities`
-3. 本地默认兜底(按文本模型处理)
-
-### 5.4 Hosted 附件与 `ae-upload://`
-
-!!! new "0.6.6 新增"
-
-Hosted 部署下,用户在 Hosted UI 上传的附件不再以本地 `ksadk-upload://` 落盘,而是由控制面托管,统一以 `ae-upload://` scheme 引用。两类 URI 的区别:
-
-| 附件 URI scheme | 来源 | 存储 | 读取方式 |
-| --- | --- | --- | --- |
-| `ksadk-upload://` | 本地 `agentengine run -i` / CLI 上传 | 本地 `files/` 目录 + KS3 兜底 | 本地直读或 KS3 回源 |
-| `ae-upload://` | Hosted 控制面上传 | 控制面托管对象 | 经由 `AttachmentContent` action 拉取 |
-
-读取 `ae-upload://` 附件时,conversation runtime 会调用 Hosted 控制面的 `AttachmentContent` action:
-
-```
-GET /agentengine/api/v1/AttachmentContent?FileUri=ae-upload://
-```
-
-返回内容包含二进制字节、`display_name` 与 `content_type`。拉取成功后,runtime 会把内容写回本地附件 cache 目录(`/files/`)并落一份 `.meta.json`,后续同一会话内再次读取该附件时优先命中本地 cache,避免重复远端拉取。
-
-会话刷新(refresh / rehydrate)后,已写回本地 cache 的 Hosted 附件会继续以本地 cache 读取;未命中本地 cache 的 `ae-upload://` 附件会再次触发 `AttachmentContent` 拉取。`ensure_local_path` / `read` 会保证返回可用本地路径,业务代码无需关心附件原始来源。
-
-### 5.5 会话与事件分页
-
-`ListSessions` 与 `ListSessionEvents` 支持分页,字段对齐控制面 action 契约:
-
-| Action | 请求字段 | 响应字段 |
-| --- | --- | --- |
-| `ListSessions` | `AgentId`、`UserId`(默认 `user`)、`Page`(≥1)、`PageSize`(1~200,默认 20) | `Sessions`、`Total`、`Page`、`PageSize` |
-| `ListSessionEvents` | `SessionId`、`Offset`(≥0)、`Limit`(≥1) | `Events`、`Total`、`Offset`、`Limit` |
-
-`ListSessions` 用 `Page` / `PageSize` 做页式分页,客户端按 `Total` 计算总页数;`ListSessionEvents` 用 `Offset` / `Limit` 做偏移分页,`Total` 为该会话事件总数。事件按追加顺序返回,分页只读取已落盘事件,不会阻塞正在写入的事件流。
-
-### 5.6 当前不支持
-
-本期不支持 `previous_response_id`、`store`、复杂 `reasoning/text` 控制、完整 tool schema 请求面,也不新增原生 LangGraph state endpoint。自定义 LangGraph State 请使用 `ksadk_prepare_state` 或 `agentengine init --from-agent` 自动生成的 adapter。
-
-## 6. 统一模型策略与 fallback
-
-!!! new "0.6.7 新增"
-
-`0.6.6` 起引入统一模型策略契约,`0.6.7` 补齐 reasoning 声明与 thinking disabled 兼容。Hermes、OpenClaw 与通用 Agent 共用一套默认语义,避免三类 runtime 各自维护一份模型清单。
-
-### 6.1 策略契约
-
-策略以 JSON 描述,可通过环境变量 `AGENTENGINE_MODEL_POLICY_JSON` 整体覆盖。未设置时使用内置默认策略 `DEFAULT_MODEL_POLICY`(版本号 `v1`):
-
-```json
-{
- "version": "v1",
- "primary": {"model": "glm-5.2"},
- "multimodal": {"model": "kimi-k2.7-code"},
- "fallback": {
- "model": "deepseek-v4-pro",
- "fallback_errors": [
- "timeout",
- "temporarily unavailable",
- "model unavailable",
- "rate limit",
- "too many requests",
- "503",
- "504"
- ],
- "on_errors": [
- "timeout",
- "temporarily unavailable",
- "model unavailable",
- "rate limit",
- "too many requests",
- "503",
- "504"
- ]
- },
- "models": {
- "glm-5.2": {"reasoning": true, "options": {}},
- "kimi-k2.7-code": {"input": ["text", "image"], "reasoning": true, "options": {"temperature": 1}},
- "deepseek-v4-pro": {"reasoning": true, "options": {}}
- }
-}
-```
-
-三个角色的语义:
-
-| 角色 | 默认模型 | 用途 |
-| --- | --- | --- |
-| `primary` | `glm-5.2` | 默认文本主模型,未显式指定 `model` 时使用 |
-| `multimodal` | `kimi-k2.7-code` | 图片/多模态输入时路由到的模型 |
-| `fallback` | `deepseek-v4-pro` | 主模型遇到可恢复错误时重试一次的目标模型 |
-
-构建期会把策略序列化后注入 runtime 环境变量,并按 runtime 类型分别填充对应的模型变量:
-
-- 通用 Agent:`OPENAI_MODEL_NAME`(primary)、`OPENAI_FALLBACK_MODEL_NAME`(fallback)
-- `hermes`:`HERMES_DEFAULT_MODEL`(primary)、`HERMES_FALLBACK_MODEL`(fallback)、`HERMES_MODEL_CATALOG_JSON`
-- `openclaw`:`OPENAI_MODEL_NAME`(primary,带 `ksyun/` provider 前缀)、`OPENCLAW_FALLBACK_MODEL`、`OPENCLAW_IMAGE_MODEL`(multimodal)、`OPENCLAW_MODEL_CATALOG_JSON`
-
-!!! info "策略合并"
-`AGENTENGINE_MODEL_POLICY_JSON` 传入的 JSON 会与 `DEFAULT_MODEL_POLICY` 做深度合并(deep merge),未声明的字段保留默认值;只覆盖你想改的部分即可。`fallback.fallback_errors` / `fallback.on_errors` 会被同步成同一份列表。
-
-### 6.2 自动 fallback 与重试
-
-conversation runtime 在主模型调用失败时按错误信息判断是否自动 fallback 重试一次:
-
-- 可恢复错误会触发 fallback:超时、限流(rate limit / too many requests)、模型不可用、`503` / `504` 等临时不可用。
-- 不会吞掉的错误,直接抛回调用方:`400` 参数错误、`invalid request` / `bad request`、业务错误、tool 执行错误。
-- fallback 目标模型等于当前模型时不重试,避免空转。
-- 只重试一次;fallback 仍失败则按原错误返回。
-
-### 6.3 reasoning 声明与 catalog
-
-!!! new "0.6.7 新增"
-
-`0.6.7` 起在 `models.` 中声明 `reasoning: true`。构建期生成的 model catalog(`OPENCLAW_MODEL_CATALOG_JSON` / `HERMES_MODEL_CATALOG_JSON`)会输出 `reasoning` 字段,Hosted UI 可据此判断是否渲染思考内容区。catalog 每条记录形如:
-
-```json
-{
- "id": "glm-5.2",
- "name": "glm-5.2",
- "api": "openai-completions",
- "input": ["text"],
- "reasoning": true
-}
-```
-
-`input` 字段用于声明多模态能力(如 `kimi-k2.7-code` 为 `["text", "image"]`);`options` 透传模型默认参数(如 temperature)。
-
-### 6.4 thinking disabled 兼容
-
-当本轮请求显式关闭思考(`reasoning.effort` 归一化为 `none` / `disabled`,或 `max_reasoning_tokens=0`)时,runtime 会向 OpenAI 兼容请求的 `extra_body` 注入:
-
-```json
-{
- "enable_thinking": false,
- "chat_template_kwargs": {"enable_thinking": false}
-}
-```
-
-这套字段是 DeepSeek 系模型关闭 thinking 的兼容写法;对不识别该字段的模型无副作用。流式输出阶段会过滤 reasoning output item,确保关闭思考时不会向客户端回吐思考内容。
-
-```mermaid
-flowchart LR
- classDef model fill:#dbeafe,stroke:#1d4ed8,stroke-width:2px,color:#1e3a8a;
- classDef fallback fill:#fee2e2,stroke:#dc2626,stroke-width:2px,color:#7f1d1d;
- classDef drop fill:#f3f4f6,stroke:#6b7280,stroke-width:2px,color:#374151;
-
- Req["请求 model=primary"]:::model --> Call1["调用 primary 模型"]:::model
- Call1 -->|"超时/限流/503/504/模型不可用"| FB["fallback 重试一次 deepseek-v4-pro"]:::fallback
- Call1 -->|"400/业务错误/tool 错误"| Err["直接抛回调用方,不 fallback"]:::drop
- FB --> OK["返回结果"]:::model
- Call1 -->|"成功"| OK
- FB -->|"仍失败"| Err
-```
-
-## 7. Framework、挂盘与 workspace 约定
-
-### 7.1 默认 PVC 规则
-
-来自 `ksadk/cli/storage.py` 的统一约束:
-
-- 默认容量:`20Gi`
-- 最小容量:`20Gi`
-- 最大容量:`500Gi`
-
-### 7.2 默认挂载目录
-
-| Framework | 默认挂载目录 | workspace 逻辑根 | 当前代码里可直接确认的绝对路径 |
-| --- | --- | --- | --- |
-| `adk` | `/home/node/.agentengine` | `workspace:/` | 运行时对外统一以逻辑根 `workspace` 暴露 |
-| `langchain` | `/home/node/.agentengine` | `workspace:/` | 运行时对外统一以逻辑根 `workspace` 暴露 |
-| `langgraph` | `/home/node/.agentengine` | `workspace:/` | 运行时对外统一以逻辑根 `workspace` 暴露 |
-| `deepagents` | `/home/node/.agentengine` | `workspace:/` | 运行时对外统一以逻辑根 `workspace` 暴露 |
-| `hermes` | `/home/node/.hermes` | `workspace:/` | `/home/node/.hermes/workspace` |
-| `openclaw` | `/home/node/.openclaw` | `workspace:/` | `/home/node/.openclaw/workspace` |
-
-补充说明:
-
-- 本地 `ksadk server` 的 workspace 根目录是 `/.agentengine/ui/workspace`。
-- 对外 CLI 和 Hosted UI 一律展示逻辑根 `workspace:/...`。
-- 当运行时响应里带有 `workspace_real_root` 或 `workspace_path` 时,CLI 会同时显示“实际目录”。
-
-```mermaid
-flowchart TB
- classDef runtime fill:#e2e8f0,stroke:#475569,stroke-width:2px,color:#1e293b;
- classDef storage fill:#ffedd5,stroke:#ea580c,stroke-width:2px,color:#9a3412;
- classDef data fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#166534;
-
- Local["本地项目"]:::runtime --> LocalRoot[".agentengine/ui/workspace"]:::storage
- Generic["adk / langchain / langgraph / deepagents"]:::runtime --> GenericMount["/home/node/.agentengine"]:::storage
- Hermes["hermes"]:::runtime --> HermesRoot["/home/node/.hermes/workspace"]:::storage
- OpenClaw["openclaw"]:::runtime --> OpenClawRoot["/home/node/.openclaw/workspace"]:::storage
- LocalRoot --> Logical["逻辑展示统一为 workspace:/"]:::data
- GenericMount --> Logical
- HermesRoot --> Logical
- OpenClawRoot --> Logical
-```
-
-## 8. `build / deploy / launch` 参数
-
-### 8.1 构建体积与依赖策略
-
-`agentengine build` 默认优先保持源码包轻量,不会把所有平台增强能力的重依赖都打进包:
-
-- 默认包含:KsADK runtime 必需依赖、附件基础解析依赖、`kingsoftcloud-sdk-python`、`requests-aws4auth`。
-- 推荐多模态图片写法:让支持图片的模型直接消费 OpenAI Responses `input_image` / runner `input_content`,不要为了“看图”默认启用本地 OCR。
-- 兼容 OCR 写法:如果业务明确需要平台先把图片转成 `current_attachment_results[*].text`,再设置 `KSADK_BUILD_ENABLE_ATTACHMENT_OCR=true`,或在项目 `requirements.txt` 显式写入 OCR 依赖。
-- MCP adapter:默认不打包;当项目 import `mcp` / `langchain_mcp_adapters`,或 `.env` 配置了非空 `KSADK_MCP_SERVERS` 时自动加入。自动发现不到时可设置 `KSADK_BUILD_ENABLE_MCP=true`。
-- PostgreSQL session:默认不打包 `asyncpg`;当 `.env` 设置 `KSADK_SESSION_BACKEND=postgres` 或 PostgreSQL DSN 时自动加入。自动发现不到时可设置 `KSADK_BUILD_ENABLE_POSTGRES_SESSION=true`。
-
-构建会复用 `.agentengine/code_build/pip_cache`,依赖清单未变化时也会复用 `.agentengine/code_build/linux_deps`,避免第二次构建从头下载。`pip install` 默认超时为 45 分钟,可用 `KSADK_BUILD_PIP_INSTALL_TIMEOUT_SECONDS` 调整。
-
-构建完成会打印 zip 体积、解压体积和 Top 体积来源。只有当解压体积超过 500 MB 或 zip 超过 300 MB 时,才会提示切换 container 模式:
-
-```bash
-agentengine build . --mode container --push --registry
-```
-
-源码包里依赖本身很多时,优先建议业务拆分不必要依赖、使用环境变量显式关闭未用能力、或切到已有的 container 模式;本轮不建议把 `ksadk` 内置进固定 base 镜像,因为 SDK 更新频繁,固定 base 镜像会降低版本灵活性。
-
-### 8.2 部署、存储与网络参数
-
-以下参数在 `deploy`、`launch` 以及对应 framework 命令中统一存在:
-
-- `--storage-size-gi`
-- `--storage-mount-path`
-- `--no-storage`
-
-以下 network 参数在 `agentengine deploy`、`agentengine launch` 和 `agentengine openclaw deploy` 中统一存在:
-
-- `--enable-public-access / --disable-public-access`
-- `--enable-vpc-access`
-- `--vpc-id`
-- `--subnet-id`
-- `--security-group-id`
-- `--availability-zone`
-
-示例:
-
-```bash
-agentengine deploy . --target serverless --storage-size-gi 50
-agentengine launch . --target kce --storage-mount-path /home/node/.agentengine
-agentengine hermes deploy --storage-size-gi 20
-agentengine openclaw deploy --no-storage
-```
-
-VPC 网络示例:
-
-```bash
-agentengine deploy . \
- --target serverless \
- --disable-public-access \
- --enable-vpc-access \
- --vpc-id vpc-xxx \
- --subnet-id subnet-xxx \
- --security-group-id sg-xxx \
- --availability-zone cn-beijing-6a
-
-agentengine launch . \
- --enable-vpc-access \
- --vpc-id vpc-xxx \
- --subnet-id subnet-xxx \
- --security-group-id sg-xxx
-
-agentengine openclaw deploy \
- --enable-vpc-access \
- --vpc-id vpc-xxx \
- --subnet-id subnet-xxx \
- --security-group-id sg-xxx
-```
-
-配置文件也可写入 network。CLI 显式参数优先级高于配置文件:
-
-```yaml
-network:
- enable_public_access: false
- enable_vpc_access: true
- vpc_id: vpc-xxx
- subnet_id: subnet-xxx
- security_group_id: sg-xxx
- availability_zone: cn-beijing-6a
-
-deploy:
- network:
- enable_public_access: false
- enable_vpc_access: true
- vpc_id: vpc-xxx
- subnet_id: subnet-xxx
- security_group_id: sg-xxx
-```
-
-行为要点:
-
-- 不传时使用框架默认挂载目录。
-- 容量会在客户端侧先做 `20~500Gi` 校验。
-- `--no-storage` 会显式关闭默认 PVC 挂载。
-- 只要开启 VPC 访问,或传入 `--vpc-id` / `--subnet-id` / `--security-group-id` 中任意一个,就必须同时具备 `VpcId`、`SubnetId`、`SecurityGroupId`。
-- `--availability-zone` 是可选字段,不替代子网或安全组。
-
-### 8.3 运行时环境变量 `--env` / `--env-file`
-
-!!! new "0.6.7 新增"
-
-`agentengine deploy` 与 `agentengine launch` 支持显式透传运行时环境变量,不再只依赖项目根 `.env`:
-
-```bash
-# 多次 --env 传 KEY=VALUE
-agentengine deploy . --target serverless \
- --env MODEL_NAME=glm-5.2 \
- --env LOG_LEVEL=DEBUG
-
-# 或用一个 .env / JSON 对象文件
-agentengine launch . --target kce --env-file ./prod.env
-```
-
-- `--env` 可重复传入,格式 `KEY=VALUE`;变量名必须为合法环境变量名(`[A-Za-z_][A-Za-z0-9_]*`)。
-- `--env-file` 支持 `.env`(dotenv)或 JSON 对象文件;路径不存在或格式不合法会直接报错退出。
-- `--env` 与 `--env-file` 可同时使用,同名变量以 `--env` 为准(命令行优先级高于文件)。
-
-`.env` 构建上下文边界:
-
-- 真实 `.env` 只通过 deploy payload 注入 runtime,不会进入镜像构建上下文或源码包。
-- 构建期会复制 `.env.example`(作为模板),但跳过真实 `.env`,避免凭证被打进镜像。
-- `.git`、`__pycache__`、`node_modules`、`.pytest_cache` 等同样不会进入构建上下文。
-
-### 8.4 镜像仓库凭证(KCR 企业版 / 个人版 / 第三方)
-
-镜像拉取凭证按目标镜像仓库地址自动判别类型,避免企业版/第三方误用 `KSYUN_ACCOUNT_ID`:
-
-| 仓库类型 | 判别规则 | 凭证要求 |
-| --- | --- | --- |
-| 企业版 KCR | host 以 `.ksyunkcr.com` 结尾 | 必须配 `KCR_USERNAME` + `KCR_PASSWORD` |
-| 个人版 KCR | host 以 `.kce.ksyun.com` 结尾 | `KCR_USERNAME` 可留空,运行时用 `KSYUN_ACCOUNT_ID` 兜底;`KCR_PASSWORD` 必填 |
-| 第三方镜像仓库 | 其他 host | 必须配 `KCR_USERNAME` + `KCR_PASSWORD` |
-
-```bash
-agentengine config
-# 个人版 KCR 可留空 KCR_USERNAME,运行时使用 KSYUN_ACCOUNT_ID 作为用户名兜底
-# 企业版 KCR 和第三方镜像仓库必须配置 KCR_USERNAME + KCR_PASSWORD
-```
-
-行为要点:
-
-- 检测到 `KCR_PASSWORD` 但缺少 `KCR_USERNAME`,且仓库不是个人版 KCR 时,CLI 会忽略该凭证并给出告警,不会用错误的用户名去拉私有镜像。
-- 个人版 KCR 在缺 `KCR_USERNAME` 时,会用 `KSYUN_ACCOUNT_ID` 作为用户名兜底。
-- KCR 访问凭证获取:`https://kcr.console.ksyun.com/` → 访问凭证。
-
-### 8.5 Custom UI 与 RuntimeCapabilities
-
-!!! new "0.6.7 新增"
-
-Agent 可以声明自己的 Hosted UI bundle,替代内置统一 UI。两种声明方式:
-
-1. 在项目根 `agentengine.yaml`(或 `ksadk.yaml` / `ksadk.yml`)中声明:
-
-```yaml
-ui_profile: custom
-ui_path: /
-ui_bundle_path: research-ui/dist
-```
-
-2. 不写配置时,`agentengine web` 与 runtime 会自动探测项目根 `research-ui/dist/index.html`:存在即视为 custom UI bundle,自动启用 `ui_profile=custom`。
-
-Hosted 部署下,bootstrap 会返回 `RuntimeCapabilities` 字段,声明当前 runtime 支持的能力位,Hosted UI 据此决定是否渲染对应入口。当前包含的能力位:
-
-| 能力字段 | 含义 |
-| --- | --- |
-| `CancelRun` | 是否支持取消正在运行的 run |
-| `ResumeRun` | 是否支持从 checkpoint 恢复长任务(含 `Supported` 子字段) |
-
-`ResumeRun.Supported=true` 时,`ListSessionCheckpoints` 返回的每条 checkpoint 会带 `ResumeDisabled` / `ResumeDisabledReason`,标记哪些恢复点当前可用。已恢复过的 checkpoint 在当前策略下不允许重复恢复。
-
-## 9. 长任务恢复与 CancelRun / ResumeRun
-
-!!! new "0.6.7 新增"
-
-长任务(长 LLM 生成、多步 tool 编排、流式任务)可能因超时、用户主动中断或异常退出而中断。`0.6.7` 起补齐用户向的恢复流程,避免长任务丢失进度。
-
-用户侧典型流程:
-
-```mermaid
-sequenceDiagram
- participant U as 用户
- participant UI as Hosted / Local UI
- participant RT as Conversation Runtime
-
- U->>UI: 发起长任务请求
- UI->>RT: /v1/responses (stream=true)
- RT-->>UI: response.created / output_text.delta ...
- U->>UI: 点击「取消」
- UI->>RT: CancelRun(InvocationId)
- RT-->>UI: Status=cancelling
- Note over RT: 运行被中断,写入 checkpoint
- U->>UI: 刷新会话
- UI->>RT: ListSessionEvents(SessionId, Offset, Limit)
- RT-->>UI: 已落盘事件 + Total
- U->>UI: 选择「从恢复点继续」
- UI->>RT: ListSessionCheckpoints(AgentId, SessionId)
- RT-->>UI: checkpoints (含 ResumeDisabled 标记)
- UI->>RT: ResumeRun(AgentId, SessionId, RunId, CheckpointId)
- RT-->>UI: 从恢复点继续生成
-```
-
-关键 action:
-
-- `CancelRun`:传入 `InvocationId`(即 `run_id`)取消正在运行的流式任务。runtime 会先尝试取消进程内 detached stream,再调用 runner 的 cancel 接口;返回 `Cancelled`、`Found`、`Status`、`RunnerCancelStatus`。
-- `ListSessionCheckpoints`:列出某个会话可恢复的 checkpoint,支持 `OnlyResumable` 过滤、`Offset` / `Limit` 分页(`Limit` 上限 500)。
-- `ResumeRun`:传入 `AgentId` / `SessionId` / `RunId` / `CheckpointId` 从指定恢复点继续。同一 session+run 已有进行中的 resume 时会返回 `resume_already_running`,避免并发重复恢复。
-
-!!! warning "不可恢复的 checkpoint"
-- 已是终态的 checkpoint 不可恢复(`ResumeDisabledReason` 会提示「选择更早恢复点重跑」)。
-- 进程内 checkpoint 不能跨实例恢复。
-- 同一 checkpoint 在当前策略下不允许重复恢复。
-
-## 10. `agentengine files` 工作区文件管理
-
-### 10.1 子命令清单
-
-- `agentengine files list`
-- `agentengine files upload`
-- `agentengine files download`
-- `agentengine files delete`
-- `agentengine files push`
-- `agentengine files pull`
-
-### 10.2 路径语义
-
-- 远端路径统一是 workspace 相对路径。
-- `.`、空字符串和 `/` 会被解释为逻辑根 `workspace:/`。
-- 输出里会同时给出:
- - 逻辑路径:`workspace:/docs/readme.md`
- - 真实路径:当运行时返回真实根目录时,显示绝对路径
-
-### 10.3 常用示例
-
-列目录:
-
-```bash
-agentengine files list --path .
-agentengine files list --path docs --recursive
-```
-
-上传单文件:
-
-```bash
-agentengine files upload \
- --local-path ./report.md \
- --remote-path reports/report.md
-```
-
-下载文件:
-
-```bash
-agentengine files download \
- --remote-path reports/report.md \
- --output-path ./downloads/report.md
-```
-
-删除文件:
-
-```bash
-agentengine files delete --remote-path reports/report.md
-```
-
-推送目录:
-
-```bash
-agentengine files push \
- --local-dir ./dist \
- --remote-path releases/current
-```
-
-拉取目录:
-
-```bash
-agentengine files pull \
- --remote-path releases/current \
- --local-dir ./synced
-```
-
-### 10.4 `push / pull` 覆盖策略
-
-- 默认不会强制覆盖已有文件。
-- 加 `--force` 时,已有同名文件会进入 `overwritten` 结果集。
-- 输出会区分:
- - `created`
- - `overwritten`
- - `skipped`
-
-### 10.5 JSON 输出
-
-所有 `files` 子命令都可配合 `--output json` 使用。典型字段包括:
-
-- `workspace_root`
-- `workspace_display_path`
-- `workspace_real_path`
-- `entry_count`
-- `size_bytes`
-- `size_human`
-- `transport_mode`
-- `results.created / overwritten / skipped`
-
-### 10.6 大小限制
-
-当前统一上限来自 `ksadk_runtime_common.workspace_files.constants`:
-
-- 单文件上传上限:`100MB`
-
-目录同步时还有两条额外限制:
-
-- 本地目录中任一单文件不能超过上限
-- 本地目录总大小也不能超过同一个上限
-
-### 10.7 传输模式
-
-CLI 内部会在两种模式间切换:
-
-- `runtime_direct`:直连 runtime 的 `/_ksadk/workspace/v1/*`
-- `action_proxy`:经由控制面 Action 调用
-
-当前代码里的实际策略:
-
-- 常规 agent:优先使用 `runtime_direct`
-- OpenClaw:优先使用 `action_proxy`
-
-更完整的协议与安全说明见 [工作区文件技术设计](../internal/工作区文件技术设计.md)。
-
-## 11. `agentengine agent invoke`
-
-`agentengine agent invoke` 是当前主线命令;`agentengine invoke` 仍保留为兼容别名。
-
-### 11.1 常见用法
-
-```bash
-agentengine agent invoke my-agent
-agentengine agent invoke my-agent --message "你好"
-agentengine agent invoke my-agent --transport chat
-```
-
-### 11.2 Hermes 远端 native 模式
-
-`--local-workspace` 只支持 Hermes 的远端 native 模式:
-
-```bash
-agentengine agent invoke my-hermes \
- --transport native \
- --local-workspace ./local-workspace
-```
-
-可选指定远端目录:
-
-```bash
-agentengine agent invoke my-hermes \
- --transport native \
- --local-workspace ./local-workspace \
- --remote-workspace-path demos/hermes-pre
-```
-
-当前行为:
-
-- 如果不传 `--remote-workspace-path`,默认使用本地目录名作为远端子目录名
-- 本地空目录不会被同步
-- 会先读取 `GetAgentUiBootstrap` 中的 `WorkspaceFiles.MaxUploadBytes`
-- 如 bootstrap 获取失败,则回退到默认 `100MB`
-
-### 11.3 约束
-
-- `--remote-workspace-path` 必须与 `--local-workspace` 一起使用
-- `--local-workspace` 不能和单次 `--message` 模式一起使用
-- 当前只支持 Hermes 远端 native 模式
-
-## 12. Hermes 命令主线
-
-常用命令:
-
-```bash
-agentengine hermes deploy --name hermes-demo
-agentengine hermes status
-agentengine hermes open --chat
-agentengine hermes connect
-agentengine hermes exec -- status
-```
-
-部署相关默认值:
-
-- 默认 PVC 大小:`20Gi`
-- 默认挂载目录:`/home/node/.hermes`
-- 默认 workspace 根目录:`/home/node/.hermes/workspace`
-
-## 13. OpenClaw 命令主线
-
-常用命令:
-
-```bash
-agentengine openclaw deploy
-agentengine openclaw list
-agentengine openclaw status
-agentengine openclaw gateway doctor
-agentengine openclaw channel status --probe
-```
-
-### 13.1 当前支持的记忆参数
-
-```bash
-agentengine openclaw deploy --memory-system openclaw_default
-agentengine openclaw deploy \
- --memory-system mem0 \
- --mem0-instance-id \
- --mem0-instance-name my-mem0 \
- --mem0-region cn-beijing-6
-```
-
-约束:
-
-- `--memory-system mem0` 时必须传 `--mem0-instance-id`
-- `--memory-system openclaw_default` 时不能再传 mem0 细节参数
-- 不显式传 `--memory-system` 时,CLI 不会主动覆盖现有服务端配置
-
-### 13.2 当前 mem0 行为
-
-- OpenClaw 镜像内置 mem0 插件资产
-- bootstrap 默认把 `openclaw-mem0` 视为延迟同步插件
-- 只有在存在 `MEMORY_BACKEND_MANIFEST` 且渲染结果要求该插件时,才会把插件真正同步到实例目录
-- 不使用 mem0 时,不会把该插件种到实例的持久化状态里
-
-### 13.3 OpenClaw 存储默认值
-
-- 默认 PVC 大小:`20Gi`
-- 默认挂载目录:`/home/node/.openclaw`
-- 默认 workspace 根目录:`/home/node/.openclaw/workspace`
-
-更多 OpenClaw 细节见 [OpenClaw一键部署指南](../reference/openclaw一键部署指南.md)。
-
-## 14. 常见验证项
-
-### 14.1 验证工作区文件
-
-```bash
-agentengine files list --output json
-agentengine files push --local-dir ./workspace --remote-path demo
-agentengine files pull --remote-path demo --local-dir ./downloaded --force
-```
-
-### 14.2 验证 Hermes remote workspace
-
-```bash
-agentengine agent invoke hermes-demo \
- --transport native \
- --local-workspace ./workspace
-```
-
-### 14.3 验证 OpenClaw mem0 参数
-
-```bash
-agentengine openclaw deploy \
- --memory-system mem0 \
- --mem0-instance-id e52b7fac-e641-4b34-b9f7-6b0b9f190cd4
-```
-
-## 15. 相关文档
-
-- [ksadk技术设计](../reference/ksadk技术设计.md)
-- [工作区文件技术设计](../internal/工作区文件技术设计.md)
-- [记忆使用指南](./记忆使用指南.md)
-- [知识库与记忆示例](./知识库与记忆示例.md)
-- [OpenClaw一键部署指南](../reference/openclaw一键部署指南.md)
-- [DeepAgents说明](./DeepAgents说明.md)
diff --git "a/docs/guides/\347\237\245\350\257\206\345\272\223\344\270\216\350\256\260\345\277\206\347\244\272\344\276\213.md" "b/docs/guides/\347\237\245\350\257\206\345\272\223\344\270\216\350\256\260\345\277\206\347\244\272\344\276\213.md"
deleted file mode 100644
index 0cb5216a..00000000
--- "a/docs/guides/\347\237\245\350\257\206\345\272\223\344\270\216\350\256\260\345\277\206\347\244\272\344\276\213.md"
+++ /dev/null
@@ -1,114 +0,0 @@
-# 知识库与记忆示例
-
-本文档给出当前仓库可直接复用的知识库(KB)与长期记忆(LTM)组合示例,口径以现有示例目录、工具函数和测试为准。
-
-## 1. 能力矩阵
-
-| 能力 | 入口 | 典型用途 |
-| --- | --- | --- |
-| 知识库检索 | `search_knowledge` / `search_knowledge_base` | RAG、文档检索 |
-| 平台长期记忆 | `load_memory` / `save_memory` | 用户偏好、跨会话结论 |
-| ADK 长期记忆 | `LongTermMemory.from_env()` | ADK 场景下的自动检索与持久化 |
-
-## 2. 最小准备项
-
-### 2.1 KB
-
-- `KSADK_KB_DATASET_ID`
-- `KSADK_KB_ACCESS_KEY`
-- `KSADK_KB_SECRET_KEY`
-
-可选:
-
-- `KSADK_KB_REGION`
-- `KSADK_KB_ENDPOINT`
-- `KSADK_KB_SCHEME`
-- `KSADK_KB_TOP_K`
-
-### 2.2 LTM
-
-- `KSADK_LTM_BACKEND`
-
-后端相关:
-
-- `http`:`KSADK_LTM_HTTP_URL`、`KSADK_LTM_HTTP_TOKEN`
-- `sdk`:`KSADK_LTM_ACCESS_KEY`、`KSADK_LTM_SECRET_KEY` 等
-
-## 3. ADK 示例
-
-参考目录:`examples/knowledge_base_adk/`
-
-```python
-from google.adk.agents import Agent
-from ksadk.knowledge_base.adk_tool import search_knowledge_base
-
-root_agent = Agent(
- name="knowledge_base_assistant",
- tools=[search_knowledge_base],
-)
-```
-
-运行:
-
-```bash
-cd examples/knowledge_base_adk
-agentengine run .
-```
-
-## 4. 跨框架记忆工具示例
-
-```python
-from ksadk.memory.tool import load_memory, save_memory
-
-memory_text = load_memory("这个用户对输出风格有什么偏好")
-save_memory("用户偏好:回答先给结论,再给分步说明")
-```
-
-## 5. KB + LTM 组合模式
-
-推荐顺序:
-
-1. 先用 KB 回答客观事实
-2. 再用 LTM 补用户偏好与历史决策
-3. 回复结束后保存本轮结论
-
-```mermaid
-flowchart LR
- classDef client fill:#dbeafe,stroke:#1d4ed8,stroke-width:2px,color:#1e3a8a;
- classDef data fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#166534;
- classDef runtime fill:#e2e8f0,stroke:#475569,stroke-width:2px,color:#1e293b;
-
- Question["用户问题"]:::client --> KB["search_knowledge"]:::data
- Question --> LTM["load_memory"]:::data
- KB --> Reason["LLM 组织答案"]:::runtime
- LTM --> Reason
- Reason --> Save["save_memory"]:::data
-```
-
-## 6. OpenClaw 场景
-
-OpenClaw deploy 当前支持:
-
-```bash
-agentengine openclaw deploy --memory-system openclaw_default
-agentengine openclaw deploy --memory-system mem0 --mem0-instance-id
-```
-
-说明:
-
-- `openclaw_default` 不需要 mem0 实例参数
-- `mem0` 由 server 注入环境变量并下发 manifest
-- runtime 只负责渲染 patch 和按需同步插件
-
-## 7. 建议验证入口
-
-- `tests/test_platform_memory_tools.py`
-- `tests/unit/memory/test_adk_memory_comprehensive.py`
-- `tests/test_runtime_common_memory_backend.py`
-- `tests/unit/knowledge_base/test_client_env.py`
-
-## 8. 相关文档
-
-- [记忆使用指南](./记忆使用指南.md)
-- [ksadk使用文档](./ksadk使用文档.md)
-- [OpenClaw一键部署指南](../reference/openclaw一键部署指南.md)
diff --git "a/docs/guides/\350\256\260\345\277\206\344\275\277\347\224\250\346\214\207\345\215\227.md" "b/docs/guides/\350\256\260\345\277\206\344\275\277\347\224\250\346\214\207\345\215\227.md"
deleted file mode 100644
index 46ac67e2..00000000
--- "a/docs/guides/\350\256\260\345\277\206\344\275\277\347\224\250\346\214\207\345\215\227.md"
+++ /dev/null
@@ -1,227 +0,0 @@
-# 记忆使用指南
-
-本文档说明 `ksadk-python` 当前代码里可直接使用的记忆能力,分为两条主线:
-
-1. 平台长期记忆工具:`load_memory` / `save_memory`
-2. OpenClaw runtime memory backend:`openclaw_default` / `mem0`
-
-## 1. 能力分层
-
-```mermaid
-flowchart TB
- classDef client fill:#dbeafe,stroke:#1d4ed8,stroke-width:2px,color:#1e3a8a;
- classDef control fill:#ede9fe,stroke:#7c3aed,stroke-width:2px,color:#581c87;
- classDef data fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#166534;
- classDef runtime fill:#e2e8f0,stroke:#475569,stroke-width:2px,color:#1e293b;
-
- Tool["load_memory / save_memory"]:::client --> Service["LongTermMemoryService.from_env()"]:::runtime
- ADK["LongTermMemory.from_env()"]:::client --> Service
- OpenClaw["MEMORY_BACKEND_MANIFEST"]:::control --> Backend["ksadk_runtime_common.memory_backend"]:::runtime
- Service --> BackendImpl["local / http / sdk backend"]:::data
- Backend --> Mem0["mem0 provider"]:::data
- Backend --> Default["openclaw_default provider"]:::data
-```
-
-## 2. 平台长期记忆工具
-
-### 2.1 入口
-
-文件:`ksadk/memory/tool.py`
-
-- `load_memory(query: str) -> str`
-- `save_memory(content: str) -> str`
-
-### 2.2 行为约束
-
-- 依赖运行时上下文 `platform_invocation_context`
-- 无上下文时不会盲写,而是返回诊断信息
-- 保存时会附带 `agent_id / user_id / session_id / runner_type` 等元数据
-
-### 2.3 使用示例
-
-```python
-from ksadk.memory.tool import load_memory, save_memory
-
-history = load_memory("用户之前提过哪些偏好")
-save_memory("用户偏好:先给结论,再给简短原因")
-```
-
-## 3. ADK 记忆服务
-
-文件:`ksadk/memory/adk/long_term_memory.py`
-
-核心入口:
-
-```python
-from ksadk.memory.adk.long_term_memory import LongTermMemory
-
-ltm = LongTermMemory.from_env(app_name="demo_app")
-```
-
-当前行为:
-
-- 只持久化用户事件
-- 会把检索结果转换成 ADK `MemoryEntry`
-- 支持 `local`、`http`、`sdk` 三类后端
-
-## 4. 环境变量
-
-### 4.1 通用 LTM 环境变量
-
-- `KSADK_LTM_BACKEND`:`local` / `http` / `sdk`
-- `KSADK_LTM_TOP_K`:默认 `5`
-- `KSADK_LTM_INDEX`
-- `KSADK_LTM_APP_NAME`
-
-### 4.2 `http` 后端
-
-- `KSADK_LTM_HTTP_URL`
-- `KSADK_LTM_HTTP_TOKEN`
-
-### 4.3 `sdk` 后端
-
-- `KSADK_LTM_ACCESS_KEY`
-- `KSADK_LTM_SECRET_KEY`
-- `KSADK_LTM_REGION`
-- `KSADK_LTM_ENDPOINT`
-- `KSADK_LTM_SCHEME`
-- `KSADK_LTM_NAMESPACE`:环境变量名保持不变,值对应新版 SDK 的 `MemoryCollectionId`
-- `KSADK_LTM_AGENT_ID`
-- `KSADK_LTM_SCENE_ID`:默认 `_sys_general`
-- `KSADK_LTM_AUTO_SAVE`:布尔开关。默认在 `KSADK_LTM_BACKEND=sdk` 且 `KSADK_LTM_NAMESPACE` 已设置时开启;设为 `false` / `0` / `off` 可关闭 runtime 每轮完成后的会话文本镜像。
-
-自动镜像只写 user/assistant 文本和附件摘要,metadata 会包含 `agent_id`、`session_id`、`invocation_id`、`model`、`runner_type`。图片、文件 base64 和二进制内容不会写入长期记忆;完整附件回显仍由 AgentEngine conversation 存储负责。
-
-!!! info "invocation_id 与账号边界从哪来(0.6.5 新增)"
- `load_memory` / `save_memory` 不接收显式的 user/session 参数,而是通过 `ksadk.runtime_context.get_current_invocation_context()` 读取当前运行时上下文,取其中的 `user_id` / `session_id` / `runner_type` 等字段写入记忆元数据,`invocation_id` 即来自该上下文。`account_id` 字段同样来自此上下文,用于在多账号共享同一记忆后端时做账号隔离。
-
- 业务侧若要在工具或自定义流程里直接拿到账号标识,可用 0.6.5 新增的便捷函数:
-
- ```python hl_lines="2"
- from ksadk.runtime_context import get_current_account_id
- account_id = get_current_account_id(default="")
- ```
-
- `get_current_account_id()` 同样基于 `get_current_invocation_context()`;上下文缺失时返回传入的 `default`(默认空串),不会抛异常。`get_current_user_id()` 是其姊妹函数,行为一致。
-
-## 5. OpenClaw 的记忆后端声明
-
-### 5.1 CLI 入口
-
-```bash
-agentengine openclaw deploy --memory-system openclaw_default
-agentengine openclaw deploy --memory-system mem0 --mem0-instance-id
-```
-
-### 5.2 当前支持的 backend
-
-| backend_type | 含义 |
-| --- | --- |
-| `openclaw_default` | 保持 OpenClaw 默认记忆模式 |
-| `mem0` | 使用 mem0 插件和平台注入的环境变量 |
-| `lancedb` | 进程内 LanceDB OpenClaw 记忆插件(plugin id `memory-lancedb`)。通过 `MEMORY_BACKEND_MANIFEST` 直接声明,不走 `agentengine openclaw deploy --memory-system` CLI 入口;manifest 可选覆盖 `dbPath`、embedding 配置和 storage 选项。 |
-
-### 5.2.1 `lancedb` manifest 示例
-
-`lancedb` 后端通过 `MEMORY_BACKEND_MANIFEST` 声明,manifest 经 JSON Schema 校验后由 `LanceDBProvider` 渲染成 `memory-lancedb` 插件配置。`config.dbPath` 可选,不传时使用插件默认路径:
-
-```json
-{
- "schema_version": "v1",
- "backend_type": "lancedb",
- "config": {
- "dbPath": "/home/node/.openclaw/memory/lancedb"
- }
-}
-```
-
-渲染后 `plugins.entries.memory-lancedb.config.dbPath` 会被原样透传,`plugins.slots.memory` 指向 `memory-lancedb`,并自动禁用 `openclaw-mem0` 插件。`config.embedding` 和 `config.storageOptions` 同样为可选覆盖项。
-
-### 5.3 manifest 契约
-
-`ksadk_runtime_common.memory_backend.manifest` 当前强制按 JSON Schema 校验。即使调用方直接传 `MemoryBackendManifest` 模型实例,也会先 `model_dump()` 再走 schema 校验。
-
-### 5.4 `mem0` provider 约束
-
-当前 `mem0` 渲染要求:
-
-- `config.mem0_instance_id` 必填
-- 运行时环境变量必须存在:
- - `MEM0_API_KEY`
- - `MEM0_USER_ID`
- - `MEM0_BASE_URL`
-
-渲染结果会生成:
-
-- `plugins.slots.memory = "openclaw-mem0"`
-- `plugins.entries.openclaw-mem0.enabled = true`
-- `plugins.entries.openclaw-mem0.config.mode = "platform"`
-
-## 6. 服务端与 runtime 的分工
-
-```mermaid
-sequenceDiagram
- autonumber
- participant CLI as agentengine openclaw deploy
- participant Server as agentengine-server
- participant Runtime as OpenClaw bootstrap
- participant Render as ksadk_runtime_common.memory_backend
-
- CLI->>Server: MemoryConfig
- Server->>Server: 查询 mem0 实例并生成 env + manifest
- Server-->>Runtime: MEMORY_BACKEND_MANIFEST + MEM0_*
- Runtime->>Render: render_to_json()
- Render-->>Runtime: config_patch + plugin_ids
- Runtime->>Runtime: 写入 openclaw.json 并按需同步插件
-```
-
-分工边界:
-
-- server 是 mem0 实例查询、校验和密钥拼装的事实源
-- runtime 只消费 manifest 和环境变量,不直接查询 mem0 控制面
-
-## 7. 常见排查
-
-### 7.1 `load_memory` 返回诊断文本
-
-通常表示当前调用不在受支持的运行时上下文里。
-
-### 7.2 `sdk` 后端初始化失败
-
-优先检查:
-
-- 依赖是否安装
-- AK/SK 是否齐全
-- `KSADK_LTM_ENDPOINT` 与网络环境是否匹配
-
-### 7.3 OpenClaw mem0 渲染失败
-
-当前渲染失败会直接指出缺失的环境变量,例如:
-
-- `mem0 backend requires environment variable 'MEM0_API_KEY'`
-
-### 7.4 OpenClaw 启动后配置校验失败
-
-优先检查:
-
-- `MEMORY_BACKEND_MANIFEST` 是否为合法 JSON
-- manifest 中 `mem0_instance_id` 是否满足 schema 要求
-- runtime 中的 mem0 插件是否按渲染结果被同步
-
-### 7.5 LanceDB `dbPath` 写入失败(0.6.7 新增)
-
-`lancedb` 后端在容器内初始化时,若指定的 `config.dbPath` 目录不存在或无写权限,`memory-lancedb` 插件启动会失败。优先检查:
-
-- `dbPath` 所在目录是否已挂载为可写卷,Pod 重启后路径是否仍持久化
-- 运行时进程对该路径是否有读写权限(OpenClaw runtime 通常以非 root 用户运行)
-- `MEMORY_BACKEND_MANIFEST` 是否为合法 JSON,且 `config.dbPath` 是绝对路径
-- 若不传 `dbPath`,确认插件默认路径在当前部署环境可写
-
-!!! warning "校验仍走 schema"
- 即使 `dbPath` 目录不存在,manifest 也会先通过 JSON Schema 校验(schema 只校验字段类型,不校验路径可达性)。路径/权限问题在 runtime 拉起插件时才暴露,排查时以 runtime 日志为准。
-
-## 8. 相关文档
-
-- [知识库与记忆示例](./知识库与记忆示例.md)
-- [ksadk技术设计](../reference/ksadk技术设计.md)
-- [OpenClaw一键部署指南](../reference/openclaw一键部署指南.md)
diff --git a/docs/maintainer-approval-record.md b/docs/maintainer-approval-record.md
index 8d07f6e3..18261e23 100644
--- a/docs/maintainer-approval-record.md
+++ b/docs/maintainer-approval-record.md
@@ -11,7 +11,7 @@ PyPI publication.
| License | Apache-2.0 |
| Python repository | kingsoftcloud/ksadk-python |
| Web UI repository | kingsoftcloud/ksadk-web |
-| Python package version | 0.6.8 |
+| Python package version | 0.6.9 |
| Public docs URL | https://kingsoftcloud.github.io/ksadk-python/ |
| Package metadata repository URL | https://github.com/kingsoftcloud/ksadk-python |
| Package metadata documentation URL | https://kingsoftcloud.github.io/ksadk-python/ |
@@ -19,19 +19,19 @@ PyPI publication.
## Publication Strategy
-Record exactly one approved source publication strategy:
+Record exactly one approved source publication strategy.
| Strategy | Approved |
| --- | --- |
| Reviewed GitHub pull request | No |
-| Clean export from reviewed candidate | No |
-| Rewritten Git history after secret scan | Yes |
+| Clean export from reviewed candidate | Yes |
+| Rewritten Git history after secret scan | No |
The approved strategy must name the reviewed commit, tag, pull request, or
export archive used for:
-- `ksadk-python`: rewritten public `new-main` candidate `3bbf295e4f27a4f7f6a8b8cdf76a17227ad40033`, verified by `make public-preflight KSADK_WEB_VERSION=0.2.16` and public source/dist audits.
-- `ksadk-web`: npm package `@kingsoftcloud/ksadk-web@0.2.16` (`latest` on 2026-07-03), bundled from npm during `make public-preflight KSADK_WEB_VERSION=0.2.16`.
+- `ksadk-python`: clean export candidate from reviewed internal commit `eb76b17d7d3c176f7cf6126ceb001ab00f5d651d`; local candidate directory `/tmp/ksadk-python-export-candidate-0.6.9`; verified on 2026-07-08 with public source audit, registry-bundled `make public-preflight` in the public candidate worktree, Fumadocs static build, wheel/sdist build, twine check, and source/dist package audits.
+- `ksadk-web`: npm package `@kingsoftcloud/ksadk-web@0.2.18` from commit `24551d0f290e5a4efc5b5d60d02fa298cccd2efa`; Python candidate commit `eb76b17d7d3c176f7cf6126ceb001ab00f5d651d`; published by the trusted GitHub npm workflow on 2026-07-08 and consumed from the npm registry during `make public-preflight`.
Both approved source references must include the current commit SHA at approval
time. This prevents a stale approval record from passing after candidate
@@ -40,7 +40,7 @@ changes.
## Required Evidence Before Approval
- `make public-preflight` exits successfully.
-- `make public-publish-check PUBLIC_PUBLISH_PHASE=pre-publish V=0.6.8` confirms
+- `make public-publish-check PUBLIC_PUBLISH_PHASE=pre-publish V=0.6.9` confirms
the target version is not already on PyPI.
- Branch protection and publish environment are configured according to
`.github/BRANCH_PROTECTION.md`.
@@ -59,6 +59,6 @@ changes.
| Role | Name | Decision | Date |
| --- | --- | --- | --- |
-| Maintainer | xiayu | Approved for one-time public main rewrite | 2026-07-03 |
-| Security reviewer | automated public audit | Passed source, wheel, and sdist audits with 0 violations | 2026-07-03 |
-| Release owner | xiayu | Approved GitHub Release / PyPI Trusted Publishing for 0.6.8 | 2026-07-03 |
+| Maintainer | xiayu | Approved clean export candidate for ksadk 0.6.9 | 2026-07-08 |
+| Security reviewer | automated public audit | Passed source, wheel, and sdist audits with 0 violations | 2026-07-08 |
+| Release owner | xiayu | Approved trusted GitHub PyPI and Pages workflow for 0.6.9 | 2026-07-08 |
diff --git "a/docs/reference/ksadk\346\212\200\346\234\257\350\256\276\350\256\241.md" "b/docs/reference/ksadk\346\212\200\346\234\257\350\256\276\350\256\241.md"
deleted file mode 100644
index bcaf6d11..00000000
--- "a/docs/reference/ksadk\346\212\200\346\234\257\350\256\276\350\256\241.md"
+++ /dev/null
@@ -1,524 +0,0 @@
-# ksadk技术设计
-
-本文档描述 `ksadk-python` 当前主线的正式技术设计。写法采用技术设计文档结构,但内容只记录已经体现在代码、测试、CLI 帮助、Dockerfile 与默认常量中的行为。
-
-## 1. 目标与边界
-
-`ksadk-python` 负责三类职责:
-
-1. 开发者入口
- 提供 `agentengine` / `ksadk` CLI,本地开发、调试、构建和部署都从这里进入。
-2. 本地运行时
- 提供本地 Web UI、会话存储、本地 workspace 数据面以及开发态调用能力。
-3. 托管运行时资产
- 提供 Hermes / OpenClaw 共享镜像、bootstrap 脚本、共享 workspace_files 与 memory_backend 源码。
-4. Skill Runtime 与内置工具消费
- 提供 Skill Space 运行时消费、Skill 包校验与加载、sandbox backend 编排、内置 toolset 绑定、Tool Gateway 审批 envelope。
-
-不在本仓承担最终事实源的能力:
-
-- Agent 生命周期持久化
-- endpoint / api_key 写回
-- Hosted UI bootstrap
-- Workspace Files Hosted Action
-- OpenClaw `MEMORY_BACKEND_MANIFEST` 生成
-- Skill 注册、CRUD、版本治理和 marketplace
-- Sandbox template、instance、token 与网络生命周期
-
-这些分别由 `agentengine-server`、Skill Service、Sandbox Service 或平台控制面负责。
-
-## 2. 总体架构
-
-```mermaid
-flowchart LR
- classDef client fill:#dbeafe,stroke:#1d4ed8,stroke-width:2px,color:#1e3a8a;
- classDef control fill:#ede9fe,stroke:#7c3aed,stroke-width:2px,color:#581c87;
- classDef data fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#166534;
- classDef storage fill:#ffedd5,stroke:#ea580c,stroke-width:2px,color:#9a3412;
- classDef runtime fill:#e2e8f0,stroke:#475569,stroke-width:2px,color:#1e293b;
-
- subgraph Client["开发者入口"]
- CLI["agentengine CLI"]:::client
- Web["agentengine web"]:::client
- Invoke["agent invoke / files"]:::client
- end
-
- subgraph Repo["ksadk-python"]
- Dispatch["命令分发与框架识别"]:::runtime
- Local["ksadk.server.app"]:::runtime
- Common["ksadk_runtime_common"]:::runtime
- Toolsets["ksadk.toolsets + Tool Gateway"]:::runtime
- SkillRT["ksadk.skills.runtime"]:::runtime
- Sandbox["ksadk.sandbox"]:::runtime
- Assets["agentengine-images: deploy/hermes + deploy/openclaw"]:::runtime
- end
-
- subgraph Control["控制面"]
- Server["agentengine-server"]:::control
- end
-
- subgraph Runtime["运行时"]
- Generic["通用 runtime"]:::data
- Hermes["Hermes"]:::data
- OpenClaw["OpenClaw"]:::data
- end
-
- subgraph Storage["持久化"]
- LocalRoot[".agentengine/ui/workspace"]:::storage
- PVC["PVC / 挂盘目录"]:::storage
- end
-
- CLI --> Dispatch
- Web --> Local
- Invoke --> Server
- Dispatch --> Local
- Dispatch --> Server
- Dispatch --> Toolsets
- Toolsets --> SkillRT
- Toolsets --> Sandbox
- Common --> Local
- Common --> Assets
- Assets --> Hermes
- Assets --> OpenClaw
- Server --> Generic
- Server --> Hermes
- Server --> OpenClaw
- Local --> LocalRoot
- Generic --> PVC
- Hermes --> PVC
- OpenClaw --> PVC
-```
-
-## 3. 代码分层
-
-### 3.1 CLI 层
-
-入口在 `pyproject.toml`:
-
-```toml
-agentengine = "ksadk.cli:main"
-```
-
-CLI 层负责:
-
-- framework 检测
-- 本地运行与调试
-- 构建产物准备
-- 调用 `agentengine-server`
-- `files` 与 `agent invoke` 的传输选择
-- framework 级默认存储参数
-
-### 3.2 本地运行时
-
-本地运行时的核心是 `ksadk.server.app`,负责:
-
-- 本地会话
-- 本地统一 Web UI
-- 本地附件上传
-- 本地 workspace files 路由
-
-当前本地目录约定:
-
-- UI 根目录:`/.agentengine/ui`
-- 本地会话:`/.agentengine/ui/sessions.sqlite`
-- 本地 workspace:`/.agentengine/ui/workspace`
-
-### 3.3 托管运行时资产
-
-Hermes / OpenClaw 运行时镜像资产曾在本仓库 `deploy/hermes/`、`deploy/openclaw/`、`deploy/openclaw-user-template/` 下维护,现已迁出至 `agentengine-images` 仓库。这些目录不仅是模板,还包含线上运行时镜像的事实约定,例如:
-
-- Hermes 的 `entrypoint.sh`
-- OpenClaw 的 `bootstrap.sh`
-- 共享 workspace sidecar 与 memory backend 渲染入口
-
-### 3.4 Skill Runtime、Sandbox 与 Toolsets
-
-`0.6.2` 起,SDK 侧新增三层运行时消费抽象:
-
-- `ksadk.skills.runtime`:负责 workflow 请求解析、Skill 选择、远端 Skill 包下载、`sha256` 校验、安全解压、runtime agent 执行和 artifacts 汇总。
-- `ksadk.sandbox`:通用 Sandbox Runtime 底座,当前首个 backend 是 E2B-compatible sandbox;Skill Runtime 和 sandbox direct tools 共用这层,不把 sandbox 语义写死为 Skill 专用。
-- `ksadk.toolsets`:给 LangGraph、LangChain、DeepAgents、ADK 或自定义 runner 暴露内置工具,包括 Skill、Workspace、Platform、Sandbox 四组工具,以及聚合入口 `get_agentengine_tools()`。
-
-推荐绑定方式是显式渐进式披露:
-
-```python
-from ksadk.toolsets import get_agentengine_tools
-
-tools = get_agentengine_tools(include=["focused", "agentengine_tool_dispatcher"])
-```
-
-`get_agentengine_tools()` 无参保持全量工具兼容。`focused/core` profile 只直接暴露 Skill 发现/加载、Workspace 状态/搜索/片段编辑/lint、组件状态和 sandbox 状态;`execute_skills`、`run_command`、`run_code`、Workspace 写入/删除等低频或高风险工具通过 `agentengine_tool_dispatcher` 按需 `list` / `describe` / `call`。
-
-Tool Gateway 位于实际工具执行前,负责风险策略和人工确认 envelope。strict 模式下,中高风险工具返回 `approval_required`,由 Hosted/local UI 或调用方回传批准后继续;dispatcher 调用真实工具对象,不绕过 Tool Gateway。
-
-```mermaid
-flowchart LR
- classDef agent fill:#dbeafe,stroke:#1d4ed8,stroke-width:2px,color:#1e3a8a;
- classDef tool fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#166534;
- classDef runtime fill:#e2e8f0,stroke:#475569,stroke-width:2px,color:#1e293b;
- classDef service fill:#ede9fe,stroke:#7c3aed,stroke-width:2px,color:#581c87;
-
- Agent["LangGraph / LangChain / ADK Agent"]:::agent --> Focused["focused tools"]:::tool
- Agent --> Dispatcher["agentengine_tool_dispatcher"]:::tool
- Focused --> Gateway["Tool Gateway"]:::runtime
- Dispatcher --> Gateway
- Gateway --> Skill["Skill tools / execute_skills"]:::tool
- Gateway --> Workspace["Workspace tools"]:::tool
- Gateway --> SandboxTools["Sandbox direct tools"]:::tool
- Skill --> SkillService["Skill Service"]:::service
- Skill --> SkillRuntime["Skill Runtime backend"]:::runtime
- SkillRuntime --> Sandbox["E2B / Sandbox backend"]:::runtime
- SandboxTools --> Sandbox
-```
-
-## 4. `ksadk_runtime_common` 同仓共享源码
-
-这是当前主线的核心去重点。
-
-```mermaid
-flowchart TB
- classDef runtime fill:#e2e8f0,stroke:#475569,stroke-width:2px,color:#1e293b;
- classDef data fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#166534;
- classDef control fill:#ede9fe,stroke:#7c3aed,stroke-width:2px,color:#581c87;
-
- Common["ksadk_runtime_common"]:::runtime
- WF["workspace_files"]:::data
- MB["memory_backend"]:::control
- Local["ksadk.server.app"]:::runtime
- Hermes["agentengine-images: deploy/hermes/runtime/app.py"]:::runtime
- OpenClaw["agentengine-images: deploy/openclaw/bootstrap.sh + workspace_files_app.py"]:::runtime
-
- Common --> WF
- Common --> MB
- WF --> Local
- WF --> Hermes
- WF --> OpenClaw
- MB --> OpenClaw
-```
-
-当前共享源码包含两块:
-
-### 4.1 `workspace_files`
-
-职责:
-
-- 统一 runtime 路由前缀
-- 统一 Hosted bootstrap payload
-- 统一路径逃逸拦截
-- 统一上传大小上限和动作常量
-
-关键常量:
-
-- `WORKSPACE_ENTRY_ACTION = "ListWorkspaceFiles"`
-- `WORKSPACE_UPLOAD_ACTION = "AddWorkspaceFile"`
-- `WORKSPACE_CONTENT_PATH = "/agentengine/api/v1/GetWorkspaceFileContent"`
-- `DEFAULT_WORKSPACE_MAX_UPLOAD_BYTES = 100MB`
-
-### 4.2 `memory_backend`
-
-职责:
-
-- 解析并校验 `MEMORY_BACKEND_MANIFEST`
-- 基于 provider 渲染 OpenClaw 需要的配置 patch
-- 返回需要同步的插件 ID 列表
-
-当前 provider:
-
-- `openclaw_default`
-- `mem0`
-
-当前 `mem0` 渲染所要求的环境变量:
-
-- `MEM0_API_KEY`
-- `MEM0_USER_ID`
-- `MEM0_BASE_URL`
-
-## 5. 存储与 workspace 设计
-
-`ksadk/cli/storage.py` 统一定义了 framework 级默认值。
-
-### 5.1 容量约束
-
-- 默认:`20Gi`
-- 最小:`20Gi`
-- 最大:`500Gi`
-
-### 5.2 默认挂载目录
-
-| Framework | 默认挂载目录 |
-| --- | --- |
-| `adk` | `/home/node/.agentengine` |
-| `langchain` | `/home/node/.agentengine` |
-| `langgraph` | `/home/node/.agentengine` |
-| `deepagents` | `/home/node/.agentengine` |
-| `hermes` | `/home/node/.hermes` |
-| `openclaw` | `/home/node/.openclaw` |
-
-### 5.3 workspace 对外语义
-
-- CLI 和 Hosted UI 统一把根目录表示为 `workspace:/`
-- Hermes 明确把 `KSADK_WORKSPACE_ROOT` 绑定到 `HERMES_WORKDIR`
-- OpenClaw 明确把 `KSADK_WORKSPACE_ROOT` 绑定到 `${OPENCLAW_STATE_DIR}/workspace`
-
-## 6. 文件访问与传输选择
-
-```mermaid
-sequenceDiagram
- autonumber
- participant U as 用户
- participant CLI as agentengine files
- participant Client as AgentEngineClient
- participant Server as agentengine-server
- participant Runtime as runtime data plane
-
- U->>CLI: files list / upload / push
- CLI->>Client: 规范化 agent_ref 与路径
- alt 常规 agent
- Client->>Runtime: 直连 /_ksadk/workspace/v1/*
- Runtime-->>Client: JSON / 文件流
- else OpenClaw 或 Hosted 场景
- Client->>Server: ListWorkspaceFiles / AddWorkspaceFile
- Server->>Runtime: 代发请求
- Runtime-->>Server: JSON / 文件流
- Server-->>Client: ActionResponse
- end
- Client-->>CLI: pretty / json 输出
-```
-
-当前策略重点:
-
-- 常规 agent 有 endpoint + api_key 时,优先 `runtime_direct`
-- OpenClaw 默认优先 `action_proxy`
-- CLI 会把逻辑路径和真实路径同时渲染到输出中
-
-## 7. `agentengine agent invoke` 与本地目录同步
-
-`agentengine agent invoke` 是远端交互入口;其中 Hermes native 模式额外接通了本地目录同步。
-
-同步前会做四件事:
-
-1. 递归扫描本地目录
-2. 校验目录非空
-3. 校验任一单文件不超过 `MaxUploadBytes`
-4. 校验目录总大小不超过同一个上限
-
-如果不传 `--remote-workspace-path`,会默认使用本地目录名作为远端子目录名。
-
-## 8. Hermes 运行时设计要点
-
-Hermes runtime 的关键事实:
-
-- Docker 构建时从仓根复制 `ksadk_runtime_common`
-- `PYTHONPATH=/opt`
-- `HERMES_HOME=/home/node/.hermes`
-- `HERMES_WORKDIR=/home/node/.hermes/workspace`
-- `KSADK_WORKSPACE_ROOT` 默认跟随 `HERMES_WORKDIR`
-
-Hermes 既承载 dashboard,又承载:
-
-- `/v1/*`
-- `/_ksadk/terminal/ws`
-- `/_ksadk/workspace/v1/*`
-
-## 9. OpenClaw 运行时设计要点
-
-OpenClaw runtime 的关键事实:
-
-- Docker 构建时从仓根复制 `ksadk_runtime_common`
-- `PYTHONPATH=/opt`
-- bootstrap 会启动 workspace files sidecar
-- sidecar 默认监听 `127.0.0.1:8091`
-- gateway 内部通过 `OPENCLAW_WORKSPACE_FILES_PROXY_URL` 转发到 sidecar
-
-### 9.1 memory backend 主链路
-
-```mermaid
-flowchart LR
- classDef control fill:#ede9fe,stroke:#7c3aed,stroke-width:2px,color:#581c87;
- classDef data fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#166534;
- classDef runtime fill:#e2e8f0,stroke:#475569,stroke-width:2px,color:#1e293b;
-
- Server["agentengine-server"]:::control --> Manifest["MEMORY_BACKEND_MANIFEST"]:::control
- Manifest --> Render["python -m ksadk_runtime_common.memory_backend.render"]:::runtime
- Render --> Patch["memory patch JSON + plugin_ids"]:::data
- Patch --> Bootstrap["bootstrap.sh"]:::runtime
- Bootstrap --> Config["openclaw.json"]:::data
- Bootstrap --> Extensions["按需同步 default extension"]:::data
-```
-
-当前链路特征:
-
-- manifest 由控制面生成
-- 渲染在 runtime 内完成
-- `mem0` 需要环境变量齐全,否则 bootstrap 直接失败
-- 插件不是无条件落盘,而是由渲染结果驱动按需同步
-- `lancedb`(`backend_type=lancedb`)走同一 manifest→render 链路,但不依赖 mem0 环境变量;渲染产出 `memory-lancedb` 插件 entry,同时把 `openclaw-mem0` 加入 `disabled_plugin_ids`,避免两个 memory 插件同时生效。manifest 可选 `config.dbPath` / `config.embedding` / `config.storageOptions` 三个 LanceDB 专属字段,由 schema 校验,缺省时插件使用内置默认。
-
-!!! info "0.6.7 新增 backend"
- LanceDB 作为进程内向量存储 backend,给 OpenClaw 提供无需外部 mem0 实例的长期记忆能力;当 `backend_type=lancedb` 时,`secrets_env` 可留空。
-
-## 10. Docker 构建与根上下文
-
-当前 Hermes / OpenClaw 镜像都采用同仓共享源码 + 根上下文构建:
-
-- `COPY ksadk_runtime_common /opt/ksadk_runtime_common`
-- `PYTHONPATH=/opt`
-- 根目录 `.dockerignore` 负责排除 `.git`、`dist`、`build`、缓存目录和本地产物
-
-收益:
-
-- 不依赖额外 wheel 仓发布
-- 共享源码与 runtime 资产同仓演进
-- Docker 构建可直接消费最新共享模块
-
-## 11. 与服务端的协作边界
-
-| 能力 | `ksadk-python` | `agentengine-server` |
-| --- | --- | --- |
-| CLI / 本地开发 | 负责 | 不负责 |
-| Agent 生命周期 | 调用方 | 真相源 |
-| Hosted UI bootstrap | 消费方 | 负责 |
-| Workspace Files runtime data plane | 负责 | 不负责 |
-| Workspace Files Hosted Action | 消费方 | 负责 |
-| Memory manifest 渲染 | 负责(含 `openclaw_default` / `mem0` / `lancedb` 三种 backend_type) | 不负责 |
-| Memory manifest 生成 | 不负责 | 负责 |
-| endpoint / api_key 写回 | 不负责 | 负责 |
-
-## 12. 平台上下文与 invocation_id
-
-!!! new "0.6.5 新增"
- 平台调用上下文(`PlatformInvocationContext`)与 `invocation_id` 贯穿 runner payload 与 OpenAI 兼容接口,为 Skill / Workspace / Sandbox / Memory 工具提供统一的账号边界读取入口。
-
-### 12.1 PlatformInvocationContext
-
-`ksadk.runtime_context.PlatformInvocationContext` 在 runner 执行前由 conversation runtime 注入到 `ContextVar`,携带 `agent_id` / `user_id` / `session_id` / `account_id` 等字段。工具实现优先读取当前调用上下文,而不是裸环境变量。
-
-```python
-from ksadk.runtime_context import (
- get_current_invocation_context_or_default,
- get_current_user_id,
- get_current_account_id,
-)
-
-ctx = get_current_invocation_context_or_default()
-user_id = get_current_user_id() # ctx.user_id
-account_id = get_current_account_id() # ctx.account_id,未注入时为空串
-```
-
-`PlatformInvocationContext.account_id` 是 0.6.5 新增字段,用于把控制面透传的账号边界下沉到工具层。
-
-### 12.2 invocation_id 与 account_id 透传
-
-`invocation_id` 作为单次调用的稳定标识,由 runner payload 与 OpenAI 兼容接口共同透传:
-
-- runner payload 携带 `invocation_id`,由 conversation runtime 进入 `platform_invocation_scope` / `tool_execution_scope`。
-- `/v1/responses`、`/v1/chat/completions` 与 `RunAgent` action 都接收并透传 `account_id`,进入 `PlatformInvocationContext`。
-- 后台 stream(`Background=true`)使用 `invocation_id` 作为 detached stream 的索引键,供 `SubscribeRunEvents` 拉起始态。
-
-```python hl_lines="3"
-# RunAgent action 透传示例(伪代码)
-result = await conversation.invoke_conversation_once(
- runner=active_runner,
- agent_id=agent_id,
- user_id=run_user_id,
- account_id=account_id, # 控制面透传的账号边界
- invocation_id=invocation_id, # 单次调用稳定标识
- session_id=resolved_session_id,
-)
-```
-
-### 12.3 工具按账号边界读取当前调用上下文
-
-Skill / Workspace / Sandbox / Memory 工具在执行时通过 `get_current_invocation_context_or_default()` 读取当前 `account_id` / `user_id` / `session_id`,使同一 runner 进程内的多账号调用互不串扰:
-
-- Memory 工具使用 `context.user_id` / `context.session_id` 限定长期记忆的读写范围。
-- Skill 工具通过 `KSYUN_ACCOUNT_ID` / `KSADK_SKILL_SERVICE_ACCOUNT_ID` 把账号写入 Skill Service 请求头 `X-Ksc-Account-Id`。
-- Workspace / Sandbox 工具通过 `tool_execution_scope` 读取 `session_id` / `run_id` / `invocation_id`,把执行范围绑定到当前调用。
-
-!!! tip "工具实现建议"
- 自定义工具优先调用 `get_current_invocation_context_or_default()`,不要直接读 `os.environ` 里的账号信息;前者反映当前调用边界,后者只反映进程启动环境。
-
-## 13. Hosted/本地附件统一解析
-
-!!! new "0.6.6 新增"
- 附件 URI 统一为两种 scheme:本地 `ksadk-upload://` 与 Hosted `ae-upload://`。runtime 侧统一解析、按需下载并恢复本地 cache。
-
-### 13.1 双 scheme 解析
-
-`ksadk.conversations.attachment_storage` 同时识别两种 scheme:
-
-| Scheme | 来源 | 解析动作 |
-| --- | --- | --- |
-| `ksadk-upload://` | 本地上传或 KS3 回填 | 直接读本地 cache 或 KS3 object |
-| `ae-upload://` | Hosted 控制面下发 | 走 KOP Action `AttachmentContent` 下载 |
-
-`parse_file_id()` 对两种 scheme 都返回去掉前缀后的 `file_id`;`is_runtime_upload_uri()` / `is_hosted_upload_uri()` 用于分支判断。
-
-### 13.2 KOP Action 下载
-
-Hosted 附件通过 `AgentEngineClient.download_attachment_content(file_uri)` 走签名后的 KOP Action API 拉取字节流,返回 `AttachmentContent`(`data` / `content_type` / `display_name`)。下载失败时返回 `None`,由上层决定是否降级。
-
-### 13.3 本地 cache 恢复链路
-
-`AttachmentStorageService.read()` 按以下顺序恢复附件字节:
-
-1. `ae-upload://` → 调用 KOP Action 下载,写入本地 cache 并落 `.meta.json`。
-2. `ksadk-upload://` 且 metadata 标记 `backend=ks3` → 读 KS3 object,失败时回退本地 cache。
-3. metadata 里有 `local_path` → 直接读本地文件。
-4. 上述都缺失 → 尝试 legacy 本地路径兜底。
-
-`_restore_local_cache()` 负责把下载字节落盘到 session files 目录并回填 `local_path`,保证同一 `invocation_id` 内的重复读取不反复走网络。
-
-## 14. 会话与事件分页
-
-!!! new "0.6.6 新增"
- `ListSessions` / `ListSessionEvents` 增加 `count_sessions` / `count_events`,返回 `Total` 供 UI 分页。
-
-会话与事件查询在原有 `offset` / `limit` 基础上新增总数统计:
-
-| Action | 入参 | 出参新增 |
-| --- | --- | --- |
-| `ListSessions` | `Page` / `PageSize`(agent_id + user_id) | `Total`(`count_sessions`) |
-| `ListSessionEvents` | `Offset` / `Limit`(session_id) | `Total`(`count_events`) |
-
-`SessionService` 基类与 `LocalSessionService` / `PostgresSessionService` 实现统一提供 `count_sessions` / `count_events`,保证本地 SQLite 与托管 PG 行为一致。
-
-## 15. Workspace 导出 facade
-
-!!! new "0.6.6 新增"
- `ExportWorkspaceZip` 作为统一 facade,把 workspace 目录打包成 zip 流式下载。
-
-`GET /agentengine/api/v1/ExportWorkspaceZip?path=` 由 `ksadk_runtime_common.workspace_files.router` 提供,本地 `ksadk.server.app` 与 OpenClaw sidecar 共用同一 handler。`path` 默认为 `.`(workspace 根),导出前做路径逃逸拦截,符号链接逃逸会被拒绝。
-
-## 16. Custom UI 配置体系与 checkpoint/resume
-
-!!! new "0.6.7 新增"
- Custom UI profile 与 LangGraph checkpoint/resume 能力层在 0.6.7 稳定。
-
-### 16.1 Custom UI 配置体系
-
-`ksadk.ui_config.resolve_ui_config()` 按「CLI 参数 → `.agentengine.state` → framework 默认 → 全局默认」优先级合并出最终 `UIConfig`(`profile` / `path` / `url`)。`ui_profile=custom` 时:
-
-- `path` 默认 `/`(不复用 `/chat`)。
-- 本地 `agentengine web` / `agentengine dashboard` 通过 `_configure_custom_ui_env()` 解析 custom bundle 目录并挂载静态资源。
-- Hosted 侧由控制面把 `ui_profile` / `ui_path` / `ui_url` 写入 state,runtime 读取后路由到 custom bundle。
-
-支持 profile 列表:`auto` / `adk` / `langchain` / `openclaw` / `hermes` / `custom`。
-
-### 16.2 LangGraph checkpoint/resume 能力层
-
-`LangGraphRunner` 暴露 checkpoint 描述与恢复能力:
-
-- `describe_checkpoint_capability()` 返回 `Supported` / `Backend` / `Scope` / `Durable` / `Reason`,供 UI 判断是否可 resume。
-- `_latest_checkpoint_metadata()` 从 `aget_state(config)` 提取 `thread_id` / `checkpoint_ns` / `checkpoint_id` / `next_node`,标注 `is_terminal` / `is_resumable`。
-- resume 时 `_apply_checkpoint_resume_config()` 把 `thread_id` / `checkpoint_ns` / `checkpoint_id` 写入 `configurable`,保留 `checkpoint_ns` 以命中正确的子图状态。
-
-!!! warning "checkpoint_ns 必须保留"
- LangGraph 的 checkpoint 在子图(subgraph)场景下按 `thread_id` + `checkpoint_ns` + `checkpoint_id` 定位;丢掉 `checkpoint_ns` 会错误恢复到父图状态。`_checkpoint_ref_from_state()` 只在 `checkpoint_ns` 非空时写入 `framework_ref.langgraph.checkpoint_ns`。
-
-## 17. 文档索引
-
-- [ksadk使用文档](../guides/ksadk使用文档.md)
-- [工作区文件技术设计](../internal/工作区文件技术设计.md)
-- [记忆使用指南](../guides/记忆使用指南.md)
-- [OpenClaw一键部署指南](./openclaw一键部署指南.md)
diff --git "a/docs/reference/ksadk\347\216\257\345\242\203\345\217\230\351\207\217\345\217\202\350\200\203.md" "b/docs/reference/ksadk\347\216\257\345\242\203\345\217\230\351\207\217\345\217\202\350\200\203.md"
index a2517525..93dae208 100644
--- "a/docs/reference/ksadk\347\216\257\345\242\203\345\217\230\351\207\217\345\217\202\350\200\203.md"
+++ "b/docs/reference/ksadk\347\216\257\345\242\203\345\217\230\351\207\217\345\217\202\350\200\203.md"
@@ -2,7 +2,7 @@
本文档面向部署、运行、运维和 SDK 集成排障。它不是业务代码 `.env` 模板;业务方自己的变量,例如 `APP_ENV`、`DB_URL`、`CUSTOM_API_KEY`,只要不是 KsADK / 平台运行时读取的变量,都属于业务自定义变量,不在本文逐项维护。
-本文档覆盖 `ksadk/`、`deploy/`、`tests/` 中已经注册或常见可配置的运行时变量,由 `tests/test_config_env_registry.py` 保证 `ENV_VAR_REGISTRY` 注册项与文档一致。测试专用变量、PID/marker/cache 等进程内部临时变量、镜像构建脚本内部常量不会逐项列入表格;如果要排查这些高级项,以对应脚本源码和模板 README 为准。
+本文档覆盖 `ksadk/` 中已经注册或常见可配置的运行时变量,由 `tests/test_config_env_registry.py` 保证 `ENV_VAR_REGISTRY` 注册项与文档一致。测试专用变量、PID/marker/cache 等进程内部临时变量、镜像构建脚本内部常量不会逐项列入表格;如果要排查这些高级项,以对应脚本源码和模板 README 为准。
## 1. 阅读规则
@@ -138,6 +138,7 @@
| `OTEL_EXPORTER_OTLP_TRACES_HEADERS` | 否 | 无 | 是 | 平台 / 开发者 | traces 专用 OTLP headers;设置后优先于通用 headers。 |
| `OTEL_SERVICE_NAME` | 否 | 无 | 否 | 平台 / 开发者 | OTel service name。 |
| `OTEL_RESOURCE_ATTRIBUTES` | 否 | 无 | 否 | 平台 / 开发者 | OTel resource attributes。 |
+| `KSADK_OTLP_MAX_EXPORT_BATCH_SIZE` | 否 | 无 | 否 | 平台 / 开发者 | OTLP 单次 export 最大 span 数,默认 `64`,用于避免 collector 请求过大。 |
## 3. 通用模型与 LLM 变量
@@ -342,6 +343,7 @@
| `KSADK_WEB_RELEASE_URL` | Hosted Web UI static sync | 否 | 未设置 | 无 | 否 | 构建环境 / 开发者 | 否 | 可选兼容兜底。设置后跳过 npm pack,改从该 tarball URL 下载。 |
| `KSADK_WEB_CACHE_DIR` | Hosted Web UI static sync | 否 | `.cache/ksadk-web` | 无 | 否 | 构建环境 / 开发者 | 否 | KsADK Web 包解压缓存目录。 |
| `KSADK_GLOBAL_CONFIG_ENV_KEYS` | CLI | 否 | 未设置 | 无 | 否 | CLI 内部 | 否 | CLI 启动时记录哪些环境变量由 `~/.agentengine/settings.json` 补入,用于区分用户显式环境变量和全局配置默认值。 |
+| `KSYUN_IAM_URL` | 身份反查 | 否 | `https://iam.api.ksyun.com` | 无 | 否 | CLI | 否 | 覆盖 IAM endpoint,用于 AK/SK 反查子账号 user uuid。内部账号 AK 公网访问被拒时,CLI 自动 fallback 到 `http://iam.inner.api.ksyun.com`。 |
| `AGENTENGINE_LOCAL_RUNTIME_VENV_REEXEC` | 本地 runtime CLI | 否 | 自动判断 | 无 | 否 | 本地开发者 / 测试 | 否 | 控制本地 runtime 是否在虚拟环境中 re-exec。普通用户通常无需设置。 |
| `AGENTENGINE_WEB_VENV_REEXEC` | 本地 Web CLI | 否 | 自动判断 | 无 | 否 | 本地开发者 / 测试 | 否 | 控制本地 Web 命令是否在虚拟环境中 re-exec。普通用户通常无需设置。 |
| `AGENTENGINE_DEBUG` | CLI | 否 | 未设置 | 无 | 否 | 开发者 | 否 | 开启更详细错误输出。 |
@@ -430,6 +432,7 @@
| `OTEL_EXPORTER_OTLP_TRACES_HEADERS` | OTel | 否 | 未设置 | 无 | 是 | 平台 / 开发者 | 否 | traces 专用 OTLP headers;设置后优先于通用 headers。 |
| `OTEL_SERVICE_NAME` | OTel | 否 | 未设置 | 无 | 否 | 平台 / 开发者 | 否 | service name。 |
| `OTEL_RESOURCE_ATTRIBUTES` | OTel | 否 | 未设置 | 无 | 否 | 平台 / 开发者 | 否 | resource attributes。 |
+| `KSADK_OTLP_MAX_EXPORT_BATCH_SIZE` | OTel | 否 | `64` | 无 | 否 | 平台 / 开发者 | 否 | OTLP 单次 export 最大 span 数,用于避免 collector 请求过大。 |
## 12. Hermes 和 OpenClaw 常见运行时变量
diff --git "a/docs/reference/\350\277\234\347\250\213Agent\350\277\220\350\241\214\346\227\266\346\216\245\345\217\243\350\257\264\346\230\216.md" "b/docs/reference/\350\277\234\347\250\213Agent\350\277\220\350\241\214\346\227\266\346\216\245\345\217\243\350\257\264\346\230\216.md"
deleted file mode 100644
index 251e4781..00000000
--- "a/docs/reference/\350\277\234\347\250\213Agent\350\277\220\350\241\214\346\227\266\346\216\245\345\217\243\350\257\264\346\230\216.md"
+++ /dev/null
@@ -1,2168 +0,0 @@
-# 远程Agent运行时接口说明
-
-本文档基于当前 `master` 分支的真实代码实现整理,目标是说明:
-
-- Agent 部署到远程 K8s / Serverless Pod 之后,最终通过 `PublicEndpoint` 对外暴露哪些接口
-- 不同运行时类型的接口差异:通用 Agent、Hermes、OpenClaw
-- 公共鉴权、公共 Header、流式行为、WebSocket 约束
-- 各接口的请求体 / 响应体 shape
-
-本文档只把当前代码里可以确认的 contract 写出来;对仓库中未完整定义、但依赖上游项目的 OpenClaw 原生接口,不做超出代码证据的推断。
-
-## 1. 事实来源
-
-本文档主要依据以下代码与文档:
-
-- `agentengine-server/app/api/v1/actions/agent_actions.py`
-- `agentengine-server/app/api/v1/actions/chat_actions.py`
-- `agentengine-server/app/api/v1/actions/feedback_actions.py`
-- `agentengine-server/app/gateway/api.py`
-- `agentengine-server/app/gateway/router_service.py`
-- `agentengine-server/docs/技术设计.md`
-- `agentengine-server/docs/网关鉴权说明.md`
-- `ksadk-python/ksadk/server/app.py`
-- `ksadk-python/ksadk/server/api_models.py`
-- `ksadk-python/ksadk/conversations/runtime.py`
-- `ksadk-python/ksadk_runtime_common/workspace_files/*.py`
-- Hermes / OpenClaw 运行时镜像资产(`runtime/app.py`、`README.md`、`bootstrap.sh`、用户镜像 `Dockerfile` 等)已迁出至 `agentengine-images` 仓库,原 `deploy/hermes/`、`deploy/openclaw/`、`deploy/openclaw-user-template/` 路径不再存在于本仓库
-
-## 2. 入口模型
-
-### 2.1 公网入口
-
-远程 Agent 部署成功后,控制面 `GetAgent` 会返回:
-
-- `QuickAccess.PublicEndpoint`
-
-这个地址就是外部调用运行时接口时应使用的根地址。例如:
-
-```text
-http://ar-20260506162108-d30283cd.agent-pre.kspmas.ksyun.com
-```
-
-说明:
-
-- 对外看到的是 `PublicEndpoint`
-- 实际请求先进入 Ingress / Gateway,再由 `agentengine-server` 的 router 做鉴权和转发
-- 因此“部署后暴露的接口”应以公网入口经过网关后可访问的路径为准,而不是简单把 Pod 内部监听端口当成外部 contract
-
-### 2.2 内网入口
-
-`GetAgent` 也可能返回:
-
-- `QuickAccess.PrivateEndpoint`
-
-这类地址用于内网访问,不作为本文主线。本文默认描述通过 `PublicEndpoint` 暴露的接口。
-
-## 3. 鉴权与公共 Header
-
-## 3.1 外部访问鉴权
-
-当前数据面统一通过网关校验,外部调用主要有两种认证方式:
-
-1. `Authorization: Bearer `
-2. `ae_ui_session` Cookie
-
-其中:
-
-- API/SDK/CLI 直连运行时接口时,使用 `Authorization: Bearer `
-- 浏览器经 dashboard share link 或 hosted UI 访问时,通常使用 `ae_ui_session` Cookie
-
-代码证据:
-
-- `agentengine-server/docs/网关鉴权说明.md`
-- `agentengine-server/app/gateway/api.py`
-
-### 3.1.1 Bearer Token 的含义
-
-Bearer Token 有两种来源:
-
-1. AgentEngine 为该 Agent 签发的 API Key,通常是 `ak-...` 或 `sk-...`
-2. OpenClaw 在 `token` 模式下使用的 shared secret
-
-对绝大多数自动化调用,推荐理解为:
-
-```http
-Authorization: Bearer
-```
-
-### 3.1.2 Cookie 会话的适用场景
-
-`ae_ui_session` 主要用于:
-
-- `https:///chat`
-- `https:///`
-- share link 跳转后的浏览器会话
-
-它不是给通用脚本调用运行时 API 设计的主接口。
-
-## 3.2 公共请求 Header
-
-### 3.2.1 通用 HTTP Header
-
-建议按以下方式构造:
-
-| Header | 是否必填 | 说明 |
-| --- | --- | --- |
-| `Authorization: Bearer ` | 外部 API 调用必填 | 由网关校验 |
-| `Content-Type: application/json` | JSON 请求推荐 | `POST /v1/*`、`POST /agentengine/api/v1/*` 常用 |
-| `Accept: application/json` | 非流式请求推荐 | 返回 JSON |
-| `Accept: text/event-stream` | 流式请求推荐 | `stream=true` 时推荐显式声明 |
-
-说明:
-
-- 对于 `multipart/form-data` 上传,如 `UploadFile` / `AddWorkspaceFile`,`Content-Type` 由客户端自动生成 boundary
-- 运行时应用本身没有在 `ksadk.server.app` 内显式校验 Bearer;鉴权发生在网关层
-
-### 3.2.2 WebSocket Header
-
-Hermes 终端 WebSocket 额外要求:
-
-| Header | 是否必填 | 说明 |
-| --- | --- | --- |
-| `Authorization: Bearer ` | 公网访问建议携带 | 网关鉴权 |
-| `Sec-WebSocket-Protocol: ks-terminal.v1` | 必填 | Hermes 终端子协议 |
-
-如果缺少 `ks-terminal.v1`,Hermes runtime 会直接拒绝连接。
-
-## 3.3 内部 Header 与外部调用边界
-
-以下 Header 会在网关和运行时之间使用,但**不应由外部调用方手工构造**:
-
-| Header | 用途 |
-| --- | --- |
-| `X-Auth-Agent-Id` | 网关鉴权后注入的 Agent ID |
-| `X-Auth-Account-Id` | 网关鉴权后注入的账号 ID |
-| `X-Auth-Framework` | 网关鉴权后注入的 framework |
-| `X-Auth-Openclaw-Gateway-Mode` | OpenClaw 模式透传 |
-| `X-Forwarded-Host` | 原始 Host 透传 |
-| `x-forwarded-user` | OpenClaw trusted-proxy / workspace 代理链路使用 |
-| `X-Hermes-Session-Token` | Hermes dashboard 内部 fetch shim 使用 |
-
-外部用户应只关心:
-
-- Bearer API Key
-- Cookie Session
-- WebSocket 子协议
-
-## 4. 运行时类型矩阵
-
-当前主线下,公网可见接口按运行时分为三类:
-
-| 运行时类型 | 典型 framework | 主入口实现 | 对外特征 |
-| --- | --- | --- | --- |
-| 通用 Agent 运行时 | `adk` / `langchain` / `langgraph` / `deepagents` | `ksadk.server.app` | `/v1/*` + workspace files;公网 `/chat` 由独立 hosted UI 服务承载并调用 Hosted UI action 接口 |
-| Hermes 托管运行时 | `hermes` | `agentengine-images` 仓库内 `deploy/hermes/runtime/app.py` 外层 wrapper | `/` dashboard、`/v1/*`、`/_ksadk/terminal/ws`、workspace files;公网 `/chat` 同样由独立 hosted UI 服务承载 |
-| OpenClaw 托管运行时 | `openclaw` | OpenClaw gateway + ksadk 补丁 | 以 OpenClaw gateway 为主,平台额外挂出 workspace files |
-
-## 5. 公网暴露范围总览
-
-### 5.1 通用 Agent 运行时
-
-公网入口可确认的主路径:
-
-- `GET /health`
-- `POST /v1/responses`
-- `POST /v1/chat/completions`
-- `GET /chat`
-- `GET /build`
-- `GET /deploy`
-- `GET /agentengine/api/v1/AttachmentContent`
-- `GET /agentengine/api/v1/GetWorkspaceFileContent`
-- `POST /agentengine/api/v1/GetAgentUiBootstrap`
-- `POST /agentengine/api/v1/CreateSession`
-- `POST /agentengine/api/v1/GetSession`
-- `POST /agentengine/api/v1/ListSessions`
-- `POST /agentengine/api/v1/DeleteSession`
-- `POST /agentengine/api/v1/ListSessionEvents`
-- `GET /agentengine/api/v1/SubscribeRunEvents`
-- `POST /agentengine/api/v1/RunAgent`
-- `POST /agentengine/api/v1/ListSessionCheckpoints`
-- `POST /agentengine/api/v1/GetCheckpointResumePreview`
-- `POST /agentengine/api/v1/ListToolReceipts`
-- `POST /agentengine/api/v1/ResumeRun`
-- `POST /agentengine/api/v1/CancelRun`
-- `POST /agentengine/api/v1/UploadFile`
-- `POST /agentengine/api/v1/ListWorkspaceFiles`
-- `POST /agentengine/api/v1/AddWorkspaceFile`
-- `POST /agentengine/api/v1/DeleteWorkspaceFile`
-- `POST /agentengine/api/v1/ListAgentModels`
-- `GET /agentengine/api/v1/ExportWorkspaceZip`
-- `POST /run_sse`
-- `GET/POST/DELETE /apps/{app_name}/users/{user_id}/sessions*`
-
-注意:
-
-- 并不是所有 `/agentengine/api/v1/*` 都会通过公网数据面暴露
-- 网关只放行 Hosted UI 所需的那一小组 action
-- 对 `PublicEndpoint` 而言,`POST /agentengine/api/v1/*` 这组 Hosted UI action 实际会被 router 代理回 `agentengine-server`,不是直接命中 runtime pod 的本地同名路由
-
-### 5.2 Hermes 运行时
-
-公网入口可确认的主路径:
-
-- `GET /`
-- `GET /health`
-- `GET/POST/PUT/PATCH/DELETE/OPTIONS /v1/{path}`
-- `GET/POST/PUT/PATCH/DELETE/OPTIONS /{path}`
- 这部分本质是 Hermes dashboard 与其 API 的代理入口
-- `GET/HEAD/POST/DELETE /_ksadk/workspace/v1/*`
-- `WS /_ksadk/terminal/ws`
-- `GET /chat`
-
-### 5.3 OpenClaw 运行时
-
-当前代码中可以**准确确认**的平台追加 contract 只有:
-
-- `/_ksadk/workspace/v1/*`:通过 ksadk sidecar / proxy 增加的文件接口
-
-此外还可以确认:
-
-- OpenClaw gateway 默认跑在 `8080`
-- 鉴权模式支持 `trusted-proxy | token | none`
-- 健康检查使用的是上游 gateway 的 `/healthz`
-
-但 OpenClaw gateway 原生完整 API 面不是本仓当前代码独立定义的,因此本文不把其所有原生端点逐条列为平台 contract。
-
-## 6. 通用 Agent 运行时详细接口
-
-本节适用于原始 runtime 服务本身:
-
-- `adk`
-- `langchain`
-- `langgraph`
-- `deepagents`
-
-底层实现:`ksadk-python/ksadk/server/app.py`
-
-重要边界:
-
-- 本节里的 `/v1/*`、`/health`、`/run_sse`、`/apps/.../sessions*` 是 runtime pod 自身实现
-- 但对公网 `PublicEndpoint` 来说,`/agentengine/api/v1/*` Hosted UI action 以 `agentengine-server` facade 为准
-- 因此本文后续会把“runtime 原始接口”和“公网 Hosted facade”拆开写
-
-## 6.1 健康检查
-
-### `GET /health`
-
-用途:
-
-- 检查运行时是否启动
-- 返回当前 runner 识别出的 framework 和 agent 名
-
-请求示例:
-
-```bash
-curl -H "Authorization: Bearer " \
- "https:///health"
-```
-
-响应示例:
-
-```json
-{
- "status": "ok",
- "framework": "langgraph",
- "agent": "demo-agent"
-}
-```
-
-## 6.2 OpenAI Responses 兼容接口
-
-### `POST /v1/responses`
-
-说明:
-
-- 非流式返回 OpenAI Responses 风格 JSON
-- 流式返回 `text/event-stream`
-
-请求体字段:
-
-| 字段 | 类型 | 必填 | 说明 |
-| --- | --- | --- | --- |
-| `input` | `string | array` | 是 | 用户输入;字符串或 KOP 风格消息数组 |
-| `model` | `string` | 否 | 本次调用显式模型 |
-| `model_metadata` | `object` | 否 | 模型元数据 |
-| `instructions` | `string` | 否 | 额外系统指令 |
-| `metadata` | `object` | 否 | 请求级 metadata |
-| `conversation` | `string | object` | 否 | OpenAI Responses 会话绑定字段;可传 `"conv_xxx"` 或 `{ "id": "conv_xxx" }`,runtime 会映射为内部会话 ID |
-| `previous_response_id` | `string` | 否 | OpenAI Responses 上一轮 response id;不能和 `conversation` 同时使用 |
-| `safety_identifier` | `string` | 否 | OpenAI 推荐的最终用户稳定标识;runtime 会映射为内部 user id 和 Langfuse UserID,建议传 hash 后值 |
-| `prompt_cache_key` | `string` | 否 | OpenAI prompt cache 路由提示;runtime 当前保留到请求 metadata,不作为用户身份 |
-| `user` | `string` | 否 | OpenAI deprecated 用户字段;仅在未传 `safety_identifier` 时作为兼容兜底 |
-| `store` | `boolean` | 否 | OpenAI Responses 存储开关;runtime 当前保留到请求 metadata |
-| `stream` | `boolean` | 否 | 是否流式 |
-| `session_id` | `string` | 否 | ksadk legacy extension;兼容旧客户端。新接入应优先使用 `conversation` |
-| `account_id` | `string` | 否 | 0.6.7 新增。账号 ID 透传;写入 `PlatformInvocationContext`,用于多租户隔离与审计 |
-
-最小请求示例:
-
-```json
-{
- "input": "你好",
- "stream": false
-}
-```
-
-带会话与模型示例:
-
-```json
-{
- "input": [
- {
- "role": "user",
- "content": [
- {
- "text": "请总结一下这份设计"
- }
- ]
- }
- ],
- "model": "glm-5.2",
- "stream": true,
- "conversation": "conv_customer_001",
- "safety_identifier": "hash_user_001"
-}
-```
-
-会话字段边界:
-
-- 官方兼容路径:连续对话传 `conversation`;最终用户标识传 `safety_identifier`。
-- `previous_response_id` 只表达 Responses 链式上下文,不能和 `conversation` 同时使用。
-- `session_id` 是 ksadk 早期扩展字段,仅为旧客户端保留;不要在新代码中把它当作 OpenAI 官方字段。
-- 不要通过 `metadata.user_id`、`metadata.session_id` 或其他私有 metadata 约定传用户身份和会话身份。
-
-推荐请求示例:
-
-```json
-{
- "model": "deepseek-v4-pro",
- "input": "帮我分析这张账单",
- "conversation": "conv_bill_20260525_001",
- "safety_identifier": "user_hash_001",
- "stream": false
-}
-```
-
-图片与附件输入:
-
-推荐写法:
-
-- `/v1/responses` 推荐使用 OpenAI Responses content blocks:`input_text` / `input_image` / `input_file`
-- runner 业务代码推荐读取 `payload["input_content"]` / `payload["input_messages"]`,这是 KsADK 默认 canonical 输入
-- 判断当前轮是否传了图片或文件,推荐使用 `payload["has_current_files"]` 和 `payload["current_attachments"]`
-- 读取当前轮 OCR、文档抽取、压缩包摘要,推荐使用 `payload["current_attachment_results"]`
-
-兼容写法:
-
-- 老客户端仍可使用 KsADK 兼容扩展 part 数组:`text` / `inlineData` / `fileData`
-- runner 里仍保留 `payload["input_parts"]`,用于兼容已有 `text / inlineData / fileData` 业务代码
-- `payload["attachments"]` / `payload["attachment_results"]` 仍保留,但语义是最近有效附件上下文,可能来自历史 fallback;不要用它判断当前最新 user turn 是否上传了文件
-- `/v1/chat/completions` 对外仍保持 Chat Completions 语义,官方图片块使用 `text` / `image_url`;`inlineData` / `fileData` 在 Chat 入口只属于 KsADK 兼容扩展,不是 OpenAI Chat 官方能力
-
-字段细节:
-
-- `input_image.image_url` 支持远程图片 URL 或 `data:image/...;base64,...`,运行时会归一化为内部附件上下文
-- `input_file.file_data` 会归一化为内部 `inlineData`;`input_file.file_url` / `input_file.file_id` 会归一化为内部 `fileData` 引用
-- `inlineData` 适合旧客户端直接内联 base64 内容
-- `fileData` 适合旧客户端先调用 `UploadFile`,再引用返回的 `ksadk-upload://...`
-- 远程图片 URL 会作为引用保留,并可在支持原生图片输入的 LangGraph 路径下继续传给模型;KsADK 不会主动拉取远程图片或远程文件做 OCR / 文本提取。需要平台提取、OCR 或本地附件内容时,请使用 data URL、`file_data`、`inlineData` 或 `fileData`
-
-图片示例(OpenAI Responses 风格 data URL):
-
-```json
-{
- "input": [
- {
- "role": "user",
- "content": [
- {
- "type": "input_text",
- "text": "请分析这张图片"
- },
- {
- "type": "input_image",
- "image_url": "data:image/png;base64,"
- }
- ]
- }
- ],
- "model": "glm-5.2",
- "stream": false
-}
-```
-
-业务代码获取图片信息:
-
-```python
-def ksadk_prepare_input(payload, session_context):
- # 当前轮是否真的上传了图片/文件。不要用 attachments 判断当前轮,
- # attachments 可能是历史最近一次有效附件上下文。
- has_current_files = payload.get("has_current_files", False)
- current_attachments = payload.get("current_attachments", [])
-
- images = [
- item
- for item in current_attachments
- if str(item.get("mime_type", "")).startswith("image/")
- ]
-
- # OpenAI Responses canonical content,适合直接转给支持原生多模态的模型。
- input_content = payload.get("input_content", [])
- image_blocks = [
- block
- for block in input_content
- if block.get("type") == "input_image"
- ]
-
- return {
- "input": payload.get("input", ""),
- "images": images,
- "image_blocks": image_blocks,
- }
-```
-
-如果业务 agent 使用 LangGraph / LangChain 并且模型支持原生多模态,优先从 `input_content` 或 `input_messages` 读取 `input_image`,按底层模型 SDK 需要的消息格式继续传递;如果需要读取平台归一化后的附件元信息、OCR / 文档抽取结果,则读取 `current_attachments` 和 `current_attachment_results`。`input_parts`、`inlineData`、`fileData` 是 legacy/internal 兼容输入,仍可作为老客户端兜底。
-
-多模态模型“看图”和平台 OCR 是两条不同链路:推荐让支持图片的模型直接消费 `input_image` / `input_content`,这样不需要在代码包里安装本地 OCR 依赖。平台本地 OCR 只用于需要把图片预先转成 `current_attachment_results[*].text` 的场景;源码构建默认不打包 OCR 二进制栈,如需启用请在构建环境设置 `KSADK_BUILD_ENABLE_ATTACHMENT_OCR=true`,或在项目 `requirements.txt` 中显式加入 OCR 相关依赖。
-
-图片 data URL 或 `inlineData.data` 本身就是 base64 字符串,payload 可能很大,这是内联传图时的正常现象。业务日志不要直接打印完整 `payload`、`input_content`、`input_parts` 或 `current_attachments`;建议只记录字段摘要,例如文件名、MIME、大小、transport、data URL 前缀和长度:
-
-```python
-def summarize_attachment(item):
- data = item.get("data") or ""
- return {
- "display_name": item.get("display_name"),
- "mime_type": item.get("mime_type"),
- "transport": item.get("transport"),
- "file_uri": item.get("file_uri"),
- "size_bytes": item.get("size_bytes"),
- "has_inline_data": bool(data),
- "inline_data_length": len(data),
- }
-
-logger.info(
- "ksadk_prepare_state attachments=%s has_current_files=%s",
- [summarize_attachment(item) for item in payload.get("current_attachments", [])],
- payload.get("has_current_files", False),
-)
-```
-
-旧客户端图片示例(先上传,再引用):
-
-```json
-{
- "input": [
- {
- "role": "user",
- "content": [
- {
- "text": "请分析这张图片"
- },
- {
- "fileData": {
- "fileUri": "ksadk-upload://abc123.png",
- "displayName": "diagram.png",
- "mimeType": "image/png"
- }
- }
- ]
- }
- ],
- "model": "glm-5.2",
- "stream": false
-}
-```
-
-旧客户端图片示例(直接内联):
-
-```json
-{
- "input": [
- {
- "role": "user",
- "content": [
- {
- "text": "请分析这张图片"
- },
- {
- "inlineData": {
- "data": "",
- "displayName": "diagram.png",
- "mimeType": "image/png"
- }
- }
- ]
- }
- ]
-}
-```
-
-当前附件类型支持矩阵:
-
-| 类型 | 典型扩展名 / MIME | 传输支持 | 平台提取支持 | 原生多模态直通 |
-| --- | --- | --- | --- | --- |
-| 文本 | `.txt` `.md` `.json` `.yaml` `.yml` `.csv` `.tsv` `.log` | 支持 | 支持 | 不适用 |
-| 文档 | `.pdf` `.docx` `.pptx` `.xlsx` `.html` `.htm` | 支持 | 部分支持:文本提取 / OCR | 不适用 |
-| 图片 | `.png` `.jpg` `.jpeg` `.webp` / `image/*` | 支持 | 元信息提取默认支持;OCR 需构建时显式启用 | 部分支持,见下方框架差异 |
-| 压缩包 | `.zip` | 支持 | 支持:目录/可读文件抽样提取 | 不适用 |
-| 其他二进制 | 其他后缀或 `application/octet-stream` | 支持 | 通常仅保留为附件引用 | 不支持 |
-
-框架差异:
-
-- `ADK`
- - 图片附件会优先以 bytes 形式构造成底层 SDK `Part`
- - 若底层模型支持原生多模态,可直接消费图片
-- `LangGraph`
- - 简化输入路径下,若模型支持图片输入,图片附件会自动转换为多模态 `HumanMessage.content` blocks
- - 非图片附件仍保留为普通附件上下文
-- `LangChain`
- - 当前没有对所有 agent 统一做“自动图片直通”
- - 如需原生多模态,建议在 `ksadk_prepare_input(payload, session_context)` 中优先消费 `input_content / input_messages`,必要时再兼容 `input_parts / current_attachments / attachments`
- - 判断当前轮是否传文件用 KsADK runner payload 扩展字段 `has_current_files`;该字段不是 OpenAI Responses API 官方字段
-
-模型能力判断优先级:
-
-1. 请求里显式传入的 `model_metadata`
-2. runtime 通过 `OPENAI_BASE_URL` / `OPENAI_API_KEY` 查询上游 `/v1/models` 返回的 `architecture.input_modalities`
-3. 本地默认兜底(按文本模型处理)
-
-多轮会话历史:
-
-- `/v1/responses` 本身不要求客户端每轮重传完整历史
-- 新客户端应持续传同一个 `conversation`,runtime 会从服务端会话存储里恢复该会话的历史 transcript
-- 旧客户端只传 `session_id` 时仍可恢复同一会话,但这是 ksadk legacy extension
-- 进入 runner 前,`ksadk` 会把历史、附件上下文、知识库上下文和长期记忆上下文统一重建成标准运行输入
-- `safety_identifier` 会作为内部 user id,并用于 Langfuse UserID;未传时 deprecated `user` 字段可作为兜底
-- `previous_response_id` 按 OpenAI Responses 语义接收并保留;当使用 `conversation` 时不要同时传 `previous_response_id`
-
-### Responses approval / interrupt 恢复
-
-如果流式执行遇到工具审批或人工确认,runtime 不会把本轮包装成 completed,而是返回 incomplete:
-
-- `status`: `incomplete`
-- `incomplete_details.reason`: `approval_required`
-- MCP/tool approval 场景会输出 `mcp_approval_request`
-- 非 MCP 的通用 interrupt 会输出 `response.ksadk.approval_request`
-
-#### MCP approval 恢复
-
-MCP/tool approval 场景按 OpenAI Responses 标准语义恢复。客户端应传同一个 `conversation` 或 legacy `session_id`,并把 `input` 写成 `mcp_approval_response`:
-
-```json
-{
- "conversation": "conv_customer_001",
- "input": [
- {
- "type": "mcp_approval_response",
- "id": "mcprsp_123",
- "approval_request_id": "appr_123",
- "approve": true,
- "reason": "approved by user"
- }
- ],
- "stream": true
-}
-```
-
-运行时处理方式:
-
-- 记录一条 `approval_response` 会话事件
-- 向 runner 传入 `resume=True`
-- `input` 原样保留为 `mcp_approval_response`
-- LangGraphRunner 在内部转换成 `Command(resume=...)`
-
-调用方不需要、也不应该直接传 Python `Command`。
-
-#### 通用 interrupt 恢复
-
-如果 interrupt 不是 MCP/tool approval,而是普通人工确认、补充信息或业务分支选择,客户端可以使用平台扩展 `ksadk_resume`:
-
-```json
-{
- "conversation": "conv_customer_001",
- "input": [
- {
- "type": "ksadk_resume",
- "interrupt_id": "intr_123",
- "value": {
- "approved": true,
- "answer": "继续"
- }
- }
- ],
- "stream": true
-}
-```
-
-这类事件属于 `ksadk` 扩展,不伪装成 OpenAI MCP approval。
-
-### Agent 开发者如何在业务代码中拿到上下文
-
-这部分不属于远程 API 调用 contract。不同框架的业务代码接入方式已经内化到框架专属文档:
-
-- LangGraph: [LangGraph开发最佳实践](../guides/LangGraph开发最佳实践.md)
-- 平台公共上下文总览: [Agent 开发者上下文接入指南](../guides/Agent 开发者上下文接入指南.md)
-
-调用方只需要理解:
-
-- `/v1/responses` 不要求每轮重传完整历史
-- 同一会话应持续传同一个 `conversation`;旧客户端传 `session_id` 也能继续兼容
-- runtime 会在进入 runner 前重建历史、附件、知识库和长期记忆上下文
-- 框架业务代码如何消费这些上下文,由对应框架最佳实践文档说明
-
-### 历史压缩(compaction)是怎么做的
-
-长会话不会无限把所有历史原样塞进模型。
-
-当前策略是:
-
-1. transcript 按 API round / `invocation_id` 分组
-2. 保留最近若干轮原始消息
-3. 把更早历史压成一条 `context_checkpoint`
-4. 后续模型看到的是:
- - 一条 `Earlier conversation summary: ...`
- - 最近若干轮原始 user / assistant 消息
-
-重要特性:
-
-- 原始事件不会物理删除,compaction 是 append-only
-- 工具调用、审批请求、附件引用等关键信息不会简单丢弃,会以 summary 或占位文本形式保留
-- 压缩阈值会结合 `model_metadata` 的上下文窗口能力自动调整
-
-非流式响应字段:
-
-| 字段 | 说明 |
-| --- | --- |
-| `id` | response ID |
-| `object` | 固定 `response` |
-| `created_at` | Unix 时间戳 |
-| `status` | 默认 `completed` |
-| `model` | 模型名 |
-| `output` | 输出条目数组 |
-| `output_text` | 文本聚合结果 |
-| `usage` | 简化 token 统计 |
-| `session_id` | ksadk 返回的内部会话 ID;当请求传了 `conversation` 时与其 id 一致 |
-
-非流式响应示例:
-
-```json
-{
- "id": "resp_123",
- "object": "response",
- "created_at": 1710000000,
- "status": "completed",
- "error": null,
- "incomplete_details": null,
- "instructions": null,
- "metadata": {},
- "model": "glm-5.2",
- "parallel_tool_calls": true,
- "temperature": null,
- "top_p": null,
- "tools": [],
- "output": [
- {
- "id": "msg_abc",
- "type": "message",
- "status": "completed",
- "role": "assistant",
- "content": [
- {
- "type": "output_text",
- "text": "你好,我可以帮你分析代码。"
- }
- ]
- }
- ],
- "output_text": "你好,我可以帮你分析代码。",
- "usage": {
- "input_tokens": 0,
- "output_tokens": 12,
- "total_tokens": 12
- },
- "session_id": "conv_customer_001"
-}
-```
-
-流式行为:
-
-- `Content-Type: text/event-stream`
-- 每个事件格式为:
-
-```text
-event:
-data:
-
-```
-
-当前可能出现的主要事件:
-
-- `response.created`
-- `response.in_progress`
-- `response.output_text.delta`
-- `response.reasoning.delta`
-- `response.tool_call`
-- `response.tool_result`
-- `response.output_item.added` / `response.output_item.done`:MCP approval request 等结构化 output item
-- `response.ksadk.approval_request`:非 MCP 的通用 interrupt 扩展事件
-- `response.compaction.start`
-- `response.compaction.done`
-- `response.incomplete`
-- `response.completed`
-
-## 6.3 OpenAI Chat Completions 兼容接口
-
-### `POST /v1/chat/completions`
-
-请求体字段:
-
-| 字段 | 类型 | 必填 | 说明 |
-| --- | --- | --- | --- |
-| `messages` | `array