Skip to content

Commit dcdcd76

Browse files
committed
add docs for agents in plane
1 parent 36d8eb7 commit dcdcd76

File tree

5 files changed

+1794
-1
lines changed

5 files changed

+1794
-1
lines changed
Lines changed: 368 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,368 @@
1+
---
2+
title: Best Practices
3+
sidebarTitle: Best Practices
4+
description: Guidelines for building responsive, user-friendly Plane agents that provide a seamless experience.
5+
---
6+
7+
<Note>Plane Agents are currently in **Beta**. Please send any feedback to [email protected].</Note>
8+
9+
## Overview
10+
11+
Building a great agent experience requires thoughtful design around responsiveness, error handling, and user communication. This guide covers best practices to ensure your agent feels native to Plane and provides a seamless experience for users.
12+
13+
## Sending Immediate Thought Activity
14+
15+
When your agent receives a webhook, users are waiting for a response. The most important best practice is to **acknowledge the request immediately**.
16+
17+
### Why Immediate Acknowledgment Matters
18+
19+
- Users see that your agent is active and processing their request
20+
- Prevents the Agent Run from being marked as `stale` (5-minute timeout)
21+
- Builds trust that the agent received and understood the request
22+
- Provides visual feedback during potentially long processing times
23+
24+
### Implementation
25+
26+
Send a `thought` activity within the first few seconds of receiving a webhook:
27+
28+
<Tabs>
29+
<Tab title="TypeScript">
30+
31+
```typescript
32+
async function handleWebhook(webhook: AgentRunActivityWebhook) {
33+
const agentRunId = webhook.agent_run.id;
34+
35+
// IMMEDIATELY acknowledge receipt
36+
await planeClient.agentRuns.activities.create(workspaceSlug, agentRunId, {
37+
type: "thought",
38+
content: {
39+
type: "thought",
40+
body: "Received your request. Analyzing...",
41+
},
42+
});
43+
44+
// Now proceed with actual processing
45+
// This can take longer since user knows agent is working
46+
const result = await processRequest(webhook);
47+
48+
// ... rest of the logic
49+
}
50+
```
51+
52+
</Tab>
53+
<Tab title="Python">
54+
55+
```python
56+
async def handle_webhook(webhook: dict):
57+
agent_run_id = webhook["agent_run"]["id"]
58+
59+
# IMMEDIATELY acknowledge receipt
60+
plane_client.agent_runs.activities.create(
61+
workspace_slug=workspace_slug,
62+
agent_run_id=agent_run_id,
63+
type="thought",
64+
content={
65+
"type": "thought",
66+
"body": "Received your request. Analyzing...",
67+
},
68+
)
69+
70+
# Now proceed with actual processing
71+
result = await process_request(webhook)
72+
73+
# ... rest of the logic
74+
```
75+
76+
</Tab>
77+
</Tabs>
78+
79+
### Thought Activity Best Practices
80+
81+
- Keep thoughts concise but informative
82+
- Update thoughts as you progress through different stages
83+
- Use thoughts to explain what the agent is doing, not technical details
84+
85+
**Good examples:**
86+
- "Analyzing your question about project timelines..."
87+
- "Searching for relevant work items..."
88+
- "Preparing response with the requested data..."
89+
90+
**Avoid:**
91+
- "Initializing LLM context with temperature 0.7..."
92+
- "Executing database query SELECT * FROM..."
93+
- Generic messages like "Working..." repeated multiple times
94+
95+
## Acknowledging Important Signals
96+
97+
Signals communicate user intent beyond the message content. Your agent **must** handle the `stop` signal appropriately.
98+
99+
### The Stop Signal
100+
101+
When a user wants to stop an agent run, Plane sends a `stop` signal with the activity. Your agent should:
102+
103+
1. **Recognize the signal immediately**
104+
2. **Stop any ongoing processing**
105+
3. **Send a confirmation response**
106+
107+
<Tabs>
108+
<Tab title="TypeScript">
109+
110+
```typescript
111+
async function handleWebhook(webhook: AgentRunActivityWebhook) {
112+
const signal = webhook.agent_run_activity.signal;
113+
const agentRunId = webhook.agent_run.id;
114+
115+
// ALWAYS check for stop signal first
116+
if (signal === "stop") {
117+
// Cancel any ongoing work
118+
cancelOngoingTasks(agentRunId);
119+
120+
// Acknowledge the stop
121+
await planeClient.agentRuns.activities.create(workspaceSlug, agentRunId, {
122+
type: "response",
123+
content: {
124+
type: "response",
125+
body: "Understood. I've stopped processing your previous request.",
126+
},
127+
});
128+
129+
return; // Exit early
130+
}
131+
132+
// Continue with normal processing...
133+
}
134+
```
135+
136+
</Tab>
137+
<Tab title="Python">
138+
139+
```python
140+
async def handle_webhook(webhook: dict):
141+
signal = webhook["agent_run_activity"]["signal"]
142+
agent_run_id = webhook["agent_run"]["id"]
143+
144+
# ALWAYS check for stop signal first
145+
if signal == "stop":
146+
# Cancel any ongoing work
147+
cancel_ongoing_tasks(agent_run_id)
148+
149+
# Acknowledge the stop
150+
plane_client.agent_runs.activities.create(
151+
workspace_slug=workspace_slug,
152+
agent_run_id=agent_run_id,
153+
type="response",
154+
content={
155+
"type": "response",
156+
"body": "Understood. I've stopped processing your previous request.",
157+
},
158+
)
159+
160+
return # Exit early
161+
162+
# Continue with normal processing...
163+
```
164+
165+
</Tab>
166+
</Tabs>
167+
168+
### Signal Considerations
169+
170+
| Signal | How to Handle |
171+
|--------|---------------|
172+
| `continue` | Default behavior, proceed with processing |
173+
| `stop` | Immediately halt and confirm |
174+
175+
## Progress Communication
176+
177+
For long-running tasks, keep users informed with progress updates.
178+
179+
### Multi-Step Operations
180+
181+
When your agent performs multiple steps, send thought activities for each:
182+
183+
```typescript
184+
// Step 1: Acknowledge
185+
await createThought("Understanding your request...");
186+
187+
// Step 2: First action
188+
await createAction("searchDocuments", { query: userQuery });
189+
const searchResults = await searchDocuments(userQuery);
190+
191+
// Step 3: Processing
192+
await createThought("Found relevant information. Analyzing...");
193+
194+
// Step 4: Additional work
195+
await createAction("generateSummary", { data: searchResults });
196+
const summary = await generateSummary(searchResults);
197+
198+
// Step 5: Final response
199+
await createResponse(`Here's what I found: ${summary}`);
200+
```
201+
202+
### Avoiding Information Overload
203+
204+
While progress updates are important, too many can be overwhelming:
205+
206+
- **Don't** send a thought for every internal function call
207+
- **Do** send thoughts for user-meaningful milestones
208+
- **Don't** expose technical implementation details
209+
- **Do** explain what value is being created for the user
210+
211+
## Error Handling
212+
213+
Graceful error handling is crucial for a good user experience.
214+
215+
### Always Catch and Report Errors
216+
217+
```typescript
218+
async function handleWebhook(webhook: AgentRunActivityWebhook) {
219+
const agentRunId = webhook.agent_run.id;
220+
221+
try {
222+
await createThought("Processing your request...");
223+
224+
// Your logic here...
225+
const result = await processRequest(webhook);
226+
227+
await createResponse(result);
228+
229+
} catch (error) {
230+
// ALWAYS inform the user about errors
231+
await planeClient.agentRuns.activities.create(workspaceSlug, agentRunId, {
232+
type: "error",
233+
content: {
234+
type: "error",
235+
body: getUserFriendlyErrorMessage(error),
236+
},
237+
});
238+
}
239+
}
240+
241+
function getUserFriendlyErrorMessage(error: Error): string {
242+
// Map technical errors to user-friendly messages
243+
if (error.message.includes("rate limit")) {
244+
return "I'm receiving too many requests right now. Please try again in a few minutes.";
245+
}
246+
if (error.message.includes("timeout")) {
247+
return "The operation took too long. Please try a simpler request or try again later.";
248+
}
249+
// Generic fallback
250+
return "I encountered an unexpected error. Please try again or contact support if the issue persists.";
251+
}
252+
```
253+
254+
### Error Message Guidelines
255+
256+
**Do:**
257+
- Use clear, non-technical language
258+
- Suggest next steps when possible
259+
- Be honest about what went wrong (at a high level)
260+
261+
**Don't:**
262+
- Expose stack traces or technical details
263+
- Blame the user for errors
264+
- Leave users without any feedback
265+
266+
## Handling Conversation Context
267+
268+
For multi-turn conversations, maintain context from previous activities.
269+
270+
### Fetching Previous Activities
271+
272+
```typescript
273+
// Get all activities for context
274+
const activities = await planeClient.agentRuns.activities.list(
275+
workspaceSlug,
276+
agentRunId
277+
);
278+
279+
// Build conversation history
280+
const history = activities.results
281+
.filter(a => a.type === "prompt" || a.type === "response")
282+
.map(a => ({
283+
role: a.type === "prompt" ? "user" : "assistant",
284+
content: a.content.body,
285+
}));
286+
287+
// Use history in your LLM call or logic
288+
const response = await processWithContext(newPrompt, history);
289+
```
290+
291+
### Context Best Practices
292+
293+
- Retrieve relevant history, not every single activity
294+
- Filter to meaningful exchanges (prompts and responses)
295+
- Consider summarizing long histories to save tokens/processing
296+
- Don't assume infinite context availability
297+
298+
## Rate Limiting and Timeouts
299+
300+
Be mindful of Plane's API limits and your own processing time.
301+
302+
### Stale Run Prevention
303+
304+
Agent Runs are marked as `stale` after 5 minutes of inactivity. For long operations:
305+
306+
```typescript
307+
async function longRunningTask(agentRunId: string) {
308+
const HEARTBEAT_INTERVAL = 60000; // 1 minute
309+
310+
const heartbeat = setInterval(async () => {
311+
await createThought("Still working on your request...");
312+
}, HEARTBEAT_INTERVAL);
313+
314+
try {
315+
const result = await performLongOperation();
316+
return result;
317+
} finally {
318+
clearInterval(heartbeat);
319+
}
320+
}
321+
```
322+
323+
### Webhook Response Time
324+
325+
- Return HTTP 200 from your webhook handler quickly (within seconds)
326+
- Process the actual agent logic asynchronously
327+
- Don't block the webhook response waiting for LLM calls
328+
329+
```typescript
330+
// Good: Respond immediately, process async
331+
app.post("/webhook", async (req, res) => {
332+
res.status(200).json({ received: true });
333+
334+
// Process in background
335+
processWebhookAsync(req.body).catch(console.error);
336+
});
337+
```
338+
339+
## Summary Checklist
340+
341+
<CardGroup cols={2}>
342+
<Card title="Responsiveness" icon="bolt">
343+
- Send thought within seconds of webhook
344+
- Return webhook response quickly
345+
- Send heartbeats for long operations
346+
</Card>
347+
<Card title="Signal Handling" icon="signal">
348+
- Always check for `stop` signal first
349+
- Handle all signal types appropriately
350+
- Confirm when stopping
351+
</Card>
352+
<Card title="Error Handling" icon="triangle-exclamation">
353+
- Wrap processing in try/catch
354+
- Always send error activity on failure
355+
- Use friendly error messages
356+
</Card>
357+
<Card title="User Experience" icon="user">
358+
- Progress updates for long tasks
359+
- Clear, non-technical communication
360+
- Maintain conversation context
361+
</Card>
362+
</CardGroup>
363+
364+
## Next Steps
365+
366+
- Learn about [Signals & Content Payload](/dev-tools/agents/signals-content-payload) for advanced activity handling
367+
- Review the [Building an Agent](/dev-tools/agents/building-an-agent) guide for implementation details
368+

0 commit comments

Comments
 (0)