Review an essay against a rubric, one criterion at a time

A single prompt grades the whole essay at once and its scores drift. This workflow reads the rubric, scores each criterion on its own with quotes, re-checks every score against the essay and the points allowed, and only then writes the feedback.

5 steps in 4 stages · 5 subagents · a kit of 10 files

Free. Built in your browser; nothing is uploaded. No API key, no account.

How it runs

Steps in the same stage start at the same time. Each subagent gets only what its incoming edges carry, and a step in a fresh context never sees the reasoning of the steps before it. A gate decides whether the work passes, and a failed gate re-runs the work it checks.

  1. Stage 12 in parallel
    • Read the rubric · Planner

      sonnet · WebFetch

    • Line edits · Worker

      sonnet · WebFetch

  2. Stage 2
    • Score each criterion · Worker

      sonnet · WebFetch · once per item of rubric.criteria

  3. Stage 3
    • Check the scores · Checker

      opus · WebFetch · fresh context

      Gate: Every score quotes the essay, stays within its criterion's points, and no two scores contradict each other.

  4. Stage 4
    • Feedback · Writer

      sonnet · WebFetch

Use it when

  • You are a student checking your essay before you hand it in.
  • You are a teacher who wants consistent first-pass feedback.
  • You want feedback tied to quotes, not impressions.

Change these first

  • Paste the rubric exactly as given.
  • Say whether you want line edits or only scores and comments.

Need a workflow built for your team? Fixed-price packages

What you get

An orchestrating SKILL.md, one Claude Code subagent per step with its own tools and model, and the same skill as a Cursor rule. Your agent runs it with its own subagents; nothing on this site calls a model.

essay-rubric-review-skill-kit.zip10 files
  • .claude/skills/essay-rubric-review/SKILL.mdorchestrator
  • .claude/skills/essay-rubric-review/graph.skill.mdgraph source
  • .claude/agents/essay-rubric-review-rubric.mdsubagent
  • .claude/agents/essay-rubric-review-score-criterion.mdsubagent
  • .claude/agents/essay-rubric-review-line-edits.mdsubagent
  • .claude/agents/essay-rubric-review-score-check.mdsubagent
  • .skill-graph/.gitignoresupport
  • .cursor/rules/essay-rubric-review.mdcCursor rule
  • README.mdreadme
  • + 1 more subagents
.claude/skills/essay-rubric-review/SKILL.md
---
name: essay-rubric-review
description: "Reviews an essay against a rubric, one criterion at a time, checks every score against quotes from the essay, then writes the feedback."
---

# essay-rubric-review

Reviews an essay against a rubric, one criterion at a time, checks every score against quotes from the essay, then writes the feedback.

This skill is a graph: each step has its own prompt and context, and only the payloads named below move between steps.

## Plan

Run the stages in order. Every model step runs in its own subagent; do not do a step's work yourself. Only command steps and approvals are yours to run. Steps in the same stage do not depend on each other: start them together, in one message with several subagent calls.

The input (`essay`) is the user's goal: their request, word for word, handed on under `### essay`. Ask for it if the user has not given one.

### Stage 1 · 2 steps in parallel

- **READ THE RUBRIC** (`rubric`) → subagent `essay-rubric-review-rubric`
  - Hand it: the user's goal.
  - Expect: `criteria_list` (json).
  - Contract check (`criteria_list`): save its output to `.skill-graph/essay-rubric-review/rubric.json`, then run `node -e "const fs=require('fs');const d=JSON.parse(fs.readFileSync(process.argv[1],'utf8'));const k=['criteria'];const ok=d!==null&&typeof d==='object'&&Array.isArray(d)===false;const m=ok?k.filter((x)=>Object.hasOwn(d,x)===false):k;if(m.length>0){console.error('Missing keys: '+m.join(', '));process.exit(1)}" .skill-graph/essay-rubric-review/rubric.json` with Bash. Exit code 0 passes.
  - If a contract check fails: re-run this step once with its previous answer and the error output. If it fails again, stop and show the user the output.
- **LINE EDITS** (`line_edits`) → subagent `essay-rubric-review-line-edits`
  - Hand it: the user's goal.
  - Expect: `edits` (markdown).

### Stage 2 · 1 step

- **SCORE EACH CRITERION** (`score_criterion`) → subagent `essay-rubric-review-score-criterion` · one run per item, up to 4 at a time
  - Take the list: the `criteria` array in the JSON of READ THE RUBRIC (`rubric`). If it is not an array, stop and report. If it is empty, skip this step: its output is `[]`.
  - If the list has more than 12 items, stop before starting any run and tell the user how many there are.
  - Start one run of the subagent per item, up to 4 at a time (several subagent calls in one message); start the next as one finishes.
  - Hand each run: the user's goal; its item: one entry of `criteria` from the JSON of READ THE RUBRIC (`rubric`).
  - Expect, per run: the result for its item. Checks and gates below apply to each run on its own; a retry re-runs only the runs that failed.
  - Output: a JSON array of the runs' results, in item order. That array is what later steps get.

### Stage 3 · 1 step


… 37 more lines in the kit

Run it in a Claude chat

Add Skill Graph to Claude as a connector, then ask. Claude runs each step and the connector checks every answer before the next one starts.

Set up the connector

Ask Claude

Use Skill Graph to run the essay-rubric-review workflow on: Here is my essay and the rubric my teacher gave: …

The graph file

The whole workflow is one Markdown file, graph.skill.md: the steps and edges in the frontmatter, one prompt per step below it. The guide explains every field.

graph.skill.md · 121 lines
---
name: essay-rubric-review
description: Reviews an essay against a rubric, one criterion at a time, checks every score against quotes from the essay, then writes the feedback.
spec: skill-graph/1
topology:
  nodes:
    - id: essay
      type: input
      label: ESSAY AND RUBRIC
    - id: rubric
      type: orchestrator
      label: READ THE RUBRIC
      model: sonnet
      tools: [WebFetch]
      output_format: json
    - id: score_criterion
      type: worker
      label: SCORE EACH CRITERION
      model: sonnet
      tools: [WebFetch]
      foreach: rubric.criteria
      max_items: 12
    - id: line_edits
      type: worker
      label: LINE EDITS
      model: sonnet
      tools: [WebFetch]
    - id: score_check
      type: verifier
      label: CHECK THE SCORES
      isolated_context: true
      model: opus
      tools: [WebFetch]
      gate: Every score quotes the essay, stays within its criterion's points, and no two scores contradict each other.
      on_fail: retry
      max_retries: 1
    - id: feedback
      type: synthesizer
      label: FEEDBACK
      model: sonnet
      tools: [WebFetch]
  edges:
    - from: essay
      to: [rubric, score_criterion, line_edits, score_check]
    - from: rubric
      to: score_criterion
      contract: criteria_list
    - from: score_criterion
      to: score_check
      contract: criterion_scores
    - from: line_edits
      to: score_check
      contract: edits
    - from: score_check
      to: feedback
      contract: checked_review
contracts:
  criteria_list:
    format: json
    description: "{ criteria: one entry per rubric criterion, each with name, what good looks like and the points it is worth }"
    required: [criteria]
  criterion_scores:
    format: json
    description: One entry per criterion, in rubric order; each has the score, the quotes behind it and one fix.
  edits:
    format: markdown
    description: A numbered list of line edits, each quoting the sentence and giving the better version.
  checked_review:
    format: markdown
    description: The checked scores with their quotes and fixes, the line edits worth keeping, and what was corrected.
---

# Node Prompts & Edge Contracts

## @node: rubric
Read the essay and rubric below. If no rubric is given, use a standard one for this kind of essay: thesis, structure, evidence, style and mechanics.

{{essay}}

Return JSON: {"criteria": [{"name": "...", "good": "what full marks look like", "points": 10}]}, one entry per criterion, in the rubric's order.

## @node: score_criterion
Score the essay on this one criterion only:

{{item}}

The essay (with its rubric):
{{essay}}

Return a JSON object: {"criterion": the name, "score": a number within its points, "quotes": two or three short quotes from the essay that justify the score, "fix": the one change that would raise the score most}.

## @node: line_edits
Read this essay and pick the ten sentences that would gain most from an edit: unclear, wordy, or wrong.

{{essay}}

Return a numbered list. For each one quote the sentence, give a better version, and say why in a few words.

## @node: score_check
You are a second marker. You did not write these scores; check them as if a stranger did.

The essay and rubric:
{{essay}}

The scores, one per criterion:
{{score_criterion}}

The line edits:
{{line_edits}}

Check that every quote is really in the essay, that each score fits its quotes and its criterion, and that the line edits do not change the meaning. Correct what is wrong.

Return the checked scores with their quotes and fixes, the line edits worth keeping, and a short list of what you corrected.

## @node: feedback
Write the feedback for the essay's author from this checked review:

{{score_check}}

Return Markdown: the total score and a one-line verdict, a table of criteria with score and the fix for each, the three changes that matter most, then the line edits.