Anatomy of a working LinkedIn Writer agent.
This is the architecture behind the demo in Chapter 6. Every box is a real call. Every arrow is real bytes over a real wire.
Topic → Guideline → Research → Writer.
Each stage's output is persisted and editable. The user can tweak the guideline before research starts, or edit the research before the writer runs. Determinism flows from human control points, not from praying to the model.
The four files that shape every run.
guideline.mdThe dynamic, per-task contract. Generated by the Guideline Agent, edited by the human, consumed by the Writer.
research.mdStreaming output from the Research Agent. Sourced facts, quotes, links, with citations. Editable before writer starts.
profiles/paul.mdStatic writing profiles (structure, terminology, character). The Writer picks one; the Evaluator scores against all three.
few-shot/01.md5-10 archetypal good posts. Loaded into the Writer's prompt as user/assistant pairs. The cheapest quality multiplier.
Client ↔ SSE ↔ orchestrator.
EventSource ↔ Audit Log
SSE · text/event-stream
// Server emits typed events; client renders them live.
event: status data: { stage: "research", message: "Searching..." }
event: chunk data: { stage: "research", text: "..." }
event: tool_call data: { name: "web_search", args: {...} }
event: tool_result data: { name: "web_search", result: "..." }
event: review data: { iteration: 1, issues: [...] }
event: done data: { post: "...", iterations: 2 }Tools the research agent uses.
web_search()Query the web for fresh sources. Returns title, URL, snippet.
fetch_page()Scrape a URL. Returns cleaned markdown content.
fetch_transcript()Pull a YouTube transcript by video ID.
synthesize()Internal LLM call. Compresses N raw results into the running research.md.
cite()Mark a claim with a numbered source. Builds the references block.
done()Signal that research is sufficient. Hands off to Writer.
The evaluator-optimizer in detail.
type ReviewIssue = {
profile: "structure" | "terminology" | "character";
location: string; // e.g. "paragraph 2, sentence 3"
comment: string;
severity: "low" | "medium" | "high";
};
type Review = { iteration: number; issues: ReviewIssue[] };