Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 19 additions & 8 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 6 additions & 3 deletions docs/en/guides/durable-agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,13 +263,16 @@ The sweep is written not to throw, because the SDK gives a failed scheduled call

### The store and the operator side are yours

There is no default approval store, for the same reason the audit sink has no default: one degrading to process memory would answer "approved" for a record the next isolate never heard of. Implement `AgentApprovalStore` — the four methods and the two guarantees are in [Configuring the queue](./agent-interface.md#configuring-the-queue) — and resolve requests through your own routes. [`examples/agents`](https://github.com/gurenjs/guren/tree/main/examples/agents) carries a Drizzle implementation and a small operator API to copy from.
There is no default approval store, for the same reason the audit sink has no default: one degrading to process memory would answer "approved" for a record the next isolate never heard of. Implement `AgentApprovalStore` — the four methods and the two guarantees are in [Configuring the queue](./agent-interface.md#configuring-the-queue) — and resolve requests through your own routes. [`examples/agents`](https://github.com/gurenjs/guren/tree/main/examples/agents) carries a Drizzle implementation and two operator surfaces over it: a JSON API an operator drives with `curl`, and a browser console showing the tickets, the pending approvals, and the agent's own report on one page. Copy whichever shape your deployment needs — they are the same rules either way.

Three things that operator API taught, worth repeating in yours:
Four things those operator surfaces taught, worth repeating in yours:

- **Derive the status; never read the column.** A request whose window has closed still reads `pending` in SQL. Drop it from the answerable listing rather than offering it.
- **Answer a re-answer with 409.** A request someone already resolved, and one whose window closed, are both un-answerable now; a `404` should mean only that no request has that id.
- **Retention is a policy question.** A settled request is your record of what an agent was allowed to do. Deleting old ones is worth a route an operator calls, not a schedule that decides on your behalf.
- **One rule, however many surfaces.** The example's console and JSON API answer approvals through a single shared module, because a second copy of "which rows are answerable" is the copy that hands the agent a grant a human gave once. Only the *presentation* differs: the console redirects back and flashes the refusal, the API returns it as a status code.

A browser console alongside the bearer API needs less wiring than it looks. CSRF is mounted over the whole app by `createApp({ auth })`, and the tool routes stay exempt on their own terms — a bearer request that carries no cookies, and a request carrying the principal the pipeline installed, are both skipped — so nothing has to be excluded by hand. Two things do need care: the session store must be database-backed, since on Workers the login redirect and the page it lands on are answered by different isolates, and the routes carrying `.agent()` metadata must keep returning JSON. `guren check` warns about an agent route that answers with an Inertia response, because a rendered page is not a tool result.

A durable agent's own status check gets exactly the answer `guren.approval_status` gives an MCP client, by the same rule and audited under the same tool name — including the part that is a refusal to distinguish: an unknown id and another principal's id are one message, so neither surface can be used to enumerate what your colleagues are waiting on.

Expand Down Expand Up @@ -422,4 +425,4 @@ The routes an agent calls are checked by the ordinary agent-route rules, and the
- [Encryption](./encryption.md) — `APP_KEY` and the encrypter the ledger needs
- [CLI](./cli.md) — `make:agent`, `check`, `audit`, `tool:list`
- [RFC 0017 — Durable Agent Runtime](https://github.com/gurenjs/guren/blob/main/rfcs/0017-durable-agent-runtime.md) — the design, and every place the shipped behaviour deviates from it
- [`examples/agents`](https://github.com/gurenjs/guren/tree/main/examples/agents) — a working triager, its approval store, its operator API, and the Free-plan measurements
- [`examples/agents`](https://github.com/gurenjs/guren/tree/main/examples/agents) — a working triager, its approval store, its operator API and browser console, and the Free-plan measurements
9 changes: 6 additions & 3 deletions docs/ja/guides/durable-agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,13 +263,16 @@ sweep は throw しないように書かれています。SDK は失敗したス

### ストアと運用者側は自分で書く

承認ストアに既定の実装はありません。監査 sink に既定がないのと同じ理由で、プロセスメモリに退化する実装は、次の isolate が見たこともないレコードに対して「承認済み」と答えてしまうからです。`AgentApprovalStore` を実装してください(4つのメソッドと2つの保証は[キューを設定する](./agent-interface.md#キューを設定する)にあります)。リクエストの解決は自前のルートで行います。[`examples/agents`](https://github.com/gurenjs/guren/tree/main/examples/agents) Drizzle 実装と小さな運用 API があるので、そこから写せます
承認ストアに既定の実装はありません。監査 sink に既定がないのと同じ理由で、プロセスメモリに退化する実装は、次の isolate が見たこともないレコードに対して「承認済み」と答えてしまうからです。`AgentApprovalStore` を実装してください(4つのメソッドと2つの保証は[キューを設定する](./agent-interface.md#キューを設定する)にあります)。リクエストの解決は自前のルートで行います。[`examples/agents`](https://github.com/gurenjs/guren/tree/main/examples/agents) には Drizzle 実装と、その上に載る運用面が2つあります: `curl` で叩く JSON API と、チケット・保留中の承認・エージェント自身のレポートを1画面に並べたブラウザコンソールです。どちらのかたちでも規則は同じなので、デプロイに合うほうを写してください

その運用 API が教えてくれたことのうち、あなたの実装でも繰り返す価値のあるものが3つあります
その2つの運用面が教えてくれたことのうち、あなたの実装でも繰り返す価値のあるものが4つあります

- **status は導出する。カラムを読まない。** ウィンドウが閉じたリクエストも SQL 上はまだ `pending` に見えます。回答可能な一覧からは落としてください。
- **二重回答には 409 を返す。** すでに誰かが解決したリクエストも、ウィンドウが閉じたリクエストも、今は回答できません。`404` は「その id のリクエストが存在しない」だけを意味すべきです。
- **保持期間はポリシーの問題。** 決着したリクエストは、エージェントが何を許されたかの記録です。古いものの削除は、勝手に判断するスケジュールではなく運用者が叩くルートに値します。
- **面がいくつあっても規則はひとつ。** 例のコンソールと JSON API は、承認の解決を共有モジュール1つに通しています。「どの行が回答可能か」の2つ目のコピーは、人間が一度だけ与えた許可をエージェントにもう一度渡してしまうコピーだからです。違うのは見せ方だけで、コンソールは元のページに戻して拒否理由をフラッシュし、API はステータスコードで返します。

bearer API の隣にブラウザコンソールを置く配線は、見た目ほど多くありません。CSRF は `createApp({ auth })` がアプリ全体に載せますが、ツールのルートはそれ自身の条件で除外されます: cookie を持たない bearer リクエストと、パイプラインが principal を載せたリクエストはどちらもスキップされるため、手で除外リストを書く必要はありません。逆に注意が要るのは2点です。セッションストアはデータベース実装にすること(Workers ではログインのリダイレクトと、その着地先のページを別々の isolate が返すため)、そして `.agent()` を持つルートは JSON を返し続けることです。エージェントのルートが Inertia レスポンスを返すと `guren check` が警告します。描画されたページはツールの結果ではないからです。

永続エージェント自身の status 確認は、MCP クライアントに対する `guren.approval_status` とまったく同じ答えを、同じルールで、同じツール名の下に監査されながら受け取ります。区別を拒む部分も含めてです。未知の id と他の principal の id は1つの同じメッセージになるため、どちらの面も同僚が何の承認を待っているかの列挙には使えません。

Expand Down Expand Up @@ -422,4 +425,4 @@ const MAX_ASKS_PER_SWEEP = 10
- [暗号化](./encryption.md): `APP_KEY` と、台帳が必要とする encrypter
- [CLI](./cli.md): `make:agent`・`check`・`audit`・`tool:list`
- [RFC 0017: Durable Agent Runtime](https://github.com/gurenjs/guren/blob/main/rfcs/0017-durable-agent-runtime.md): 設計と、実装が設計から外れたすべての箇所
- [`examples/agents`](https://github.com/gurenjs/guren/tree/main/examples/agents): 動くトリアージャ、その承認ストア、運用 API、そして無料プランでの実測値
- [`examples/agents`](https://github.com/gurenjs/guren/tree/main/examples/agents): 動くトリアージャ、その承認ストア、運用 API とブラウザコンソール、そして無料プランでの実測値
2 changes: 2 additions & 0 deletions examples/agents/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ node_modules
dist
.guren
.cloudflare
.wrangler
.dev.vars
data/
public/assets
types/generated
Loading
Loading