Share what a CrewAI crew produced
A research crew runs for six minutes. The writer agent hands off to the editor
agent, the editor produces the final markdown, and output_file="report.html"
drops it next to your script.
Then someone asks for it, and the honest answer is "it's on my machine."
output_file is an artifact, not a channel
output_file is doing exactly what it says. The problem is that a crew is
usually run somewhere other than where the reader sits — a scheduled container,
a Docker image, CrewAI's own deployment surface — and none of those hand out
filesystem access.
Two ways to fix it, depending on how much you trust the agents with the network.
Option one — a publish tool on the last agent
from crewai.tools import tool
import httpx, os
@tool("publish_report")
def publish_report(title: str, html: str) -> str:
"""Publish an HTML report and return its shareable URL."""
r = httpx.post(
"https://commareports.com/api/v1/reports",
headers={"Authorization": f"Bearer {os.environ['COMMA_API_TOKEN']}"},
json={"title": title, "html": html},
)
r.raise_for_status()
return r.json()["url"]
Attach it to the editor, and make the expectation explicit in the task, because a task that does not name its output shape will not produce one:
Task(
description="Assemble the findings into a single HTML report.",
expected_output="The URL returned by publish_report, and nothing else.",
agent=editor,
tools=[publish_report],
)
Option two — publish after kickoff
If you would rather the agents never touch HTTP, let the crew return the document and publish it yourself:
result = crew.kickoff(inputs={"topic": topic})
url = publish(title=f"{topic} — research", html=render(result.raw))
print(url)
This is the version to prefer for anything scheduled. The crew stays deterministic in what it produces, and the publish step is ordinary code you can retry, rather than a tool call an agent might decide to skip.
Feedback as the next input
The interesting part is the return leg. A crew that can list the comment threads on its last report gets a brief that is grounded in what actually bothered the reader:
Fetch the open comments on report
<id>. Treat each one as a revision request, produce an updated report, update the same report id, and reply in each thread saying what changed.
That is the loop described in letting an agent respond to comments. Updating the same id matters — a second link means the reviewer's comments are stranded on a page nobody is looking at any more.
Worth knowing
- Hierarchical crews should publish from the manager. The worker agents hold fragments; the manager holds the document.
reports:writeis the whole scope a crew needs. Addcomments:readonly for the revision loop — see scoped tokens for AI agents.- 5 MB per report body.
- Keep the URL, drop the HTML. Passing a rendered document between tasks spends context on every subsequent step.
Try it
Free — unlimited reports, commenters and revisions.