Technical Documentation: Significant-Gravitas/AutoGPT
ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference
Overview (README.md)
AutoGPT — AI agents that finish the work
The open-source platform for AI agents
AutoGPT lets you build, deploy, and run AI agents that carry out complete workflows. Describe an outcome in plain English or shape every step in the visual builder, then run the agent on demand, on a schedule, or from a trigger.
185,000+ GitHub stars. Cited by:
| “Next frontier of prompt engineering imo: ‘AutoGPTs’.” | Andrej Karpathy, founding member of OpenAI |
| “If you have a phone you can run AutoGPT. You don't even need to learn how to code.” | Amjad Masad, co-founder & CEO of Replit |
| “AutoGPT might be the next big step in AI.” | Lior Alexander, CEO of AlphaSignal |
Four surfaces, one platform
| AutoPilot
Describe the job in plain English and turn the conversation into a working agent. | Agents
See every agent, run, cost, and action that needs your attention. |
| --- | --- |
| Marketplace
Start from proven agents, add one to your library, and customize it for your work. | Build
Drag, connect, branch, and inspect blocks for exact control over every step. |
Get started
AutoGPT Platform — public, hosted, and managed
The hosted Platform is publicly available. We manage the infrastructure, model access, credentials, reliability, and updates so you can focus on the work your agents perform.
Get started on AutoGPT Platform →
- AutoPilot, Agents, Marketplace, and Build
- 45+ connected platforms and hundreds of AI models
- No model API keys or infrastructure setup
- Agents that run on demand, on schedules, and from triggers
The hosted Platform is a paid service with usage-based agent runs. Compare plans and pricing →
Self-host AutoGPT
[!NOTE]
Self-hosting is the free path. You provide the infrastructure and model API keys, and you maintain the deployment. If you want zero setup, use the managed Platform.
The Linux and macOS single-container release installer is coming with the next
appliance release. Until the public installer endpoint and image tags pass the
documented release gates,
use the manual self-hosting guide.
The release installer will require an already-running local Docker daemon using
Linux containers on amd64 or arm64. It pulls the published appliance and
runs its immutable digest; it does not install Docker or build from source.
Windows users should continue with the manual self-hosting guide for now.
Managed Platform vs. self-hosting
| AutoGPT Platform | Self-hosted | |
|---|---|---|
| Access | Public signup | Operate the published appliance or a development checkout |
| Cost | Paid plan plus agent usage | No license fee; pay your own infrastructure and model providers |
| Setup | Managed | Docker and configuration required |
| Model access | Built in | Bring your own API keys |
| Updates and operations | Managed by AutoGPT | Managed by you |
| Core builder and agent runtime | Included | Included |
| Data and infrastructure control | Hosted by AutoGPT | Runs on your infrastructure |
| Support | Plan-dependent | Community support |
Both paths use the same repository. Choose the managed Platform when you want agents running immediately; self-host when infrastructure control matters more than operational convenience.
Why the hosted Platform is paid
Every agent run consumes real model usage, compute, storage, secrets management, and operational support. The managed Platform covers that infrastructure and funds continued development of the open-source project.
Self-hosting remains available without a license fee for people and teams that want to provide and operate those resources themselves.
What you can automate
| Area | Example |
|---|---|
| Executive operations | Prepare a daily brief from internal and external signals |
| Sales | Research every account before tomorrow's meetings |
| Marketing | Turn a launch brief into campaign drafts across channels |
| Engineering | Triage incidents and start with a likely cause |
| Customer support | Draft replies, collect context, and flag escalations |
| Research | Monitor sources and return structured reports when something changes |
Integrations
Connect the apps that are yours. AutoGPT provides access to hundreds of AI models and connects agents to 45+ platforms, including:
Gmail · Google Calendar · Google Docs · Google Sheets · GitHub · Slack · Discord · Notion · HubSpot · Linear · Airtable · Jira · Salesforce · Stripe · Webflow
Community and support
| Resource | Link |
|---|---|
| Discord | Join the AutoGPT community |
| Documentation | docs.agpt.co |
| Bug reports | GitHub Issues |
| Feature requests | GitHub Issues |
| Contributing | CONTRIBUTING.md |
License
| Component | License | What it means |
|---|---|---|
autogpt_platform/ |
Polyform Shield | Free for personal and internal business use; cannot be sold as a competing hosted service |
classic/ and everything else |
MIT | Permissive open-source use |
AutoGPT Classic
Looking for the original standalone AutoGPT agent? It remains available in classic/ under the MIT License.
Contributors
Deutsch |
Español |
français |
日本語 |
한국어 |
Português |
Русский |
中文
Content Index (`docs/content/index.md`)
AutoGPT Documentation
Welcome to the AutoGPT Documentation.
What is the AutoGPT Platform?
The AutoGPT Platform is a groundbreaking system that revolutionizes AI utilization for businesses and individuals. It enables the creation, deployment, and management of continuous agents that work tirelessly on your behalf, bringing unprecedented efficiency and innovation to your workflows.
Key Features
- Seamless Integration and Low-Code Workflows: Rapidly create complex workflows without extensive coding knowledge.
- Autonomous Operation and Continuous Agents: Deploy cloud-based assistants that run indefinitely, activating on relevant triggers.
- Intelligent Automation and Maximum Efficiency: Streamline workflows by automating repetitive processes.
- Reliable Performance and Predictable Execution: Enjoy consistent and dependable long-running processes.
Platform Architecture
The AutoGPT Platform consists of two main components:
1. AutoGPT Server
The powerhouse of our platform, containing:
- Source Code: Core logic driving agents and automation processes.
- Infrastructure: Robust systems ensuring reliable and scalable performance.
- Marketplace: A comprehensive marketplace for pre-built agents.
2. AutoGPT Frontend
The user interface where you interact with the platform:
- Agent Builder: Design and configure your own AI agents.
- Workflow Management: Build, modify, and optimize automation workflows.
- Deployment Controls: Manage the lifecycle of your agents.
- Ready-to-Use Agents: Select from pre-configured agents.
- Agent Interaction: Run and interact with agents through a user-friendly interface.
- Monitoring and Analytics: Track agent performance and gain insights.
Platform Components
Agents and Workflows
In the platform, you can create highly customized workflows to build agents. An agent is essentially an automated workflow that you design to perform specific tasks or processes. Create customized workflows to build agents for various tasks, including:
- Data processing and analysis
- Task scheduling and management
- Communication and notification systems
- Integration between different software tools
- AI-powered decision making and content generation
Blocks as Integrations
Blocks represent actions and are the building blocks of your workflows, including:
- Connections to external services
- Data processing tools
- AI models for various tasks
- Custom scripts or functions
- Conditional logic and decision-making components
You can learn more under: Build your own Blocks
Available Language Models
The platform comes pre-integrated with cutting-edge LLM providers:
- OpenAI - https://openai.com/
- Anthropic - https://www.anthropic.com/
- Groq - https://groq.com/
- Llama - https://llamaindex.ai/
- AI/ML API - https://aimlapi.com/
- AI/ML API provides 300+ AI models including Deepseek, Gemini, ChatGPT. The models run at enterprise-grade rate limits and uptimes.
License Overview
We've adopted a dual-license approach to balance open collaboration with sustainable development:
- MIT License: The majority of the AutoGPT repository remains under this license.
- Polyform Shield License: Applies to the new
autogpt_platformfolder.
This strategy allows us to share previously closed-source components, fostering a vibrant ecosystem of developers and users.
Ready to Get Started?
- Read the Getting Started docs to self-host
- Join the waitlist for the cloud-hosted beta
- Contribute
Engineering README (`docs/engineering/README.md`)
Engineering notes
Working documents for the team and the agents that work on AutoGPT: analytics and tracking plans, rollout plans, runbooks, internal references, and design and architecture notes.
Nothing in this folder is on the docs site. The site at agpt.co/docs is built from docs/home/, docs/platform/ and docs/integrations/, and shows the pages listed in each folder's SUMMARY.md. A page written for people outside the team, whether they use, self-host or contribute to AutoGPT, goes there. Everything else goes here.
These notes are still public. The repository is public, and AutoPilot's documentation search indexes every Markdown file under docs/, this folder included.
- Don't list a file from this folder in a
SUMMARY.md. - To link to a note from a published page, use its GitHub URL on
dev. The relative path does not exist on the docs site.
Home README (`docs/home/README.md`)
cover: .gitbook/assets/Banner_image.png
coverY: 56.53835084561286
layout:
width: wide
cover:
visible: true
size: full
title:
visible: false
visible: false
tableOfContents:
visible: false
outline:
visible: false
pagination:
visible: false
metadata:
visible: true
Developer Platform
AutoGPT Documentation
| Cover image | ||||
|---|---|---|---|---|
| :leaf: | AutoGPT Platform | Get started with the developer platform in 5 minutes. | AutoGPT Platform | AGPT_Platform.png |
| :server: | Integrations | Learn more about hosting the developer platform. | Integrations | Integrations.png |
| :terminal: | Contribute | Browse, test, and implement APIs. | Contributing to the Docs | Contribute.png |
{% columns %}
{% column valign="middle" %}
{% endcolumn %}
{% column %}
{% endcolumn %}
{% endcolumns %}
Join a community of 65,000+ developers
| :discord: | Discord community | Join our Discord community to post questions, get help, and share resources with our growing community of over 55,000 members. | Join Discord | https://discord.com/invite/autogpt | |
| :github: | GitHub | Our product is 100% open source and built by developers just like you. Head to our GitHub repository to learn how to submit your first PR. | Submit a PR | https://github.com/Significant-Gravitas/AutoGPT |
Integrations README (`docs/integrations/README.md`)
layout:
width: default
title:
visible: true
visible: true
tableOfContents:
visible: false
outline:
visible: true
pagination:
visible: true
metadata:
visible: true
AutoGPT Blocks Overview
AutoGPT uses a modular approach with various "blocks" to handle different tasks. These blocks are the building blocks of AutoGPT workflows, allowing users to create complex automations by combining simple, specialized components.
{% hint style="info" %}
Creating Your Own Blocks
Want to create your own custom blocks? Check out our guides:
- Build your own Blocks - Step-by-step tutorial with examples
- Block SDK Guide - Advanced SDK patterns with OAuth, webhooks, and provider configuration
{% endhint %}
Below is a comprehensive list of all available blocks, categorized by their primary function. Click on any block name to view its detailed documentation.
Basic Operations
| Block Name | Description |
|---|---|
| Add Memory | Add new memories to Mem0 with user segmentation |
| Add To Dictionary | Adds a new key-value pair to a dictionary |
| Add To Library From Store | Add an agent from the store to your personal library |
| Add To List | Adds a new entry to a list |
| Agent Date Input | Block for date input |
| Agent Dropdown Input | Block for dropdown text selection |
| Agent File Input | Block for file upload input (string path for example) |
| Agent Google Drive File Input | Agent-level input for a Google Drive file |
| Agent Input | A block that accepts and processes user input values within a workflow, supporting various input types and validation |
| Agent Long Text Input | Block for long text input (multi-line) |
| Agent Number Input | Block for number input |
| Agent Output | A block that records and formats workflow results for display to users, with optional Jinja2 template formatting support |
| Agent Short Text Input | Block for short text input (single-line) |
| Agent Table Input | Block for table data input with customizable headers |
| Agent Time Input | Block for time input |
| Agent Toggle Input | Block for boolean toggle input |
| Block Installation | Given a code string, this block allows the verification and installation of a block code into the system |
| Concatenate Lists | Concatenates multiple lists into a single list |
| Dictionary Is Empty | Checks if a dictionary is empty |
| File Store | Downloads and stores a file from a URL, data URI, or local path |
| Find In Dictionary | A block that looks up a value in a dictionary, list, or object by key or index and returns the corresponding value |
| Find In List | Finds the index of the value in the list |
| Flatten List | Flattens a nested list structure into a single flat list |
| Get All Memories | Retrieve all memories from Mem0 with optional conversation filtering |
| Get Latest Memory | Retrieve the latest memory from Mem0 with optional key filtering |
| Get List Item | Returns the element at the given index |
| Get Store Agent Details | Get detailed information about an agent from the store |
| Get Weather Information | Retrieves weather information for a specified location using OpenWeatherMap API |
| Human In The Loop | Pause execution for human review |
| Interleave Lists | Interleaves elements from multiple lists in round-robin fashion, alternating between sources |
| List Difference | Computes the difference between two lists |
| List Intersection | Computes the intersection of two lists, returning only elements present in both |
| List Is Empty | Checks if a list is empty |
| List Library Agents | List all agents in your personal library |
| Note | A visual annotation block that displays a sticky note in the workflow editor for documentation and organization purposes |
| Print To Console | A debugging block that outputs text to the console for monitoring and troubleshooting workflow execution |
| Remove From Dictionary | Removes a key-value pair from a dictionary |
| Remove From List | Removes an item from a list by value or index |
| Replace Dictionary Value | Replaces the value for a specified key in a dictionary |
| Replace List Item | Replaces an item at the specified index |
| Reverse List Order | Reverses the order of elements in a list |
| Search Memory | Search memories in Mem0 by user |
| Search Store Agents | Search for agents in the store |
| Slant3D Cancel Order | Cancel an order before production starts |
| Slant3D Create Order | Order physical 3D-printed parts from Slant3D for manufacturing and delivery |
| Slant3D Estimate Order | Quote a 3D-printed parts order including shipping by creating an uncharged Slant3D draft |
| Slant3D Estimate Shipping | Estimate delivery costs for physical 3D-printed parts by creating an uncharged Slant3D order draft |
| Slant3D Filament | Find available materials and colors for quoting or ordering physical 3D-printed parts from Slant3D |
| Slant3D Get Orders | Get all orders for the account |
| Slant3D Process Order | Submit an approved Slant3D draft to order physical 3D-printed parts for manufacturing and delivery |
| Slant3D Slicer | Get a live 3D printing quote for physical parts from STL URLs or attached workspace files |
| Slant3D Tracking | Track order status and shipping |
| Store Value | Holds or receives a value and outputs it statically so that it can be used multiple times within the same agent run |
| Universal Type Converter | This block is used to convert a value to a universal type |
| XML Parser | Parses XML using gravitasml to tokenize and coverts it to dict |
| Zip Lists | Zips multiple lists together into a list of grouped elements |
Data Processing
| Block Name | Description |
|---|---|
| Airtable Create Base | Create or find a base in Airtable |
| Airtable Create Field | Add a new field to an Airtable table |
| Airtable Create Records | Create records in an Airtable table |
| Airtable Create Table | Create a new table in an Airtable base |
| Airtable Delete Records | Delete records from an Airtable table |
| Airtable Get Record | Get a single record from Airtable |
| Airtable List Bases | List all bases in Airtable |
| Airtable List Records | List records from an Airtable table |
| Airtable List Schema | Get the complete schema of an Airtable base |
| Airtable Update Field | Update field properties in an Airtable table |
| Airtable Update Records | Update records in an Airtable table |
| Airtable Update Table | Update table properties |
| Airtable Webhook Trigger | Starts a flow whenever Airtable emits a webhook event |
| Baas Bot Delete Recording | Permanently delete a meeting's recorded data |
| Baas Bot Fetch Meeting Data | Retrieve recorded meeting data |
| Create Dictionary | Creates a dictionary with the specified key-value pairs |
| Create List | Creates a list with the specified values |
| Data For Seo Keyword Suggestions | Get keyword suggestions from DataForSEO Labs Google API |
| Data For Seo Related Keywords | Get related keywords from DataForSEO Labs Google API |
| Exa Create Import | Import CSV data to use with websets for targeted searches |
| Exa Delete Import | Delete an import |
| Exa Export Webset | Export webset data in JSON, CSV, or JSON Lines format |
| Exa Get Import | Get the status and details of an import |
| Exa Get New Items | Get items added since a cursor - enables incremental processing without reprocessing |
| Exa List Imports | List all imports with pagination support |
| File Read | Reads a file and returns its content as a string, with optional chunking by delimiter and size limits |
| Google Calendar Read Events | Retrieves upcoming events from a Google Calendar with filtering options |
| Google Docs Append Markdown | Append Markdown content to the end of a Google Doc with full formatting - ideal for LLM/AI output |
| Google Docs Append Plain Text | Append plain text to the end of a Google Doc (no formatting applied) |
| Google Docs Create | Create a new Google Doc |
| Google Docs Delete Content | Delete a range of content from a Google Doc |
| Google Docs Export | Export a Google Doc to PDF, Word, text, or other formats |
| Google Docs Find Replace Plain Text | Find and replace plain text in a Google Doc (no formatting applied to replacement) |
| Google Docs Format Text | Apply formatting (bold, italic, color, etc |
| Google Docs Get Metadata | Get metadata about a Google Doc |
| Google Docs Get Structure | Get document structure with index positions for precise editing operations |
| Google Docs Insert Markdown At | Insert formatted Markdown at a specific position in a Google Doc - ideal for LLM/AI output |
| Google Docs Insert Page Break | Insert a page break into a Google Doc |
| Google Docs Insert Plain Text | Insert plain text at a specific position in a Google Doc (no formatting applied) |
| Google Docs Insert Table | Insert a table into a Google Doc, optionally with content and Markdown formatting |
| Google Docs Read | Read text content from a Google Doc |
| Google Docs Replace All With Markdown | Replace entire Google Doc content with formatted Markdown - ideal for LLM/AI output |
| Google Docs Replace Content With Markdown | Find text and replace it with formatted Markdown - ideal for LLM/AI output and templates |
| Google Docs Replace Range With Markdown | Replace a specific index range in a Google Doc with formatted Markdown - ideal for LLM/AI output |
| Google Docs Set Public Access | Make a Google Doc public or private |
| Google Docs Share | Share a Google Doc with specific users |
| Google Sheets Add Column | Add a new column with a header |
| Google Sheets Add Dropdown | Add a dropdown list (data validation) to cells |
| Google Sheets Add Note | Add a note to a cell in a Google Sheet |
| Google Sheets Append Row | Append or Add a single row to the end of a Google Sheet |
| Google Sheets Batch Operations | This block performs multiple operations on a Google Sheets spreadsheet in a single batch request |
| Google Sheets Clear | This block clears data from a specified range in a Google Sheets spreadsheet |
| Google Sheets Copy To Spreadsheet | Copy a sheet from one spreadsheet to another |
| Google Sheets Create Named Range | Create a named range to reference cells by name instead of A1 notation |
| Google Sheets Create Spreadsheet | This block creates a new Google Sheets spreadsheet with specified sheets |
| Google Sheets Delete Column | Delete a column by header name or column letter |
| Google Sheets Delete Rows | Delete specific rows from a Google Sheet by their row indices |
| Google Sheets Export Csv | Export a Google Sheet as CSV data |
| Google Sheets Filter Rows | Filter rows in a Google Sheet based on a column condition |
| Google Sheets Find | Find text in a Google Sheets spreadsheet |
| Google Sheets Find Replace | This block finds and replaces text in a Google Sheets spreadsheet |
| Google Sheets Format | Format a range in a Google Sheet (sheet optional) |
| Google Sheets Get Column | Extract all values from a specific column |
| Google Sheets Get Notes | Get notes from cells in a Google Sheet |
| Google Sheets Get Row | Get a specific row by its index |
| Google Sheets Get Row Count | Get row count and dimensions of a Google Sheet |
| Google Sheets Get Unique Values | Get unique values from a column |
| Google Sheets Import Csv | Import CSV data into a Google Sheet |
| Google Sheets Insert Row | Insert a single row at a specific position |
| Google Sheets List Named Ranges | List all named ranges in a spreadsheet |
| Google Sheets Lookup Row | Look up a row by finding a value in a specific column |
| Google Sheets Manage Sheet | Create, delete, or copy sheets (sheet optional) |
| Google Sheets Metadata | This block retrieves metadata about a Google Sheets spreadsheet including sheet names and properties |
| Google Sheets Protect Range | Protect a cell range or entire sheet from editing |
| Google Sheets Read | A block that reads data from a Google Sheets spreadsheet using A1 notation range selection |
| Google Sheets Remove Duplicates | Remove duplicate rows based on specified columns |
| Google Sheets Set Public Access | Make a Google Spreadsheet public or private |
| Google Sheets Share Spreadsheet | Share a Google Spreadsheet with users or get shareable link |
| Google Sheets Sort | Sort a Google Sheet by one or two columns |
| Google Sheets Update Cell | Update a single cell in a Google Sheets spreadsheet |
| Google Sheets Update Row | Update a specific row by its index |
| Google Sheets Write | A block that writes data to a Google Sheets spreadsheet at a specified A1 notation range |
| JSON Decoder | Decodes a JSON string into the value or data structure, it represents, e |
| JSON Encoder | Encodes any value or data structure into a JSON string |
| Keyword Suggestion Extractor | Extract individual fields from a KeywordSuggestion object |
| Persist Information | Persists a key-value pair for use across multiple runs of an agent |
| RMFG Analyze Design | Uploads a STEP file to RMFG, a manufacturer that makes and ships real sheet-metal and tube-laser parts, and returns its analyzed parts: the first step before a live quote, cart or order |
| RMFG Create Cart | Creates an RMFG cart with a website checkout link for one or more configured designs, priced live with shipping and tax once an address is set |
| RMFG Create DFM Report | Runs an RMFG manufacturability (DFM) check on a configured design |
| RMFG Create Quote | Gets a live price from RMFG, a manufacturer that laser-cuts, bends and ships real sheet-metal and tube parts, plus manufacturability findings |
| RMFG Create Review Link | Creates an RMFG review link so a person can inspect and adjust a design |
| RMFG Get Cart | Fetches an RMFG cart and its latest quote by ID |
| RMFG Get DFM Report | Fetches an RMFG DFM report by ID |
| RMFG Get Design | Fetches an RMFG design and its analyzed parts by ID |
| RMFG Get Order | Fetches an RMFG order's status and shipment tracking |
| RMFG Get Quote | Fetches an RMFG quote by ID |
| RMFG Get Review Link | Fetches an RMFG review link and the configuration a person saved on it |
| RMFG List Finishes | Lists the finishes RMFG can apply to sheet or tube parts |
| RMFG List Hardware | Lists the taps, studs, nuts or standoffs RMFG can install |
| RMFG List Materials | Lists the sheet-metal stock RMFG can cut and bend, with thickness in mm and inches |
| RMFG List Orders | Lists the RMFG account's manufacturing orders |
| RMFG List Powder Coat Colors | Lists the powder-coat colors RMFG offers |
| RMFG List Tube Profiles | Lists the tube stock profiles RMFG can laser-cut |
| RMFG Pay Cart | Pays an RMFG cart with the saved card and places a real production order |
| RMFG Update Cart | Updates an open RMFG cart's address, shipping option or items |
| Read Spreadsheet | Reads CSV and Excel files and outputs the data as a list of dictionaries and individual rows |
| Related Keyword Extractor | Extract individual fields from a RelatedKeyword object |
| Retrieve Information | Reads back a key-value pair previously saved by PersistInformationBlock |
| SQL Query | Execute a SQL query |
| Screenshot Web Page | Takes a screenshot of a specified website using ScreenshotOne API |
| Stripe Link Create Card Spend Request | Create a Stripe Link spend request for a one-time virtual card |
| Stripe Link Create Token Spend Request | MPP step 2 of 3: ask the user to authorize a payment to a merchant that answers HTTP 402, and provision a Shared Payment Token for it |
| Stripe Link Get Payment Challenge | MPP step 1 of 3: read a merchant's HTTP 402 payment challenge to learn its network ID and amount |
| Stripe Link Get Shipping Address | Get the delivery addresses saved on the user's Link wallet, with the default one resolved for you |
| Stripe Link Get Spend Request Status | Check whether a Stripe Link spend request has been approved yet |
| Stripe Link Get User Info | Get the Link account holder's name, email and phone |
| Stripe Link List Payment Methods | List the cards and bank accounts in the user's Link wallet |
| Stripe Link MPP Pay | MPP step 3 of 3: spend an approved Shared Payment Token at the merchant's endpoint |
| Stripe Link Retrieve Card | Get the one-time virtual card number and CVC for an approved spend request, to type into a normal checkout form |
| Stripe Subscription Trigger | Triggers on Stripe subscription events (new, upgrade, cancel) |
Text Processing
| Block Name | Description |
|---|---|
| Code Extraction | Extracts code blocks from text and identifies their programming languages |
| Combine Texts | This block combines multiple input texts into a single output text |
| Countdown Timer | This block triggers after a specified duration |
| Extract Text Information | This block extracts the text from the given text using the pattern (regex) |
| Fill Text Template | This block formats the given texts using the format template |
| Get Current Date | This block outputs the current date with an optional offset |
| Get Current Date And Time | This block outputs the current date and time |
| Get Current Time | This block outputs the current time |
| Match Text Pattern | Matches text against a regex pattern and forwards data to positive or negative output based on the match |
| Text Decoder | Decodes a string containing escape sequences into actual text |
| Text Encoder | Encodes a string by converting special characters into escape sequences |
| Text Replace | This block is used to replace a text with a new text |
| Text Split | This block is used to split a text into a list of strings |
| Word Character Count | Counts the number of words and characters in a given text |
AI and Language Models
| Block Name | Description |
|---|---|
| AI Ad Maker Video Creator | Creates an AI‑generated 30‑second advert (text + images) |
| AI Condition | Uses AI to evaluate natural language conditions and provide conditional outputs |
| AI Conversation | A block that facilitates multi-turn conversations with a Large Language Model (LLM), maintaining context across message exchanges |
| AI Image Customizer | Generate and edit custom images using Google's Nano-Banana models from Gemini |
| AI Image Editor | Edit images using Flux Kontext or Google Nano Banana models |
| AI Image Generator | Generate images using various AI models through a unified interface |
| AI List Generator | A block that creates lists of items based on prompts using a Large Language Model (LLM), with optional source data for context |
| AI Music Generator | This block generates music using Meta's MusicGen model on Replicate |
| AI Screenshot To Video Ad | Turns a screenshot into an engaging, avatar‑narrated video advert |
| AI Shortform Video Creator | Creates a shortform video using revid |
| AI Structured Response Generator | A block that generates structured JSON responses using a Large Language Model (LLM), with schema validation and format enforcement |
| AI Text Generator | A block that produces text responses using a Large Language Model (LLM) based on customizable prompts and system instructions |
| AI Text Summarizer | A block that summarizes long texts using a Large Language Model (LLM), with configurable focus topics and summary styles |
| AI Video Generator | Generate videos using FAL AI models |
| Bannerbear Text Overlay | Add text overlay to images using Bannerbear templates |
| Claude Code | Execute tasks using Claude Code in an E2B sandbox |
| Code Generation | Generate or refactor code using an OpenAI API key or a connected ChatGPT plan through Codex App Server |
| Create Talking Avatar Video | This block integrates with D-ID to create video clips and retrieve their URLs |
| Exa Answer | Get an LLM answer to a question informed by Exa search results |
| Exa Create Enrichment | Create enrichments to extract additional structured data from webset items |
| Exa Create Research | Create research task with optional waiting - explores web and synthesizes findings with citations |
| Ideogram Model | This block runs Ideogram models with both simple and advanced settings |
| Jev Ask Many | Ask multiple Choice, Score, or Noul questions of one shared state with Jev in a single call |
| Jev Choice | Make a typed choice with Jev |
| Jev Filter | Filter items with sequential Jev scores, one call per item |
| Jev Pick Best | Choose the best candidate using Jev and rank candidates directly by its probabilities |
| Jev Route | Route data using Jev's typed choice |
| Jev Score | Score evidence with Jev using an explicit ordered scale |
| Jev Yes No | Ask Jev a plain-language yes/no question and forward data to the chosen pin, or unsure below your confidence threshold |
| Jina Chunking | Chunks texts using Jina AI's segmentation service |
| Jina Embedding | Generates embeddings using Jina AI |
| Orchestrator | Uses AI to intelligently decide what tool to use |
| Perplexity | Query Perplexity's sonar models with real-time web search capabilities and receive annotated responses with source citations |
| Replicate Flux Advanced Model | This block runs Flux models on Replicate with advanced settings |
| Replicate Model | Run Replicate models synchronously |
| Smart Search | Natural-language search for people, leads or companies using DataForB2B's B2B database — describe your ideal lead or ICP in plain English and get matching profiles |
| Stagehand Act | Interact with a web page by performing actions on a web page |
| Stagehand Extract | Extract structured data from a webpage |
| Stagehand Observe | Find suggested actions for your workflows |
| Unreal Text To Speech | Converts text to speech using the Unreal Speech API |
| Video Narration | Generate AI narration and add to video |
Search and Information Retrieval
| Block Name | Description |
|---|---|
| AnySearch | Searches the web using AnySearch - general queries plus vertical domains (finance, academic, health, legal, and more) via domain/sub_domain filters |
| AnySearch Extract | Extracts readable content from a single URL using AnySearch, optimized for LLM consumption |
| AnySearch Parallel Search | Runs several AnySearch queries in parallel (client-side concurrency via asyncio) |
| Ask Wolfram | Ask Wolfram Alpha a question |
| Exa Bulk Webset Items | Get all items from a webset in bulk (with configurable limits) |
| Exa Cancel Enrichment | Cancel a running enrichment operation |
| Exa Cancel Webset | Cancel all operations being performed on a Webset |
| Exa Cancel Webset Search | Cancel a running webset search |
| Exa Contents | Retrieves document contents using Exa's contents API |
| Exa Create Monitor | Create automated monitors to keep websets updated with fresh data on a schedule |
| Exa Create Or Find Webset | Create a new webset or return existing one by external_id (idempotent operation) |
| Exa Create Webset | Create a new Exa Webset for persistent web search collections with optional waiting for initial results |
| Exa Create Webset Search | Add a new search to an existing webset to find more items |
| Exa Delete Enrichment | Delete an enrichment from a webset |
| Exa Delete Monitor | Delete a monitor from a webset |
| Exa Delete Webset | Delete a Webset and all its items |
| Exa Delete Webset Item | Delete a specific item from a webset |
| Exa Find Or Create Search | Find existing search by query or create new - prevents duplicate searches in workflows |
| Exa Find Similar | Finds similar links using Exa's findSimilar API |
| Exa Get Enrichment | Get the status and details of a webset enrichment |
| Exa Get Monitor | Get the details and status of a webset monitor |
| Exa Get Research | Get status and results of a research task |
| Exa Get Webset | Retrieve a Webset by ID or external ID |
| Exa Get Webset Item | Get a specific item from a webset by its ID |
| Exa Get Webset Search | Get the status and details of a webset search |
| Exa List Monitors | List all monitors with optional webset filtering |
| Exa List Research | List all research tasks with pagination support |
| Exa List Webset Items | List items in a webset with pagination support |
| Exa List Websets | List all Websets with pagination support |
| Exa Preview Webset | Preview how a search query will be interpreted before creating a webset |
| Exa Search | Searches the web using Exa's advanced search API |
| Exa Update Enrichment | Update an existing enrichment configuration |
| Exa Update Monitor | Update a monitor's status, schedule, or metadata |
| Exa Update Webset | Update metadata for an existing Webset |
| Exa Wait For Enrichment | Wait for a webset enrichment to complete with progress tracking |
| Exa Wait For Research | Wait for a research task to complete with configurable timeout |
| Exa Wait For Search | Wait for a specific webset search to complete with progress tracking |
| Exa Wait For Webset | Wait for a webset to reach a specific status with progress tracking |
| Exa Webset Items Summary | Get a summary of webset items without retrieving all data |
| Exa Webset Status | Get a quick status overview of a webset |
| Exa Webset Summary | Get a comprehensive summary of a webset with samples and statistics |
| Extract Website Content | This block scrapes the content from the given web URL |
| Fact Checker | This block checks the factuality of a given statement using Jina AI's Grounding API |
| Firecrawl Crawl | Firecrawl crawls websites to extract comprehensive data while bypassing blockers |
| Firecrawl Extract | Firecrawl crawls websites to extract comprehensive data while bypassing blockers |
| Firecrawl Map Website | Firecrawl maps a website to extract all the links |
| Firecrawl Scrape | Firecrawl scrapes a website to extract comprehensive data while bypassing blockers |
| Firecrawl Search | Firecrawl searches the web for the given query |
| Get Person Detail | Get detailed person data with Apollo API, including email reveal |
| Get Wikipedia Summary | This block fetches the summary of a given topic from Wikipedia |
| Google Maps Search | This block searches for local businesses using Google Maps API |
| Hacker News Get Item | Get a Hacker News story, comment, poll or job by its id or link, with its comments as a list in reading order |
| Hacker News Get Stories | Get the top (front page), new, best, Ask HN, Show HN or job stories from Hacker News, in the order Hacker News ranks them |
| Hacker News Get User | Get a Hacker News user's profile by username: karma, when the account was created, the about text and how many items they have posted |
| Hacker News Search | Search Hacker News stories and comments by keyword, author, date, points or linked domain, sorted by relevance or newest first |
| Search Filter Typeahead | Resolve the exact filter value (company, industry, job title, skill, school, investor, location, category) for people and company searches with DataForB2B |
| Search Organizations | Search for organizations in Apollo |
| Search People | Search for people in Apollo |
| Search The Web | This block searches the internet for the given search query |
| Tavily Crawl | Crawls a website with Tavily, following links from the root URL and extracting page content |
| Tavily Extract | Extracts page content from one or more URLs using Tavily, optimized for LLM consumption |
| Tavily Map | Maps a website's structure with Tavily, discovering its URLs without extracting content |
| Tavily Search | Searches the web using Tavily's AI-native search API |
| Validate Emails | Validate emails |
Social Media and Content
| Block Name | Description |
|---|---|
| Approve Reddit Post | Approves a Reddit post or comment from the mod queue |
| Ban Subreddit User | Bans a user from a subreddit |
| Create Discord Thread | Creates a new thread in a Discord channel |
| Create Reddit Post | Create a new post on a subreddit |
| Delete Reddit Comment | Delete a Reddit comment that you own |
| Delete Reddit Post | Delete a Reddit post that you own |
| Delete Telegram Message | Delete a message from a Telegram chat |
| Discord Channel Info | Resolves Discord channel names to IDs and vice versa |
| Discord Get Current User | Gets information about the currently authenticated Discord user using OAuth2 credentials |
| Discord User Info | Gets information about a Discord user by their ID |
| Edit Reddit Post | Edit the body text of an existing Reddit post that you own |
| Edit Telegram Message | Edit the text of an existing message sent by the bot |
| Get Linkedin Profile | Fetch LinkedIn profile data using Enrichlayer |
| Get Linkedin Profile Picture | Get LinkedIn profile pictures using Enrichlayer |
| Get Reddit Comment | Get details about a specific Reddit comment by its ID |
| Get Reddit Comment Replies | Get replies to a specific Reddit comment |
| Get Reddit Inbox | Get messages, mentions, and comment replies from your Reddit inbox |
| Get Reddit Post | Get detailed information about a specific Reddit post by its ID |
| Get Reddit Post Comments | Get top-level comments on a Reddit post |
| Get Reddit Posts | This block fetches Reddit posts from a defined subreddit name |
| Get Reddit User Info | Get information about a Reddit user including karma, account age, and verification status |
| Get Subreddit Flairs | Get available link flair options for a subreddit |
| Get Subreddit Info | Get information about a subreddit including subscriber count, description, and rules |
| Get Subreddit Rules | Get the rules for a subreddit to ensure compliance before posting |
| Get Telegram File | Download a file from Telegram using its file_id |
| Get User Posts | Fetch posts by a specific Reddit user |
| Linkedin Person Lookup | Look up LinkedIn profiles by person information using Enrichlayer |
| Linkedin Role Lookup | Look up LinkedIn profiles by role in a company using Enrichlayer |
| Lock Reddit Post | Locks or unlocks a Reddit post or comment to prevent or allow replies |
| Mod Queue | Fetches the mod queue for a subreddit |
| Post Reddit Comment | This block posts a Reddit comment on a specified Reddit post |
| Post To Bluesky | Post to Bluesky using Ayrshare |
| Post To Facebook | Post to Facebook using Ayrshare |
| Post To GMB | Post to Google My Business using Ayrshare |
| Post To Instagram | Post to Instagram using Ayrshare |
| Post To LinkedIn | Post to LinkedIn using Ayrshare |
| Post To Pinterest | Post to Pinterest using Ayrshare |
| Post To Reddit | Post to Reddit using Ayrshare |
| Post To Snapchat | Post to Snapchat using Ayrshare |
| Post To Telegram | Post to Telegram using Ayrshare |
| Post To Threads | Post to Threads using Ayrshare |
| Post To Tik Tok | Post to TikTok using Ayrshare |
| Post To X | Post to X / Twitter using Ayrshare |
| Post To YouTube | Post to YouTube using Ayrshare |
| Publish To Medium | Publishes a post to Medium |
| Read Discord Messages | Reads new messages from a Discord channel using a bot token and triggers when a new message is posted |
| Reddit Get My Posts | Fetch posts created by the authenticated Reddit user (you) |
| Remove Reddit Post | Removes a Reddit post or comment as a moderator |
| Reply To Discord Message | Replies to a specific Discord message |
| Reply To Reddit Comment | Reply to a specific Reddit comment |
| Reply To Telegram Message | Reply to a specific message in a Telegram chat |
| Search Reddit | Search Reddit for posts matching a query |
| Send Discord DM | Sends a direct message to a Discord user using their user ID |
| Send Discord Embed | Sends a rich embed message to a Discord channel |
| Send Discord File | Sends a file attachment to a Discord channel |
| Send Discord Message | Sends a message to a Discord channel using a bot token |
| Send Mod Mail | Sends a modmail message from a subreddit to a user |
| Send Reddit Message | Send a private message (DM) to a Reddit user |
| Send Slack Message | Send a text message to any Slack channel, DM, or thread |
| Send Telegram Audio | Send an audio file to a Telegram chat |
| Send Telegram Document | Send a document (any file type) to a Telegram chat |
| Send Telegram Message | Send a text message to a Telegram chat |
| Send Telegram Photo | Send a photo to a Telegram chat |
| Send Telegram Video | Send a video to a Telegram chat |
| Send Telegram Voice | Send a voice message to a Telegram chat |
| Telegram Message Reaction Trigger | Triggers when a reaction to a message is changed |
| Telegram Message Trigger | Triggers when a message is received or edited in your Telegram bot |
| Transcribe Youtube Video | Transcribes a YouTube video using a proxy |
| Twitter Add List Member | This block adds a specified user to a Twitter List owned by the authenticated user |
| Twitter Bookmark Tweet | This block bookmarks a tweet on Twitter |
| Twitter Create List | This block creates a new Twitter List for the authenticated user |
| Twitter Delete List | This block deletes a specified Twitter List owned by the authenticated user |
| Twitter Delete Tweet | This block deletes a tweet on Twitter |
| Twitter Follow List | This block follows a specified Twitter list for the authenticated user |
| Twitter Follow User | This block follows a specified Twitter user |
| Twitter Get Blocked Users | This block retrieves a list of users blocked by the authenticating user |
| Twitter Get Bookmarked Tweets | This block retrieves bookmarked tweets from Twitter |
| Twitter Get Followers | This block retrieves followers of a specified Twitter user |
| Twitter Get Following | This block retrieves the users that a specified Twitter user is following |
| Twitter Get Home Timeline | This block retrieves the authenticated user's home timeline |
| Twitter Get Liked Tweets | This block gets information about tweets liked by a user |
| Twitter Get Liking Users | This block gets information about users who liked a tweet |
| Twitter Get List | This block retrieves information about a specified Twitter List |
| Twitter Get List Members | This block retrieves the members of a specified Twitter List |
| Twitter Get List Memberships | This block retrieves all Lists that a specified user is a member of |
| Twitter Get List Tweets | This block retrieves tweets from a specified Twitter list |
| Twitter Get Muted Users | This block gets a list of users muted by the authenticating user |
| Twitter Get Owned Lists | This block retrieves all Lists owned by a specified Twitter user |
| Twitter Get Pinned Lists | This block returns the Lists pinned by the authenticated user |
| Twitter Get Quote Tweets | This block gets quote tweets for a specific tweet |
| Twitter Get Retweeters | This block gets information about who has retweeted a tweet |
| Twitter Get Space Buyers | This block retrieves a list of users who purchased tickets to a Twitter Space |
| Twitter Get Space By Id | This block retrieves information about a single Twitter Space |
| Twitter Get Space Tweets | This block retrieves tweets shared in a Twitter Space |
| Twitter Get Spaces | This block retrieves information about multiple Twitter Spaces |
| Twitter Get Tweet | This block retrieves information about a specific Tweet |
| Twitter Get Tweets | This block retrieves information about multiple Tweets |
| Twitter Get User | This block retrieves information about a specified Twitter user |
| Twitter Get User Mentions | This block retrieves Tweets mentioning a specific user |
| Twitter Get User Tweets | This block retrieves Tweets composed by a single user |
| Twitter Get Users | This block retrieves information about multiple Twitter users |
| Twitter Hide Reply | This block hides a reply to a tweet |
| Twitter Like Tweet | This block likes a tweet |
| Twitter Mute User | This block mutes a specified Twitter user |
| Twitter Pin List | This block allows the authenticated user to pin a specified List |
| Twitter Post Tweet | This block posts a tweet on Twitter |
| Twitter Remove Bookmark Tweet | This block removes a bookmark from a tweet on Twitter |
| Twitter Remove List Member | This block removes a specified user from a Twitter List owned by the authenticated user |
| Twitter Remove Retweet | This block removes a retweet on Twitter |
| Twitter Retweet | This block retweets a tweet on Twitter |
| Twitter Search Recent Tweets | This block searches all public Tweets in Twitter history |
| Twitter Search Spaces | This block searches for Twitter Spaces based on specified terms |
| Twitter Unfollow List | This block unfollows a specified Twitter list for the authenticated user |
| Twitter Unfollow User | This block unfollows a specified Twitter user |
| Twitter Unhide Reply | This block unhides a reply to a tweet |
| Twitter Unlike Tweet | This block unlikes a tweet |
| Twitter Unmute User | This block unmutes a specified Twitter user |
| Twitter Unpin List | This block allows the authenticated user to unpin a specified List |
| Twitter Update List | This block updates a specified Twitter List owned by the authenticated user |
| Unban Subreddit User | Unbans a user from a subreddit |
Communication
| Block Name | Description |
|---|---|
| Agent Mail Create Draft | Create a draft email for review or scheduled sending |
| Agent Mail Create Inbox | Create a new email inbox for an AI agent via AgentMail |
| Agent Mail Create List Entry | Add an email address or domain to an allow/block list |
| Agent Mail Create Pod | Create a new pod for multi-tenant customer isolation |
| Agent Mail Create Pod Inbox | Create a new email inbox within a pod |
| Agent Mail Delete Draft | Delete a draft or cancel a scheduled email |
| Agent Mail Delete Inbox | Permanently delete an AgentMail inbox and all its messages, threads, and drafts |
| Agent Mail Delete Inbox Thread | Permanently delete a conversation thread and all its messages |
| Agent Mail Delete List Entry | Remove an email address or domain from an allow/block list to stop filtering it |
| Agent Mail Delete Pod | Permanently delete a pod |
| Agent Mail Forward Message | Forward an email message to one or more recipients |
| Agent Mail Get Draft | Retrieve a draft email to review its contents, recipients, and scheduled send status |
| Agent Mail Get Inbox | Retrieve details of an existing AgentMail inbox including its email address, display name, and configuration |
| Agent Mail Get Inbox Thread | Retrieve a conversation thread with all its messages |
| Agent Mail Get List Entry | Check if an email address or domain is in an allow/block list |
| Agent Mail Get Message | Retrieve a specific email message by ID |
| Agent Mail Get Message Attachment | Download a file attachment from an email message |
| Agent Mail Get Org Thread | Retrieve a conversation thread by ID from anywhere in the organization, without needing the inbox ID |
| Agent Mail Get Pod | Retrieve details of an existing pod including its client_id mapping and metadata |
| Agent Mail Get Thread Attachment | Download a file attachment from a conversation thread |
| Agent Mail List Drafts | List drafts in an AgentMail inbox |
| Agent Mail List Entries | List all entries in an AgentMail allow/block list |
| Agent Mail List Inbox Threads | List all conversation threads in an AgentMail inbox |
| Agent Mail List Inboxes | List all email inboxes in your AgentMail organization with pagination support |
| Agent Mail List Messages | List messages in an AgentMail inbox |
| Agent Mail List Org Drafts | List all drafts across every inbox in your organization |
| Agent Mail List Org Threads | List threads across ALL inboxes in your organization |
| Agent Mail List Pod Drafts | List all drafts across all inboxes within a pod |
| Agent Mail List Pod Inboxes | List all inboxes within a pod |
| Agent Mail List Pod Threads | List all conversation threads across all inboxes within a pod |
| Agent Mail List Pods | List all tenant pods in your organization |
| Agent Mail Reply To Message | Reply to an existing email in the same conversation thread |
| Agent Mail Send Draft | Send a draft immediately, converting it into a delivered message |
| Agent Mail Send Message | Send a new email from an AgentMail inbox |
| Agent Mail Update Draft | Update a draft's content, recipients, or scheduled send time |
| Agent Mail Update Inbox | Update the display name of an AgentMail inbox |
| Agent Mail Update Message | Add or remove labels on an email message |
| Baas Bot Join Meeting | Deploy a bot to join and record a meeting |
| Baas Bot Leave Meeting | Remove a bot from an ongoing meeting |
| Gmail Add Label | A block that adds a label to a specific email message in Gmail, creating the label if it doesn't exist |
| Gmail Create Draft | Create draft emails in Gmail with automatic HTML detection and proper text formatting |
| Gmail Draft Reply | Create draft replies to Gmail threads with automatic HTML detection and proper text formatting |
| Gmail Forward | Forward Gmail messages to other recipients with automatic HTML detection and proper formatting |
| Gmail Get Profile | Get the authenticated user's Gmail profile details including email address and message statistics |
| Gmail Get Thread | A block that retrieves an entire Gmail thread (email conversation) by ID, returning all messages with decoded bodies for reading complete conversations |
| Gmail List Labels | A block that retrieves all labels (categories) from a Gmail account for organizing and categorizing emails |
| Gmail Read | A block that retrieves and reads emails from a Gmail account based on search criteria, returning detailed message information including subject, sender, body, and attachments |
| Gmail Remove Label | A block that removes a label from a specific email message in a Gmail account |
| Gmail Reply | Reply to Gmail threads with automatic HTML detection and proper text formatting |
| Gmail Send | Send emails via Gmail with automatic HTML detection and proper text formatting |
| Hub Spot Engagement | Manages HubSpot engagements - sends emails and tracks engagement metrics |
Developer Tools
| Block Name | Description |
|---|---|
| AllQuiet Create Incident | Creates an incident in All Quiet and pages the on-call responder |
| AllQuiet Get Incident | Fetches a single All Quiet incident by ID |
| AllQuiet Get On Call | Looks up who is on call in All Quiet, now or at a given time |
| AllQuiet Incident Trigger | Triggers a graph when All Quiet posts an incident to this webhook |
| AllQuiet List Incidents | Searches All Quiet incidents by status, severity, team or text |
| AllQuiet List Teams | Lists All Quiet teams and their IDs |
| AllQuiet Update Incident | Investigates, resolves, escalates or comments on an All Quiet incident |
| Conductor Create Session | Start a new agent session (chat) in an existing Conductor workspace, optionally with a first prompt, and optionally wait for the agent's reply |
| Conductor Create Workspace | Create a Conductor cloud workspace for a project or repository, optionally start its agent with a prompt and wait for the reply |
| Conductor Get Account | Get an overview of your Conductor account in one call: who you are, the projects (repositories) you can open workspaces in, your sections and your routines |
| Conductor Get Session | Get a Conductor agent session: its details, whether the agent is idle, working or errored, and recent transcript messages |
| Conductor Get Workspace | Get everything about one Conductor workspace: details, current status, shared preview URL and its agent sessions |
| Conductor List Workspaces | List Conductor workspaces, optionally filtered by project, state, name, repository, creator or activity date |
| Conductor Manage Routine | Create a Conductor routine (a saved prompt that runs a fresh agent in a project whenever its webhook URL is called) or rotate a routine's webhook secret |
| Conductor Manage Section | Create or delete a Conductor cloud section |
| Conductor Manage Session | Rename, cancel or archive a Conductor agent session |
| Conductor Manage Workspace | Change a Conductor workspace: rename it, archive, unarchive or sleep it, share or stop sharing a port at its public preview URL, or move it into a section |
| Conductor Send Message | Send a prompt to a Conductor agent session and, by default, wait for the agent to finish and return its reply |
| Exa Code Context | Search billions of GitHub repos, docs, and Stack Overflow for relevant code examples |
| Execute Code | Executes code in a sandbox environment with internet access |
| Execute Code Step | Execute code in a previously instantiated sandbox |
| Github Add Label | A block that adds a label to a GitHub issue or pull request for categorization and organization |
| Github Assign Issue | A block that assigns a GitHub user to an issue for task ownership and tracking |
| Github Assign PR Reviewer | This block assigns a reviewer to a specified GitHub pull request |
| Github Comment | A block that posts comments on GitHub issues or pull requests using the GitHub API |
| Github Compare Branches | This block compares two branches or commits in a GitHub repository |
| Github Create Check Run | Creates a new check run for a specific commit in a GitHub repository |
| Github Create Comment Object | Creates a comment object for use with GitHub blocks |
| Github Create File | This block creates a new file in a GitHub repository |
| Github Create PR Review | This block creates a review on a GitHub pull request with optional inline comments |
| Github Create Repository | This block creates a new GitHub repository |
| Github Create Status | Creates a new commit status in a GitHub repository |
| Github Delete Branch | This block deletes a specified branch |
| Github Discussion Trigger | This block triggers on GitHub Discussions events |
| Github Fork Repository | This block forks a GitHub repository to your account or an organization |
| Github Get CI Results | This block gets CI results for a commit or PR, with optional search for specific errors/warnings in logs |
| Github Get Notification Thread | This block fetches a single GitHub notification thread |
| Github Get PR Review Comments | This block gets all review comments from a GitHub pull request or from a specific review |
| Github Get Repository Info | This block retrieves metadata about a GitHub repository |
| Github Get Repository Traffic | Get a GitHub repository's views and clones over the last 14 days, with unique visitors and cloners, plus its top referring sites and most viewed pages |
| Github Get Repository Tree | This block lists the entire file tree of a GitHub repository recursively |
| Github Get User Info | This block fetches information about a GitHub user, or about the authenticated user (yourself) if no username is given |
| Github Issues Trigger | This block triggers on GitHub issues events |
| Github List Branches | This block lists all branches for a specified GitHub repository |
| Github List Comments | A block that retrieves all comments from a GitHub issue or pull request, including comment metadata and content |
| Github List Commits | This block lists commits on a branch in a GitHub repository |
| Github List Discussions | This block lists recent discussions for a specified GitHub repository |
| Github List Issues | A block that retrieves a list of issues from a GitHub repository with their titles and URLs |
| Github List Notifications | This block lists GitHub notifications for the authenticated user, e |
| Github List PR Reviewers | This block lists the requested reviewers for a specified GitHub pull request, optionally including users who have already submitted a review |
| Github List PR Reviews | This block lists all reviews for a specified GitHub pull request |
| Github List Pull Requests | This block lists all pull requests for a specified GitHub repository |
| Github List Releases | This block lists releases for a specified GitHub repository, optionally filtered by type and date range |
| Github List Stargazers | This block lists all users who have starred a specified GitHub repository |
| Github List Tags | This block lists all tags for a specified GitHub repository |
| Github Make Branch | This block creates a new branch from a specified source branch |
| Github Make Issue | A block that creates new issues on GitHub repositories with a title and body content |
| Github Make Pull Request | This block creates a new pull request on a specified GitHub repository |
| Github Mark Notification Thread As Done | This block marks a GitHub notification thread as done, removing it from the notification inbox |
| Github Mark Notification Thread As Read | This block marks a single GitHub notification thread as read |
| Github Mark Notifications As Read | This block marks all GitHub notifications as read, optionally scoped to a single repository |
| Github Merge Pull Request | This block merges a pull request using merge, squash, or rebase |
| Github Multi File Commit | This block creates a single commit with multiple file upsert/delete operations using the Git Trees API |
| Github Pull Request Trigger | This block triggers on pull request events and outputs the event type and payload |
| Github Read File | This block reads the content of a specified file from a GitHub repository |
| Github Read Folder | This block reads the content of a specified folder from a GitHub repository |
| Github Read Issue | A block that retrieves information about a specific GitHub issue, including its title, body content, and creator |
| Github Read Pull Request | This block reads the body, title, user, changes, and full raw object of a specified GitHub pull request |
| Github Release Trigger | This block triggers on GitHub release events |
| Github Remove Label | A block that removes a label from a GitHub issue or pull request |
| Github Resolve Review Discussion | This block resolves or unresolves a review discussion thread on a GitHub pull request |
| Github Search Code | This block searches for code in GitHub repositories |
| Github Star Repository | This block stars a GitHub repository |
| Github Star Trigger | This block triggers on GitHub star events |
| Github Submit Pending Review | This block submits a pending (draft) review on a GitHub pull request |
| Github Unassign Issue | A block that removes a user's assignment from a GitHub issue |
| Github Unassign PR Reviewer | This block unassigns a reviewer from a specified GitHub pull request |
| Github Unsubscribe Notification Thread | This block unsubscribes you from a GitHub notification thread, muting future notifications unless you are mentioned again |
| Github Update Check Run | Updates an existing check run in a GitHub repository |
| Github Update Comment | A block that updates an existing comment on a GitHub issue or pull request |
| Github Update File | This block updates an existing file in a GitHub repository |
| Instantiate Code Sandbox | Instantiate a sandbox environment with internet access in which you can execute code with the Execute Code Step block |
| MCP Tool | Connect to any MCP server and execute its tools |
| Slant3D Order Webhook | This block triggers on Slant3D order status updates and outputs the event details, including tracking information when orders are shipped |
Media Generation
| Block Name | Description |
|---|---|
| Add Audio To Video | Block to attach an audio file to a video file using moviepy |
| Loop Video | Block to loop a video to a given duration or number of repeats |
| Media Duration | Block to get the duration of a media file |
| Video Clip | Extract a time segment from a video |
| Video Concat | Merge multiple video clips into one continuous video |
| Video Download | Download video from URL (YouTube, Vimeo, news sites, direct links) |
| Video Text Overlay | Add text overlay/caption to video |
Productivity
| Block Name | Description |
|---|---|
| Google Calendar Create Event | This block creates a new event in Google Calendar with customizable parameters |
| Notion Create Page | Create a new page in Notion |
| Notion Read Database | Query a Notion database with optional filtering and sorting, returning structured entries |
| Notion Read Page | Read a Notion page by its ID and return its raw JSON |
| Notion Read Page Markdown | Read a Notion page and convert it to Markdown format with proper formatting for headings, lists, links, and rich text |
| Notion Search | Search your Notion workspace for pages and databases by text query |
| Todoist Close Task | Closes a task in Todoist |
| Todoist Create Comment | Creates a new comment on a Todoist task or project |
| Todoist Create Label | Creates a new label in Todoist, It will not work if same name already exists |
| Todoist Create Project | Creates a new project in Todoist |
| Todoist Create Task | Creates a new task in a Todoist project |
| Todoist Delete Comment | Deletes a Todoist comment |
| Todoist Delete Label | Deletes a personal label in Todoist |
| Todoist Delete Project | Deletes a Todoist project and all its contents |
| Todoist Delete Section | Deletes a section and all its tasks from Todoist |
| Todoist Delete Task | Deletes a task in Todoist |
| Todoist Get Comment | Get a single comment from Todoist |
| Todoist Get Comments | Get all comments for a Todoist task or project |
| Todoist Get Label | Gets a personal label from Todoist by ID |
| Todoist Get Project | Gets details for a specific Todoist project |
| Todoist Get Section | Gets a single section by ID from Todoist |
| Todoist Get Shared Labels | Gets all shared labels from Todoist |
| Todoist Get Task | Get an active task from Todoist |
| Todoist Get Tasks | Get active tasks from Todoist |
| Todoist List Collaborators | Gets all collaborators for a specific Todoist project |
| Todoist List Labels | Gets all personal labels from Todoist |
| Todoist List Projects | Gets all projects and their details from Todoist |
| Todoist List Sections | Gets all sections and their details from Todoist |
| Todoist Remove Shared Labels | Removes all instances of a shared label |
| Todoist Rename Shared Labels | Renames all instances of a shared label |
| Todoist Reopen Task | Reopens a task in Todoist |
| Todoist Update Comment | Updates a Todoist comment |
| Todoist Update Label | Updates a personal label in Todoist |
| Todoist Update Project | Updates an existing project in Todoist |
| Todoist Update Task | Updates an existing task in Todoist |
Logic and Control Flow
| Block Name | Description |
|---|---|
| Calculator | Performs a mathematical operation on two numbers |
| Condition | Handles conditional logic based on comparison operators |
| Count Items | Counts the number of items in a collection |
| Data Sampling | This block samples data from a given dataset using various sampling methods |
| Exa Webset Ready Check | Check if webset is ready for next operation - enables conditional workflow branching |
| If Input Matches | Handles conditional logic based on comparison operators |
| Pinecone Init | Initializes a Pinecone index |
| Pinecone Insert | Upload data to a Pinecone index |
| Pinecone Query | Queries a Pinecone index |
| Step Through Items | Iterates over a list or dictionary and outputs each item |
Input/Output
| Block Name | Description |
|---|---|
| Exa Webset Webhook | Receive webhook notifications for Exa webset events |
| Generic Webhook Trigger | This block will output the contents of the generic input for the webhook |
| Read RSS Feed | Reads RSS feed entries from a given URL |
| Send Authenticated Web Request | Make an authenticated HTTP request with host-scoped credentials (JSON / form / multipart) |
| Send Email | This block sends an email using the provided SMTP credentials |
| Send Web Request | Make an HTTP request (JSON / form / multipart) |
Agent Integration
| Block Name | Description |
|---|---|
| Agent Executor | Executes an existing agent inside your agent |
| AutoPilot | Execute tasks using AutoGPT AutoPilot with full access to platform tools (agent management, workspace files, web fetch, block execution, and more) |
CRM Services
| Block Name | Description |
|---|---|
| Add Lead To Campaign | Add a lead to a campaign in SmartLead |
| Company Enrichment | Look up and enrich a company using DataForB2B's B2B database — firmographics, headcount/size, industry, domain and social profiles from a company domain, name or LinkedIn URL |
| Company Search | Search companies and accounts by structured filters — industry, headcount/size, location, funding, keywords — using DataForB2B's database |
| Create Campaign | Create a campaign in SmartLead |
| Hub Spot Company | Manages HubSpot companies - create, update, and retrieve company information |
| Hub Spot Contact | Manages HubSpot contacts - create, update, and retrieve contact information |
| People Search | Search people and B2B leads by structured filters — job title, company, location, industry, seniority, skills — using DataForB2B's database |
| Profile Enrichment | Look up and enrich a professional profile from a LinkedIn URL using DataForB2B's B2B database — returns the full profile (current role, experience and skills) plus work email, personal email and GitHub |
| Save Campaign Sequences | Save sequences within a campaign |
AI Safety
| Block Name | Description |
|---|---|
| Nvidia Deepfake Detect | Detects potential deepfakes in images using Nvidia's AI API |
Issue Tracking
| Block Name | Description |
|---|---|
| Linear Create Comment | Creates a new comment on a Linear issue |
| Linear Create Issue | Creates a new issue on Linear |
| Linear Get Project Issues | Gets issues from a Linear project filtered by status and assignee |
| Linear Search Issues | Searches for issues on Linear |
| Linear Search Projects | Searches for projects on Linear |
Hardware
| Block Name | Description |
|---|---|
| Compass AI Trigger | This block will output the contents of the compass transcription |
| RMFG Event Trigger | Triggers when an RMFG design, quote, cart or order changes |
| RMFG Get Image | Downloads RMFG's rendered picture of a design or part, with holes and bends labelled |
Platform Getting Started (`docs/platform/getting-started.md`)
Getting Started with AutoGPT: Self-Hosting Guide
Introduction
This guide will help you setup the server and builder for the project.
!!! warning
DO NOT FOLLOW ANY OUTSIDE TUTORIALS AS THEY WILL LIKELY BE OUT OF DATE
Prerequisites
The single-container appliance's only product prerequisite is an installed,
running Docker CLI and daemon. Select a local Docker endpoint using Linux
containers on amd64 or arm64. The Unix bootstrap also uses Bash, curl, andsha256sum or shasum. Docker Compose, Git, Node.js, and NPM are not required
for the appliance installer.
Install Docker from the official Docker documentation,
start it, then verify the selected daemon:
docker -v
docker infoQuick Setup with the Appliance Installer
The release installer pulls and starts the published single-container
appliance: one Docker container, one loopback port, no source checkout. It
needs a running Docker daemon with Linux containers on amd64 or arm64; it
does not install Docker or build AutoGPT from source. See
the installer reference for details.
The hosted installer is not live yet: setup.agpt.co/install.sh still serves
the Compose installer, and the appliance image tags are not public until the
release gates pass. This first release
supports Linux and macOS. Until then, and on Windows, use the
from-source setup below, which is also the path that supports
a fully offline install with a local LLM.
Manual Setup
Development prerequisites
The manual source checkout requires
Git,
Node.js and NPM, Docker, and
Docker Compose. Verify them before
continuing:
git --version
node -v
npm -v
docker -v
docker compose versionCloning the Repository
The first step is cloning the AutoGPT repository to your computer.
To do this, open a terminal window in a folder on your computer and run:
git clone https://github.com/Significant-Gravitas/AutoGPT.gitIf you get stuck, follow this guide.
Once that's complete you can continue the setup process.
Running the AutoGPT Platform
To run the platform, follow these steps:
- Navigate to the
autogpt_platformdirectory inside the AutoGPT folder:bashcd AutoGPT/autogpt_platform
Create the
.envfiles and generate your local secrets:textmake init-envThis copies each
.env.defaultto.env(forautogpt_platform,backend
andfrontend) and fills in the secrets that.env.defaultdeliberately
leaves blank —ENCRYPTION_KEY,UNSUBSCRIBE_SECRET_KEYandBETTER_AUTH_SECRET— with values generated for your machine. Those files
are public, so shipping working values in them would mean every install in
the world shared one publicly-readable key. It is safe to re-run: it never
overwrites an existing.envor a value you set yourself. You can then edit
the.envfiles to add your own environment variables.The backend refuses to start while
ENCRYPTION_KEYis empty, so run this
before bringing the stack up.Run the platform services:
textdocker compose up -d --buildThis command will start all the necessary backend services defined in the
docker-compose.ymlfile in detached mode.
🛠️ Using the Makefile for Common Tasks
The repository includes a Makefile with helpful commands to streamline setup and development. You may use make commands as an alternative to calling Docker or scripts directly.
Most-used Makefile commands
Inside the autogpt_platform directory, you can use:
| Command | What it Does |
|---|---|
make init-env |
Create missing .env files from .env.default (autogpt_platform, backend, and frontend) and generate the secrets they leave blank |
make start-core |
Start just the core services (Postgres, Redis, RabbitMQ) in background |
make stop-core |
Stop the core services |
make logs-core |
Tail the logs for core services |
make format |
Format & lint backend (Python) and frontend (TypeScript) code |
make migrate |
Run backend database migrations |
make publish-skills |
Publish the skills catalog (marketplace skills and the expert roster) into your database; run make load-store-agents first if you want the experts' preload workflows too |
make run-backend |
Run the backend FastAPI server |
make run-frontend |
Run the frontend Next.js development server |
Example usage:
make init-env
make start-core
make migrate
make publish-skills
make run-backend
make run-frontenddocker compose up runs the migrations and the skills catalog publish for you: the publish_skills service downloads the public Significant-Gravitas/skills-catalog repository after migrate finishes, so the Skills Hub and the roster experts are there on first start. It needs network access to GitHub; offline, point SKILLS_CATALOG_PATH in backend/.env at a local checkout of that repository.
make init-envmatters when running the frontend outside Docker: Next.js
only reads.env(not.env.default), and the frontend's embedded auth
service needsDATABASE_URLandBETTER_AUTH_SECRETfrom it.
You can always check available Makefile recipes by running:
make help(or just inspecting the Makefile in the repo root).
Checking if the application is running
You can check if the server is running by visiting http://localhost:3000 in your browser.
Notes:
By default the application for different services run on the following ports:
Frontend UI Server: 3000
Backend Websocket Server: 8001
Execution API Rest Server: 8006
Upgrading: secrets are generated per install
ENCRYPTION_KEY, UNSUBSCRIBE_SECRET_KEY and BETTER_AUTH_SECRET used to
come with a value in .env.default, so every install that did not set its own
ran on the same three values. They are now blank there and generated for each
install, and the backend does not start without an ENCRYPTION_KEY or
with the one .env.default used to contain.
A fresh install needs nothing beyond make init-env (or the installer script,
which does the same). An install that already set its own values needs
nothing either. Follow the steps below if you are upgrading an install that
- has no
autogpt_platform/backend/.env, or one without anENCRYPTION_KEY
line — it was running on the value from.env.default; or - stops on startup with
ENCRYPTION_KEY is set to a value that was published…
orENCRYPTION_KEY is not set.
Your connected integrations are encrypted with ENCRYPTION_KEY, so the steps
move them to the new key instead of losing them. Run everything fromautogpt_platform/.
Stop the stack:
bashdocker compose downKeep the key your data is currently encrypted with. If you never set one,
it is the value.env.defaultcontained up to releasev0.7.4:bashexport OLD_ENCRYPTION_KEY=$(git show autogpt-platform-beta-v0.7.4:autogpt_platform/backend/.env.default \ | grep '^ENCRYPTION_KEY=' | cut -d= -f2-)If you did set one and are replacing it, export that value instead, and keep
a copy of the file outside the checkout until step 4 reports nothing
unreadable, because step 3 removes the only other place the key is written
down:bashcp backend/.env ~/autogpt-backend.env.$(date +%Y%m%d-%H%M%S)Generate the new values.
make init-envcreates any missing.envfile
and fills in every secret whose line is present but empty; it never
overwrites a value. So inbackend/.envmake sure these two lines exist
with nothing after the=, and do the same forBETTER_AUTH_SECRET=infrontend/.env:textENCRYPTION_KEY= UNSUBSCRIBE_SECRET_KEY=Then:
bashmake init-envWithout
make, these are the same steps by hand. Copy a.env.default
only where no.envexists yet:bashcp -n .env.default .env cp -n backend/.env.default backend/.env cp -n frontend/.env.default frontend/.env python3 single-container/runtime_config.py fill-env --path .env python3 single-container/runtime_config.py fill-env --path backend/.env python3 single-container/runtime_config.py fill-env --path frontend/.envDo not re-run the installer script for this: it also starts the stack,
which is step 5.Re-encrypt what is stored. Build the new images and bring the database up
to date first;migratestarts the database on its own:bashdocker compose build migrate rest_server docker compose run --rm migrateThen run the command, first as a dry run that only reports what it would
change, then with--applyto write it.--no-depskeeps it from waiting
on the rest of the stack, which it does not need:bashdocker compose run --rm --no-deps -e OLD_ENCRYPTION_KEY rest_server cli rotate-encryption-key docker compose run --rm --no-deps -e OLD_ENCRYPTION_KEY rest_server cli rotate-encryption-key --applyIt is safe to run more than once: values already under the new key are left
alone, and a value that neither key can read is listed and not touched.
Running the backend outside Docker, the same command ispoetry run cli rotate-encryption-keyinautogpt_platform/backend.Start the stack again. If
BETTER_AUTH_SECRETchanged in step 3, first
clear the token signing key the frontend stored under the old value: it can
no longer be decrypted, and until it is removed nobody can reach the
backend, even after signing in again. A new one is created on the next
sign-in.--buildbrings the remaining services to the release you built
in step 4:bashdocker compose up -d --wait db docker compose exec db psql -U postgres -c 'DELETE FROM platform."UserAuthJwks";' docker compose up -d --build
Two smaller effects of the new values: unsubscribe links in emails sent before
the upgrade stop working (UNSUBSCRIBE_SECRET_KEY), and everyone signs in
again once (BETTER_AUTH_SECRET).
Do step 4 before step 5. On a new key the stored values are still in the
database but read as empty, and a user who connects an integration in that
state replaces their stored set: their other credentials are marked revoked.
If the stack already ran on the new key, stop it and run step 4 now. Whatever
nobody touched is recovered; a user who connected something in between gets
their older credentials re-encrypted but still revoked, and reconnects those.
Upgrading an existing (Supabase-based) installation
Older versions of the platform ran authentication on a bundled Supabase
stack. If you self-hosted before the switch to the built-in auth service,
three things changed:
Environment files: refresh your
.envfiles against the new.env.defaults.make init-envcopies.env.default→.envforautogpt_platform,backendandfrontend, but only where no.env
exists yet (it usescp -n): it creates missing.envfiles and never
overwrites an existing one. It does not merge newly-added variables
into an.envyou already have — for an existing install, diff each.envagainst its.env.defaultand copy the new keys across yourself.
TheSUPABASE_*URL/key variables are gone; the frontend now usesBETTER_AUTH_SECRETandDATABASE_URL.ENCRYPTION_KEY,UNSUBSCRIBE_SECRET_KEYandBETTER_AUTH_SECRETare no
longer filled in by.env.default: follow
Upgrading: secrets are generated per install
as part of this step.Database location: the database now lives in a plain Postgres
container (pgvector/pgvector:pg15) with its data inautogpt_platform/data/db/data. Your old data is untouched atautogpt_platform/db/docker/volumes/db/databut is no longer mounted.If you already booted the new stack while that folder was still called
volumes/, move your data across before starting it again:shmkdir -p autogpt_platform/data/db mv autogpt_platform/volumes/db/data autogpt_platform/data/db/dataTo carry the old Supabase data over, pick one of the two routes below.
Neither has been validated against a real old volume yet, so back upautogpt_platform/db/docker/volumes/db/databefore you start.The old bundled stack ran
supabase/postgres:15.8.1.049and the newdb
service runspgvector/pgvector:pg15— the same Postgres major, so
reusing the data directory as-is is plausible rather than impossible. It
is not guaranteed: a data directory is only portable between servers on
the same major and with a compatible extension set /shared_preload_libraries. The Supabase image ships extensions and roles
(supabase_admin,pgjwt,pgsodium,pg_graphql, …) that the plain
pgvector image does not have, so a moved directory can fail to start, or
start and then fail on objects that reference the missing extensions.Fast path — reuse the data directory:
shcd autogpt_platform docker compose down mkdir -p data/db rm -rf data/db/data # discards a freshly-initialised new DB cp -a db/docker/volumes/db/data data/db/data # copy, so the old dir stays intact docker compose up -d db docker compose logs -f dbOn Linux the data directory is mode
0700owned by the container'spostgresuser, so the copy needssudo cp -a(the plain Postgres
entrypoint fixes ownership on first boot). On Docker Desktop for
macOS/Windows the plaincp -ais enough.
It worked if the log settles ondatabase system is ready to accept connectionsand your data is there:shdocker compose exec db psql -U postgres -c '\dn' docker compose exec db psql -U postgres -c 'select count(*) from platform."User"'It did not work if the container restart-loops with errors such as
could not open configuration file,could not access file "$libdir/…",unrecognized configuration parameter,extension "…" is not available,data directory … has wrong ownership, orPermission denied— Postgres
is either missing something the Supabase image provided, or can't read the
copied files. In that caserm -rf data/db/dataand use the fallback.Fallback — same-major dump and restore:
Step 1 starts a real Postgres server against your original data
directory, read-write. Make sure you took the backup above first.shcd autogpt_platform # 1. Bring the OLD image up against the OLD data directory, on a spare port. docker run --rm -d --name old-db -p 5433:5432 \ -e POSTGRES_PASSWORD=your-super-secret-and-long-postgres-password \ -v "$(pwd)/db/docker/volumes/db/data:/var/lib/postgresql/data" \ supabase/postgres:15.8.1.049 # 2. Dump without Supabase-owned ownership/ACL metadata. docker exec old-db pg_dump -U postgres -d postgres \ --no-owner --no-privileges -Fc -f /tmp/old.dump docker cp old-db:/tmp/old.dump ./old.dump docker stop old-db # 3. Restore into the new db service (fresh volume). docker compose up -d db # A fresh volume runs db/init/00-init.sql, which creates an EMPTY auth.users # shim with only the columns the migrations need. Drop it first, or the # restore of your real auth.users collides with it and copies no users. docker compose exec db psql -U postgres -c 'DROP SCHEMA IF EXISTS auth CASCADE;' docker compose cp ./old.dump db:/tmp/old.dump docker compose exec db pg_restore -U postgres -d postgres \ --no-owner --no-privileges /tmp/old.dump # 4. Confirm your accounts actually landed BEFORE migrating. docker compose exec db psql -U postgres -c 'select count(*) from auth.users'pg_restorereports errors for objects belonging to Supabase-only
extensions and roles (storage,realtime,supabase_admin,pgsodium,
…). Those are harmless. An error onauth.usersis not: that table is
where your accounts live, and the migration in step 3 below copies them out
of it. If the count above is0— orpg_restorefailed onauth.users—
stop and fix the restore before continuing, or you will bring the stack up
with no user accounts.Either way, finish with the migrations before bringing up the rest:
shdocker compose run --rm migrate docker compose up -dUser accounts and sessions: a normal upgrade (stack stopped, then
restarted on the new version) needs no extra step here.- Existing users are copied from the Supabase
auth.userstable into the
Better Auth tables by the backend Prisma migration20260716120000_copy_supabase_users_to_better_auth, which runs as part
of thedocker compose run --rm migratestep above. - Existing browser sessions keep working because the frontend recognises
old Supabase JWT cookies and swaps them for a Better Auth session on
the user's next visit. KeepSUPABASE_JWT_SECRETset infrontend/.envfor as long as you want that bridge open. frontend/scripts/migrate-supabase-auth.tsis optional and only
applies to a live cutover, where Supabase kept accepting signups
while the new stack was already running. It is a re-runnable sweep for
those stragglers; if you stopped the stack to upgrade, skip it.shcd frontend && DATABASE_URL=postgresql://postgres:<password>@localhost:5432/postgres npx tsx scripts/migrate-supabase-auth.ts
- Existing users are copied from the Supabase
A fresh install (empty database) needs none of this.
Additional Notes
make init-env already generates a unique ENCRYPTION_KEY for your install, so
there is normally nothing to change here. To rotate it — for example if you
carried a key over from an older checkout, back when .env.default shipped a
working (and therefore public) one — generate a new key in python:
from cryptography.fernet import Fernet;Fernet.generate_key().decode()Or run the following command in the autogpt_platform/backend directory:
poetry run cli gen-encrypt-keyThen replace the value in autogpt_platform/backend/.env and re-encrypt the
stored integration credentials under it, with the previous value asOLD_ENCRYPTION_KEY — steps 4 and 5 of
Upgrading: secrets are generated per install.
Without that step the credentials stored under the previous key are unreadable
and those integrations need reconnecting.
Auth transport security (JWKS over untrusted networks)
The backend verifies login tokens using signing keys it fetches from the frontend at JWT_JWKS_URL (.../api/auth/jwks). It trusts whatever keys that URL returns, so the fetch must run over a trusted path:
- Plain
httpis fine forlocalhostand for container-to-container traffic on a single host (the defaulthttp://frontend:3000over the Docker network) — there is no network segment for an attacker to sit on. - Use
httpson an untrusted network. If you split the backend and frontend across separate machines on a LAN, or expose them publicly, a cleartext JWKS fetch can be intercepted: an attacker who swaps the published keys can forge tokens for any user. Put the frontend behind TLS (a reverse proxy), or issue locally-trusted certificates (e.g. mkcert), and pointJWT_JWKS_URLat thehttps://URL.
The backend refuses to start if JWT_JWKS_URL is a cleartext http:// URL pointing at a non-local host. If your network path is trusted (e.g. an isolated private LAN), set JWKS_ALLOW_INSECURE_TRANSPORT=true to boot anyway — a startup warning stays on record so the tradeoff is visible in logs.
This is a property of stateless JWT/JWKS verification in general, not something specific to AutoGPT. On a standard single-host Docker install you don't need to change anything.
📌 Windows Installation Note
When installing Docker on Windows, it is highly recommended to select WSL 2 instead of Hyper-V. Using Hyper-V can cause compatibility issues with the platform's containers, leading to the db (Postgres) container being marked as unhealthy.
**Steps to enable WSL 2 for Docker:**
- Install WSL 2.
- Ensure that your Docker settings use WSL 2 as the default backend:
- Open Docker Desktop.
- Navigate to Settings > General.
- Check Use the WSL 2 based engine.
- Restart Docker Desktop.
**Already Installed Docker with Hyper-V?**
If you initially installed Docker with Hyper-V, you don’t need to reinstall it. You can switch to WSL 2 by following these steps:
- Open Docker Desktop.
- Go to Settings > General.
- Enable Use the WSL 2 based engine.
- Restart Docker.
🚨 Warning: Enabling WSL 2 may erase your existing containers and build history. If you have important containers, consider backing them up before switching.
For more details, refer to Docker's official documentation.
⚠️ Podman Not Supported
AutoGPT requires Docker (Docker Desktop or Docker Engine). Podman and podman-compose are not supported and may cause path resolution issues, particularly on Windows.
If you see errors like:
Error: the specified Containerfile or Dockerfile does not exist, ..\..\autogpt_platform\backend\DockerfileThis indicates you're using Podman instead of Docker. Please install Docker Desktop and use docker compose instead of podman-compose.
Development
Frontend Development
Running the frontend locally
To run the frontend locally, you need to have Node.js and PNPM installed on your machine.
Install Node.js to manage dependencies and run the frontend application.
Install PNPM to manage the frontend dependencies.
Run the service dependencies (backend, database, message queues, etc.):
docker compose --profile local up deps_backend --build --detachGo to the autogpt_platform/frontend directory:
cd frontendInstall the dependencies:
pnpm installGenerate the API client:
pnpm generate:api-clientRun the frontend application:
pnpm devFormatting & Linting
Auto formatter and linter are set up in the project. To run them:
Format the code:
pnpm formatLint the code:
pnpm lintOr for both frontend and backend, from the root:
make formatTesting
To run the tests, you can use the following command:
pnpm testBackend Development
Running the backend locally
To run the backend locally, you need to have Python 3.10 or higher installed on your machine.
Install Poetry to manage dependencies and virtual environments.
Run the backend dependencies (database, message queues, etc.):
docker compose --profile local up deps --build --detachOr equivalently with Makefile:
make start-coreGo to the autogpt_platform/backend directory:
cd backendInstall the dependencies:
poetry install --with devRun the backend server:
poetry run appOr from within autogpt_platform:
make run-backendFormatting & Linting
Auto formatter and linter are set up in the project. To run them:
Format the code:
poetry run formatLint the code:
poetry run lintOr format both frontend and backend at once:
make formatTesting
To run the tests:
poetry run pytest -sAdding a New Agent Block
To add a new agent block, you need to create a new class that inherits from Block and provides the following information:
- All the block code should live in the
blocks(backend.blocks) module. input_schema: the schema of the input data, represented by a Pydantic object.output_schema: the schema of the output data, represented by a Pydantic object.runmethod: the main logic of the block.test_input&test_output: the sample input and output data for the block, which will be used to auto-test the block.- You can mock the functions declared in the block using the
test_mockfield for your unit tests. - Once you finish creating the block, you can test it by running
poetry run pytest backend/blocks/test/test_block.py -s. - Create a Pull Request to the
devbranch of the repository with your changes so you can share it with the community :)
Content Classic Index (`docs/content/classic/index.md`)
AutoGPT Agent
🔧 Setup
|
💻 User guide
|
🐙 GitHub
Location: classic/original_autogpt/ in the GitHub repo
Maintance Notice: AutoGPT Classic is not supported from a security perspective.
Dependencies will not be updated, nor will issues be fixed. If someone wishes to
contribute to novel development, we will give best effort merging to the changes that
pass the existing CI.
AutoGPT Classic was conceived when OpenAI published their GPT-4 model accompanied by a paper
outlining the advanced reasoning and task-solving abilities of the model. The concept
was (and still is) fairly simple: let an LLM decide what to do over and over, while
feeding the results of its actions back into the prompt. This allows the program to
iteratively and incrementally work towards its objective.
The fact that this program is able to execute actions on behalf of its user makes
it an agent. In the case of AutoGPT Classic, the user still has to authorize every action,
but as the project progresses we'll be able to give the agent more autonomy and only
require consent for select actions.
AutoGPT Classic is a generalist agent, meaning it is not designed with a specific task in
mind. Instead, it is designed to be able to execute a wide range of tasks across many
disciplines, as long as it can be done on a computer.
AutoGPT Classic Documentation
Welcome to the AutoGPT Classic Documentation.
The AutoGPT Classic project consists of four main components:
- The Agent – also known as just "AutoGPT Classic"
- The Benchmark – AKA
agbenchmark - The Forge
- The Frontend
To tie these together, we also have a CLI at the root of the project.
🤖 Agent
📖 About AutoGPT Classic
|
🔧 Setup
|
💻 Usage
The former heart of AutoGPT, and the project that kicked it all off: a semi-autonomous agent powered by LLMs to execute any task for you*.
We continue to develop this project with the goal of providing access to AI assistance to the masses, and building the future transparently and together.
💡 Explore - See what AI can do and be inspired by a glimpse of the future.
🚀 Build with us - We welcome any input, whether it's code or ideas for new features or improvements! Join us on Discord and find out how you can join in on the action.
If you'd like to see what's next, check out the AutoGPT Platform.
* it isn't quite there yet, but that is the ultimate goal that we are still pursuing
🎯 Benchmark
Measure your agent's performance! The agbenchmark can be used with any agent that supports the agent protocol, and the integration with the project's CLI makes it even easier to use with AutoGPT Classic and forge-based agents. The benchmark offers a stringent testing environment. Our framework allows for autonomous, objective performance evaluations, ensuring your agents are primed for real-world action.
📦
agbenchmarkon Pypi🔌 Agent Protocol Standardization - AutoGPT Classic uses the agent protocol from the AI Engineer Foundation to ensure compatibility with many agents, both from within and outside the project.
🏗️ Forge
Forge your own agent! The Forge is a ready-to-go template for your agent application. All the boilerplate code is already handled, letting you channel all your creativity into the things that set your agent apart.
- 🛠️ Building with Ease - We've set the groundwork so you can focus on your agent's personality and capabilities. Comprehensive tutorials are available here.
💻 Frontend
An easy-to-use and open source frontend for any Agent Protocol-compliant agent.
🎮 User-Friendly Interface - Manage your agents effortlessly.
🔄 Seamless Integration - Smooth connectivity between your agent and our benchmarking system.
🔧 CLI
The project CLI makes it easy to use all of the components of AutoGPT Classic in the repo, separately or
together. To install its dependencies, simply run ./run setup, and you're ready to go!
$ ./run
Usage: cli.py [OPTIONS] COMMAND [ARGS]...
Options:
--help Show this message and exit.
Commands:
agent Commands to create, start and stop agents
benchmark Commands to start the benchmark and list tests and categories
setup Installs dependencies needed for your system.Common commands:
./run agent start autogpt– runs the AutoGPT Classic agent./run agent create <name>– creates a new Forge-based agent project atagents/<name>./run benchmark start <agent>– benchmarks the specified agent
🤔 Join the AutoGPT Discord server for any queries:
discord.gg/autogpt
Glossary of Terms
- Repository: Space where your project resides.
- Forking: Copying a repository under your account.
- Cloning: Making a local copy of a repository.
- Agent: The AutoGPT you'll create and develop.
- Benchmarking: Testing your agent's skills in the Forge.
- Forge: The template for building your AutoGPT agent.
- Frontend: The UI for tasks, logs, and task history.
Content Contribute Index (`docs/content/contribute/index.md`)
Contributing to the Docs
We welcome contributions to our documentation! Our docs are hosted on GitBook and published from GitHub.
How It Works
- Documentation lives in the
docs/directory and changes like any other code: through a pull request todev - The published docs match
master. When a release reachesmaster, itsdocs/directory is copied to thegitbookbranch, and GitBook publishes that - The
gitbookbranch belongs to that copy. Don't open pull requests against it, and don't edit pages in GitBook: the next copy overwrites anything that isn't inmaster
Editing Docs Locally
Clone the repository and create a branch from
dev:shellgit clone https://github.com/Significant-Gravitas/AutoGPT.git cd AutoGPT git checkout -b my-docs-change origin/devMake your changes to markdown files in
docs/Preview changes with any markdown preview tool
Adding a New Page
- Create a new markdown file in the appropriate
docs/subdirectory - Add the new page to the relevant
SUMMARY.mdfile to include it in the navigation - Submit a pull request to the
devbranch
Submitting a Pull Request
When you're ready to submit your changes, create a pull request targeting the dev branch. We will review your changes and merge them if appropriate. They appear on the published docs with the next release.
Platform Changelog README (`docs/platform/changelog/README.md`)
Changelog
What's new in AutoPilot and the AutoGPT Platform. We ship updates regularly and document everything here.
Release windows reflect how changes were grouped for publication and may overlap when work spans consecutive releases.
2026
| Date | Highlights |
|---|---|
| June 18 – July 9 | File folders, shareable chats, Discord bot |
| May 7 – June 10 | AutoPilot upgrades, redesigned login, live subscriptions, rebuilt settings |
| April 10 – May 1 | Artifacts panel, agent briefings, AI chat in builder |
| March 23 – April 9 | Themed prompt categories, live timer stats, redesigned onboarding, copy your prompts |
| March 20 – March 25 | Import workflows from other tools, marketplace UI polish, dry-run mode, parallel AutoPilot actions |
| March 13 – March 20 | Refreshed marketplace, usage monitor, feedback button, personalized prompts |
| March 5 – March 12 | Folders, notifications, cleaner reasoning, inline outputs |
| February 26 – March 4 | Connect any app, share files, browse the web, run code, text-to-speech |
| February 11 – 26 | Telegram bots, agent folders, rebuilt flow editor, major AutoPilot upgrade |
| January 29 – February 11 | Voice input, persistent file workspace, Claude Opus 4.6, marketplace agent customization, rebuilt chat streaming |
Content Classic Setup Index (`docs/content/classic/setup/index.md`)
AutoGPT Agent setup
🐋 Set up & Run with Docker
|
👷🏼 For Developers
📋 Requirements
Linux / macOS
- Python 3.10 or later
- Poetry (instructions)
Windows (WSL)
- WSL 2
- See also the requirements for Linux
- Docker Desktop
Windows
!!! attention
We recommend setting up AutoGPT with WSL. Some things don't work exactly the same on
Windows and we currently can't provide specialized instructions for all those cases.
- Python 3.10 or later (instructions)
- Poetry (instructions)
- Docker Desktop
Setting up AutoGPT
Getting AutoGPT
Since we don't ship AutoGPT as a desktop application, you'll need to download the
project from GitHub and give it a place on your computer.
- To get the latest bleeding edge version, use
master. - If you're looking for more stability, check out the latest AutoGPT release.
!!! note
These instructions don't apply if you're looking to run AutoGPT as a docker image.
Instead, check out the Docker setup guide.
Completing the Setup
Once you have cloned or downloaded the project, you can find the AutoGPT Agent in theoriginal_autogpt/ folder.
Inside this folder you can configure the AutoGPT application with an .env file and (optionally) a JSON configuration file:
.envfor environment variables, which are mostly used for sensitive data like API keys- a JSON configuration file to customize certain features of AutoGPT's Components
See the Configuration reference for a list of available environment variables.
Find the file named
.env.template. This file may
be hidden by default in some operating systems due to the dot prefix. To reveal
hidden files, follow the instructions for your specific operating system:
Windows and macOS.Create a copy of
.env.templateand call it.env;
if you're already in a command prompt/terminal window:shellcp .env.template .envOpen the
.envfile in a text editor.Set API keys for the LLM providers that you want to use: see below.
Enter any other API keys or tokens for services you would like to use.
!!! note
To activate and adjust a setting, remove the#prefix.Save and close the
.envfile.Optional: run
poetry installto install all required dependencies. The
application also checks for and installs any required dependencies when it starts.Optional: configure the JSON file (e.g.
config.json) with your desired settings.
The application will use default settings if you don't provide a JSON configuration file.
Learn how to set up the JSON configuration file
You should now be able to explore the CLI (./autogpt.sh --help) and run the application.
See the user guide for further instructions.
Setting up LLM providers
You can use AutoGPT with any of the following LLM providers.
Each of them comes with its own setup instructions.
AutoGPT was originally built on top of OpenAI's GPT-4, but now you can get
similar and interesting results using other models/providers too.
If you don't know which to choose, you can safely go with OpenAI*.
* subject to change
OpenAI
!!! attention
To use AutoGPT with GPT-4 (recommended), you need to set up a paid OpenAI account
with some money in it. Please refer to OpenAI for further instructions (link).
Free accounts are limited to GPT-3.5 with only 3 requests per minute.
Make sure you have a paid account with some credits set up: Settings > Organization > Billing
Get your OpenAI API key from: API keys
Open
.envFind the line that says
OPENAI_API_KEY=Insert your OpenAI API Key directly after = without quotes or spaces:
iniOPENAI_API_KEY=sk-proj-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx!!! info "Using a GPT Azure-instance"
If you want to use GPT on an Azure instance, setUSE_AZUREtoTrueand
make an Azure configuration file.
Renameazure.yaml.templatetoazure.yamland provide the relevant
azure_api_base,azure_api_versionand deployment IDs for the models that you
want to use.
E.g. if you want to usegpt-3.5-turboandgpt-4-turbo:
yaml # Please specify all of these values as double-quoted strings # Replace string in angled brackets (<>) to your own deployment Name azure_model_map: gpt-3.5-turbo: "<gpt-35-turbo-deployment-id>" gpt-4-turbo: "<gpt-4-turbo-deployment-id>" ...
Details can be found in the openai/python-sdk/azure, and in the [Azure OpenAI docs] for the embedding model.
If you're on Windows you may need to install an MSVC library.
!!! important
Keep an eye on your API costs on the Usage page.
Anthropic
- Make sure you have credits in your account: Settings > Plans & billing
- Get your Anthropic API key from Settings > API keys
- Open
.env - Find the line that says
ANTHROPIC_API_KEY= - Insert your Anthropic API Key directly after = without quotes or spaces:ini
ANTHROPIC_API_KEY=sk-ant-api03-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - Set
SMART_LLMand/orFAST_LLMto the Claude 3 model you want to use.
See Anthropic's models overview for info on the available models.
Example:iniSMART_LLM=claude-3-opus-20240229
!!! important
Keep an eye on your API costs on the Usage page.
Groq
!!! note
Although Groq is supported, its built-in function calling API isn't mature.
Any features using this API may experience degraded performance.
Let us know your experience!
- Get your Groq API key from Settings > API keys
- Open
.env - Find the line that says
GROQ_API_KEY= - Insert your Groq API Key directly after = without quotes or spaces:ini
GROQ_API_KEY=gsk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - Set
SMART_LLMand/orFAST_LLMto the Groq model you want to use.
See Groq's models overview for info on the available models.
Example:iniSMART_LLM=llama3-70b-8192
Llamafile
With llamafile you can run models locally, which means no need to set up billing,
and guaranteed data privacy.
For more information and in-depth documentation, check out the llamafile documentation.
!!! warning
At the moment, llamafile only serves one model at a time. This means you can not
set SMART_LLM and FAST_LLM to two different llamafile models.
!!! warning
Due to the issues linked below, llamafiles don't work on WSL. To use a llamafile
with AutoGPT in WSL, you will have to run the llamafile in Windows (outside WSL).
Instructions
1. Get the `llamafile/serve.py` script through one of these two ways:
1. Clone the AutoGPT repo somewhere in your Windows environment,
with the script located at `classic/original_autogpt/scripts/llamafile/serve.py`
2. Download just the [serve.py] script somewhere in your Windows environment
2. Make sure you have `click` installed: `pip install click`
3. Run `ip route | grep default | awk '{print $3}'` *inside WSL* to get the address
of the WSL host machine
4. Run `python3 serve.py --host {WSL_HOST_ADDR}`, where `{WSL_HOST_ADDR}`
is the address you found at step 3.
If port 8080 is taken, also specify a different port using `--port {PORT}`.
5. In WSL, set `LLAMAFILE_API_BASE=http://{WSL_HOST_ADDR}:8080/v1` in your `.env`.
6. Follow the rest of the regular instructions below.
[serve.py]: https://github.com/Significant-Gravitas/AutoGPT/blob/master/classic/original_autogpt/scripts/llamafile/serve.py
* [Mozilla-Ocho/llamafile#356](https://github.com/Mozilla-Ocho/llamafile/issues/356)
* [Mozilla-Ocho/llamafile#100](https://github.com/Mozilla-Ocho/llamafile/issues/100)!!! note
These instructions will download and use mistral-7b-instruct-v0.2.Q5_K_M.llamafile.
mistral-7b-instruct-v0.2 is currently the only tested and supported model.
If you want to try other models, you'll have to add them to LlamafileModelName in
llamafile.py.
For optimal results, you may also have to add some logic to adapt the message format,
like LlamafileProvider._adapt_chat_messages_for_mistral_instruct(..) does.
Run the llamafile serve script:
shellpython3 ./scripts/llamafile/serve.pyThe first time this is run, it will download a file containing the model + runtime,
which may take a while and a few gigabytes of disk space.To force GPU acceleration, add
--use-gputo the command.In
.env, setSMART_LLM/FAST_LLMor both tomistral-7b-instruct-v0.2If the server is running on different address than
http://localhost:8080/v1,
setLLAMAFILE_API_BASEin.envto the right base URL
AGENTS (`docs/AGENTS.md`)
Documentation Guidelines
Where a Document Goes
- Published pages live in
docs/home/,docs/platform/anddocs/integrations/. GitBook publishes them at agpt.co/docs, and a page appears on the site once that folder'sSUMMARY.mdlists it. Put a page there only if it is written for people outside the team: people using, self-hosting or contributing to AutoGPT. - Engineering notes live in
docs/engineering/: the team's working documents, such as analytics and tracking plans, rollout plans, runbooks, internal references, and design and architecture notes. They are not on the docs site. Don't list one in aSUMMARY.md.
docs/engineering/ is not private. The repository is public and AutoPilot's documentation search indexes the folder, so keep secrets and confidential material out of it, as with any file in the repository.
When unsure, write the document in docs/engineering/. Listing a page in a SUMMARY.md publishes it, so do that only for a page meant for the site.
The site follows master. .github/workflows/docs-gitbook-sync.yml copies master's docs/ to the gitbook branch, and GitBook publishes that branch. Change docs through a pull request to dev. Never commit to gitbook, open a pull request against it, or edit pages in GitBook: the next copy overwrites anything that isn't on master.
Every Markdown page under docs/platform/ must be listed in docs/platform/SUMMARY.md: every .md file other than SUMMARY.md itself and GitBook's own files under a .gitbook/ directory. A SUMMARY.md may list only pages inside its own folder. .github/workflows/scripts/test_docs_layout.py checks those two things on every pull request. It cannot tell whether a listed page is meant for the public; that is for the author and the reviewer to judge.
Block Documentation Manual Sections
When updating manual sections (``) in block documentation files (e.g., docs/integrations/basic.md), follow these formats:
How It Works Section
Provide a technical explanation of how the block functions:
- Describe the processing logic in 1-2 paragraphs
- Mention any validation, error handling, or edge cases
- Use code examples with backticks when helpful (e.g.,
[[1, 2], [3, 4]]becomes[1, 2, 3, 4])
Example:
The block iterates through each list in the input and extends a result list with all elements from each one. It processes lists in order, so `[[1, 2], [3, 4]]` becomes `[1, 2, 3, 4]`.
The block includes validation to ensure each item is actually a list. If a non-list value is encountered, the block outputs an error message instead of proceeding.Use Case Section
Provide 3 practical use cases in this format:
- Bold Heading: Short one-sentence description
Example:
**Paginated API Merging**: Combine results from multiple API pages into a single list for batch processing or display.
**Parallel Task Aggregation**: Merge outputs from parallel workflow branches that each produce a list of results.
**Multi-Source Data Collection**: Combine data collected from different sources (like multiple RSS feeds or API endpoints) into one unified list.Style Guidelines
- Keep descriptions concise and action-oriented
- Focus on practical, real-world scenarios
- Use consistent terminology with other blocks
- Avoid overly technical jargon unless necessary
Engineering Activation Metrics (`docs/engineering/activation-metrics.md`)
Activation Metrics & Experiments
How the platform measures activation, retention and unit economics across
every surface (classic agents, Autopilot, experts, schedules), and how to run
an A/B/C experiment whose results can be sliced in both PostHog and Looker.
This page is the contract between engineering and go-to-market: the event
names, the SQL views and the definitions below are the ones dashboards should
be built on. Change a definition here and in every view that repeats it: the
task predicate is spelled out in user_task_daily, user_lifecycle,retention_task_weekly and unit_economics_monthly until a shared base view
exists, so a change to one must be made in all four.
Where each tool fits
| Tool | Owns | Reads |
|---|---|---|
Postgres analytics.* views |
The numbers. Every metric below is a view in autogpt_platform/analytics/queries/. |
Primary tables (platform.*, auth.*). |
| Looker Studio | GTM dashboards. | Only analytics.* views, via the analytics_readonly role. |
| PostHog | Funnels, session replay, experiment bucketing and significance testing. | Frontend autocapture + the server-side activation events below. Can also read analytics.* as a data-warehouse source. |
| LaunchDarkly | Feature gating (on/off, rollouts, targeting). Not for A/B measurement. | — |
| DataFast | Marketing attribution: which channel brought a visitor who signed up, ran an agent, paid. | Browser goals only (signup, paywall_view, run_agent, schedule_agent, ...). |
| Admin dashboard | Ops views: platform costs, copilot usage export, execution accuracy. | Backend admin routes. |
| Sentry | Errors and stack traces behind agent_run_finished with status: failed. |
— |
Definitions
- Task: a unit of work a person asked for now. Either a human-started
agent run (triggerSourceinmanual,api) or a human chat turn (ausermessage in an Autopilot or expert session whose origin is notautomation). A run the copilot tool started (triggerSource=copilot)
is counted once, through the chat turn that asked for it. Dry runs, nested
sub-graph runs and dream/memory sessions are never tasks. - Automated task: a schedule fire, a webhook trigger, or a scheduled
follow-up turn. Counted separately so "people doing things" and "agents
running" can be compared. - Activated: at least 3 tasks on at least 2 distinct days within 14 days
of signup. (analytics.user_lifecycle.activated) - Last active: latest of last task, last visit, last scheduled run,
falling back to signup. Because a scheduled run counts, an account whose
schedule fires daily is neverstale_*orchurned_30dwhile it keeps
firing;retention_task_weeklydeliberately ignores schedules and measures
people coming back. Use the one that matches the question. - Login / visit: a session row, from
auth.sessions(Supabase, history
up to the Better Auth cutover on 2026-07-30) orplatform.UserAuthSession
(Better Auth, everything since). Every login column reads both. - Stale: no activity in 14 / 30 days (
stale_14d,stale_30d). - Churned (30d): had at least one task ever, signed up more than 30 days
ago, no activity in the last 30 days. - Never activated (30d): signed up more than 30 days ago and never did a task.
- Cost to us:
PlatformCostLog.costMicrodollars, our real provider spend,
split into agent (block runs), copilot (turns) and background (dream passes). - Credits charged:
CreditTransactionUSAGErows, in cents. A run
billed to an organisation writesOrgCreditTransactioninstead; the credit
columns inuser_task_daily,user_lifecycleandunit_economics_monthly
include those rows under the user who initiated them (initiatedByUserId),
while cost stays with the acting user, so per-user margin stays comparable
for org members.
Activation events (PostHog)
Emitted server-side by backend/util/product_analytics.py withdistinct_id = user id, so they join the frontend's posthog.identify.
Every event carries environment and source: "platform".
| GTM asked for | Event | Fires when | Key properties |
|---|---|---|---|
| run_agent | agent_run_started without expert_id |
A person starts an agent run (UI, API key, copilot tool). Not schedules, webhooks, sub-graphs or dry runs. | via, via_ref, graph_id, graph_exec_id |
| run_autopilot | chat_message_sent without expert_id |
A person sends a message in an Autopilot chat. | chat_session_id, origin (web, slack, telegram, discord) |
| run_expert | chat_message_sent / agent_run_started with expert_id |
A person sends a message in an expert chat or starts an expert workflow run (kind: workflow_run). |
expert_id, kind |
| schedule_agent_run / schedule_autopilot_run / schedule_expert_run | schedule_created |
Any schedule is registered, from any surface. | target: agent / autopilot / expert, cron, is_recurring, schedule_id |
| schedule_agent_ran / schedule_autopilot_ran / schedule_expert_ran | schedule_fired |
A schedule produced work. | target, schedule_id, graph_exec_id or chat_session_id |
| (trigger) | trigger_fired |
A webhook produced a run. | webhook_id, graph_exec_id, target |
| agent_fail | agent_run_finished with status: failed |
A run reaches FAILED. | via, failure_reason, expert_id |
| — | agent_run_finished with status: completed |
A run reaches COMPLETED. | via, cost_cents, duration_seconds |
| — | expert_hired |
A user hires an expert from a template, from any surface. | expert_id, template_id, surface |
| — | integration_connected |
A user connects a credential, by OAuth or by pasting a key. | provider, credential_type, method |
| — | credential_oauth_started |
The backend issues an OAuth login URL (GET /api/integrations/{provider}/login). Not Codex, which signs in with a device login. |
provider |
| — | credential_oauth_exchange_failed |
POST /api/integrations/{provider}/callback returns an error, on any path. Not Codex. |
provider, status_code (unset for an unexpected error), failure_class, detail (error message or class, secrets removed, at most 200 characters) |
| agent_idle, stale account | (not events) | Computed states, see agent_health and user_lifecycle. |
— |
credential_oauth_started, integration_connected (method: oauth) andcredential_oauth_exchange_failed give started / connected / failed per
provider. They are sent by the backend so they are not held back by the
browser's analytics consent. failure_class says where the callback failed:
failure_class |
Status | Cause |
|---|---|---|
invalid_state |
400 | State token missing, expired, or issued for another provider. |
provider_unavailable |
400 / 404 / 500 / 501 | No OAuth handler for the provider, or its client id and secret are not set. |
token_exchange |
400 | The provider rejected the code, or the exchange raised. |
credential_merge |
400 | The new token could not be stored on an existing credential (username or provider mismatch, managed or system credential). |
An unexpected error (not an HTTP error we raise, e.g. a database error) keeps
the class of the step it happened in, with only the exception class asdetail and no status_code: the app's exception handlers pick the response
status later (ValueError is a 400, for example).
A granted scope set narrower than the one requested is not a callback
failure: the credential is stored and integration_connected fires.
The event names follow the product analytics plan; the chat events
(chat_tool_called, chat_outcome, ...) and billing events (topup_completed,trial_*, subscription_*, ...) are listed there too. Every PostHog event, its sender, its properties and whether it is live,
planned or removed is listed in the PostHog Tracking Plan.
SQL views (Looker)
New or extended in this change. Existing views (retention_login_*,retention_execution_*, users_activities, platform_cost_log, ...) are
untouched.
| View | Grain | Answers |
|---|---|---|
graph_execution (extended) |
one row per agent run | Now also triggerSource, triggerRef, expertId, failureReason, isDryRun, isSubgraphRun. |
chat_turn |
one row per human chat message | Autopilot vs expert usage; the SQL twin of chat_message_sent. |
user_task_daily |
user × day | How many tasks people run, by surface and by how they started; failures; our cost; credits charged; logins. |
user_lifecycle |
one row per user | First/last activity per surface, first-two-weeks behaviour, cost and revenue, and the labels activated, stale_*, churned_30d, never_activated_30d. The feature table for churn analysis. |
user_lifecycle_funnel_weekly |
signup-week cohort | Signup → onboarded → first task in 7d → activated in 14d → schedule / expert / purchase → retained at week 4. |
retention_task_weekly |
cohort × lifetime week | Retention where "active" means any human task on any surface. |
unit_economics_monthly |
user × month | Cost to us per user per month, per task and per active day; credits charged; gross margin; by tier. |
agent_health |
user × library agent | Idle, never-run and failing agents. |
experiment_assignment |
user × experiment | The arm each user saw; join on user_id to split anything else by variant. |
user_attribution |
one row per user | Where the user came from: shared anonymous id, PostHog device id, DataFast visitor id, first landing page, UTM tags, signup method. Join to user_lifecycle for activation by channel. |
Apply them to a database with:
cd autogpt_platform/backend
poetry run analytics-viewsThen add each new view as a data source in Looker Studio (the header of every.sql file documents its columns and example queries). triggerSource is
NULL on runs created before September 2026; the views expose those asagent_runs_untagged and count them as human tasks.
One identity across LaunchDarkly, PostHog and the database
The two tools used to disagree about who a visitor is. LaunchDarkly only
knew a user after login (every logged-out visitor shared the literal keyanonymous, so it could not bucket pre-signup at all), while PostHog minted
its own anonymous id and merged it into the user id at identify. Nothing
tied a pre-signup arm to a post-signup person, and nothing tied either to
the DataFast visitor that marketing sees.
Now the frontend owns one first-party anonymous id
(src/services/analytics/anonymous-id.ts, stored in localStorage, adopting
an existing PostHog device id when there is one) and hands it to everything:
- PostHog is bootstrapped with it as the anonymous distinct id, so
identify(user.id)merges it into the person as usual. - LaunchDarkly gets it as the anonymous
userkey before login, and as
adevicecontext alongside theusercontext after login. Rules on theuserkind are unchanged; a rule that buckets bydevicekeeps the same
arm across signup. - The database stores it on the user at first login (
UserAttribution,
reported by the browser toPOST /api/analytics/attribution) together
with the PostHog device id, the DataFast visitor and session ids (from the
request headers the frontend already sends), the first landing page, UTM
tags and the signup method.analytics.user_attributionexposes it.
Rule of thumb: LaunchDarkly gates, PostHog measures, the database joins.
Server-side flag evaluation stays keyed by user id (the backend has no
device context yet), so bucket pre-login experiments on the client.
Running an experiment
- Create a multivariate feature flag in PostHog with the arm keys you want
(control,variant-a,variant-b, ...) and make it an experiment there. - In the frontend, read it through
useExperiment(key)fromsrc/services/experiments/useExperiment.ts. It returns{ variant, isResolved }:
render the control experience untilisResolved, and do not fire one-shot
side effects before then, or a late variant is mis-recorded as control.
The hook reports the arm toPOST /api/experiments/assignmentsonce per
user and experiment; the backend keeps the first arm it sees. - Backend-decided experiments can call
backend.data.experiments.record_assignment(user_id, key, variant, source="backend"). - Read results in PostHog (exposure + the activation events above as goals),
and in Looker by joininganalytics.experiment_assignmenttouser_lifecycle,user_task_dailyorunit_economics_monthly.
The onboarding paywall experiment (subscription-pricing-page-initial-state)
is already wired this way.
Deploying this change
- Run the migrations
20260902120000_add_execution_trigger_source(addsAgentGraphExecution.triggerSource/triggerRef),20260902120100_add_experiment_assignment(theExperimentAssignment
table) and20260902120200_add_user_attribution(theUserAttribution
table). Existinganalytics.*views are unaffected: no column they select
from changes type. - Deploy the backend. From that moment every run row carries its trigger and
every activation event flows to PostHog. - Nick Tindle runs
poetry run analytics-viewsagainst production right
after the deploy that carries this change, before any Looker source is
added. Before that, on staging:EXPLAIN (ANALYZE, BUFFERS)onuser_lifecycle,user_lifecycle_funnel_weekly,experiment_assignment
anduser_attribution.user_lifecyclehas no date bound (it is a
per-user lifetime table), so every refresh aggregates the full history ofAgentGraphExecution,ChatMessage,PlatformCostLogandCreditTransaction; if staging shows that hurting, the follow-up is a
nightly-refreshed materialized view (agenerate_views.pychange), not a
Looker source on the live view. Then wire the new views into Looker Studio. - Confirm in PostHog that
agent_run_started,chat_message_sent,schedule_createdandagent_run_finishedare arriving withsource = platform.
The questions GTM will ask next
The list GTM gave us is the first layer. These are the questions that follow
once they see the data, with where the answer already lives or what is still
missing.
Activation
- Which first action predicts retention: an agent run, an Autopilot chat, an
expert, or a schedule? —user_lifecyclehas the first-time timestamps
and early counts per surface; group by the earliest one and comparechurned_30d. - How long from signup to first task, and where do people stall? —
hours_to_first_taskplus the onboarding funnel view; combine with PostHog
session replay on users withnever_activated_30d. - Does connecting an integration change activation? —
integrations_connected_totalandfirst_integration_connected_atinuser_lifecycle(fromIntegrationCredential),connected_integrationin
the funnel, and theintegration_connectedevent in PostHog. - Which marketing channel brings activated users? — join
user_attribution(UTM tags, referrer, landing page, DataFast visitor id)
touser_lifecycle. For channels only DataFast knows, export DataFast by
visitor id and join ondatafast_visitor_id.
Retention and churn
- Are we retaining people, or just their schedules? —
retention_task_weekly
(people) next toretention_execution_weekly(all runs incl. automated). - Which early behaviours separate churned from retained users? — the
example query onuser_lifecycle; the next step is a proper model, which
needs the same table exported to a notebook. - Who is about to churn? —
stale_14dusers withtasks_28ddropping vs
their prior 28 days;agent_health.idle_7dandfailingare the earliest
per-agent signals. Gap: a weekly "at-risk" list is a view away, but
acting on it (email, in-product nudge) needs the alerts pipeline. - Do failures cause churn? —
agent_runs_failed_totalandagent_runs_no_credits_totalinuser_lifecycle;failure_reasoningraph_execution. Insufficient balance is already separated because it is
a pricing problem, not a product problem.
Cost and pricing
- What will a free trial with card on file cost us? —
unit_economics_monthlyby tier and by month-of-life; thePERCENTILE_CONTexample shows the tail, which is what a trial cap must
cover. - Cost per task by surface and model? — split exists by surface in
user_task_daily; per-model cost is inplatform_cost_log. Gap: chat
turns are not linked to the exact cost rows (copilot cost rows carry the
session id ingraphExecId), so cost per turn is an average, not exact. - Gross margin per user and per tier? —
gross_margin_usdinunit_economics_monthly, with the caveat that credits are prepaid. - How much do we spend on users who never come back? — join
unit_economics_monthlytouser_lifecycle.churned_30d. - Background cost (dream/memory passes) as a share of total? —
background_cost_usdinuser_task_dailyandunit_economics_monthly.
Experiments
- Did the pricing-page arm change activation or retention, not just
conversion? —experiment_assignmentjoined touser_lifecycle. - Can we A/B backend behaviour (model routing, default expert, prompt)? —
yes viarecord_assignment(source="backend"); LaunchDarkly stays the
gate, PostHog and the assignment table hold the arm. - Can the team keep using LaunchDarkly for arms? — not in the browser
today: the unuseduseLaunchDarklyExperimentbridge and itsexperiment_exposedevent were removed (SECRT-2722), so a LaunchDarkly arm
reaches the assignment table only throughrecord_assignment. Gap: the
backend's LaunchDarkly context has nodevicekind yet, so server-evaluated
flags cannot bucket by the anonymous id.
Experts and schedules
- Do hired experts get used after day one? —
expert_turns_total,expert_workflow_runs_total,experts_activeinuser_lifecycle;expert_hiredevent in PostHog for the funnel. - How many schedules are created and then silently stop? — creations are in
ActivityEvent, fires are ingraph_execution.triggerSource = 'schedule'.
Gap: schedules live only in the APScheduler job store, so there is no
durable schedule row to join pauses and deletions to; aScheduletable
would make schedule health first-class.
Operational gaps to close next
- No
lastActiveAtonUser;user_lifecyclederives it, which is fine for
dashboards but not for real-time gating. - Frontend PostHog is not bootstrapped with flags, so experiment variants
resolve after first paint.useExperiment.isResolvedpapers over this;
server-side bootstrap would remove the delay. - DataFast goals fire only after analytics consent, so they undercount
relative to the server-side events. Use the server-side numbers as truth
and DataFast for channel attribution only.
Engineering Codex Subscription Preview (`docs/engineering/codex-subscription-preview.md`)
ChatGPT/Codex subscription transport preview
This private preview lets a user connect a ChatGPT account and use that
account's Codex allowance for three explicit execution paths:
- The existing Code Generation block through the
codex_app_server
transport. - A newly created AutoPilot chat session when ChatGPT/Codex is the only
connected subscription transport, or is selected from multiple connected
subscription transports. - An AutoPilot graph block with an explicitly selected ChatGPT/Codex
connection.
codex is the credential provider and Codex App Server is the execution
runtime. A ChatGPT plan is not unlimited inference: these calls consume the
connected account's Codex allowance or credits and remain subject to its rate
limits. Subscription-backed usage records tokens andbilling_mode=user_subscription; it is not reported as AutoGPT provider USD
spend.
The preview does not route Orchestrator, shared LLM blocks, image generation,
or background system work through the connected account. Existing graphs and
AutoPilot sessions keep their stored route. New AutoPilot sessions use a
connected subscription transport automatically when exactly one is available.
Run locally
From autogpt_platform:
docker compose up -d --buildCodex pairing and its supported execution routes are available without feature
flag environment variables. The stack mounts /run/autogpt-codex as a
memory-backed temporary filesystem for REST, executor, and Copilot executor
processes.
Open http://localhost:3000, then open Settings > Integrations, choose
OpenAI, select ChatGPT, and click Sign in with ChatGPT. In the sign-in
window, open the ChatGPT verification page and enter the one-time code.
Test the Code Generation block
- Add a Code Generation block to a graph.
- Set Transport to Codex App Server.
- Select the connected ChatGPT for Codex credential.
- Run the graph.
Existing graphs continue to default to the OpenAI API transport. The block's
model dropdown applies to that API transport. The subscription transport uses
App Server's current account-compatible default; its live catalog can change
independently of public API model names.
Test AutoPilot
- Open AutoPilot and start a new task.
- If ChatGPT/Codex is the only connected subscription transport, it is used
automatically and no connection selector is shown. If multiple subscription
transports are connected, choose the desired connection before sending the
first message. - Choose Balanced or Advanced as available, then send the message. Text and
permitted AutoGPT tool calls stream through the existing AutoPilot event
surface.
The route and credential are stored on the new session and are immutable for
that session. Start another task to use a different connected subscription
transport. The server reloads the authoritative session route before each
queued turn; queued messages carry only the credential ID, never the OAuth
tokens. A missing, revoked, or busy Codex credential fails visibly. There is no
silent fallback to an AutoGPT-funded model or another user's credential.
The visible model tier and the server-selected execution path select a Codex
route from the shared model catalog, then validate it against the models
advertised by the connected account.
| Execution path | Balanced | Advanced |
|---|---|---|
| Fast | GPT-5.6 Luna | GPT-5.6 Terra |
| Claude Agent SDK | GPT-5.6 Terra | GPT-5.6 Sol |
If the account does not expose the mapped model, the transport uses the visible
account default. File attachments, agent-building tools, and SDK sub-sessions
use the same Claude Agent SDK path as platform-funded AutoPilot.
Builder-panel-bound sessions remain platform-funded in this preview because
their persistent session is created without an AI connection selector.
The Copilot executor keeps one exclusive credential lease and one Codex runtime
per connected account, then multiplexes overlapping chats onto separate Codex
threads. Canceling or finishing one chat does not stop its siblings. This pool
is process-local: a request routed to another executor process or replica cannot
join the owner yet and still fails with the bounded, retryablecodex_credential_busy error. A hosted multi-replica rollout therefore needs
credential-owner routing or a dedicated bridge. The normal platform AutoPilot
route is unchanged.
Test the AutoPilot graph block
- Add an AutoPilot block to a graph.
- Select the saved connection under ChatGPT / Codex connection.
- Run the graph.
The credential field is a reference-only credential input: graph validation
checks ownership and export strips the credential ID, while the graph executor
does not hold the outer credential lease. The Copilot executor acquires the
single runtime lease when it consumes the queued turn. This preserves normal
credential rebinding without deadlocking the nested execution path. Leaving the
field empty preserves the existing platform-funded behavior.
Claude Agent SDK transport
AutoPilot keeps the existing Claude Agent SDK and Claude Code CLI as its agent
harness. A request-scoped loopback Anthropic Messages endpoint translates model
rounds to the pinned Codex App Server's experimental dynamicTools protocol.
Claude Code still owns the MCP tool loop, permission hooks, builder behavior,
transcript, resume, compaction, and sub-session machinery. Codex supplies the
model response using the selected user's ChatGPT/Codex credential.
The loopback endpoint is bound to 127.0.0.1 on an ephemeral port and accepts
only a random capability generated for that turn. The capability is placed in
the Claude CLI child environment; ChatGPT access, refresh, and ID tokens are
never placed there. Anthropic text and tool-use events are mapped to the same
Claude SDK response adapter used by normal AutoPilot.
The App Server process itself is fail-closed: native shell, filesystem, web
search, apps, plugins, environments, workspace roots, and approval requests are
disabled, and its sandbox is read-only. Any side effect comes only through a
tool that Claude Code was already permitted to invoke through AutoGPT's MCP and
security hooks.
ChatGPT is reached over HTTPS: sign-in, refresh, the model catalog and
inference all run against auth.openai.com and chatgpt.com, so no Codex
binary ships in the image and there is no version to pin. The Claude Agent SDK
and its CLI are still pinned by the backend lock. Because the compatibility
surface is version-sensitive, updates require the focused protocol and Messages
conformance tests to pass before rollout.
Verify an existing local login
To test an existing local Codex login before starting the stack, run this fromautogpt_platform/backend:
poetry run python -m scripts.codex_preview_smokeThe smoke test reads ~/.codex/auth.json without writing to it, checks the
account and model catalog, and performs one subscription-backed turn with a tool
call over HTTPS -- failing if the model never calls the tool or returns no text.
It reports the quota the turn's response headers carried. It does consume a
small amount of the connected account's usage.
Deploy a private cloud preview
Codex pairing and supported execution routes do not require availability flags
or frontend build-time overrides. Scope a private preview with the deployment's
ordinary access controls instead of hiding device login with environment
variables. The transport selector is driven by the connected, compatible
subscription providers: it is hidden for zero or one and shown when more than
one is available.
REST, executor, and Copilot executor must share the normal AutoGPT credential
database, encryption configuration, and Redis cluster. Give every process that
may launch App Server an isolated, memory-backed CODEX_TEMP_ROOT; never mount
it on a persistent volume.
Set FRONTEND_BASE_URL, NEXT_PUBLIC_FRONTEND_BASE_URL, and BETTER_AUTH_URL
to the exact public origin of the preview, including its https:// scheme and
without a path. REST deliberately builds the device-login URL from this
configured origin instead of trusting forwarded host headers. Leaving a
production or localhost origin in a cloud preview sends the popup to a host
that does not have the preview's Better Auth cookie.
NEXT_PUBLIC_FRONTEND_BASE_URL is compiled into the frontend bundle. Pass it as
a frontend image build argument, then set BETTER_AUTH_URL on the frontend at
runtime and FRONTEND_BASE_URL on REST at runtime. The development Compose file
forwards all three exported values and keeps http://localhost:3000 as their
local default.
Device-login status and ownership are Redis-backed, so polling and cancellation
can reach different REST replicas. The active App Server login actor is still
process-local, however: an owner-pod restart interrupts the login and another
replica cannot take it over or resume it. Keep rolling deploys away from active
sign-ins and ask the user to restart an interrupted login. Broad cloud rollout
requires moving the login actor into the dedicated bridge described below.
The containers need outbound HTTPS access to OpenAI's ChatGPT/Codex endpoints.
No callback ingress from OpenAI is required because Codex App Server polls for
device-code completion.
Preview safety boundary
- The encrypted, USER-scoped
IntegrationCredentialrow remains the source of
truth. - Raw ChatGPT tokens do not enter Redis, RabbitMQ, frontend responses,
container-wide environment variables, or AutoPilot session records. - Tokens are held in memory for the life of a turn and never written to disk:
there is no on-disk auth home, because there is no process to hand one to.
A client is built per turn and never cached, so one user's subscription can
never serve another's request. Refresh is owned by the credentials manager
and serialized behind its Redis mutex, since OpenAI rotates the refresh token
on every exchange. - Code Generation turns expose no dynamic tools or host workspace. AutoPilot
exposes only the tools registered by its existing Claude SDK/MCP harness. - Runtime reuse is scoped to the exact user and credential. Credentials are
never pooled across users or substituted for another account. - There is no silent platform-key fallback.
The current implementation launches Codex App Server in-process from the
ordinary REST, executor, and Copilot executor containers. Those containers
currently run as root and retain the backend services' broader database, Redis,
filesystem, and network privileges. The temporary-home and fail-closed thread
configuration reduce exposure, but they do not make this a safe boundary for a
broad external cloud rollout.
Treat the current shape as local development or a tightly access-controlled
staff/private preview only. Before enabling external users, move runtime and
device-login supervision into a dedicated unprivileged bridge with a non-root
user, read-only root filesystem, dropped capabilities, memory-only credential
homes, process and concurrency limits, narrowly restricted egress, and a
least-privilege internal credential lease interface. The bridge must also own
login actor takeover or explicit restart semantics. Complete the commercial
and policy review for hosted subscription passthrough before broader rollout.
Engineering Feature Flags (`docs/engineering/feature-flags.md`)
Feature flags
Which vendor answers a flag read is set per deployment by FEATURE_FLAG_BACKEND:launchdarkly (the default), posthog, or dual — evaluate both, serve
LaunchDarkly's answer, and log every disagreement. Call sites are unaffected by
the choice; they read through evaluate_feature_flag() on the backend anduseGetFlag / useFlagStatus on the frontend.
One PostHog project per environment
Dev and Prod each have their own PostHog project, and each project holds its
own flags. A deployment reads its flags from the same project its analytics go
to, the one POSTHOG_API_KEY names, and evaluates them locally with that
project's Feature Flags Secure API Key in POSTHOG_SECRET_KEY. Where local
evaluation can't decide a read, the SDK asks PostHog's /flags endpoint
instead, unless the read is local-only, as a signed-out visitor's own read is.
| Dev | Prod | |
|---|---|---|
| PostHog project | 281733 | 117338 |
POSTHOG_API_KEY, POSTHOG_SECRET_KEY (backend) |
Dev project's | Prod project's |
NEXT_PUBLIC_POSTHOG_KEY (Vercel) |
Dev project's | Prod project's |
PostHog has no environments within a project, so a flag both deployments read
is defined in each project. The Projects tab on the
flag list
and on each flag's page shows a flag across both projects side by side.
Until Prod switches to PostHog, Prod serves LaunchDarkly's answers
(FEATURE_FLAG_BACKEND=dual only compares PostHog's against them), so a change
to a Prod flag is made in LaunchDarkly and mirrored in the Prod PostHog project.
The browser. With NEXT_PUBLIC_FEATURE_FLAG_BACKEND=server the browser
gets its flags from the backend's GET /api/feature-flags, which evaluates
them through the same path as the backend's own reads, so the browser and the
backend give one answer from one project. A Vercel preview in server mode
gets the answer of the backend it calls. The other values read flags in the
browser itself: launchdarkly (the default) from LaunchDarkly, and posthog /dual from the project NEXT_PUBLIC_POSTHOG_KEY names. useExperiment reads
posthog-js in every mode.
The environment property. Every PostHog flag read the backend makes, its
own and the browser's in server mode, carries the person propertyenvironment: the backend's APP_ENV (local, dev or prod), the same
value its analytics events carry. A release condition can use it in either
project like any other person property. It is only an evaluation input, never
stored on a person. Browser reads through posthog-js (posthog / dual mode)
don't carry it: they send buildFlagPersonProperties (email, email domain,
role, created at) only, so a condition on environment matches only the
backend's reads there.
Splitting signed-out visitors. A visitor who allowed analytics is bucketed
under visitor:, but only by conditions their read can
decide: a rollout percentage, environment and is_anonymous, with no email.
A condition group on another person property or a cohort (the migrated
"email in employees", for one) can't be decided for a visitor, so a visitor
whom no decidable group matches gets the shared signed-out answer instead.
"Employees OR 20%" is then all or nothing for visitors, whichever side the
shared anonymous key lands on. For a real 20% of visitors, addis_anonymous is not true to the rollout group: the shared answer is then
off, and only each visitor's own bucket turns the flag on.
Sharing PostHog's flag definitions
PostHog evaluates flags locally, in the process, from a set of flag definitions
it polls for every 30 seconds. PostHog
bills one definitions fetch as ten flag requests,
so a poller in every process makes that bill scale with replica count: at two
polls a minute, each process costs 28,800 flag requests a day whether or not it
reads a single flag.
So one process is elected to refresh the definitions and write them to Redis,
and every other process evaluates from that shared copy. The fleet then costs
one process's worth of polling — 28,800 flag requests a day in total, instead of
28,800 per replica.
This is PostHog's own FlagDefinitionCacheProvider interface; the SDK calls it
from its polling thread, never from a flag read, so no request can block on
Redis.
Settings
| Setting | Default | What it does |
|---|---|---|
POSTHOG_FLAG_DEFINITION_CACHE |
redis |
redis shares definitions through Redis; memory keeps them in the process; none restores one poller per process. |
POSTHOG_FLAG_DEFINITION_REFRESH_SECONDS |
30 |
How often the refresher fetches, and how often everyone else re-reads the shared copy. |
POSTHOG_FLAG_DEFINITION_CACHE_TTL_SECONDS |
600 |
How long the shared copy stays readable after the last refresh. |
None of them is read unless FEATURE_FLAG_BACKEND is posthog or dual andPOSTHOG_SECRET_KEY is set — without that key the SDK evaluates over the
wire and has no definitions to share.
Per environment. Leave the defaults on dev and prod: both run several
replicas against a shared Redis, which is what the cache is for. Local
development and CI run FEATURE_FLAG_BACKEND=launchdarkly, so none of this is
reached; set POSTHOG_FLAG_DEFINITION_CACHE=memory if you want to exercise
PostHog locally without Redis.
What the refresher is
Every process tries SET <key> NX PX on one Redis key each time it polls.
The one that gets it fetches the definitions from PostHog and writes them to
Redis; the others read that copy. The winner renews its own lock on every poll,
so it keeps the job. The lock expires two poll intervals after its last renewal,
and the next process to poll takes it, so a refresher that dies is replaced
within 90 seconds at the defaults. One shutting down cleanly releases the lock
immediately, and is replaced on the next poll.
Telling it is working
One process logs, once, when it takes the job:
This process now refreshes the shared PostHog flag definitions (<host>:<pid>:<id>)and the others log Another process now refreshes the shared PostHog flag definitions when they lose it.
The counter autogpt_posthog_flag_definition_cache_events_total on /metrics
carries the same story, by outcome:
| outcome | meaning |
|---|---|
refresher / stored |
this process holds the lock and wrote its definitions to the shared copy, which it does every poll whether or not PostHog reported a change |
follower / cached |
this process read the shared copy |
stale |
the shared copy has not been rewritten for five poll intervals, so no refresher is writing it; it is served anyway |
empty |
nothing in the cache; a process with no definitions yet fetches from PostHog directly, and one that has some keeps them |
error |
Redis was unreachable |
Healthy, with N replicas: stored increments once per poll on exactly one
process, cached once per poll on each of the other N−1, and empty anderror stay flat.
Failure modes
| What happens | What the platform does |
|---|---|
| Cache empty at boot | The process fetches from PostHog directly, once, and the refresher fills the cache. |
| Redis unreachable | Every process polls PostHog for itself, as it would without the cache, and logs once per five minutes. Flag reads are unaffected. |
| Shared copy goes stale | It is served anyway, with a warning, while the refresher catches up. Stale definitions beat none. |
| The refresher dies | Its lock expires and another process takes the job on its next poll. |
| A definitions fetch fails | The definitions already in memory stay in use, and the refresher keeps writing that copy to the cache. |
Nothing here can make a flag read wait on Redis: evaluation reads the
definitions already in the process, and only a read they can't decide waits on
PostHog's /flags.
Engineering Hosted Mcp Catalog (`docs/engineering/hosted-mcp-catalog.md`)
Maintaining MCP Integrations
mcp_catalog.json defines the official services shown in Integrations and the AutoPilot MCP guide. mcp_catalog.py validates the records and exposes them through get_mcp_catalog().
Add or update a service
- Keep a stable
mcp_...ID and link directly to the vendor's setup documentation. - Describe the server's purpose and required plan, admin approval, region, endpoint, and credentials in
descriptionandsetup_instructions. - Choose the connection setup that matches the vendor's server; see Choose how users connect.
- Order the supported
auth_methodsby preference; the first method is selected initially. Choices areoauth,bearer,basic, andnone. - Set
oauth_server_urlwhen the vendor supplies a separate signed-in endpoint. - Set documented
oauth_scopesfor the initial grant and separateoauth_write_scopesfor the optional Allow changes choice. An empty default list omits the scope parameter; omission/null uses discovery defaults.
Choose how users connect
Match the entry to where the vendor runs its MCP server. Every URL must be a public HTTPS URL.
- One public URL for every customer: use
connection_mode: hostedwithserver_url. The connect dialog shows the URL locked, and AutoPilot uses it. Examples: PostHog, Notion. - One public URL, and the service can also be self-hosted: use
hostedwithserver_urlandallow_custom_url: true. The dialog opens onserver_url, and users can replace it with their own instance's URL. AutoPilot still usesserver_url. Say insetup_instructionshow to connect a self-hosted instance, including any sign-in setup it needs. Example: OpenSEO. - A separate URL per region: use
connection_mode: customwith oneserver_url_optionsentry per region and noserver_url. Users pick their region, and the picker's Custom URL option covers self-hosted instances. Prefer this to one entry per region. Don't make it a hosted entry that defaults to one region, because users in the other regions would connect to the wrong one. Examples: Amplitude, Langfuse. - A URL unique to each customer account: use
connection_mode: customwith noserver_urland no options. Say insetup_instructionswhere users find their URL. The dialog opens with an empty URL field. Examples: Chargebee, Sourcegraph.
Both custom setups show Setup required in the integrations list. AutoPilot tells users to add their URL under Settings → Integrations before it can use the server.
Validate the change
Check the vendor's current endpoint and authentication contract. Verify initialization and tool listing with the MCP client, then a benign read using the intended permissions. For OAuth, check registration, consent, and refresh with the deployment's callback and an authorized test account.
Update affected catalog, provider, guide, and page integration tests. Cover changes to region selection, custom URLs, token handling, or optional grants. Regenerate API types when the metadata schema changes, and run the relevant backend and frontend checks.
Engineering Oauth Integration Flow (`docs/engineering/oauth-integration-flow.md`)
OAuth Integration Flow Documentation
Overview
The AutoGPT platform implements OAuth 2.0 in two distinct contexts:
- User Authentication (SSO): Handled by Better Auth, embedded in the Next.js frontend at
/api/auth/* - API Integration Credentials: Custom OAuth implementation for third-party service access
This document focuses on the API Integration OAuth flow used for connecting to external services. For the list of supported providers, see autogpt_platform/backend/backend/integrations/providers.py. For user authentication documentation, see autogpt_platform/frontend/src/lib/auth/SESSION_VALIDATION.md.
Trust Boundaries
1. Frontend Trust Boundary
- Location: Browser/Client-side application
- Components:
CredentialsInputcomponent (autogpt_platform/frontend/src/components/contextual/CredentialsInput/CredentialsInput.tsx)- OAuth callback route (
autogpt_platform/frontend/src/app/(platform)/auth/integrations/oauth_callback/route.ts)
- Trust Level: Untrusted - user-controlled environment
- Security Measures:
- CSRF protection via state tokens
- Popup-based flow to prevent URL exposure
- Message validation for cross-window communication
2. Backend API Trust Boundary
- Location: Server-side FastAPI application
- Components:
- Integration router (
autogpt_platform/backend/backend/api/features/integrations/router.py) - OAuth handlers (
autogpt_platform/backend/backend/integrations/oauth/) - Credentials store (
autogpt_platform/backend/backend/integrations/credentials_store.py)
- Integration router (
- Trust Level: Trusted - server-controlled environment
- Security Measures:
- JWT-based authentication
- Encrypted credential storage
- Token refresh handling
- Scope validation
3. External Provider Trust Boundary
- Location: Third-party OAuth providers
- Components: Provider authorization endpoints
- Trust Level: Semi-trusted - external services
- Security Measures:
- HTTPS-only communication
- Provider-specific security features
- Token revocation support
Component Architecture
Frontend Components
1. CredentialsInput Component
- Purpose: UI component for credential selection and OAuth initiation
- Key Functions:
- Displays available credentials
- Initiates OAuth flow via popup window
- Handles OAuth callback messages
- Manages credential selection state
2. OAuth Callback Route
- Path:
/auth/integrations/oauth_callback - Purpose: Receives OAuth authorization codes from providers
- Flow:
- Receives
codeandstateparameters from provider - Posts message to parent window with results
- Auto-closes popup window
- Receives
Backend Components
1. Integration Router
- Base Path:
/api/integrations - Key Endpoints:
GET /{provider}/login- Initiates OAuth flowPOST /{provider}/callback- Exchanges auth code for tokensGET /credentials- Lists user credentialsDELETE /{provider}/credentials/{id}- Revokes credentials
2. OAuth Base Handler
- Purpose: Abstract base class for provider-specific OAuth implementations
- Key Methods:
get_login_url()- Constructs provider authorization URLexchange_code_for_tokens()- Exchanges auth code for access tokensrefresh_tokens()- Refreshes expired access tokensrevoke_tokens()- Revokes tokens at provider
3. Credentials Store
- Purpose: Manages credential persistence and state
- Key Features:
- Redis-backed mutex for concurrent access control
- OAuth state token generation and validation
- PKCE support with code challenge generation
- Default system credentials injection
OAuth Flow Sequence
1. Flow Initiation
sequenceDiagram
participant User
participant Frontend
participant Backend
participant Redis
participant Provider
User->>Frontend: Click "Sign in with Provider"
Frontend->>Backend: GET /api/integrations/{provider}/login
Backend->>Redis: Store state token + code verifier
Backend->>Frontend: Return login URL + state token
Frontend->>Frontend: Open popup window
Frontend->>Provider: Redirect to authorization URL2. Authorization
sequenceDiagram
participant User
participant Provider
participant Callback
participant Frontend
participant Backend
User->>Provider: Authorize application
Provider->>Callback: Redirect with code + state
Callback->>Frontend: PostMessage with code + state
Frontend->>Backend: POST /api/integrations/{provider}/callback
Backend->>Provider: Exchange code for tokens
Provider->>Backend: Return access + refresh tokens
Backend->>Backend: Store credentials
Backend->>Frontend: Return credential metadata3. Token Refresh
sequenceDiagram
participant Application
participant Backend
participant Provider
Application->>Backend: Request with credential ID
Backend->>Backend: Check token expiry
Backend->>Provider: POST refresh token
Provider->>Backend: Return new tokens
Backend->>Backend: Update stored credentials
Backend->>Application: Return valid access tokenSystem Architecture Diagram
/* Detailed source-code truncated for AI context efficiency. */Data Flow Diagram
graph LR
subgraph "Data Types"
ST[State Token]
CV[Code Verifier]
CC[Code Challenge]
AC[Auth Code]
AT[Access Token]
RT[Refresh Token]
end
subgraph "Frontend Flow"
U1[User Initiates]
U2[Receives State]
U3[Opens Popup]
U4[Receives Code]
U5[Sends to Backend]
end
subgraph "Backend Flow"
B1[Generate State]
B2[Store in Redis]
B3[Validate State]
B4[Exchange Code]
B5[Store Credentials]
end
U1 --> B1
B1 --> ST
B1 --> CV
CV --> CC
B2 --> U2
U3 --> AC
AC --> U4
U5 --> B3
B3 --> B4
B4 --> AT
B4 --> RT
AT --> B5
RT --> B5Security Architecture
graph TB
subgraph "Security Layers"
subgraph "Transport Security"
HTTPS[HTTPS Only]
CSP[Content Security Policy]
end
subgraph "Authentication"
JWT[JWT Tokens]
STATE[CSRF State Tokens]
PKCE[PKCE Challenge]
end
subgraph "Storage Security"
ENC[Encrypted Credentials]
SEC[SecretStr Type]
MUTEX[Redis Mutex Locks]
end
subgraph "Access Control"
USER[User Scoped]
SCOPE[OAuth Scopes]
EXPIRE[Token Expiration]
end
end
HTTPS --> JWT
JWT --> USER
STATE --> PKCE
PKCE --> ENC
ENC --> SEC
SEC --> MUTEX
USER --> SCOPE
SCOPE --> EXPIRECredential Lifecycle
stateDiagram-v2
[*] --> Initiated: User clicks sign-in
Initiated --> Authorizing: Popup opened
Authorizing --> Authorized: User approves
Authorizing --> Failed: User denies
Authorized --> Active: Tokens stored
Active --> Refreshing: Token expires
Refreshing --> Active: Token refreshed
Refreshing --> Expired: Refresh fails
Active --> Revoked: User deletes
Failed --> [*]
Expired --> [*]
Revoked --> [*]
note right of Active: Credentials can be used
note right of Refreshing: Automatic process
note right of Revoked: Tokens revoked at providerOAuth Types Comparison
User Authentication (SSO) via Better Auth
- Purpose: Authenticate users to access the AutoGPT platform
- Provider: Better Auth, embedded in the Next.js frontend (supports Google, GitHub, and Discord SSO)
- Flow Path:
/login→/api/auth/*(Better Auth) → provider OAuth → back to the app - Session Storage: Better Auth-managed cookies
- Token Management: Automatic by Better Auth; the Python backend validates JWTs via the JWKS endpoint (
/api/auth/jwks) - User Experience: Single sign-on to the platform
API Integration Credentials
- Purpose: Grant AutoGPT access to user's third-party services
- Providers: Examples include GitHub, Google APIs, Notion, and others
- Full list in
autogpt_platform/backend/backend/integrations/providers.py - OAuth handlers in
autogpt_platform/backend/backend/integrations/oauth/
- Full list in
- Flow Path: Integration settings →
/api/integrations/{provider}/login→/auth/integrations/oauth_callback - Credential Storage: Encrypted in PostgreSQL
- Token Management: Custom refresh logic with mutex locking
- User Experience: Connect external services to use in workflows
Data Flow and Security
1. State Token Flow
- Generation: Random 32-byte token using
secrets.token_urlsafe() - Storage: Redis with 10-minute expiration
- Validation: Constant-time comparison using
secrets.compare_digest() - Purpose: CSRF protection and request correlation
2. PKCE Implementation
- Code Verifier: Random string generated using
secrets.token_urlsafe(128)(approximately 171 characters when base64url encoded, though RFC 7636 recommends 43-128 characters) - Code Challenge: SHA256 hash of verifier, base64url encoded
- Storage: Stored with state token in database (encrypted) with 10-minute expiration
- Usage: Enhanced security for public clients (currently used by Twitter provider)
3. Credential Storage
Structure:
pythonOAuth2Credentials: - id: UUID - provider: ProviderName - access_token: SecretStr (encrypted) - refresh_token: Optional[SecretStr] - scopes: List[str] - expires_at: Optional[int] - username: Optional[str]Persistence: PostgreSQL via Prisma ORM
Access Control: User-scoped with mutex locking
4. Token Security
- Storage: Tokens stored as
SecretStrtype - Transport: HTTPS-only, never logged
- Refresh: Automatic refresh 5 minutes before expiry
- Revocation: Supported for providers that implement it
Provider Implementations
Supported Providers
The platform supports various OAuth providers including GitHub, Google, Notion, Twitter, and others. For the complete list, see:
autogpt_platform/backend/backend/integrations/providers.py- All supported providersautogpt_platform/backend/backend/integrations/oauth/- OAuth implementations
Provider-Specific Security Considerations
- GitHub: Supports optional token expiration - tokens may be non-expiring by default
- Linear: Returns scopes as space-separated string, requiring special parsing
- Google: Requires explicit offline access scope for refresh tokens
- Twitter: Uses PKCE for enhanced security on public clients
Each provider handler implements the security measures defined in BaseOAuthHandler, ensuring consistent token management and refresh logic across all integrations.
Security Best Practices
1. Frontend Security
- Use popup windows to prevent URL tampering
- Validate state tokens before processing callbacks
- Clear sensitive data from window messages
- Implement timeout for OAuth flows (5 minutes)
2. Backend Security
- Store client secrets in environment variables
- Use HTTPS for all OAuth endpoints
- Implement proper scope validation
- Log security events without exposing tokens
- Use database transactions for credential updates
3. Token Management
- Refresh tokens proactively (5 minutes before expiry)
- Revoke tokens when credentials are deleted
- Never expose tokens in logs or error messages
- Use constant-time comparison for token validation
Error Handling
Common Error Scenarios
- Invalid State Token: 400 Bad Request
- Provider Configuration Missing: 501 Not Implemented
- Token Exchange Failure: 400 Bad Request with hint
- Webhook Conflicts: 409 Conflict, requires confirmation
- Credential Not Found: 404 Not Found
Error Response Format
{
"detail": {
"message": "Human-readable error description",
"hint": "Actionable suggestion for resolution"
}
}Testing Considerations
Unit Testing
- Mock OAuth providers for flow testing
- Test state token generation and validation
- Verify PKCE implementation
- Test concurrent access scenarios
Integration Testing
- Use provider sandboxes when available
- Test full OAuth flow with real providers
- Verify token refresh mechanisms
- Test error scenarios and recovery
Logging Guidelines
- Log flow initiation and completion
- Log errors with context (no tokens)
- Track provider-specific issues
- Monitor for suspicious patterns
Engineering Org Feature Map (`docs/engineering/org-feature-map.md`)
Organization Feature Map
A capability map for org/team tenancy on the AutoGPT Platform: what the
foundation shipped in the org-workspace PR series provides, and the feature
surface we expect to build on top of it. Each section lists the existing
hooks in the codebase that a feature would attach to, so scoping starts from
real anchor points instead of a blank page.
Foundation (shipped)
- Schema:
Organization,Team,OrgMember,TeamMember,OrgInvitation,TeamInvite,OrganizationProfile,OrganizationAlias,OrgBalance,OrgCreditTransaction,OrganizationSubscription,OrganizationSeatAssignment,TransferRequest,AuditLog - Request context:
RequestContext(org_id,team_id, role flags,seat_status) resolved per-request viaget_request_context, withX-Org-Id/X-Team-Idheader overrides and personal-org fallback - Execution context:
ExecutionContext.organization_id/team_id
threaded through graph executions, webhooks, schedules, copilot runs - Billing:
get_credit_model(user_id, org_id)routes toOrgCreditModelwhen an org is active; executor charging follows - Resources: graphs, executions, API keys, chat sessions, store
listings carryorganizationId/teamId; marketplace listings support
org ownership and cross-org transfers - Frontend: org/team switcher in the navbar,
OrgTeamProvider
bootstraps context on login, zustand store persists the active org/team - Migration: idempotent personal-org bootstrap
(create_orgs_for_existing_users) behind a Redis lock at startup
Deferred from the foundation series: seat enforcement (paywall/subscription
gating work owns this) and the NOT-NULL cutover migration fororganizationId columns.
1. Governance & policy controls
The headline ask: org/team admins control what members can do and which
capabilities agents may use.
| Feature | Sketch |
|---|---|
| Block enable/disable per org/team | New BlockPolicy table (orgId, teamId?, blockId, allowed, optional reason). Enforcement at three choke points: get_graph_blocks (filter the palette), validate_graph (reject save/activation of graphs using denied blocks), and executor node resolution (defense-in-depth refusal at run time, since policies can change after a graph is saved). Admin UI under org settings; team policy overrides org policy only toward more-restrictive. |
| Provider/credential policy | Same pattern keyed on provider name instead of block ID: restrict which integrations (HTTP request, code execution, specific SaaS providers) teams may attach credentials for. IntegrationCredential already carries an org relation to hang this on. |
| Model allowlist + per-team spending caps | Policy keyed on LLM model family; enforced where block costs resolve (block_usage_cost) and in copilot model selection. Caps check OrgCreditTransaction aggregates per team before dispatch. |
| Sensitive-action approval routing | ExecutionContext.sensitive_action_safe_mode and human-in-the-loop review already exist per-user; org version routes pending reviews to org/team admins instead of only the owner, using the existing review queue. |
| Safe-mode defaults per org | Org settings JSON already exists on Organization; add admin-managed defaults that seed human_in_the_loop_safe_mode / sensitive_action_safe_mode for all member executions, member-overridable only if policy allows. |
2. Identity & access
| Feature | Sketch |
|---|---|
| SSO (SAML/OIDC) | Supabase auth supports external IdPs; org-level config maps IdP groups → teams. OrganizationAlias (domain aliases) is the natural anchor for domain-capture ("anyone @acme.com joins Acme org"). |
| SCIM provisioning | Sync engine writing to OrgMember/TeamMember; deprovisioning must also revoke API keys and seats (OrganizationSeatAssignment). |
| Custom roles / RBAC | Today roles are three booleans (isOwner, isAdmin, isBillingManager) on both org and team membership. A Role table with permission grants replaces the booleans when customers need finer slicing (e.g. "can publish to marketplace but not manage billing"). RequestContext already centralizes the flag derivation, so the cutover is contained. |
| Audit log surfacing | AuditLog model exists with org relation; needs consistent write coverage across mutating routes (member changes, policy changes, transfers, key creation/revocation) and an admin-facing viewer with filtering/export. |
| Session & network policy | Org-enforced re-auth interval, IP allowlists for API-key traffic (check at validate_api_key where org context is already resolved). |
3. Resource management & collaboration
| Feature | Sketch |
|---|---|
| Team-shared agent library | AgentGraph.teamId exists; the library views currently scope by user. Team-visible library = library queries keyed by team membership plus visibility (enum already on graphs/library agents/chat sessions). |
| Org-shared credentials | IntegrationCredential already relates to Organization; needs scoping rules (org-owned credentials usable by member teams), admin management UI, and executor credential resolution preferring team → org → user. |
| Environment promotion (dev → prod teams) | TransferRequest covers cross-org moves; intra-org promotion is a lighter copy with provenance (forkedFromId already tracked on graphs). |
| Quotas per team | Run-concurrency and storage ceilings checked in add_graph_execution (org/team already threaded there) and workspace upload paths. |
| Shared chat / handoff | Chat sessions carry organizationId/teamId and a sharing layer (share tokens, linked execution shares) exists; team-visible sessions are a visibility policy on top. |
4. Billing & finance
| Feature | Sketch |
|---|---|
| Seat management & enforcement | Schema shipped (OrganizationSubscription, OrganizationSeatAssignment, seat_status on RequestContext); enforcement intentionally deferred to the paywall/subscription workstream. |
| Budgets & alerts per team | Aggregate OrgCreditTransaction by team; thresholds trigger the existing notification pipeline (low-balance handling already has a user-level analog in the executor). |
| Cost attribution dashboards | Transactions already record user, team, graph, and execution metadata — the work is aggregation endpoints + admin UI, not new write paths. |
| Org auto-top-up | Organization.topUpConfig field exists; needs the org analog of set_auto_top_up plus Stripe customer on org (stripeCustomerId present). |
| Invoicing (PO / net terms / tax IDs) | Stripe invoice plumbing per org customer; OrgCreditModel.list_invoices is stubbed for this. |
5. Marketplace
| Feature | Sketch |
|---|---|
| Org-private marketplace | Store listings already support owningOrgId; an org-internal visibility tier reuses the listing/version review flow with org-scoped queries instead of the public StoreAgent view. |
| Publishing approval chain | Submission review exists for platform admins; org version inserts an org-admin approval step before a listing leaves the org boundary. |
| Listing transfers | Shipped (TransferRequest source/target org with admin approval on both ends). |
6. Observability & compliance
| Feature | Sketch |
|---|---|
| Org-wide run history & analytics | Executions carry org/team; admin endpoints mirror the existing per-user execution listings with org-role authorization. |
| Data retention policy | Org settings-driven TTL sweeping executions/chat sessions; deletion paths (cascade rules on shares, workspaces) already exist to build on. |
| Export / portability | Org-scoped bulk export of graphs (graph export logic exists per-graph), execution history, and transactions. |
| Compliance evidence | Once AuditLog coverage is complete, scheduled export bundles cover the SOC2-style asks. |
Suggested build order
- Block/provider policy (per-team enable/disable) — highest admin demand,
clear enforcement choke points, no schema risk beyond one table - Audit log write coverage + viewer — prerequisite trust layer for
everything else admins touch - Team-shared library + org credentials — turns tenancy from isolation
into collaboration; biggest day-to-day user value - Budgets/caps + cost attribution — natural follow-on to org billing
already in place - RBAC generalization — only when boolean roles demonstrably block a
customer; cutover is contained inget_request_context - SSO/SCIM — enterprise gate; heavy lift, schedule against real demand
Engineering Tests (`docs/engineering/tests.md`)
Testing
The AutoGPT Platform uses several test frameworks at different layers:
- Backend tests use pytest.
- Frontend integration tests use Vitest, React Testing Library, and MSW. These
are the primary frontend tests. - End-to-end browser tests use Playwright.
- Design system components use Storybook stories for visual coverage.
Run these Bash commands from the repository root after installing dependencies:
Backend tests require Docker running and the platform environment files. Runmake -C autogpt_platform init-env to create them with fresh local secrets.
Playwright requires the backend running; pnpm test builds
and starts the frontend, reusing an existing server when available.
(cd autogpt_platform/backend && poetry run test)
(cd autogpt_platform/autogpt_libs && poetry run pytest)
(cd autogpt_platform/frontend && pnpm test:unit) # no servers required
(cd autogpt_platform/frontend && pnpm test) # E2E: start the backend firstFor the frontend's fast edit/test loop, run pnpm test:unit:watch from its workspace.
See the backend testing guide
and frontend testing guide
for patterns, commands, and guidance on choosing integration or end-to-end
coverage.
Before you start
Playwright tests require the backend server. pnpm test builds and starts the
frontend for you, or reuses an existing server. Wait for the backend to be ready
before running browser tests.
Running the Playwright tests
To run the tests, you can use the following commands:
Running the tests without the UI, and headless:
pnpm testIf you want to run the tests in a UI where you can identify each locator used you can use the following command:
pnpm test-uiYou can also pass --debug to the test command to open the browsers in view mode rather than headless. This works with both the pnpm test and pnpm test-ui commands.
pnpm test --debugIn CI, the full-stack workflow
runs the Chromium project headlessly with --retries=0 --trace=retain-on-failure,
overriding the shared config's CI defaults. Its JSON validator requires every test to have exactly one successful
attempt and rejects skipped, flaky, unexpected, or missing results and top-level
errors. See the shared settings in
playwright.config.ts.
Failure traces and test artifacts may contain requests, cookies, and session
state. Use isolated test accounts, never production accounts, and avoid exposing
credentials in traced requests or application output.
Continuous integration
Sharding
The backend workflow
and frontend workflow
define the shard matrix. CI divides the backend and frontend integration suites into disjoint parallel
shards. Backend shards select data, copilot, and util/executor paths; the
remainder uses normal workspace discovery excluding those paths, including new
top-level test files and directories. All shards explicitly load the backend's
pytest configuration. Backend shards use real PostgreSQL, RabbitMQ, and Redis,
with an isolated database, virtual host, and Redis cluster for each shard.
Each shard pays its own service-startup cost, so adding shards also multiplies setup work.
Caching
Dependency and browser caches are keyed to their lockfiles. Browser caches can
fall back to a previous cache for the same runner OS, then install missing versions.
Docker builds reuse unchanged layers. Full-stack cache export is limited to trusted dev pushes or an
explicit cache-publishing dispatch. Single-container builds use GHCR caches per
architecture instead of the repository Actions cache. Pull requests read the shareddev validation cache but never write one; on a pull request a failed GHCR login
or an unreadable cache gives a cold build, not a failed job. Manual dispatches read and update a
branch-specific cache, with the shared dev validation cache as a read-only fallback.
The helper writes that shared tag only on a push to dev, but repository workflows
with package-write permission can overwrite it. Release publication therefore
builds each platform digest cold, without importing this cache, and smoke-tests
and scans the exact pushed digest before publishing the multi-platform manifest.
Validation cache exports are best-effort; failed image builds, smoke tests, and
security scans still fail validation.
The generated E2E seed-data cache is keyed to the files that determine the
seeded data, so it restores across commits that do not change them. Warm
caches may avoid repeated dependency downloads, seed generation, or cache
export, but they never bypass tests, linting, type checks, coverage collection, image builds, image
smoke tests, or security scans.
Test reports
Every test job in the validation workflows uploads a JUnit XML or JSON report,
plus coverage artifacts where applicable; this does not describe release-only
publication jobs. The JUnit validator
and Playwright validator
fail validation on missing or malformed reports,
zero discovered tests, count mismatches, failures, and errors. Frontend
integration and Playwright reports also reject skipped tests; the
single-container appliance workflow
allows one exact test ID to skip when Bash 3 is unavailable. The
unittest reporter
enforces this policy, including class, module, and subtest skips. Backend skips must match exact test IDs in.github/scripts/backend-allowed-skips.json, seeded from the 136 existing skips
per Python version in run 33918906861; a new skip fails validation. Do not add an
ID merely to make CI green: investigate and review the changed skip policy.
An entirely skipped backend shard also fails.
Wrapped commands and malformed backend/frontend reports produce a synthetic
machine-readable error. Single-container validation instead uploads its original
reports and leaves status.json marked validated: false if verification fails.
Manual dispatch
The backend, frontend, full-stack, single-container, Classic, CodeQL, and block-docs
workflows have no required manual-dispatch inputs. The separate advisory PR-overlap
workflow still requires pr_number; it is not a CI-status checker. Backend
dispatches can refresh a stacked PR's backend coverage withgh workflow run platform-backend-ci.yml --ref -f pr_number=;
the optional pr_number only selects the Codecov upload target. Full-stack
dispatches import caches by default; set publish_build_cache only when a test
commit should deliberately refresh them. A manually dispatched single-container
run builds, smoke-tests, and scans the image but cannot publish it; publication
jobs are reachable only from a release event. Single-container validation also
runs on every dev push and release, while pull requests trigger it only when
appliance packaging inputs change.
When CI fails
Open the failed Actions run and download its test-report and coverage artifacts
from the run summary. JUnit failures identify the test and traceback; validator
errors identify missing, malformed, or unexpected skipped results. Do not treat
a successful upload as proof the tests passed.
To reproduce a backend shard after configuring the same local services, runpoetry run pytest -c pyproject.toml backend/copilot from the backend workspace
(substitute the failing shard's paths). For a frontend shard, runpnpm test:unit --shard=1/4 from the frontend workspace, using its CI shard number.
Debugging tests
There's a lot of different ways to debug tests.
My preferred is a mix of playwright's test editor and vscode.
No matter what you do, you should always double check that your locators are correct. Playwright will often "time out" and not give you the error message that the locator is incorrect because it can't find the element. You can do this via devtools on your browser and they should be visible on the elements tab when you use the inspect and select elements tools.
Using the playwright test editor
If you need to debug a test, you can use the below command to open the test in the playwright test editor. This is helpful if you want to see the test in the browser and see the state of the page as the test sees it and the locators it uses.
pnpm test --debug --test-name-pattern="test-name"Using vscode
You can install the Playwright Test for VSCode extension to get autocomplete for the playwright api (id: ms-playwright.playwright).
Installing this will enable the Test Explorer view in vscode which allows you to run, debug, and view all tests in the current project. Adding breakpoints to your tests and running them will automatically open the test editor with the correct context.
Setting up for generating tests
With playwright, you can generate tests from existing recordings of user sessions. This is useful for creating tests that are more representative of how a user would interact with the application. We generally use this for checking what ids stuff will have and what needs ids to be added.
It is super annoying to continuously login so I highly recommend using a saved session for your tests.
This will save a file called .auth/gentest-user.json that can be loaded for all future gentests so that you don't have to login every time.
Saving a session for gen tests to always use
pnpm gentests --save-storage .auth/gentest-user.jsonStop your session with CTRL + C after you are logged in and swap the --save-storage flag with --load-storage to load the session for all future tests.
Loading a session for gen tests to always use
pnpm gentests --load-storage .auth/gentest-user.jsonHow to make a new test
Tests are composed of page objects and test files.
Do not commit skipped or todo Vitest/Playwright tests. Fix the prerequisite or
ask maintainers to review an explicit policy exception; do not hide a failing
test behind an environment check.
The current Playwright config discovers *-happy-path.spec.ts files underautogpt_platform/frontend/src/playwright/. Other filenames are not collected;
follow that naming pattern for tests intended to run in CI.
A page object is a class that contains methods for interacting with a page.
A test file is a file that contains tests for a page or a set of pages.
Making a new Page Object
For tests, we use the page object model. This is a pattern where each page is a class that contains all the methods and locators for that page.
This is useful for keeping your tests organized and easy to read as well as ensuring that your tests only need to be updated in one place when the UI changes.
You should make a new page object (only when needing to add a new page, or UI element that is across multiple tests) using the following example.
We extend the BasePage class which contains shared methods for pages that have the common functionality like a navbar. If you add something like that (for example a sidebar) you should add it to the BasePage class. Otherwise, you should make a new page object.
Each page object should be in its own file and be named like page-name.page.ts.
A page object should contain methods that are actions that a user can do on that page. For example, clicking a button, filling out a form, etc. It should also contain the various helpful abstractions that are unique to that page. For example, the BuildPage has a method to connect blocks together.
This is a shortened example of a page object for the profile page:
--8<-- "autogpt_platform/frontend/src/playwright/pages/profile.page.ts:ProfilePageExample"
}Making a new Test File
For tests, we use our page objects to create tests. Each test file should be in autogpt_platform/frontend/src/playwright/ and be named like test-name-happy-path.spec.ts. A test file can contain multiple tests. Each of which should be related to the same conceptual function. For example, a test file for the build page could have tests for building agents, creating inputs and outputs, and connecting blocks. If you wanted to specifically test building agents, you could make a new test called building-agents-happy-path.spec.ts.
Tests can inherit from one or more page objects, have pre-actions, and have post-actions, as well as many other features. You can learn more about the different features and how to use them here.
A good focused (unit or single concept) test will:
- Have a short name that describes what it is testing
- Have a single concept (building a agent, adding all blocks, connecting two blocks, etc.)
- Check pre-conditions, actions, and post-conditions, as well as have multiple validations along the way
A good non-focused (integration or multiple concepts) test will:
- Have a short name that describes what it is testing
- Have multiple concepts (building agents, creating-?exporting->importing->running an agent, connecting blocks in multiple ways with multiple inputs and outputs, etc.)
- Have a clear user experience that they are making sure works (for example, clicking the build button and making sure the agent is built, or clicking the export button and making sure the agent is exported and shows up in the monitoring system)
- Not focus on a single concept, but instead test the flow of the application as a whole. Remember you're not testing the pixel perfect UI, but the user experience.
A good test suite will have a healthy mix of focused and non-focused tests.
Example focused test
This uses the current coverage fixture and builder page object. Save new cases
under autogpt_platform/frontend/src/playwright/ using the*-happy-path.spec.ts filename pattern.
import { expect, test } from "./coverage-fixture";
import { E2E_AUTH_STATES } from "./credentials/accounts";
import { BuildPage } from "./pages/build.page";
test.use({ storageState: E2E_AUTH_STATES.builder });
test("builder saves an agent", async ({ page }) => {
const buildPage = new BuildPage(page);
await buildPage.createAndSaveSimpleAgent("Example Agent");
await expect(page).toHaveURL(/flowID=/);
expect(await buildPage.isRunButtonEnabled()).toBeTruthy();
});The coverage fixture preserves coverage collection. The storage state supplies
an isolated test account, the page object encapsulates UI interactions, and the
assertions verify both navigation and the saved agent's runnable state.
See the current
builder tests
for full scenarios and timeout choices.
Passing information within a test
Keep tests independent: do not make one test consume an ID created by another.
Use local variables or fixtures to share setup within a test, and usetestInfo.attach for safe diagnostics. Avoid attaching credentials or session
state. See Playwright's
fixtures guide
and TestInfo API.
See Also
- Writing Tests
- Code Generation
- Test UI Mode
- Trace Viewer
- Getting Started with VSCode
- Debugging Tests
- Test Fixtures
- Global Setup and Teardown
- Test Parameterization
- Test Events
- Test Components
- Test Sharding
- Accessibility Testing
- Authentication
- Mocking
- Mock Browser APIs
- Code Generation
- Pages
- Test Annotations
Engineering Tracking Plan (`docs/engineering/tracking-plan.md`)
PostHog Tracking Plan
The single list of PostHog events: for every user action, one event name, one
sender and one set of properties. Activation Metrics & Experiments
explains how PostHog fits next to the SQL views, Looker, LaunchDarkly and
DataFast; this page is the event contract.
Event names follow the product analytics plan, Every Second Counts (owner:
Toran), section "What we will record". Where that plan names an event for an
action, this list uses its name; Differences from the analytics plan
lists what is not aligned yet.
In code the names live in two modules. Browser call sites may still pass a
literal (trackBrainDump("brain_dump_started")), which is type-checked
against these modules, and backend funnel data_index keys embed the name
too (briefing_generated:, briefing_delivered:). A
rename therefore has to search for the old string, not just edit the modules:
| Sender | Module | Pin test |
|---|---|---|
| Backend | autogpt_platform/backend/backend/util/posthog_events.py (PostHogEvent, PlannedPostHogEvent) |
posthog_events_test.py |
| Browser | autogpt_platform/frontend/src/services/analytics/posthog-events.ts (PostHogEvent and its groups, PlannedPostHogEvent) |
__tests__/posthog-events.test.ts |
Rules
Naming
- Use the analytics plan's name. When the plan names an event for an
action, that is the name, even if we used to send something else. - Otherwise the name is
object_action, snake_case, action in the past
tense:signup_completed,checkout_started,paywall_viewed. - Chat (Autopilot) events start with
chat_, nevercopilot_. The trial
lifecycle istrial_*. - Rename once, then never again. PostHog stores the raw string, so a
rename empties every insight, funnel and cohort built on the old name, and
past events cannot be backfilled. The live names that differed from the
plan were renamed together in SECRT-2722 (see Renamed); the
pin tests reserve the old names and fail on any change to a live one. - Put a new name in
PlannedPostHogEventwhen it is agreed, and move it into
the live list in the change that starts sending it.
Who sends it
- Outcomes are sent by the backend: something was created, charged, run,
hired or delivered. The backend sees every path (web, API, copilot, Slack,
Telegram, webhooks), cannot be blocked by an ad blocker, and is what theanalytics.*SQL views count. - Interactions are sent by the browser: views, clicks, dismissals and
client-side timings that the backend never sees. - One action has one event. Where two emitters describe the same action,
the backend event survives and the other stops being sent (see
Removed).
Base properties
Every event must carry:
| Property | Value |
|---|---|
environment |
settings.config.app_env on the backend (local, dev, prod), the matching app environment in the browser. |
source |
Which emitter sent it: chat_copilot (the events copilot/tracking.py captures itself; chat_message_sent goes through product_analytics and is platform), platform (every other backend emitter), web (the browser). |
Every backend event goes through capture() inbackend/util/posthog_client.py, which applies both last, so a caller cannot
overwrite them. The browser registers them once as PostHog super properties
(src/services/analytics/posthog-base-properties.ts), again after reset()
at logout; a Vercel preview reports environment: preview, while the dev
deployment, which is Vercel's staging custom environment rather than a
preview, reports environment: dev.
source is reserved for the emitter. The three events that used it for
something else now use hire_started.surface,expert_recommended.team_source and workflow_installed_on_expert.workflow_source.
Identity
distinct_idis the platform user id for every backend event, the same
id the browser passes toposthog.identify(user.id), so both halves land
on one person.- Before login the browser uses the first-party anonymous id
(src/services/analytics/anonymous-id.ts), passed toposthog.initas the
bootstrapdistinctID.identifymerges it into the user at login, andresetAnalyticsIdentitymints a fresh one at logout. - The browser waits for analytics consent (Cookiebot,
src/providers/posthog/posthog-consent.ts). PostHog starts opted out:
without consent, browser events are dropped,identifyis not called and
the anonymous id lasts only for the page load. Backend events do not
depend on it. - No synthetic ids. A backend event with no user is dropped
(posthog_client.capturedoes it for every emitter), since a made-up id
creates a person nobody can merge. - No email or name in event properties. Identify ids only. The email and
display name are person properties set byidentifyand nowhere else.
(expert_hired.nameis the expert's display name, not a person's.)
What counts as a task
The analytics.* views (user_task_daily, user_lifecycle,retention_task_weekly, unit_economics_monthly) define a task as a unit of
work a person asked for now:
- a top-level, non-dry-run agent run whose
triggerSourceismanualorapi(or NULL, for rows older than the column), expert workflow runs
included; or - a
usermessage in an Autopilot or expert chat whose session origin is notautomationand whose kind is notdream.
A run the copilot started (triggerSource = copilot) is not a second
task: the chat turn that asked for it already counts. agent_run_started does fire
for copilot-started runs (with trigger: copilot), because it measures
human-initiated runs, not tasks. user_lifecycle.agent_runs_human_total is
its SQL twin and includes copilot runs too.
The same definition as a PostHog filter:
event IN ('agent_run_started', 'chat_message_sent')
AND coalesce(properties.via, '') != 'copilot'In the insight UI: events agent_run_started and chat_message_sent,
filtered by via "is not" copilot. Chat turns carry no via, so
the filter must keep events where it is unset. The emitters already leave
out dry runs, sub-graph runs, schedule and webhook runs, and automation-origin
turns.
Automated work is measured separately: schedule_fired and trigger_fired.
Status legend
| Status | Meaning |
|---|---|
live |
Sent today under this name. Keep it. |
add |
Not sent yet; the name is in PlannedPostHogEvent until the change that starts sending it. |
Events that are no longer sent are listed under Removed, with
what replaced them.
Acquisition
| Event | Sender | Status | Required properties | Fires when |
|---|---|---|---|---|
$pageview |
browser | live | $current_url |
A route or query string changes (PostHogPageViewTracker). |
tour_started |
browser | live | — | The public /tour page is opened (once per tab). |
tour_scenario_started |
browser | live | scenario |
A tour scenario starts playing. |
tour_scenario_completed |
browser | live | scenario |
A tour scenario reaches its end. |
tour_cta_clicked |
browser | live | label (free-trial, signup, pricing, another-scenario, self-host, share), placement where the CTA has one, surface (tour, marketplace) on sidebar-card clicks |
A tour or logged-out marketplace call to action is clicked. |
marketing_opted_out |
browser | live | surface (signup) |
Someone refuses marketing email in the app. signup: "opt out" is clicked in the legal line under the signup buttons (not "Undo"); nothing else is sent, since there is no user yet, so it only gives the opt-out rate. The refusal itself is stored on the user (marketingOptOutAt). |
signup_completed |
backend | live | signup_method (the auth provider, e.g. email, google: from the user's first Better Auth account row, where credential is reported as email, else from a Supabase token's app_metadata.provider; omitted when neither has it) |
The user row is created (data/user.py), whichever request creates it. |
Sidebar CTA clicks retain the tour event names for compatibility. Filtersurface to distinguish the marketplace from the tour; older sidebar events
do not carry this property. For tour-only sidebar reports after deployment,
require surface = tour. The signup CTA uses free-trial in cloud mode andsignup on self-hosted installations, where creating an account starts no trial.
The tour funnel also goes to DataFast (tour_start, tour_scenario_start,tour_scenario_complete, tour_cta_click); the PostHog events mirror it with
the same properties and nothing identifying in them: /tour is public and
pre-signup, so they land on the visitor's anonymous id. /tour sends the
DataFast goals without consent (SECRT-2713), but the PostHog events wait for
analytics consent like every browser event, so the two funnels count
differently.
Onboarding and activation
| Event | Sender | Status | Required properties | Fires when |
|---|---|---|---|---|
onboarding_step_viewed |
browser | live | step (team, autopilot, role, pain_points, connect, hire, preparing) |
A wizard step is shown (once per tab, same keys as the DataFast onboarding_<step> goals). The paywall step reports paywall_viewed instead. |
onboarding_completed |
backend | live | — | ONBOARDING_COMPLETE is first recorded for the user (complete_onboarding_step). |
brain_dump_started |
browser | live | — | Recording starts. |
brain_dump_completed |
browser | live | input_mode, duration_secs and finalize_latency_ms (voice) or chars (typed) |
A dump was accepted and the wizard advances. finalize_latency_ms is the finalizeBrainDump() round trip, timed after the upload flush. |
brain_dump_canceled |
browser | live | — | Recording is cancelled. |
brain_dump_skipped |
browser | live | — | The step is skipped. |
brain_dump_recovery_shown |
browser | live | parts |
A saved partial recording is offered back. |
brain_dump_recovery_used |
browser | live | — | The saved recording is used. |
brain_dump_retry |
browser | live | attempt |
A failed finalize is retried. |
brain_dump_restarted |
browser | live | — | Recording starts over. |
brain_dump_download |
browser | live | — | The recording is downloaded after a failure. |
brain_dump_permission_denied |
browser | live | — | Microphone permission is refused. |
brain_dump_typed_fallback |
browser | live | reason |
The user types instead of speaking. |
transcription_failed |
browser | live | error_code, finalize_latency_ms |
Finalize returns a failure. |
welcome_dialog_closed |
browser | live | — | The first-landing welcome dialog closes. |
capability_card_viewed |
browser | live | card_index, deck |
A capability card is shown. |
capability_cards_completed |
browser | live | card_index, deck |
The deck is finished. |
capability_cards_skipped |
browser | live | card_index, deck |
The deck is skipped. |
intro_path |
browser | live | path |
The intro path (A/B) is chosen. |
intro_followup_sent |
browser | live | chars |
First message after the intro card. |
later_dump_completed |
browser | live | — | A brain dump sent later from the composer. |
expert_recommended |
browser | live | template_id, position, team_source |
A recommended expert card is shown. |
expert_recommendation_clicked |
browser | live | template_id, position |
A recommended expert card is clicked. |
hire_step_continued |
browser | live | hired, recommended |
The hire step is left. |
tab_intro_shown |
browser | live | tab |
A tab intro card is shown. |
tab_intro_cta_clicked |
browser | live | tab, cta |
Its primary CTA is used. |
tab_intro_dismissed |
browser | live | tab |
It is dismissed any other way. |
integration_connected |
backend | live | provider, credential_type, method |
A credential is stored (OAuth, key, device code). |
credential_oauth_started |
backend | live | provider |
The backend issues an OAuth login URL (GET /api/integrations/{provider}/login). Not Codex. |
credential_oauth_exchange_failed |
backend | live | provider, status_code (unset for an unexpected error), failure_class (invalid_state, provider_unavailable, token_exchange, credential_merge), detail (redacted, at most 200 characters) |
POST /api/integrations/{provider}/callback returns an error, on any path. Not Codex. |
credential_card_never_rendered |
browser | live | provider, failure_class |
A provider is missing from the provider map. |
credential_oauth_popup_blocked |
browser | live | provider, failure_class |
Both the popup and the new tab were blocked. |
credential_oauth_flow_timed_out |
browser | live | provider, failure_class |
The OAuth flow timed out. |
credential_scope_shortfall_blocked_selection |
browser | live | provider, failure_class |
The granted scopes are narrower than required. |
credential_proceed_stuck_after_connect |
browser | live | provider, failure_class |
A connected credential never reached the card. |
Engagement
| Event | Sender | Status | Required properties | Fires when |
|---|---|---|---|---|
agent_run_started |
backend | live | graph_id, graph_exec_id, via (manual, api, copilot), via_ref, preset_id; an expert's workflow run adds expert_id and kind: workflow_run |
A person starts an agent run. |
chat_message_sent |
backend | live | chat_session_id, origin (web, or the bot platform the chat was opened from: slack, discord, ...), kind: chat_turn, message_length; expert_id in an expert chat |
A person sends a message in an Autopilot or expert chat. |
agent_run_finished |
backend | live | status (completed, failed), graph_id, graph_exec_id, via, expert_id, cost_cents, duration_seconds, is_subgraph_run; failure_reason when failed |
A run reaches COMPLETED or FAILED (sub-graph and automated runs included). A top-level expert run is expert_id set and is_subgraph_run false. Deduplicated on graph_exec_id, so a requeue or resume that finishes the same run again sends no second event. |
schedule_created |
backend | live | schedule_id, target (agent, autopilot, expert), expert_id, cron, is_recurring, run_at, graph_id, chat_session_id |
Any schedule is registered, from any surface. |
chat_tool_called |
backend | live | chat_session_id, tool_name, tool_call_id |
The copilot calls a tool. |
chat_outcome |
backend | live | outcome_type (agent_run_success, schedule_created), chat_session_id; agent_run_success: graph_id, execution_id, library_agent_id; schedule_created: target (agent: graph_id, schedule_id, cron, library_agent_id; followup: schedule_id, target_chat_session_id, is_recurring) |
A moment of value in a chat: the copilot started a (non-dry) run or created a schedule for the user. agent_run_started / schedule_created still count the run or schedule itself. |
chat_library_check_outcome |
backend | live | chat_session_id, outcome, matches_count, top_score |
The create-agent library check ends. |
voice_mode_started |
browser | live | entry |
Voice mode is switched on. |
voice_mode_stopped |
browser | live | turns, state |
Switched off by the user. |
voice_mode_timed_out |
browser | live | turns, state |
Closed by the silence timeout. |
voice_turn_sent |
browser | live | turn_index, transcript_chars, transcribe_latency_ms |
A spoken turn is sent. |
voice_turn_dropped |
browser | live | reason (vad_misfire, transcribe_failed, filler_or_empty, interrupted); transcribe_latency_ms with filler_or_empty; turn_index and first_sound_latency_ms (null when nothing played yet) with interrupted |
A spoken turn is discarded, or with interrupted a sent turn's reply is cut off by switching voice mode off or leaving the page. A sent turn ends in exactly one of voice_turn_completed or this. |
voice_turn_completed |
browser | live | turn_index, first_sound_latency_ms (null when nothing played) |
The mic reopens after the reply. |
voice_transcribe_retried |
browser | live | turn_index |
A failed transcription is retried. |
voice_recording_downloaded |
browser | live | turn_index |
The audio is downloaded instead. |
voice_mode_permission_denied |
browser | live | stage |
Microphone permission is refused. |
voice_mode_error |
browser | live | stage |
The VAD, synthesis or send failed. |
experts_section_viewed |
browser | live | — | The marketplace experts shelf renders with results. |
expert_profile_opened |
browser | live | template_id |
An expert profile page opens. |
hire_started |
browser | live | template_id, surface (onboarding, expert_page) |
A hire button is clicked. |
hire_flow_abandoned |
browser | live | template_id, stage |
The hire dialog is closed before hiring. |
expert_hired |
backend | live | expert_id, template_id, name, failed_preloads_count, surface (onboarding, expert_page, copilot; unset for other API callers) |
An expert is hired or an archived one revived, from any surface (experts_db.hire_expert). Sent once the hire's background setup settles (_run_hire_setup), so failed_preloads_count is known; a revival with nothing to set up sends it at once. An idempotent re-hire of an active expert does not fire. |
hire_failed |
backend | live | template_id, failed_preloads_count |
Hiring raised. |
expert_thread_created |
browser | live | expert_id |
A new expert chat is created. |
writing_style_added |
backend | live | expert_id |
A writing style is saved on an expert. |
workflow_installed_on_expert |
backend | live | expert_id, workflow_source (library, marketplace), library_agent_id or store_listing_version_id |
A workflow is attached to an expert. |
home_viewed |
browser | live | — | The home dashboard renders with data. |
home_attention_actioned |
browser | live | kind, action |
A "needs you" item is approved or declined. |
home_team_member_clicked |
browser | live | expert_id |
A team member row is clicked. |
listing_added_to_library |
backend | live | store_listing_version_id, graph_id, library_agent_id |
A marketplace agent is added to the library for the first time. Only a user adding a listing counts (the library route and the add-to-library block). Restoring an entry the user already had does not, and neither does a system install: an add inside another write's transaction, an expert's hire preloads, or attaching a marketplace workflow to an expert (that is workflow_installed_on_expert). |
listing_downloaded |
backend | live | store_listing_version_id, graph_id |
A signed-in user downloads a marketplace agent file. Signed-out downloads have no user and are not sent. |
Monetization
| Event | Sender | Status | Required properties | Fires when |
|---|---|---|---|---|
paywall_viewed |
browser | live | surface (onboarding, paywall_gate, billing) |
A paywall or plan picker is shown (once per tab per surface): the onboarding subscription step, the in-app paywall modal, the billing page's plan card. The pricing arm comes from PostHog's own $feature/... properties. |
plan_selected |
browser | live | subscription_tier (PRO, MAX, BUSINESS for Team), billing_cycle, surface; pricing_variant on onboarding, only when PostHog has assigned an arm (omitted while flags load and for users outside the rollout, who are not in control) |
A plan is picked: a plan card on either paywall, or Upgrade / Downgrade on the billing page. |
billing_portal_opened |
browser | live | surface (billing for the plan card, billing_payment_method for the payment method card) |
The Stripe billing portal is opened. |
checkout_started |
backend | live | checkout_kind (subscription, top_up, trial), subscription_tier, billing_cycle, surface (sent by the client; unset for the low-credit top-up dialog and the legacy /profile/credits page), $feature/<experiment> for every recorded experiment arm |
A Stripe Checkout session is created and its URL returned (subscription, credit top-up or trial card setup). A paid plan changed in place opens no Checkout and sends nothing. |
checkout_abandoned |
browser | live | checkout_kind, surface |
The user returns from Stripe Checkout without paying (the cancel URL: subscription=cancelled, topup=cancel, trial=cancelled, or the paywall modal's paywall_checkout=cancelled marker). Browser-sent: Stripe only reports the expiry a day later. A trial abandonment is sent once per tab per surface until the next trial checkout starts, so a refresh on the cancel URL does not count twice but a second real abandonment does. |
trial_offer_viewed |
browser | live | trial_offer_version, subscription_tier, trial_duration_days, surface |
A trial offer card is shown. |
trial_cancel_popup_viewed |
browser | live | days_left (whole days of trial access left, rounded up; 1 under a day) |
The "Cancellation confirmed" popup opens, once, right after a cancel that sets the trial to cancel at its end, so trial access continues until then. A reload never reopens it. |
trial_resume_clicked |
browser | live | days_left |
Resume trial is clicked on a trial set to cancel at its end, in the popup or on the trial card. Sent on the click; trial_resumed (backend) confirms it took effect. |
trial_subscribe_now_clicked |
browser | live | days_left |
Subscribe now is clicked in the cancellation popup. It opens Billing. There, the trial's own plan converts the trial at once on the saved card (trial_converted, no checkout_started); any other plan, or a card that asks for authentication, goes through Stripe Checkout (checkout_started), which ends the trial without a trial_ended. |
trial_started |
backend | live | trial_id, trial_offer_version, subscription_tier, billing_cycle, trial_duration_days |
The trial starts. Like every trial lifecycle event, it is sent only when its notification email is queued. |
trial_ending |
backend | live | as above | The reminder window opens. |
trial_canceled |
backend | live | as above | The trial is set to cancel at its end: the cancellation is scheduled, trial access continues until the end date, and the card is not charged. |
trial_resumed |
backend | live | as above | A trial set to cancel is resumed before its end date, so it converts to paid at its end as first agreed. |
payment_failed |
backend | live | as above | The conversion charge fails. |
trial_converted |
backend | live | as above | The trial converts to paid. |
trial_ended |
backend | live | as above | The trial ends without converting. Not sent when the trial subscription ends because another plan was bought while the trial was set to cancel; that plan's checkout_started and payment_succeeded cover it. |
subscription_changed |
backend | live | change_type: upgrade, previous_subscription_tier, subscription_tier, billing_cycle |
A paid tier upgrade takes effect. Downgrades and cancellations are not sent here yet. |
payment_succeeded |
backend | live | subscription_tier, billing_cycle, amount_cents (the invoice's amount_paid), currency |
A subscription invoice is paid. Deduplicated on the invoice id: Stripe delivers both invoice.payment_succeeded and invoice_payment.paid for one invoice, and redelivers either, so revenue is counted once. |
topup_completed |
backend | live | amount_credits, top_up_type, amount_cents (the Checkout total or the auto top-up charge), currency |
Credits are bought. |
subscription_cancellation_scheduled |
backend | live | subscription_tier |
A paid plan is set to cancel at period end. |
subscription_ended |
backend | live | subscription_tier, billing_cycle, reason (Stripe cancellation_details.reason: cancellation_requested, payment_failed, payment_disputed; payment_failed too when our own payment-failure handler cancelled the subscription because the balance could not cover a failed renewal, which Stripe would stamp cancellation_requested) |
A paid subscription ends (Stripe customer.subscription.deleted), once per subscription, alongside the ended email. A trial that ends unconverted is trial_ended instead; a trial ended by buying another plan sends neither. |
subscription_tier_reconciled |
backend | live | direction, previous_subscription_tier, subscription_tier, via |
Ops signal: Stripe and the stored tier disagreed. Not a user action; keep out of funnels. |
The paywall funnel in PostHog is paywall_viewed → plan_selected →checkout_started → payment_succeeded, with checkout_abandoned
as the way out. The onboarding paywall's DataFast goals (paywall_view,paywall_checkout_cancelled) keep firing alongside.
Browser events captured before PostHog captures (a page's mount effect on a
full page load, such as the return from Stripe, runs before posthog.init
and before the consent opt-in) are held bysrc/services/analytics/posthog-capture.ts while the consent answer is
unknown. With analytics granted (an earlier answer, or a region where
Cookiebot consents on its own once uc.js loads) they are sent, with the
time they happened, once PostHog is opted in. When analytics is declined,
there is no consent manager, or the banner is asking, they are dropped: an
answer given on the banner covers what happens after it, not what was held
before it. The once-per-tab guards on paywall_viewed, checkout_abandoned,onboarding_step_viewed and tour_started are set only when the event is
sent or dropped this way, not while it is held.
Retention
| Event | Sender | Status | Required properties | Fires when |
|---|---|---|---|---|
schedule_fired |
backend | live | schedule_id, target, expert_id, graph_id, graph_exec_id or chat_session_id |
A schedule produces work. |
trigger_fired |
backend | live | webhook_id, graph_id, graph_exec_id, expert_id, preset_id, target |
A webhook produces a run. |
briefing_generated |
backend | live | run_count, decision_count, has_content |
A morning briefing is composed (or found empty). |
briefing_delivered |
backend | live | briefing_id |
It is posted to the user's thread. |
briefing_opened |
browser | live | — | The briefing renders on home. Not the plan's briefing_opened_in_chat, which is opening it in the chat thread. |
briefing_outcome_clicked |
browser | live | status |
An outcome row in the briefing is clicked. |
expert_fired |
backend | live | expert_id |
An expert is fired. |
Return visits are $pageview; account-level retention is computed inretention_task_weekly and user_lifecycle, not sent as events.
LLM analytics
AutoPilot's LLM calls and the ratings of its replies, in PostHog's LLM
analytics (SECRT-2730). Both are sent server-side for every user, whatever
their cookie consent, like every backend event. The event names are
PostHog's own, not the analytics plan's.
| Event | Sender | Status | Required properties | Fires when |
|---|---|---|---|---|
$ai_generation |
OpenRouter Broadcast | live | set by OpenRouter from the request (backend/copilot/llm_attribution.py): distinct_id = the request's user (the platform user id), $ai_session_id = its session_id (the chat session id), $ai_span_name = trace.generation_name (copilot-title, copilot-sdk, copilot-baseline), metadata_environment (local, dev, prod), metadata_impersonated; model, tokens, cost and latency from OpenRouter |
An AutoPilot LLM call goes through OpenRouter: a session title, an SDK turn (main agent, sub-agents and the CLI's compaction, via CLAUDE_CODE_EXTRA_BODY) or a baseline turn. |
$ai_metric |
backend | live | $ai_metric_name (user-feedback, copy), $ai_metric_value (1 thumbs up or copy, 0 thumbs down), $ai_session_id and chat_session_id (the chat session id), message_id (the reply's row id), message_sequence, feedback_id (the stored rating, shared with its Langfuse score), impersonated; $ai_feedback_text when a thumbs-down has a comment |
A reply is rated (POST /api/chat/sessions/{id}/feedback, copilot/feedback.py). Rating a reply again sends another event; the latest per feedback_id stands. |
What joins them:
- Person. A generation's
distinct_idand a rating's are both the
platform user id, the id every other event andposthog.identifyuse, so
both land on the person with their product events and replays. - Chat.
$ai_session_idis the chat session id on both, so a chat's
generations and its ratings group in the Sessions tab and join on it.message_idsays which reply was rated; a rating carries no$ai_trace_id, since Broadcast assigns the generations' trace ids. - Environment. OpenRouter forwards our
tracekeys asmetadata_<key>,
so a generation's environment ismetadata_environment, not theenvironmentbase property;$ai_metrichas both base properties as usual. - Impersonation. A turn or rating an admin made while impersonating the
user (X-Act-As-User-Id) is still sent, as the user, withmetadata_impersonated/impersonatedtrue, so it can be filtered out.
The mark is kept for a turn queued behind the running-turn cap and for
sub-sessions such a turn starts. A follow-up sent while a turn runs takes
that turn's mark, and an engine-switch continuation or an approval wake
starts unmarked.
Calls that skip OpenRouter never reach Broadcast and have no$ai_generation: direct Anthropic and Claude Code subscription deployments,
a user's connected Codex account and the local transport. Auxiliary calls
that are not attributed yet (the baseline path's context compaction, the
action gate's classifiers, web search, Graphiti memory) arrive without the
user or chat when their client goes through OpenRouter.
Experiments
| Event | Sender | Status | Required properties | Fires when |
|---|---|---|---|---|
$feature_flag_called |
browser (posthog-js) | live | set by PostHog | A PostHog flag is read (useExperiment). |
feature_flag_mismatched |
browser | live | flag, launchdarkly (value, resolved), posthog (value, resolved) |
Ops signal: with both flag vendors configured, LaunchDarkly and PostHog resolved a flag to different values (useDualFlag). Not a user action; keep out of funnels. |
Arms are also stored in the database (analytics.experiment_assignment),
so an experiment can be read in PostHog and Looker alike.
Person properties
The backend keeps these on the person (distinct id = platform user id) with
a $set event (PostHogEvent.SET_PERSON_PROPERTIES), sent frombackend/data/posthog_lifecycle_sync.py after signup, tier changes and every
Stripe subscription sync, and by a daily sweep at 04:15 UTC. The lifecycle
events above mark the moment something happens; these properties hold the
person's current state, so a cohort or breakdown can filter on them without
replaying events.
| Property | Value |
|---|---|
subscription_status |
signed, in_trial, trial_canceled (a trial set to cancel, which keeps access until its end and goes back to in_trial on resume, or a trial that ended unconverted), subscribed, subscription_canceled (set to cancel, active until the period ends), payment_failed (a renewal or the first charge after a trial failed; Stripe is retrying), subscription_ended. Worked out from the current user, trial and Stripe state, never from the last event received. |
signup_at |
When the user row was created. |
trial_started_at |
When the trial started. |
subscription_started_at |
Start of the current or last paid subscription; for a converted trial, the conversion. |
subscription_canceled_at |
When the subscription was canceled, while subscription_canceled or subscription_ended. |
subscription_ended_at |
When the subscription ended, while subscription_ended. |
Dates are ISO-8601 UTC strings of the lifecycle moment, not of the sync. A
date that doesn't apply to the current status is $unset, never sent as
null.
The role picked in the onboarding wizard is set with a $set of its own when
the profile is submitted at the Preparing step (POST /api/onboarding/profile,product_analytics.set_onboarding_role), so funnels and retention can be split
by role. Someone who left the wizard before Preparing has neither property.
| Property | Value |
|---|---|
onboarding_role |
The pick, as labelled in the wizard: Founder / CEO, Operations, Sales / BD, Marketing, Product / PM, Engineering, HR / People or Other. |
onboarding_role_other |
What was typed after picking Other (trimmed, at most 100 characters); null for any other pick. |
The pick is kept on UserOnboarding, not read back from the AutoPilot
business understanding, whose copy AutoPilot rewrites. poetry run cli onboarding-role-backfill sets both for earlier accounts with $set_once, from
exact picks only: any other stored value may be Other's text or a rewrite,
so it is left unset. MailerLite gets the same values as role androle_other.
The fields GTM segments checkout openers on in MailerLite are on the person
too (SECRT-2895, backend/notifications/posthog_audience.py). They are worked
out by the same code (audience_enrichment.checkout_fields) and named as in
MailerLite, except the checkout open, which is a moment here and a day there.
Every account gets the first two at signup. Every checkout opener gets all of
them from the checkout routes and the completed-checkout webhook. Unlike
MailerLite, PostHog also gets accounts that opted out of marketing or that a
signal places in Iran or Russia: it is analytics, not marketing.
| Property | Value |
|---|---|
email_type |
business, personal (a webmail provider) or education. |
signin_method |
google or email; google wins for an account that has both. |
checkout_opened_at |
When the account first opened Stripe checkout. |
country |
The country's English name, from the strongest source seen (see country_source). |
country_code |
The same country as an ISO 3166-1 alpha-2 code. |
country_source |
stripe (the billing address), ip (the visitor's IP country at checkout) or timezone (the browser's). |
exclude_de_at |
yes when any signal points at Germany or Austria, else no. |
MailerLite reads the subscriber before each write so it never makes a value
worse; PostHog can't be read on the way, so $set_once, which only fills in
what the person doesn't hold yet, keeps the same rules. The first open stays.exclude_de_at yes is set and no only filled in, so it never goes back tono. A billing country is set, so it always wins, and an IP or timezone
country is only filled in, so it never replaces one. The one difference:
PostHog keeps the first IP or timezone country it gets, where MailerLite moves
on to a later IP country. poetry run cli posthog-audience-backfill sends them for earlier accounts, by the same rules,
from Stripe's first Checkout Session (set outright, since Stripe holds every
open) and its billing country or else the browser's timezone. An IP country is
never backfilled, since it was never stored.
Removed
No longer sent (SECRT-2722, SECRT-2723). The names stay reserved: the pin tests fail if
one comes back, because reusing it would splice a different action onto the
history PostHog already holds.
| Event | Was sent by | Read instead |
|---|---|---|
subscription_trial_checkout_started |
browser | checkout_started with checkout_kind: trial, sent by the backend once the trial's Checkout session exists; surface is the same onboarding / billing. trial_offer_version is dropped; trial_started carries it. |
hire_completed |
backend | expert_hired (same hire; it now also skips idempotent re-hires). The DataFast hire_completed goal is unchanged. |
hire_flow_completed |
browser | expert_hired with surface: expert_page. elapsed_ms is PostHog's time to convert from hire_started; voice_picked is dropped. |
onboarding_expert_hired |
browser | expert_hired with surface: onboarding. The card position is on expert_recommendation_clicked. |
copilot_message_sent |
backend | chat_message_sent, which carries message_length. |
expert_run_completed |
backend | agent_run_finished with expert_id set and is_subgraph_run: false. |
finalize_latency_ms |
browser | The finalize_latency_ms property on brain_dump_completed and transcription_failed. |
voice_transcribe_latency_ms |
browser | The transcribe_latency_ms property on voice_turn_sent (and voice_turn_dropped with reason: filler_or_empty). |
voice_first_sound_latency_ms |
browser | The first_sound_latency_ms property on voice_turn_completed, or on voice_turn_dropped with reason: interrupted for a reply that was cut off. |
experiment_exposed |
browser | Nothing: useLaunchDarklyExperiment had no caller and was deleted. PostHog-bucketed experiments use $feature_flag_called. |
copilot_trigger_setup |
backend | Nothing: never sent (no caller). |
intro_card_dismissed, raise_door_clicked, intro_start_with_autopilot |
browser | Nothing: declared, never sent. |
Renamed
Renamed once to the analytics plan's names (SECRT-2722). The old names are
reserved like removed ones; an insight on the old name must be pointed at the
new one.
| Old name | New name | Filter to keep the old meaning |
|---|---|---|
run_agent |
agent_run_started |
expert_id is not set |
run_expert (kind: workflow_run) |
agent_run_started |
expert_id is set |
run_autopilot |
chat_message_sent |
expert_id is not set |
run_expert (kind: chat_turn) |
chat_message_sent |
expert_id is set |
agent_run_completed |
agent_run_finished |
status = completed |
agent_run_failed |
agent_run_finished |
status = failed |
copilot_tool_called |
chat_tool_called |
— |
copilot_library_check_outcome |
chat_library_check_outcome |
— |
copilot_agent_run_success |
chat_outcome |
outcome_type = agent_run_success. Dry runs no longer count; graph_name is dropped. |
copilot_agent_scheduled |
chat_outcome |
outcome_type = schedule_created and target = agent. graph_name and schedule_name are dropped. |
copilot_followup_scheduled |
chat_outcome |
outcome_type = schedule_created and target = followup. chat_session_id is now the chat that scheduled it; the destination is target_chat_session_id. |
subscription_trial_offer_viewed |
trial_offer_viewed |
— |
subscription_trial_started |
trial_started |
— |
subscription_trial_ending |
trial_ending |
— |
subscription_trial_canceled |
trial_canceled |
— |
subscription_trial_resumed |
trial_resumed |
— |
subscription_trial_converted |
trial_converted |
— |
subscription_trial_ended |
trial_ended |
— |
subscription_trial_payment_failed |
payment_failed |
— (only trial conversion charges send it today) |
subscription_upgraded |
subscription_changed |
change_type = upgrade |
subscription_payment_success |
payment_succeeded |
— |
credit_topup_success |
topup_completed |
— |
subscription_tier_reconciliation_discrepancy |
subscription_tier_reconciled |
— |
feature_flag_mismatch |
feature_flag_mismatched |
— (not in the analytics plan; renamed to the object_action past-tense form before it reached production) |
Renamed properties
Renamed in the same release as the events, to the analytics plan's envelope,
so filters and breakdowns break once. Values are unchanged unless noted.
| Old property | New property | On |
|---|---|---|
session_id |
chat_session_id |
chat_message_sent, chat_tool_called, chat_outcome, chat_library_check_outcome, schedule_created, schedule_fired |
target_session_id |
target_chat_session_id |
chat_outcome (target = followup) |
trigger |
via |
agent_run_started, agent_run_finished |
trigger_ref |
via_ref |
agent_run_started |
surface (chat, or the bot platform) |
origin (web, or the bot platform) |
chat_message_sent. The plan's origin is the channel. |
origin (interactive) |
removed | chat_message_sent. Automation turns are not sent, so it was always interactive. |
Differences from the analytics plan
Left for follow-up changes, so this list and the plan can be compared line by
line:
- Events the plan folds into others keep their names until that change.
The brain-dump events (brain_dump_*,transcription_failed,later_dump_completed) and the wizard'sintro_pathandhire_step_continuedbecome properties ofonboarding_step_viewed/_completed/_skipped/_back. Spoken turns (voice_turn_sent)
becomechat_message_sentwithinput_mode: voice. subscription_changedcovers upgrades only. The plan also counts
cancellations and downgrades there;subscription_cancellation_scheduled
andsubscription_endedstay separate events.chat_outcomehas two outcome types. Onlyagent_run_successandschedule_createdhave an emitter.agent_created,trigger_setup,artifact_created,file_producedandanswer_onlydo not.- Plan events we do not send yet (
signup_started,chat_session_started,chat_response_completed,chat_blocked,screen_viewed,screen_engaged,milestone_reached, the builder, library and email
families, ...) come with the plan's phases, not with this list. - Events the plan has no name for keep their own, e.g.
integration_connected,schedule_created,hire_started,billing_portal_opened,marketing_opted_out,tour_*,tab_intro_*,voice_*andcredential_*.briefing_openedis the
briefing shown on home, a different action from the plan'sbriefing_opened_in_chat.
Events not in the constants modules
These are sent by PostHog or a third party, not by our code, and are listed
so nobody adds a duplicate:
$pageleave,$autocapture,$identifyand$feature_flag_called:
posthog-js (capture_pageleaveandautocaptureare on).$ai_generation: OpenRouter Broadcast, for every AutoPilot LLM call that
goes through OpenRouter (see LLM analytics).
Engineering Transactional Email Design (`docs/engineering/transactional-email-design.md`)
Transactional email design
All platform-generated Postmark emails use the Edition 1 email design system
(2 October 2026). The renderer builds HTML and plain text from the same event
payload; Postmark receives those bodies rather than a hosted template ID.
Inventory
| Family | Variants | Design and delivery |
|---|---|---|
| Lifecycle | Welcome, payment failed, final notice, cancellation confirmed, cancellation reversed, ended (payment/cancellation) | Billing sender; contact reply-to; service footer; no unsubscribe |
| Trial | Started, ending, canceled, resumed, converted, payment failed, ended | Same billing rules; neutral Otto; one plan/payment action |
| Briefing | Daily, weekly, monthly; quiet/standard; attention/no attention | Product sender; hello reply-to; signed cadence links; one-click unsubscribe |
| Alert | Each cause, including coalesced alerts | Amber needs-you strip and primary attention card; secondary actions are text links |
| Verdict | Approved; changes requested with/without notes | State eyebrow; reviewer feedback preserved and escaped; one marketplace action |
| Ops | Refund request, refund processed | Internal amount band and compact Otto; admin action; no unsubscribe |
| Auth | Verify email, set/reset password, confirm new email | Compact layout without hero; action button and complete fallback URL; HTML and text; original subjects and origin validation |
Organization invitations have no email sender yet. SMTP/Gmail workflow blocks
send user-authored content and are not platform transactional notices. The
onboarding/changelog files remain marketing references; updating them does not
publish content or settings to MailerLite.
Design contract
backend/notifications/templates/_email_ui.j2 is the shared kit. It defines the
560px sheet, 48px desktop/22px phone gutters, Geist with system fallbacks, purple#6144DF actions, ink #171925, white sheet and lavender paper. Facts stack on
phones. At 600px and below, the paper has no side padding and the sheet has
square corners; content keeps its 22px gutters. The sheet is fluid even when a
client strips the stylesheet. Button links own their full padded surface,
including when styles are removed. MSO-only rules transfer that padding to
table cells for Outlook without doubling it. Inline font stacks preserve
the fallback appearance in Outlook; action glyphs request text
presentation instead of emoji. Each family owns its HTML, subject/preheader,
and text templates. The preheader appears in the lede; supplied user names and
feedback stay escaped, and customer-authored punctuation is preserved.
Plan labels use a middle dot (Pro · monthly). Trial prices use the same
formatted amount as lifecycle emails, with a currency code retained when the
formatter has no currency symbol (20.00 CAD / month, 2,000 JPY / year).
One primary button is the default. Cancellation confirmation uses a billing
text link. A Briefing without an attention item uses highlight/run-history text
links; its cadence controls are signed for that recipient. Only the welcome is
signed by a person. Its Expert CTA is selected using the recipient'sHIRE_EXPERTS flag; older queued messages and failed flag lookups default to
the workflow action. Secondary Marketplace copy follows the same selection.
Billing has no unsubscribe promise it cannot honor. Product emails match the
footer unsubscribe URL to both one-click headers. Auth and internal ops have no
unsubscribe. All include the registered address; auth has only the Discord
footer link and ops has no footer links.
Images and configuration
EMAIL_ASSET_BASE_URL defaults to https://platform.agpt.co/email, containinglogo-light.png (790x356 served at 100x45). Otto uses the same URL origin plus/autogpt-characters/v1.1/otto/neutral-transparent/256.png, served at 160x160 or
80x80 for ops. Its transparent background lets the surrounding hero band show
through when a mail client recolors the message in dark mode. The original
opaque export remains available for already-delivered messages. The 256px,
512px and 1024px transparent exports come unchanged from the approved October 6
design handoff.
The logo and transparent Otto ship in the frontend public assets. A custom
asset host must serve both paths publicly over HTTPS without cookies or
authentication. Deploy the frontend/static assets before the backend renderer
so the new image URL resolves as soon as templates start using it.
Images use hosted PNG URLs, explicit dimensions, and descriptive alt text.
There are no data URIs or inline SVGs. The same text, facts and action links
remain when an email client blocks images. Historical image files are retained
because already-delivered messages can still reference their URLs; new
transactional templates do not use the retired art.
Deployment overrides still control POSTMARK_SENDER_EMAIL,BILLING_SENDER_EMAIL, PRODUCT_SENDER_EMAIL, OPS_SENDER_EMAIL, andPOSTMARK_TRANSACTIONAL_STREAM. This migration preserves those From values.BILLING_REPLY_TO_EMAIL defaults to [email protected] andPRODUCT_REPLY_TO_EMAIL to [email protected]. Configure a verified named auth
sender in deployment; the repository must not invent or activate a sender.
The auth RPC remains compatible across old and new API and notification-service
versions during deployment and rollback. The client sends named JSON fields;
an older service ignores the new text_body field and still sends the HTML,
while the new service accepts requests without that field. Plain text is
included when both components use the new code. The auth RPC itself does not
require a particular service deployment order.
The trial payload formatting change does require ordered rollout: deploy the
frontend/static assets first, the notification service second, and the API
producer last. The new notification service accepts both the exact old and new
plan labels/prices while still rejecting changed trial terms. An older service
would consider the new producer's formatting obsolete and drop the queued
notice. For rollback, revert the producer first and drain new-format messages
before reverting the notification service. Keep the published assets available.
Production configuration and reminder checks
The repository's deployment workflows dispatch to the infrastructure repository;
the defaults here do not prove the effective production values. Before rollout:
- Set
POSTMARK_SENDER_EMAILto the existing Postmark-verified auth address with
its approved display name. Check the effective notification-service value;[email protected]is only a repository placeholder. Do not invent a new
sending identity as part of a template deploy. - Set
[email protected]and[email protected]explicitly in deployment, and inspect
delivered message headers. Preserve the configured billing, product and ops
senders and the transactional Postmark stream. - Check that the new public Otto URL returns a PNG without authentication from
the configured asset host before deploying the notification service.
To verify the trial-ending reminder, follow the delivery path in order:
- In the live Stripe account, confirm the enabled platform webhook endpoint
subscribes tocustomer.subscription.trial_will_end. - Check the Stripe Dashboard's Trial ending events setting is three days.
Compare a recent event's creation time with its subscription'strial_end.
The code only sends an ending notice inside the final three days, while the
trial is active, the card is verified and cancellation is not scheduled. - Check the endpoint's delivery result and backend processing. A successful
Stripe webhook response is not proof that an email was delivered. - Check PostHog for
trial_ending, the current event name, andsubscription_trial_endingfor older deployments. This event records a
successful notification enqueue, not Postmark delivery. - Check Postmark activity in the configured transactional stream for the
billing sender and subject beginningYour AutoGPT trial ends on, then check
the delivery result for the affected recipient.
Stripe's trial-ending webhook setting
is separate from its hosted reminder emails,
which are sent seven days before trial end. Keep the hosted reminder disabled
when AutoGPT owns this message, so a seven-day trial does not receive a second
reminder at signup.
Validation
The original migration passed 258 focused tests and a browser audit of 31
scenarios at two widths (62 renders), including images blocked, with 1,548 text
contrast checks and no failures. Those results and the screenshots below
predate the transparent Otto and email-client compatibility follow-up.
The notification design fixtures cover each lifecycle/trial branch, welcome
feature cohorts, Briefing cadences/attention, coalesced alerts, verdicts, and
refunds. Tests check frame/tokens, safe images, CTA exceptions, size, footers,
link parity and escaped feedback. Auth tests check all four actions, URL
escaping, origin restrictions, and plain-text delivery. Transport tests inspect
the actual Postmark request fields with the network client mocked.
For changes to the shared kit, render at 640px, 390px and 360px with images on
and off, with styles stripped, and with simulated dark backgrounds. Check
horizontal overflow, text and button visibility, and contrast against the
effective background.
Browser evidence is not an actual Gmail, Apple Mail or Outlook delivery test.
Before release, send staging samples to authorized test inboxes and inspect
light/dark mode, images off, plain text, button and fallback links, and signed
unsubscribe/cadence controls. Also exercise destinations signed out, with the
feature disabled, and after the relevant task is complete. No email was sent,
production configuration changed, or deployment performed by this PR.
Review previews
The client compatibility follow-up was checked across all 32 fixture/auth
variants at 640px, 390px and 360px in five modes: normal, stylesheet removed,
images blocked, dark preference, and simulated dark backgrounds (480 layout
and asset checks). No case overflowed; the largest HTML was 16,204 bytes. The
dark simulation checks the transparent artwork against recolored backgrounds;
it does not reproduce a mail client's color algorithm or establish dark-mode
text contrast. Real inbox validation remains part of the release checklist.
Both button sizes also passed 60 browser clicks across the three widths with
normal and stripped styles, including each padded edge. Previously, all 48
edge clicks missed the link; only the 12 label-center clicks succeeded.
The comparisons below use local checked-in assets and synthetic fixture data.
Desktop remains 560px wide. Phones now use the viewport width, and the inline
fluid layout still fits when the stylesheet is removed.
| Scenario | Before follow-up | After follow-up |
|---|---|---|
| Desktop, 640px | ||
| Phone, 390px | ||
| Phone, stylesheet removed |
Original migration
These screenshots use synthetic data and the supplied production logo/Otto
assets. The before column is the original dev template; after is this shared
kit. Each comparison includes desktop (640px) and phone (390px) renderings.
Engineering Workspace Media Architecture (`docs/engineering/workspace-media-architecture.md`)
Workspace & Media File Architecture
This document describes the architecture for handling user files in AutoGPT Platform, covering persistent user storage (Workspace) and ephemeral media processing pipelines.
Overview
The platform has two distinct file-handling layers:
| Layer | Purpose | Persistence | Scope |
|---|---|---|---|
| Workspace | Long-term user file storage | Persistent (DB + GCS/local) | Per-user, session-scoped access |
| Media Pipeline | Ephemeral file processing for blocks | Temporary (local disk) | Per-execution |
Database Models
UserWorkspace
Represents a user's file storage space. Created on-demand (one per user).
model UserWorkspace {
id String @id @default(uuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
userId String @unique
Files UserWorkspaceFile[]
}Key points:
- One workspace per user (enforced by
@uniqueonuserId) - Created lazily via
get_or_create_workspace() - Uses upsert to handle race conditions
UserWorkspaceFile
Represents a file stored in a user's workspace.
model UserWorkspaceFile {
id String @id @default(uuid())
workspaceId String
name String // User-visible filename
path String // Virtual path (e.g., "/sessions/abc123/image.png")
storagePath String // Actual storage path (gcs://... or local://...)
mimeType String
sizeBytes BigInt
checksum String? // SHA256 for integrity
isDeleted Boolean @default(false)
deletedAt DateTime?
metadata Json @default("{}")
@@unique([workspaceId, path]) // Enforce unique paths within workspace
}Key points:
pathis a virtual path for organizing files (not actual filesystem path)storagePathcontains the actual GCS or local storage location- Soft-delete pattern:
isDeletedflag withdeletedAttimestamp - Path is modified on delete to free up the virtual path for reuse
WorkspaceManager
Location: backend/util/workspace.py
High-level API for workspace file operations. Combines storage backend operations with database record management.
Initialization
from backend.util.workspace import WorkspaceManager
# Basic usage
manager = WorkspaceManager(user_id="user-123", workspace_id="ws-456")
# With session scoping (CoPilot sessions)
manager = WorkspaceManager(
user_id="user-123",
workspace_id="ws-456",
session_id="session-789"
)Session Scoping
When session_id is provided, files are isolated to /sessions/{session_id}/:
# With session_id="abc123":
manager.write_file(content, "image.png")
# → stored at /sessions/abc123/image.png
# Cross-session access is explicit:
manager.read_file("/sessions/other-session/file.txt") # WorksWhy session scoping?
- CoPilot conversations need file isolation
- Prevents file collisions between concurrent sessions
- Allows session cleanup without affecting other sessions
Core Methods
| Method | Description |
|---|---|
write_file(content, filename, path?, mime_type?, overwrite?) |
Write file to workspace |
read_file(path) |
Read file by virtual path |
read_file_by_id(file_id) |
Read file by ID |
list_files(path?, limit?, offset?, include_all_sessions?) |
List files |
delete_file(file_id) |
Soft-delete a file |
get_download_url(file_id, expires_in?) |
Get signed download URL |
get_file_info(file_id) |
Get file metadata |
get_file_info_by_path(path) |
Get file metadata by path |
get_file_count(path?, include_all_sessions?) |
Count files |
Storage Backends
WorkspaceManager delegates to WorkspaceStorageBackend:
| Backend | When Used | Storage Path Format |
|---|---|---|
GCSWorkspaceStorage |
private_user_data_bucket (or the legacy bucket fallback) is configured |
gcs://bucket/workspaces/{ws_id}/{file_id}/{filename} |
LocalWorkspaceStorage |
No GCS bucket configured | local://{ws_id}/{file_id}/{filename} |
Public and private media
Hosted storage has two trust boundaries:
PRIVATE_USER_DATA_BUCKETis the default destination for user uploads,
generated library images, custom Expert avatars, workspaces, transcripts,
and temporary agent inputs. Private media is read through an authenticated,
non-cacheable API that serves a file to its owner, admins and members of
an organization the owner belongs to.PUBLIC_SITE_MEDIA_BUCKETcontains only objects that a trusted publication
flow explicitly copied after approval, plus media whose purpose is inherently
public such as OAuth consent-screen logos. Anonymous exact-object reads are
allowed, but anonymous bucket listing is not.
An upload caller cannot select the public destination. Publication is a
separate privileged operation; sharing a private resource grants access through
its opaque application URL and does not make its storage prefix public.
Moving a single-bucket deployment to split buckets
Stored rows hold gcs:///... paths and full GCS URLs, and the storage
code only reads from the configured private bucket, so the existing bucket must
become the private one:
- Create the new public bucket. Deploy with
PRIVATE_USER_DATA_BUCKETset to
the existingMEDIA_GCS_BUCKET_NAMEbucket andPUBLIC_SITE_MEDIA_BUCKET
set to the new one. PointingPRIVATE_USER_DATA_BUCKETat a new bucket
instead makes every existing workspace file and transcript unreadable.
Cloud startup rejects that unsafe partial migration. After every stored
bucket-qualified path has been migrated to a new private bucket, clearMEDIA_GCS_BUCKET_NAMEbefore selecting the new private bucket.
Deploy the frontend first: once the backend has both names set, uploads
return/api/store/submissions/media/..., which a frontend without the new
rewrite answers with a 404.
If anonymous users holdroles/storage.objectVieweron the old bucket,
they can list every object in it until step 4. Swap that binding forroles/storage.legacyObjectReaderfirst: existing links keep working and
listing stops. - Copy everything that is already public to the public bucket and repoint its
rows:poetry run python scripts/publish_live_media.py(dry run), then
again with--applyuntil it exits 0. A non-zero exit means a live
reference was not published (copy failure, conflict, an object outside the
listing's owners or an unrecognised URL) and would break in step 4. This
covers approved listing media, the avatars of creators with a public
listing, library copies of listing images and OAuth app logos. It is also
the repair tool when a copy at approval time failed. - Rewrite the remaining private media URLs to the authenticated API path:
poetry run python scripts/backfill_private_media_urls.py(dry run), then--apply, re-running until it reports no conflicts. It exits 2 while any
reference stays on the old bucket (held as public, cross-user, ambiguous,
malformed or unrecognised): those stop loading in step 4, so check the
counts before going on. Both scripts commit in small batches and can be
re-run. - Run step 2's dry run once more and check it exits 0. Then remove every
public binding from the old bucket and turn on public access prevention, so
no object-level grant can expose a file again.
The private media endpoint serves a file to its owner, to admins and to
members of an organization the owner belongs to, so a leaked URL is useless to
anyone else. Published copies are never deleted automatically: when a listing
is taken down or a creator changes their avatar, the old public copy stays in
the public bucket until someone removes it by hand.
Hosted private image uploads are limited to 4 MiB so the frontend proxy can
buffer and deliver the complete authenticated response below Vercel's body
limit. Private videos retain the general 50 MiB upload limit and are delivered
in bounded range responses. An oversized object written before this limit is
served only after the same authorization check, using a 60-second signed URL
that bypasses the frontend proxy without granting bucket listing access.
store_media_file()
Location: backend/util/file.py
The media normalization pipeline. Handles various input types and normalizes them for processing or output.
Purpose
Blocks receive files in many formats (URLs, data URIs, workspace references, local paths). store_media_file() normalizes these to a consistent format based on what the block needs.
Input Types Handled
| Input Format | Example | How It's Processed |
|---|---|---|
| Data URI | data:image/png;base64,iVBOR... |
Decoded, virus scanned, written locally |
| HTTP(S) URL | https://example.com/image.png |
Downloaded, virus scanned, written locally |
| Workspace URI | workspace://abc123 or workspace:///path/to/file |
Read from workspace, virus scanned, written locally |
| Cloud path | gcs://bucket/path |
Downloaded, virus scanned, written locally |
| Local path | image.png |
Verified to exist in exec_file directory |
Return Formats
The return_format parameter determines what you get back:
from backend.util.file import store_media_file
# For local processing (ffmpeg, MoviePy, PIL)
local_path = await store_media_file(
file=input_file,
execution_context=ctx,
return_format="for_local_processing"
)
# Returns: "image.png" (relative path in exec_file dir)
# For external APIs (Replicate, OpenAI, etc.)
data_uri = await store_media_file(
file=input_file,
execution_context=ctx,
return_format="for_external_api"
)
# Returns: "data:image/png;base64,iVBOR..."
# For block output (adapts to execution context)
output = await store_media_file(
file=input_file,
execution_context=ctx,
return_format="for_block_output"
)
# In CoPilot: Returns "workspace://file-id#image/png"
# In graphs: Returns "data:image/png;base64,..."Execution Context
store_media_file() requires an ExecutionContext with:
graph_exec_id- Required for temp file locationuser_id- Required for workspace accessworkspace_id- Optional; enables workspace featuressession_id- Optional; for session scoping in CoPilot
Responsibility Boundaries
Virus Scanning
| Component | Scans? | Notes |
|---|---|---|
store_media_file() |
✅ Yes | Scans all content before writing to local disk |
WorkspaceManager.write_file() |
✅ Yes | Scans content before persisting |
Scanning happens at:
store_media_file()— scans everything it downloads/decodesWorkspaceManager.write_file()— scans before persistence
Tools like WriteWorkspaceFileTool don't need to scan because WorkspaceManager.write_file() handles it.
Persistence
| Component | Persists To | Lifecycle |
|---|---|---|
store_media_file() |
Temp dir (/tmp/exec_file/{exec_id}/) |
Cleaned after execution |
WorkspaceManager |
GCS or local storage + DB | Persistent until deleted |
Automatic cleanup: clean_exec_files(graph_exec_id) removes temp files after execution completes.
Decision Tree: WorkspaceManager vs store_media_file
┌─────────────────────────────────────────────────────┐
│ What do you need to do with the file? │
└─────────────────────────────────────────────────────┘
│
┌─────────────┴─────────────┐
▼ ▼
Process in a block Store for user access
(ffmpeg, PIL, etc.) (CoPilot files, uploads)
│ │
▼ ▼
store_media_file() WorkspaceManager
with appropriate
return_format
│
│
┌──────┴──────┐
▼ ▼
"for_local_ "for_block_
processing" output"
│ │
▼ ▼
Get local Auto-saves to
path for workspace in
tools CoPilot context
Store for user access
│
├── write_file() ─── Upload + persist (scans internally)
├── read_file() / get_download_url() ─── Retrieve
└── list_files() / delete_file() ─── ManageQuick Reference
| Scenario | Use |
|---|---|
| Block needs to process a file with ffmpeg | store_media_file(..., return_format="for_local_processing") |
| Block needs to send file to external API | store_media_file(..., return_format="for_external_api") |
| Block returning a generated file | store_media_file(..., return_format="for_block_output") |
| API endpoint handling file upload | WorkspaceManager.write_file() (handles virus scanning internally) |
| API endpoint serving file download | WorkspaceManager.get_download_url() |
| Listing user's files | WorkspaceManager.list_files() |
Key Files Reference
| File | Purpose |
|---|---|
backend/data/workspace.py |
Database CRUD operations for UserWorkspace and UserWorkspaceFile |
backend/util/workspace.py |
WorkspaceManager class - high-level workspace API |
backend/util/workspace_storage.py |
Storage backends (GCS, local) and WorkspaceStorageBackend interface |
backend/util/file.py |
store_media_file() and media processing utilities |
backend/util/virus_scanner.py |
VirusScannerService and scan_content_safe() |
schema.prisma |
Database model definitions |
Common Patterns
Block Processing a User's File
async def run(self, input_data, *, execution_context, **kwargs):
# Normalize input to local path
local_path = await store_media_file(
file=input_data.video,
execution_context=execution_context,
return_format="for_local_processing",
)
# Process with local tools
output_path = process_video(local_path)
# Return (auto-saves to workspace in CoPilot)
result = await store_media_file(
file=output_path,
execution_context=execution_context,
return_format="for_block_output",
)
yield "output", resultAPI Upload Endpoint
from backend.util.virus_scanner import VirusDetectedError, VirusScanError
async def upload_file(file: UploadFile, user_id: str, workspace_id: str):
content = await file.read()
# write_file handles virus scanning internally
manager = WorkspaceManager(user_id, workspace_id)
try:
workspace_file = await manager.write_file(
content=content,
filename=file.filename,
)
except VirusDetectedError:
raise HTTPException(status_code=400, detail="File rejected: virus detected")
except VirusScanError:
raise HTTPException(status_code=503, detail="Virus scanning unavailable")
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
return {"file_id": workspace_file.id}Configuration
| Setting | Purpose | Default |
|---|---|---|
private_user_data_bucket |
Private GCS bucket for user media, workspace storage, transcripts, and temporary uploads | Falls back to media_gcs_bucket_name, then local storage |
public_site_media_bucket |
Public GCS bucket for explicitly published marketplace media and OAuth app logos | Falls back to media_gcs_bucket_name for compatibility |
media_gcs_bucket_name |
Legacy single-bucket setting for self-hosted compatibility | None |
workspace_storage_dir |
Local storage directory | {app_data}/workspaces |
max_file_size_mb |
Maximum file size in MB | 100 |
clamav_service_enabled |
Enable virus scanning | true |
clamav_service_host |
ClamAV daemon host | localhost |
clamav_service_port |
ClamAV daemon port | 3310 |
clamav_max_concurrency |
Max concurrent scans to ClamAV daemon | 5 |
clamav_mark_failed_scans_as_clean |
If true, scan failures pass content through instead of rejecting (⚠️ security risk if ClamAV is unreachable) | false |
Integrations SUMMARY (`docs/integrations/SUMMARY.md`)
Table of contents
Guides
Block Integrations
- Agent Mail Attachments
- Agent Mail Drafts
- Agent Mail Inbox
- Agent Mail Lists
- Agent Mail Messages
- Agent Mail Pods
- Agent Mail Threads
- Airtable Bases
- Airtable Records
- Airtable Schema
- Airtable Triggers
- All Quiet Incident Search
- All Quiet Incidents
- All Quiet On Call
- All Quiet Teams
- All Quiet Triggers
- AnySearch Extract
- AnySearch Parallel Search
- AnySearch Search
- Apollo Organization
- Apollo People
- Apollo Person
- Ayrshare Post To Bluesky
- Ayrshare Post To Facebook
- Ayrshare Post To GMB
- Ayrshare Post To Instagram
- Ayrshare Post To LinkedIn
- Ayrshare Post To Pinterest
- Ayrshare Post To Reddit
- Ayrshare Post To Snapchat
- Ayrshare Post To Telegram
- Ayrshare Post To Threads
- Ayrshare Post To TikTok
- Ayrshare Post To X
- Ayrshare Post To YouTube
- Baas Bots
- Bannerbear Text Overlay
- Basic
- Compass Triggers
- Conductor Account
- Conductor Create Session
- Conductor Create Workspace
- Conductor Get Session
- Conductor Get Workspace
- Conductor List Workspaces
- Conductor Manage Session
- Conductor Manage Workspace
- Conductor Routines
- Conductor Sections
- Conductor Send Message
- Data
- DataForB2B Enrich
- DataForB2B Reasoning
- DataForB2B Search
- DataForB2B Typeahead
- Dataforseo Keyword Suggestions
- Dataforseo Related Keywords
- Discord Bot Blocks
- Discord OAuth Blocks
- Enrichlayer LinkedIn
- Exa Answers
- Exa Code Context
- Exa Contents
- Exa Research
- Exa Search
- Exa Similar
- Exa Webhook Blocks
- Exa Websets
- Exa Websets Enrichment
- Exa Websets Import Export
- Exa Websets Items
- Exa Websets Monitor
- Exa Websets Polling
- Exa Websets Search
- Fal AI Video Generator
- Firecrawl Crawl
- Firecrawl Extract
- Firecrawl Map
- Firecrawl Scrape
- Firecrawl Search
- Generic Webhook Triggers
- GitHub Checks
- GitHub CI
- GitHub Commits
- GitHub Issues
- GitHub Notifications
- GitHub Pull Requests
- GitHub Repo
- GitHub Repo Branches
- GitHub Repo Files
- GitHub Reviews
- GitHub Statuses
- GitHub Traffic
- GitHub Triggers
- GitHub Users
- Google Calendar
- Google Docs
- Google Gmail
- Google Sheets
- Hacker News Items
- Hacker News Search
- Hacker News Stories
- Hacker News Users
- HubSpot Company
- HubSpot Contact
- HubSpot Engagement
- Jina Chunking
- Jina Embeddings
- Jina Fact Checker
- Jina Search
- Linear Comment
- Linear Issues
- Linear Projects
- LLM
- Logic
- Mcp Block
- Misc
- Notion Create Page
- Notion Read Database
- Notion Read Page
- Notion Read Page Markdown
- Notion Search
- Nvidia Deepfake
- Replicate Flux Advanced
- Replicate Replicate Block
- Rmfg Carts
- Rmfg Catalog
- Rmfg Designs
- Rmfg Dfm
- Rmfg Images
- Rmfg Orders
- Rmfg Pay Cart
- Rmfg Quotes
- Rmfg Review Links
- Rmfg Triggers
- Search
- Slack Blocks
- Slant3D Filament
- Slant3D Order
- Slant3D Order Status
- Slant3D Slicing
- Slant3D Webhook
- Smartlead Campaign
- Stagehand Blocks
- Stripe Triggers
- Stripe Link MPP
- Stripe Link Profile
- Stripe Link Spend Request
- System Library Operations
- System Store Operations
- Tavily Crawl
- Tavily Extract
- Tavily Map
- Tavily Search
- Telegram Blocks
- Telegram Triggers
- Text
- Todoist Comments
- Todoist Labels
- Todoist Projects
- Todoist Sections
- Todoist Tasks
- Twitter Blocks
- Twitter Bookmark
- Twitter Follows
- Twitter Hide
- Twitter Like
- Twitter List Follows
- Twitter List Lookup
- Twitter List Members
- Twitter List Tweets Lookup
- Twitter Manage
- Twitter Manage Lists
- Twitter Mutes
- Twitter Pinned Lists
- Twitter Quote
- Twitter Retweet
- Twitter Search Spaces
- Twitter Spaces Lookup
- Twitter Timeline
- Twitter Tweet Lookup
- Twitter User Lookup
- Typesafe Ask Many
- Typesafe Choice
- Typesafe Filter
- Typesafe Pick Best
- Typesafe Route
- Typesafe Score
- Typesafe Yes No
- Video Add Audio
- Video Clip
- Video Concat
- Video Download
- Video Duration
- Video Loop
- Video Narration
- Video Text Overlay
- Wolfram LLM API
- Zerobounce Validate Emails
Platform SUMMARY (`docs/platform/SUMMARY.md`)
Table of contents
Using the Platform
- Getting Started (Cloud)
- AutoPilot
- Agent Builder Guide
- Agent Library
- Marketplace
- Scheduling & Triggers
- Templates
- Credits & Billing
- Integrations & Credentials
- Data Flow & Execution
- Sharing & Exporting Agents
Self-Hosting
- Setting Up AutoGPT (Self-Host)
- AutoGPT Platform Installer
- Run AutoGPT in One Docker Container (Experimental)
- Advanced Setup
Tutorials
- Create a Basic Agent
- Edit an Agent
- Delete an Agent
- Download & Import an Agent
- Submit an Agent to the Marketplace
Building Blocks
Using AI Services
- Using Google Gemini
- Using Ollama
- AutoPilot on a self-hosted LLM (no API keys)
- Using AI/ML API
- Using D-ID
API & Integrations
Contributing
Changelog
- What's New
- Organize files, share your work, connect Discord
- Smarter Automation, Fresher Design, Open for Business
- Artifacts, Briefings, and Builder Chat
- Smarter Starts, Faster Feedback
- Import workflows from other platforms and enjoy a polished marketplace
- Explore a new marketplace, track usage, and give instant feedback
- Organize your agents, get notified, and enjoy a cleaner chat
- Connect any app, share files, and let AutoPilot browse for you
- Telegram bots, agent folders, and a rebuilt builder
- Voice input, persistent files, and a smarter AI
Platform Advanced Setup (`docs/platform/advanced_setup.md`)
Advanced Setup
The advanced steps below are intended for people with sysadmin experience. If you are not comfortable with these steps, please refer to the basic setup guide.
Introduction
For the advanced setup, first follow the basic setup guide to get the server up and running. Once you have the server running, you can follow the steps below to configure the server for your specific needs.
Configuration
Setting config via environment variables
The server uses environment variables to store configs. You can set these environment variables in a .env file in the root of the project. The .env file should look like this:
# .env
KEY1=value1
KEY2=value2The server will automatically load the .env file when it starts. You can also set the environment variables directly in your shell. Refer to your operating system's documentation on how to set environment variables in the current session.
The valid options are listed in .env.default in the root of the builder and server directories. You can copy the .env.default file to .env and modify the values as needed.
# Copy the .env.default file to .env
cp .env.default .envSecrets directory
The secret directory is located at ./secrets. You can store any secrets you need in this directory. The server will automatically load the secrets when it starts.
An example for a secret called my_secret would look like this:
# ./secrets/my_secret
my_secret_valueThis is useful when running on docker so you can copy the secrets into the container without exposing them in the Dockerfile.
Database selection
PostgreSQL
We use PostgreSQL (with the pgvector extension) as the database. You will swap the commands you use to generate and run prisma to the following
poetry run prisma generate --schema postgres/schema.prismaThis will generate the Prisma client for PostgreSQL. You will also need to run the PostgreSQL database in a separate container. You can use the docker-compose.yml file in the rnd directory to run the PostgreSQL database.
cd autogpt_platform/
docker compose up -d --buildYou can then run the migrations from the backend directory.
cd ../backend
prisma migrate dev --schema postgres/schema.prismaRedis
The platform uses Redis for caching, distributed locks, rate limits, and the streams that carry agent output to the browser.
The Docker Compose stack runs its own Redis Cluster: three redis:7 shards on ports 17000 to 17002, joined by a one-shot redis-init service. make start-core starts it along with the other dependencies.
Using an external Redis cluster
The backend connects with a cluster client only, so the deployment must have cluster mode enabled. A standalone server, with cluster mode disabled, does not work.
The cluster also needs:
- Redis 7.0 or later, or a compatible engine such as Valkey. The backend uses sharded pub/sub (
SPUBLISH,SSUBSCRIBE), which Redis 7.0 introduced, along with Streams and Lua scripts. It uses no Redis modules. - Shard addresses the backend can reach. With
REDIS_USE_ANNOUNCED_ADDRESS=true, the backend connects to each shard at the address the cluster announces, so those addresses must resolve from where the backend runs. Without it, the backend connects to every shard atREDIS_HOSTand keeps only the announced port, which works only when every shard is reachable on that one host. - A private network between the backend and every shard. The backend connects without TLS and does not send a username. It sends
REDIS_PASSWORDwhen one is set, and a cluster without a password works too. Nothing on the connection is encrypted, so don't route it over a network you don't trust.
Point REDIS_HOST and REDIS_PORT at any node of the cluster, and set REDIS_PASSWORD if the cluster has one.
In the Compose stack, REDIS_HOST, REDIS_PORT and REDIS_USE_ANNOUNCED_ADDRESS are set in the x-backend-env block of autogpt_platform/docker-compose.platform.yml, which takes precedence over backend/.env, so change them there. REDIS_PASSWORD is read from backend/.env. The bundled shards still start, because the backend services wait for redis-0 to be healthy.
AutoGPT Agent Server Advanced set up
This guide walks you through a dockerized set up, with an external DB (postgres)
Setup
We use the Poetry to manage the dependencies. To set up the project, follow these steps inside this directory:
Install Poetry
shpip install poetryConfigure Poetry to use .venv in your project directory
shpoetry config virtualenvs.in-project trueEnter the poetry shell
shpoetry shellInstall dependencies
shpoetry installCopy .env.default to .env
shcp .env.default .envGenerate the Prisma client
shpoetry run prisma generateIn case Prisma generates the client for the global Python installation instead of the virtual environment, the current mitigation is to just uninstall the global Prisma package:
shpip uninstall prismaThen run the generation again. The path should look something like this:
<some path>/pypoetry/virtualenvs/backend-TQIRSwR6-py3.12/bin/prismaRun the postgres database from the /rnd folder
shcd autogpt_platform/ docker compose up -dRun the migrations (from the backend folder)
shcd ../backend prisma migrate deploy
Running The Server
Starting the server directly
Run the following command:
poetry run appPlatform Agent Blocks (`docs/platform/agent-blocks.md`)
**How to Create an AI Agent as a Block in AutoGPT**
**Overview**
This guide explains how to create a reusable agent block that can be used as a component in other agents.
**What Are Agent Blocks?**
Agent blocks are pre-configured, reusable AI workflows that can be used as components within larger automation systems. Think of them as "smart building blocks" - each agent block is itself a complete workflow that can:
- Accept specific inputs
- Process data using AI and traditional automation
- Produce defined outputs
- Be easily reused in different contexts
The power of agent blocks lies in their modularity. Once you create an agent with a specific capability (like translating text or analyzing sentiment), you can reuse it as a single block in other workflows. This means you can:
- Combine multiple agent blocks to create more complex automations
- Reuse proven workflows without rebuilding them
- Share agent blocks with other users
- Create hierarchical systems where specialized agents work together
For example, a content creation workflow might combine several agent blocks:
- A research agent block that gathers information
- A writing agent block that creates the initial draft
- An editing agent block that polishes the content
- A formatting agent block that prepares the final output
**Creating the Base Agent**
**Required Components**
- Input Block
- AI Text Generator Block
- Output Block
**Step-by-Step Setup**
- Add and Configure Blocks
- Add an Input Block
- Add an AI Text Generator Block
- Add an Output Block
- Connect Components
- Connect Input's result to AI Text Generator's Prompt
- Connect AI Text Generator's response to Output's value
- Name the Components
- Name the Input Block: "question"
- Name the Output Block: "answer"
- Save the Agent
- Choose a descriptive name (e.g., "Weather Agent")
- Click Save
**Converting to a Block**
- Access the Block Menu
- Go to the Builder interface
- Click the Blocks menu
- Click the agent tag or search the name of your agent
- Using the Agent Block
- Click on the agent block to add to your workflow
- Save the new agent with a descriptive name (e.g., "Weather Agent")
**Testing the Agent Block**
- Run the Agent
- Enter a test question (e.g., "How far is the Earth from the Moon?")
- Click Run
- View Results
- Option 1: Check "Agent Outputs" section*
- Option 2: Click "View More" for detailed results
*Note: if there is no output block then the "Agent Outputs" button will show up blank. You can see the output under view more or at bottom of the block.
**Advanced Usage**
- You can make more complex agents by combining multiple agent blocks
- Chain different agents together for more sophisticated workflows
**Note**
This is a basic example that can be expanded upon to create more complex agent blocks with additional functionality.
Platform Agent Builder Guide (`docs/platform/agent-builder-guide.md`)
Agent Builder Guide
Overview
The Agent Builder is the visual editor where you design and build agents by connecting blocks together on a canvas. Each block is a single action — like generating text, calling an API, or processing data — and you wire them together to create automated workflows.
The Builder Interface
When you open the builder, you'll see:
- Canvas: The main workspace where you place and connect blocks
- Blocks Menu: A panel on the left-hand side where you browse and search for blocks
- Save Button: Save your agent with a title and description
Working with Blocks
Types of Blocks
Blocks fall into three categories:
| Type | Description |
|---|---|
| Input Blocks | Define what information the agent needs when it runs. These become the input fields users fill in when starting a task. Types include text inputs, file inputs, and more. |
| Action Blocks | Perform operations — AI text generation, image creation, API calls, data processing, and hundreds of integrations with external platforms. |
| Output Blocks | Define what the agent returns as its result. These become the visible output when a task completes. |
Input and output blocks define the schema of your agent — they determine what users see when running the agent. All other blocks inside the agent are internal and not exposed to the user at runtime.
{% hint style="info" %}
There is also a special type of input block: Trigger Blocks. These allow your agent to be activated by external events via webhooks rather than manual input. See Scheduling & Triggers for details.
{% endhint %}
Adding Blocks
- Open the Blocks menu on the left-hand side of the builder
- Browse categories or use the search bar to find a specific block
- Click on a block to add it to the canvas
There are hundreds of blocks available, integrating with many platforms and services.
Connecting Blocks
Blocks have input pins and output pins. Pins are typed — they handle specific data types like text, numbers, files, and more.
To connect blocks:
- Click on an output pin of one block
- Drag to an input pin of another block (or simply click the output pin, then click the input pin)
- A connection line will appear between the two pins
When the agent runs, data flows along these connections. This is visually represented by a coloured bead that slides along the connection line from the output pin to the input pin.
Configuring Blocks
Many blocks have settings you can configure directly on the block. For example:
- The AI Text Generator block lets you choose which language model to use
- Integration blocks may require credentials (see Integrations & Credentials)
- Some blocks allow you to hardcode values on their input pins instead of connecting them to other blocks
If a block requires a credential you haven't connected yet, a credential bar will appear prompting you to add it (via OAuth, API key, or username/password depending on the service).
Saving Your Agent
To save your agent:
- Press Ctrl+S (or Cmd+S on Mac), or
- Click the Save button in the builder
Saving creates or updates the agent graph version in your library. The builder also keeps a local recovery draft while you edit; a successful save clears that draft. Publishing to the marketplace is a separate action—see Publishing to the Marketplace.
Navigating Back to Your Library
After saving, click the Agents button in the navigation bar to return to your library and find your agent.
Editing an Existing Agent
To open an existing agent in the builder:
- Go to your Agent Library (click Agents in the nav bar)
- Click on the agent you want to edit
- Click the three dots menu (⋯) on the far right-hand side of the screen
- Select Edit Agent
This will open the agent in the builder with all its existing blocks and connections.
Error Handling
When a block fails during execution, it produces data on its error pin. As the agent creator, you decide how to handle errors:
- Surface the error: Connect the error pin to an output block so the error is returned as the agent's result
- Handle gracefully: Connect the error pin to other blocks that provide fallback behaviour, ensuring the agent continues working even when individual blocks encounter problems
Tips
- Name your input and output blocks clearly — these names become the labels users see when running your agent
- Test incrementally — save and run your agent frequently as you build to catch issues early
- Use the search in the blocks menu to quickly find what you need among hundreds of available blocks
- Check the pin types — connections only work between compatible pin types
Platform Agent Library (`docs/platform/agent-library.md`)
Agent Library
Overview
The Agent Library is your personal collection of agents. From here you can run agents, view task history, set up schedules and triggers, edit agents, and manage your collection.
Access: Click the Agents button in the navigation bar.
Library View
Your library displays all of your agents, including agents you've built yourself and agents you've added from the marketplace. Each agent shows its name and key information at a glance.
Favorites and folders
Click the heart icon on an agent to add or remove it from the Favorites tab. Use folders to organize agents; create a folder from the library and choose Move to folder from an agent's menu.
Agent Detail View
Click on any agent to open its detail view. This screen provides:
- Tasks: A full history of every time this agent has been executed
- Scheduled: A list of active schedules for this agent
- Triggers: Configured webhook triggers for trigger-based agents
- Templates: Saved task inputs and credential references for reuse
These are accessible via tabs on the left-hand side of the agent screen (e.g., Tasks 286 | Scheduled 1 | Templates 0).
Agent Actions Menu
Click the three dots (⋯) on the far right-hand side of the agent screen to access:
| Action | Description |
|---|---|
| Edit Agent | Opens the agent in the builder; marketplace agents are read-only until duplicated |
| Delete Agent | Permanently removes the agent from your library |
| Export Agent to File | Downloads the agent as a file you can share with others |
Running an Agent
To run an agent manually:
- Open the agent from your library
- Click New agent task
- Fill in the required inputs and credentials
- Click Start Task
The task will begin executing immediately. You can watch the progress and view the results once it completes.
{% hint style="warning" %}
For a trigger-based agent, use the same New agent task dialog and click Set up Trigger. Trigger-based agents cannot be started manually. See Scheduling & Triggers for details.
{% endhint %}
Viewing Task Results
Every time an agent runs, it creates a task. To view task results:
- Open the agent from your library
- In the left-hand pane, browse the list of completed tasks
- Click on a task to view its details
A task detail view shows:
- Inputs: The values that were provided when the task started
- Outputs: The results the agent produced
- Cost: The run's reported cost, displayed at the top of the task detail view
To share an output, click Share results and explicitly enable sharing; copying the authenticated task URL does not create a public link. See Sharing & Exporting Agents for visibility and revocation details.
Importing an Agent
To import an agent from a file:
- Go to your Agent Library
- Click Import
- Keep AutoGPT agent selected
- Choose the exported JSON file, complete the requested metadata, and click Upload
The agent will be added to your library and can be run or edited like any other agent. This is useful for importing agents shared by other users outside of the marketplace.
Deleting an Agent
- Open the agent from your library
- Click the three dots (⋯) on the far right
- Select Delete Agent
- Confirm the deletion when prompted
{% hint style="danger" %}
Deleting an agent is permanent and cannot be undone. Make sure you want to remove it before confirming.
{% endhint %}
Platform Aimlapi (`docs/platform/aimlapi.md`)
🧠 Running AI/ML API with AutoGPT
Follow these steps to connect AI/ML API with the AutoGPT platform for high-performance AI text generation.
✅ Prerequisites
- Make sure you have gone through and completed the AutoGPT Setup Guide, and AutoGPT is running locally at
http://localhost:3000. - You have an API key from AI/ML API.
⚙️ Setup Steps
1. Start AutoGPT Locally
Follow the official guide:
📖 AutoGPT Getting Started Guide
Make sure AutoGPT is running and accessible at:
http://localhost:3000
💡 Keep AutoGPT running in a terminal or Docker throughout the session.
2. Open the Visual Builder
Open your browser and go to:
http://localhost:3000/build
Or click “Build” in the navigation bar.
3. Add an AI Text Generator Block
Click the "Blocks" button on the left sidebar.
In the search bar, type
AI Text Generator.Drag the block into the canvas.
4. Select an AI/ML API Model
Click the AI Text Generator block to configure it.
In the LLM Model dropdown, select the model currently available from AI/ML API:
| Model | Model ID |
|---|---|
| Llama 3.3 70B Instruct Turbo | meta-llama/Llama-3.3-70B-Instruct-Turbo |
✅ This model is available via the OpenAI-compatible API from AI/ML API
5. Configure the Prompt and API Key
Inside the AI Text Generator block:
- Enter your prompt text in the Prompt field.
- Enter your AI/ML API Key in the designated field.
🔐 You can get your key from:
https://aimlapi.com/app/keys/
6. Save Your Agent
Click the “Save” button at the top-right of the builder interface:
- Give your agent a name (e.g.,
aimlapi_test_agent). - Click “Save Agent” to confirm.
💡 Saving allows reuse, scheduling, and chaining in larger workflows.
7. Run Your Agent
From the workspace:
- Press “Run” next to your saved agent.
- The request will be sent to the selected AI/ML API model.
8. View the Output
- Scroll to the AI Text Generator block.
- Check the Output panel below it.
- You can copy, export, or pass the result to further blocks.
🔄 Expand Your Agent
Now that AI/ML API is connected, expand your workflow by chaining additional blocks:
- 🔧 Tools – fetch URLs, call APIs, scrape data
- 🧠 Memory – retain context across interactions
- ⚙️ Actions / Chains – create full pipelines
🎉 You’re now generating AI responses using enterprise-grade models from AI/ML API in AutoGPT!
Platform Autopilot (`docs/platform/autopilot.md`)
AutoPilot
Overview
AutoPilot is your AI assistant built directly into the AutoGPT Platform. It can perform supported platform actions through natural conversation — from running agents to generating images, conducting research, and building agents for you.
Accessing AutoPilot
Open AutoPilot from Home or New Task in the main navigation, or go directly to platform.agpt.co/copilot.
What AutoPilot Can Do
AutoPilot can use supported platform tools. Here's what it can help you with:
Run Agents
Ask AutoPilot to run a supported agent in your Agent Library. It will request any missing inputs or credentials before executing it.
Build Agents
Describe the workflow you want and AutoPilot will create an agent for you in the builder. You can also ask it to edit your existing agents; duplicate a read-only marketplace agent before editing it. See the Agent Builder Guide for hands-on editing.
Browse the Marketplace
Ask AutoPilot to find agents for a specific use case and it will search the marketplace for you. You can also browse the Marketplace directly.
Execute Blocks Directly
AutoPilot can run supported blocks directly without building a full agent. This means you can:
- Conduct research with Perplexity
- Generate images with the latest image models
- Edit pictures using AI image editing blocks
- Generate videos using video generation blocks
- Run supported models on inference services such as Replicate
- Make custom HTTP requests to APIs you configure
- Write and execute code with supported coding tools
Manage Your Library
AutoPilot can help you manage your agent library, view task results, set up schedules, and more.
Tips for Using AutoPilot
- Be specific: The more detail you provide, the better AutoPilot can assist you. Instead of "make me an agent", try "build me an agent that takes a blog topic as input, generates an outline with Claude, then writes the full article".
- Iterate: You can refine results by asking follow-up questions or requesting changes.
- Explore capabilities: If you're unsure whether AutoPilot supports something, ask it or browse the available blocks.
AutoPilot vs. the Agent Builder
AutoPilot and the Agent Builder offer complementary ways to work on the platform. AutoPilot can create and edit reusable agents through natural language and run supported blocks directly, while the builder provides hands-on control over the graph.
| AutoPilot | Agent Builder | |
|---|---|---|
| Who is it for | Everyone — no technical knowledge required | Technical users with a grasp of visual programming |
| Best for | Natural language interaction, doing things fast, and accessing blocks directly | Hands-on visual control over exactly how an agent is wired together |
| How it works | Conversational — describe what you want | Visual — drag, drop, and connect blocks on a canvas |
| Can build agents | Yes — describe what you want and it builds the agent for you | Yes — you build the agent manually |
| Can edit agents | Yes — for user-created or duplicated editable agents | Yes — full visual editing |
| Block access | Can run supported blocks directly without building an agent | Blocks must be connected into an agent workflow |
Choose whichever approach suits you. Many users use both — AutoPilot for speed, and the builder when they want fine-grained visual control over their workflow.
Platform Block Sdk Guide (`docs/platform/block-sdk-guide.md`)
Block Creation with SDK
This guide explains how to create new blocks for the AutoGPT Platform using the SDK pattern with advanced features.
Overview
Blocks are reusable components that perform specific tasks in AutoGPT workflows. They can integrate with external services, process data, or perform any programmatic operation.
Basic Structure
1. Create Provider Configuration
First, create a _config.py file to configure your provider using the ProviderBuilder:
!!! note "Simple API key provider"
```python
from backend.sdk import BlockCostType, ProviderBuilder
my_provider = (
ProviderBuilder("my_provider")
.with_api_key("MY_PROVIDER_API_KEY", "My Provider API Key")
.with_base_cost(1, BlockCostType.RUN)
.build()
)
```For OAuth providers:
!!! note "OAuth provider configuration"
```python
from backend.sdk import BlockCostType, ProviderBuilder
from ._oauth import MyProviderOAuthHandler
my_provider = (
ProviderBuilder("my_provider")
.with_oauth(
MyProviderOAuthHandler,
scopes=["read", "write"],
client_id_env_var="MY_PROVIDER_CLIENT_ID",
client_secret_env_var="MY_PROVIDER_CLIENT_SECRET",
)
.with_base_cost(1, BlockCostType.RUN)
.build()
)
```2. Create the Block Class
Create your block file (e.g., my_block.py):
/* Detailed source-code truncated for AI context efficiency. */!!! note "Input Schema Fields"
- credentials: Use my_provider.credentials_field() to add provider authentication
- query: Simple string field with description
- limit: Integer field with validation constraints (ge=1, le=100)
- advanced_option: Marked with advanced=True to hide from basic UI
!!! note "Output Schema Fields"
- results: List of results from the block
- count: Total count of results
- The error output pin is already defined on BlockSchemaOutput
!!! note "Block Initialization"
- id: Generate a unique ID using uuid.uuid4()
- description: Brief description of what the block does
- categories: Choose from BlockCategory enum (e.g., SEARCH, AI, PRODUCTIVITY)
- input_schema / output_schema: Assign the Input and Output classes
!!! note "Run Method"
- Implement your block logic in process_data() helper method
- Use credentials.api_key.get_secret_value() to access the API key
- Use yield to output results
- Do NOT wrap your logic in try/except — the executor framework handles errors at a higher level. Raise exceptions (e.g., BlockExecutionError, BlockInputError) for user-fixable errors; let unexpected errors propagate naturally. See Error Handling in new_blocks.md for details.
Key Components Explained
Provider Configuration
!!! tip "Simple API Key Integrations"
For integrations that only use API key authentication, define credentials in _auth.py and use a small _config.py to register the provider description and supported auth types for the settings UI. See API-Key-Only Provider Auth in the main block guide for the complete pattern.
The ProviderBuilder is available for advanced use cases (OAuth, webhooks, cost tracking). It allows you to:
.with_api_key(): Add API key authentication.with_oauth(): Add OAuth authentication.with_base_cost(): Set resource costs for the block.with_webhook_manager(): Add webhook support.with_user_password(): Add username/password auth
Block Schema
- Input/Output classes: Define the data structure using
BlockSchema - SchemaField: Define individual fields with validation
- CredentialsMetaInput: Special field for handling credentials
Block Implementation
- Unique ID: Generate using
uuid.uuid4() - Categories: Choose from
BlockCategoryenum (e.g., SEARCH, AI, PRODUCTIVITY) - async run(): Main execution method that yields outputs
- Error handling: Error output pin is already defined on BlockSchemaOutput
Advanced Features
Testing
Add test configuration to your block:
!!! note "Test Configuration"
python def __init__(self): super().__init__( # ... other config ... test_input={ "query": "test query", "limit": 5, "credentials": { "provider": "my_provider", "id": str(uuid.uuid4()), "type": "api_key" } }, test_output=[ ("results", ["result1", "result2"]), ("count", 2) ], test_mock={ "process_data": lambda *args, **kwargs: ["result1", "result2"] } )
OAuth Support
Create an OAuth handler in _oauth.py:
!!! note "OAuth Handler Implementation"
```python
from backend.integrations.oauth.base import BaseOAuthHandler
class MyProviderOAuthHandler(BaseOAuthHandler):
PROVIDER_NAME = "my_provider"
def _get_authorization_url(self, scopes: list[str], state: str) -> str:
# Implement URL generation for OAuth flow
pass
def _exchange_code_for_token(self, code: str, scopes: list[str]) -> dict:
# Implement token exchange logic
pass
```Webhook Support
Create a webhook manager in _webhook.py:
!!! note "Webhook Manager Implementation"
```python
from backend.integrations.webhooks._base import BaseWebhooksManager
class MyProviderWebhookManager(BaseWebhooksManager):
PROVIDER_NAME = "my_provider"
async def validate_event(self, event: dict) -> bool:
# Implement event validation logic
pass
```File Organization
backend/blocks/my_provider/
├── __init__.py # Export your blocks
├── _config.py # Provider configuration
├── _oauth.py # OAuth handler (optional)
├── _webhook.py # Webhook manager (optional)
├── _api.py # API client wrapper (optional)
├── models.py # Data models (optional)
└── my_block.py # Block implementationsBest Practices
- Error Handling: Use
BlockInputErrorfor validation failures andBlockExecutionErrorfor runtime errors (import frombackend.util.exceptions). These inherit fromValueErrorso the executor treats them as user-fixable. See Error Handling in new_blocks.md for details. - Credentials: Use the provider's
credentials_field()method - Validation: Use SchemaField constraints (ge, le, min_length, etc.)
- Categories: Choose appropriate categories for discoverability
- Advanced Fields: Mark complex options as
advanced=True - Async Operations: Use
async/awaitfor I/O operations - API Clients: Use
Requests()from SDK or external libraries - Testing: Include test inputs/outputs for validation
Common Patterns
Making API Requests
from backend.sdk import Requests
async def run(self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs):
headers = {
"Authorization": f"Bearer {credentials.api_key.get_secret_value()}",
"Content-Type": "application/json"
}
response = await Requests().post(
"https://api.example.com/endpoint",
headers=headers,
json={"query": input_data.query}
)
data = response.json()
yield "results", data.get("results", [])Multiple Auth Types
async def run(
self,
input_data: Input,
*,
credentials: OAuth2Credentials | APIKeyCredentials,
**kwargs
):
if isinstance(credentials, OAuth2Credentials):
# Handle OAuth credentials
token = credentials.access_token.get_secret_value()
else:
# Handle API key credentials
token = credentials.api_key.get_secret_value()!!! note "Authentication Types"
- OAuth2Credentials: Access token via credentials.access_token.get_secret_value()
- APIKeyCredentials: API key via credentials.api_key.get_secret_value()
Handling Files
When your block works with files (images, videos, documents), use store_media_file():
from backend.data.execution import ExecutionContext
from backend.util.file import store_media_file
from backend.util.type import MediaFileType
async def run(
self,
input_data: Input,
*,
execution_context: ExecutionContext,
**kwargs,
):
# PROCESSING: Need local file path for tools like ffmpeg, MoviePy, PIL
local_path = await store_media_file(
file=input_data.video,
execution_context=execution_context,
return_format="for_local_processing",
)
# EXTERNAL API: Need base64 content for APIs like Replicate, OpenAI
image_b64 = await store_media_file(
file=input_data.image,
execution_context=execution_context,
return_format="for_external_api",
)
# OUTPUT: Return to user/next block (auto-adapts to context)
result = await store_media_file(
file=generated_url,
execution_context=execution_context,
return_format="for_block_output",
)
yield "image_url", result!!! note "File Handling Patterns"
- PROCESSING: Use "for_local_processing" when you need a local file path for tools like ffmpeg, MoviePy, PIL
- EXTERNAL API: Use "for_external_api" when sending content to APIs like Replicate or OpenAI (returns base64 data URI)
- OUTPUT: Use "for_block_output" to return results - this automatically adapts: workspace:// in CoPilot, data URI in graphs
Return format options:
"for_local_processing"- Local file path for processing tools"for_external_api"- Data URI for external APIs needing base64"for_block_output"- Always use for outputs - automatically picks best format
Testing Your Block
# Run all block tests
poetry run pytest backend/blocks/test/test_block.py -xvs
# Test specific block
poetry run pytest 'backend/blocks/test/test_block.py::test_available_blocks[MyBlock]' -xvsIntegration Checklist
- Add provider to
ProviderNameinbackend/integrations/providers.pyif using that enum in credentials; SDK integrations can register names throughProviderBuilder - Define credentials with
_auth.pyhelpers or the SDK provider configuration, and register the description and supported auth types in_config.py - Implement block class with Input/Output schemas
- Generate unique block ID with
uuid.uuid4() - Choose appropriate block categories
- Implement
async run()method - Handle errors with
BlockInputError/BlockExecutionError(don't wrap in try/except) - Add test configuration (inline
test_input/test_outputand/or standalone{name}_test.py) - Add integration icon PNG at
frontend/public/integrations/{provider_name}.png - Run
cd autogpt_platform/backend && poetry run python scripts/generate_block_docs.pyto generate docs - Test the block
- Document any special requirements
Example Blocks for Reference
- Simple API:
/backend/blocks/firecrawl/- Basic API key authentication - OAuth + API:
/backend/blocks/linear/- OAuth and API key support - Webhooks:
/backend/blocks/exa/- Includes webhook manager
Study these examples to understand different patterns and approaches for building blocks.
Platform Copilot Local Llm (`docs/platform/copilot-local-llm.md`)
Running AutoPilot on a self-hosted LLM
Important: This page covers the AutoPilot chat path — the
conversational agent on/copilot. For the block-layer AI Text
Generator block (used inside agent graphs you build yourself), see
Running Ollama with AutoGPT. The two paths read different
env vars, so configuring one does not configure the other.Self-hosting only — the cloud
agpt.codeployment routes AutoPilot
through Anthropic / OpenRouter and ignores the variables below.
This guide makes the AutoPilot chat work without an Anthropic, OpenAI,
or OpenRouter key by routing it through any OpenAI-compatible HTTP
endpoint you control.
The transport is called local because it's the typical case, butCHAT_BASE_URL is just a URL — every deployment shape below works
equally well:
| Scenario | CHAT_BASE_URL |
|---|---|
| Ollama on the same Docker host (most common) | http://192.168.1.42:11434/v1 (LAN IP) |
| Ollama on the same Docker host, via Docker Desktop | http://host.docker.internal:11434/v1 |
| Ollama on a separate LAN box | http://ollama.lab.local:11434/v1 |
| Ollama behind an HTTPS reverse proxy on the public internet | https://ollama.example.com/v1 |
| vLLM, LocalAI, LM Studio, LiteLLM proxy | their respective /v1 URLs |
| A managed OpenAI-compatible API you don't pay AutoGPT for | its /v1 URL |
Anything that speaks the OpenAI /v1/chat/completions shape — includingtools=[...] for function calling — will work. The rest of this guide
uses Ollama as the running example because it's the easiest, but
substitute your own endpoint anywhere you see http://...:11434/v1.
How it works
AutoPilot's ChatConfig (backend/backend/copilot/config.py) recognises
four chat transports. When CHAT_USE_LOCAL=true:
| Transport behaviour | Local |
|---|---|
Routes the baseline (fast) path to CHAT_BASE_URL over OpenAI-compatible HTTP |
✅ |
| Supports the SDK / extended-thinking path (Claude Agent SDK) | ❌ — auto-downgrades to fast |
api_key falls back to OPEN_ROUTER_API_KEY / OPENAI_API_KEY if CHAT_API_KEY is unset |
❌ — explicit CHAT_API_KEY only |
Aux + advanced models (title_model, simulation_model, fast_advanced_model) inherit fast_standard_model if left at a cloud default |
✅ |
Allows non-anthropic/* SDK model slugs (vendor validator skipped) |
✅ |
The downgrade is logged at WARNING when an extended_thinking request
arrives — there is no 500. The frontend toggle should already be hidden
because the CHAT_MODE_OPTION LaunchDarkly flag defaults off in
self-hosted deployments.
On the managed cloud platform (BEHAVE_AS=cloud), CHAT_*_MODEL env
vars are the bottom layer of model resolution: LaunchDarkly per-user
override → the LLM catalog's routing cell
(backend/data/llm_registry/catalog.py) → env default, with slugs
unknown to the catalog or disabled in it refused at serve time.
On self-hosted installs — any transport — the catalog's routing cells
are skipped entirely: they are the cloud deployment's config traveling
in the shipped file, and they never override your CHAT_*_MODEL
configuration. Resolution here is LaunchDarkly → CHAT_*_MODEL, exactly
the pre-catalog behavior; your env vars stay authoritative. See
Managing LLM Models.
Required environment variables
In autogpt_platform/backend/.env:
# Turn on the local transport
CHAT_USE_LOCAL=true
# Where the OpenAI-compatible endpoint lives. From inside the docker
# containers this must NOT be 127.0.0.1 / localhost — use the host's LAN
# IP (e.g. 192.168.1.42) or, if you've added the directive to compose,
# host.docker.internal:host-gateway.
CHAT_BASE_URL=http://192.168.1.42:11434/v1
# Any non-empty string — Ollama doesn't validate it. The local transport
# deliberately does NOT fall back to OPENAI_API_KEY (which is usually
# present for graphiti / embedders), so this must be set explicitly.
CHAT_API_KEY=ollama
# The chat model. Bare model names ONLY — provider/model slugs (e.g.
# `anthropic/claude-...`) are passed through verbatim and Ollama can't
# resolve them. See "Picking a model" below.
CHAT_FAST_STANDARD_MODEL=hf.co/ornith-ai/Ornith-1.5-9B-GGUF:Q4_K_M
# Optional — override for the advanced tier. If you leave it out, the
# local transport derives title_model, simulation_model, AND
# CHAT_FAST_ADVANCED_MODEL from CHAT_FAST_STANDARD_MODEL automatically
# (see _apply_local_aux_models in backend/backend/copilot/config.py),
# so the advanced toggle never sends a cloud-only slug to Ollama. Set
# it explicitly only if you want a bigger model for the advanced tier
# and have the VRAM for it.
CHAT_FAST_ADVANCED_MODEL=qwen3:14b-q4_K_MPicking a model
The platform's chat loop calls OpenAI-style tool-calling on every
turn, streams responses, and ships an ~8 k-token system prompt. Pick a
model that handles all three.
| Tier | Recommended Ollama tag | Why | Footprint |
|---|---|---|---|
| Default | hf.co/ornith-ai/Ornith-1.5-9B-GGUF:Q4_K_M |
Official Ornith GGUF; agentic 9B model with OpenAI-compatible tool calling; 262,144-token native context; reasoning model (the chat UI renders its thinking separately from the answer) | ~5.8 GB model file; allow additional RAM for context and the KV cache |
| Tight RAM | qwen3:4b |
Smaller; native tools; set think: false to avoid the unclosed-`` tool-call render bug |
~3-4 GB resident |
| GPU / advanced | qwen3:14b-q4_K_M |
Best tool-selection accuracy in this size class | ~12 GB VRAM |
Pull whichever you choose:
ollama pull hf.co/ornith-ai/Ornith-1.5-9B-GGUF:Q4_K_MContext window — set it once, on the backend
Ollama defaults num_ctx to 4096 tokens regardless of the model's
advertised window (ollama/ollama#2714).
That's smaller than AutoPilot's system prompt + tool schemas — without a
larger window Ollama only sees the end of the instructions and responses
are incoherent or 500 outright.
Set the window once, on the server, via OLLAMA_CONTEXT_LENGTH. There is
no AutoGPT-side context config to keep in sync: AutoPilot reads the
backend's actual loaded window back at runtime — Ollama /api/ps, llama.cpp/props, vLLM / LM Studio /v1/models — and compacts the conversation under
it. Backends that don't report a window (LiteLLM proxy, Jan,
text-generation-webui) fall back to assuming 32k.
The default Ornith model has a 262,144-token native window, so the installer
sets OLLAMA_CONTEXT_LENGTH=262144. This maximizes available conversation
history but substantially increases KV-cache RAM/VRAM use. Operators using a
custom model or constrained hardware can lower it, but should keep at least
24k: below that, the system prompt + tools leave almost no room for
conversation and AutoPilot logs a warning.
The installer sets OLLAMA_CONTEXT_LENGTH for you. Manual setup per platform:
Linux (systemd drop-in):
# /etc/systemd/system/ollama.service.d/host.conf
[Service]
Environment="OLLAMA_HOST=0.0.0.0:11434"
Environment="OLLAMA_CONTEXT_LENGTH=262144"Then sudo systemctl daemon-reload && sudo systemctl restart ollama.
macOS (launchctl, persists across logins for launchd-spawned processes):
launchctl setenv OLLAMA_HOST 0.0.0.0:11434
launchctl setenv OLLAMA_CONTEXT_LENGTH 262144
# Then restart Ollama — either:
brew services restart ollama # if installed via the brew formula
# …or quit the menu-bar app and relaunch it (the .dmg install)Windows (user-scope env vars, persists across reboots):
setx OLLAMA_HOST "0.0.0.0:11434"
setx OLLAMA_CONTEXT_LENGTH "262144"
# Then quit Ollama from the system tray and relaunch it
# (setx writes to HKCU but does NOT update already-running processes).Verify on any platform with ollama ps (the CONTEXT column should
show your value, e.g. 262144). If you change it, AutoPilot picks up the
new window automatically on the next turn — nothing else to update.
Networking — same host, different host, or remote
The endpoint can be on the same machine, on the LAN, or anywhere
internet-reachable. Pick whichever matches your deployment shape:
Same host as the AutoGPT containers
How containers reach the host depends on whether you're on Docker
Desktop (macOS / Windows) or native Docker (Linux):
macOS + Windows (Docker Desktop) — every container already has ahost.docker.internal entry pointing at the host. No extra wiring:
CHAT_BASE_URL=http://host.docker.internal:11434/v1Still set OLLAMA_HOST=0.0.0.0:11434 so the .app/tray-managed Ollama
accepts the connection from the Desktop network — by default it binds
only to 127.0.0.1.
Linux (native Docker) — there's no auto-injectedhost.docker.internal. Pick one:
- Use the LAN IP in
CHAT_BASE_URL— simplest, works everywhere. - Bind Ollama to all interfaces:
OLLAMA_HOST=0.0.0.0:11434(set
in the systemd unit or a drop-in), so containers reach it via the
bridge gateway. - Add
extra_hosts: ["host.docker.internal:host-gateway"]to the
chat services inautogpt_platform/docker-compose.yml.
The bundled installer does these for you on a fresh box:
| Platform | Command |
|---|---|
| Linux | installer/setup-autogpt.sh --with-ollama |
| macOS | installer/setup-autogpt.sh --with-ollama |
| Windows | installer\setup-autogpt.bat /with-ollama |
Different LAN box (dedicated GPU server, NAS, …)
Set CHAT_BASE_URL to the box's hostname or IP:
CHAT_BASE_URL=http://gpu-rig.lab.local:11434/v1On the Ollama box, set OLLAMA_HOST=0.0.0.0:11434 so it accepts
non-loopback connections, and either open port 11434 in the firewall
for your AutoGPT host's IP or put both behind a private VPN /
WireGuard mesh.
Remote / public-internet endpoint
Two approaches, in increasing order of "please do this":
Trusted private network (Tailscale, WireGuard, ZeroTier,
corporate VPN). Treat the remote endpoint exactly like a LAN box.Public HTTPS with auth — terminate TLS at a reverse proxy
(Caddy, nginx, Cloudflare Tunnel) in front of Ollama / vLLM /
whatever, and require a bearer token. Set:bashCHAT_BASE_URL=https://ollama.example.com/v1 CHAT_API_KEY=<the-bearer-the-proxy-checks>
Do not expose raw Ollama on the public internet. Ollama itself
performs no authentication — anyone who can reach:11434can
use (and exhaust) your model. Always front it with a proxy that
enforces a token.
Other platform features that use the local transport
The local transport isn't just for AutoPilot chat. The same client
flows to every backend helper that needs an LLM, so a singleCHAT_USE_LOCAL=true install also covers:
- Dry-run block simulator — when a user clicks "Test" in the agent
builder, blocks role-play their execution against an LLM rather than
hitting external APIs. UsesChatConfig.simulation_model
(auto-derived tofast_standard_modelunder local). - Onboarding business-understanding extraction — the post-signup
Tally form is extracted into structured suggestions via the LLM.
UsesChatConfig.title_model. - Long-run prompt compression — the chat / agent loop summarizes
message history when context grows beyond a threshold. - Marketplace semantic search — the store generates embeddings for
agent descriptions to power hybrid (lexical + semantic) ranking.
Hybrid search degrades gracefully to lexical-only when no embedding
backend is available.
Embeddings (marketplace search, agent uploads)
The store's embedding model is overridable via env so deployments with
a compatible backend (vLLM, LiteLLM proxy, Ollama with an embedding
model pulled, Azure OpenAI) can swap models without a code change.
The replacement model must emit 1536-dim vectors — the pgvector
column is declared vector(1536) in schema.prisma and inserts with
any other dim hard-fail.
# Default — OpenAI text-embedding-3-small (1536 dim):
STORE_EMBEDDING_MODEL=text-embedding-3-small
# Example: nomic-embed-text on Ollama emits 768 dims natively, so it
# DOES NOT fit the existing schema — picking it would break every
# publish + reindex. Use a 1536-dim model instead, e.g.
# text-embedding-ada-002 (OpenAI legacy) or one of the LiteLLM proxy's
# 1536-dim shims. Custom-dim support would need a schema migration
# beyond the scope of this guide.pgvector dimension is fixed in the schema, not configurable at
runtime. A model that emits a different vector length will succeed
at the embedding call and fail at every subsequentINSERT. If you
need a different dim, you'll need to fork the schema and migrate
existing rows — it's not a runtime knob.
If you don't configure an embedding backend at all, marketplace
hybrid search auto-degrades to lexical-only (no semantic ranking) —
not fatal, just less smart.
Verifying the wiring
After docker compose up -d:
# 1. CHAT_USE_LOCAL is in the live container env
docker exec autogpt_platform-copilot_executor-1 env | grep ^CHAT_
# CHAT_USE_LOCAL=true
# CHAT_BASE_URL=http://192.168.1.42:11434/v1
# ...
# 2. Send a turn from the UI, then confirm baseline routing in the log
docker logs autogpt_platform-copilot_executor-1 | grep -E "Using.*service"
# [CoPilotExecutor|...] Using baseline service (mode=default)
# 3. Confirm Ollama saw the request — per platform:
# Linux (systemd-managed Ollama):
journalctl -u ollama --since "1 minute ago" | grep "POST"
# [GIN] ... | 200 | 7.5s | ... | POST "/v1/chat/completions"
# macOS (brew formula):
tail -F "$(brew --prefix)/var/log/ollama.log" | grep "POST"
# macOS (.app from ollama.com): logs live in ~/.ollama/logs/server.log
tail -F ~/.ollama/logs/server.log | grep "POST"
# Windows: the Ollama tray app writes to %LOCALAPPDATA%\Ollama\server.log
powershell -Command "Get-Content $env:LOCALAPPDATA\Ollama\server.log -Wait | Select-String POST"If Using baseline service appears and Ollama logs a 200, the
end-to-end path is working — any remaining errors are model / RAM /
quantization concerns rather than config-routing bugs.
Troubleshooting
Frontend shows "The assistant encountered an error" — check the
copilot_executor log for the upstream error. Common causes:
model requires more system memory (X GiB) than is available (Y GiB)
→ free RAM (stop ClamAV, raise VM memory) or pick a smaller modelmodel "..." not found→ollama pull <slug>firstconnection refused→ containers can't reach the host on:11434;
see "Container → host networking" above
api_key is None even though I set OPENAI_API_KEY — by design.
The local transport requires an explicit CHAT_API_KEY so a stray cloud
key set for graphiti / embedders doesn't silently bind to your local
backend as the bearer token.
Title generation fails / returns "Untitled chat" — title_model
should auto-inherit fast_standard_model under the local transport. If
you've explicitly set CHAT_TITLE_MODEL=openai/gpt-4o-mini somewhere,
remove it.
Slow first response — Ollama loads the model into RAM on the first
request, which can take 5-15 s for 8 B models on CPU. Subsequent
requests are much faster while the model stays resident.
Every AutoPilot turn takes minutes on CPU — expected on CPU-only
hosts, not a hang. AutoPilot ships an ~8 k-token system prompt and the
model must prefill (compute KV-cache state for) every token of that
prompt before the first output token is emitted. On 4 CPU cores an
8 B Q4 model prefills at roughly 3-4 tokens/sec, so a fresh turn
takes ~35-45 min just to start generating. Title generation (~70-token
prompt) finishes in seconds because there's almost nothing to prefill.
A consumer GPU brings this down to seconds. If you're CPU-only and
just want to validate the install end-to-end, tail the Ollama server
log (see the per-platform commands in "Verifying the wiring" above)
and watch for the POST /v1/chat/completions line — once it appears
with a 200, prefill finished and the model is generating.
Dream pass + memory under local transport
The graphiti memory layer and the nightly dream pass both ride the
same self-hosted backend CHAT_USE_LOCAL=true points at. Three
things you should know:
Dream pass runs sync-baseline only
The dream pass's batch path (Anthropic batch, OpenAI batch) is
provider-locked and unavailable on local backends. CHAT_USE_LOCAL=true
forces execution_path="sync_baseline" regardless of which API keys
might be set elsewhere on the box — your local LLM handles all three
phases (consolidate / recombine / sanitize) on the same endpoint as
chat. Cost-log rows label provider="ollama" so the admin
platform-costs dashboard distinguishes them from cloud spend.
Memory uses the chat models by default
When CHAT_USE_LOCAL=true, GraphitiConfig._apply_local_graphiti_models
rewrites the cloud OpenAI defaults to local Ollama equivalents:
| Setting | Cloud default | Local default |
|---|---|---|
GRAPHITI_LLM_MODEL |
gpt-4.1-mini |
hf.co/ornith-ai/Ornith-1.5-9B-GGUF:Q4_K_M |
GRAPHITI_RERANKER_MODEL |
gpt-4.1-nano |
hf.co/ornith-ai/Ornith-1.5-9B-GGUF:Q4_K_M |
GRAPHITI_EMBEDDER_MODEL |
text-embedding-3-small |
nomic-embed-text |
The LLM + reranker reuse the same Ornith 1.5 9B model the --with-ollama
installer already pulls for chat, so no extra ollama pull is needed
unless you've overridden them. The embedder is a separate model — see
the next section.
You can pin your own slugs at any time by setting the matchingGRAPHITI_*_MODEL env var; the validator only touches slots still at
their cloud default. A custom slug (qwen3:8b, hf.co/...,my-registry.io/model:tag) passes through untouched.
Embeddings require an embedding model pulled into Ollama
Ollama doesn't ship an embedding model in its default model set, so
graphiti's per-turn entity extraction will 404 on /v1/embeddings
until you pull one:
ollama pull nomic-embed-textWithout it, chat still works, but graphiti.add_episode(...) fails
silently per turn — the agent loses memory of the conversation
between sessions. With it pulled, graphiti round-trips
embeddings against the same Ollama endpoint as the LLM, and
warm-context retrieval works end-to-end on the local stack.
If you'd rather use a different embedding model (e.g.mxbai-embed-large for higher recall at higher disk cost), pull
that and set GRAPHITI_EMBEDDER_MODEL=<slug> to override the
local default.
Community rebuild stays on sync tier
graphiti_config.community_rebuild_use_flex_tier=True (the default)
is treated as a request, not a guarantee. OpenAI's flex tier only
delivers the ~50% discount through OpenRouter's pass-through to
OpenAI / Google upstreams, so on local + Anthropic transports the
flex client is silently swapped for the regular OpenAIClient
(logged at INFO). The weekly community rebuild still runs — at full
sync price, which on local Ollama is $0.
Subscription mode caveat
If you also use Claude Code subscription (CHAT_USE_CLAUDE_CODE_SUBSCRIPTION=true)
for the chat path, the dream pass needs a separate ANTHROPIC_API_KEY
set in the environment. The Claude Code OAuth token authenticates the
chat CLI only; per Anthropic's Feb-2026 ToS update, OAuth tokens
cannot call the Messages API directly. Without ANTHROPIC_API_KEY,
the dream pass writes an errored JobStatus with a friendly hint
pointing you at this section.
A separate Anthropic Agent-SDK credit pool launches 2026-06-15
($20 Pro / $100 Max-5x / $200 Max-20x at standard API rates,
one-time opt-in). Once that lands, subscription users will have
the option of routing the dream pass through the Agent SDK instead
of the Messages API — coverage tracked as a post-Jun-15 follow-up.
Platform Create Basic Agent (`docs/platform/create-basic-agent.md`)
**Creating a Basic AI Agent with AutoGPT**
**Overview**
This guide walks through creating a simple question-answer AI agent using AutoGPT's visual builder. This is a basic example that can be expanded into more complex agents.
**Prerequisites**
**Cloud-Hosted AutoGPT**
If you're using the cloud-hosted version at agpt.co, you're ready to go! AI blocks come with built-in credits — no API keys required to get started. If you'd prefer to use your own API keys, you can add them via Profile → Integrations.
**Self-Hosted (Docker)**
If you're running AutoGPT locally with Docker, you'll need to add your own API keys to autogpt_platform/backend/.env:
# Create or edit backend/.env
OPENAI_API_KEY=sk-your-key-here
ANTHROPIC_API_KEY=sk-ant-your-key-here
# Add other provider keys as neededAfter adding keys, restart the services:
docker compose down && docker compose up -dNote: The Calculator example below doesn't require any API credentials — it's a good way to test your setup before adding AI blocks.
**Example Agent: Q&A (with AI)**
A step-by-step guide to creating a simple Q&A agent using input and output blocks.
**Components Required**
- Input Block
- AI Text Generator Block
- Output Block
**Step-by-Step Instructions**
**1. Set Up the Basic Structure**
- Add an Input Block
- Add an AI Text Generator Block
- Add an Output Block
**2. Connect the Components**
- Connect the Input Block to the AI Text Generator's Prompt
- Connect the AI Text Generator's response to the Output Block's value
**3. Configure Block Names**
- Name the Input Block: "question"
- Name the Output Block: "answer"
**4. Save the Agent**
- Click the Save button
- Name your agent (e.g., "question and answer")
**5. Test the Agent**
- Click the Run button
- Enter a question in the input field (e.g., "How far is Earth from Pluto?")
- View the results through either:
- The "View More" option
- The "Agent Outputs" section
**Viewing Results**
You can access the AI's response in two ways:
- Click "View More" for detailed results
- Check the "Agent Outputs" section for the response
**Example Agent: Calculator (without AI)**
A step-by-step guide to creating a simple calculator agent using input and output blocks.
##Components Required
- Input Block (2 instances)
- Calculator Block
- Output Block
**Setup Instructions**
**1. Adding Input Blocks**
- Add two input blocks, positioned one below the other
- Name the first input block "a"
- Name the second input block "b"
**2. Adding Calculator Block**
- Add a calculator block to your workspace
- Position it after the input blocks
**3. Adding Output Block**
- Add an output block
- Name it "results"
**4. Connecting the Blocks**
- Connect the "a" input block to the "a" input on the calculator block
- Connect the "b" input block to the "b" input on the calculator block
- Connect the calculator block's result to the value input of the output block
**5. Saving Your Agent**
- Name your agent (e.g., "Simple Calculate Agent")
- Save the configuration
**Testing Your Calculator**
**Running Calculations**
- Enter values in both input blocks
- Select the desired operation (e.g., multiply)
- Click the "Run" button to execute the calculation
**Viewing Results**
There are two ways to view the calculation results:
- Click "View More" to see the detailed output
- Check the "Agent Outputs" section, which displays results from the output block
**Example Calculation**
- Input A: 227
- Input B: 17
- Operation: Multiply
- Expected output will be shown in the results section
**Tips**
- Always save your agent after making changes
- Verify all connections are properly made before running
- Use the "View More" option for detailed output information
**Note**
While these are basic example, you can enhance the agent with additional blocks and functionality to create more complex interactions.
Platform Credits And Billing (`docs/platform/credits-and-billing.md`)
Credits & Billing
Overview
The AutoGPT Platform uses automation credits for metered agent execution. This guide explains how usage is priced, how to review costs, and how to manage your balance.
{% hint style="info" %}
Plans and pricing can change. See the pricing page for current details.
{% endhint %}
How Credits Work
Credits are charged when blocks run. Depending on the block, the final charge may be fixed or based on actual usage, such as tokens, provider cost, elapsed time, data size, or number of items. For usage-based blocks, the platform may estimate a charge before execution and reconcile it against actual usage afterward.
Block Pricing
Block pricing varies by block, configuration, and usage. Review the estimate shown in the platform before running an agent; the final charge for usage-based blocks may differ from the estimate.
Your total cost depends on your plan and automation-credit usage. See the pricing page for current subscription and included-credit details.
Checking Your Balance
Your balance is available from the automation-credits wallet in the platform navigation and from Billing settings.
Viewing Task Costs
To review the cost of a specific agent run:
- Go to your Agent Library
- Open the agent
- Click on a completed task in the left-hand pane
- The task detail header displays Cost in dollars.
{% hint style="info" %}
Use the Automation Credits section in Billing settings for account-level usage and transaction history. Individual task costs are also available within each agent.
{% endhint %}
Running Out of Credits
If your automation-credit balance is exhausted, paid blocks cannot start until credits are added. Scheduled tasks may fail for insufficient balance. Final reconciliation for a usage-based block that already ran can leave the balance below zero.
You can add credits manually or configure auto-refill from Billing settings.
Adding Credits
- Open Settings from your profile menu.
- Select Billing.
- Open the Automation Credits tab.
- Select Add credits and follow the checkout prompts.
You can also go directly to Billing settings.
Platform D Id (`docs/platform/d_id.md`)
Find available voices for D-ID
ElevenLabs
- Select any voice from the voice list: https://api.elevenlabs.io/v1/voices
- Copy the voice_id
- Use it as a string in the voice_id field in the CreateTalkingAvatarClip Block
Microsoft Azure Voices
- Select any voice from the voice gallery: https://speech.microsoft.com/portal/voicegallery
- Click on the "Sample code" tab on the right
- Copy the voice name, for example: config.SpeechSynthesisVoiceName ="en-GB-AbbiNeural"
- Use this string en-GB-AbbiNeural in the voice_id field in the CreateTalkingAvatarClip Block
Amazon Polly Voices
- Select any voice from the voice list: https://docs.aws.amazon.com/polly/latest/dg/available-voices.html
- Copy the voice name / ID
- Use it as string in the voice_id field in the CreateTalkingAvatarClip Block
Platform Data Flow And Execution (`docs/platform/data-flow-and-execution.md`)
Data Flow & Execution
Overview
Understanding how agents execute is key to building effective workflows. This guide explains how data flows through an agent, what determines execution order, and how to work with lists and errors.
Execution Order
Agent execution follows the graph's starting nodes, data connections, configured values, and input validation.
How It Works
- Execution starts from every starting node: an Agent Input block or any executable block with no inbound links.
- A downstream block is queued when its required inputs validate after upstream outputs and configured values are applied.
- Execution continues until no more downstream blocks can be queued; conditional branches and skipped nodes mean not every block necessarily runs.
- Output blocks collect any results that reach them.
Required Inputs
A block will only execute when:
- All connected input pins have received data from their upstream blocks
- All required input pins have values — either from a connection or from a hardcoded value set directly on the block
This means you can have blocks that don't depend on each other execute in any order, while blocks that depend on the output of another block will always wait.
Working with Pins
Pin Types
Input and output pins are typed. Common types include:
- Text: String values
- Number: Numeric values
- File: File uploads or downloads
- List: Arrays of items
- Boolean: True/false values
- Object: Structured data
Connections can only be made between compatible pin types.
Data Flow Visualisation
When an agent is running, you can see data moving through the workflow in real time. Data flow is represented by a coloured bead that slides along each connection line from the output pin to the input pin, giving you a clear visual of what's happening.
Working with Lists
Blocks can handle list data in flexible ways:
- Outputting lists: Some blocks produce a list of items as their output. You can choose to receive the full list as a single output or receive individual items one at a time.
- Iterating over lists: You can send a list into a block that iterates through its contents, yielding each item one by one. This is useful for processing each item in a list independently.
This makes it straightforward to build agents that process batches of data — for example, fetching a list of URLs and then processing each one through an AI block.
Error Handling
When a block fails during execution, it does not automatically stop the entire agent. Instead:
- The failed block produces data on its error pin
- What happens next depends on how you've wired the agent
Handling Errors Gracefully
You have full control over error handling through the block connections:
- Surface the error: Connect the error pin to an output block to return the error as part of the agent's result. This is useful for debugging or when you want users to see what went wrong.
- Handle and continue: Connect the error pin to other blocks that provide fallback behaviour. For example, retry with different settings, use a default value, or route to an alternative workflow path.
- Ignore the error: If the error pin is not connected, the error data is simply not propagated. Downstream blocks that depend on the failed block's normal output pins will not execute (since their inputs won't be satisfied).
{% hint style="info" %}
Building robust agents means thinking about what happens when things go wrong. Consider connecting error pins to output blocks during development so you can see any issues, then add proper error handling once your agent is working.
{% endhint %}
Execution Summary
| Concept | How It Works |
|---|---|
| Execution order | Determined by starting nodes, data connections, configured values, and input validation |
| Starting point | Agent Input blocks and executable blocks with no inbound links |
| Ending point | Output blocks collect final results |
| Parallel execution | Blocks with no dependencies on each other can execute in any order |
| Error handling | Failed blocks yield data on their error pin — you decide what to do with it |
| Lists | Can be processed as a whole or iterated item by item |
| Visual feedback | Coloured beads slide along connection lines during execution |
Platform Delete Agent (`docs/platform/delete-agent.md`)
**How to Delete an Agent in AutoGPT**
**Overview**
Is your AutoGPT Monitor getting cluttered or did you create an agent you no longer see? Learn how to quickly delete unwanted agents.
**Steps to Delete an Agent**
- Locate the Agent
- Navigate to the Monitor Tab in the AutoGPT builder
- Find the agent you want to delete from the list
- Select the Agent
- Click on the agent you wish to delete
- Delete the Agent
- Look for the trash icon on the right side of the interface
- Click the trash icon
- A confirmation dialog will appear asking "Are you sure you want to delete this agent?"
- Click "Yes, delete" to confirm
Once confirmed, the agent will be immediately removed from your list.
Note: This action cannot be undone, so make sure you want to permanently delete the agent before confirming.
Platform Download Agent From Marketplace Local (`docs/platform/download-agent-from-marketplace-local.md`)
**How to Download and Import an Agent from the AutoGPT Marketplace (Local Hosting)**
**Overview**
This guide explains how to download an agent from the AutoGPT marketplace and import it into your locally hosted instance.
**Prerequisites**
- A local installation of AutoGPT
- Access to the marketplace for local hosters
**Step-by-Step Process**
**1. Access the Marketplace**
- Navigate to the marketplace section
- Note: The interface will differ from the cloud-hosted platform
**2. Download the Agent**
- Browse and select your desired agent
- Click on the agent to view details
- Look for and click the "Download Agent" button
- The agent file will save to your computer
**3. Import the Agent**
- Return to the Monitor tab
- Locate the dropdown menu next to "Create"
- Select "Import from File"
- In the file selection dialog:
- Navigate to your downloaded agent file
- Select the file
- Click "Open"
- Click "Import and Edit"
**4. Verify Import**
- The agent should now appear in your Builder
- You can begin editing or using the imported agent
**Important Notes**
- The marketplace interface differs between local hosting and cloud-hosted versions
- Ensure you're following the local hosting specific steps
- The agent will be available in your Builder after successful import
Platform Edit Agent (`docs/platform/edit-agent.md`)
**How to Edit an Agent in AutoGPT**
**Overview**
This guide explains how to access and edit existing agents in AutoGPT, whether they're your own creations or downloaded from the marketplace.
**Steps to Edit an Agent**
**1. Access the Monitor Tab**
- Navigate to the Monitor Tab in the AutoGPT builder
- You'll see a list of all your agents, including:
- Agents you've created
- Agents downloaded from the marketplace
**2. Select the Agent**
- Locate the agent you want to edit from the list
- Click on the agent name to select it
**3. Enter Edit Mode**
- Look for the pencil icon next to the selected agent
- Click the pencil icon to open the editor
**4. Modify the Agent**
- The agent will load in the editor with all its existing components
- Make your desired modifications to the agent's configuration
**5. Save Changes**
- After making your modifications, save the updated agent
**Note**
- All agents can be edited, regardless of whether they were created by you or downloaded from the marketplace
- Changes are saved to your local instance
Platform Gemini (`docs/platform/gemini.md`)
Using Google Gemini with AutoGPT
This guide covers integrating Google Gemini models with AutoGPT using OpenRouter.
Prerequisites
- Make sure you have completed the AutoGPT Setup Guide and have AutoGPT running locally at
http://localhost:3000. - You have an OpenRouter API key from OpenRouter.
Getting Your API Key
AutoGPT routes all Gemini models through OpenRouter. You need an OpenRouter API key:
- Visit OpenRouter Keys
- Sign in or create an account
- Click "Create Key"
- Copy the generated key for use in AutoGPT
Setup Steps
1. Start AutoGPT Locally
Follow the official guide:
AutoGPT Getting Started Guide
Ensure AutoGPT is running and accessible at:
http://localhost:3000
2. Open the Visual Builder
Open your browser and navigate to:
http://localhost:3000/build
Or click "Build" in the navigation bar.
3. Add an AI Text Generator Block
- Click the "Blocks" button on the left sidebar.
- In the search bar, type
AI Text Generator. - Drag the block into the canvas.
4. Select a Gemini Model
Click the AI Text Generator block to configure it.
In the LLM Model dropdown, select one of the available Gemini models:
| Model | Model ID |
|---|---|
| Gemini 3.1 Pro Preview | google/gemini-3.1-pro-preview |
| Gemini 3.1 Flash Lite Preview | google/gemini-3.1-flash-lite-preview |
| Gemini 3 Flash Preview | google/gemini-3-flash-preview |
| Gemini 2.5 Pro | google/gemini-2.5-pro |
| Gemini 2.5 Flash | google/gemini-2.5-flash |
| Gemini 2.5 Flash Lite | google/gemini-2.5-flash-lite |
| Gemini 2.0 Flash 001 | google/gemini-2.0-flash-001 |
| Gemini 2.0 Flash Lite 001 | google/gemini-2.0-flash-lite-001 |
Select the models prefixed with
google/in the dropdown.
5. Configure Your Credentials
Inside the AI Text Generator block:
- API Key: Enter your OpenRouter API key
- Prompt: Enter your desired prompt text
Get your API key from:
https://openrouter.ai/keys
Save your API key as a credential in AutoGPT for easy reuse across multiple blocks.
6. Save Your Agent
Click the "Save" button at the top-right of the builder interface:
- Give your agent a descriptive name (e.g.,
gemini_research_agent) - Click "Save Agent" to confirm
7. Run Your Agent
From the workspace:
- Click "Run" next to your saved agent
- The request will be sent to the selected Gemini model
8. View the Output
- Scroll to the AI Text Generator block
- Check the Output panel below it
- Copy, export, or pass the result to additional blocks
Gemini-Specific Features
Multimodal Capabilities
Gemini models support multiple input types:
- Text: Standard text prompts and completions
- Images: Upload and analyze images
- Code: Programming and technical reasoning
- Long Context: Large context windows for document analysis
Expand Your Agent
Enhance your workflow with additional blocks:
- Tools – Fetch URLs, call APIs, scrape data
- Memory – Retain context across interactions
- Document Processing – Analyze PDFs, text files
- Web Search – Combine with real-time information
- Chains – Create multi-step reasoning pipelines
Pricing
Gemini models are priced through OpenRouter. Check current rates at:
OpenRouter Google Models
Pricing varies by model tier and usage volume.
Troubleshooting
API Key Issues
- Ensure you're using an OpenRouter API key, not a Google AI Studio key
- Verify the key has sufficient credits
- Check that the key is entered correctly without extra spaces
Model Not Available
- Gemini models are accessed through OpenRouter
- Ensure you've selected a model with the
google/prefix in the dropdown
Rate Limiting
- Free tier has request limits per minute
- Upgrade to paid tier for production usage
- Consider using
google/gemini-2.5-flash-litefor lower-cost, high-volume tasks
Context Length Errors
- Each Gemini model has a maximum context window
- Split large tasks across multiple blocks for very long documents
Additional Resources
- Google AI Studio Documentation
- Gemini API Quickstart
- Model Capabilities
- OpenRouter Documentation
- AutoGPT Platform Docs
You are now set up to use Google Gemini models in AutoGPT.
Platform Getting Started Cloud (`docs/platform/getting-started-cloud.md`)
Getting Started with AutoGPT (Cloud)
Introduction
This guide will get you up and running on the hosted AutoGPT Platform at platform.agpt.co. No local installation or Docker setup is required — just sign up and start building.
{% hint style="info" %}
Looking to self-host instead? See the Self-Hosting Guide.
{% endhint %}
Creating Your Account
- Navigate to platform.agpt.co
- Click Sign Up and create your account
- Once signed in, you'll land on the AutoPilot home screen
That's it — you're ready to go. Available credits and managed AI providers depend on your plan and account configuration. If a block requires credentials that are not included, the platform will prompt you to connect your own. See the pricing page for current plan details.
Platform Navigation
The AutoGPT Platform has four main areas, accessible from the main navigation:
| Nav Button | URL | Description |
|---|---|---|
| Home | platform.agpt.co | AutoPilot — your AI assistant for the platform |
| Build | platform.agpt.co/build | Agent Builder — visual editor for creating agents |
| Agents | platform.agpt.co/library | Your Agent Library — all your saved agents |
| Marketplace | platform.agpt.co/marketplace | Community marketplace for discovering agents |
Use the automation-credits wallet or Billing settings to review your balance. Account settings and integrations are available from Settings.
What to Do First
Here are three great ways to get started:
Option 1: Chat with AutoPilot
Click Home in the nav bar to open AutoPilot. You can ask it to perform supported actions such as browsing the marketplace, running agents, building agents, generating images, and conducting research. It's the fastest way to experience the platform.
Option 2: Add an Agent from the Marketplace
- Click Marketplace in the nav bar
- Browse or search for an agent that interests you
- Click on an agent to view its details
- Click Add to Library
- Navigate to Agents to find it in your library
- Click on the agent and press New agent task to run it
Option 3: Build Your Own Agent
- Click Build in the nav bar to open the Agent Builder
- Open the Blocks menu on the left-hand side
- Add an Input Block, an AI Text Generator Block, and an Output Block
- Connect them together by dragging between their pins
- Press Ctrl+S (Windows/Linux) or Cmd+S (macOS), or click the save button to save your agent
- Navigate to Agents to find and run it
For a detailed walkthrough, see Creating a Basic Agent.
Key Concepts
Before diving deeper, here are the core concepts you'll encounter:
- Agent: An automated workflow you design to perform specific tasks. Agents are made up of connected blocks.
- Block: A single action within an agent — such as generating text with AI, sending an email, or looking up data. The available block catalog depends on the platform deployment.
- Task: A single execution of an agent. When you run an agent with a set of inputs, that creates a task. You can view the task's inputs, outputs, and reported cost.
- AutoPilot: Your AI assistant that can perform supported platform actions through natural conversation.
- Marketplace: A public library of community-built agents you can add to your own library.
- Credits: The currency used for metered block execution. Pricing varies by block and usage; review your balance from the automation-credits wallet or Billing settings.
Next Steps
| Guide | Description |
|---|---|
| AutoPilot | Learn what AutoPilot can do for you |
| Agent Builder Guide | Master the visual agent builder |
| Agent Library | Manage your agents, tasks, and schedules |
| Marketplace | Discover and share agents |
| Credits & Billing | Understand the credit system |
Platform Installer (`docs/platform/installer.md`)
AutoGPT Platform Release Installer
The release installer runs the published AutoGPT Platform appliance as one
Docker container. It does not clone the repository, build AutoGPT, install
Docker, elevate privileges, or accept Docker Desktop license terms.
The appliance exposes one loopback-only port. Application state and generated
secrets live in a named Docker volume; installer identity and runtime
configuration live in a dedicated private state directory.
Prerequisites
Before running the installer:
- Install and start Docker from the
official Docker Engine or
Docker Desktop documentation. - Select a local
unix://Docker endpoint on Linux or macOS. Remote and
non-Unix contexts are rejected. - Configure the daemon for Linux containers on
amd64orarm64. - Allow about 25 GB of free disk; at least 8 GB RAM is recommended.
Docker Desktop is a separate product with its own license terms. AutoGPT does
not install it or grant a Docker license.
Install (After the Release Gates Pass)
[!WARNING]
Do not use the hosted command yet.setup.agpt.co/install.shstill serves
the legacy Compose installer, and the appliance image tags are not public.
Maintainers must complete every
release gate before exposing these commands in
the README or getting-started guide.
This first appliance-installer release supports Linux and macOS. Windows is
intentionally withheld until its standard-user filesystem checks and native
Docker argument handling are validated; use the manual setup guide there.
The command below uses a unique temporary file, executes it only after the HTTPS
download succeeds, and removes it afterward. Do not stream a network response
directly into a shell.
Linux and macOS
(
installer="$(mktemp)" &&
trap 'rm -f "$installer"' EXIT &&
curl --proto '=https' --proto-redir '=https' --tlsv1.2 -fsSL \
-o "$installer" https://setup.agpt.co/install.sh &&
bash "$installer"
)Options
| Goal | Linux/macOS |
|---|---|
| Current published appliance | (no flag) |
| Specific published version | --release=vX.Y.Z |
| Appliance release-tag form | --release=autogpt-platform-beta-vX.Y.Z |
| Hardware and Docker checks only | --preflight-only |
| Print image selection only | --resolve-only |
| Skip RAM/disk checks | --skip-preflight |
| Custom private state directory | --dir=PATH |
--skip-preflight does not skip the local-endpoint, Linux-container, or
architecture checks.
The default state directory is $XDG_CONFIG_HOME/autogpt, or$HOME/.config/autogpt when XDG_CONFIG_HOME is unset. A custom directory
must be user-owned and private; the installer rejects symlink paths and shared
or unsafe locations.
Artifact and Runtime Contract
With no release flag, the installer pullssignificantgravitas/autogpt:latest. An explicit vX.Y.Z selects the matching
version tag. After pulling, the installer:
- Requires the expected OCI title and source repository plus a well-formed
40-hex source revision label. - Requires a Linux image matching the local daemon's
amd64orarm64
architecture. - Resolves exactly one native
RepoDigestand runs that immutable digest,
never the mutable tag. - Creates an installer-owned, labelled
autogpt-platform-datavolume. - Starts
autogptwith127.0.0.1:3000, the private environment file,
restart and stop policies, log rotation, shared memory, and file-descriptor
limits.
The bootstrap authenticates transport with HTTPS, and the installer pins the
pulled image by digest after checking its appliance metadata. It does not
independently verify a publisher signature or protect against compromise of
the image-publishing credentials. Signed release artifacts are a follow-up,
not a claim made by this installer.
The installer refuses to adopt an unlabelled container or volume. A rerun is a
no-op, or starts a stopped container, only when the installer identity,
environment hash, immutable image digest, process, port, mount, privilege,
restart, logging, memory, and limit settings all match. Drift fails closed.
If established installer lifecycle state exists but its named volume is
missing, the installer refuses to create an empty replacement. An interrupted
first pull has no established marker and can be retried safely. Restore a
previously established volume or choose a new state directory.
Runtime Configuration
The private autogpt.env initially contains:
AUTOGPT_PUBLIC_URL=http://localhost:3000Add provider keys and other appliance variables there. The installer binds the
environment file's SHA-256 hash to the container label. To apply a deliberate
configuration change, stop and remove only the container, then rerun the same
installer release:
docker stop --time 360 autogpt
docker rm autogptDo not remove autogpt-platform-data; it contains accounts, agents, generated
secrets, and application state.
First Run
After the installer reports a healthy container, open
http://localhost:3000. Registration starts open so
you can create the intended account. Promote it:
docker exec autogpt autogpt-admin promote [email protected]Then set this in autogpt.env, recreate the container as described above, and
verify registration is closed:
AUTH_ALLOW_NEW_ACCOUNTS=falseKeep the default loopback binding. To serve other machines, place AutoGPT
behind a TLS reverse proxy and set AUTOGPT_PUBLIC_URL to the exact public URL.
Upgrades
Before an upgrade, back up autogpt-platform-data. Stop and remove only theautogpt container, then rerun the installer with the intended release. The
installer will reuse the named volume only when its ownership labels and the
private installer identity still match.
Maintainer Release Gates
Repository CI cannot prove the public bootstrap handoff. Do not publish or
announce the installation commands until every external gate below passes:
Deploy the new repository
install.shthrough the Caddy or object-storage
configuration behindsetup.agpt.co. The endpoint must no longer serve the
legacy clone/Compose installer.Publish
significantgravitas/autogpt:vX.Y.Zand:latestonly after the
multi-architecture workflow smoke-tests and scans both runnable images.Ensure the appliance release's tag commit contains the full installer,
publication workflow, and helper, then verify the release-triggered run.
Manual development dispatches publish SHA artifacts, not the release channel
used by this installer.Verify both public tags expose Linux
amd64andarm64manifests and the
expected OCI identity:bashdocker buildx imagetools inspect significantgravitas/autogpt:vX.Y.Z docker buildx imagetools inspect significantgravitas/autogpt:latestFetch the hosted installer into a new temporary file from an external
machine and compare it with the released repository file. On each supported
operating system, perform a clean install against the public image, wait for
health, verify the loopback endpoint, rerun to prove exact-contract
idempotency, and confirm no mutable tag was used to create the container.
Installing from source (Compose)
The scripts below are the other supported path: they clone the repository and
start every service with Docker Compose. Use them on Windows, for development,
or for a fully offline install with a local LLM. The appliance described above
is the image documented in
Run AutoGPT in one Docker container; these scripts do
not use it.
Manual Installation
If you prefer, you can manually download and run the installer scripts:
- Linux/macOS:
setup-autogpt.sh - Windows:
setup-autogpt.bat
These scripts are located in the autogpt_platform/installer/ directory.
Both create the three .env files if they are missing and generate the
secrets .env.default leaves blank (ENCRYPTION_KEY,UNSUBSCRIBE_SECRET_KEY, BETTER_AUTH_SECRET), the same as make init-env.
Running a script again never changes a value that is already set. If you are
upgrading an install that ran without its own ENCRYPTION_KEY, read
Upgrading: secrets are generated per install
first so your connected integrations move to the new key.
Running fully offline with a local LLM (Ollama)
Both installer scripts accept an opt-in flag that installs
Ollama, pulls a default chat model, and wiresbackend/.env so AutoPilot runs without any cloud API keys. This
is useful for air-gapped or privacy-sensitive deployments — see
Running AutoPilot on a self-hosted LLM for the
full reference.
Linux / macOS
cd autogpt_platform/installer
./setup-autogpt.sh --with-ollama
# Optional overrides:
# --ollama-model=qwen3:14b-instruct-q4_K_M
# --ollama-host=http://gpu-rig.lab:11434 # use an existing OllamaWindows
cd autogpt_platform\installer
setup-autogpt.bat /with-ollama
REM Optional overrides:
REM /ollama-model=qwen3:14b-instruct-q4_K_M
REM /ollama-host=http://gpu-rig.lab:11434The installer:
- Installs Ollama (skipped if already present, or if
--ollama-hostpoints at an existing one). - Configures
OLLAMA_HOST=0.0.0.0:11434+OLLAMA_CONTEXT_LENGTH=32768so containers can reach it and so AutoPilot's ~8 k system prompt isn't truncated by Ollama's 4 k default. - Pulls the chat model (default
hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M). - Appends a marker-bounded block to
autogpt_platform/backend/.envwithCHAT_USE_LOCAL=trueplus theCHAT_BASE_URL/CHAT_API_KEY/CHAT_*_MODELoverrides.
Re-running with --with-ollama is idempotent — the wiring block is rewritten in place.
Troubleshooting
- Confirm
docker infosucceeds against a local Linux-container daemon. - If an image pull fails just after release, wait for public manifest
publication and retry; the installer never falls back to a source build. - If the installer reports state or runtime drift, preserve the named volume,
inspect the existing container, and follow the explicit upgrade procedure. - Check
docker logs --tail 100 autogptwhen health checks fail.
Platform Integrations And Credentials (`docs/platform/integrations-and-credentials.md`)
Integrations & Credentials
Overview
Many blocks on the AutoGPT Platform integrate with external services like Google, GitHub, Linear, Twitter, and more. These integrations require credentials — such as OAuth connections, API keys, or username/password pairs — to access your accounts on those services.
This guide explains how credentials work on the platform, how to add them, and how to manage them.
How Credentials Work
Platform-Provided Credentials
On the cloud-hosted platform at platform.agpt.co, some plans and account configurations include managed AI providers. If a block requires credentials that are not included, the platform will prompt you to connect your own. See the pricing page for current plan details.
User-Provided Credentials
For services tied to your personal accounts, connect your own credentials. Credentials can be reused across agents, but you choose which compatible credential an agent or task uses. Managed credentials may be selected automatically when available.
Adding Credentials
You can connect credentials centrally from Settings → Integrations → Connect Service, or in context when a builder block or task asks for one.
When Building an Agent
If a block needs credentials, select an existing compatible credential or connect a new one from the block.
When Running an Agent
Credential fields show the credentials that task will use. Select an existing compatible credential or connect a new one.
Credential Types
Depending on the service, you'll be prompted to authenticate in one of three ways:
| Type | Description | Example Services |
|---|---|---|
| OAuth | Click to authorise via the service's login page | Google, GitHub, Twitter |
| API Key | Paste your API key from the service's dashboard | Linear, OpenAI |
| Username & Password | Enter your account credentials | Varies by service |
{% hint style="info" %}
Credentials are reusable, but each block or task retains its selected credential reference.
{% endhint %}
Connecting MCP Services
Open Settings → Integrations, find a service with the MCP badge, and follow its setup instructions. The same services appear in Connect Service.
- Choose the offered sign-in or token option for account access, or No sign-in → Check connection for public tools.
- If prompted, select your account's region or enter the official server URL for your tenant or deployment.
- Review the requested permissions. Allow changes, when offered, adds the service's optional actions.
For catalog updates, see Maintaining MCP Integrations.
Managing Credentials
- Open Settings.
- Select Integrations.
- Use Connect Service to add a credential, or manage/remove credentials already listed.
URL: platform.agpt.co/settings/integrations
{% hint style="warning" %}
Removing a credential can break agents, workflows, or active webhooks that reference it. Review the dependency warning before confirming removal.
{% endhint %}
Self-Hosted Credentials
When self-hosting, configure deployment-level provider keys and OAuth application credentials in autogpt_platform/backend/.env, then connect end-user accounts from Settings → Integrations as needed. See the Self-Hosting Guide for details.
Platform Marketplace (`docs/platform/marketplace.md`)
Marketplace
Overview
The AutoGPT Marketplace is a public library of agents created by the community and by the AutoGPT team. You can discover agents for a wide range of use cases, add them to your library with one click, and even publish your own agents for others to use.
URL: platform.agpt.co/marketplace
Access: Click Marketplace in the navigation bar.
Browsing the Marketplace
The marketplace allows you to:
- Search for agents by name or keyword
- Browse by category to explore different use cases
- View agent details by clicking on any agent
Agent Detail Page
Each agent in the marketplace has a dedicated page that includes:
- Title and description of what the agent does
- Creator information — who built the agent and a link to their other agents
- Video (when available) — a demonstration of the agent in action
- Output showcase (when available) — examples of the agent's results
- Instructions — guidance on how to run the agent and what to expect
Adding an Agent to Your Library
When you find an agent you want to use:
- Click on the agent to open its marketplace page
- Click the Add to Library button
The agent appears in your Agent Library, where you can run or schedule it. Marketplace agents open read-only; duplicate one before editing.
{% hint style="info" %}
If you already have the agent in your library, the button changes to See Runs, which takes you directly to that agent in your library.
{% endhint %}
Download for Self-Hosting
Marketplace detail pages also provide Download here under “Want to use this agent locally?” Download the agent JSON and import it into your local instance. See Download & Import an Agent for details.
Publishing an Agent
You can publish your own agents to the marketplace for the community to discover and use.
How to Publish
- Open the agent in the Builder and select Publish to Marketplace, or open Settings → Creator Dashboard and select Publish Agent
- Choose the agent and version you want to publish if prompted
- Fill out the publishing form:
| Field | Description |
|---|---|
| Title | The name of your agent as it will appear in the marketplace |
| Subheader | A short tagline for your agent |
| Slug | A URL-friendly identifier (e.g., resume-rewriter) |
| Thumbnail Images | Upload images — the first image becomes the marketplace thumbnail |
| YouTube Video Link | Optional — a video demonstrating your agent |
| Output demo | Optional — a URL showing example output the agent produces |
| Category | Select the most relevant category for your agent |
| Description | A detailed explanation of what your agent does |
| Instructions | Explain to users how to run this agent and what to expect |
| Recommended Schedule | Suggest when users should run this agent for best results |
- Click Submit for review
Review Process
After submission, a member of the AutoGPT team will review your agent against curation standards. If approved, your agent will be published to the marketplace and visible to all users.
Editing Marketplace Agents
An agent added from the marketplace is read-only. Open it in the builder and click Duplicate to create an editable fork in your library. Changes to that fork do not modify the original marketplace listing.
Platform New Blocks (`docs/platform/new_blocks.md`)
Contributing to AutoGPT Agent Server: Creating and Testing Blocks
This guide will walk you through the process of creating and testing a new block for the AutoGPT Agent Server, using the WikipediaSummaryBlock as an example.
!!! tip "New SDK-Based Approach"
For a more comprehensive guide using the new SDK pattern with ProviderBuilder and advanced features like OAuth and webhooks, see the Block SDK Guide.
Understanding Blocks and Testing
Blocks are reusable components that can be connected to form a graph representing an agent's behavior. Each block has inputs, outputs, and a specific function. Proper testing is crucial to ensure blocks work correctly and consistently.
Block Auto-Discovery
All block modules are automatically discovered and imported at startup. The block loader in backend/blocks/__init__.py uses Path(__file__).parent.rglob("*.py") to find every .py file in the blocks/ directory tree. Files named __init__.py and those starting with test_ are excluded.
This means:
- You do not need to manually register your block anywhere — just create the file and your
Blocksubclass will be picked up automatically. - Any module-level side effects (e.g., global registrations, print statements) in your block files will execute on import. Keep block packages clean and avoid side-effect-heavy code at module scope.
- Block auto-discovery does not replace provider setup. For a new API-key integration, follow the provider auth setup below: add the provider name, credentials, display metadata, and integration icon. Frontend provider data is loaded dynamically; no manual frontend provider registration is needed.
Creating and Testing a New Block
Follow these steps to create and test a new block:
Create a new Python file for your block in the
autogpt_platform/backend/backend/blocksdirectory. Name it descriptively and use snake_case. For example:get_wikipedia_summary.py.Import necessary modules and create a class that inherits from
Block. Make sure to include all necessary imports for your block.Every block should contain the following:
pythonfrom backend.blocks._base import Block, BlockSchemaInput, BlockSchemaOutput, BlockOutputExample for the Wikipedia summary block:
pythonfrom backend.blocks._base import Block, BlockSchemaInput, BlockSchemaOutput, BlockOutput from backend.utils.get_request import GetRequest import requests class WikipediaSummaryBlock(Block, GetRequest): # Block implementation will go hereDefine the input and output schemas using
BlockSchema. These schemas specify the data structure that the block expects to receive (input) and produce (output).- The input schema defines the structure of the data the block will process. Each field in the schema represents a required piece of input data.
- The output schema defines the structure of the data the block will return after processing. Each field in the schema represents a piece of output data.
Example:
pythonclass Input(BlockSchemaInput): topic: str # The topic to get the Wikipedia summary for class Output(BlockSchemaOutput): summary: str # The summary of the topic from WikipediaImplement the
__init__method, including test data and mocks:!!! important
Use UUID generator (e.g. https://www.uuidgenerator.net/) for every new blockidand do not make up your own. Alternatively, you can run this python code to generate an uuid:print(__import__('uuid').uuid4())pythondef __init__(self): super().__init__( # Unique ID for the block, used across users for templates # If you are an AI leave it as is or change to "generate-proper-uuid" id="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", input_schema=WikipediaSummaryBlock.Input, # Assign input schema output_schema=WikipediaSummaryBlock.Output, # Assign output schema # Provide sample input, output and test mock for testing the block test_input={"topic": "Artificial Intelligence"}, test_output=("summary", "summary content"), test_mock={"get_request": lambda url, json: {"extract": "summary content"}}, )id: A unique identifier for the block.input_schemaandoutput_schema: Define the structure of the input and output data.
Let's break down the testing components:
test_input: This is a sample input that will be used to test the block. It should be a valid input according to your Input schema.test_output: This is the expected output when running the block with thetest_input. It should match your Output schema. For non-deterministic outputs or when you only want to assert the type, you can use Python types instead of specific values. In this example,("summary", str)asserts that the output key is "summary" and its value is a string.test_mock: This is crucial for blocks that make network calls. It provides a mock function that replaces the actual network call during testing.
In this case, we're mocking the
get_requestmethod to always return a dictionary with an 'extract' key, simulating a successful API response. This allows us to test the block's logic without making actual network requests, which could be slow, unreliable, or rate-limited.Implement the
runmethod with error handling. This should contain the main logic of the block:pythondef run(self, input_data: Input, **kwargs) -> BlockOutput: try: topic = input_data.topic url = f"https://en.wikipedia.org/api/rest_v1/page/summary/{topic}" response = self.get_request(url, json=True) yield "summary", response['extract'] except requests.exceptions.HTTPError as http_err: raise RuntimeError(f"HTTP error occurred: {http_err}")- Try block: Contains the main logic to fetch and process the Wikipedia summary.
- API request: Send a GET request to the Wikipedia API.
- Error handling: Handle various exceptions that might occur during the API request and data processing. We don't need to catch all exceptions, only the ones we expect and can handle. The uncaught exceptions will be automatically yielded as
errorin the output. Any block that raises an exception (or yields anerroroutput) will be marked as failed. Prefer raising exceptions over yieldingerror, as it will stop the execution immediately. - Yield: Use
yieldto output the results. Prefer to output one result object at a time. If you are calling a function that returns a list, you can yield each item in the list separately. You can also yield the whole list as well, but do both rather than yielding the list. For example: If you were writing a block that outputs emails, you'd yield each email as a separate result object, but you could also yield the whole list as an additional single result object. Yielding output namederrorwill break the execution right away and mark the block execution as failed. - kwargs: The
kwargsparameter is used to pass additional arguments to the block. It is not used in the example above, but it is available to the block. You can also have args as inline signatures in the run method aladef run(self, input_data: Input, *, user_id: str, **kwargs) -> BlockOutput:.
Available kwargs are:user_id: The ID of the user running the block.graph_id: The ID of the agent that is executing the block. This is the same for every version of the agentgraph_exec_id: The ID of the execution of the agent. This changes every time the agent has a new "run"node_exec_id: The ID of the execution of the node. This changes every time the node is executednode_id: The ID of the node that is being executed. It changes every version of the graph, but not every time the node is executed.execution_context: AnExecutionContextobject containing user_id, graph_exec_id, workspace_id, and session_id. Required for file handling.
Handling Files in Blocks
When your block needs to work with files (images, videos, documents), use store_media_file() from backend.util.file. This function handles downloading, validation, virus scanning, and storage.
Import:
from backend.data.execution import ExecutionContext
from backend.util.file import store_media_file
from backend.util.type import MediaFileTypeThe return_format parameter determines what you get back:
| Format | Use When | Returns |
|---|---|---|
"for_local_processing" |
Processing with local tools (ffmpeg, MoviePy, PIL) | Local file path (e.g., "image.png") |
"for_external_api" |
Sending content to external APIs (Replicate, OpenAI) | Data URI (e.g., "data:image/png;base64,...") |
"for_block_output" |
Returning output from your block | Smart: workspace:// in CoPilot, data URI in graphs |
Examples:
async def run(
self,
input_data: Input,
*,
execution_context: ExecutionContext,
**kwargs,
) -> BlockOutput:
# PROCESSING: Need to work with file locally (ffmpeg, MoviePy, PIL)
local_path = await store_media_file(
file=input_data.video,
execution_context=execution_context,
return_format="for_local_processing",
)
# local_path = "video.mp4" - use with Path, ffmpeg, subprocess, etc.
full_path = get_exec_file_path(execution_context.graph_exec_id, local_path)
# EXTERNAL API: Need to send content to an API like Replicate
image_b64 = await store_media_file(
file=input_data.image,
execution_context=execution_context,
return_format="for_external_api",
)
# image_b64 = "data:image/png;base64,iVBORw0..." - send to external API
# OUTPUT: Returning result from block to user/next block
result_url = await store_media_file(
file=generated_image_url,
execution_context=execution_context,
return_format="for_block_output",
)
yield "image_url", result_url
# In CoPilot: result_url = "workspace://abc123" (persistent, context-efficient)
# In graphs: result_url = "data:image/png;base64,..." (for next block/display)Key points:
for_block_outputis the only format that auto-adapts to execution context- Always use
for_block_outputfor block outputs unless you have a specific reason not to - Never manually check for
workspace_id- letfor_block_outputhandle the logic - The function handles URLs, data URIs,
workspace://references, and local paths as input
Field Types
oneOf fields
oneOf allows you to specify that a field must be exactly one of several possible options. This is useful when you want your block to accept different types of inputs that are mutually exclusive.
Example:
attachment: Union[Media, DeepLink, Poll, Place, Quote] = SchemaField(
discriminator='discriminator',
description="Attach either media, deep link, poll, place or quote - only one can be used"
)The discriminator parameter tells AutoGPT which field to look at in the input to determine which type it is.
In each model, you need to define the discriminator value:
class Media(BaseModel):
discriminator: Literal['media']
media_ids: List[str]
class DeepLink(BaseModel):
discriminator: Literal['deep_link']
direct_message_deep_link: strOptionalOneOf fields
OptionalOneOf is similar to oneOf but allows the field to be optional (None). This means the field can be either one of the specified types or None.
Example:
attachment: Union[Media, DeepLink, Poll, Place, Quote] | None = SchemaField(
discriminator='discriminator',
description="Optional attachment - can be media, deep link, poll, place, quote or None"
)The key difference is the | None which makes the entire field optional.
Blocks with authentication
Our system supports auth offloading for API keys and OAuth2 authorization flows.
Adding a block with API key authentication is straight-forward, as is adding a block
for a service that we already have OAuth2 support for.
Implementing the block itself is relatively simple. On top of the instructions above,
you're going to add a credentials parameter to the Input model and the run method:
/* Detailed source-code truncated for AI context efficiency. */The credentials will be automagically injected by the executor in the back end.
The APIKeyCredentials and OAuth2Credentials models are defined here.
To use them in e.g. an API request, you can either access the token directly:
# credentials: APIKeyCredentials
response = requests.post(
url,
headers={
"Authorization": f"Bearer {credentials.api_key.get_secret_value()})",
},
)
# credentials: OAuth2Credentials
response = requests.post(
url,
headers={
"Authorization": f"Bearer {credentials.access_token.get_secret_value()})",
},
)or use the shortcut credentials.auth_header():
# credentials: APIKeyCredentials | OAuth2Credentials
response = requests.post(
url,
headers={"Authorization": credentials.auth_header()},
)The backend discovers provider names from the ProviderName enum and the SDK's AutoRegistry.
The _auth.py pattern below uses a ProviderName member, so add the new provider to that enum. SDK integrations can instead register provider names through ProviderBuilder; see the Block SDK Guide.
ProviderName definition
--8<-- "autogpt_platform/backend/backend/integrations/providers.py:ProviderName"API-Key-Only Provider Auth (Recommended Pattern)
For integrations that only use API key authentication (no OAuth), keep credential definitions in _auth.py and register display metadata separately. The setup is:
- Add to
ProviderNameenum inbackend/integrations/providers.py - Create
_auth.pyin your block package with credentials type, field factory, and test credentials - Create the block using the credentials field from
_auth.py - Create
_config.pywith the provider description and supported auth types for the settings UI - Add the integration icon as described below
Here's the complete _auth.py pattern (using ZeroBounce as a reference):
from typing import Literal
from pydantic import SecretStr
from backend.data.model import APIKeyCredentials, CredentialsField, CredentialsMetaInput
from backend.integrations.providers import ProviderName
# Type aliases for your provider
MyProviderCredentials = APIKeyCredentials
MyProviderCredentialsInput = CredentialsMetaInput[
Literal[ProviderName.MY_PROVIDER],
Literal["api_key"],
]
# Field factory for use in block Input schemas
def MyProviderCredentialsField() -> MyProviderCredentialsInput:
return CredentialsField(
description="The My Provider integration requires an API key.",
)
# Test credentials for block tests
TEST_CREDENTIALS = APIKeyCredentials(
id="01234567-89ab-cdef-0123-456789abcdef",
provider="my_provider",
api_key=SecretStr("mock-my-provider-api-key"),
title="Mock My Provider API key",
expires_at=None,
)
TEST_CREDENTIALS_INPUT = {
"provider": TEST_CREDENTIALS.provider,
"id": TEST_CREDENTIALS.id,
"type": TEST_CREDENTIALS.type,
"title": TEST_CREDENTIALS.title,
}Then use it in your block:
from ._auth import MyProviderCredentials, MyProviderCredentialsField, MyProviderCredentialsInput
class MyBlock(Block):
class Input(BlockSchemaInput):
credentials: MyProviderCredentialsInput = MyProviderCredentialsField()
# ... other inputs
async def run(
self,
input_data: Input,
*,
credentials: MyProviderCredentials,
**kwargs,
) -> BlockOutput:
api_key = credentials.api_key.get_secret_value()
# ... use the API key
yield "result", ...Register the provider metadata in _config.py, following backend/blocks/zerobounce/_config.py:
from backend.integrations.providers import ProviderName
from backend.sdk import ProviderBuilder
my_provider = (
ProviderBuilder(ProviderName.MY_PROVIDER.value)
.with_description("A short description of the integration")
.with_supported_auth_types("api_key")
.build()
)This metadata tells the settings UI which connection methods to offer. It does not replace the credentials declared in _auth.py. The block loader imports _config.py automatically, and .build() registers the provider. OAuth, webhooks, and cost tracking use additional ProviderBuilder methods described in the Block SDK Guide.
Multiple credentials inputs
Multiple credentials inputs are supported, under the following conditions:
- The name of each of the credentials input fields must end with
_credentials. - The names of the credentials input fields must match the names of the corresponding
parameters on therun(..)method of the block. - If more than one of the credentials parameters are required,
test_credentials
is adict[str, Credentials], with for each required credentials input the
parameter name as the key and suitable test credentials as the value.
Adding an OAuth2 service integration
To add support for a new OAuth2-authenticated service, you'll need to add an OAuthHandler.
All our existing handlers and the base class can be found here.
Every handler must implement the following parts of the BaseOAuthHandler interface:
--8<-- "autogpt_platform/backend/backend/integrations/oauth/base.py:BaseOAuthHandler1"
--8<-- "autogpt_platform/backend/backend/integrations/oauth/base.py:BaseOAuthHandler2"
--8<-- "autogpt_platform/backend/backend/integrations/oauth/base.py:BaseOAuthHandler3"
--8<-- "autogpt_platform/backend/backend/integrations/oauth/base.py:BaseOAuthHandler4"
--8<-- "autogpt_platform/backend/backend/integrations/oauth/base.py:BaseOAuthHandler5"
--8<-- "autogpt_platform/backend/backend/integrations/oauth/base.py:BaseOAuthHandler6"As you can see, this is modeled after the standard OAuth2 flow.
Aside from implementing the OAuthHandler itself, adding a handler into the system requires two more things:
- Adding the handler class to
HANDLERS_BY_NAMEunderintegrations/oauth/__init__.py
--8<-- "autogpt_platform/backend/backend/integrations/oauth/__init__.py:HANDLERS_BY_NAMEExample"- Adding
{provider}_client_idand{provider}_client_secretto the application'sSecretsunderutil/settings.py
--8<-- "autogpt_platform/backend/backend/util/settings.py:OAuthServerCredentialsExample"Adding to the frontend
Integration Icon (Required)
When adding a new integration provider, you must add a PNG icon at:
autogpt_platform/frontend/public/integrations/{provider_name}.pngThe frontend block menu dynamically loads icons using the URL /integrations/{integration.name}.png (see PaginatedIntegrationList.tsx). If the icon file is missing, the block will show a broken image in the builder UI.
Use the same provider_name that matches your ProviderName enum value (lowercase). For example, if your provider is ProviderName.SLACK, the icon should be at frontend/public/integrations/slack.png.
Frontend Provider Registration
No manual frontend provider registration is required. The credentials provider context fetches provider names from the backend, and the integrations UI uses the description and supported_auth_types returned by GET /integrations/providers. Set those fields in the backend provider configuration as shown above.
Block credential inputs derive their accepted credential types from the block's input schema. CredentialsProviderName is a string type; CredentialsType describes authentication methods, not provider names, so neither needs a new frontend enum entry for a provider.
The credentials list has an optional providerIcons map in frontend/src/components/contextual/CredentialsInput/helpers.ts; unlisted providers use a fallback icon. This is separate from the PNG asset required by the block menu.
Example: GitHub integration
- GitHub blocks with API key + OAuth2 support:
blocks/github
--8<-- "autogpt_platform/backend/backend/blocks/github/issues.py:GithubCommentBlockExample"- GitHub OAuth2 handler:
integrations/oauth/github.py
--8<-- "autogpt_platform/backend/backend/integrations/oauth/github.py:GithubOAuthHandlerExample"Example: Google integration
- Google OAuth2 handler:
integrations/oauth/google.py
--8<-- "autogpt_platform/backend/backend/integrations/oauth/google.py:GoogleOAuthHandlerExample"You can see that google has defined a DEFAULT_SCOPES variable, this is used to set the scopes that are requested no matter what the user asks for.
--8<-- "autogpt_platform/backend/backend/blocks/google/_auth.py:GoogleOAuthIsConfigured"You can also see that GOOGLE_OAUTH_IS_CONFIGURED is used to disable the blocks that require OAuth if the oauth is not configured. This is in the __init__ method of each block. This is because there is no api key fallback for google blocks so we need to make sure that the oauth is configured before we allow the user to use the blocks.
Webhook-triggered Blocks
Webhook-triggered blocks allow your agent to respond to external events in real-time.
These blocks are triggered by incoming webhooks from third-party services
rather than being executed manually.
Creating and running a webhook-triggered block involves three main components:
- The block itself, which specifies:
- Inputs for the user to select a resource and events to subscribe to
- A
credentialsinput with the scopes needed to manage webhooks - Logic to turn the webhook payload into outputs for the webhook block
- The
WebhooksManagerfor the corresponding webhook service provider, which handles:- (De)registering webhooks with the provider
- Parsing and validating incoming webhook payloads
- The credentials system for the corresponding service provider, which may include an
OAuthHandler
There is more going on under the hood, e.g. to store and retrieve webhooks and their
links to nodes, but to add a webhook-triggered block you shouldn't need to make changes
to those parts of the system.
Creating a Webhook-triggered Block
To create a webhook-triggered block, follow these additional steps on top of the basic block creation process:
Define
webhook_configin your block's__init__method.Example:
GitHubPullRequestTriggerBlockpython title="backend/blocks/github/triggers.py"--8<-- "autogpt_platform/backend/backend/blocks/github/triggers.py:example-webhook_config"BlockWebhookConfigdefinitionpython title="backend/blocks/_base.py"--8<-- "autogpt_platform/backend/backend/blocks/_base.py:BlockWebhookConfig"Define event filter input in your block's Input schema.
This allows the user to select which specific types of events will trigger the block in their agent.Example:
GitHubPullRequestTriggerBlockpython title="backend/blocks/github/triggers.py"--8<-- "autogpt_platform/backend/backend/blocks/github/triggers.py:example-event-filter"- The name of the input field (
eventsin this case) must matchwebhook_config.event_filter_input. - The event filter itself must be a Pydantic model with only boolean fields.
- The name of the input field (
Include payload field in your block's Input schema.
Example:
GitHubTriggerBasepython title="backend/blocks/github/triggers.py"--8<-- "autogpt_platform/backend/backend/blocks/github/triggers.py:example-payload-field"Define
credentialsinput in your block's Input schema.- Its scopes must be sufficient to manage a user's webhooks through the provider's API
- See Blocks with authentication for further details
Process webhook payload and output relevant parts of it in your block's
runmethod.Example:
GitHubPullRequestTriggerBlockpythondef run(self, input_data: Input, **kwargs) -> BlockOutput: yield "payload", input_data.payload yield "sender", input_data.payload["sender"] yield "event", input_data.payload["action"] yield "number", input_data.payload["number"] yield "pull_request", input_data.payload["pull_request"]Note that the
credentialsparameter can be omitted if the credentials
aren't used at block runtime, like in the example.
Adding a Webhooks Manager
To add support for a new webhook provider, you'll need to create a WebhooksManager that implements the BaseWebhooksManager interface:
--8<-- "autogpt_platform/backend/backend/integrations/webhooks/_base.py:BaseWebhooksManager1"
--8<-- "autogpt_platform/backend/backend/integrations/webhooks/_base.py:BaseWebhooksManager2"
--8<-- "autogpt_platform/backend/backend/integrations/webhooks/_base.py:BaseWebhooksManager3"
--8<-- "autogpt_platform/backend/backend/integrations/webhooks/_base.py:BaseWebhooksManager4"
--8<-- "autogpt_platform/backend/backend/integrations/webhooks/_base.py:BaseWebhooksManager5"
--8<-- "autogpt_platform/backend/backend/integrations/webhooks/_base.py:BaseWebhooksManager6"!!! info "Signature verification (verify_signature)"
If the upstream provider signs its deliveries (e.g. GitHub's
X-Hub-Signature-256, Airtable's X-Airtable-Content-MAC), override
verify_signature and use hmac.compare_digest for the comparison.
Raise fastapi.HTTPException(403) on missing/invalid signatures.
If the provider has no signing scheme (consumer wearables, simple
"POST to this URL" tools), leave the default in place. The webhook
URL's UUID is then the bearer secret — document that for users.And add a reference to your WebhooksManager class in load_webhook_managers:
--8<-- "autogpt_platform/backend/backend/integrations/webhooks/__init__.py:load_webhook_managers"Example: GitHub Webhook Integration
GitHub Webhook triggers: blocks/github/triggers.py
--8<-- "autogpt_platform/backend/backend/blocks/github/triggers.py:GithubTriggerExample"GitHub Webhooks Manager: integrations/webhooks/github.py
--8<-- "autogpt_platform/backend/backend/integrations/webhooks/github.py:GithubWebhooksManager"Key Points to Remember
- Unique ID: Give your block a unique ID in the init method.
- Input and Output Schemas: Define clear input and output schemas.
- Error Handling: Implement error handling in the
runmethod. - Output Results: Use
yieldto output results in therunmethod. - Testing: Provide test input and output in the init method for automatic testing.
Understanding the Testing Process
The testing of blocks is handled by test_block.py, which does the following:
- It calls the block with the provided
test_input.
If the block has acredentialsfield,test_credentialsis passed in as well. - If a
test_mockis provided, it temporarily replaces the specified methods with the mock functions. - It then asserts that the output matches the
test_output.
For the WikipediaSummaryBlock:
- The test will call the block with the topic "Artificial Intelligence".
- Instead of making a real API call, it will use the mock function, which returns
{"extract": "summary content"}. - It will then check if the output key is "summary" and its value is a string.
This approach allows us to test the block's logic comprehensively without relying on external services, while also accommodating non-deterministic outputs.
Security Best Practices for SSRF Prevention
When creating blocks that handle external URL inputs or make network requests, it's crucial to use the platform's built-in SSRF protection mechanisms. The backend.util.request module provides a secure Requests wrapper class that should be used for all HTTP requests.
Using the Secure Requests Wrapper
from backend.util.request import requests
class MyNetworkBlock(Block):
def run(self, input_data: Input, **kwargs) -> BlockOutput:
try:
# The requests wrapper automatically validates URLs and blocks dangerous requests
response = requests.get(input_data.url)
yield "result", response.text
except ValueError as e:
# URL validation failed
raise RuntimeError(f"Invalid URL provided: {e}")
except requests.exceptions.RequestException as e:
# Request failed
raise RuntimeError(f"Request failed: {e}")The Requests wrapper provides these security features:
URL Validation:
- Blocks requests to private IP ranges (RFC 1918)
- Validates URL format and protocol
- Resolves DNS and checks IP addresses
- Supports whitelisting trusted origins
Secure Defaults:
- Disables redirects by default
- Raises exceptions for non-200 status codes
- Supports custom headers and validators
Protected IP Ranges:
The wrapper denies requests to these networks:python title="backend/util/request.py"--8<-- "autogpt_platform/backend/backend/util/request.py:BLOCKED_IP_NETWORKS"
Custom Request Configuration
If you need to customize the request behavior:
from backend.util.request import Requests
# Create a custom requests instance with specific trusted origins
custom_requests = Requests(
trusted_origins=["api.trusted-service.com"],
raise_for_status=True,
extra_headers={"User-Agent": "MyBlock/1.0"}
)Error Handling
Blocks should raise appropriate exceptions for errors that users can fix. The executor classifies errors based on whether they inherit from ValueError - these are treated as "expected failures" (user-fixable) rather than system errors.
Block Exception Classes
Import from backend.util.exceptions:
from backend.util.exceptions import BlockInputError, BlockExecutionError| Exception | Use Case | Example |
|---|---|---|
BlockInputError |
Invalid user input, validation failures, missing required fields | Bad API key format, invalid URL, missing credentials |
BlockExecutionError |
Runtime failures the user can address | API errors, auth failures, resource not found, rate limits |
ValueError |
Simple cases (auto-wrapped to BlockExecutionError) |
Basic validation errors |
Raising Exceptions
from backend.util.exceptions import BlockInputError, BlockExecutionError
class MyBlock(Block):
async def run(self, input_data: Input, **kwargs) -> BlockOutput:
# Input validation - use BlockInputError
if not input_data.api_key:
raise BlockInputError(
message="API key is required",
block_name=self.name,
block_id=self.id,
)
try:
result = await self.call_api(input_data)
yield "result", result
except AuthenticationError as e:
# API/runtime errors - use BlockExecutionError
raise BlockExecutionError(
message=f"Authentication failed: {e}",
block_name=self.name,
block_id=self.id,
) from eWhat NOT to Catch
Don't catch errors that require system admin intervention:
- Out of money/credits
- Unreachable infrastructure
- Database connection failures
- Internal server errors from your own services
Let these propagate as unexpected errors so they get proper attention.
Data Models
Use pydantic base models over dict and typeddict where possible. Avoid untyped models for block inputs and outputs as much as possible
File Input
You can use MediaFileType to handle the importing and exporting of files out of the system. Explore how its used through the system before using it in a block schema.
Tips for Effective Block Testing
Provide realistic test_input: Ensure your test input covers typical use cases.
Define appropriate test_output:
- For deterministic outputs, use specific expected values.
- For non-deterministic outputs or when only the type matters, use Python types (e.g.,
str,int,dict). - You can mix specific values and types, e.g.,
("key1", str), ("key2", 42).
Use test_mock for network calls: This prevents tests from failing due to network issues or API changes.
Consider omitting test_mock for blocks without external dependencies: If your block doesn't make network calls or use external resources, you might not need a mock.
Consider edge cases: Include tests for potential error conditions in your
runmethod.Update tests when changing block behavior: If you modify your block, ensure the tests are updated accordingly.
By following these steps, you can create new blocks that extend the functionality of the AutoGPT Agent Server.
Standalone Unit Tests
In addition to the inline test_input/test_output/test_mock approach described above, you can write standalone unit tests for more complex validation. Place test files alongside your block as {block_name}_test.py (e.g., my_block_test.py).
!!! warning "Test file naming and auto-discovery"
The block loader in backend/blocks/__init__.py skips files whose names start with test_ but still auto-imports files ending in _test.py at startup. Keep _test.py modules free of module-level side effects (no top-level patch(...) / network calls / fixtures-with-side-effects), or name your file test_{block_name}.py to opt out of auto-import entirely.
Test Structure
Two patterns are supported:
1. Drive the block directly (mirror backend/blocks/slack/slack_test.py) — gives you full control over inputs, mocks, and output assertions:
import pytest
from unittest.mock import AsyncMock, MagicMock, patch
from backend.blocks.my_provider._auth import TEST_CREDENTIALS, TEST_CREDENTIALS_INPUT
from backend.blocks.my_provider.my_block import MyBlock
async def _collect_outputs(block, input_data, credentials):
outputs: dict[str, object] = {}
async for name, value in block.run(input_data, credentials=credentials):
outputs[name] = value
return outputs
@pytest.mark.asyncio
async def test_my_block_success():
block = MyBlock()
input_data = MyBlock.Input(
credentials=TEST_CREDENTIALS_INPUT,
query="test query",
)
mock_resp = MagicMock()
mock_resp.json.return_value = {"result": "test data"}
# Patch where the symbol is *used*. The block ecosystem uses
# `backend.util.request.Requests` (httpx under the hood), not stdlib `requests`.
with patch(
"backend.blocks.my_provider.my_block.Requests.post",
new_callable=AsyncMock,
return_value=mock_resp,
):
outputs = await _collect_outputs(block, input_data, TEST_CREDENTIALS)
assert outputs["result"] == "test data"2. Run the block's inline test_input / test_output / test_mock via the framework helper. The helper takes a block instance only and reads the test data defined on the block itself:
import pytest
from backend.blocks.my_provider.my_block import MyBlock
from backend.util.test import execute_block_test
@pytest.mark.asyncio
async def test_my_block_inline_fixtures():
await execute_block_test(MyBlock())Key Points
TEST_CREDENTIALS_INPUT(from_auth.py) is used to construct theInputobject — it contains the credential metadata (provider, id, type).TEST_CREDENTIALS(from_auth.py) is passed toblock.run(..., credentials=...)— it contains the actual mock API key/token.execute_block_test(block)runs the block against its own inlinetest_input/test_output/test_mockattributes (set in__init__). It does not acceptinput_data=orcredentials=and does not return outputs.- The block attribute for test credentials is
test_credentials(nottest_credentials_input) when using the inline test pattern in__init__. - Use
pytest.mark.asynciofor async blockrun()methods. Mock the HTTP layer where it is used — typicallybackend.util.request.Requests(orhttpx.AsyncClient), not stdlibrequests.
Running Tests
# Run a specific block's standalone tests
poetry run pytest backend/blocks/my_provider/my_block_test.py -xvs
# Run all block tests (inline test_input/test_output validation)
poetry run pytest backend/blocks/test/test_block.py -xvs
# Test a specific block by name (inline tests)
poetry run pytest 'backend/blocks/test/test_block.py::test_available_blocks[MyBlock]' -xvsBlock Documentation Sync
After creating or modifying blocks, you must regenerate the block documentation to keep it in sync with the code.
Generating Documentation
cd autogpt_platform/backend
poetry run python scripts/generate_block_docs.pyFor provider packages, the generator writes one page per Python module:
docs/integrations/block-integrations/{provider}/{module}.mdFor example, backend/blocks/zerobounce/validate_emails.py generates docs/integrations/block-integrations/zerobounce/validate_emails.md. Top-level block modules are grouped into category pages such as docs/integrations/block-integrations/basic.md.
CI Enforcement
The "Block Documentation Sync Check" CI workflow (.github/workflows/docs-block-sync.yml) runs on every PR that touches files in backend/blocks/ or docs/integrations/. It runs the generator in --check mode and fails the build if docs are out of sync.
If CI fails with a docs sync error, run the generator locally, commit the updated docs, and push.
Manual Sections
The generated docs support manually-written content that is preserved across regeneration. Use these markers in the generated markdown files:
Your custom usage examples here. This will not be overwritten.Use the generator-supported block sections how_it_works, use_case, and extras, or file-level file_description and additional_content. Keep the existing markers rather than inventing section names. These sections hold usage examples, caveats, and integration notes that cannot be generated from block metadata.
Platform Ollama (`docs/platform/ollama.md`)
Running Ollama with AutoGPT
Important: Ollama integration is only available when self-hosting the AutoGPT platform. It cannot be used with the cloud-hosted version.
Follow these steps to set up and run Ollama with the AutoGPT platform.
Prerequisites
- Make sure you have gone through and completed the AutoGPT Setup steps, if not please do so before continuing with this guide.
- Before starting, ensure you have Ollama installed on your machine.
Setup Steps
1. Launch Ollama
To properly set up Ollama for network access, choose one of these methods:
Method A: Using Ollama Desktop App (Recommended)
Open the Ollama desktop application
Go to Settings and toggle "Expose Ollama to the network"
Click on the model name field in the "New Chat" window
Search for "llama3.2" (or your preferred model)
Click on it to start the download and load the model to be used
??? note "Method B: Using Docker (Alternative)"
If you prefer to run Ollama via Docker instead of the desktop app, you can use the official [Ollama Docker image](https://hub.docker.com/r/ollama/ollama):
1. **Start Ollama container** (choose based on your hardware):
**CPU only:**
```bash
docker run -d -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama
```
**With NVIDIA GPU** (requires [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html)):
```bash
docker run -d --gpus=all -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama
```
**With AMD GPU:**
```bash
docker run -d --device /dev/kfd --device /dev/dri -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama:rocm
```
**Download your desired model:**
```bash
docker exec -it ollama ollama run llama3.2
```
!!! note
The Docker method automatically exposes Ollama on `0.0.0.0:11434`, making it accessible to AutoGPT. More models can be found on the [Ollama library](https://ollama.com/library).??? warning "Method C: Using Ollama Via Command Line (Legacy)"
For users still using the traditional CLI approach or older Ollama installations:
1. **Set the host environment variable:**
**Windows (Command Prompt):**
```cmd
set OLLAMA_HOST=0.0.0.0:11434
```
**Linux/macOS (Terminal):**
```bash
export OLLAMA_HOST=0.0.0.0:11434
```
2. **Start the Ollama server:**
```bash
ollama serve
```
3. **Open a new terminal/command window** and download your desired model:
```bash
ollama pull llama3.2
```
!!! note
This will download the [llama3.2](https://ollama.com/library/llama3.2) model. Keep the terminal with `ollama serve` running in the background throughout your session.2. Start the AutoGPT Platform
Navigate to the autogpt_platform directory and start all services:
cd autogpt_platform
docker compose up -d --buildThis command starts both the backend and frontend services. Once running, visit http://localhost:3000 to access the platform. After registering/logging in, navigate to the build page at http://localhost:3000/build.
3. Using Ollama with AutoGPT
Now that both Ollama and the AutoGPT platform are running, we can use Ollama with AutoGPT:
Add an AI Text Generator block to your workspace (it can work with any AI LLM block but for this example will be using the AI Text Generator block):
Configure the API Key field: Enter any value (e.g., "dummy" or "not-needed") since Ollama doesn't require authentication.
In the "LLM Model" dropdown, select "llama3.2" (This is the model we downloaded earlier)
Compatible Models: The following Ollama models are available in AutoGPT by default:
llama3.2(Recommended for most use cases)llama3.3llama3llama3.1:405bdolphin-mistral:latest
Note: To use other models, follow the "Add Custom Models" step above.
Set your local IP address in the "Ollama Host" field:
To find your local IP address:
Windows (Command Prompt):
cmdipconfigLinux/macOS (Terminal):
baship addr showor
bashifconfigLook for your IPv4 address (e.g.,
192.168.0.39), then enter it with port11434in the "Ollama Host" field:text192.168.0.39:11434Important: Since AutoGPT runs in Docker containers, you must use your host machine's IP address instead of
localhostor127.0.0.1. Docker containers cannot reachlocalhoston the host machine.Add prompts to your AI block, save the graph, and run it:
That's it! You've successfully setup the AutoGPT platform and made a LLM call to Ollama.
Using Ollama on a Remote Server with AutoGPT
For running Ollama on a remote server, simply make sure the Ollama server is running and is accessible from other devices on your network/remotely through the port 11434.
To find your local IP address of the system running Ollama:
Windows (Command Prompt):
ipconfigLinux/macOS (Terminal):
ip addr showor
ifconfigLook for your IPv4 address (e.g., 192.168.0.39).
Then you can use the same steps above but you need to add the Ollama server's IP address to the "Ollama Host" field in the block settings like so:
192.168.0.39:11434Add Custom Models (Advanced)
Model definitions are centralized in the LLM catalog — see Managing LLM Models for the full field reference and workflow. To add a custom Ollama model:
Add a catalog entry in
autogpt_platform/backend/backend/data/llm_registry/catalog.py, next to the other Ollama models:pythonCatalogModel( slug="The-model-name-from-ollama", # bare name, exactly as Ollama serves it display_name="Your Model", provider="ollama", context_window=8192, # adjust for your model cost=CatalogModelCost(run_credits=1), ),Ollama models use bare model names (no
vendor/prefix) — the name must match whatollama listshows. A run cost of1is fine for local usage; cost tracking is disabled for self-hosted instances.Also add one
LLMModelname line inautogpt_platform/backend/backend/data/llm_registry/llm_models.py(Ollama section) — that identifier is what block schemas serialize; every model FACT (metadata, costs) lives in the catalog entry itself, and an import-time check refuses to boot if an enum name has no catalog entry. The catalog entry is what centralizes the model's metadata and makes it routable for AutoPilot.Rebuild the backend:
bashdocker compose up -d --buildPull the model in Ollama:
bashollama pull your-model-name
Troubleshooting
If you encounter any issues, verify that:
- Ollama is properly installed and running with
ollama serve - Docker is running before starting the platform
- If running Ollama outside Docker, ensure it's set to
0.0.0.0:11434for network access
Common Issues
Connection Refused / Cannot Connect to Ollama
- Most common cause: Using
localhostor127.0.0.1in the Ollama Host field - Solution: Use your host machine's IP address (e.g.,
192.168.0.39:11434) - Why: AutoGPT runs in Docker containers and cannot reach
localhoston the host - Find your IP: Use
ipconfig(Windows) orifconfig(Linux/macOS) - Test Ollama is running:
curl http://localhost:11434/api/tagsshould work from your host machine
Model Not Found
- Pull the model manually:bash
ollama pull llama3.2 - If using a custom model, ensure it's added to the model list in
backend/api/model.py
Docker Issues
- Ensure Docker daemon is running:bash
docker ps - Try rebuilding:bash
docker compose up -d --build
API Key Errors
- Remember that Ollama doesn't require authentication - any value works for the API key field
Model Selection Issues
- Look for models with "ollama" in their description in the dropdown
- Only the models listed in the "Compatible Models" section are guaranteed to work
Platform Scheduling And Triggers (`docs/platform/scheduling-and-triggers.md`)
Scheduling & Triggers
Overview
AutoGPT agents can be run on demand, on a recurring schedule, or automatically in response to external events via webhooks. This guide covers both scheduling and trigger-based execution.
Scheduling an Agent
Scheduling lets you run an agent automatically on a recurring basis with pre-configured inputs.
Setting Up a Schedule
- Go to your Agent Library and open the agent you want to schedule
- Click New agent task (the same button used for manual runs)
- Fill in all the input fields with the values you want the agent to use
- At the bottom of the input form, you'll see two buttons: Start Task and Schedule Task
- Click Schedule Task
Configuring the Schedule
The schedule configuration screen allows you to set:
| Setting | Description |
|---|---|
| Schedule Name | A descriptive name for this schedule |
| Repeats | Frequency — e.g., Weekly |
| Repeats On | Select specific days (individual days, weekdays, weekends, or select all) |
| At | The time of day to run (hour and minute) |
| Timezone | Schedule runs in your local timezone, displayed on screen |
Example: Run "Keyword SEO Expert" every weekday at 9:00 AM in the timezone shown in the schedule form.
Managing Schedules
After creating a schedule, you can view and manage it on your agent's detail page:
- Open the agent from your library
- Select the Scheduled tab on the left-hand side
- The tab shows the count of active schedules (e.g.,
Scheduled 1)
Triggers (Webhook-Based Execution)
Triggers allow external services or your own code to start an agent automatically by sending data to a webhook URL.
How Triggers Work
Unlike standard input blocks, trigger blocks are a special type of input block. When you add a trigger block to your agent in the builder, the agent's execution model changes — instead of being started manually, it waits for incoming webhook events.
Setting Up a Trigger
Step 1: Add a Trigger Block in the Builder
- Open the Agent Builder
- From the blocks menu, add a trigger block to your agent
- Connect it to the rest of your workflow like any other input block
- Save your agent
Step 2: Configure the Trigger in Your Library
- Go to your Agent Library and open the agent
- Click New agent task
- Enter the trigger name and description and complete any required inputs or credentials
- Click Set up Trigger
Step 3: Copy the Webhook URL
Once the trigger is created, you'll see a status panel:
Trigger Status
Status: Active
This trigger is ready to be used. Use the Webhook URL below to set up the trigger connection with the service of your choosing.
Webhook URL:
https://backend.agpt.co/api/integrations/generic_webhook/webhooks/<webhook-id>/ingress
Copy this webhook URL and provide it to the external platform or code that will be sending events to trigger your agent.
{% hint style="info" %}
Trigger-based agents cannot be started manually. Configure them through New agent task and Set up Trigger, then send data to the webhook URL to execute them.
{% endhint %}
Example Use Cases
- GitHub webhook: Trigger an agent whenever a pull request is opened
- Payment processor: Run an agent when a new payment is received
- Form submission: Process data when a user submits a form on your website
- Custom integration: Send data from any service that supports webhooks
Schedule vs. Trigger
| Schedule | Trigger | |
|---|---|---|
| How it starts | Automatically at configured times | When an external event sends data to the webhook URL |
| Input source | Pre-configured when the schedule is created | Provided by the incoming webhook payload |
| Use case | Recurring tasks with fixed inputs (daily reports, weekly summaries) | Event-driven tasks (new PR opened, form submitted, payment received) |
| Setup | Through the New agent task → Schedule Task flow | Through trigger blocks in the builder, then New agent task → Set up Trigger in the library |
Platform Sharing And Exporting (`docs/platform/sharing-and-exporting.md`)
Sharing & Exporting Agents
Overview
There are several ways to share agents and their outputs with others on the AutoGPT Platform. This guide covers all the available options.
Sharing Options
Share Task Output via URL
A completed task remains private until you explicitly enable sharing:
- Open the agent and select the completed task.
- Click Share results.
- Review the public-access warning and click Enable Sharing.
- Copy the generated Share URL.
Anyone with that URL can view the run's outputs. Use Stop Sharing in the same dialog to revoke the link.
{% hint style="warning" %}
A share URL is publicly accessible to anyone who has it. It does not publish the task inputs, but outputs may contain sensitive data.
{% endhint %}
Publish to the Marketplace
The most visible way to share an agent is to publish it to the Marketplace. Published agents are discoverable by all platform users and go through a review process by the AutoGPT team.
See Publishing an Agent for the full guide.
Export as a File
For sharing agents privately — without publishing to the marketplace — you can export an agent as a file:
- Go to your Agent Library and open the agent
- Click the three dots (⋯) on the far right
- Select Export Agent to File
- The agent file will download to your computer
You can then send this file to anyone via email, messaging, or any other method.
Import a Shared File
To import an agent file someone has shared with you:
- Go to your Agent Library
- Click Import
- Select the AutoGPT agent tab
- Upload the exported
.jsonfile - The agent will be added to your library
Teams & Collaboration
Organization and team workspaces are available only when enabled for your account or deployment. Use their built-in access controls where available. Export/import remains the portable way to transfer an agent file between separate accounts or deployments.
Platform Single Container (`docs/platform/single-container.md`)
Run AutoGPT in One Docker Container (Experimental)
The image packages the AutoGPT Platform frontend and backend together with
PostgreSQL, a three-node Valkey cluster, RabbitMQ, nginx, and FalkorDB.
Only nginx listens on the container's public interface, on port 3000.
The FalkorDB service is a core part of this image and always runs; Graphiti
memory is enabled by the image's default configuration and still requires a
working model-provider profile.
All durable state is stored under /data. Use a named Docker volume for every
installation you intend to keep.
Requirements
- Docker Engine or Docker Desktop capable of running Linux containers on
linux/amd64orlinux/arm64. Building locally also requires Docker Buildx. - More than the measured 5–6 GiB of appliance memory use, with additional
headroom for agents, local models, and workload growth. - Host storage with enough capacity and throughput for the image, the growing
/datavolume, and at least one complete compressed backup archive alongside
the live data. Shutdown and backup time depend on storage throughput and fsync
latency; SSD-class storage is recommended but not a measured support floor.
There is no measured minimum CPU count or supported concurrent-run ceiling;
performance depends on the enabled services and workload.
Get an image
Use latest for the most recent fully verified stable AutoGPT Platform image:
IMAGE=significantgravitas/autogpt:latest
docker pull "${IMAGE}"For a reproducible installation, replace latest with an immutable vX.Y.Z
tag or manifest digest. Docker image tags map to source releases as follows:
latestpoints to the newest stable AutoGPT Platform release.vX.Y.Zis the immutable image for GitHub releaseautogpt-platform-beta-vX.Y.Z.sha-<git-sha>is the immutable image for an exactdevor release source
revision.
Older canary-sha-* tags are legacy pre-release validation artifacts, not a
currently published or supported tag family.
To build from source instead, run Docker Buildx Bake from the repository root:
docker buildx bake \
--file autogpt_platform/single-container/docker-bake.hcl \
--load \
single-container
IMAGE=autogpt-platform:single-container-devCommands below use the shell variable IMAGE, whether it identifies the
Docker Hub image, a pinned digest, or the local build.
The image has a complete default command, so you can optionally perform a
foreground boot check:
docker run --rm "${IMAGE}"Skip this check if you want to proceed directly to a persistent installation;
Quick start performs the same first-boot work against its named volume. The
check runs in the foreground. First boot may take several minutes and use
roughly 5–6 GiB of memory; press Ctrl-C to stop it. That interruption is safe
for this check because --rm discards its anonymous /data volume. Do not
generalize it to an installation that uses a named volume. The command does not
publish the web port. Use the full setup below for a usable installation.
Quick start
Create a private environment file:
umask 077
touch autogpt.env
chmod 600 autogpt.envWhen working from a source checkout, you can copyautogpt_platform/single-container/.env.example instead to see common optional
settings. The backend-only BEHAVE_AS control and its limits are documented in
Security boundary.
Edit the file and set at least the public URL and exact address for the first
account:
AUTOGPT_PUBLIC_URL=http://localhost:3000
[email protected]Replace [email protected] with the email address for the intended first
account. Passwords must contain at least 12 characters. Exact-address
allowlisting compares the email string asserted during signup; because this
image does not support required email verification, it is not proof of mailbox
ownership. New-account creation defaults open when the allowlist is empty; the
value above immediately restricts signup to that address. If the allowlist is
omitted, anyone who can reach the published port can create an account untilAUTH_ALLOW_NEW_ACCOUNTS=false is applied. The run command below binds the app
only to loopback. Create and promote the administrator and close signup while
the app is still loopback-only. Configure an HTTPS origin before entering
credentials on any LAN or remote deployment.
Provider keys are not required to boot, create an account, use Builder, or run
provider-free blocks. Model-backed functions return their normal actionable
missing-credential error until you configure a profile from
Models and memory.
Start the appliance:
docker run --detach --name autogpt \
--restart unless-stopped \
--shm-size 2g \
--ulimit nofile=65536:65536 \
--log-driver json-file \
--log-opt max-size=50m \
--log-opt max-file=5 \
--env-file autogpt.env \
--publish 127.0.0.1:3000:3000 \
--volume autogpt-data:/data \
"${IMAGE}"The --shm-size 2g allocation keeps temporary ChatGPT/Codex authentication
homes in memory instead of the container's writable layer. The nofile limit
sets a predictable per-process file-descriptor ceiling for the bundled services.
The JSON log options retain about five 50 MB files instead of allowing container
logs to grow without a bound.
Wait for the complete appliance to become healthy:
docker inspect --format '{{.State.Health.Status}}' autogpt
docker logs --tail 100 --follow autogptDo not stop the container while the first boot is applying database migrations.
If a migration is interrupted, later boots refuse to continue and the logs
identify the migration and recovery choices. For a brand-new empty installation,
remove that installation's unused /data volume and start again. For an existing
installation, restore the pre-upgrade backup. Use prisma migrate resolve only
after determining whether that migration's changes reached the database.
For an empty first boot that used the exact Quick start names, first verify that
the volume contains no user data, then discard that failed installation:
docker stop autogpt
docker rm autogpt
docker volume rm autogpt-dataRepeat Quick start afterward. The failed appliance can remain in a restart loop
until explicitly stopped, so docker exec is not a reliable way to runprisma migrate resolve; that advanced path requires PostgreSQL running against
the affected volume from a separate recovery environment. Do not use it without
inspecting whether the migration's database changes completed.
Test installations used about 5–6 GiB of memory during startup and steady-state
health checks. This is measured guidance, not a guaranteed minimum; allow
headroom for enabled services, agents, local models, and workload growth. On
Docker Desktop, make sure the VM's memory allocation in Settings → Resources
exceeds that observed use and leaves the same headroom.
Open http://localhost:3000, create the intended account, and promote it:
docker exec autogpt autogpt-admin promote [email protected]Replace [email protected] with the email address of the account you created.
Sign out and back in after promotion so the new session has the administrator
role.
Then set this in the environment file:
AUTH_ALLOW_NEW_ACCOUNTS=falseApply the change by replacing only the container. Keep the same named volume:
docker stop autogpt
docker rm autogptRepeat the docker run command above. Removing the container does not remove
the autogpt-data volume.
Stopping
docker stop autogpt is designed and tested to complete inside Docker's stock
10-second timeout, so no host-wide timeout change is needed; a longer host
timeout does not extend the internal Supervisor caps. The shipped one-second
runtime, five-second state, and one-second event-listener phases measured about
8.4 seconds in the shutdown test setup. That is a narrow measured margin, not a
graceful-shutdown guarantee for slower storage, so do not shorten Docker's
timeout. Runtime processes are signaled first. PostgreSQL, RabbitMQ, Valkey,
and FalkorDB are signaled afterward and each gets at most five seconds to exit
before Supervisor forces it down. The event listener stops last. Larger or
slower state may require normal crash recovery on the next boot.
Agent runs still executing when the container stops are abandoned. Their queue
messages can be dropped and their execution rows can remain RUNNING, but they
do not resume. Start a new run after restart.
Supervisor process names are group-qualified. Use supervisorctl status to see
names such as runtime:rest and state:postgres.
Port and public URL
Container port 3000 does not change. To use host port 3300, change the run
command to:
--publish 127.0.0.1:3300:3000and set:
AUTOGPT_P