A useful skill: every Monday at 9am, an agent looks at the last week's product changes, drafts a "what shipped this week" post, and publishes it. Total runtime: about 20 seconds. Total cost: under a cent of LLM tokens.
What you'll build
An agent that runs on a schedule and:
- Gathers raw input — git log, Linear updates, customer feedback notes — whatever you feed it
- Drafts a blog post in your brand voice
- Tags it correctly and assigns the right category
- Publishes it immediately or schedules it for later
System prompt
You are the weekly content agent for Acme Co. Each Monday you publish a
"What we shipped" post.
Voice rules:
- 2nd-person, warm, no marketing fluff
- Bullets for shipped items, prose for the wrap-up paragraph
- Max 400 words
- Always end with a CTA to the docs
Process:
1. Call blog.list_categories — find the "Product Updates" category.
If it doesn't exist, use null.
2. Call blog.list_tags — find existing tags like "weekly", "shipped".
Reuse them; don't invent variants.
3. Call blog.create_draft with the post body.
4. Call blog.publish_post with no scheduledFor to go live immediately.
Confirmations:
- After publishing, return the post slug and ID to the operator.Implementation (TypeScript + Claude)
import Anthropic from "@anthropic-ai/sdk";
const VLOZI_KEY = process.env.VLOZI_API_KEY!;
const MCP = "https://mcp.vlozi.app";
const anthropic = new Anthropic();
async function callTool(name: string, input: object) {
const r = await fetch(`${MCP}/tools/${name}`, {
method: "POST",
headers: {
"Authorization": `Bearer ${VLOZI_KEY}`,
"content-type": "application/json",
"x-agent-id": "weekly-publisher",
},
body: JSON.stringify(input),
});
return r.json();
}
async function runWeekly(rawInput: string) {
// Fetch only the tools this skill needs — keeps the agent focused
const tools = [
{ name: "blog_list_categories", description: "List categories", input_schema: { type: "object", properties: {} } },
{ name: "blog_list_tags", description: "List tags with usage counts", input_schema: { type: "object", properties: {} } },
{ name: "blog_create_draft", description: "Create a new draft", input_schema: { type: "object", required: ["title"], properties: { title: { type: "string" }, content: { type: "string" }, categoryId: { type: "string" }, tags: { type: "array", items: { type: "string" } } } } },
{ name: "blog_publish_post", description: "Publish a draft now", input_schema: { type: "object", required: ["id"], properties: { id: { type: "string" } } } },
];
const messages: any[] = [
{
role: "user",
content: `Here's this week's raw changelog input. Turn it into a published post.\n\n${rawInput}`,
},
];
while (true) {
const res = await anthropic.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 4096,
system: WEEKLY_PUBLISHER_SYSTEM_PROMPT,
tools,
messages,
});
if (res.stop_reason !== "tool_use") return res.content;
const results: any[] = [];
for (const block of res.content) {
if (block.type === "tool_use") {
const vloziName = block.name.replace("_", ".");
const result = await callTool(vloziName, block.input);
results.push({
type: "tool_result",
tool_use_id: block.id,
content: JSON.stringify(result),
});
}
}
messages.push({ role: "assistant", content: res.content });
messages.push({ role: "user", content: results });
}
}
const WEEKLY_PUBLISHER_SYSTEM_PROMPT = `<paste the system prompt above>`;
// Run from a cron / GitHub Action / Vercel Cron / wherever
const weeklyInput = `
- Shipped MCP Phase 1 (14 tools, blog + brain)
- Fixed 4 typescript inference bugs in the public SDK
- Added .ico favicons to the dashboard
- Customer Ben asked for cross-tenant copy — declined for now
`;
await runWeekly(weeklyInput);Schedule it
Pick whatever runs on a cron:
Vercel Cron
// app/api/cron/weekly-publish/route.ts
import { runWeekly } from "@/lib/agents";
export async function GET() {
const input = await fetchWeeklyChangelog();
await runWeekly(input);
return Response.json({ ok: true });
}// vercel.json
{ "crons": [{ "path": "/api/cron/weekly-publish", "schedule": "0 9 * * 1" }] }GitHub Actions
# .github/workflows/weekly-publish.yml
on:
schedule:
- cron: "0 9 * * 1"
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- run: node scripts/weekly-publish.mjs
env:
VLOZI_API_KEY: ${{ secrets.VLOZI_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}Tips for production
- Dry-run mode: Add a
DRY_RUN=trueenv that swapsblog.publish_postfor a console.log so you can verify before flipping live. - Review window: Instead of publishing immediately, schedule for the next morning (
scheduledFor: ISO 8601) — gives a human window to review and callblog.unschedule_postif something's off. - Idempotency: Skip the run if a post titled
"What we shipped — week of {YYYY-MM-DD}"already exists. Oneblog.search_postscall before drafting. - Failure alerts: If any tool returns an error, send it to Slack / PagerDuty — don't fail silently.