Apache Maka 0.2 Agent 开发实战详解

实战详解 Apache Maka 0.2 的 agent 开发:自建 OpenAI 兼容 stub 模型、配置连接与凭证、跑通带工具调用的完整会话,并从事件库取证分析 Runtime 事件链。

Apache MakaMakaApacheAI Agent2026/10/15

Apache Maka 0.2 Agent 开发实战详解

状态提示:Apache Maka 处于孵化器孵化阶段,0.2.0-incubating 为孵化期候选发布,接口与存储格式可能变化,请勿用于生产环境。本文全部命令、代码与回显均来自 Ubuntu 22.04 服务器实测(安装步骤见本系列部署篇)。

一、实战目标

在不依赖任何外部 API key 的前提下,完整体验 Maka 的 agent 会话链路,并深入其事件存储。具体目标:

  1. 编写一个 OpenAI Chat 兼容的本地 stub 模型服务(支持 SSE 流式与工具调用);
  2. 以 openai-compatible 连接类型接入 Maka;
  3. 跑通"模型消息 → 工具调用 → 工具执行 → 工具结果 → 最终总结"的完整闭环;
  4. 从 runtime.sqlite 取证,分析 Runtime 的事件模型;
  5. 沉淀三个真实踩坑记录:流式协议、工具参数校验、连接文档校验。

这套实验方法的价值在于:它把"模型"从 agent 评测中抽掉,让你能确定性地复现 Runtime 行为——做工具、权限、事件存储的二次开发时,stub 模型是最可靠的测试伙伴。 展开说确定性三字的分量。真实模型有三个特性让集成测试举步维艰:输出随机(同一提示两次回答不同),行为依赖网络与配额(失败原因可能是限流而非代码),工具调用参数带自由度(难以断言精确值)。stub 把三者同时归零:响应序列写死在脚本里,测试进程与外网完全隔离,工具参数由测试者精心构造(包括故意构造非法参数去触发校验分支)。于是对 Runtime 的每一项行为——事件写入顺序、工具校验规则、错误分类、超时重试——都可以写成"给定输入、断言输出"的确定性用例。本系列实测中的三个坑(第五节)正是靠 stub 才把根因定位到协议细节:若是真实模型,流式失败与模型抽风难以区分,而 stub 让"每次都 stream_truncated"成为稳定复现的铁证。

二、编写 stub 模型服务

2.1 协议要点

Maka 的 openai-compatible 适配器按 OpenAI Chat Completions 协议调用端点,且默认以流式(stream=true)发起。因此 stub 必须实现:

  • POST /v1/chat/completions:请求体含 model、messages、stream 字段;
  • 流式响应:Content-Type 为 text/event-stream,每条事件形如 data: {JSON}\n\n,以 data: [DONE]\n\n 结束;
  • 工具调用:assistant 增量消息的 delta.tool_calls 携带工具名与 JSON 参数,finish_reason 为 tool_calls;
  • 工具结果回传:下一轮请求的 messages 中会出现 role 为 tool 的消息。 四条要点背后各有一段协议语义,值得逐条说透。流式帧格式方面,SSE 把响应拆成一串 data: 行,每行一个 JSON 对象(chat.completion.chunk),行间以空行分隔,结尾帧的 data 字面量为 [DONE];Content-Type 必须是 text/event-stream,这是客户端识别流式响应的依据。增量累积方面,chunk 内的 delta 是"这一帧新增的内容",客户端把多帧的 delta.content 字符串拼接、把 delta.tool_calls 按数组索引合并(首帧带 id/name/完整 arguments,后续帧可续传参数片段),最终在内存中合成完整 assistant 消息——stub 每帧给出完整字段是为省去合并测试,真实模型普遍分片。finish_reason 语义方面,tool_calls 表示"模型要求执行工具,请把结果作为下一轮输入",stop 表示"回答结束",Runtime 正是靠它决定走工具分支还是收尾;若流中断(连接断开、缺 [DONE]),客户端无法确认 finish_reason,即第五节坑一的 stream_truncated。工具结果回传方面,tool 消息的 content 是 Runtime 序列化的执行结果对象(含 stdout、stderr、exitCode 等字段),格式对模型服务是透明的——stub 直接 JSON.parse 提取即可。理解了这四点,读 2.2 节脚本就只剩纯粹的 Node 语法。

2.2 完整脚本

将以下内容保存为 /opt/maka-stub/server.js(Node 原生模块实现,零依赖):

const http = require("http");

function toolCallChunks(model) {
  const base = { id: "chatcmpl-stub-1", object: "chat.completion.chunk",
    created: Math.floor(Date.now() / 1000), model };
  return [
    { ...base, choices: [{ index: 0,
      delta: { role: "assistant", content: "I will list the packages directory with a shell command." },
      finish_reason: null }] },
    { ...base, choices: [{ index: 0,
      delta: { tool_calls: [{ index: 0, id: "call_stub_1", type: "function",
        function: { name: "Bash",
          arguments: JSON.stringify({ command: "ls -1 /opt/apache-maka-0.2.0-incubating/packages" }) } }] },
      finish_reason: null }] },
    { ...base, choices: [{ index: 0, delta: {}, finish_reason: "tool_calls" }] }
  ];
}

function finalChunks(model, messages) {
  let pkgs = [];
  for (const m of messages) {
    if (m.role !== "tool") continue;
    try {
      const parsed = typeof m.content === "string" ? JSON.parse(m.content) : m.content;
      const stdout = parsed && parsed.output && parsed.output.stdout;
      if (typeof stdout === "string") pkgs = stdout.split("\n").map(s => s.trim()).filter(Boolean);
    } catch (e) {
      const text = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
      pkgs = text.split("\n").map(s => s.trim()).filter(Boolean);
    }
  }
  const summary = "The workspace contains " + pkgs.length + " packages: " + pkgs.join(", ") + ".";
  const base = { id: "chatcmpl-stub-2", object: "chat.completion.chunk",
    created: Math.floor(Date.now() / 1000), model };
  return [
    { ...base, choices: [{ index: 0, delta: { role: "assistant", content: summary }, finish_reason: null }] },
    { ...base, choices: [{ index: 0, delta: {}, finish_reason: "stop" }] }
  ];
}

function writeSSE(res, chunks) {
  res.writeHead(200, { "Content-Type": "text/event-stream",
    "Cache-Control": "no-cache", Connection: "keep-alive" });
  for (const c of chunks) res.write("data: " + JSON.stringify(c) + "\n\n");
  res.write("data: [DONE]\n\n");
  res.end();
}

const server = http.createServer((req, res) => {
  if (req.method === "POST" && req.url === "/v1/chat/completions") {
    let body = "";
    req.on("data", c => body += c);
    req.on("end", () => {
      const p = JSON.parse(body);
      const msgs = p.messages || [];
      const final = msgs.some(m => m.role === "tool");
      console.log("[stub] stream=%s roles=%s", p.stream, JSON.stringify(msgs.map(m => m.role)));
      if (p.stream) {
        writeSSE(res, final ? finalChunks(p.model, msgs) : toolCallChunks(p.model));
      } else {
        const chunks = final ? finalChunks(p.model, msgs) : toolCallChunks(p.model);
        const message = { role: "assistant" };
        if (final) {
          message.content = chunks[0].choices[0].delta.content;
        } else {
          message.content = chunks[0].choices[0].delta.content;
          message.tool_calls = chunks[1].choices[0].delta.tool_calls.map(tc =>
            ({ id: tc.id, type: tc.type, function: tc.function }));
        }
        const completion = {
          id: final ? "chatcmpl-stub-2" : "chatcmpl-stub-1", object: "chat.completion",
          created: Math.floor(Date.now() / 1000), model: p.model,
          choices: [{ index: 0, message, finish_reason: final ? "stop" : "tool_calls" }],
          usage: { prompt_tokens: 100, completion_tokens: 50, total_tokens: 150 }
        };
        res.writeHead(200, { "Content-Type": "application/json" });
        res.end(JSON.stringify(completion));
      }
    });
    return;
  }
  if (req.method === "GET" && req.url === "/v1/models") {
    res.writeHead(200, { "Content-Type": "application/json" });
    res.end(JSON.stringify({ object: "list", data: [{ id: "stub-echo", object: "model" }] }));
    return;
  }
  res.writeHead(404, { "Content-Type": "application/json" });
  res.end(JSON.stringify({ error: { message: "not found" } }));
});
server.listen(18080, "127.0.0.1", () => console.log("[stub] listening on 127.0.0.1:18080"));

脚本逻辑:第一轮请求(messages 中无 tool 角色)返回文本 + 一个 Bash 工具调用(执行 ls 列出 Maka 源码的 packages 目录);后续轮次从工具结果 JSON 中提取 output.stdout 字段,拼接成最终总结。 实现上有三处设计值得一提。一是 writeSSE 函数统一了流式出口:所有响应都走同一函数,帧序写完立即 [DONE] 并 res.end(),杜绝了"忘发结束帧"这一新手最容易犯的流式错误。二是 finalChunks 的结果解析做了双保险:先按 Runtime 的标准格式从 content 中 JSON.parse 出 output.stdout,解析失败则把 content 按纯文本拆行兜底——这让脚本对结果格式的细微变化保持韧性,也是学习"工具结果长什么样"的活教材。三是 messages 遍历取所有 tool 消息而不是最后一条,天然兼容多工具调用的会话形态。整个脚本刻意控制在零依赖:不用任何 npm 包,Node 原生 http 模块即可,复制到任何有 Node 的机器都能跑,这本身就是对"OpenAI 兼容端点最低实现成本"的一次量化——不到百行。

2.3 启动与自测

mkdir -p /opt/maka-stub
node /opt/maka-stub/server.js &

流式自测:

curl -s -N -m 5 -X POST http://127.0.0.1:18080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model":"stub-echo","stream":true,"messages":[{"role":"user","content":"hi"}]}' | head -c 120

实测回显:

data: {"id":"chatcmpl-stub-1","object":"chat.completion.chunk","created":1792010467,"model":"stub-echo","choices":[{"ind

自测通过的标准是三个可观察特征:响应以 data: 开头(SSE 帧形态正确);JSON 对象的 object 字段为 chat.completion.chunk(chunk 结构正确);-m 5 限时内 curl 正常退出(无挂起)。若这里看到的不是 data: 开头而是完整 JSON 对象,说明跑的是旧版非流式脚本,接入 Maka 必然复现坑一。curl 的 -N 关闭缓冲,让流式帧实时打印;-m 5 是保险丝,stub 挂起时保证命令行不被拖死。这个自测步骤看似多余,实际把"stub 正确"与"Runtime 正确"两个变量解耦了——后面 maka run 出问题时,可以先回这里跑一次,快速排除 stub 自身的原因。

三、连接接入

3.1 连接目录(connection-catalog.json)

写入工作区(root 用户路径为 /root/.config/Maka Dev/workspaces/default/):

WS="/root/.config/Maka Dev/workspaces/default"
cat > "$WS/connection-catalog.json" << EOF
{
  "schemaVersion": 2,
  "revision": 1,
  "defaultTarget": {"connectionId": "11111111-2222-4333-8444-555555555555", "modelId": "stub-echo"},
  "connections": [
    {
      "connectionId": "11111111-2222-4333-8444-555555555555",
      "revision": 1,
      "slug": "stub-relay",
      "name": "Stub Relay",
      "providerType": "openai-compatible",
      "baseUrl": "http://127.0.0.1:18080/v1",
      "enabled": true,
      "enabledModelIds": ["stub-echo"],
      "models": []
    }
  ]
}
EOF

字段要点(均为实测通过的写法):

  • providerType 用 openai-compatible:该类型允许任意 baseUrl(anthropic/openai 等类型绑定官方域名);
  • models 留空数组:适配器会在运行时按需发现模型(stub 实现了 GET /v1/models);
  • defaultTarget 指向本连接与 stub-echo 模型,使命令行可以省略 --connection/--model 参数;
  • revision 从 1 开始,每次修改文档需递增。 补充连接在运行时被解析的路径,有助于理解字段间的关联:CLI 启动时读取 connection-catalog.json,按 --connection 参数(或 defaultTarget)匹配 slug 找到连接条目;openai-compatible 适配器取 baseUrl 拼出完整端点(baseUrl 以 /v1 结尾,运行时在其后追加 /chat/completions);再到 credential-vault.json 按 locator(connectionId + kind=api_key)找到密钥,注入请求的 Authorization 头;enabledModelIds 决定 --model 参数的合法取值范围。四份信息各就各位后,一次模型调用的全部要素才齐备。任一环节断裂的报错形态不同:slug 匹配不到报连接不存在,模型不在 enabledModelIds 里报模型未启用,凭证缺失报凭证错误——分层报错让配置问题可以按报错文本直接定位到具体字段。

3.2 凭证库(credential-vault.json)

openai-compatible 类型要求配置 api_key 凭证:

cat > "$WS/credential-vault.json" << EOF
{
  "schemaVersion": 1,
  "revision": 1,
  "entries": [
    {
      "locator": {"scope": "connection", "connectionId": "11111111-2222-4333-8444-555555555555", "kind": "api_key"},
      "credentialId": "66666666-7777-4888-8999-000000000000",
      "revision": 1,
      "secret": "sk-stub-local-0001",
      "updatedAt": 1760000000000
    }
  ]
}
EOF

凭证条目通过 locator(scope=connection + connectionId + kind)与连接关联;secret 为任意非空字符串(stub 不校验密钥,真实服务则填真实 key)。凭证独立成文档意味着 connection-catalog.json 可以安全地展示、导出与评审。两份文档都建议设置 chmod 600 权限,凭证文件的读取面越窄越好。

四、运行完整会话

4.1 非交互执行

cd /opt/apache-maka-0.2.0-incubating
maka run "List packages dir" --connection stub-relay --model stub-echo --yolo --timeout 80

参数说明:--connection/--model 显式选择连接与模型(省略时用 defaultTarget);--yolo 给任务完整的文件与网络访问权(跳过确认);--timeout 控制单次调用超时(秒)。 四个参数的取值在本实验中各有用意:--connection/--model 显式写出而不依赖 defaultTarget,是为了验证显式参数与目录解析的匹配逻辑(若 slug 拼写错误,报错会直接指向连接未找到);--yolo 是因为任务要执行 ls 命令,跳过确认环节让会话全自动完成(stub 场景不存在误操作风险,目录边界是本机源码包);--timeout 80 给三轮模型调用与一次工具执行留出充足余量。换成真实模型时,只有 baseUrl 与密钥需要变,参数结构与超时策略可以原样保留——这也是连接抽象的价值:任务描述、权限模式、超时策略与"哪个模型服务"完全解耦。

实测回显:

The workspace contains 9 packages: cli, computer-use, core, eval, mcp, runtime, runtime-host, storage, ui.

这一行输出背后发生了完整的四步闭环。stub 服务的日志如实记录了三轮消息形态:

[stub] stream=true roles=["user"]
[stub] stream=true roles=["system","user"]
[stub] stream=true roles=["system","user","assistant","tool"]

第一轮只有用户指令;第二轮请求里 Maka 附上了系统提示词(system);第三轮请求里 assistant 的工具调用消息与 tool 角色的执行结果都已追加——这正是"记录模型消息、工具调用与工具结果"在协议层的直接体现。 三轮消息形态的信息量比表面更丰富。第二轮注入 system 是 agent 框架的标配动作:Runtime 在用户指令之外叠加自己的系统提示词(描述可用工具、执行规范、输出要求),这就是模型"知道"自己有 Bash 工具可用的原因——工具能力通过协议层的工具定义与提示词双重传递。第三轮的四角色消息(system、user、assistant、tool)就是标准的上下文累积:前一轮 assistant 发起的工具调用与其执行结果一并回传,模型据此生成下一轮回答。注意 Runtime 没有为工具结果开辟任何旁路——一切都回归 Chat 协议的消息流,这意味着任何 OpenAI 兼容服务天然具备接入 Maka 的协议基础,适配成本集中在流式与工具调用两个字段族上。

4.2 事件取证

会话数据全部落库。用 Node 内置 sqlite 模块查看事件流:

node -e "
const { DatabaseSync } = require('node:sqlite');
const db = new DatabaseSync('/root/.config/Maka Dev/workspaces/default/runtime.sqlite', { readOnly: true });
const evs = db.prepare('SELECT event_seq, event_kind FROM runtime_events ORDER BY event_seq DESC LIMIT 8').all();
for (const e of evs.reverse()) console.log(e.event_seq, e.event_kind);
" 2>/dev/null

实测回显(一次成功会话的尾部事件):

4 function_call
5 tool_dispatch
5 tool_dispatch
5 function_response
6 function_response
6 function_response
6 text
7 text
7 text
7 completed

事件链解读:function_call(模型请求工具)→ tool_dispatch(运行时派发)→ function_response(工具结果落库)→ text(模型总结)→ completed(会话终态)。工具操作表中对应的 Bash 调用终态为:

[{"tool_name":"Bash","current_state":"outcome_committed"}]

runtime_events 的 payload_json 字段保存了完整载荷(消息内容、工具参数、执行结果的 stdout/stderr/exitCode),可据此重建整次会话的执行轨迹。 事件序号后的成对与三连模式(5 tool_dispatch 两条、6 function_response 三条、7 text 三条)值得留意:同一序号的多条事件说明 Runtime 采用"意图 + 确认"的两段式写入——先写一条标记动作开始,再写一条携带完整载荷标记动作完成。这种模式在进程崩溃时的表现是:库里可能存在只有前半段的孤立事件,恢复逻辑据此识别"上次执行中断于此",而不是把半条记录当作完整事实回放。event_seq 的全局单调递增则保证了任意时刻 dump 出事件流都能按序还原,与 SQLite 的事务性叠加,构成"进程随时可死、状态永远一致"的持久化保证。对审计场景,payload_json 里的 stdout/stderr/exitCode 三元组让每次命令执行的完整输出可复查,这是比 shell history 强得多的取证粒度。

五、三个真实踩坑实录

5.1 坑一:不支持的流式协议导致超时

最初的 stub 直接返回完整 JSON(非流式)。现象是 CLI 长时间无响应后报:

maka run: timed out after 50000ms

取证:查询 usage_model_call_attempts 表,每次模型调用的错误分类清晰可见:

ATTEMPT: {"status":"failed","error_class":"stream_truncated","latency_ms":15,"connection_slug":"stub-relay","provider_id":"openai-compatible","model_id":"stub-echo"}

(节选关键字段;完整行还包含 attempt_id、session_id、completed_at 等列。)

Maka 以流式发起调用,端点返回整段 JSON 而非 SSE 时,客户端判定流被截断并重试,直至超时。修复:实现 SSE 流式响应(见 2.2 节 writeSSE)。排查此类问题时 usage_model_call_attempts 的 error_class(stream_truncated、network 等)是最直接的证据来源。 这个坑的教训可以提炼为一条协议纪律:实现 OpenAI 兼容端点时,流式支持不是可选项。它的隐蔽性来自测试假象——用 curl 发普通请求能看到完整 JSON 回复,直觉上服务"没问题";但 Runtime 的请求带 stream:true,期待的是事件流,收到的是一次性 JSON 体,两者在 HTTP 层都是 200,差异只在 Content-Type 与报文结构。从 stub 演进的角度记录时间线:第一版脚本按"最简实现"写成了非流式,curl 自测通过,接入 Maka 即超时;改流式后一次通过。给自建端点的开发者的建议是把"带 stream:true 的 curl 自测"写进验收清单,而不是只测非流式路径。

5.2 坑二:工具参数的严格校验

stub 最初在 Bash 参数里带了 description 字段(模拟常见 OpenAI 工具调用习惯)。工具执行被拒绝,CLI 输出的错误:

Error: Tool "Bash" arguments failed validation: [{"code":"unrecognized_keys","keys":["description"],"path":[],"message":"Unrecognized key: [redacted]"}]

Runtime 对工具参数做严格校验:Bash 仅接受 command、timeout_ms、run_in_background、pty 四个键,多余键整体拒绝。这个行为对 agent 可靠性是好事(防止模型幻觉出参数),对接入自定义模型服务的一方则要求 arguments 与工具 schema 严格一致。 报错文本本身也展示了 Runtime 的工程质量:unrecognized_keys 是结构化错误码,keys 数组指出被拒绝的字段名,path 定位错误发生的参数层级——这类可机读的错误让上层(CLI、事件库、监控)都能统一解析。值得对照的是主流模型的行为差异:不少模型在工具调用里附加 description 等说明性字段(训练数据中的常见模式),宽容实现的系统会静默忽略,Maka 选择直接拒绝。拒绝而非忽略的理由可以理解:静默忽略会让"模型输出了非契约字段"这一事实不可见,长期积累可能掩盖模型行为漂移;显式拒绝则把问题立刻暴露在日志与错误通道里。接入自建模型服务时,确保 arguments 序列化自工具 schema 的严格子集即可规避。

5.3 坑三:连接文档的严格校验

手工构造 connection-catalog.json 时,任何字段不合规都会被 invalid_document 类错误拒绝。实测确认的硬性约束:

  • connectionId、credentialId 必须是标准 UUID 字符串;
  • slug 仅小写字母、数字与连字符;
  • baseUrl 必须是合法 http/https URL,不能含用户名/密码与查询串;
  • providerType 必须是注册表中的类型(openai-compatible 是自定义中转的合法值);
  • schemaVersion 必须为 2(连接目录)/1(凭证库)。

建议首次配置时优先用 CLI 交互向导生成标准文档,再按需手工微调。 把三条坑串起来看,会得到一个一致的结论:Maka 在协议与配置两个边界上都选择了严格校验而非宽容解析,宁可让第一次配置多花十分钟,也不让格式偏差沉到运行期的诡异行为里。三处报错(stream_truncated、unrecognized_keys、invalid_document)全部是结构化、可检索、含定位信息的形态,配合事件库的落库,任何失败都留有可查证据。这种设计取向在孵化期项目里并不多见——多数项目的早期版本边界校验粗糙,往往要到大规模使用后被迫补课,兼容包袱沉重。对评估者的启示是:接入阶段遇到的每个校验报错都是学习存储格式的机会,报错信息本身就能带你去源码里对应的 schema 定义。

六、进阶玩法

6.1 stdin 上下文注入

run 命令支持管道输入作为任务上下文:cat 文件 | maka run - "总结这个文件" 形态可把任意文本喂给 agent,适合批处理场景。上下文经 stdin 进入后与普通用户指令同等参与会话,脚本化集成时这是最轻量的数据通道。

6.2 会话延续

--continue 继续当前目录最近一次兼容任务,--resume <session-id> 恢复指定会话。会话 ID 可从 core_agent_runs 表查询。配合事件库的完整回放能力,可以做到"昨天的任务今天接着干"。

6.3 评估框架(eval)

eval 子包支持声明式多臂实验:定义若干 subject 与任务集,自动并行运行并汇总。用 stub 模型作为其中一个 subject,可以精确对照"不同 Runtime 配置"下的行为差异(如权限模式、上下文上限),排除模型随机性干扰。 把三条进阶路径合起来看,可以拼出一个完整的工程化场景:用 stdin 注入喂入变更日志,让 agent 起草发布说明(6.1);中途断了用 --resume 接着跑,事件库保证上下文不丢(6.2);同一个任务跑 eval 多臂实验对比新旧 Runtime 配置的表现差异,用 stub 消除模型噪声后,差异全部来自配置本身(6.3)。这三步在真实团队里的对应物是:自动化例行任务、长任务断点续跑、基础设施升级前的行为回归——Maka 的设计让三者都不需要额外的胶水代码。评估结果的解读要以事件库为准绳:臂与臂的差异先看事件序列是否同构,再看用量表的时间分布,最后才看最终文本——文本是最容易受干扰的表层,事件才是行为真相。

七、常见问题(FAQ)

1. stub 模型返回正常,但 CLI 一直 timed out? 九成是流式协议问题:Maka 默认 stream=true 调用,端点必须返回 SSE(data: 行 + [DONE] 结尾)。查 usage_model_call_attempts 的 error_class,出现 stream_truncated 即为此因。

2. 如何看一次会话到底调用了哪些工具? 查 tool_operations 表的 tool_name 与 current_state 列;结合 runtime_events 中 function_call/function_response 事件的 payload_json 可以还原完整参数与结果。

3. 能接 Ollama 本地模型吗? 可以。providerType 选 ollama(默认指向本地推理端口),配合真实本地模型即可;但模型需支持工具调用,否则 agent 无法完成需要工具的任务。

4. --yolo 到底放开了什么? 赋予任务完整的文件与网络访问权限并跳过执行确认。开发调试 stub 场景用它最省事;真实使用时建议默认权限模式,让敏感操作有人工确认。

5. 工具调用的并发与超时如何控制? --max-steps 限制单次任务的工具步数上限;--timeout 限制单次调用时长。stub 场景下三个调用(首轮 + 工具 + 总结)通常在数秒内完成。

6. 凭证文件明文保存安全吗? credential-vault.json 中的 secret 为明文,文件权限依赖文件系统(建议 chmod 600)。孵化期实现尚未提供系统级密钥链集成,敏感环境请自行评估或外置密钥管理。

7. 换了模型服务只需要改哪里? 改 connection-catalog.json 的 baseUrl/模型 ID 与 credential-vault.json 的密钥即可;也可以在命令行用 --connection <slug> --model <id> 临时切换,工作区支持并存多条连接。

8. 会话记录占空间吗?如何清理? 每次会话写入 runtime.sqlite(含全部事件载荷)。长期使用后可整库备份后删除重建工作区(删除 "/root/.config/Maka Dev" 目录即完全重置),或按需清理 runtime.sqlite 中历史会话相关行。

9. 大模型的 system 提示词能自定义吗? Runtime 构造 system 消息(实测第二轮请求可见 roles=["system","user"])。孵化期版本对自定义 system 提示词的开关有限,可通过 agent 配置或二次开发 runtime 包调整,接口以源码为准。

10. 如何确认我的端点被 Maka 正确调用? 看 stub 服务的访问日志(本文 stub 打印 stream 与 roles);或查 usage_llm_calls / usage_model_call_attempts 表的 connection_slug、model_id、latency_ms、status 字段。

11. eval 实验结果在哪里? eval 运行产物落在工作区的 artifacts 相关表中,配合 attempt-store 与 fleet-store 表记录每次尝试;详细用法以源码 packages/eval 的 README 与 cli 帮助为准。

12. 孵化期 API 变更会影响这篇文档吗? CLI 参数、连接文档 schemaVersion、工具参数 schema 都可能随版本调整。本文基于 0.2.0-incubating 实测,升级前请对照新版本源码复核关键字段。

13. stub 能模拟工具调用失败吗? 能,而且这是 stub 方法论的重要用例:让 arguments 构造非法参数(如多一个键)即可复现校验拒绝路径;让 ls 指向不存在的目录则产生非零 exitCode 的工具结果,用于验证 Runtime 对失败工具调用的记录与模型的应对。失败路径的测试价值不低于成功路径——事件库中失败调用的状态流转、错误分类的落库行为,都只有靠确定性失败才能稳定验证。

14. 多个任务并行运行时工作区会冲突吗? 会话数据按 session 隔离(core_agent_runs 每次运行一条记录,事件带 session_id),并行任务互不覆写;共享的是工作区文件系统本身,因此并行的 agent 任务如果写同一批文件,仍需上层协调。用不同工作区目录运行完全独立的任务组是更干净的隔离方式。

八、参考链接