Skip to content

Commit 0f60733

Browse files
yaozheng-fangclaude
andcommitted
feat(harness): update same-name runtime in place + keep deploy creds off the runtime
deploy --harness now resolves a name collision instead of always failing: - single existing harness runtime -> prompt (or --yes) to update it in place; the platform releases a new version. Echoes the version before and after. - non-harness same-name runtime, or multiple matches -> fast-fail. - non-interactive (no tty) without --yes -> fast-fail. build_agentkit_config gains a runtime_id arg (Auto = create, real id = update, which the runner routes to UpdateRuntime/ReleaseRuntime). Credential safety: a local .env is loaded for deploy auth, but the Volcengine deploy credentials in it are no longer uploaded into the runtime environment (the runtime authenticates via its IAM role). Implemented via an opt-in COMPAT_ENV_EXCLUDE applied to the veADK-compat env layer; other .env keys and spec-provided credentials are unaffected. Docs: HARNESS_QUICKSTART.md updated (--yes, same-name update, credential note). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 12b1940 commit 0f60733

7 files changed

Lines changed: 420 additions & 29 deletions

File tree

‎HARNESS_QUICKSTART.md‎

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
# AgentKit Harness 全流程
2+
3+
## 1. init —— 初始化项目
4+
5+
用 harness 模板在当前目录创建项目脚手架(含 `.env.example`、`Dockerfile`)。
6+
7+
```bash
8+
agentkit init my-harness -t harness
9+
```
10+
11+
## 2. add harness —— 生成 Harness 配置
12+
13+
创建/更新 `<name>.harness.json`,设置模型、工具、技能、系统提示等。
14+
15+
```bash
16+
agentkit add harness --name my-harness \
17+
--model-name doubao-seed-1-6-250615 \
18+
--tools web_search \
19+
--system-prompt "You are a helpful assistant."
20+
```
21+
22+
### 2.1 通过 OAuth(用户池)鉴权部署
23+
24+
默认情况下 harness 部署后用 API Key(`key_auth`)鉴权。
25+
若希望用火山引擎**用户池**签发的 JWT 来网关鉴权(`custom_jwt`),
26+
在 `add harness` 时传入用户池的 `--discovery-url` 和允许的客户端 `--allowed-id`。
27+
二者会被写入 `<name>.harness.json` 的 `auth` 块,
28+
后续 `deploy` 会自动据此把运行时切换为 OAuth2/JWT 鉴权。
29+
30+
```bash
31+
agentkit add harness --name my-harness \
32+
--model-name doubao-seed-1-6-250615 \
33+
--discovery-url "https://userpool-<userpool-id>.userpool.auth.id.cn-beijing.volces.com/.well-known/openid-configuration" \
34+
--allowed-id "<client-id-1>,<client-id-2>"
35+
```
36+
37+
- `--discovery-url`:用户池的 OIDC discovery 地址,其中 `<userpool-id>` 即在火山引擎身份中心创建的用户池 ID。
38+
- `--allowed-id`:允许访问该运行时的客户端 ID(即 JWT 的 audience),逗号分隔。
39+
40+
写入后 `my-harness.harness.json` 会包含:
41+
42+
```json
43+
{
44+
"harness_name": "my-harness",
45+
"auth": {
46+
"discovery_url": "https://userpool-<userpool-id>.userpool.auth.id.cn-beijing.volces.com/.well-known/openid-configuration",
47+
"allowed_ids": ["<client-id-1>", "<client-id-2>"]
48+
}
49+
}
50+
```
51+
52+
说明:
53+
54+
- 用户池、客户端、外部 IdP 需先在火山引擎身份中心控制台创建;CLI 只引用它们的 ID,不涉及任何密钥。
55+
- `discovery_url` 与 `allowed_ids` 必须同时提供,否则部署时快速失败。
56+
- 也可在部署时用 `agentkit deploy --harness my-harness --discovery-url ... --allowed-id ...` 临时覆盖 `auth` 块。
57+
- 采用 OAuth2/JWT 后,调用时不再使用 API Key,而需携带用户池签发的 JWT:`agentkit invoke harness my-harness -ak <jwt> "..."`(CLI 不负责签发该 JWT)。
58+
59+
## 3. deploy —— 部署为云端运行时
60+
61+
读取 `my-harness.harness.json` 做云端构建并创建运行时,结果记录到 `harness.json`。
62+
63+
```bash
64+
agentkit deploy --harness my-harness
65+
```
66+
67+
完整参数:
68+
69+
- `--harness <name>` 部署 `<name>.harness.json`(云端构建+部署),而非 `agentkit.yaml`
70+
- `--config-file <path>` 配置文件,默认 `agentkit.yaml`
71+
- `--region <region>` AgentKit 区域(harness 部署)
72+
- `--volcengine-access-key <ak>` Volcengine 访问密钥(harness 部署)
73+
- `--volcengine-secret-key <sk>` Volcengine 密钥(harness 部署)
74+
- `--discovery-url <url>` OIDC discovery URL,启用 OAuth2/JWT 鉴权
75+
- `--allowed-id <ids>` 允许的 client ID,逗号分隔(OAuth2/JWT)
76+
- `--yes, -y` 存在同名 harness 时直接更新为新版本,不再交互询问(CI / 非交互场景)
77+
78+
> AK/SK 未显式传入时,会自动从环境变量或 `.env` 读取(见各命令通用的凭证解析)。
79+
80+
### 3.1 同名处理:新建 or 更新
81+
82+
部署前会按名称在云端查重,据此决定行为:
83+
84+
- **无同名** → 新建运行时(版本从 `v1` 开始);
85+
- **已存在同名 harness** → 回显其当前版本号并询问是否更新(`[y/N]`,或用 `--yes` 跳过);
86+
确认后**原地更新**同一运行时(`runtime_id` 不变,平台发布为新版本),完成后回显新版本号;
87+
- **同名但不是 harness** 应用(无 `agentkit:agenttype=harness` 标签)→ 拒绝部署,避免误改非 harness 运行时;
88+
- **存在多个同名** → 报错,需先清理后再部署;
89+
- **非交互**(无 TTY)且未带 `--yes` → 直接快速失败,不阻塞管道。
90+
91+
```bash
92+
# 同名已存在时,直接更新为新版本(不询问)
93+
agentkit deploy --harness my-harness --yes
94+
```
95+
96+
更新成功时会看到类似:
97+
98+
```text
99+
Updating Runtime: r-ye9j62wydcn****hsoa
100+
✅ Harness 'my-harness' updated to version v2.
101+
```
102+
103+
### 3.2 凭证安全:`.env` 的火山 AK/SK 不会上云
104+
105+
当前目录的 `.env` 仅用于**本地部署鉴权**。其中的火山引擎部署凭证
106+
(`VOLCENGINE_ACCESS_KEY` / `VOLCENGINE_SECRET_KEY` 等)**不会**被写入云端运行时的环境变量——
107+
运行时通过自身 IAM 角色(`RUNTIME_IAM_ROLE_TRN`)鉴权。`.env` 中的其它变量仍会正常合并进运行时环境。
108+
109+
> 若 harness 确实需要在运行时使用某些火山凭证(例如 Viking 知识库),应在 `<name>.harness.json`
110+
> 的对应组件中显式声明,而不是依赖 `.env`——它们的优先级更高,不受上述排除影响。
111+
112+
## 4. list harness —— 列出已部署的 Harness 运行时
113+
114+
使用环境变量中的火山引擎凭证(AK/SK)列出**所有可见的运行时**,再筛选出在部署时被打上 harness 标签(`agentkit:agenttype=harness`,由 `deploy` 自动写入)的运行时并展示。
115+
116+
```bash
117+
agentkit list harness
118+
```
119+
120+
表格输出包含 `RuntimeId`、`Name`、`Status`、`Version`(当前发布版本号 `CurrentVersionNumber`)、`ProjectName`、`UpdatedAt` 等列:
121+
122+
```text
123+
Harness Runtimes (Count: 1)
124+
┏━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┓
125+
┃ RuntimeId ┃ Name ┃ Status┃ Version ┃ ProjectName ┃ UpdatedAt ┃
126+
┡━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━┩
127+
│ r-ye9j62wydcn****hsoa │ my-harness │ Ready │ 1 │ default │ 2026-06-18 10:24:01 │
128+
└───────────────────────┴────────────┴───────┴─────────┴─────────────┴─────────────────────┘
129+
```
130+
131+
完整选项:
132+
133+
- `--region <region>` 区域覆盖(如 `cn-beijing`、`cn-shanghai`),默认取 `VOLCENGINE_AGENTKIT_REGION` / `VOLCENGINE_REGION` / 全局配置
134+
- `--output <fmt>` 输出格式:`table`(默认)、`json`、`yaml`
135+
- `--quiet, -q` 仅打印 `RuntimeId`(便于脚本消费)
136+
- `--no-color, -nc` 关闭彩色输出
137+
- `--limit, -l <n>` 翻页时每批拉取的运行时数量(默认 `50`),命令始终遍历全部分页
138+
139+
```bash
140+
# 仅打印 RuntimeId
141+
agentkit list harness --quiet
142+
143+
# JSON 输出并指定区域
144+
agentkit list harness --output json --region cn-beijing
145+
```
146+
147+
> 该命令会自动按 `NextToken` 游标翻页,遍历全部运行时后再做筛选,因此位于任意分页上的 harness 运行时都不会遗漏。
148+
149+
## 5. invoke —— 测试调用
150+
151+
按名称向已部署的 harness 发送一条 prompt 进行验证。
152+
153+
```bash
154+
agentkit invoke harness my-harness "你好,帮我算 2+2"
155+
```
156+
157+
位置参数(必填):
158+
159+
- `NAME` 已部署的 harness 名称(从 `harness.json` 注册表查找)
160+
- `MESSAGE` 要发送的 prompt
161+
162+
完整选项(覆写仅对本次调用生效,不会持久化;只发送显式传入的字段):
163+
164+
- `--directory <dir>` `harness.json` 所在目录,默认 `.`
165+
- `--user-id <id>` 本次运行的 user_id,默认 `agentkit_user`
166+
- `--session-id <id>` 本次运行的 session_id,默认 `agentkit_sample_session`
167+
- `--max-llm-calls <n>` 覆写本次调用的最大 LLM 调用数
168+
- `--system-prompt <text>` 覆写系统提示
169+
- `--model-name <name>` 覆写模型名
170+
- `--tools <list>` 覆写工具,逗号分隔(增量添加)
171+
- `--skills <list>` 覆写技能,逗号分隔(增量添加)
172+
- `--runtime <name>` 覆写 runtime 后端
173+
- `--apikey, -ak <token>` Bearer token(如 custom_jwt harness 的 OAuth JWT)
174+
- `--raw` 打印原始 `InvokeHarnessResponse` JSON
175+
176+
```bash
177+
# 单次覆写示例
178+
agentkit invoke harness my-harness --system-prompt "Be terse." "What is 2+2?"
179+
agentkit invoke harness my-harness --max-llm-calls 10 "Plan a trip."
180+
```
181+
182+
## 6. logs —— 查询运行时日志
183+
184+
查询该 harness 运行时的日志(默认最近 15 分钟、最多 200 条);普通 Agent 应用会提示「非 Harness 应用,无法查询日志」。
185+
186+
```bash
187+
# 默认查询(最近 15 分钟)
188+
agentkit logs --harness my-harness
189+
190+
# 相对时间窗口 + 写入文件(--since 支持 1h/30m/2d/1h30m,与 --start 互斥)
191+
agentkit logs --harness my-harness --since 1h --output ./logs/my-harness.log
192+
193+
# 调整条数与排序
194+
agentkit logs --harness my-harness --limit 50 --sort asc
195+
```
196+
197+
## 7. skill —— 安装/管理技能
198+
199+
管理单个 Skill。`--skill-space` 为可选项:以 `ss-` 开头按 ID 处理,否则按名称解析;不传则只作用于 Skill 本身。
200+
201+
安装:先获取临时 TOS URL,再创建 Skill。带 `--skill-space` 时额外加入该 SkillSpace。
202+
新建后处于 `creating`,默认轮询至 `running`(`--no-wait` 可跳过)。
203+
204+
```bash
205+
# 仅创建 Skill
206+
agentkit skill install theme-factory
207+
208+
# 创建并加入 SkillSpace(名称或 ss- ID 均可)
209+
agentkit skill install theme-factory --skill-space agentkit_skill_space_hehy8dw
210+
```
211+
212+
列出:不带 space 列出全部 Skill;带 space 列出该 SkillSpace 内的 Skill。
213+
214+
```bash
215+
agentkit skill list
216+
agentkit skill list --skill-space agentkit_skill_space_hehy8dw
217+
```
218+
219+
卸载:带 `--skill-space` 仅从该 SkillSpace 移除(保留 Skill)。
220+
不带 space 则删除整个 Skill(需确认,`--force` 跳过)。
221+
222+
```bash
223+
# 仅从 SkillSpace 移除
224+
agentkit skill uninstall theme-factory --skill-space agentkit_skill_space_hehy8dw
225+
226+
# 删除整个 Skill
227+
agentkit skill uninstall theme-factory --force
228+
```

‎agentkit/toolkit/cli/cli_deploy.py‎

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"""AgentKit CLI - Deploy command implementation."""
1616

1717
from pathlib import Path
18-
from typing import Optional
18+
from typing import Callable, Dict, Optional
1919

2020
import typer
2121
from rich.console import Console
@@ -25,6 +25,17 @@
2525
console = Console()
2626

2727

28+
def _prompt_harness_update(info: Dict) -> bool:
29+
"""Interactive [y/N] confirmation to update an existing same-name harness."""
30+
version = info.get("version")
31+
version_label = f"v{version}" if version is not None else "unknown"
32+
return typer.confirm(
33+
f"Harness '{info['name']}' already exists (current version "
34+
f"{version_label}). Update it to a new version?",
35+
default=False,
36+
)
37+
38+
2839
def deploy_command(
2940
config_file: Path = typer.Option("agentkit.yaml", help="Configuration file"),
3041
harness: Optional[str] = typer.Option(
@@ -52,6 +63,12 @@ def deploy_command(
5263
"--allowed-id",
5364
help="Comma-separated allowed client IDs for OAuth2/JWT auth (harness deploy).",
5465
),
66+
yes: bool = typer.Option(
67+
False,
68+
"--yes",
69+
"-y",
70+
help="Harness deploy: update an existing same-name harness without prompting.",
71+
),
5572
):
5673
"""Deploy the Agent to target environment."""
5774
from agentkit.toolkit.executors import DeployExecutor
@@ -66,6 +83,7 @@ def deploy_command(
6683
secret_key=volcengine_secret_key,
6784
discovery_url=discovery_url,
6885
allowed_id=allowed_id,
86+
assume_yes=yes,
6987
)
7088
return
7189

@@ -107,9 +125,12 @@ def _deploy_harness(
107125
secret_key,
108126
discovery_url,
109127
allowed_id,
128+
assume_yes: bool = False,
110129
):
111130
"""Deploy a harness spec <name>.harness.json from the current directory."""
112-
from agentkit.toolkit.sdk import deploy_harness
131+
import sys
132+
133+
from agentkit.toolkit.sdk import deploy_harness, HarnessDeployAborted
113134
from agentkit.toolkit.cli.console_reporter import ConsoleReporter
114135
from agentkit.toolkit.context import ExecutionContext
115136

@@ -118,6 +139,16 @@ def _deploy_harness(
118139
reporter = ConsoleReporter()
119140
ExecutionContext.set_reporter(reporter)
120141

142+
# Decide how a same-name harness collision is resolved:
143+
# --yes -> update without prompting
144+
# tty -> prompt [y/N]
145+
# non-tty -> on_conflict=None, so deploy_harness fast-fails
146+
on_conflict: Optional[Callable[[Dict], bool]] = None
147+
if assume_yes:
148+
on_conflict = lambda info: True # noqa: E731
149+
elif sys.stdin.isatty():
150+
on_conflict = _prompt_harness_update
151+
121152
# Surface fast-fail input/validation errors (missing spec, missing creds,
122153
# name collision) as a clean CLI error + exit code, not a raw traceback.
123154
try:
@@ -129,7 +160,11 @@ def _deploy_harness(
129160
discovery_url=discovery_url,
130161
allowed_id=allowed_id,
131162
reporter=reporter,
163+
on_conflict=on_conflict,
132164
)
165+
except HarnessDeployAborted:
166+
console.print("[yellow]Harness deploy cancelled.[/yellow]")
167+
return
133168
except (ValueError, FileNotFoundError, NotADirectoryError) as exc:
134169
console.print(f"[red]❌ Harness deploy failed: {exc}[/red]")
135170
raise typer.Exit(1)

‎agentkit/toolkit/config/utils.py‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,26 @@ def load_veadk_yaml_file(project_dir: Path) -> Dict[str, str]:
8282
return {}
8383

8484

85+
# Opt-in denylist of env keys to drop from the veADK-compat (.env / config.yaml)
86+
# layer before it is merged into a runtime's uploaded environment. Empty by
87+
# default (no-op). A deploy flow may populate it (scoped, restored afterwards) to
88+
# keep deploy-only secrets in a local `.env` from leaking into the cloud runtime;
89+
# e.g. harness deploy excludes the Volcengine deploy credentials here. Compared
90+
# case-insensitively. Higher-priority sources (agentkit.yaml runtime_envs) are
91+
# unaffected, so a value explicitly set there still reaches the runtime.
92+
COMPAT_ENV_EXCLUDE: set = set()
93+
94+
8595
def load_compat_config_files(project_dir: Optional[Path] = None) -> Dict[str, str]:
8696
"""Load compatibility configuration files (.env and veADK config.yaml).
8797
8898
This function loads external configuration files for veADK compatibility:
8999
1. Load standard .env file if exists (higher priority)
90100
2. Load veADK config.yaml file if exists and flatten nested structure (lower priority)
91101
102+
Keys listed in :data:`COMPAT_ENV_EXCLUDE` are dropped from the result (used to
103+
keep deploy-only credentials out of the uploaded runtime environment).
104+
92105
Args:
93106
project_dir: Project directory to search for files. If None, uses current working directory.
94107
@@ -102,6 +115,10 @@ def load_compat_config_files(project_dir: Optional[Path] = None) -> Dict[str, st
102115
veadk_envs.update(load_veadk_yaml_file(project_dir))
103116
veadk_envs.update(load_dotenv_file(project_dir))
104117

118+
if COMPAT_ENV_EXCLUDE:
119+
excluded = {k.upper() for k in COMPAT_ENV_EXCLUDE}
120+
veadk_envs = {k: v for k, v in veadk_envs.items() if k.upper() not in excluded}
121+
105122
return veadk_envs
106123

107124

‎agentkit/toolkit/harness/__init__.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@
1515
"""Harness deploy support: flatten a layered harness spec and deploy it as a runtime."""
1616

1717
from .config_builder import build_agentkit_config
18-
from .deploy import deploy_harness, load_harness_registry
18+
from .deploy import HarnessDeployAborted, deploy_harness, load_harness_registry
1919
from .env_mapping import to_runtime_env
2020

2121
__all__ = [
2222
"to_runtime_env",
2323
"build_agentkit_config",
2424
"deploy_harness",
2525
"load_harness_registry",
26+
"HarnessDeployAborted",
2627
]

0 commit comments

Comments
 (0)