Promote: Source to Platform-Native Social Drafts
WorkflowTurns an existing article into grounded, platform-native social drafts for each network requested, polished by a fix/audit loop until all pass, then delivered to files or inline. Chain after content.
Usage
echo "<your request>" | octomind workflow promote Reads your request from stdin. Add --dry-run to validate and print the plan without
running any steps.
Pipeline
-
Prepare a work brief for a social-post writer from the request below. You resolve the source; the writer only writes. FIRST, resolve the source: if the request gives a file path, read it now to confirm it's readable and…
-
If the brief below is a single `SOURCE-UNREACHABLE:` line, output that exact line and nothing else — do not research, do not proceed. Otherwise, for EACH platform tagged in the brief, research what is genuinely current …
-
If the brief below is a single `SOURCE-UNREACHABLE:` line, output that exact line and nothing else — write no drafts. Otherwise write one draft per platform tagged in the brief, following each platform's rulebook (the `…
- 4 polish Loop
- fix content:editor
If the drafts below are a single `SOURCE-UNREACHABLE:` line, output that exact line and nothing else. Otherwise surgically edit every platform block below per its audit findings — no diagnosis phase, no scope confirmati…
- audit content:audit
If the drafts below are a single `SOURCE-UNREACHABLE:` line, end your whole response with exactly `ALL-PASS` (nothing to audit; let the run finish and report the unreachable source). Nothing else. Otherwise, FIRST run t…
-
If the final drafts below are a single `SOURCE-UNREACHABLE:` line, write nothing to disk — report to the user that the source could not be read (quote the line) and that no drafts were produced. Stop there. Otherwise yo…
Definition
# Title: Promote: Source to Platform-Native Social Drafts
#
# Public workflow: turn an existing, trusted piece of content (a file path, a
# URL, or pasted text) into grounded, platform-native social drafts for every
# network requested in the input — parsed at runtime, no hardcoded platform
# list.
#
# Role split (a writer should receive a prepared brief, not do research):
# - `content:draft` RESOLVES the source into a brief: a file source is
# verified readable and referenced by path (every later step reads it from
# disk — the article never round-trips through the conversation); pasted or
# fetched text is captured verbatim once. It won't fabricate a source it
# couldn't read.
# - `assistant:researcher` RESEARCHES trends — what's genuinely live on each
# platform for the posting date, citation-minded by identity.
# - `content:social` WRITES the drafts from that brief, one per platform,
# following the platform rulebooks.
# - `content:editor` fixes per audit findings; `content:audit` scores against
# the shared rubric (incl. the Phoenix-style reach verdict for X).
# - `content:social` DELIVERS — writes files or returns inline, verbatim.
#
# Grounding contract: every fact, number, and claim in a draft must trace to
# the source (the file on disk, or the captured pasted/fetched text); every
# trend angle must trace to something the
# pulse step actually found. If the source can't be read, the run HALTS
# (SOURCE-UNREACHABLE) rather than inventing grounding from the wider web.
#
# One turn covers ALL platforms (as N platform-tagged blocks) per step:
# `loop` cannot fan out per item, and a convergence loop that re-checks its
# own work matters more than parallel speed. Platform rulebooks live in the
# `social-<platform>` skills; the audit rubric in `content-audit`.
#
# Note: `max_iterations = 3` is a ceiling, not a target. `retries = 4` guards
# transient provider/network errors.
#
# Source paths: a file path or pasted text is the reliable source — the brief
# step reads those directly. A URL works only when the runtime gives the brief
# step a web-fetch tool; with search-only web access it may not resolve a
# specific URL and the run halts SOURCE-UNREACHABLE rather than guessing. The
# natural chain is `content` (writes the article to a file) -> `promote` (reads
# that file), so file-path is the common, dependable input.
#
# Input shape: name the platforms and the source; optionally destinations,
# posting timing, audience/brand notes. Example:
# Promote as an X thread and a LinkedIn post:
# ./blog/my-article.md
#
# Public roles only.
name = "promote"
description = "Turns an existing article into grounded, platform-native social drafts for each network requested, polished by a fix/audit loop until all pass, then delivered to files or inline. Chain after content."
# ── 1. Brief — resolve the source (read file / use pasted text) once ────────
# Role note: content:draft carries filesystem-read + websearch, so it reads a
# file source directly and structures raw input into a brief — its native job.
# It has no webfetch, so a URL source that can't be reached halts cleanly
# (SOURCE-UNREACHABLE) rather than being guessed at; file and pasted text are
# the dependable inputs (and the content->promote chain passes a file).
[[steps]]
name = "brief"
role = "content:draft"
session = "fresh"
retries = 4
prompt = """
Prepare a work brief for a social-post writer from the request below. You
resolve the source; the writer only writes.
FIRST, resolve the source: if the request gives a file path, read it now to
confirm it's readable and on-topic — later steps read that file themselves,
so do NOT reproduce its content here; pasted text, use it as-is; a URL, read
it if you have a fetch tool.
HARD STOP: if the source can't be read — missing/unreadable file, or a URL you
have no tool to fetch — do NOT substitute web-search results, the site's other
pages, or general knowledge. Output exactly this one line and nothing else:
`SOURCE-UNREACHABLE: <url-or-path> — <reason>`. A promote run with no readable
source must halt, never fabricate grounding from elsewhere.
If the source read cleanly, output the brief with ONLY:
1. Every requested platform, one XML tag per target using the platform code as
the tag name, empty, each on its own line — `<x-post></x-post>`,
`<x-thread></x-thread>`, `<linkedin></linkedin>`, `<reddit></reddit>`,
`<hackernews></hackernews>`, `<threads></threads>`, `<bluesky></bluesky>`,
`<mastodon></mastodon>`. Exactly what the request asks for, nothing assumed.
2. `<source>` — the file path verbatim when the source is a file (the ground
truth every later step reads from disk); otherwise the full pasted or
fetched source content, verbatim, inside `<source_text>`.
3. The source's key extractable specifics — concrete numbers, named tools,
direct quotes, dated facts — listed so the writer reaches for them (these
are what make a post land; a vague post wastes a strong source).
4. Posting timing: the date/time the request states, otherwise {{DATE}}.
5. Audience, brand, or voice notes the request gives, verbatim.
EXCLUDE any destination file paths or delivery instructions — the working
steps must not see destinations; delivery reads the original request. No
meta-commentary.
<request>
{{input}}
</request>
"""
# ── 2. Pulse — researcher finds what's genuinely live per platform ──────────
[[steps]]
name = "pulse"
role = "assistant:researcher"
session = "fresh"
retries = 4
prompt = """
If the brief below is a single `SOURCE-UNREACHABLE:` line, output that exact
line and nothing else — do not research, do not proceed.
Otherwise, for EACH platform tagged in the brief, research what is genuinely
current on/around that platform as of the brief's posting date: live
discussions, recent news in the source topic's space, active angles or
formats. Use websearch.
Honesty contract: report ONLY signals you actually found, each with a source.
If nothing relevant is genuinely trending for a platform, say so and recommend
the evergreen angle — a real "no strong signal" beats an invented trend, which
is a critical defect.
<brief>
{{brief}}
</brief>
Output per platform: 1–3 candidate angles connecting the source topic to what
you verified is live (or the evergreen recommendation), each with its evidence
in one line. No meta-commentary.
"""
# ── 3. Draft — writer produces one grounded block per platform ──────────────
[[steps]]
name = "draft"
role = "content:social"
session = "fresh"
retries = 4
prompt = """
If the brief below is a single `SOURCE-UNREACHABLE:` line, output that exact
line and nothing else — write no drafts.
Otherwise write one draft per platform tagged in the brief, following each
platform's rulebook (the `social-<platform>` skills) for shape, length, hooks,
and conventions — a post is not a thread is not a comment; match the exact
requested format. Reach for the brief's listed specifics (real numbers, named
tools, quotes) — a concrete post beats a generic one.
Grounding: every fact, number, name, and claim comes from the source — read
the file the brief's <source> names, or use its <source_text>. Pick each
platform's angle from the pulse findings when a verified signal exists,
otherwise the evergreen angle — never invent a trend, a stat, or a detail the
source doesn't contain. Write like a person posting, not a brand broadcasting
(the content-voice rules apply).
Completeness contract: output exactly one block for EVERY platform tagged in
the brief — before ending, count your blocks against the brief's platform
list.
This step produces conversation output only — do not write or modify any file.
<brief>
{{brief}}
</brief>
<pulse>
{{pulse}}
</pulse>
Wrap each draft in its platform's own XML tag as the delimiter, e.g.
`<x-post>` on its own line, the draft, then `</x-post>` on its own line. The
blocks are the ENTIRE output — no length notes, posting tips, angle labels, or
any other text before, between, or after them. Fit each draft to its
platform's own length sweet spot (per the `social-<platform>` skill — e.g.
LinkedIn's dwell-optimal band, not a thin post).
"""
# ── 4. Fix <-> audit loop — every platform together, until all pass ─────────
[[steps]]
name = "polish"
loop = true
max_iterations = 3
exit_when = { output = "audit", matches = '(?m)^ALL-PASS' }
[[steps.run]]
name = "fix"
role = "content:editor"
session = "continue"
retries = 4
prompt = """
If the drafts below are a single `SOURCE-UNREACHABLE:` line, output that exact
line and nothing else.
Otherwise surgically edit every platform block below per its audit findings —
no diagnosis phase, no scope confirmation; the findings ARE the confirmed
scope, and this step always outputs the edited drafts, never a report.
THIS round (no auditor findings exist yet): diagnose and fix in one pass,
self-directed, against each platform's rulebook (`social-<platform>` skills),
the `content-voice` rules, and `content-humanize` patterns — hook first,
slop-shape, dwell risk, format-fits-venue, and length within the platform's
own sweet spot.
LATER rounds: you receive the auditor's findings directly — resolve every one,
criticals first, without reintroducing anything already fixed. A platform the
auditor reported no findings against gets output unchanged.
Grounding: read the source (the file the brief's <source> names, or its
<source_text>); never add a fact, number, or claim absent from it, and never
keep one it doesn't support. A repeated product or brand
name stays verbatim every time — only style words get varied.
Completeness contract: your final output MUST contain exactly one block for
EVERY platform in the brief — finishing after only some is a broken response;
count your blocks against the brief's platform list before ending.
This step produces conversation output only — do not write or modify any file.
<brief>
{{brief}}
</brief>
<drafts>
{{draft}}
</drafts>
Wrap each draft in its platform's own XML tag as the delimiter (e.g.
`<x-post>` … `</x-post>`). No meta-commentary before, between, or after.
"""
[[steps.run]]
name = "audit"
role = "content:audit"
session = "fresh"
retries = 4
prompt = """
If the drafts below are a single `SOURCE-UNREACHABLE:` line, end your whole
response with exactly `ALL-PASS` (nothing to audit; let the run finish and
report the unreachable source). Nothing else.
Otherwise, FIRST run the completeness check: every platform tagged in the
brief must have its own block in the drafts. A brief platform with no block
gets an automatic `<code>: FAIL (missing draft)` — no quality audit rescues an
absent draft.
For each platform present: audit its block against the `content-audit` rubric
with that platform's weights and rulebook (`social-<platform>` skills) —
include the Phoenix-style verdict for X surfaces. Verify grounding against the
source — read the file the brief's <source> names, or use its <source_text>:
any fact, number, or claim the source doesn't support is a Critical finding. List findings severity-ordered with quotes and fix
direction, then close each platform's section with exactly one line —
`<code>: PASS` or `<code>: FAIL`. PASS requires zero Critical or Moderate
findings — a platform with a Moderate finding still listed is a FAIL, because
a PASS ends the loop and whatever you listed would never get applied. Minor
polish-level findings alone don't block a PASS.
Then derive the final line MECHANICALLY against the BRIEF's platform list: if
and only if EVERY platform in the brief has a verdict line saying PASS, end
the WHOLE response with exactly `ALL-PASS`; otherwise end with exactly
`CONTINUE`. Nothing after that line. This step is read-only — report, never
edit files.
<brief>
{{brief}}
</brief>
<all_drafts>
{{fix}}
</all_drafts>
"""
# ── 5. Deliver — files if destinations named, else inline ───────────────────
[[steps]]
name = "deliver"
role = "content:social"
session = "fresh"
retries = 4
prompt = """
If the final drafts below are a single `SOURCE-UNREACHABLE:` line, write
nothing to disk — report to the user that the source could not be read (quote
the line) and that no drafts were produced. Stop there.
Otherwise you are a writer placing finished work, not an editor: the `<final>`
drafts are the complete, audited deliverables — put each where it belongs
verbatim, no editorial changes of your own.
<final>
{{fix}}
</final>
<final_audit>
{{audit}}
</final_audit>
For each platform: if the original request below named a destination file for
it, write that platform's final draft there — the file's new content IS the
final block, whole. No staging, temporary, backup, or suffixed copies; remove
any temp file you needed mid-process. If no destination applies, deliver that
platform's draft inline — never invent a path, and never post to any platform
yourself.
<original_request>
{{input}}
</original_request>
After handling every platform, give a brief per-platform summary: the angle
used and its evidence (only claim a trend the pulse actually verified), the
final audit verdict, and where the draft landed — file path or "inline".
"""