By Sagar Shankaran, Founder of CallSphere
Create an AI writing coach that provides layered feedback on grammar, style, structure, and tone — with actionable revision suggestions and progress tracking across writing sessions.
Key takeaways
Good writing feedback operates at multiple levels simultaneously. A grammar checker catches surface errors but ignores whether the argument is coherent. A structural review ensures logical flow but might miss awkward phrasing. An effective writing coach agent addresses all these layers in a prioritized way — fixing a thesis statement is more important than fixing a comma splice.
The agent provides feedback in four categories, from most impactful to least: Structure (organization and argument flow), Content (clarity of ideas and evidence), Style (voice, tone, and readability), and Mechanics (grammar, spelling, punctuation).
Define structured feedback that organizes suggestions by category and priority:
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
flowchart LR
CALLER(["Student or Parent"])
subgraph TEL["Telephony"]
SIP["Twilio SIP and PSTN"]
end
subgraph BRAIN["Education AI Agent"]
STT["Streaming STT<br/>Deepgram or Whisper"]
NLU{"Intent and<br/>Entity Extraction"}
TOOLS["Tool Calls"]
TTS["Streaming TTS<br/>ElevenLabs or Rime"]
end
subgraph DATA["Live Data Plane"]
CRM[("CRM and Notes")]
CAL[("Calendar and<br/>Schedule")]
KB[("Knowledge Base<br/>and Policies")]
end
subgraph OUT["Outcomes"]
O1(["Enrollment captured"])
O2(["Tour scheduled"])
O3(["Counselor callback"])
end
CALLER --> SIP --> STT --> NLU
NLU -->|Lookup| TOOLS
TOOLS <--> CRM
TOOLS <--> CAL
TOOLS <--> KB
NLU --> TTS --> SIP --> CALLER
NLU -->|Resolved| O1
NLU -->|Schedule| O2
NLU -->|Escalate| O3
style CALLER fill:#f1f5f9,stroke:#64748b,color:#0f172a
style NLU fill:#4f46e5,stroke:#4338ca,color:#fff
style O1 fill:#059669,stroke:#047857,color:#fff
style O2 fill:#0ea5e9,stroke:#0369a1,color:#fff
style O3 fill:#f59e0b,stroke:#d97706,color:#1f2937
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional
class FeedbackCategory(str, Enum):
STRUCTURE = "structure"
CONTENT = "content"
STYLE = "style"
MECHANICS = "mechanics"
class Severity(str, Enum):
CRITICAL = "critical" # Must fix: breaks understanding
IMPORTANT = "important" # Should fix: weakens writing
SUGGESTION = "suggestion" # Could improve: polish
@dataclass
class WritingIssue:
category: FeedbackCategory
severity: Severity
location: str # Paragraph or sentence reference
original_text: str
issue_description: str
suggestion: str
revised_text: Optional[str] = None
rule_name: Optional[str] = None # e.g., "passive_voice"
@dataclass
class WritingAnalysis:
overall_score: float # 0-100
category_scores: dict[str, float] = field(default_factory=dict)
issues: list[WritingIssue] = field(default_factory=list)
strengths: list[str] = field(default_factory=list)
word_count: int = 0
readability_grade: float = 0.0
sentence_variety_score: float = 0.0
@property
def critical_issues(self) -> list[WritingIssue]:
return [i for i in self.issues if i.severity == Severity.CRITICAL]
@property
def issues_by_category(self) -> dict[str, list[WritingIssue]]:
grouped: dict[str, list[WritingIssue]] = {}
for issue in self.issues:
cat = issue.category.value
if cat not in grouped:
grouped[cat] = []
grouped[cat].append(issue)
return grouped
Before the AI agent reviews the writing, compute quantitative metrics that inform the feedback:
import re
def compute_readability_metrics(text: str) -> dict:
"""Compute readability statistics for the text."""
sentences = re.split(r'[.!?]+', text)
sentences = [s.strip() for s in sentences if s.strip()]
words = text.split()
syllable_count = sum(count_syllables(w) for w in words)
num_sentences = len(sentences)
num_words = len(words)
if num_sentences == 0 or num_words == 0:
return {"error": "text too short to analyze"}
# Flesch-Kincaid Grade Level
avg_sentence_length = num_words / num_sentences
avg_syllables_per_word = syllable_count / num_words
fk_grade = (
0.39 * avg_sentence_length
+ 11.8 * avg_syllables_per_word
- 15.59
)
# Sentence length variety (std deviation)
lengths = [len(s.split()) for s in sentences]
mean_length = sum(lengths) / len(lengths)
variance = sum((l - mean_length) ** 2 for l in lengths) / len(lengths)
std_dev = variance ** 0.5
# Paragraph analysis
paragraphs = [p.strip() for p in text.split("\n\n") if p.strip()]
return {
"word_count": num_words,
"sentence_count": num_sentences,
"paragraph_count": len(paragraphs),
"avg_sentence_length": round(avg_sentence_length, 1),
"sentence_length_std": round(std_dev, 1),
"flesch_kincaid_grade": round(fk_grade, 1),
"avg_syllables_per_word": round(avg_syllables_per_word, 2),
}
def count_syllables(word: str) -> int:
"""Rough syllable count using vowel groups."""
word = word.lower().strip(".,!?;:'"")
if not word:
return 0
vowels = "aeiouy"
count = 0
prev_vowel = False
for char in word:
is_vowel = char in vowels
if is_vowel and not prev_vowel:
count += 1
prev_vowel = is_vowel
if word.endswith("e") and count > 1:
count -= 1
return max(1, count)
The writing coach agent operates as a pipeline of specialized reviewers, each focusing on one feedback category:
from agents import Agent, Runner
from pydantic import BaseModel
class StructureFeedback(BaseModel):
thesis_clear: bool
logical_flow: bool
paragraph_transitions: list[str]
organization_issues: list[str]
suggestions: list[str]
structure_reviewer = Agent(
name="Structure Reviewer",
instructions="""Review the writing's organizational structure.
Evaluate:
1. THESIS/MAIN IDEA: Is there a clear central argument or purpose?
If not, suggest where and how to add one.
2. LOGICAL FLOW: Do paragraphs follow a logical progression? Flag
any jumps in logic or missing connections.
3. TRANSITIONS: Are transitions between paragraphs smooth? Identify
abrupt shifts.
4. PARAGRAPH UNITY: Does each paragraph focus on one main idea?
Flag paragraphs that try to cover too much.
5. INTRODUCTION/CONCLUSION: Does the intro set up the argument?
Does the conclusion synthesize rather than merely repeat?
Focus ONLY on structure. Ignore grammar and style issues.""",
output_type=StructureFeedback,
)
style_reviewer = Agent(
name="Style Reviewer",
instructions="""Review the writing's style and voice. Evaluate:
1. ACTIVE vs PASSIVE VOICE: Flag unnecessary passive constructions.
"The ball was thrown by John" -> "John threw the ball"
2. WORDINESS: Identify phrases that can be shortened.
"due to the fact that" -> "because"
3. SENTENCE VARIETY: Flag sections where sentence structure is
monotonous (e.g., five Subject-Verb-Object sentences in a row).
4. TONE CONSISTENCY: Is the tone appropriate and consistent
throughout? Flag shifts.
5. JARGON: Flag technical terms that are not defined for the audience.
Provide specific rewrites, not just general advice.""",
)
Run all reviewers in parallel and merge their feedback into a single prioritized report:
import asyncio
import json
async def full_writing_review(text: str, context: str = "") -> WritingAnalysis:
"""Run all review layers and produce a unified analysis."""
metrics = compute_readability_metrics(text)
prompt = f"Review this writing:\n\n{text}"
if context:
prompt += f"\n\nContext: {context}"
# Run reviewers in parallel
structure_task = Runner.run(structure_reviewer, prompt)
style_task = Runner.run(style_reviewer, prompt)
results = await asyncio.gather(structure_task, style_task)
structure_result = results[0]
style_result = results[1]
analysis = WritingAnalysis(
overall_score=0.0,
word_count=metrics["word_count"],
readability_grade=metrics["flesch_kincaid_grade"],
sentence_variety_score=metrics["sentence_length_std"],
)
# Merge feedback from all reviewers and score
# (In production, parse structured outputs into WritingIssue objects)
analysis.overall_score = calculate_composite_score(
metrics, structure_result, style_result
)
return analysis
Instead of just pointing out problems, the agent generates concrete revision options:
Still reading? Stop comparing — try CallSphere live.
CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.
revision_agent = Agent(
name="Revision Suggester",
instructions="""Given a piece of writing and identified issues,
generate specific revision suggestions. For each issue:
1. Quote the exact original text
2. Explain what is wrong and why it matters
3. Provide 2-3 alternative phrasings ranked by quality
4. Explain why the top suggestion is best
Never rewrite the entire piece. Focus on targeted improvements
that the writer can learn from. The goal is to teach the writer
to self-edit, not to edit for them.
Format each suggestion clearly so the writer can accept or reject
individual changes.""",
)
async def get_revision_suggestions(
text: str, issues: list[WritingIssue]
) -> str:
issue_summary = json.dumps([
{
"category": i.category.value,
"location": i.location,
"description": i.issue_description,
"original": i.original_text,
}
for i in issues[:10] # Limit to top 10 issues
])
result = await Runner.run(
revision_agent,
f"Writing:\n{text}\n\nIssues to address:\n{issue_summary}",
)
return result.final_output
The severity classification (critical, important, suggestion) creates a natural triage. The agent presents critical issues first — things like unclear thesis, broken logic flow, or sentences that are genuinely confusing. Style suggestions and minor mechanics come last. For first drafts, the agent might limit feedback to structure and content only, deferring style and mechanics to later revision rounds.
Yes. The context parameter passed to the review pipeline changes the evaluation criteria. Academic writing needs formal tone, citation support, and hedged claims. Business writing prioritizes brevity and clear action items. Creative writing tolerates rule-breaking for effect. The agent's system prompt includes context-specific rules so "Use active voice" becomes a firm rule in business writing but a suggestion in creative writing.
Store each WritingAnalysis result with a timestamp and compare category scores over time. A student who consistently improves their structure score from 60 to 80 but plateaus on style at 55 would see the agent shift its coaching emphasis toward style. Trend visualization and session-over-session diffs help the student see concrete progress.
#WritingCoach #GrammarAnalysis #AIFeedback #Python #EducationAI #AgenticAI #LearnAI #AIEngineering

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
Step-by-step build of a working agent with the OpenAI Agents SDK — Agent class, tools, handoffs, tracing — plus an eval pipeline that catches regressions before merge.
Smolagents lets agents write Python instead of JSON. Why code-as-action reduces tool errors and where the security trade-offs are for production deployments.
Modal turns a Python function into autoscaling serverless compute with optional GPU. Deploy a LiveKit Agent with one command and get pay-per-second billing.
Pydantic AI's April release tightens the typed-agent loop and adds structured tool definitions. Why type-safe agents reduce production bugs and speed iteration.
Index a knowledge base with text-embedding-3-large into ChromaDB, expose a retrieve tool to your voice agent, and ground every answer in real documents — full Python tutorial.
Shrink an AI voice agent image from 950MB to 80MB with a Python 3.13 multi-stage build, uv for deps, and gcr.io/distroless/python3 nonroot. Real Dockerfile + benchmarks.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco
Watch how CallSphere handles real customer calls, schedules appointments, and processes payments — live.