Skip to main content

Artifacts

Artifacts are structured, interactive outputs that agents produce during conversations. Instead of returning plain text, an agent can produce a rich artifact — an email draft, a meeting invite, a CRM records table, a task, or a calendar view — that the user can review, edit, and act on directly in the chat interface.

How artifacts work

When an agent produces an artifact, it creates a typed payload with two components:
  • Content — the structured data the UI renders (e.g., email fields, meeting details, table rows)
  • State — lifecycle status driven by user actions (draft, sent, scheduled, confirmed, cancelled)
Artifacts are tied to conversations and persist across turns. The agent can see summaries of existing artifacts as context, enabling multi-turn refinement (“change the subject line”, “add Sarah to the invite”).

Artifact types

TypeDescriptionActions
Email draftStructured email with to/cc/bcc, subject, bodySend, edit, discard
Meeting inviteCalendar event with attendees, time, agendaSchedule via Google Calendar
TaskAction item with assignee, due date, priorityCreate in task system
Scheduled taskRecurring or time-triggered agent taskActivate, pause, delete
Slack messageFormatted Slack message draftSend to channel
Records tableCRM data table (accounts, contacts, opportunities)Sort, filter, drill-in
CalendarInline calendar viewNavigate, click events
PlanMulti-step execution planReview, modify steps

API

List registered artifact types

GET /artifacts/types
Returns all artifact types with UI metadata — label, icon, default display mode (panel or inline), and streaming support.

Get an artifact

GET /artifacts/{artifact_id}

List artifacts in a conversation

GET /artifacts/conversation/{conversation_id}

Update artifact state

When a user takes action on an artifact (sends an email, schedules a meeting):
PATCH /artifacts/{artifact_id}/state
{
  "sent": true,
  "sent_at": "2026-01-15T10:30:00Z"
}

Edit artifact content

For inline editing (e.g., changing email subject before sending):
PATCH /artifacts/{artifact_id}/content
{
  "subject": "Updated: Q3 Review Follow-up",
  "body": "Hi team..."
}

Execute artifact actions

# Create a task from a draft
POST /artifacts/{artifact_id}/create-task

# Schedule a meeting via Google Calendar
POST /artifacts/{artifact_id}/schedule-meeting

# Create or update a scheduled agent task
POST /artifacts/{artifact_id}/scheduled-task

# Toggle scheduled task active/inactive
POST /artifacts/{artifact_id}/scheduled-task/active

# Record user interaction (e.g., calendar click)
POST /artifacts/{artifact_id}/interaction

Summarizers and the caching layer

Every artifact type has a summarizer — a function that takes the full artifact content and produces a compact summary dict. This powers two critical features:

LLM context efficiency

When tool results are large (CRM record sets, email threads, meeting lists), the full data would consume the entire context window. Instead, AgentFlow:
  1. Caches the full result in a TTL-scoped result cache
  2. Summarizes it into a compact dict (total count, field names, 3-row preview)
  3. Returns the summary to the LLM conversation, with a cache_id reference
  4. The agent can retrieve the full data later via get_cached_result(cache_id=...) if needed
This means a 50-row CRM query result becomes a ~200-token summary instead of consuming 10,000+ tokens — while the full data remains accessible.

Cache-ref artifact auto-registration

Tools that produce cacheable structured output use the @tool(cache_ref=True, summarizer=...) decorator pattern. This automatically:
  • Registers a cache configuration (summarizer function, TTL, cache threshold)
  • Registers a matching artifact type (so the UI can render the data as a panel)
  • Links the tool result to both the cache and the artifact system
@tool(
    name="get_account_health",
    description="Get health metrics for a CRM account",
    agent="RecordsAgent",
    cache_ref=True,
    summarizer=summarize_account_health,
)
async def get_account_health(account_id: str) -> dict:
    ...
The summarizer follows a (dict, str) -> dict interface — it takes the full result and returns the compact summary:
def summarize_account_health(result: dict, tool_name: str) -> dict:
    return {
        "score": result.get("health_score"),
        "trend": result.get("trend"),
        "risk_factors": len(result.get("risks", [])),
        "last_activity": result.get("last_activity_date"),
    }

Built-in summarizers

Tool categoryWhat’s summarizedTTL
CRM recordsTotal count, field names, 3-row preview30 min
EmailsTotal count, field names, 3-row preview1 hour
MeetingsTotal count, field names, 3-row preview1 hour
TasksTotal count, field names, 3-row preview30 min
Slack messagesTotal count, has_more flag, 3-message preview10 min
Record fields/viewsField names, object type, counts24 hours

Agent context injection

Agents automatically receive compact summaries of existing artifacts in the conversation as <system_context> parts. This means the agent knows what drafts are pending, what’s been sent, and what actions the user has taken — enabling natural follow-up interactions like “actually, change the meeting to 3pm” or “add the quarterly numbers to that email”. When a user takes an action (sends an email, schedules a meeting), an <artifact_action> context part is persisted so subsequent agent turns are aware of what changed.