Theo Agents

Theo Agentsare the user-visible brand for OpenCharts' persistent agent platform. Each Theo Agent owns a name, an icon, a trigger, a step graph, a sharing visibility, and an optional verification badge — and runs on the same EVI runtime that powered the legacy Automation Designer. Build one in the Designer, run it manually or on a schedule, share it with your org, and the Library lets your teammates install a verified copy with one click.

Where Theo Agents liveThe dashboard is at /theo-agents. The Designer is at /theo-agents/[id] and opens as a dedicated full-page workspace — the regular sidebar + dashboard header step out of the way so you can focus on the agent. The Library is at /theo-agents/library. Run history at /theo-agents/[id]/runs. Insights at /theo-agents/[id]/insights. Legacy /automations and /steelagents URLs 308 to the matching Theo Agents route so existing bookmarks keep working.

Two kinds of agents

Workflow agents

Deterministic step-by-step pipelines built in the visual Designer. Best when the shape of the work is known in advance — fetch, transform, post, notify.

Autonomous agents (coming soon)

Goal-driven agents that compose their own steps at runtime are on the roadmap. The Workflow ↔ Autonomous toggle is hidden in the Designer until that runtime ships — today every agent runs as a deterministic Workflow, so build your agents as explicit step graphs.

In the Designer

  • Dedicated full-page canvas — the Designer drops the global sidebar + header so the agent is the only thing on screen. A slim breadcrumb keeps you anchored and a single click on the emblem takes you back to Mission Control.
  • Run vs Test — the Test button does a free dry-run with live per-step status, while the Run button fires a real, credit-consuming run that lands in Run history. For a manual-trigger agent the Run button is the primary action (nothing fires it automatically); you can also run any agent on demand from its card’s actions menu or the Run history page.
  • Drag-and-drop StepLibrary with the live step kinds: action (connector), theo_action (AI action), path (branching), approval (human gate), delay, and call_agent (sub-agent dispatch).
  • TheoBuilderRail — natural-language co-builder. Tell Theo "add a step that summarises last week's Stripe payments and posts to Slack" and watch the canvas update. One clear close button on the rail, an ‘Ask Theo’ pill that brings it back, and your collapse choice is remembered across reloads.
  • Delay step — pause the flow for a short, fixed wait (up to 2 minutes) before the next step runs. Longer or recurring waits belong on a scheduled trigger instead.
  • Wait for Approval (human gate) — the agent pauses and pings you in Telegram (and the in-app bell) with Accept / Reject buttons. Approving resumes the run right where it left off; a paused agent waiting on you is never counted as a failure.
  • Per-step model + temperature override for theo_action steps so you can pin a specific engine on a step without changing the rest of the agent.
  • Scheduled agents get the same time budget as a manual run, so a multi-step agent on a schedule finishes instead of being cut off early.

Steps are still authored as the same AutomationStep[] JSON the legacy Automations Designer used — Theo Agents is a brand-level rename plus the new agent-aware features (per-step model controls, verification, library, feedback). The underlying /api/automations/* + /api/steelagents/* endpoints and the automations collection are unchanged.

Paths & conditions

A Path step splits the chain into parallel branches that re-converge once one of them completes. The Designer renders the split as a contained Parallel branches panel that sits inside the path card via a hairline separator — same lane width as the rest of the chain, no breakout, no horizontal scroll. Branches stack vertically inside the panel; the layout looks identical at 360px and 1920px and grows downward gracefully no matter how many branches you add.

Each branch is a full-lane-width branch card with a left-edge accent stripe — blue for indexed branches, amber for the default. The card header carries a Branch N / Default chip, an inline label input (If match / Else / whatever you type), and a kebab menu (⋯) with the branch-level actions (see below).

Below the header, every labeled branch shows an always-visible inline condition editor with three knobs:

  • Field — the upstream output key to test, picked from a context-aware dropdown that groups From trigger first, then one section per upstream step (e.g. From Step 1 — Theo Decision). Each entry shows a one-line description from the skill’s output schema so you know why decision, summary, or sent appears. Choose Custom path… at the bottom of the list to type a freeform dot-path like triggerData.customer.email for connector-specific or webhook-payload keys the registry doesn’t model. Leave blank to fall back to the path step’s default subject set on the expanded path card.
  • Operator — one of equals, does not equal,>, >=,<, <=,contains, starts with, plus the unary checks exists, is truthy, and is falsy / empty. Numeric operators coerce both sides; contains and starts with are case-insensitive.
  • Value — what the field is compared to. Hidden automatically when the operator is unary (exists / truthy / falsy).

The kebab menu (⋯) on each branch card hosts the less-frequent actions: Mark as default branch (promotes the lane to the explicit “else”, replaces the condition editor with Runs when no other branch matches), Custom expression (collapses the structured editor to a single freeform condition string the runtime matches against input.decision / result / value), and Delete branch.

Below the editor, each branch carries its own Then step chain — the same full-width <StepCard> chrome the main lane uses, plus an + Add step button at the bottom. Adding the 3rd, 4th, 10th branch just extends the panel downward; no fan-out, no per-lane width compromise, no horizontal scroll. The panel ends with an ↓ All branches continue here marker that anchors the run back into the main chain.

Runtime contract. At save time the EVI adapter rewrites each branch onto an edge labelled branch_0, branch_1, … (or default for the else branch). The switch executor walks the branches in order, evaluates each expression against the upstream input, and emits { decision: "branch_<i>" } for the first match. If nothing matches and there is no default branch the path fails the step with an actionable error — silent dead-ends are intentional bugs that the validation banner also warns about before you can Go Live.

Every new path you add starts seeded with one structured “If match” branch + one explicit “Else” default. Add more branches with the Add branch button; the editor keeps the default trailing so the visual order matches the runtime fall-through order.

Heads-up validation. When a branch references a field key no upstream trigger or step is known to emit, the Designer surfaces a neutral Heads up note above the canvas with a suggestion to pick from the dropdown or switch to the Custom path option. This is advisory only — it never blocks Test or Go Live because custom connectors and webhook payloads can legitimately emit keys the registry doesn’t model.

Research a list (web enrichment)

Theo Agents can take a list — a CSV/Excel you upload or names & companies you paste — crawl the public web for each row, and hand back an enriched file. A classic example: upload a list of people and have Theo find each one's public social / LinkedIn profile, then download the list with a new linkedin_url column.

The easiest way — drop a CSV in the Theo Builder. Open the Theo Builder rail on the right of the Designer, click the paperclip to attach your CSV, and type what you want in plain language — “add each person's LinkedIn and current company”. Theo reads your file's column headers and builds the whole Import → Find / Enrich → Export flow for you, pre-filled with your real columns — no step assembly, no comma-typing. Or, on a brand-new agent, click Or enrich a list from a CSV under Add your first step (and the Enrich a List starter in the template gallery) to drop the same three-step recipe in one click.

Two ways to run it:

  • Open-ended (chat with a worker). Attach a file to a worker and just say what you want — “find each person's LinkedIn and email these results to me”. The worker reads the rows, searches the web for each, and can export a CSV (and even attach it to an email). Best when the task is fuzzy or one-off.
  • Repeatable (Designer pipeline). Build it once in the Designer: Import CSV / JSON (or Read Google Sheets / Airtable) → Find / Enrich Export to Spreadsheet. Run it on demand or on a schedule against a fresh list each time. The Import / Read steps live under the new Data tab in the step picker; Find / Enrich is in AI Actions.

Giving the Import step your list. Open the Import CSV / JSON step and pick one of three sources: Upload file(browse for a CSV, Excel (.xlsx), TSV, or JSON file — it's re-read fresh on every run; Excel workbooks are parsed straight into rows, no need to export to CSV first), Paste data (drop rows straight into a box; Theo shows a live count of the rows and columns it detected), or From URL (a public link to a CSV/TSV/JSON file Theo fetches each run). Read Google Sheets and Read Airtable instead connect your account and ask for the spreadsheet ID + range, or the base ID + table name.

Personalize from your list. Any creation or messaging step downstream of an Import / Read step can weave the data into its fields with {{column}} placeholders — e.g. a document titled Welcome packet for {{company}} or an email body Hi {{first_name}}. The step config shows the columns you can use; at run time each {{column}}is filled from the incoming row. Placeholders that don't match a column are left exactly as typed, so brace syntax in code or JSON steps is never clobbered.

Find / Enrichtakes a plain-English goal (“find each person's current company”) plus the columns to add. You don't type comma-separated text — click to add each column as a chip, and Theo seeds suggestions straight from your uploaded list's real headers, so the identifying columns are one tap away. For every row it runs a focused web search built from the row's identifying columns, reads the most promising public results, and fills in the requested fields — attaching a _source link and a _confidencelevel. When it can't find a confident match it leaves the field blank rather than guessing, so you can trust what comes back.

Already have a website or profile URL? If your rows carry a website / domain / link column, name it in the optional Website / URL column picker (Theo also auto-detects obvious ones like website or url). Theo then scrapes that page directly instead of running a blind web search — faster and far more reliable when you already know the source. If the page can't be reached it quietly falls back to a web search for that row.

The export is your spreadsheet — plus the new columns. Export to Spreadsheet automatically detects the spreadsheet the agent started from. Leave its config blank and the downloaded file keeps the imported file's name (e.g. insurance-agents.csv) and every original column in its original order, with the new columns (like linkedin_url, plus _source / _confidence) appended at the end — so the result reads as the same spreadsheet with new columns, every run. This works even when an Approval or AI step sits between Find / Enrich and the export. Type your own file name or column list on the step only when you want to override that.

What to expect. Enrichment uses public search results only — it surfaces public profile links and never logs in or pulls anything behind a login or paywall. Small lists (up to ~50 rows) finish inside the run itself. Bigger lists — hundreds of rows — continue automatically in the background: the run returns right away, and the Designer plus your Run history show it as Running in background(not Completed) while Theo keeps going through every row. When it finishes, the run flips to Completed and you get an email and an in-app notification with the finished spreadsheet's download link (the export step's same-spreadsheet naming and column rules still apply). If your AI credits run out mid-list, you still get the file — enriched up to where the credits stopped, with the rest included unenriched. Rows that error out are kept (flagged, never dropped) so one bad lookup never sinks the whole batch. Note: the Designer's Test button only processes the first batch — use a real run for the full list.

Library, install, and fork

The Library shows every verifiedTheo Agent in the user's org. Clicking Install mints a personal fork of the source agent (verified flag cleared, visibility set to private). Re-installing the same source is idempotent — you always get back the existing fork instead of a duplicate.

The fork carries a forkOfId pointer so future version-pinning work can resolve the right published version. Today the runtime dispatches the live target document; the executor records target.publishedVersion on everycall_agent step output so the insights aggregator can attribute future runs.

How verification works

Verification is what unlocks two things at once: org-wide Library install and cross-user chat dispatch. Until an admin flips the badge, a Theo Agent can only be dispatched by its owner.

  1. Share with the org. In the Designer header, set Visibility to Org. Personal-private agents cannot be verified — they’re scoped to the owner and never appear in the Library.
  2. Ask an admin to verify. Owners and admins of the agent’s org see a Verify button next to the badge cluster in the Designer. Editors and viewers don’t see the button at all — the verify-context route hides it for them.
  3. Click Verify. The Designer POSTs to /api/steelagents/[id]/verify, which runs the canonical gate end-to-end. On success the badge shows Verified and the agent is immediately routable from Theo Chat and installable from the Library. Click again to unverify if you need to pull it back.

All three steps are gated on the verify_steelagents org permission, which defaults to owner + admin.

Dispatch another agent from chat

Theo can route any chat request to one of your routable Theo Agents instead of handling it directly. The agents your org has shared (plus your own private agents) appear in Theo’s Routable Theo Agents system-prompt block; Theo picks the best match and calls route_to_agent to dispatch.

You can also nudge Theo by mentioning an agent explicitly:

  • Use the agent’s slug with an @ prefix — e.g. “kick off the @daily-standup-brief and send it to me”. The chat tool matches case-insensitively and accepts the slug with or without the leading @.
  • Paste the agent’s $id directly when you already have it. Theo Chat looks up the registry first, then falls back to a direct service lookup so the dispatch works even when the agent isn’t in the in-prompt block.

Once Theo dispatches, the chat shows a confirmation card with the agent name, the run link (/theo-agents/[id]/runs), and the thumbs-up / thumbs-down feedback widget. Your rating feeds the agent’s Insights page — every run is attributable to the user who triggered it, so an admin can see real quality signal from chat dispatches.

Three guardrails mirror the runtime call_agent rules above:

  • You can always dispatch agents you own from chat, even when they’re private.
  • Agents owned by another user must be verified and in an org you belong to. Cross-org dispatch is refused.
  • Soft-deleted agents are refused, and Theo will tell you when a route fails so you can correct the reference inline.

Chat-side dispatch is currently gated by the STEELAGENTS_ROUTER server flag. While the flag is off the registry block is empty and Theo Chat treats the tool as disabled.

Sub-agent dispatch (call_agent)

A Theo Agent can call another Theo Agent as a step. Three hard rules govern who can invoke whom:

Same-org-only sub-agent dispatch

A Theo Agent's `call_agent` step can only invoke another Theo Agent in the same org. Cross-org calls are rejected by the executor with a typed permission error — even when the target carries the verified badge.

Cycle detection

The runtime tracks every ancestor automation id in the trigger payload's call stack. Re-entering an agent that's already on the stack (A → B → A) fails fast before any work runs.

Verified-or-owned

A Theo Agent can only call agents it owns or agents that carry the platform `verified` badge. Org admins flip the badge via the `verify_steelagents` permission (env-var + RBAC action names retained for backwards compatibility).

The runtime stamps the caller's org id and a call stack onto the EVI trigger payload before each run; the evi/call-agent executor reads those annotations and short-circuits cross-org / cyclic dispatch before invoking the target.

Feedback & insights

1

Every run gets attribution

Each Theo Agent execution writes an `execution_runs` row keyed by the agent id. The runner is also stamped — only the user who actually executed a run can rate it.

2

Thumbs-up / thumbs-down + comment

The RunFeedbackWidget posts to `POST /api/steelagents/feedback` (API namespace retained for backwards compatibility). Resubmitting upserts the existing row so users can flip their rating without leaving duplicates behind.

3

Insights aggregator

The Insights tab at `/theo-agents/[id]/insights` reads the last 100 runs + every feedback row and renders success rate, average duration, credits per run, feedback score, and the top failure clusters.

Org permissions

Two new actions live on the org permission matrix:

  • manage_steelagents_library — gates the org admin UI for curating categories and approving agents submitted by org members.
  • verify_steelagents — flips the platform-levelautomations.verified badge. Verified agents become eligible for cross-user call_agent dispatch and library install.

Both default to owner and admin.

How Skills relate to Theo Agents

Skills (from theSkill Store) and Theo Agentsare two distinct surfaces that share Theo's underlying capabilities but live in their own runtimes:

  • Skillsextend Theo's chat tool surface. You install one, then mention it in chat and Theo picks the right tools automatically. Triggered by manual prompts, keywords, scheduled heartbeats, or platform events.
  • Theo Agents run as deterministic step-by-step automations — manual, scheduled, webhook, event, or Telegram-triggered. Each step calls a specific canvas skill (evi/send-email, evi/ai-summarize, etc.).

The two systems intersect through a single canvas step: Run Theo Skill (evi/run-autonomy-skill). Drop it into an agent step graph, choose a Skill Store skill by slug, and the runtime dispatches the skill mid-run with a credit-capped budget (Haiku + 3 sub-steps). Use this when you want a workflow to hand off a self-contained creative task (e.g. generate a business plan) without rebuilding the skill's prompt graph inline.

Two namespace gotchas worth knowing:

  • Different slug namespaces. Each Theo Agent step has a canvasSkillSlug like evi/research-deep — that names a canvas building block, NOT a Skill Store skill. Skill Store slugs (e.g. biz-plan-generator) only appear inside the config of a Run Theo Skill step. (The on-disk JSON wire key on `automations.steps` is still skillSlug for back-compat; the rename to canvasSkillSlug happens in-memory via the `parseSteps` / `serializeSteps` adapter.)
  • Different categories. Skills use { productivity, domain, integration, automation, creative }; Theo Agents use department categories ({ sales, support, hr, engineering, marketing, it, opencharts, starters }). The Library's category chip row is the canonical place to filter agents; the Skill Store has its own filter.

Feature manifest

The canonical surface inventory lives at .manifests/theo-agents.json. It enumerates every type, route, collection, service, test, and hard rule that belongs to Theo Agents. Update it whenever you add or remove a Theo Agents surface so the contract test in __tests__/contracts/registryVerification.test.tskeeps catching drift.

Run history

Every execution writes a row to execution_runs with the per-node status, total duration, credits used, and any error message. The run history page at /theo-agents/[id]/runs renders these for owners, and the Insights aggregator scans them when building its snapshot.

Was this article helpful?

Related Articles