You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CHANGELOG.md
+9Lines changed: 9 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -17,3 +17,12 @@
17
17
***TECHOPS-18898:** use conventional commits and attach github release assets ([#9](https://github.com/circlefin/circle-ooak/issues/9)) ([dba08f5](https://github.com/circlefin/circle-ooak/commit/dba08f54b5661a31fead25c94637eb2caeb20644))
18
18
***TECHOPS-19100:** fix ci ([#10](https://github.com/circlefin/circle-ooak/issues/10)) ([552bde8](https://github.com/circlefin/circle-ooak/commit/552bde8416413efe214eb20562e55d70a3fbe1a9))
19
19
***TECHOPS-19100:** switch to public worfklow tokens ([#11](https://github.com/circlefin/circle-ooak/issues/11)) ([d0ac119](https://github.com/circlefin/circle-ooak/commit/d0ac119acd17584b1cac9c0f58e08c524be01385))
20
+
21
+
## 0.2.0 (2026-06-30)
22
+
This API breaking change closes a security vulnerability by adding intent verification during execution.
23
+
24
+
***API breaking change** Tools no longer need to take `wfid` as an argument.
25
+
***Security fix** Fixed vulnerability that allowed LLM to mix-and-match `wfid` and `intent`. This is now checked and enforced by OOAK framework.
26
+
***Tool failure support** The SecurityContext can supply an optional `on_invoke_tool_failure` hook that runs when a tool raises an exception.
27
+
***Added `bind_to_instance` on `InstanceAgent`** — optional runtime object id for instance identity to avoid name collisions across agents.
Copy file name to clipboardExpand all lines: README.md
+89-15Lines changed: 89 additions & 15 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -19,19 +19,18 @@ to add before/after hooks to your tool code.
19
19
a regular OpenAI Agents SDK `Agent` and can interact with other agents via handoffs and guardrails.
20
20
21
21
22
-
This package includes a `WorkflowManager` that implements the abstract `SecurityContext`,
22
+
This package includes a `WorkflowManager` that implements the abstract `SecureContext`,
23
23
that checks that intended actions have been approved.
24
24
25
25
1. Create intent. The agent calls the @secure_tool function with `wfid=None` argument. Instead of
26
26
executing the function, it returns an intent: a json representation of the function call.
27
-
2. Get Approval. The agent calls the WorkflowManager with a list of intents. The WorkflowManager
28
-
approves the list of intents and returns a WorkflowId.
27
+
2. Get Approval. The agent calls the WorkflowManager with a list of intents. The manager invokes `get_approval()` (which you override to connect your UX or policy) and, if approved, returns a WorkflowId.
29
28
3. Execute. The agent now calls the @secure_tools in the correct order with the WorkflowId. The
30
29
WorkflowManager ensures that each subsequent function call matches the approved workflow.
31
30
32
31
Sample code can be found at `https://github.com/circlefin/circle-ooak/example`
33
32
34
-
Below is an exmple of a `WalletWorkflowAgent`.
33
+
Below is an example of a `WalletWorkflowAgent`.
35
34
36
35
```python
37
36
from circle_ooak.instance_agent import InstanceAgent
@@ -68,7 +67,7 @@ class WalletWorkflowAgent(InstanceAgent):
OOAK is a framework: it handles intent capture, workflow state, and **enforcement** (each `@secure_tool` call must match the approved plan). It does **not** include a production approval UI or policy engine.
92
+
93
+
**You must connect your own UX and rules** by subclassing `WorkflowManager` and overriding `get_approval()`. The default implementation auto-approves every workflow so the demo runs without extra setup.
94
+
95
+
Example:
96
+
97
+
```python
98
+
from circle_ooak.workflow_manager import WorkflowManager, ManagerResponse
result =await Runner.run(agent, question, context=manager)
122
+
```
123
+
124
+
After approval, `WorkflowManager` ensures execution matches the stored intents (tool name, arguments, order) at **start** time. Your `get_approval()` implementation decides **whether** a workflow may run; the manager **enforces** your approvals.
125
+
126
+
`approve()` validates intent shape (JSON structure and allowed keys). It does not require intents to come from a particular tool call — what matters is the **content** of each intent and whether your user or policy approves it in `get_approval()`.
127
+
90
128
91
129
## Setup Python environment
92
130
Install the `circle-ooak` package and other dependencies:
@@ -168,22 +206,58 @@ Sending 5 USDC from 0x222222 to 0x333333
168
206
LOG: Finished action {"function": "send_usdc", "arguments": {"sender": "0x222222", "receiver": "0x333333", "amount": 5}, "instance": "Secure Agent"} with result txhash=1234567890
169
207
170
208
LOG: Workflow completed successfully with result txhash=1234567890
171
-
The transactions were successfully executed. Here are the transaction hashes:
209
+
The transactions have been successfully executed:
172
210
173
-
1. Mint 10 USDC from `0x111111` to `0x222222`: `txhash=0987654321`
174
-
2. Send 5 USDC from `0x222222` to `0x333333`: `txhash=1234567890`
211
+
1. Minting 10 USDC from 0x111111 to 0x222222 was successful with transaction hash: `0987654321`.
212
+
2. Sending 5 USDC from 0x222222 to 0x333333 was successful with transaction hash: `1234567890`.
175
213
176
214
```
177
215
178
216
## Dev notes
179
-
Functions decorated with `@secure_tool` must include the following two arguments:
180
-
-`wfid: WorkflowId`. Agents will use the workflow id to get permission to perform tasks.
181
-
-`ctxt: RunContextWrapper[SecurityContext]`. The runner must provide an object as context that
182
-
implements the abstract class `SecurityContext` such as the `WorkflowManager` included in this project.
217
+
Functions decorated with `@secure_tool` on an `InstanceAgent` should include `ctxt: RunContextWrapper[SecureContext]` when they need the workflow context. The runner must provide an object that implements `SecureContext` (for example `WorkflowManager`).
218
+
219
+
OOAK uses a workflow id `wfid` to manage approvals. Do **not** include `wfid` in your Python function signature. Authorization metadata stays in the secure wrapper; your handler only receives arguments in the original function definition.
220
+
221
+
222
+
For production use, subclass `WorkflowManager` and override `get_approval()`.
223
+
You may also implement a custom `SecureContext` if you need different before/after hooks than the built-in workflow state machine.
224
+
225
+
The included `WorkflowManager` is a **reference implementation** for demos and testing. It keeps approved workflows in memory for the life of the process. Production systems should subclass it for persistence, audit logs, branching logic, or other lifecycle needs.
226
+
227
+
### Start, complete, and fail
228
+
229
+
Each `@secure_tool` execution with a `wfid` uses three hooks:
230
+
231
+
| Hook | Role |
232
+
|------|------|
233
+
|`before_invoke_tool` / `start()`|**Authorization** — verify the call matches the approved intent for the current step before the handler runs |
234
+
| Handler | Your business logic |
235
+
|`after_invoke_tool` / `complete()`|**Record success** — store the handler result and advance workflow state. Does not re-authorize; the handler already ran |
236
+
|`on_invoke_tool_failure` / `fail()`|**Record failure** — best-effort transition to `failed` after a handler exception. Always succeeds; it must not leave the workflow stuck |
237
+
238
+
### Instance identity in intents
239
+
OOAK needs a way to uniquely identify agents for approval. There are two choices:
240
+
-**by name**. When an `InstanceAgent` is initialized with `bind_to_instance=False`, then OOAK identifies the agent using the `name` supplied during object creation. This creates a stable name across restarts.
241
+
-** by object reference**. When an `InstanceAgent` is initialized with `bind_to_instance=True`, then OOAK identifies the agent
242
+
using the run-time object identifier. This prevents any naming collisions. Agents have new ids after every restart.
243
+
244
+
### Side effects and workflow state
245
+
246
+
OOAK calls `before_invoke_tool` (start) **before** your handler runs, and `after_invoke_tool` (complete) **after** it returns. If your handler performs an irreversible side effect (transfer funds, sign a transaction, call an external API) and then crashes or hangs, OOAK will not know the outcome of the action. It also will not know how to clean up.
247
+
248
+
**What OOAK does:** the handler call is wrapped in a try/except. If the handler raises, `@secure_tool` calls `on_invoke_tool_failure`, which marks the current action and workflow as `failed`. This prevents a stuck `in_progress` state. It does **not** roll back side effects that already occurred before the exception.
249
+
250
+
**What integrators should do:**
251
+
252
+
-**Compensating tools** — write tools that perform their own cleanup on failure (refund, cancel reservation, revert a pending state).
253
+
-**Two-phase operations** — separate "prepare" and "commit" into different tools or workflow steps so approval covers each phase explicitly.
254
+
-**Custom workflow logic** — subclass `WorkflowManager` or implement `SecureContext` with branching based on reported results (a decision tree), rather than assuming a fixed linear sequence, when your use case requires it.
183
255
256
+
**Handler hangs and infinite loops:** OOAK does not enforce timeouts on tool handlers. A handler that never returns leaves the workflow step in `in_progress` indefinitely. Use `asyncio.wait_for`, application-level deadlines, or runner/SDK limits in your tool implementations and hosting environment.
184
257
258
+
### Testing
185
259
Agent tools with the `@secure_tool` or `@agent_tool` decorators can be tested the same way as those with `@function_tool`.
186
-
We include a model unit test file.
260
+
We include a model unit test file. Unit tests do not require an LLM.
0 commit comments