Agent Skills now belongs to the Linux Foundation, not to one AI company. Here's what that changes, where each agent looks for skills, and a complete writing-quality skill you can drop into Claude Code, Codex and Gemini CLI today.
TL;DR: On October 22, 2026, Anthropic donated the Agent Skills standard to the Agentic AI Foundation under the Linux Foundation. The SKILL.md format didn't change, which is the point: the same skill folder works across Claude Code, OpenAI Codex, Gemini CLI and a long list of other agents. In this post I build a writing-quality skill with a dependency-free Python checker, test it on real drafts, and show the folder layout that makes all three agents find it.
Table of Contents
- What Changed on October 22
- Where Each Agent Looks for Skills
- The Skill We're Building
- Writing the SKILL.md
- The Checker Script
- Testing It on Real Drafts
- One Folder, Three Agents
- Vendor-Specific Extras (and When to Skip Them)
- Going Further: Ready-Made Writing Skills
- Conclusion
- FAQs
1. What Changed on October 22
Anthropic introduced Agent Skills as an open standard in December 2025. On October 22, 2026, it donated the standard to the Agentic AI Foundation (AAIF), the Linux Foundation project that also hosts MCP. The donation covers the name, the logo, the GitHub organization and the agentskills.io domain.
What that means in practice:
- Shared maintainers. Engineers from Anthropic, Google, OpenAI and other companies maintain the spec as individuals.
- A real process. The proposed roadmap includes versioned releases, a formal proposal process and security guidance for people who write and install skills.
- No format change. A skill is still a folder with a
SKILL.md.nameanddescriptionare still the only required fields. Your existing skills keep working.
Anthropic says more than 70 agent platforms have integrated the format, with about 1 million skills published and about 280 million installs. Those are vendor-reported numbers, but the practical takeaway is clear: if you write a skill once against the spec, you're no longer writing it for one company's agent.
2. Where Each Agent Looks for Skills
The format is shared. The folders are not, at least not completely. Here's where each of the three agents in this post discovers skills, taken from each vendor's own docs:
| Agent | Project skills | Personal skills | How it activates |
|---|---|---|---|
| Claude Code | .claude/skills/<name>/ | ~/.claude/skills/<name>/ | Automatically when the description matches, or /skill-name |
| OpenAI Codex | .agents/skills/<name>/ (from your working directory up to the repo root) | ~/.agents/skills/<name>/ | Automatically, or $skill-name / /skills |
| Gemini CLI | .gemini/skills/<name>/ or the .agents/skills/ alias | ~/.gemini/skills/ or ~/.agents/skills/ | Calls activate_skill after you approve a consent prompt |
Sources: Claude Code skills docs, Codex skills docs, Gemini CLI skills docs.
The good news: .agents/skills/ is already shared by Codex and Gemini CLI. Claude Code reads .claude/skills/. One symlink covers the gap, which I'll show in section 7.
All three load skills the same way under the hood. At startup they read only each skill's name and description (about 100 tokens). The full SKILL.md is loaded only when a task matches, and bundled files like scripts are read only when the instructions point to them. That progressive loading is why a good description matters more than anything else in the file.
3. The Skill We're Building
Writing quality is a good fit for a skill because it needs two things a plain prompt handles badly:
- Measurement. "Make this better" gets you a rewrite of everything. A script that measures specific problems gets you targeted edits.
- A repeatable loop. Check, fix only what's flagged, check again.
The skill is called writing-quality. It runs a local checker on a draft and reports eight things:
- Sentence variety (standard deviation of sentence length)
- Long sentences (over 35 words)
- Readability (Flesch Reading Ease)
- Passive voice share
- Filler phrases ("it is important to note that", "in order to")
- Overused AI-style wording ("delve", "tapestry", "in today's fast-paced")
- Repeated sentence openers
- Em dash density
The script uses only the Python standard library, so it runs anywhere an agent can run Python. No API key, no network, no pip install.
Here's the folder:
writing-quality/ ├── SKILL.md ├── references/ │ └── CHECKS.md └── scripts/ └── check_writing.py 4. Writing the SKILL.md
--- name: writing-quality description: Reviews and tightens prose drafts (blog posts, docs, newsletters, emails, essays) for clarity, rhythm and filler. Runs a local checker that measures sentence-length variety, readability, passive voice, filler phrases, overused AI-style wording and repeated openers, then fixes the flagged spots without changing meaning. Use when the user asks to proofread, polish, edit, tighten or improve the writing quality of a draft, or asks why a draft sounds robotic or generic. license: MIT compatibility: Requires Python 3.9+ (standard library only). No network access needed. metadata: author: mohab-abdelkarim version: "1.0.0" --- # Writing quality Use this skill to review a prose draft and improve it with evidence, not guesswork. ## Workflow 1. Save the draft to a file if it only exists in the conversation (for example `draft.md`). 2. Run the checker that ships with this skill: `python3 <skill-folder>/scripts/check_writing.py draft.md`. The script path is relative to this SKILL.md file. Add `--json` for machine-readable output, or `--strict` to exit with code 1 when any check fails (useful in CI). 3. Read the report. Each check prints PASS or WARN with the exact sentences or phrases involved. 4. Fix only what the report flags: - Split or vary sentences listed under "Longest sentences" when the length check warns. - Rewrite sentences listed under "Passive voice" into active voice where the actor matters. - Delete or replace every phrase listed under "Filler phrases" and "Overused AI-style wording". - Reword repeated sentence openers so consecutive sentences start differently. 5. Re-run the checker on the edited draft and repeat until every check passes or the remaining warnings are deliberate. 6. Show the user a short summary: which checks failed before, what you changed, and the final report. ## Rules - Never change facts, numbers, names, quotes or the author's claims. This skill edits style, not substance. - Keep the author's voice. Prefer the smallest edit that clears a warning. - Do not add new sections, headings or content the author didn't write. - If a warning is intentional (for example a long sentence in a legal clause), leave it and say why. ## Reference See [references/CHECKS.md](references/CHECKS.md) for what each check measures, its threshold, and how to fix it. A few choices worth explaining:
- The description does the routing. It says what the skill does and lists the phrases a user might actually type ("proofread", "tighten", "sounds robotic"). Agents match tasks against this text, so vague descriptions mean skills that never trigger.
- Only spec fields in the frontmatter.
name,description,license,compatibilityandmetadataare all part of the open spec, so nothing here is Claude-only or Codex-only. - Rules that protect meaning. The biggest risk with an editing skill is an agent that "improves" facts. The rules section forbids that explicitly.
- Details live in a reference file.
references/CHECKS.mdholds the thresholds and fixes, and the agent reads it only when it needs to. That keeps the main file short, which the spec recommends (under 500 lines and roughly 5,000 tokens).
Here's the reference file:
# Checks and thresholds | Check | What it measures | Warns when | How to fix | |---|---|---|---| | Sentence variety | Standard deviation of sentence length (words) | Below 5 words | Mix short sentences with longer ones; split the longest sentences | | Long sentences | Sentences over 35 words | Any found | Split at a conjunction or move a clause into its own sentence | | Readability | Flesch Reading Ease (approximate syllable count) | Below 50 | Use shorter words and sentences | | Passive voice | "be" verb followed by a past participle (heuristic) | Over 15% of sentences | Name the actor and make it the subject | | Filler phrases | Phrases like "it is important to note that", "in order to" | Any found | Delete them or use the short form ("to") | | AI-style wording | Overused words and phrases common in generated text ("delve", "tapestry", "in today's fast-paced world") | Any found | Replace with a plain, specific word | | Repeated openers | The same first word in 3+ consecutive sentences | Any run found | Vary how the sentences start | | Em dashes | Count of em dashes | More than 2 per 500 words | Use commas, colons, parentheses or a new sentence | All checks are heuristics. They point at places worth a second look; they do not prove anything about who wrote the text. 5. The Checker Script
Save this as scripts/check_writing.py:
#!/usr/bin/env python3 """Writing-quality checker for the writing-quality Agent Skill. Standard library only. Usage: python3 check_writing.py draft.md [--json] [--strict] cat draft.md | python3 check_writing.py - [--json] """ import argparse import json import re import statistics import sys FILLER = [ "it is important to note that", "it's important to note that", "it is worth noting that", "it's worth noting that", "in order to", "the fact that", "at the end of the day", "needless to say", "for all intents and purposes", "due to the fact that", "in today's world", "when it comes to", "a wide range of", "a variety of", "it goes without saying", "plays a crucial role", "plays an essential role", "plays a vital role", ] AI_STYLE = [ "delve", "delves", "delving", "tapestry", "testament to", "in today's fast-paced", "in the ever-evolving", "ever-evolving landscape", "navigate the complexities", "unlock the potential", "unlock the power", "game-changer", "game changer", "seamlessly", "seamless", "leverage", "harness the power", "elevate your", "embark on", "a myriad of", "robust", "pivotal", "realm", "foster", "furthermore", "moreover", "in conclusion", "additionally", ] BE_VERBS = r"(?:am|is|are|was|were|be|been|being)" IRREGULAR_PP = ( "built|done|given|made|seen|shown|taken|written|known|found|kept|left|held|brought|bought|" "taught|thought|told|sold|sent|spent|paid|put|set|run|won|chosen|driven|eaten|fallen|forgotten|" "gotten|hidden|ridden|risen|spoken|stolen|sworn|torn|worn|broken|frozen" ) PASSIVE_RE = re.compile(rf"\b{BE_VERBS}\s+(?:\w+ly\s+)?(?:\w+ed|{IRREGULAR_PP})\b", re.I) def strip_markdown(text): text = re.sub(r"``` .*? ```", " ", text, flags=re.S) # code blocks text = re.sub(r"`[^`]*`", " ", text) # inline code text = re.sub(r"!\[[^\]]*\]\([^)]*\)", " ", text) # images text = re.sub(r"\[([^\]]*)\]\([^)]*\)", r"\1", text) # links -> text text = re.sub(r"^\s{0,3}#{1,6}\s+.*$", " ", text, flags=re.M) # headings text = re.sub(r"^\s*[-*+]\s+", "", text, flags=re.M) # bullets text = re.sub(r"[*_]{1,3}", "", text) # emphasis return text def split_sentences(text): text = re.sub(r"\s+", " ", text).strip() parts = re.split(r"(?<=[.!?])[\"')\]]*\s+(?=[A-Z0-9\"'(])", text) return [p.strip() for p in parts if len(p.split()) >= 3] def syllables(word): word = re.sub(r"[^a-z]", "", word.lower()) if not word: return 0 groups = re.findall(r"[aeiouy]+", word) count = len(groups) if word.endswith("e") and not word.endswith(("le", "ee")) and count > 1: count -= 1 return max(count, 1) def find_phrases(text, phrases): found = {} low = text.lower().replace("\u2019", "'") # curly apostrophe for p in phrases: n = len(re.findall(rf"\b{re.escape(p)}\b", low)) if n: found[p] = n return found def analyze(raw): text = strip_markdown(raw) sentences = split_sentences(text) words = re.findall(r"[A-Za-z0-9']+", text) if not sentences or not words: raise ValueError("No prose found. Pass a file with at least a few full sentences.") lengths = [len(s.split()) for s in sentences] syl = sum(syllables(w) for w in words) flesch = 206.835 - 1.015 * (len(words) / len(sentences)) - 84.6 * (syl / len(words)) passive = [s for s in sentences if PASSIVE_RE.search(s)] openers = [re.sub(r"[^a-z]", "", s.split()[0].lower()) for s in sentences] runs, i = [], 0 while i < len(openers): j = i while j + 1 < len(openers) and openers[j + 1] == openers[i] and openers[i]: j += 1 if j - i + 1 >= 3: runs.append({"word": openers[i], "count": j - i + 1}) i = j + 1 em = raw.count("\u2014") # em dash em_limit = max(2, round(2 * len(words) / 500)) stdev = statistics.pstdev(lengths) if len(lengths) > 1 else 0.0 long_sentences = [s for s, n in zip(sentences, lengths) if n > 35] filler = find_phrases(text, FILLER) ai_style = find_phrases(text, AI_STYLE) passive_share = len(passive) / len(sentences) checks = [ {"check": "Sentence variety", "value": round(stdev, 1), "pass": stdev >= 5, "detail": f"std dev {stdev:.1f} words (want 5+), average {statistics.mean(lengths):.1f} words"}, {"check": "Long sentences", "value": len(long_sentences), "pass": not long_sentences, "items": long_sentences[:5]}, {"check": "Readability", "value": round(flesch, 1), "pass": flesch >= 50, "detail": f"Flesch Reading Ease {flesch:.1f} (want 50+; higher is easier)"}, {"check": "Passive voice", "value": round(passive_share * 100), "pass": passive_share <= 0.15, "detail": f"{len(passive)} of {len(sentences)} sentences ({passive_share:.0%}, want 15% or less)", "items": passive[:5]}, {"check": "Filler phrases", "value": sum(filler.values()), "pass": not filler, "items": filler}, {"check": "AI-style wording", "value": sum(ai_style.values()), "pass": not ai_style, "items": ai_style}, {"check": "Repeated openers", "value": len(runs), "pass": not runs, "items": runs}, {"check": "Em dashes", "value": em, "pass": em <= em_limit, "detail": f"{em} found (limit {em_limit} for this length)"}, ] longest = sorted(zip(lengths, sentences), reverse=True)[:3] return { "words": len(words), "sentences": len(sentences), "passed": sum(c["pass"] for c in checks), "total": len(checks), "checks": checks, "longest_sentences": [{"words": n, "sentence": s} for n, s in longest], } def shorten(text, limit): if len(text) <= limit: return text return text[:limit].rsplit(" ", 1)[0] + " ..." def print_report(r): print(f"Writing-quality report: {r['words']} words, {r['sentences']} sentences") print(f"Passed {r['passed']} of {r['total']} checks\n") for c in r["checks"]: status = "PASS" if c["pass"] else "WARN" line = f"[{status}] {c['check']}" if c.get("detail"): line += f": {c['detail']}" elif not c["pass"]: line += f": {c['value']} found" print(line) if not c["pass"] and c.get("items"): items = c["items"] if isinstance(items, dict): for k, v in items.items(): print(f" - \"{k}\" x{v}") else: for it in items: text = it if isinstance(it, str) else f"{it['count']} sentences in a row start with \"{it['word']}\"" print(f" - {shorten(text, 160)}") print("\nLongest sentences:") for s in r["longest_sentences"]: print(f" ({s['words']} words) {shorten(s['sentence'], 140)}") def main(): ap = argparse.ArgumentParser(description="Check a prose draft for clarity, rhythm and filler.") ap.add_argument("path", help="Markdown or text file, or - for stdin") ap.add_argument("--json", action="store_true", help="print JSON instead of a text report") ap.add_argument("--strict", action="store_true", help="exit 1 if any check fails") a = ap.parse_args() try: raw = sys.stdin.read() if a.path == "-" else open(a.path, encoding="utf-8").read() report =Source:https://dev.to/mohab_karim/agent-skills-went-vendor-neutral-building-a-writing-quality-skill-for-claude-codex-and-gemini-31ieRelated posts
Phân tích cơ hội tham dự của tuyển nữ Việt Nam khi World Cup nữ 2031 mở rộng lên 48 đội
How to Check Your SASSA SRD R370 Grant Status
Google Intelligent Eyewear opens pre-orders from $395, due in stores December 10

