AI Engineer Code 2025

Building Intelligent Research Agents with Manus

Ivan Leo· Manus1:21:30

Read the talk

Building Intelligent Research Agents with Manus

Ivan Leo builds toward a Manus-powered Slack bot, connecting asynchronous tasks, uploaded files, webhooks and conversation state to a receipt workflow backed by Notion policies.

From a talk by Ivan Leo

Before you start: Basic Python, HTTP requests, environment variables and webhook concepts will help you follow the integration examples.

What does a general agent actually do?

What does Manus actually do when its product description spans answers, applications and automated workflows? The practical test in Ivan Leo’s workshop is to reproduce its Slack bot: receive a request where someone already works, let an agent execute it, and bring the result back into the conversation.

That requires more than a model call. Leo reports that Manus handles millions of conversations daily, bringing infrastructure, sandboxes and reliability into the foreground. The interface is only the entry point to that machinery.

Slide describing infrastructure, state persistence, tool orchestration, and reliability challenges when building agents.Open full source frame
Building AI agents is hard. Scaling them is harder.

The available entry points include web and mobile applications, Slack, an API, a browser operator and Mail Manus. Leo also introduces a recently launched Microsoft 365 integration, while describing the envisioned Word, PowerPoint and Excel workflows partly as future capabilities. Four demonstrations establish what these surfaces can do before the workshop turns to Python.

0:380:51
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

0:38 · section reference included

A journal that teaches French as you write

Leo has been learning French and wants a way to practice throughout the day. He asks Manus to build a phone-friendly journal where an ordinary thought—such as being stressed about the workshop—becomes language practice. The application identifies mistakes inline, supplies a complete corrected version, and lets him ask about an unfamiliar word such as défi.

The application combines model calls with an interface designed around the task. Leo describes built-in support for structured outputs, transcription with Whisper, and the model calls behind the corrections and vocabulary tutor. The displayed journal keeps the original writing, corrections and explanation together, so the learner can inspect the change without leaving the entry.

Language journal with highlighted corrections, an expanded corrected version, and an AI Tutor panel explaining the French word défi.Open full source frame
French journal corrections alongside an AI tutor’s explanation of “défi.”

External providers can join the same application. Leo adds ElevenLabs for speech and plays a French rendering of the idea that life is lived forward but understood backward. As he uses the app, it also builds a learner profile: an agent builder at Manus, comfortable tackling abstract ideas but struggling with expressions involving distance and time. He built the application with Manus 1.5 by prompting it between conference talks.

An audience question establishes where that profile comes from: Leo explicitly requested it in the application prompt. It is application behavior, rather than a demonstrated account-wide memory setting. Personal facts and grammatical gender help the app handle French agreement and suggest relevant daily writing topics.

3:093:20
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

3:09 · section reference included

Delegating through email and an authenticated browser

Mail Manus offers another way to hand off work: forward or reply to an email from the phone. The live example stalls because screen sharing appears to disable keyboard input, so Leo moves to the browser operator. He asks for a simple coffee near the New York venue after his journey from Singapore, specifying that an Americano would do.

The browser’s existing session is part of the useful context. A browser on the user’s computer may already be signed into LinkedIn, Instagram or other services that a separate sandbox browser cannot immediately access. The initial coffee request does not choose the intended browser, so Leo explicitly asks Manus to use his browser. After a delay, a Google Maps tab appears on his computer.

Leo suggests extending this pattern to parallel browser instances or a scheduled daily task on a dedicated Mac mini. These are automation possibilities arising from the demonstration; browser selection and authorization through the API receive a more specific qualification in the closing questions.

5:546:05
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

5:54 · section reference included

Turning conference pages into a personal schedule

The next application starts with a discovery problem: a conference has many events, and Leo wants to understand which ones to attend. He asks Manus to open individual event pages, scrape their contents, save the results as JSON, and load that data into a new website. Its brutalist styling takes inspiration from Chroma’s website.

Opening the event “2026, the year the IDE died” reveals its scraped description, a Google Calendar action and related events, including MCode and AGI: The Path Forward. Leo supplies a Chroma API key and an OpenAI key for embeddings to support similar-event discovery. Starring events builds a personal daily timeline, which he uses to find talks by Will Brown and Applied Compute. The workflow joins data collection, retrieval and a purpose-built interface.

The runtime matters when that interface needs backend behavior. Leo describes a full Docker image in which packages can be installed, including Redis with BullMQ for queued work. The Stripe integration configures webhooks that the application can receive and test. Autoscaling and warm deployments are future work in this presentation.

This reduces the number of separate assembly steps in a small application: writing the site, setting up Chroma, connecting a coding tool such as Claude Code, and selecting a deployment destination. The general agent can work across those steps while the developer supplies the desired behavior and external credentials.

9:069:18
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

9:06 · section reference included

Establishing the API connection

The API exposes an agent with its own per-chat sandbox, access to configured services such as Notion, and custom MCP integrations. Manus uses it for its public Slack bot, Zapier and n8n integrations, and internal support workflows that inspect conversations in a browser and produce reports with screenshots. Leo says the same query costs the same through the API and web chat under the account’s billing.

The workshop proceeds through five notebooks, with README screenshots for obtaining Slack credentials. Copy the required values from .env.example into .env: a Manus API key, a Slack bot token and a Slack signing secret. The first check lists uploaded files. On the fresh account, an empty list confirms that the authenticated request succeeds without requiring a task to run.

Notebook showing a connection-validation request and its list-object response beside the Manus API Workshop README.Open full source frame
Validating the connection to the Manus API.

Leo says Files API uploads are automatically deleted after 48 hours. He also discusses explicit deletion and says the session must be deleted for the file to be gone. That explanation does not establish a complete deletion policy for conversations and derived artifacts; uploaded-file expiration and session deletion need to be treated as separate lifecycle concerns.

12:3712:56
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

12:37 · section reference included

Create a task and keep its identity

Leo uses direct Python HTTP requests and mentions support for the OpenAI Responses SDK. The recording uses the historical v1 API; the current Manus documentation identifies v2 as its successor and v1 as deprecated. The task states and request behavior below follow the workshop rather than substituting today’s API contract.

The first prompt asks for a brief answer to two plus two. Creation returns a task ID, a task title and a task URL. The ID is the durable handle for the conversation: when Manus needs clarification, or the user wants to revise the result, a follow-up can target the same task. An account/API-key mismatch briefly interrupts opening the task; after correcting it, Leo shows the answer, four.

When no chat mode is specified, Leo says a router chooses between simple chat and the full agent. Explicit API parameters offer finer control. Profile selection is a separate decision:

ProfileWorkshop guidance
Manus 1.5Complex tasks, including both opening websites
Manus 1.5 LiteSimpler tasks where quicker responses matter

The notebooks use Lite because their examples are comparatively simple.

Leo describes context management that continues beyond the underlying model’s ordinary window, rather than an infinitely large model input. He connects this to high KV-cache reuse and the practices discussed in Peak Ji’s Context Engineering for AI Agents: Lessons from Building Manus. For the application developer, the immediate consequence is that the task can remain the conversation container as work continues.

16:3216:41
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

16:32 · section reference included

A task can stop to ask a question

The next prompt asks Manus to look up New York City’s weather and then ask the user a question about it. After creation, the client polls the task. The workshop enumerates four states:

StateMeaning for the application
runningWork is still underway
pendingThe agent needs user input
completedWork has finished
errorThe task failed

A pending task is an interaction point. Treating it as either completed or still computing would hide the question from the user.

Manus finds a rainy forecast and asks whether Leo would prefer an indoor or outdoor activity on Monday or Tuesday. Retrieving the task exposes the response, user and assistant messages, and credits used. Leo chooses an indoor activity on Tuesday and sends that answer with the same task ID. The follow-up appears in the existing chat, preserving the weather research and the question that prompted it.

Polling is the simplest prototype: retrieve the task, inspect its state, and decide whether to wait, request input, show the result or handle an error. It establishes the asynchronous lifecycle before a webhook receiver is introduced.

19:5320:02
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

19:53 · section reference included

Upload the context the task needs

A research task often needs material beyond its prompt: a policy PDF, a document of instructions or a dataset. The next notebook reuses an env.py helper for credentials and starts with character records from the Rick and Morty API. Leo fetches approximately the first hundred characters and saves rickandmorty.json, containing status, episode references, origin, current location and image information.

The Files API separates allocating an upload from transferring the bytes:

  1. Request a file record with an upload name such as rickandmortycharacters.json.
  2. Receive a file ID and an S3 upload URL.
  3. Send the file contents to that URL with PUT.
  4. Attach the file ID to a task asking for a visually appealing website.

The byte-transfer step in Python is small:

python

from pathlib import Path
import requests


def upload_dataset(upload_url: str, path: Path) -> None:
    with path.open("rb") as dataset:
        response = requests.put(
            upload_url,
            data=dataset,
            timeout=120,
        )
    response.raise_for_status()

The returned file ID, rather than the local path, connects the uploaded dataset to the task.

Leo starts the website task and leaves it running. He describes built-in PDF parsing and image handling, so callers can attach those formats without first building their own extraction pipeline. The Files API also provides an explicit deletion operation.

22:4622:56
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

22:46 · section reference included

Public documents, connectors and generated artifacts

A public URL avoids a separate upload. Leo initially plans to supply a call transcript, as might come from Circleback, but the sample file has been deleted. He substitutes a Berkshire Hathaway investor-letter PDF and asks Manus to summarize it and explain what the company is looking to do.

Previously configured account integrations provide another source of context. For Gmail or Notion, Leo opens Manage Connectors, copies the connector UUID and adds it to the task’s connector payload. The application can therefore combine an attached document with information available through an authorized service.

Manus interface with an open connector menu listing services including Gmail, Google Calendar, Google Drive, and Outlook Mail.Open full source frame
Available connectors in the Manus task interface.

The third attachment approach embeds Base64 data. Leo supplies a screenshot of a settings-page 404, together with the affected website, and asks for a thorough bug investigation. Other pages also contain 404s. This starts an investigation; its final findings are not shown in this part of the workshop.

While that runs, the earlier tasks produce inspectable outputs. The Rick and Morty website displays gender distribution and character status. The investor-letter task recognizes that the attachment contains a PDF even though its name remains calltranscript.json, then produces an analysis. The filename alone does not determine how the agent interprets the content.

After troubleshooting the task lookup, Leo shows the generated Markdown artifact’s metadata: a full file URL, filename and MIME type. These are useful application outputs in their own right. A downstream worker can retrieve a report or pass it to another system without scraping the chat interface.

25:4025:48
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

25:40 · section reference included

Replace repeated polling with task notifications

The earlier examples poll every 20 seconds. With many tasks in flight, repeated status checks create unnecessary work and leave the application managing waiting workers. A webhook reverses that relationship: Manus sends an HTTP request when the task reaches a relevant lifecycle boundary, and the application reacts to the notification.

Leo serves a Python FastAPI endpoint through Modal with modal serve server.py, obtaining a public URL for the receiver. He mentions a $5 signup credit available at the time. Audience members begin sending requests to the displayed endpoint during the demonstration, making its reachability immediately visible.

The workshop describes notifications for task creation and stopping, and exercises the receiver with a test payload described as “message ended.” Leo estimates that a typical Manus task takes three to five minutes. His example of a hundred simultaneous tasks explains why callbacks are useful: each task can notify the server when needed instead of requiring its own repeated polling loop.

Registration is a POST to Manus. Leo corrects the historical base address to api.manus.ai/v1, registers the receiver, and shows the webhook under Integrations → Built with Manus API. A new two-plus-two task then produces a completion notification. Its task ID correlates the result with the original request; the displayed information includes the task URL, status and output.

A notification can also trigger a fuller retrieval. Leo configures a Modal secret with the Manus credentials so the server can fetch task details and the complete conversation history. This keeps the callback handler focused on identifying the task and obtaining the material needed for the next application step.

31:0731:19
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

31:07 · section reference included

Receive a Slack mention and reply in its thread

The Slack integration begins in the Ivan Personal workspace with a bot named Mani. After loading the credentials, Leo uses auth.test to check the bot’s authentication. He then runs modal serve chatbot.py, exposing a /webhook/slack endpoint backed by the stored Slack secret and the application dependencies. A mention supplies the user-facing input that this endpoint will eventually receive.

Slack workshop channel showing the posted message “@Mani what’s up” beneath the bot’s channel-join notice.Open full source frame
A mention sent to Mani in the Slack workshop channel.

In the Slack Events API configuration, the receiver must answer Slack’s verification challenge before the Event Subscriptions URL can be saved. Once verified and saved, mentioning Mani delivers an event to the server. Its raw text contains Slack mention markup, including the bot’s identifier, which needs to be parsed before becoming a useful agent prompt.

To reply, preserve the channel and the thread’s root timestamp. Leo copies a received event into the notebook, extracts its destination fields and posts “Hello, world.” A small Python helper makes the distinction between a root message and a reply explicit:

python

from slack_sdk import WebClient


def reply_in_thread(
    client: WebClient,
    event: dict,
    text: str,
):
    thread_ts = event.get("thread_ts") or event["ts"]
    return client.chat_postMessage(
        channel=event["channel"],
        thread_ts=thread_ts,
        text=text,
    )

The timestamp used for routing is the conversation root, so a response to an existing reply stays in the same thread.

Acknowledge the event before doing the agent’s work. Slack expects an HTTP 2xx acknowledgment within three seconds and retries if it does not receive one. That deadline applies to receipt of the event, not to producing the final Manus answer. Leo recommends keeping the server warm so startup does not consume the acknowledgment window.

Files have their own routing step. Leo uploads what he calls the Berkshire Hathaway 2025 investor letter, obtains a Slack file ID and uses it in the subsequent message flow. An undefined timestamp interrupts the exercise, and the file lands in the main channel rather than the intended thread. The later return-path implementation must carry the thread destination into file upload as well as text posting.

37:1437:26
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

37:14 · section reference included

Turn the mention into an agent task

The notebook’s manual operations now move into an app_mention handler. Leo brings over post_slack_message and the supporting helpers, fixing missing imports and configuration values—including dataclass, the Manus secret and the Manus URL—as he assembles the server. The resulting mention-to-Modal-to-Slack round trip reproduces the earlier manual reply. This handler is also where an application could retrieve user metadata or prior conversation records before starting the agent.

create_manus_task adds an authenticated request containing the prompt. After waking the Modal server, Leo asks Mani for New York’s weather. Slack receives confirmation that a task was created, and the task appears in Manus. Task creation is only half the integration: the final weather answer does not yet return to Slack.

Before completing that return path, Leo improves the acknowledgment. Slack user lookup can resolve raw mention IDs and connect them to application metadata. He extends post_slack_message to accept blocks, adds the relevant typing imports, and uses Block Kit to show a progress message with a “View on Web” button pointing to the task URL. After correcting a text-argument mismatch, the richer acknowledgment works. The user can now open the running task while the integration continues developing.

44:4245:48
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

44:42 · section reference included

Keep one Manus task per Slack conversation

Multi-turn interaction needs a record of which Slack threads already have Manus tasks. Leo introduces Modal Dict, a serializable key-value store, while noting that a database or another platform’s KV store could fill the same role. Temporary set-and-retrieve endpoints first demonstrate writing and reading task metadata. The record includes a task ID, task URL, Slack channel, thread_ts, Slack user and job status.

Leo removes the demonstration endpoints and introduces a thread map. The routing decision has two branches:

  • Unseen thread: strip the bot mention from the prompt, create a task and save its association with the Slack thread.
  • Known thread: retrieve the existing task ID and send the follow-up into that conversation. Add an eyes reaction to acknowledge that the bot is handling it.

This is the same continuation mechanism used for the weather clarification, now selected automatically from Slack metadata.

The first test exposes a boundary mismatch: the handler supplies a task ID, but create_manus_task does not accept that argument. Leo adds the optional parameter and conditionally includes it in the JSON request. On the next attempt, the eyes reaction appears, and the New York temperature follow-up is visibly appended to the original Manus task. The Slack thread and Manus conversation now remain connected across turns.

54:5855:06
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

54:58 · section reference included

Route the result back, including its files

The remaining gap is the reverse direction. A Manus callback must identify the originating Slack thread, transform the result and post it there. Leo adds a /webhook/manus endpoint and extends file upload to accept thread_ts. The live test starts a new task and posts its progress acknowledgment, but the expected return callback does not complete cleanly during the demonstration.

The intended return path is a sequence of distinct operations:

  1. Save routing metadata in a task-info map when creating the task.
  2. Use the callback’s task identity to recover that metadata.
  3. Convert generated Markdown into Slack-compatible formatting.
  4. Package the text into Slack blocks.
  5. Upload generated files using both the channel and thread timestamp.

Manus outputs can contain tables, text and images, while Slack has its own formatting rules. Rendering and file placement therefore require explicit handling even after the agent has produced a correct result. Leo explains this implementation and switches to prepared final code for the receipt example.

1:01:531:02:09
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

1:01:53 · section reference included

Investigate a receipt against editable company policy

The prepared application receives a bagel receipt image through Slack. Its intake path receives the webhook, creates the file, starts the Manus task and posts a progress acknowledgment. A Notion connector gives the agent access to a deliberately fictitious company policy. The receipt supplies transaction evidence; Notion supplies the rules against which the expense should be assessed.

Leo shows the successful upload and describes OCR extracting receipt details. Earlier tests include a question about whether $30 spent on New York cabs is covered. Because the policy lives in Notion, people can edit the source document without changing the Slack bot’s code. The current task continues generating while the application waits for its final webhook.

Earlier results illustrate the fuller workflow: identify the merchant as Tompkins Square Bagels, inspect the amount and receipt summary, then accept a follow-up requesting a Markdown table. Leo shows an update to the appropriate Notion expense page and describes receipts spanning the 18th through the 28th. These prior results demonstrate the intended research-and-update behavior; the current receipt run is still awaiting its return notification.

1:08:131:08:22
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

1:08:13 · section reference included

Find a repeatable task before building its integration

The application combines built-in integrations with supplied API keys, uploaded files and custom code or toolkits. Manus handles agent execution; the surrounding application still owns the business logic that decides what to send, how conversations are associated, and where results belong.

Slide listing advanced agent patterns, production deployment, custom integrations, orchestration, specialized domains, and performance tuning.Open full source frame
Areas for further exploration with agents.

Asked how to discover useful API applications, Leo recommends starting in the web app. Pick something repeated, try it several times, and determine whether the agent can do it consistently. Only then move into API requests and work out the prompt and context each run needs. The web interface supplies the initial sandbox without first requiring webhooks or a Slack integration.

The conference website provides a concrete example of that iteration. Returning to its original chat, Leo shows a request to scrape an estimated seventy-plus talks, interpret their times as EST, and build a clean brutalist site with a search bar, calendar actions, starred events and mobile support. He explores Mixedbread before settling on Chroma and supplies service credentials as the application develops.

The work behind the interface includes a Python script that normalizes AM/PM values and converts times to UTC. In the browser, JavaScript expands page content, matches the expanded elements and reads HTML to recover the event records. The long conversation exposes the refinement involved: the goal is stable, but the agent uses several tools and intermediate representations to get there.

1:11:341:11:59
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

1:11:34 · section reference included

The boundaries: sensitive data, permissions and memory

A prospective support-agent builder asks where sensitive information lives, who can read transcripts and whether it can be deleted. Leo says staff cannot read chats except when users share them for troubleshooting. He tentatively says he thinks the data is housed in the United States. The answer does not resolve the audience’s broader deletion question.

Another question combines direct calendar insertion with a request for interesting use cases. Leo turns to research and an example from Singapore’s competitive pickleball booking system. He reports that Manus generated a Python script running six Selenium instances to scrape the government site for availability. It identified a location where he could play in two weeks; he describes finding availability, not completing a booking. The useful capability is the sandbox’s ability to write, run and test task-specific code.

The browser question brings an important limit into focus: how would an API request choose the user’s browser? Leo says selection and approval need to prevent unexpected tabs from opening. Improved permission handling for the browser and wide research is on the roadmap at the time of the workshop, qualifying the earlier broad description of API parity.

Artifact conversion is also described as forthcoming. Leo tentatively suggests a release in about two weeks for exports resembling those in the UI: research assembled into pitch slides, downloadable PPTX or PDF, and Markdown-to-PDF conversion. These are planned extensions to the artifact workflow shown earlier.

The last question asks whether account-linked memory can reuse background from other conversations, optionally disabled for a temporary chat. Leo says that capability is not available at the time and is being explored. Continuing a task and remembering across tasks are different capabilities. The Slack bot preserves context by reusing a task ID; a separate conversation still needs its relevant background supplied explicitly.

1:16:331:16:44
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

1:16:33 · section reference included

Resources

From the talk

Updates since the talk

Transcript download
Read the transcript and explore slides

Loading transcript and slides…