AS AgentScopeAPI 手册 / API Reference
MODULE C2 · runner · 160 XP · 30 MIN

状态机与事件流State machine and event stream

先说问题The problem

你以为 POST /chat/ 会返回回复。它不返回。它只是启动了一次后台运行,立刻回你一个 201。把它当请求-响应写的代码,全都拿到空消息。

You assumed POST /chat/ returns the reply. It does not. It starts a background run and answers 201 immediately. Every client written as request-response reads back an empty list.

先动手Do it first

先在页面上接线——页面只懂规则。然后跑下面那条判题,那才是真会失败的东西。Wire it on the page — the bench only knows the rules. Then run the check below; that is the part that can actually fail.

把一次带审批的工具调用排成真实的发生顺序。顺序错了,说明你心里的事件流和代码里的不是同一个。

Order one approval-bearing tool call the way it actually happens. Get it wrong and your mental event stream is not the one in the code.

  1. 工具结果写入消息,state=FINISHED,会话回到 idleThe tool result lands in the messages, state=FINISHED, session back to idle
  2. 模型产出 tool_use 块,权限引擎拿到 (tool, args)The model emits a tool_use block and the permission engine sees (tool, args)
  3. 规则命中 ASK,state=ASKING,会话停在 awaiting_permissionA rule returns ASK, state=ASKING, the session parks in awaiting_permission
  4. 人给出 AskUserAnswer,state=ALLOWEDA human answers with AskUserAnswer, state=ALLOWED
  5. SSE 用 after 游标把这段轨迹回放给客户端The SSE stream replays this stretch to the client using the after cursor
  6. 工具真正执行,state=SUBMITTEDThe tool executes, state=SUBMITTED
你拼出来的东西What you assembled
create_app(…)

为什么这样设计Why it is built this way

01四个状态,不是两个Four states, not two

SessionStatus 是 running / idle / awaiting_permission / awaiting_external_result。它把「集群里有没有 worker 持有租约」和「持久化上下文有没有停在某个工具调用上」统一成一个枚举。只看 running 会把「等人批」当成「还在跑」。

SessionStatus is running / idle / awaiting_permission / awaiting_external_result. It unifies two facts — whether any worker holds the run lease, and whether the durable context is parked on a tool call. Watching only running mistakes waiting for a human for still working.

02事件流可以回放The event stream is replayable

GET /sessions/{id}/stream 带一个 after 游标。断线重连不是重新订阅,是从游标继续读——这就是「重放轨迹」的地基。

GET /sessions/{id}/stream takes an after cursor. Reconnecting is not resubscribing, it is continuing from the cursor — the foundation of replaying a trajectory.

03消息在会话里,不在响应里Messages live in the session, not the response

回复落在 session record。评测和 UI 都应该读会话,而不是读那次 POST 的返回体。

The reply lands in the session record. Evaluation and UI should both read the session, never the POST's response body.

用到的 APIAPIs in play

代码The code

C2 · Python
status = client.get(f"/sessions/{sid}/status", params={"agent_id": aid}).json()["status"]
# running -> idle | awaiting_permission | awaiting_external_result

with client.stream("GET", f"/sessions/{sid}/stream",
                   params={"agent_id": aid, "after": cursor}) as stream:
    for line in stream.iter_lines():
        ...

判题The check

<span class="ui-zh">运行这条</span><span class="ui-en">Run this</span>
python lab.py run c2

它断言了什么What it asserts

  • POST /chat/ -> 200, body carries no reply
  • status chain: running -> awaiting_permission
  • parked call Write state=asking, 1 suggested rule(s), nothing executed
  • replay log: 1 event(s); from event_cursor 8-0 only 0 remain
  • confirmed=True -> idle, tool_result success, file written
  • POST /sessions/{id}/interrupt on a parked run -> 202

会踩的坑Where it hurts

  • 把 awaiting_permission 当作失败去轮询超时,会写出一坨重试逻辑;它是正常终态:上下文已持久化,等人来点。

    Treating awaiting_permission as a failure and polling it into a timeout breeds retry spaghetti. It is a normal resting state: the context is durable and a human is expected to click.

费曼闸门Say it back

为什么框架要把「有没有 worker」和「上下文有没有挂起」合成一个状态字段?分开会有什么样的 bug?

Why fold cluster liveness and parked-context into one status field? What bug does keeping them apart create?

页面只存通行码的哈希。判题没真跑过,这里就解不开。The page stores a hash, not the code. Nothing unlocks until the command really ran.

基于官方固定版本源码静态生成Generated from pinned official sources · Python 2.0.11 · Java 2.0.4
签名与文档为静态抽取;如有疑问请以源码为准。Signatures and docs are extracted statically; verify against the source when in doubt.