Agent 与记忆
Agent Harness 是 L2 的工程底座:多轮编排、工具调用、任务规划和可观测性开箱即用。 它最重要的部分是记忆大脑 —— 让 Agent 记住这个用户练了多久、伤过哪里、 上次为什么中断。没有这层记忆,Agent 就只是个每次都从头认识你的聊天框。
这套接口的所有产出物都属于你:用户关系、会话数据、记忆内容。 Keep 提供模型、组件和工程能力,不在你的产品里出现署名, 也不把你的用户数据用于我们自己的模型训练。这是写进合同的。
创建 Agent
Agent 是一份可复用的配置:人设、可用的 Skill、可调用的你侧函数、记忆策略与安全边界。
创建一次,之后靠 agent_id 反复发起会话。
| 参数 | 类型 | 说明 | |
|---|---|---|---|
name | string | 必填 | Agent 名称,仅用于你自己识别 |
model | string | 必填 | 驱动模型,可以是微调后的模型 |
instructions | string | 必填 | 人设与行为准则。会在每轮自动注入,命中上下文缓存 |
skills | array | 选填 | 允许该 Agent 自主调用的 Skill ID 列表 |
tools | array | 选填 | 你侧的函数定义,格式同 tool calling |
memory | object | 选填 | 记忆策略,见下一节 |
safety | string | 默认 standard | 同 koach_safety |
max_steps | integer | 默认 8 | 单次运行的最大工具调用轮数,防止失控循环 |
agent = client.agents.create(
name="晨跑营教练",
model="ft:keepace-lite:acme:morning-run-v2:9dK2",
instructions=(
"你是「晨跑营」的带队教练,说话简短直接,不用感叹号。"
"用户问训练相关的问题时,先查他最近的训练数据再回答。"
"涉及伤病时不要自己判断,引导到线下门诊。"
),
skills=[
"training.plan.generate",
"metrics.interpret",
"injury.risk.screen",
],
tools=[{
"type": "function",
"function": {
"name": "get_camp_schedule",
"description": "查询晨跑营本周的线下活动安排",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}],
memory={"mode": "auto", "retention_days": 365},
max_steps=6,
)
print(agent.id) # agt_01JQ9K3R6W
发起运行
一次 run 是一个完整的「用户说了一句话 → Agent 可能调用若干工具 → 给出最终回答」的过程。 Harness 负责编排,你不需要自己写工具调用循环。
run = client.agents.runs.create(
agent_id="agt_01JQ9K3R6W",
profile="usr_8f2a41", # 关联记忆大脑
thread="thr_weekly_checkin", # 会话线程,用于多轮
input="这周状态感觉一般,要不要减量?",
)
print(run.output_text)
for step in run.steps:
print(step.type, step.name, step.duration_ms)
curl "$KOACH_BASE_URL/agents/agt_01JQ9K3R6W/runs" \
-H "Authorization: Bearer $KOACH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"profile": "usr_8f2a41",
"thread": "thr_weekly_checkin",
"input": "这周状态感觉一般,要不要减量?"
}'
{
"id": "run_01JQ9M8T2X",
"agent_id": "agt_01JQ9K3R6W",
"thread": "thr_weekly_checkin",
"status": "completed",
"output_text": "该减。你连续三周加量,这周主观感觉下滑基本是累积疲劳,不是状态问题。\n这周把跑量砍到 24 公里,速度课只留一次,周日长距离缩到 8 公里。下周再看。",
"steps": [
{"type": "memory.read", "name": "profile", "duration_ms": 34},
{"type": "skill", "name": "metrics.interpret", "duration_ms": 812,
"summary": "近四周负荷 28 / 31 / 34 公里递增,HRV 较基线下降 12%"},
{"type": "skill", "name": "injury.risk.screen", "duration_ms": 640,
"summary": "急慢性负荷比 1.38,处于偏高区间"},
{"type": "model", "name": "final", "duration_ms": 1420}
],
"usage": {"tokens": 3210, "skill_calls": 2},
"request_id": "req_01JQ9M8T2X"
}
它记录了 Agent 这一轮到底查了什么、调了哪些 Skill、各花了多久。 排查「为什么它给了这个答案」时,先看 steps 而不是猜提示词。 控制台里有同样内容的可视化时间线。
流式运行
加 stream: true 后按事件推送。与纯模型流式不同,
这里会先推工具调用事件,让你可以在界面上显示「正在查看你的训练记录…」
这类中间状态,避免用户面对几秒钟的空白。
event: run.step.started
data: {"type":"skill","name":"metrics.interpret","label":"正在分析最近四周的训练数据"}
event: run.step.completed
data: {"type":"skill","name":"metrics.interpret","duration_ms":812}
event: run.output.delta
data: {"delta":"该减。你连续三周加量,"}
event: run.output.delta
data: {"delta":"这周主观感觉下滑基本是累积疲劳,"}
event: run.completed
data: {"id":"run_01JQ9M8T2X","usage":{"tokens":3210,"skill_calls":2}}
查询运行
运行记录保留 30 天(私有化下由你自己配置)。可用于事后审计、 质量抽检和构造微调数据。
记忆大脑
这是 Harness 里最难自己造、也最影响体验的部分。 它不是把历史对话原样塞回上下文 —— 那样几轮之后就爆了,而且噪音大于信号。 记忆大脑做的是结构化提炼:从训练数据、对话和行为里抽出稳定的用户事实, 按相关性在需要时召回。
三层记忆
写入档案
client.memory.profile.update(
"usr_8f2a41",
data={
"demographics": {"age_band": "30-34", "sex": "male", "height_cm": 176},
"body": {"weight_kg": 71.5, "resting_hr": 52},
"history": {
"training_years": 4,
"injuries": [
{"site": "achilles_right", "year": 2024, "status": "recovered"}
],
},
"goals": [
{"type": "race", "event": "marathon",
"date": "2026-10-18", "target_time_min": 230}
],
"preferences": {"time_of_day": "morning", "dislikes": ["treadmill"]},
},
)
追加事件
把你侧已有的训练数据同步进来。支持批量,单次最多 500 条。 这一步做得越全,Agent 的回答就越像个真的认识用户的教练。
client.memory.events.append("usr_8f2a41", events=[
{
"type": "workout",
"at": "2026-08-16T06:12:00+08:00",
"data": {"sport": "running", "distance_km": 12.4,
"duration_s": 3720, "avg_hr": 156, "rpe": 7},
},
{
"type": "note",
"at": "2026-08-16T07:05:00+08:00",
"data": {"text": "右脚踝有点紧,跑完拉伸了十分钟"},
},
])
读取与召回
多数情况下你不需要手动读取 —— 传 profile 给 Agent 或 Skill 时
会自动召回。这个接口主要用于在你自己的界面上展示「教练眼中的我」,
以及满足用户的知情权与查阅权要求。
{
"profile_id": "usr_8f2a41",
"updated_at": "2026-08-17T09:20:11+08:00",
"profile": { "…": "同写入结构" },
"insights": [
{
"text": "近四周负荷持续递增,急慢性负荷比升至 1.38,处于受伤风险偏高区间",
"confidence": 0.88,
"evidence": ["evt_9c21", "evt_9d04", "evt_9e77"],
"generated_at": "2026-08-17T05:00:00+08:00"
},
{
"text": "多次在计划包含跑步机课程时改期,明确偏好户外",
"confidence": 0.74,
"evidence": ["evt_8a12", "evt_8f33"]
}
],
"stats": {"events_total": 412, "first_event_at": "2024-11-02"}
}
删除
《个人信息保护法》规定用户有权要求删除其个人信息。 你的产品里需要有对应的入口,并在收到请求后调用这个接口。 删除是硬删除,包括档案、事件与洞察,不可恢复, 执行结果会写入审计日志备查。
client.memory.delete("usr_8f2a41", reason="user_request")
可观测性
Harness 自带的埋点覆盖每一次 run 的完整链路。除了控制台看板, 也可以把事件推到你自己的系统:
| 方式 | 说明 |
|---|---|
| 控制台看板 | 成功率、P50 / P95 延迟、Skill 调用分布、token 消耗、安全边界触发次数 |
| Webhook | run 完成 / 失败 / 触发安全边界时回调你的地址,用于告警与工单 |
| OpenTelemetry | 导出为标准 OTLP,接进你现有的 APM。私有化部署下这是默认方式 |
| 日志导出 | 按天导出结构化日志到你的对象存储,用于离线分析与构造微调数据 |
私有化差异
Harness 与记忆大脑都可以完整部署在客户环境内。接口一致,有三点需要注意:
- 记忆大脑依赖向量检索,私有化部署需要一个向量库。 我们默认提供内置方案,也支持接你已有的(目前验证过 Milvus 与 pgvector)
- 洞察提炼是定时任务,默认每日凌晨执行,会产生一定的推理开销, 容量规划时要算进去
- 控制台看板在私有化下同样部署在客户侧,Keep 不接收任何运行数据