← All posts

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.

Vocion Teamvocion-v2.47.4

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.

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.