Skip to content

Commit d95d036

Browse files
sahilsuman933cursoragentsahil
authored
Add rejectionPlan documentation from openapi (#613)
* Add tool rejection plan documentation with examples and usage guide Co-authored-by: sahil <[email protected]> * Refactor tool rejection plan example to use model.tools configuration Co-authored-by: sahil <[email protected]> * Add example of transferCall tool with rejection plan configuration Co-authored-by: sahil <[email protected]> --------- Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: sahil <[email protected]>
1 parent 8c7caa8 commit d95d036

File tree

2 files changed

+193
-0
lines changed

2 files changed

+193
-0
lines changed

fern/docs.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,9 @@ navigation:
175175
- page: Custom tools
176176
path: tools/custom-tools.mdx
177177
icon: fa-light fa-screwdriver-wrench
178+
- page: Tool rejection plan
179+
path: tools/tool-rejection-plan.mdx
180+
icon: fa-light fa-shield-xmark
178181
- page: Custom tools troubleshooting
179182
path: tools/custom-tools-troubleshooting.mdx
180183
icon: fa-light fa-wrench

fern/tools/tool-rejection-plan.mdx

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
---
2+
title: Tool rejection plan
3+
subtitle: Prevent unintended tool calls using conditions based on conversation state
4+
slug: tools/tool-rejection-plan
5+
---
6+
7+
## Overview
8+
9+
A rejection plan lets you prevent a tool from executing when certain conditions are met. You attach it to any tool call and it evaluates the recent conversation state to decide whether to reject the call.
10+
11+
- If all conditions match (AND logic), the tool call is rejected.
12+
- To express OR at the top level, use a single group condition with `operator: "OR"`.
13+
- If `conditions` is empty or omitted, the tool always executes.
14+
15+
<Note>
16+
Use on any tool call, e.g., `Assistant.hooks.do[type=tool].tool.rejectionPlan`.
17+
</Note>
18+
19+
## Schema
20+
21+
- **conditions**: Array of condition objects. Defaults to `[]`.
22+
- Types:
23+
- **RegexCondition**: Match message content with a regex
24+
- `type`: "regex"
25+
- `regex`: String pattern. RegExp.test-style substring matching. Escape backslashes in JSON (e.g., `"\\bhello\\b"`). Supports inline flags like `(?i)` for case-insensitive.
26+
- `target` (optional): Which message to inspect
27+
- `role`: `user` | `assistant`
28+
- `position`: Integer index in history (default `-1` for the most recent). Negative counts from the end; `0` is the first message
29+
- `negate` (optional): When `true`, the condition matches if the regex does NOT match (default `false`)
30+
- **LiquidCondition**: Evaluate a [Liquid](https://liquidjs.com/) template that must output exactly `"true"` or `"false"`
31+
- `type`: "liquid"
32+
- `liquid`: The template. You can access `messages` (recent chat messages), `now`, and assistant variables. Useful filters include `last`, `where`, and `reverse`
33+
- **GroupCondition**: Combine multiple conditions
34+
- `type`: "group"
35+
- `operator`: `AND` | `OR`
36+
- `conditions`: Nested list of conditions (can recursively nest groups)
37+
38+
## Examples
39+
40+
### 1) Reject endCall unless the user says goodbye
41+
42+
```json
43+
{
44+
"conditions": [
45+
{
46+
"type": "regex",
47+
"regex": "(?i)\\b(bye|goodbye|farewell|see you later|take care)\\b",
48+
"target": { "position": -1, "role": "user" },
49+
"negate": true
50+
}
51+
]
52+
}
53+
```
54+
55+
### 2) Reject transfer if the user is actually asking a question
56+
57+
```json
58+
{
59+
"conditions": [
60+
{
61+
"type": "regex",
62+
"regex": "\\?",
63+
"target": { "position": -1, "role": "user" }
64+
}
65+
]
66+
}
67+
```
68+
69+
### 3) Reject transfer if the user hasn't mentioned transfer recently (Liquid)
70+
71+
Liquid template for readability:
72+
73+
```liquid
74+
{% assign recentMessages = messages | last: 5 %}
75+
{% assign userMessages = recentMessages | where: 'role', 'user' %}
76+
{% assign mentioned = false %}
77+
{% for msg in userMessages %}
78+
{% if msg.content contains 'transfer' or msg.content contains 'connect' or msg.content contains 'representative' %}
79+
{% assign mentioned = true %}
80+
{% endif %}
81+
{% endfor %}
82+
{% if mentioned %}false{% else %}true{% endif %}
83+
```
84+
85+
Wired into a rejection plan:
86+
87+
```json
88+
{
89+
"conditions": [
90+
{
91+
"type": "liquid",
92+
"liquid": "{% assign recentMessages = messages | last: 5 %}{% assign userMessages = recentMessages | where: 'role', 'user' %}{% assign mentioned = false %}{% for msg in userMessages %}{% if msg.content contains 'transfer' or msg.content contains 'connect' or msg.content contains 'representative' %}{% assign mentioned = true %}{% endif %}{% endfor %}{% if mentioned %}false{% else %}true{% endif %}"
93+
}
94+
]
95+
}
96+
```
97+
98+
### 4) Top-level OR using a group
99+
100+
```json
101+
{
102+
"conditions": [
103+
{
104+
"type": "group",
105+
"operator": "OR",
106+
"conditions": [
107+
{ "type": "regex", "regex": "(?i)\\bcancel\\b", "target": { "role": "user" } },
108+
{ "type": "regex", "regex": "(?i)\\bstop\\b", "target": { "role": "user" } }
109+
]
110+
}
111+
]
112+
}
113+
```
114+
115+
## Normal tool call example
116+
117+
Attach `rejectionPlan` directly on a tool in your assistant configuration (`model.tools`):
118+
119+
```json
120+
{
121+
"model": {
122+
"provider": "openai",
123+
"model": "gpt-4o",
124+
"messages": [
125+
{ "role": "system", "content": "Only end the call after the user says goodbye." }
126+
],
127+
"tools": [
128+
{
129+
"type": "endCall",
130+
"rejectionPlan": {
131+
"conditions": [
132+
{
133+
"type": "regex",
134+
"regex": "(?i)\\b(bye|goodbye|farewell|see you later|take care)\\b",
135+
"target": { "position": -1, "role": "user" },
136+
"negate": true
137+
}
138+
]
139+
}
140+
}
141+
]
142+
}
143+
}
144+
```
145+
146+
### Another example: transferCall with rejection
147+
148+
```json
149+
{
150+
"model": {
151+
"provider": "openai",
152+
"model": "gpt-4o",
153+
"messages": [
154+
{ "role": "system", "content": "Transfer only if the user clearly asks to be connected." }
155+
],
156+
"tools": [
157+
{
158+
"type": "transferCall",
159+
"destinations": [
160+
{ "type": "number", "number": "+1234567890" }
161+
],
162+
"rejectionPlan": {
163+
"conditions": [
164+
{
165+
"type": "group",
166+
"operator": "OR",
167+
"conditions": [
168+
{ "type": "regex", "regex": "(?i)\\bconnect\\b", "target": { "role": "user" } },
169+
{ "type": "regex", "regex": "(?i)\\btransfer\\b", "target": { "role": "user" } }
170+
]
171+
},
172+
{
173+
"type": "regex",
174+
"regex": "\\?",
175+
"target": { "position": -1, "role": "user" },
176+
"negate": true
177+
}
178+
]
179+
}
180+
}
181+
]
182+
}
183+
}
184+
```
185+
186+
## Tips
187+
188+
- Escape backslashes in regex patterns: write `\\b` in JSON to mean `\b` in the regex engine.
189+
- `position: -1` targets the most recent message. Omit `role` to target regardless of role.
190+
- Prefer a `group` with `operator: "OR"` for disjunctive logic at the top level.

0 commit comments

Comments
 (0)