Social media draft agent
This page explains how a social media draft agent signs in to your social backend under an interactive grant, reads campaign context and past posts, and creates per-platform drafts awaiting confirmation. After reading it, you will understand which modules this scenario needs, why publishing must stay outside the delegated scope, and why data like brand rules belongs in a policy store rather than Memory.
Use case
Marketing teams need to turn one campaign into drafts for X, LinkedIn, newsletters, communities, or regional channels without letting the Agent publish automatically. Each platform differs in length, tone, and format, so rewriting by hand is slow and drifts from brand rules — while handing a social account to a script grants publishing, commenting, and direct-messaging authority all at once.
Typical triggers:
- A new campaign launches, and first-wave drafts for several platforms are needed within one business day.
- A single product update announcement must be rewritten per platform at different lengths and tones.
- Brand rules or platform preferences change, and the next batch of drafts must follow the new policy version.
Engineering challenges
- Many conflicting platform constraints: X length limits, LinkedIn's industry tone, and long-form article layouts all differ, so the same campaign message drifts away from brand rules during per-platform rewrites, and manual checks are expensive.
- Drafting and publishing are one button apart: in most social backends the "save draft" and "publish" controls sit side by side, and one slip is a live incident. The "drafts only" intent needs a mechanical guarantee, not just prompt instructions.
- Borrowed operator access is too broad: an operator session inherently carries publish, comment, and DM authority, while the drafting task only needs to read context and write to the draft box.
Module composition
| Module | Role | Notes |
|---|---|---|
| GenAuth | Core | Interactive delegation, revocation, and the audit chain for social backend sign-in; publish and engagement actions stay outside the grant. |
| Web Agent | Core | Controlled sessions sign in to the social tool or CMS (Profiles can reuse login state), read campaign context and past posts, and write drafts into the draft box. |
| GUMem | Optional | Only stores long-term tone preferences the user has explicitly confirmed (for example, a preferred opening style). Brand voice, platform rules, and forbidden phrases are versioned policy — keep them in your policy store and inject them per version; performance data is business state and belongs in your analytics store. Neither is Memory. |
Permission and delegation boundaries
The Agent holds no inherent permissions. The effective authority for each drafting task is the intersection of three sets: what the user actually holds ∩ what was explicitly delegated for this task ∩ what the enterprise has approved. Applied here:
- The delegated scope covers only "read past posts and campaign materials, create and update drafts" — no publishing, commenting, direct messaging, or bulk engagement.
- Delegation credentials are short-lived; minute-level validity is recommended for a single drafting task, with re-delegation after expiry.
- The user or an administrator can revoke the grant at any time; new read or draft requests fail immediately after revocation.
- Out-of-scope attempts (for example, triggering a publish action) are rejected and recorded — the audit chain covers all attempts, not just successful actions.
Note: the SDK example requests product-level scopes (such as webagent.do_anything:read). Fine-grained boundaries — domain lists, page ranges, action whitelists — are enforced by the GenAuth Agent Profile or your policy layer, not by the task prompt; that configuration is not shown on this page. See Delegate token and attenuation for the full semantics.
Workflow
The user selects a campaign and target channels (X, LinkedIn, newsletter, community, and so on).
GenAuth starts an interactive delegation; after the user confirms in Qoni Console, your server-side callback exchanges it for a least-privilege credential.
Your app loads the current version of brand voice, platform rules, and forbidden phrases from the policy store and injects them into the task.
Web Agent opens the social tool or CMS; on first access the user completes sign-in in the controlled session, and later runs can reuse login state through Profiles.
Checkpoint: When a login wall, CAPTCHA, or risk-control page appears, the Web Agent should escalate to a human instead of silently bypassing it.
Web Agent reads campaign materials and past posts, then generates drafts per platform, noting differences in length, tone, and format.
Web Agent writes drafts into the platform draft box or returns them to the app for review, with an audit id attached.
Checkpoint: No draft should ever reach a published state; where the "save draft" and "publish" controls sit side by side, this step should pass a typed gate as a risky action.
Your app parses the draft output and validates channel annotations; the user's accept, edit, and reject decisions are archived to your content database.
Example code
The example below wires this scenario into your backend with the official Qoni SDK (@qoniai/qoni): interactive delegation (with a full callback) → load brand policy from your policy store → one doAnything.run() for the drafting → parse and validate the drafts.
import { Qoni, QoniScopes } from '@qoniai/qoni'
const qoni = new Qoni({
accessKey: process.env.QONI_ACCESS_KEY!,
secretKey: process.env.QONI_SECRET_KEY!,
})
export async function startDraftTask(userId: string, taskId: string) {
// 1. Interactive delegation: sign-in is involved, so the user confirms in Qoni Console
const { data: authorization } = await qoni.delegateToken({
mode: 'interactive',
agent: 'social-media-draft',
scopes: [QoniScopes.DO_ANYTHING_READ, QoniScopes.DO_ANYTHING_MANAGE],
redirectUri: 'https://app.example.com/qoni/callback',
state: taskId,
user: { id: userId },
expiresIn: 900, // minute-level validity for a single drafting task
})
redirectUserTo(authorization.authorizationUrl)
}
// GET /qoni/callback — after the user approves, exchange for the grant
// server-side and continue the task
export async function handleQoniCallback(request: Request) {
const query = new URL(request.url).searchParams
const { data: grant } = await qoni.completeDelegateToken({
grantId: query.get('grantId')!,
code: query.get('code')!,
state: query.get('state')!,
})
const task = await loadDraftTask(query.get('state')!) // your task store
return runDraftTask(grant, task)
}
async function runDraftTask(
grant: { token: string; auditId: string; grantedScopes: string[] },
task: { campaignName: string; channels: string[] },
) {
// 2. Before the run: load the current brand policy from YOUR policy store
// (this is versioned policy, not Memory)
const policy = await loadBrandPolicy() // e.g. { version: '2026-08', voice: ..., platformRules: [...], forbiddenPhrases: [...] }
// 3. One call runs the drafting: sign in, read context, write drafts per platform
const run = await qoni.doAnything.run({
token: grant.token,
prompt: `
Open our social media tool and read the campaign "${task.campaignName}"
plus recent posts for these channels: ${task.channels.join(', ')}.
Draft one post per channel, adapting length, tone and format.
Return drafts as a JSON array of { channel, draft, notes } objects.
Save every draft to the draft box only. Do not publish, comment,
send direct messages, or perform any bulk engagement.
Brand policy (version ${policy.version}):
${JSON.stringify(policy)}
`,
capture: { screenshots: true },
})
const result = await run.wait({
// Login walls / CAPTCHAs / publish confirmations: forward to the user
onInteraction: (interaction) => notifyUserActionRequired(interaction),
})
// 4. Parse and validate the output contract on the app side:
// entries without a channel annotation never reach the deliverable
const drafts = parseDrafts(result.output).filter((d) => d.channel && d.draft)
return {
drafts,
artifacts: result.artifacts, // step screenshots, archived with the drafts
policyVersion: policy.version,
audit: { auditId: grant.auditId, permissionBoundary: grant.grantedScopes },
}
}The output structure is a contract set by the task prompt: here it is an array of { channel, draft, notes }, parsed and validated by parseDrafts on the app side, and any entry missing channel or draft is dropped. The SDK itself returns the generic RunResult (runId, status, output, artifacts, and so on).
Data and memory boundaries
This scenario touches four kinds of data; only the last belongs in GUMem:
- Versioned rules: brand voice, platform rules, forbidden phrases — managed by version in your policy store, referenced by version number in the draft output.
- Business state: drafts, accept/reject outcomes, past post performance data — archived to your content database and analytics store for review and traceability.
- Audit records: the delegation and behavior chain formed by
grantIdandauditId— maintained by GenAuth. - User Memory (optional): only long-term tone preferences distilled from accept, edit, and reject decisions the user has explicitly confirmed — this is where GUMem fits; a one-off drafting run neither recalls nor writes back by default.
Failure handling
| Situation | Recommended handling |
|---|---|
| Login state expires | Suspend the task, notify the user to sign in again, and resume from the checkpoint. |
| A tool redesign breaks draft creation | Treat it as a failure and replay the session recording; without evidence that the write succeeded, it did not succeed. |
| Publish or engagement request outside the delegated scope | Reject and record it; the attempted action remains visible in the audit chain. |
| A draft lacks a channel annotation or contains forbidden phrases | App-side validation drops the draft and the deliverable notes the reason and the policy version applied. |
Production notes
Disable automatic publishing, commenting, direct messaging, and bulk engagement by default. Drafts should require user confirmation. Cap draft-creation and read frequency per platform to avoid triggering platform risk controls; when risk controls or unusual verification appear, escalate to a human rather than attempting a bypass. When brand rules change, publish a new version in the policy store so stale rules never constrain new drafts.
Next steps
- Read the Quickstart to run the shortest path for Agent identity and delegation.
- Read Authorization and browser sandbox for the security boundaries of controlled sessions.
- Continue with the Newsletter curation agent for an adjacent scenario.