Our system can now understand the topic, gather research, find supporting examples, identify weaknesses, structure everything into a content brief, and generate multiple hook options.
But hooks alone aren't enough. We still need complete LinkedIn posts that the user can actually choose between.
That's why we built the Draft Agent.
Unlike the previous agents, the Draft Agent doesn't work with raw research. Instead, it takes the Content Brief and the five generated hooks, then turns them into two complete LinkedIn post drafts.
Content Brief
│
├───────────┐
│ │
▼ ▼
Hook Agent 5 Hooks
│ │
└────┬────┘
▼
Draft Agent
│
▼
Draft A + Draft B
Instead of generating a single post, I wanted to generate two completely different writing styles. One optimized for fast consumption on LinkedIn, and another that tells a stronger story.
Draft A: Short, punchy and highly skimmable.
Draft B: Narrative driven with a beginning, middle and end.
Before writing any code, let's define the system prompt.
I want the Draft Agent to choose the best hook from the five options, use it as the opening line for both drafts, and make sure both posts stay faithful to the content brief while having completely different writing styles.
SYSTEM_PROMPT = """
Generate exactly 2 LinkedIn drafts.
Draft A:
- Punchy
- Short paragraphs
- Mobile friendly
Draft B:
- Narrative
- Story driven
- Lesson at the end
Use the same hook for both drafts.
Return valid JSON.
"""
The production prompt is much longer, but I've shortened it here to highlight the core rules.
Unlike the Hook Agent, this agent combines two different inputs. The structured brief and all five generated hooks are passed together so the model has both the planning context and multiple opening options.
brief_dict = brief.model_dump()
user_message = (
"\n".join(
f"{key}:{value}"
for key, value in brief_dict.items()
)
+ "\n\nHOOKS:\n"
+ "\n---\n".join(hooks)
)
Just like the previous agents, we retry generation up to three times if the model returns malformed JSON.
for attempt in range(max_retries):
Since downstream agents expect exactly two drafts, we validate the response before returning it.
if len(parsed) == 2:
return parsed
Before writing the function, let's break down what happens.
The function takes the ContentBrief and five generated hooks as input. It converts the brief into a dictionary, builds the user message by appending the hooks, sends everything to the LLM, strips markdown fences, parses the JSON response, and validates that exactly two drafts were returned.
def generate_drafts(hooks: list[str], brief: ContentBrief, max_retries = 3) -> list[str]:
brief_dict = brief.model_dump()
user_message = (
"\n".join([f"{key}:{value}" for key, value in brief_dict.items()])
+ "\n\nHOOKS:\n"
+ "\n---\n".join(hooks)
)
for attempt in range(max_retries):
raw_response = call_llm(SYSTEM_PROMPT, user_message)
refined = strip_json_fences(raw_response)
try:
parsed = json.loads(refined)
if len(parsed) == 2:
return parsed
except json.JSONDecodeError:
print(f"Draft attempt {attempt + 1} failed, retrying...")
return []
At this point, our system can understand a topic, gather research, find supporting examples, critique weak arguments, structure everything into a content brief, generate multiple hooks, and finally generate two complete LinkedIn post drafts.
But generating multiple drafts introduces another question.
Which one is actually better?
In the next article, we'll build the Writing Critic Agent, which scores both drafts on originality, promise fulfilment, and shareability before recommending the stronger one.
Github Repo: https://github.com/Manav-N4/linkedin-agent#linkedin-agent
Top comments (0)