diff --git a/dev-tools/agents/best-practices.mdx b/dev-tools/agents/best-practices.mdx new file mode 100644 index 0000000..577399b --- /dev/null +++ b/dev-tools/agents/best-practices.mdx @@ -0,0 +1,368 @@ +--- +title: Best Practices +sidebarTitle: Best Practices +description: Guidelines for building responsive, user-friendly Plane agents that provide a seamless experience. +--- + +Plane Agents are currently in **Beta**. Please send any feedback to support@plane.so. + +## Overview + +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. + +## Sending Immediate Thought Activity + +When your agent receives a webhook, users are waiting for a response. The most important best practice is to **acknowledge the request immediately**. + +### Why Immediate Acknowledgment Matters + +- Users see that your agent is active and processing their request +- Prevents the Agent Run from being marked as `stale` (5-minute timeout) +- Builds trust that the agent received and understood the request +- Provides visual feedback during potentially long processing times + +### Implementation + +Send a `thought` activity within the first few seconds of receiving a webhook: + + + + +```typescript +async function handleWebhook(webhook: AgentRunActivityWebhook) { + const agentRunId = webhook.agent_run.id; + + // IMMEDIATELY acknowledge receipt + await planeClient.agentRuns.activities.create(workspaceSlug, agentRunId, { + type: "thought", + content: { + type: "thought", + body: "Received your request. Analyzing...", + }, + }); + + // Now proceed with actual processing + // This can take longer since user knows agent is working + const result = await processRequest(webhook); + + // ... rest of the logic +} +``` + + + + +```python +async def handle_webhook(webhook: dict): + agent_run_id = webhook["agent_run"]["id"] + + # IMMEDIATELY acknowledge receipt + plane_client.agent_runs.activities.create( + workspace_slug=workspace_slug, + agent_run_id=agent_run_id, + type="thought", + content={ + "type": "thought", + "body": "Received your request. Analyzing...", + }, + ) + + # Now proceed with actual processing + result = await process_request(webhook) + + # ... rest of the logic +``` + + + + +### Thought Activity Best Practices + +- Keep thoughts concise but informative +- Update thoughts as you progress through different stages +- Use thoughts to explain what the agent is doing, not technical details + +**Good examples:** +- "Analyzing your question about project timelines..." +- "Searching for relevant work items..." +- "Preparing response with the requested data..." + +**Avoid:** +- "Initializing LLM context with temperature 0.7..." +- "Executing database query SELECT * FROM..." +- Generic messages like "Working..." repeated multiple times + +## Acknowledging Important Signals + +Signals communicate user intent beyond the message content. Your agent **must** handle the `stop` signal appropriately. + +### The Stop Signal + +When a user wants to stop an agent run, Plane sends a `stop` signal with the activity. Your agent should: + +1. **Recognize the signal immediately** +2. **Stop any ongoing processing** +3. **Send a confirmation response** + + + + +```typescript +async function handleWebhook(webhook: AgentRunActivityWebhook) { + const signal = webhook.agent_run_activity.signal; + const agentRunId = webhook.agent_run.id; + + // ALWAYS check for stop signal first + if (signal === "stop") { + // Cancel any ongoing work + cancelOngoingTasks(agentRunId); + + // Acknowledge the stop + await planeClient.agentRuns.activities.create(workspaceSlug, agentRunId, { + type: "response", + content: { + type: "response", + body: "Understood. I've stopped processing your previous request.", + }, + }); + + return; // Exit early + } + + // Continue with normal processing... +} +``` + + + + +```python +async def handle_webhook(webhook: dict): + signal = webhook["agent_run_activity"]["signal"] + agent_run_id = webhook["agent_run"]["id"] + + # ALWAYS check for stop signal first + if signal == "stop": + # Cancel any ongoing work + cancel_ongoing_tasks(agent_run_id) + + # Acknowledge the stop + plane_client.agent_runs.activities.create( + workspace_slug=workspace_slug, + agent_run_id=agent_run_id, + type="response", + content={ + "type": "response", + "body": "Understood. I've stopped processing your previous request.", + }, + ) + + return # Exit early + + # Continue with normal processing... +``` + + + + +### Signal Considerations + +| Signal | How to Handle | +|--------|---------------| +| `continue` | Default behavior, proceed with processing | +| `stop` | Immediately halt and confirm | + +## Progress Communication + +For long-running tasks, keep users informed with progress updates. + +### Multi-Step Operations + +When your agent performs multiple steps, send thought activities for each: + +```typescript +// Step 1: Acknowledge +await createThought("Understanding your request..."); + +// Step 2: First action +await createAction("searchDocuments", { query: userQuery }); +const searchResults = await searchDocuments(userQuery); + +// Step 3: Processing +await createThought("Found relevant information. Analyzing..."); + +// Step 4: Additional work +await createAction("generateSummary", { data: searchResults }); +const summary = await generateSummary(searchResults); + +// Step 5: Final response +await createResponse(`Here's what I found: ${summary}`); +``` + +### Avoiding Information Overload + +While progress updates are important, too many can be overwhelming: + +- **Don't** send a thought for every internal function call +- **Do** send thoughts for user-meaningful milestones +- **Don't** expose technical implementation details +- **Do** explain what value is being created for the user + +## Error Handling + +Graceful error handling is crucial for a good user experience. + +### Always Catch and Report Errors + +```typescript +async function handleWebhook(webhook: AgentRunActivityWebhook) { + const agentRunId = webhook.agent_run.id; + + try { + await createThought("Processing your request..."); + + // Your logic here... + const result = await processRequest(webhook); + + await createResponse(result); + + } catch (error) { + // ALWAYS inform the user about errors + await planeClient.agentRuns.activities.create(workspaceSlug, agentRunId, { + type: "error", + content: { + type: "error", + body: getUserFriendlyErrorMessage(error), + }, + }); + } +} + +function getUserFriendlyErrorMessage(error: Error): string { + // Map technical errors to user-friendly messages + if (error.message.includes("rate limit")) { + return "I'm receiving too many requests right now. Please try again in a few minutes."; + } + if (error.message.includes("timeout")) { + return "The operation took too long. Please try a simpler request or try again later."; + } + // Generic fallback + return "I encountered an unexpected error. Please try again or contact support if the issue persists."; +} +``` + +### Error Message Guidelines + +**Do:** +- Use clear, non-technical language +- Suggest next steps when possible +- Be honest about what went wrong (at a high level) + +**Don't:** +- Expose stack traces or technical details +- Blame the user for errors +- Leave users without any feedback + +## Handling Conversation Context + +For multi-turn conversations, maintain context from previous activities. + +### Fetching Previous Activities + +```typescript +// Get all activities for context +const activities = await planeClient.agentRuns.activities.list( + workspaceSlug, + agentRunId +); + +// Build conversation history +const history = activities.results + .filter(a => a.type === "prompt" || a.type === "response") + .map(a => ({ + role: a.type === "prompt" ? "user" : "assistant", + content: a.content.body, + })); + +// Use history in your LLM call or logic +const response = await processWithContext(newPrompt, history); +``` + +### Context Best Practices + +- Retrieve relevant history, not every single activity +- Filter to meaningful exchanges (prompts and responses) +- Consider summarizing long histories to save tokens/processing +- Don't assume infinite context availability + +## Rate Limiting and Timeouts + +Be mindful of Plane's API limits and your own processing time. + +### Stale Run Prevention + +Agent Runs are marked as `stale` after 5 minutes of inactivity. For long operations: + +```typescript +async function longRunningTask(agentRunId: string) { + const HEARTBEAT_INTERVAL = 60000; // 1 minute + + const heartbeat = setInterval(async () => { + await createThought("Still working on your request..."); + }, HEARTBEAT_INTERVAL); + + try { + const result = await performLongOperation(); + return result; + } finally { + clearInterval(heartbeat); + } +} +``` + +### Webhook Response Time + +- Return HTTP 200 from your webhook handler quickly (within seconds) +- Process the actual agent logic asynchronously +- Don't block the webhook response waiting for LLM calls + +```typescript +// Good: Respond immediately, process async +app.post("/webhook", async (req, res) => { + res.status(200).json({ received: true }); + + // Process in background + processWebhookAsync(req.body).catch(console.error); +}); +``` + +## Summary Checklist + + + + - Send thought within seconds of webhook + - Return webhook response quickly + - Send heartbeats for long operations + + + - Always check for `stop` signal first + - Handle all signal types appropriately + - Confirm when stopping + + + - Wrap processing in try/catch + - Always send error activity on failure + - Use friendly error messages + + + - Progress updates for long tasks + - Clear, non-technical communication + - Maintain conversation context + + + +## Next Steps + +- Learn about [Signals & Content Payload](/dev-tools/agents/signals-content-payload) for advanced activity handling +- Review the [Building an Agent](/dev-tools/agents/building-an-agent) guide for implementation details + diff --git a/dev-tools/agents/building-an-agent.mdx b/dev-tools/agents/building-an-agent.mdx new file mode 100644 index 0000000..6680ae9 --- /dev/null +++ b/dev-tools/agents/building-an-agent.mdx @@ -0,0 +1,582 @@ +--- +title: Building an Agent +sidebarTitle: Building an Agent +description: Step-by-step guide to creating a Plane agent, including OAuth setup, webhook handling, and activity creation using the Node.js and Python SDKs. +--- + +Plane Agents are currently in **Beta**. Please send any feedback to support@plane.so. + +## Creating an Agent + +Building a Plane agent involves three main steps: + +1. Create an OAuth application with agent capabilities enabled +2. Implement the OAuth flow to install your agent in workspaces +3. Handle webhooks and create activities to respond to users + +### OAuth App Creation + +To create an agent, you first need to [register an OAuth application](/dev-tools/build-plane-app#registering-your-app) with the **Enable App Mentions** checkbox enabled. + +1. Navigate to `https://app.plane.so//settings/integrations/` +2. Click on **Build your own** button +3. Fill out the required details: + - **Setup URL**: The URL users are redirected to when installing your app + - **Redirect URIs**: Where Plane sends the authorization code after consent + - **Webhook URL Endpoint**: Your service's webhook endpoint for receiving events +4. **Enable the "Enable App Mentions" checkbox** — This is required for agents +5. Save and securely store your **Client ID** and **Client Secret** + + +The "Enable App Mentions" checkbox is what transforms a regular OAuth app into an agent that can be @mentioned in work items. + + +### Setting Is Mentionable + +When you enable app mentions during OAuth app creation, your application becomes mentionable in work item comments. This means: + +- Users will see your agent in the mention picker when typing `@` +- Your agent can be assigned or delegated work items +- Webhooks will be triggered when users interact with your agent + +After installation, your agent appears alongside workspace members in the mention autocomplete. + +## Agent Interaction + +Once your agent is installed via the [OAuth consent flow](/dev-tools/build-plane-app#implement-oauth-flow) and users start mentioning it, you need to handle the interactions through Agent Runs and Activities. + +### AgentRun + +An **AgentRun** tracks a complete interaction session between a user and your agent. + +#### Key Fields + +| Field | Type | Description | +|-------|------|-------------| +| `id` | UUID | Unique identifier for the agent run | +| `agent_user` | UUID | The bot user ID representing your agent | +| `issue` | UUID | The work item where the interaction started | +| `project` | UUID | The project containing the work item | +| `workspace` | UUID | The workspace where the agent is installed | +| `comment` | UUID | The comment thread for this run | +| `source_comment` | UUID | The original comment that triggered the run | +| `creator` | UUID | The user who initiated the run | +| `status` | String | Current status (`created`, `in_progress`, `awaiting`, `completed`, `stopping`, `stopped`, `failed`, `stale`) | +| `started_at` | DateTime | When the run started | +| `ended_at` | DateTime | When the run ended (if applicable) | +| `stopped_at` | DateTime | When a stop was requested | +| `stopped_by` | UUID | User who requested the stop | +| `external_link` | URL | Optional link to external dashboard/logs | +| `error_metadata` | JSON | Error details if the run failed | +| `type` | String | Type of run (currently `comment_thread`) | + +### AgentRunActivity + +An **AgentRunActivity** represents a single message or action within an Agent Run. + +#### Key Fields + +| Field | Type | Description | +|-------|------|-------------| +| `id` | UUID | Unique identifier for the activity | +| `agent_run` | UUID | The parent Agent Run | +| `type` | String | Activity type (`prompt`, `thought`, `action`, `response`, `elicitation`, `error`) | +| `content` | JSON | The activity content (structure varies by type) | +| `content_metadata` | JSON | Additional metadata about the content | +| `ephemeral` | Boolean | If true, the activity is temporary and won't create a comment | +| `signal` | String | Signal for how to handle the activity (`continue`, `stop`, `auth_request`, `select`) | +| `signal_metadata` | JSON | Additional signal data | +| `actor` | UUID | The user or bot that created the activity | +| `comment` | UUID | Associated comment (for non-ephemeral activities) | + +### Creating Activities + +Your agent communicates back to users by creating activities. Here's how to create activities using the official SDKs: + +#### Using the Node.js SDK + + + + +```bash +npm install @makeplane/plane-node-sdk +``` + + + + +```typescript +import { PlaneClient } from "@makeplane/plane-node-sdk"; + +// Initialize the client with your bot token +const planeClient = new PlaneClient({ + baseUrl: "https://api.plane.so", + accessToken: botToken, +}); + +// Create a thought activity (ephemeral) +await planeClient.agentRuns.activities.create(workspaceSlug, agentRunId, { + type: "thought", + content: { + type: "thought", + body: "Analyzing the user's request about weather data...", + }, +}); + +// Create an action activity (ephemeral) +await planeClient.agentRuns.activities.create(workspaceSlug, agentRunId, { + type: "action", + content: { + type: "action", + action: "getWeather", + parameters: { location: "San Francisco" }, + }, +}); + +// Create a response activity (creates a comment) +await planeClient.agentRuns.activities.create(workspaceSlug, agentRunId, { + type: "response", + content: { + type: "response", + body: "The weather in San Francisco is currently 68°F with partly cloudy skies.", + }, + signal: "continue", +}); + +// Create an elicitation activity (asks user for input) +await planeClient.agentRuns.activities.create(workspaceSlug, agentRunId, { + type: "elicitation", + content: { + type: "elicitation", + body: "Which city would you like me to check the weather for?", + }, +}); + +// Create an error activity +await planeClient.agentRuns.activities.create(workspaceSlug, agentRunId, { + type: "error", + content: { + type: "error", + body: "Unable to fetch weather data. Please try again later.", + }, +}); +``` + + + + +#### Using the Python SDK + + + + +```bash +pip install plane-sdk +``` + + + + +```python +from plane import PlaneClient + +# Initialize the client with your bot token +plane_client = PlaneClient( + base_url="https://api.plane.so", + access_token=bot_token, +) + +# Create a thought activity (ephemeral) +plane_client.agent_runs.activities.create( + workspace_slug=workspace_slug, + agent_run_id=agent_run_id, + type="thought", + content={ + "type": "thought", + "body": "Analyzing the user's request about weather data...", + }, +) + +# Create an action activity (ephemeral) +plane_client.agent_runs.activities.create( + workspace_slug=workspace_slug, + agent_run_id=agent_run_id, + type="action", + content={ + "type": "action", + "action": "getWeather", + "parameters": {"location": "San Francisco"}, + }, +) + +# Create a response activity (creates a comment) +plane_client.agent_runs.activities.create( + workspace_slug=workspace_slug, + agent_run_id=agent_run_id, + type="response", + content={ + "type": "response", + "body": "The weather in San Francisco is currently 68°F with partly cloudy skies.", + }, + signal="continue", +) + +# Create an elicitation activity (asks user for input) +plane_client.agent_runs.activities.create( + workspace_slug=workspace_slug, + agent_run_id=agent_run_id, + type="elicitation", + content={ + "type": "elicitation", + "body": "Which city would you like me to check the weather for?", + }, +) + +# Create an error activity +plane_client.agent_runs.activities.create( + workspace_slug=workspace_slug, + agent_run_id=agent_run_id, + type="error", + content={ + "type": "error", + "body": "Unable to fetch weather data. Please try again later.", + }, +) +``` + + + + +### Content Payload Types + +The `content` field structure varies based on the activity type: + +#### Thought + +Internal reasoning from the agent. Automatically marked as ephemeral. + +```json +{ + "type": "thought", + "body": "The user is asking about weather data for their location." +} +``` + +#### Action + +A tool invocation. Automatically marked as ephemeral. You can include results after execution. + +```json +{ + "type": "action", + "action": "searchDatabase", + "parameters": { + "query": "weather API", + "limit": "10" + } +} +``` + +With result: + +```json +{ + "type": "action", + "action": "searchDatabase", + "parameters": { + "query": "weather API", + "result": "Found 3 matching records" + } +} +``` + +#### Response + +A final response to the user. Creates a comment reply. + +```json +{ + "type": "response", + "body": "Here's the weather forecast for San Francisco..." +} +``` + +#### Elicitation + +A question requesting user input. Creates a comment and sets run to `awaiting`. + +```json +{ + "type": "elicitation", + "body": "Could you please specify which date range you're interested in?" +} +``` + +#### Error + +An error message. Creates a comment and sets run to `failed`. + +```json +{ + "type": "error", + "body": "I encountered an error while processing your request." +} +``` + +### Signals + +Signals provide additional context about how an activity should be interpreted: + +| Signal | Description | +|--------|-------------| +| `continue` | Default signal, indicates the conversation can continue | +| `stop` | User requested to stop the agent run | +| `auth_request` | Agent needs user to authenticate with an external service | +| `select` | Agent is presenting options for user to select from | + +See [Signals & Content Payload](/dev-tools/agents/signals-content-payload) for detailed information. + +### Ephemeral Activities + +Activities with `ephemeral: true` are temporary and don't create comments. They're useful for showing agent progress without cluttering the conversation. + +The following activity types are automatically marked as ephemeral: +- `thought` +- `action` +- `error` + +Ephemeral activities are displayed temporarily in the UI and replaced when the next activity arrives. + +## AgentRun Webhooks + +Your agent receives webhooks when users interact with it. There are two main webhook events: + +### AgentRun Create Webhook + +Triggered when a new Agent Run is created (user first mentions your agent). + +**Event:** `agent_run_create` + +**Payload:** + +```json +{ + "action": "created", + "agent_run": { + "id": "uuid", + "agent_user": "uuid", + "issue": "uuid", + "project": "uuid", + "workspace": "uuid", + "status": "created", + "type": "comment_thread", + "started_at": "2025-01-15T10:30:00Z" + }, + "agent_user_id": "uuid", + "app_client_id": "your-client-id", + "issue_id": "uuid", + "project_id": "uuid", + "workspace_id": "uuid", + "comment_id": "uuid", + "type": "agent_run" +} +``` + +### AgentRun Activity Webhook + +Triggered when a user sends a prompt to your agent (initial mention or follow-up). + +**Event:** `agent_run_user_prompt` + +**Payload:** + +```json +{ + "action": "prompted", + "agent_run_activity": { + "id": "uuid", + "agent_run": "uuid", + "type": "prompt", + "content": { + "type": "prompt", + "body": "What's the weather like in San Francisco?" + }, + "ephemeral": false, + "signal": "continue", + "actor": "uuid", + "workspace": "uuid" + }, + "agent_run": { + "id": "uuid", + "agent_user": "uuid", + "issue": "uuid", + "project": "uuid", + "workspace": "uuid", + "status": "in_progress" + }, + "agent_user_id": "uuid", + "app_client_id": "your-client-id", + "comment_id": "uuid", + "issue_id": "uuid", + "project_id": "uuid", + "workspace_id": "uuid", + "type": "agent_run_activity" +} +``` + +### Handling Webhooks + +Here's a complete example of handling agent webhooks: + + + + +```typescript +import { PlaneClient } from "@makeplane/plane-node-sdk"; + +interface AgentRunActivityWebhook { + action: string; + agent_run_activity: { + id: string; + content: { type: string; body?: string }; + signal: string; + }; + agent_run: { + id: string; + status: string; + }; + workspace_id: string; + project_id: string; + type: string; +} + +async function handleWebhook( + webhook: AgentRunActivityWebhook, + credentials: { access_token: string; workspace_slug: string } +) { + // Only handle agent_run_activity webhooks + if (webhook.type !== "agent_run_activity") { + return; + } + + const planeClient = new PlaneClient({ + baseUrl: "https://api.plane.so", + accessToken: credentials.access_token, + }); + + const agentRunId = webhook.agent_run.id; + const userPrompt = webhook.agent_run_activity.content.body || ""; + const signal = webhook.agent_run_activity.signal; + + // Check for stop signal + if (signal === "stop") { + await planeClient.agentRuns.activities.create( + credentials.workspace_slug, + agentRunId, + { + type: "response", + content: { + type: "response", + body: "Stopping as requested.", + }, + } + ); + return; + } + + // Send initial thought + await planeClient.agentRuns.activities.create( + credentials.workspace_slug, + agentRunId, + { + type: "thought", + content: { + type: "thought", + body: "Processing your request...", + }, + } + ); + + // Process the request and send response + const response = await processUserRequest(userPrompt); + + await planeClient.agentRuns.activities.create( + credentials.workspace_slug, + agentRunId, + { + type: "response", + content: { + type: "response", + body: response, + }, + } + ); +} +``` + + + + +```python +from plane import PlaneClient + +def handle_webhook(webhook: dict, credentials: dict): + # Only handle agent_run_activity webhooks + if webhook.get("type") != "agent_run_activity": + return + + plane_client = PlaneClient( + base_url="https://api.plane.so", + access_token=credentials["access_token"], + ) + + agent_run_id = webhook["agent_run"]["id"] + user_prompt = webhook["agent_run_activity"]["content"].get("body", "") + signal = webhook["agent_run_activity"]["signal"] + + # Check for stop signal + if signal == "stop": + plane_client.agent_runs.activities.create( + workspace_slug=credentials["workspace_slug"], + agent_run_id=agent_run_id, + type="response", + content={ + "type": "response", + "body": "Stopping as requested.", + }, + ) + return + + # Send initial thought + plane_client.agent_runs.activities.create( + workspace_slug=credentials["workspace_slug"], + agent_run_id=agent_run_id, + type="thought", + content={ + "type": "thought", + "body": "Processing your request...", + }, + ) + + # Process the request and send response + response = process_user_request(user_prompt) + + plane_client.agent_runs.activities.create( + workspace_slug=credentials["workspace_slug"], + agent_run_id=agent_run_id, + type="response", + content={ + "type": "response", + "body": response, + }, + ) +``` + + + + +## Next Steps + +- Learn about [Best Practices](/dev-tools/agents/best-practices) for building responsive agents +- Explore [Signals & Content Payload](/dev-tools/agents/signals-content-payload) for advanced activity handling + diff --git a/dev-tools/agents/overview.mdx b/dev-tools/agents/overview.mdx new file mode 100644 index 0000000..ab96a4f --- /dev/null +++ b/dev-tools/agents/overview.mdx @@ -0,0 +1,138 @@ +--- +title: Agents Overview +sidebarTitle: Overview +description: Learn how to build AI agents that integrate with Plane workspaces, enabling automated task handling, intelligent responses, and seamless collaboration. +--- + +Plane Agents are currently in **Beta**. Please send any feedback to support@plane.so. + +## What are Agents in Plane? + +Agents in Plane are AI-powered applications that can interact with your workspace similar to how human users do. They can be @mentioned in work item comments, receive prompts from users, and respond with intelligent actions. Agents enable automation and AI assistance directly within your project management workflow. + +Key capabilities of Plane agents: + +- **Mentionable** — Users can @mention agents in work item comments to trigger interactions +- **Contextual awareness** — Agents receive full context about the work item, project, and conversation +- **Activity tracking** — All agent interactions are tracked through the Agent Run system +- **Real-time responses** — Agents can send thoughts, actions, and responses back to users + +## Agent Installation + +Agents are installed as OAuth applications in your Plane workspace. When you create an OAuth app with the **Enable App Mentions** option enabled, it becomes an agent that users can mention and interact with. + +### Installation Flow + +1. A workspace admin installs your agent via the OAuth consent flow +2. Plane creates a **bot user** for your agent in that workspace +3. The bot user ID is returned in the app installation details +4. Users can now @mention your agent in work item comments + +When installed, agents appear in the mention picker alongside regular workspace members. Users can tag them in comments just like any team member. + + +Agents installed in your workspace do not count as billable users. + + +## Agent Run Lifecycle + +The Agent Run system tracks the complete lifecycle of an agent interaction, from when a user mentions the agent to when the agent completes its task. + +### What is an Agent Run? + +An **Agent Run** represents a single interaction session between a user and an agent. When a user @mentions an agent in a comment, Plane automatically creates an Agent Run to track: + +- The triggering comment and work item +- The conversation thread between user and agent +- All activities (thoughts, actions, responses) from the agent +- The current status of the interaction + +### What is an Agent Run Activity? + +An **Agent Run Activity** is a single unit of communication within an Agent Run. Activities can be: + +- **Prompt** — A message from a user to the agent +- **Thought** — Internal reasoning from the agent (ephemeral) +- **Action** — A tool invocation by the agent (ephemeral) +- **Response** — A final response from the agent (creates a comment) +- **Elicitation** — A question from the agent requesting user input +- **Error** — An error message from the agent + +### How Agent Run Works + +The Agent Run flow consists of three main phases: + +```mermaid +sequenceDiagram + participant User + participant Plane + participant Agent + + User->>Plane: @mentions agent in comment + Plane->>Plane: Create AgentRun + Activity (prompt) + Plane->>Agent: Send webhook (agent_run_activity) + + loop Agent Processing + Agent->>Plane: Create Activity (thought/action) + Agent->>Agent: Process and reason + end + + Agent->>Plane: Create Activity (response) + Plane->>User: Show response as comment +``` + +#### Phase 1: Trigger + +1. User @mentions the agent in a work item comment +2. Plane detects the mention and creates a new **Agent Run** +3. An **Agent Run Activity** is created with the user's prompt +4. The Agent Run status is set to `created` + +#### Phase 2: Webhook + +1. Plane triggers a webhook to your agent's webhook URL +2. The webhook payload includes: + - The Agent Run details + - The triggering activity (user prompt) + - Work item and project context + - Workspace information + +#### Phase 3: Agent Response + +1. Your agent processes the webhook and starts working +2. The agent sends activities back to Plane via the API: + - `thought` activities to show reasoning (ephemeral, don't create comments) + - `action` activities to show tool usage (ephemeral) + - `response` or `elicitation` when complete (creates a comment) +3. Plane updates the Agent Run status based on activities +4. Non-ephemeral activities (response, elicitation) create comment replies visible to users + +### Agent Run States + +Agent Runs transition through various states based on activities: + +| Status | Description | +|--------|-------------| +| `created` | The run has been initiated but not yet started processing | +| `in_progress` | The agent is actively processing the request | +| `awaiting` | The agent is waiting for additional input from the user | +| `completed` | The agent has successfully finished processing | +| `stopping` | A stop request has been received and is being processed | +| `stopped` | The run has been successfully stopped | +| `failed` | The run encountered an error and cannot continue | +| `stale` | The run has not been updated in 5 minutes and is considered stale | + +### Continuing a Conversation + +When a user replies to an agent's response: + +1. If an active Agent Run exists for that thread, the reply is added as a new activity +2. The webhook is triggered again with the updated context +3. The agent can continue the conversation with full history + +This enables multi-turn conversations where users and agents can have back-and-forth interactions. + +## Next Steps + +Ready to build your own agent? Continue to [Building an Agent](/dev-tools/agents/building-an-agent) to learn how to create and deploy your first Plane agent. + diff --git a/dev-tools/agents/signals-content-payload.mdx b/dev-tools/agents/signals-content-payload.mdx new file mode 100644 index 0000000..582bb92 --- /dev/null +++ b/dev-tools/agents/signals-content-payload.mdx @@ -0,0 +1,696 @@ +--- +title: Signals & Content Payload +sidebarTitle: Signals & Content Payload +description: Detailed reference for activity signals and content payload structures in Plane agents. +--- + +Plane Agents are currently in **Beta**. Please send any feedback to support@plane.so. + +## Overview + +Agent activities consist of two key components: + +1. **Content** — The message or action being communicated +2. **Signal** — Metadata indicating how the activity should be interpreted + +Understanding these components is essential for building agents that communicate effectively with users. + +## Signals + +Signals are metadata that modify how an activity should be interpreted or handled. They provide additional context about the sender's intent—guiding how the activity should be processed or responded to. + +### Available Signals + +| Signal | Description | Use Case | +|--------|-------------|----------| +| `continue` | Default signal, indicates normal flow | Standard responses, ongoing conversation | +| `stop` | User requested to stop the agent | Cancellation, abort operations | +| `auth_request` | Agent needs external authentication | OAuth flows, API key collection | +| `select` | Agent presenting options for selection | Multiple choice questions | + +### Signal: `continue` + +The default signal for most activities. Indicates normal conversation flow where the agent can continue processing. + +```json +{ + "type": "response", + "content": { + "type": "response", + "body": "Here's the information you requested." + }, + "signal": "continue" +} +``` + +### Signal: `stop` + +Sent by Plane when a user requests to stop the agent. Your agent should: + +1. Immediately halt any ongoing processing +2. Clean up resources if needed +3. Send a confirmation response + +**Incoming webhook with stop signal:** + +```json +{ + "action": "prompted", + "agent_run_activity": { + "type": "prompt", + "content": { + "type": "prompt", + "body": "Stop" + }, + "signal": "stop" + } +} +``` + +**Handling the stop signal:** + +```typescript +if (webhook.agent_run_activity.signal === "stop") { + // Cancel ongoing work + await cancelAllPendingTasks(); + + // Acknowledge the stop - this transitions run to "stopped" status + await planeClient.agentRuns.activities.create(workspaceSlug, agentRunId, { + type: "response", + content: { + type: "response", + body: "I've stopped working on your request.", + }, + }); + + return; +} +``` + +### Signal: `auth_request` + +Used when your agent needs the user to authenticate with an external service. Requires a URL in the `signal_metadata`. + +**Creating an auth request:** + +```typescript +await planeClient.agentRuns.activities.create(workspaceSlug, agentRunId, { + type: "elicitation", + content: { + type: "elicitation", + body: "I need access to your GitHub account to proceed. Please authenticate using the link below.", + }, + signal: "auth_request", + signal_metadata: { + url: "https://your-agent.com/auth/github?session=abc123", + }, +}); +``` + +**Requirements:** +- The URL must start with `https://` +- The URL should be a secure endpoint on your agent's server +- After authentication, redirect the user back or notify completion + +### Signal: `select` + +Used when presenting options for the user to choose from. Useful for disambiguation or multi-choice scenarios. + +```typescript +await planeClient.agentRuns.activities.create(workspaceSlug, agentRunId, { + type: "elicitation", + content: { + type: "elicitation", + body: "Which project would you like me to search?\n\n1. Frontend App\n2. Backend API\n3. Mobile App", + }, + signal: "select", + signal_metadata: { + options: [ + { id: "frontend", label: "Frontend App" }, + { id: "backend", label: "Backend API" }, + { id: "mobile", label: "Mobile App" }, + ], + }, +}); +``` + +## Content Payload Types + +The `content` field contains the actual message or action. Its structure varies based on the activity type. + +### Type: `thought` + +Internal reasoning or progress updates from the agent. Automatically marked as ephemeral (won't create a comment). + +**Structure:** + +```typescript +interface ThoughtContent { + type: "thought"; + body: string; // The thought message +} +``` + +**Example:** + +```json +{ + "type": "thought", + "body": "The user is asking about deployment status. I'll check the CI/CD pipeline." +} +``` + +**Creating a thought activity:** + + + + +```typescript +await planeClient.agentRuns.activities.create(workspaceSlug, agentRunId, { + type: "thought", + content: { + type: "thought", + body: "Analyzing the codebase for potential issues...", + }, +}); +``` + + + + +```python +plane_client.agent_runs.activities.create( + workspace_slug=workspace_slug, + agent_run_id=agent_run_id, + type="thought", + content={ + "type": "thought", + "body": "Analyzing the codebase for potential issues...", + }, +) +``` + + + + +**Best practices for thoughts:** +- Keep them concise and user-meaningful +- Use to show progress, not internal implementation +- Update as you move through stages of processing + +### Type: `action` + +Describes a tool invocation or external action. Automatically marked as ephemeral. + +**Structure:** + +```typescript +interface ActionContent { + type: "action"; + action: string; // Name of the tool/action + parameters: { // Key-value pairs of parameters + [key: string]: string; + }; +} +``` + +**Example - Starting an action:** + +```json +{ + "type": "action", + "action": "searchDatabase", + "parameters": { + "query": "bug reports", + "status": "open" + } +} +``` + +**Example - Action with result:** + +```json +{ + "type": "action", + "action": "searchDatabase", + "parameters": { + "query": "bug reports", + "status": "open", + "result": "Found 12 matching work items" + } +} +``` + +**Creating action activities:** + + + + +```typescript +// Before executing the action +await planeClient.agentRuns.activities.create(workspaceSlug, agentRunId, { + type: "action", + content: { + type: "action", + action: "fetchWeather", + parameters: { + location: "San Francisco", + }, + }, +}); + +// Execute the actual action +const weatherData = await fetchWeather("San Francisco"); + +// After execution, report the result +await planeClient.agentRuns.activities.create(workspaceSlug, agentRunId, { + type: "action", + content: { + type: "action", + action: "fetchWeather", + parameters: { + location: "San Francisco", + result: `Temperature: ${weatherData.temp}°F, Conditions: ${weatherData.conditions}`, + }, + }, + content_metadata: { + result: weatherData, // Store full result in metadata + }, +}); +``` + + + + +```python +# Before executing the action +plane_client.agent_runs.activities.create( + workspace_slug=workspace_slug, + agent_run_id=agent_run_id, + type="action", + content={ + "type": "action", + "action": "fetchWeather", + "parameters": { + "location": "San Francisco", + }, + }, +) + +# Execute the actual action +weather_data = fetch_weather("San Francisco") + +# After execution, report the result +plane_client.agent_runs.activities.create( + workspace_slug=workspace_slug, + agent_run_id=agent_run_id, + type="action", + content={ + "type": "action", + "action": "fetchWeather", + "parameters": { + "location": "San Francisco", + "result": f"Temperature: {weather_data['temp']}°F, Conditions: {weather_data['conditions']}", + }, + }, + content_metadata={ + "result": weather_data, + }, +) +``` + + + + +**Parameter requirements:** +- All parameter keys must be strings +- All parameter values must be strings +- Use `content_metadata` to store complex/structured data + +### Type: `response` + +A final response to the user. Creates a comment reply visible to users. + +**Structure:** + +```typescript +interface ResponseContent { + type: "response"; + body: string; // The response message (supports Markdown) +} +``` + +**Example:** + +```json +{ + "type": "response", + "body": "Based on my analysis, here are the top 3 issues affecting your sprint:\n\n1. **AUTH-123**: Login timeout affecting 15% of users\n2. **API-456**: Rate limiting too aggressive\n3. **UI-789**: Dashboard loading slowly\n\nWould you like me to provide more details on any of these?" +} +``` + +**Creating a response:** + + + + +```typescript +await planeClient.agentRuns.activities.create(workspaceSlug, agentRunId, { + type: "response", + content: { + type: "response", + body: "Here's the weather in San Francisco:\n\n🌤️ **68°F** - Partly Cloudy\n\nExpect mild conditions throughout the day.", + }, + signal: "continue", +}); +``` + + + + +```python +plane_client.agent_runs.activities.create( + workspace_slug=workspace_slug, + agent_run_id=agent_run_id, + type="response", + content={ + "type": "response", + "body": "Here's the weather in San Francisco:\n\n🌤️ **68°F** - Partly Cloudy\n\nExpect mild conditions throughout the day.", + }, + signal="continue", +) +``` + + + + +**Response best practices:** +- Use Markdown for formatting +- Be clear and concise +- Include relevant context +- End with a call-to-action if appropriate + +### Type: `elicitation` + +Requests clarification or input from the user. Creates a comment and sets the Agent Run status to `awaiting`. + +**Structure:** + +```typescript +interface ElicitationContent { + type: "elicitation"; + body: string; // The question or request (supports Markdown) +} +``` + +**Example:** + +```json +{ + "type": "elicitation", + "body": "I found multiple projects matching your query. Which one would you like me to focus on?\n\n1. Project Alpha (12 open issues)\n2. Project Beta (8 open issues)\n3. Project Gamma (23 open issues)" +} +``` + +**Creating an elicitation:** + + + + +```typescript +await planeClient.agentRuns.activities.create(workspaceSlug, agentRunId, { + type: "elicitation", + content: { + type: "elicitation", + body: "To generate the report, I need a few details:\n\n- What date range should I cover?\n- Should I include completed issues or only open ones?", + }, +}); +``` + + + + +```python +plane_client.agent_runs.activities.create( + workspace_slug=workspace_slug, + agent_run_id=agent_run_id, + type="elicitation", + content={ + "type": "elicitation", + "body": "To generate the report, I need a few details:\n\n- What date range should I cover?\n- Should I include completed issues or only open ones?", + }, +) +``` + + + + +**Elicitation best practices:** +- Ask specific, answerable questions +- Provide options when possible +- Don't ask too many questions at once +- Consider using `select` signal for multiple choice + +### Type: `error` + +Reports an error or failure. Creates a comment and sets the Agent Run status to `failed`. + +**Structure:** + +```typescript +interface ErrorContent { + type: "error"; + body: string; // The error message (supports Markdown) +} +``` + +**Example:** + +```json +{ + "type": "error", + "body": "I couldn't complete your request due to a connection issue with the external service. Please try again in a few minutes." +} +``` + +**Creating an error activity:** + + + + +```typescript +await planeClient.agentRuns.activities.create(workspaceSlug, agentRunId, { + type: "error", + content: { + type: "error", + body: "I was unable to access the GitHub repository. Please ensure the integration is properly configured.", + }, + signal_metadata: { + error_code: "GITHUB_ACCESS_DENIED", + retryable: true, + }, +}); +``` + + + + +```python +plane_client.agent_runs.activities.create( + workspace_slug=workspace_slug, + agent_run_id=agent_run_id, + type="error", + content={ + "type": "error", + "body": "I was unable to access the GitHub repository. Please ensure the integration is properly configured.", + }, + signal_metadata={ + "error_code": "GITHUB_ACCESS_DENIED", + "retryable": True, + }, +) +``` + + + + +**Error best practices:** +- Use friendly, non-technical language +- Suggest next steps when possible +- Store detailed error info in `signal_metadata` +- Don't expose stack traces or sensitive information + +### Type: `prompt` + +This type is **user-generated only**. Your agent cannot create prompt activities—they're created by Plane when a user sends a message. + +**Structure:** + +```typescript +interface PromptContent { + type: "prompt"; + body: string; // The user's message +} +``` + +**Example received in webhook:** + +```json +{ + "type": "prompt", + "body": "Can you check the status of our deployment pipeline?" +} +``` + +## Ephemeral Activities + +Ephemeral activities are temporary and won't create comment replies. They're useful for showing agent progress without cluttering the conversation thread. + +### Automatically Ephemeral Types + +The following activity types are automatically marked as ephemeral: +- `thought` +- `action` +- `error` + +### Ephemeral Behavior + +- Ephemeral activities appear temporarily in the Agent UI +- They're replaced when the next activity arrives +- They don't create permanent comment replies +- Useful for real-time progress updates + +### Visual Example + +``` +User: @WeatherBot What's the weather in Tokyo? + +[Ephemeral - disappears when next activity arrives] +💭 Analyzing your request... + +[Ephemeral - disappears when next activity arrives] +🔧 getCoordinates("Tokyo") + +[Ephemeral - disappears when next activity arrives] +🔧 getWeather(35.6762, 139.6503) → 72°F, Clear + +[Permanent - stays as comment] +🤖 The weather in Tokyo is currently 72°F with clear skies. +``` + +## Content Metadata + +Use `content_metadata` to store additional structured data about an activity: + +```typescript +await planeClient.agentRuns.activities.create(workspaceSlug, agentRunId, { + type: "action", + content: { + type: "action", + action: "analyzeCode", + parameters: { + file: "src/index.ts", + result: "Found 3 potential issues", + }, + }, + content_metadata: { + analysis_results: { + issues: [ + { line: 42, severity: "warning", message: "Unused variable" }, + { line: 78, severity: "error", message: "Type mismatch" }, + { line: 156, severity: "info", message: "Consider refactoring" }, + ], + processing_time_ms: 1250, + }, + }, +}); +``` + +## Signal Metadata + +Use `signal_metadata` to provide additional context for signals: + +```typescript +// Auth request with URL +{ + signal: "auth_request", + signal_metadata: { + url: "https://your-agent.com/auth/connect?session=xyz", + provider: "github", + scopes: ["repo", "read:user"], + } +} + +// Select with options +{ + signal: "select", + signal_metadata: { + options: [ + { id: "opt1", label: "Option 1", description: "First choice" }, + { id: "opt2", label: "Option 2", description: "Second choice" }, + ], + allow_multiple: false, + } +} + +// Error with details +{ + signal: "continue", + signal_metadata: { + error_code: "RATE_LIMIT_EXCEEDED", + retry_after: 60, + retryable: true, + } +} +``` + +## Complete Activity Creation Reference + +Here's a comprehensive example showing all activity types: + +```typescript +// 1. Thought - Show reasoning (ephemeral) +await createActivity({ + type: "thought", + content: { type: "thought", body: "Analyzing the request..." }, +}); + +// 2. Action - Tool invocation (ephemeral) +await createActivity({ + type: "action", + content: { + type: "action", + action: "searchIssues", + parameters: { query: "bug", status: "open" }, + }, +}); + +// 3. Response - Final answer (creates comment) +await createActivity({ + type: "response", + content: { type: "response", body: "Found 5 open bugs." }, + signal: "continue", +}); + +// 4. Elicitation - Ask for input (creates comment, awaits response) +await createActivity({ + type: "elicitation", + content: { type: "elicitation", body: "Which bug should I prioritize?" }, + signal: "select", + signal_metadata: { options: [...] }, +}); + +// 5. Error - Report failure (creates comment) +await createActivity({ + type: "error", + content: { type: "error", body: "Unable to access the database." }, + signal_metadata: { error_code: "DB_CONNECTION_FAILED" }, +}); +``` + +## Next Steps + +- Review [Best Practices](/dev-tools/agents/best-practices) for building responsive agents +- See [Building an Agent](/dev-tools/agents/building-an-agent) for implementation examples +- Check the [Overview](/dev-tools/agents/overview) for Agent Run lifecycle details + diff --git a/mint.json b/mint.json index f81cda8..b07b861 100644 --- a/mint.json +++ b/mint.json @@ -507,7 +507,16 @@ "pages": [ "dev-tools/build-plane-app", "dev-tools/intro-webhooks", - "dev-tools/mcp-server" + "dev-tools/mcp-server", + { + "group": "Agents", + "pages": [ + "dev-tools/agents/overview", + "dev-tools/agents/building-an-agent", + "dev-tools/agents/best-practices", + "dev-tools/agents/signals-content-payload" + ] + } ] } ],