Build an AI Support Agent With Human Approval
Build a support agent that summarizes and drafts ticket replies, then holds them in a review queue until a human approves.
A support agent with human approval separates drafting from sending: a skill summarizes the
incoming ticket, a second skill drafts a reply from that summary, and a workflow step holds the
draft in a review queue instead of sending it. The gate is a property of the workflow, not
the skill — Vocion's approve step type pauses a run and waits for a person to decide
(docs/entities/workflow.md). Vocion, an open-source agent workforce platform, builds this pattern
out of two skills — a summarizer and a drafter — as separate SKILL.md files in a workspace,
git-native and versioned.
What should it actually do
Scope it small: one ticket in, one summary and one draft out, nothing sent automatically. A
drafting skill's own description field is a good place to say so — end it with something like
"lands in the review queue for human approval before sending," since that field is what the agent
reads to decide when the skill applies, and it doubles as documentation for the next person who
opens the workspace. Resist the temptation to wire a live send in the first version. Get the draft
right and the review step trusted before anything leaves the building.
Why two skills instead of one
Splitting summarize from draft is a cost and latency decision, not a quality one. Each skill is a
SKILL.md file with its own description the agent reads to decide when to use it, so write two
narrow ones: a summarizer whose description says it reads a raw ticket and produces a short,
structured summary of the issue, and a drafter whose description says it writes a reply from that
summary rather than from the raw ticket. A short, cheap pass to extract the essentials, then a
second pass that only has to write — not also re-read and interpret the raw ticket — keeps the
expensive model's context small and its job narrow.
Where the skill files live
A workspace is a workspace.yaml (or workspace.yml) manifest at the root, plus skills/<slug>/
folders. The loader accepts only those two manifest filenames and errors if neither exists
(packages/core/src/libs/workspace/loader.ts:299, error at loader.ts:313). Skills replaced an
older operations/ layer: if the loader finds a leftover operations/ directory it throws rather
than silently skipping it — workspace ${abs} still has an operations/ directory — operations were removed; convert each to a skill folder under skills/<slug>/SKILL.md
(packages/core/src/libs/workspace/loader.ts:156-158). If you are working from an older workspace
or an older guide, that is a migration to make, not a bug to route around.
One subtlety worth knowing before you name folders: the skill folder name and the skill's own
slug: field don't have to match — the manifest schema requires slug as its own field, and
nothing in the loader enforces that it echo the folder name back. A folder named skills/my-skill/
can validly carry slug: my_other_slug in its SKILL.md frontmatter. Anything that references the
skill — an agent's skills: list, or, eventually, a workflow step — references the slug, not the
folder name, so pick folder names for readability and slugs for stability.
How an agent gets wired to a skill
An agent manifest attaches skills through a skills array of slugs:
skills: z.array(z.string()).default([]).describe('skill slugs this agent can invoke'), alongside
an objectTypes array for business object types
(packages/core/src/libs/workspace/schemas.ts:150,152). Those are the field names — skillSlugs
and objectTypeSlugs do not exist in the schema, so don't write them into a manifest expecting
them to validate.
How you make the draft wait for a person
This is the part worth being exact about, because it is easy to get backwards: there is no
per-skill approval flag. PlaybookManifestSchema, which governs an authored skill's manifest, has
no approval-related field at all (packages/core/src/libs/workspace/schemas.ts:522-546). The
string requiresApproval does exist in the codebase, but only in the marketplace-plugin contract —
a separate, unrelated layer for third-party plugin skills
(packages/core/src/libs/plugins/loader.ts:33, packages/core/src/libs/plugins/registry.ts:45,52).
Setting anything called requiresApproval on a SKILL.md file does nothing, because the schema
doesn't read it.
The actual gate is a workflow step type: approve. Vocion's workflow schema is a discriminated
union of exactly four step types —approve, ask, action, sync
(packages/core/src/libs/workspace/schemas.ts:262,277,293,300,310). The public docs describe
approve as: "Pauses in the review queue for a human decision. reviews names the earlier step
whose output is being judged" (docs/entities/workflow.md). A few lines below that table, the same
page is direct about the other half of this: "There is no skill step. Skills are read by the
agent on its own judgement." That is current, documented behavior, and it matters for how you wire
summarize_ticket and draft_reply together.
In this release, a workflow step cannot invoke a skill directly — there is no skill step type in
WorkflowStepSchema. The two paths a workflow does support are: put an approve step around
something an agent already produced (the agent decides to read draft_reply on its own judgement,
per the doc line above, and the workflow's job is only to pause and route the result to a human),
or run the connector-backed action step after that approval clears. A minimal, valid pairing of
the two steps that do exist looks like this:
- name: review
type: approve
prompt: Review the drafted support reply before sending.
- name: send
type: action
action: gmail.send
input:
to: '{{input.requester_email}}'
subject: '{{input.ticket_subject}}'
body: '{{steps.review.output.body}}'
draft: true
That is the shape a support-triage workflow takes once the drafting itself is the agent's responsibility rather than a chained pair of skill-invocation steps. If you are used to workflow engines where each step names one function call, this is the adjustment: here, an agent is the unit that reads skills, and the workflow is the unit that gates and routes what the agent produces.
Where the human does the review
The dashboard surface is /dashboard/review; the same queue is reachable over HTTP as
GET /api/v1/reviews and POST /api/v1/reviews/decide
(packages/core/src/app/api/v1/reviews/decide/route.ts). The decide endpoint's body is
{ kind, id, action, reason?, editedInput? }, where kind is workflow, mission, or action
and action is approve or reject. editedInput is the edit-then-approve payload: corrected
fields for an action, or the input a paused workflow resumes with — it's ignored on a reject. For
the full endpoint list and the release that shipped it, see
The review queue over HTTP.
What to add before production
A real ticket source. Vocion's connector list, checked directly against
packages/core/src/libs/sources/, is: Google Drive, Gmail, Google Calendar, Google Ads, GA4,
HubSpot, Jira, Slack, Strapi, S3, Zoom, Granola, web pages, local files, and file import. That list
is finite — no help-desk-specific connector ships today, so a ticket has to arrive through one of
those, or through the workflow's inputSchema for a manual or API-triggered run.
Two more things worth adding once the draft loop is trusted: a trust.yaml rule if you ever want a
send action to auto-execute above a confidence threshold — one file per workspace, off by default
(docs/entities/trust.md) — and versioning so a reply traces back to the workspace state that
produced it. Every workspace:apply writes a workspace_version audit row
(docs/entities/workspace-manifest.md), so a drafted reply can always be tied to the exact skill
prompts that were live when it was written.
Honest limits
This is a single source, a single draft, and no live send — the action step in the example above
sets draft: true on gmail.send, so it creates a Gmail draft rather than actually delivering
anything. And the most important limit: the mechanism that would let
one workflow step hand its output straight to another skill does not exist in this release. What
does exist is an agent that reads both skills on its own judgement, with the workflow doing only
the gating and the routing. Build for that shape rather than for a chain of skill steps, and this
holds up as core evolves.
Related
Go deeper on the gate itself in AI Agent Approval Workflow,
which covers the approve step, trust thresholds, and the decision API in more detail. For the
architecture behind the summarize-then-draft pattern, see the reference guide,
Building a support reply system.
Clone vocion-core, write the two SKILL.md files, and put one approve step around whatever
the agent drafts before any of it reaches a customer.