<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
  <title>Da Yan — Blog</title>
  <link>https://870603.xyz/blog/</link>
  <description>Notes on CALL, feedback, and language data science.</description>
  <language>en</language>
  <lastBuildDate>Wed, 26 Aug 2026 10:15:48 GMT</lastBuildDate>
  <atom:link href="https://870603.xyz/feed.xml" rel="self" type="application/rss+xml"/>
  <item>
    <title>Teaching My Agent to Find Papers: Installing the paper-search MCP and Building a Skill</title>
    <link>https://870603.xyz/blog/teaching-my-agent-to-find-papers-installing-the-paper-search-mcp-and-building-a-skill.html</link>
    <guid isPermaLink="true">https://870603.xyz/blog/teaching-my-agent-to-find-papers-installing-the-paper-search-mcp-and-building-a-skill.html</guid>
    <pubDate>Wed, 26 Aug 2026 10:15:48 GMT</pubDate>
    <description>How I installed the paper-search MCP with uv, worked around a proxy-induced 429 storm, and turned the hard-won know-how into a reusable agent skill.</description>
    <category>AI</category>
    <category>agent</category>
    <category>research</category>
    <category>skills</category>
    <content:encoded><![CDATA[<p>This week a reviewer asked for literature from the past five years to support a revision. Not one or two papers — enough to break a single-source citation habit and anchor every claim in independent 2024–2026 evidence. Searching manually across a dozen platforms would have taken an afternoon and produced a mess of tabs. So I went looking for a tool, found an academic search aggregator that ships as an MCP server, installed it with <code>uv</code>, and then did what I now do with any tool that works: taught my agent how to use it by writing a <code>SKILL.md</code>.</p>
<p>This post is the record of that process — the install, the walls I hit, and the skill that came out of it.</p>
<h2>Why an aggregator</h2>
<p>Academic search APIs are scattered: Semantic Scholar, OpenAlex, PubMed, CORE, DOAJ, Crossref, arXiv, Europe PMC — each with its own URL scheme, rate limits, and metadata quirks. A query that works on one returns nothing on the next. What I wanted was a single CLI that fans a query out across many sources in parallel, merges and deduplicates the results, and returns clean JSON with title, authors, abstract, DOI, and citation counts.</p>
<p>The tool I found is <code>paper-search-mcp</code> — a Python package that aggregates <strong>20+ academic platforms</strong>. It installs two entry points: a <code>paper-search</code> CLI for interactive searches, and a <code>paper-search-mcp</code> MCP server so the same engine can be wired into any MCP-capable agent.</p>
<h2>Installing with uv</h2>
<p>Installation was a single command:</p>
<pre><code class="language-bash">uv tool install paper-search-mcp
</code></pre>
<p><code>uv</code> dropped the binary into <code>~/.local/bin/paper-search</code> and kept the package in its content-addressable cache. That is the whole install story — but the interesting part, as always, is what happens after the command succeeds.</p>
<h2>Wall #1: the proxy eats the requests</h2>
<p>The first search hung. Then it returned nothing. Then — once I watched the stderr — I understood: my Windows machine runs a system proxy at <code>127.0.0.1:8888</code>, and Python <code>requests</code> was dutifully routing every API call through it. The upstream academic APIs saw a flood of identical requests and answered with <strong>429 rate-limit errors</strong>.</p>
<p>The fix is one environment variable, now burned into the skill so it is never forgotten:</p>
<pre><code class="language-bash">NO_PROXY=&#39;*&#39; paper-search search &quot;metacognition&quot; -n 5 -s crossref
</code></pre>
<p>Every single invocation needs it. Not having it costs you silent empty results — the worst kind of failure, because it looks like the tool is broken.</p>
<h2>Wall #2: the slowest source sets the pace</h2>
<p>The search command gathers from all requested sources <strong>in parallel, and waits for the slowest one</strong>. That design is invisible until you add the wrong source. <code>arxiv</code> over a direct mainland connection timed out at ~80 seconds and returned zero papers — dragging an otherwise five-second search into a two-minute wait for nothing.</p>
<p>The skill now carries a source table with measured behavior. The default combo — <code>core,doaj,semantic,openalex,pubmed,crossref</code> — completes in about 5.8 seconds with all six sources returning. Slow or unverified sources are explicitly listed as &quot;don&#39;t put this in the default combo&quot;, and <code>arxiv</code> gets its own warning.</p>
<h2>Wall #3: Windows consoles and GBK</h2>
<p>Two encoding/parsing traps bit me while scripting the output. First, piping JSON through <code>python -c</code> on a GBK console blew up with <code>UnicodeEncodeError</code> until I set <code>PYTHONIOENCODING=utf-8</code>. Second, the JSON schema is not fully uniform across sources — the <code>extra</code> field is a dict for some sources and a plain string for others — so a naive <code>.get()</code> on it crashes mid-parse and silently drops the remaining results. Both fixes are in the skill as a checklist item.</p>
<h2>API keys: one <code>.env</code> to rule them all</h2>
<p>Most sources have free tiers that require an API key: Semantic Scholar, CORE, DOAJ, and — surprisingly — OpenAIRE. The package reads a single environment file:</p>
<pre><code>~/.config/paper-search-mcp/.env
</code></pre>
<p>with <code>PAPER_SEARCH_MCP_</code>-prefixed keys (<code>PAPER_SEARCH_MCP_OPENAIRE_API_KEY</code>, etc.). Once I configured that file, the keys started working for the CLI <em>and</em> the MCP server without any code changes.</p>
<h2>Two sources that surprised me</h2>
<p><strong>OpenAIRE</strong> was listed in the skill as &quot;unverified, probably needs a key&quot;. Today I tested it properly: with the key configured it returns rich metadata — open-access status, language, publisher — and the full seven-source combo (the six above plus <code>openaire</code>) finishes in about ten seconds with zero errors. It earned a promotion to the verified row of the skill&#39;s source table.</p>
<p><strong>Unpaywall</strong> was the opposite kind of surprise. It is <em>not a search engine at all</em> — the official API only resolves a DOI to its open-access locations. Keyword searches against it return zero papers, always. But that makes it perfect for the narrow job no other source does well: after a search hits a paywalled paper, <code>paper-search search &quot;&lt;DOI&gt;&quot; -s unpaywall</code> hands you the free full-text link. The skill now flags it as &quot;special: DOI-resolver only&quot; so nobody wastes a round-trip on it.</p>
<h2>Writing the skill</h2>
<p>The <code>SKILL.md</code> under <code>~/.pi/agent/skills/paper-search/</code> is the part my agent actually reads. It encodes all of the above as operational guidance:</p>
<blockquote>
<p><strong>All <code>paper-search</code> commands must be prefixed with <code>NO_PROXY=&#39;*&#39;</code></strong> — the local system proxy causes upstream 429 rate limits; without it searches hang or return empty.</p>
</blockquote>
<blockquote>
<p><strong>Source selection decides success</strong> — search gathers in parallel and waits for the slowest source; default combo <code>core,doaj,semantic,openalex,pubmed,crossref</code> measured at ~5.8s; never add <code>arxiv</code> to the default combo; <code>openaire</code> verified usable with API key; <code>unpaywall</code> is DOI-resolution only.</p>
</blockquote>
<blockquote>
<p>Use <code>PYTHONIOENCODING=utf-8</code> when piping output; the <code>extra</code> field may be a string on some sources — handle both.</p>
</blockquote>
<p>The workflow section tells the agent how to behave in conversation: show results as a table of title / year / source / citations / DOI, let the user pick, download or read only the chosen papers, and report file paths instead of pasting full texts back into the chat.</p>
<h2>What it unlocked</h2>
<p>The revision that started all this is now backed by roughly 44 candidate references gathered across two search rounds, with every DOI re-verified against Crossref before it entered the manuscript&#39;s reference list — exactly the discipline the revision skill demands. The five-year evidence base stopped being a promise and became a table.</p>
<p>The pattern here is the one I keep repeating with my agent: <strong>find a tool that works, hit the walls, then write the walls down.</strong> A skill is not a manual for the happy path — it is a map of the traps, so the next run through them is instant. Installing the tool took one command. Learning its traps took an afternoon. Writing them down took an hour. The agent will never spend that afternoon again.</p>
]]></content:encoded>
  </item>
  <item>
    <title>Teaching My Agent to Blog: Building the blog-writing Skill</title>
    <link>https://870603.xyz/blog/teaching-my-agent-to-blog-building-the-blog-writing-skill.html</link>
    <guid isPermaLink="true">https://870603.xyz/blog/teaching-my-agent-to-blog-building-the-blog-writing-skill.html</guid>
    <pubDate>Tue, 25 Aug 2026 17:50:36 GMT</pubDate>
    <description>How I turned my blog publishing workflow into a reusable agent skill — from front matter conventions to one-command build-and-deploy.</description>
    <category>AI</category>
    <category>agent</category>
    <category>skills</category>
    <category>blogging</category>
    <content:encoded><![CDATA[<p>Today I did something a bit meta: I spent the morning building an agent skill whose entire purpose is to help me write and publish the very post you are reading right now.</p>
<p>Modern coding agents (like <a href="https://github.com/badlogic/pi-mono">pi</a>) support a concept borrowed from Anthropic&#39;s Agent Skills standard: <strong>skills</strong> are self-contained capability packages — a directory with a <code>SKILL.md</code> file that contains instructions, conventions, and scripts. At startup, the agent only sees each skill&#39;s name and description; when a task matches, it loads the full instructions on demand. It&#39;s progressive disclosure for procedures.</p>
<p>So I packaged my entire blogging workflow into one. The result is <code>blog-writing</code>, and this post is its first real test.</p>
<h2>Why package a workflow at all?</h2>
<p>Before the skill, every blog session meant re-explaining myself:</p>
<ul>
<li>Where the site repository lives on disk</li>
<li>What front matter fields my static site generator expects</li>
<li>How file naming affects URLs</li>
<li>The exact build → verify → commit → push sequence that deploys via GitHub Actions</li>
</ul>
<p>Each of these is small, but together they were friction — and worse, they were <em>unwritten knowledge</em> that I had to repeat from memory, risking inconsistency. A skill turns tribal knowledge into an executable checklist the agent follows mechanically.</p>
<h2>Anatomy of the skill</h2>
<p>The whole thing is a single Markdown file. The most important part is the front matter — the <code>description</code> is what the agent uses to decide when to load the skill:</p>
<pre><code class="language-markdown">---
name: blog-writing
description: 为 Da Yan 个人主页网站（GitHub 仓库 fabea87/site）撰写、修改和发布博客文章。当用户想写博客、发布新文章或修改现有博客时使用；通过脚手架脚本创建文章、撰写 Markdown 正文并一键构建 + git 推送部署到 GitHub Pages。
---
</code></pre>
<p>(Yes, the description is in Chinese — it matches how I actually talk to the agent.)</p>
<p>Then come the fixed paths. Since the skill should work from any working directory, everything is an absolute path to the site repo:</p>
<pre><code class="language-markdown">## 固定路径（任何文件夹下都直接用这些绝对路径）

- 仓库根目录：`C:/Users/fabea/OneDrive/24 new homepage`
- 博客源文件：`C:/Users/fabea/OneDrive/24 new homepage/blog/*.md`
- 脚手架脚本：`C:/Users/fabea/OneDrive/24 new homepage/new_post.py`
- 构建脚本：`C:/Users/fabea/OneDrive/24 new homepage/build_site.py`（输出到其 `public/` 子目录）
</code></pre>
<p>The heart of the skill is the front matter contract my site generator depends on:</p>
<pre><code class="language-yaml">---
title: 文章标题          # 必需
date: YYYY-MM-DD HH:MM:SS # 必需，决定列表排序；缺省回退到文件 mtime
summary: 一句话简介       # 可选，显示在列表页
tags: 标签1, 标签2        # 可选，逗号分隔（中英文逗号均可）
---
</code></pre>
<p>Encoding this as a comment-annotated YAML block means the agent never has to guess which fields are required versus optional.</p>
<h2>The four-step pipeline</h2>
<p>The skill defines a semi-automated writing flow. Step 1 scaffolds a new post with a helper script that fills in the title and timestamp automatically — including the date-and-time prefix in the filename so multiple posts on one day never collide:</p>
<pre><code class="language-bash">python &quot;C:/Users/fabea/OneDrive/24 new homepage/new_post.py&quot; &quot;标题&quot; -s &quot;简介&quot; -t &quot;标签1,标签2&quot;
</code></pre>
<p>Step 2 is the only human-heavy part: I write the actual prose (the agent drafts, I edit). Steps 3 and 4 collapse publishing into a one-liner plus verification rules:</p>
<pre><code class="language-bash">cd &quot;C:/Users/fabea/OneDrive/24 new homepage&quot; &amp;&amp; python build_site.py &amp;&amp; git add -A &amp;&amp; git commit -m &quot;blog: &lt;文章标题&gt;&quot; &amp;&amp; git push
</code></pre>
<p>The skill even pins down conventions I used to keep in my head — like the commit message format (<code>blog: &lt;title&gt;</code> for new posts, <code>blog: update &lt;title&gt;</code> for edits) and the pre-push checks (build must succeed, the generated <code>public/blog/index.html</code> must contain the new title).</p>
<h2>What makes a good skill</h2>
<p>Building this taught me a few things about skill design:</p>
<ol>
<li><strong>The description is everything.</strong> It&#39;s the only text always in context; it determines whether the agent reaches for your skill at all. Write it like a routing rule: what the skill does <em>and when to use it</em>.</li>
<li><strong>Absolute paths beat assumptions.</strong> Skills live in a config directory but operate on project directories. Stating every path explicitly removes an entire class of failure.</li>
<li><strong>Encode judgment, not just commands.</strong> The valuable parts aren&#39;t the shell one-liners — those I could always retype. It&#39;s the decision points: when to ask the user for a summary vs. omit it, what to verify before pushing, how to handle updating old posts without touching their sort date.</li>
<li><strong>Validate against the spec.</strong> My first version was missing required front matter (<code>name</code>, <code>description</code>), which pi warns about and refuses to load. One edit fixed it.</li>
</ol>
<h2>The meta moment</h2>
<p>This post itself went through the full pipeline: scaffold → draft → build → verify → push. The skill loaded, ran the script, embedded these snippets correctly, and will deploy through GitHub Actions in about a minute.</p>
<p>There&#39;s something satisfying about a tool documenting its own first use. The next time I want to publish — maybe from a completely different folder, maybe six months from now — I won&#39;t need to remember any of this. The skill remembers for me.</p>
]]></content:encoded>
  </item>
  <item>
    <title>Teaching an AI to Revise Papers the Way I Do</title>
    <link>https://870603.xyz/blog/paper-revision-skill.html</link>
    <guid isPermaLink="true">https://870603.xyz/blog/paper-revision-skill.html</guid>
    <pubDate>Fri, 21 Aug 2026 20:00:00 GMT</pubDate>
    <description>How I turned five rounds of peer-review revision habits into a reusable SKILL.md for my AI assistant.</description>
    <category>AI</category>
    <category>academic writing</category>
    <category>peer review</category>
    <content:encoded><![CDATA[<p>After five full rounds of peer review — three at <em>Computer Assisted Language Learning</em> and two at <em>System</em> — I realized my revision habits had quietly become a method: the same four standards behind every edit, the same structure in every response letter, the same discipline about what <em>not</em> to change. So I wrote the method down as a <code>SKILL.md</code> file and installed it as a reusable skill for my AI assistant. Whenever a revision task starts, the assistant loads this file first.</p>
<p>This post introduces what the skill does, with excerpts, and the ideas behind it.</p>
<h2>The philosophy: calibrate, don&#39;t generalize</h2>
<p>Most AI writing help is generic, because most prompts are generic. &quot;Improve the academic tone&quot; tells the model nothing about <em>your</em> academic tone. The core idea of the skill is <strong>calibration</strong>: its operational definitions are anchored to two of my own published papers — Yan and Gao (2025) in <em>Computer Assisted Language Learning</em> and Yan et al. (2026) in <em>System</em> — used as calibration samples, and its response-letter style is benchmarked against all five of my real review rounds.</p>
<blockquote>
<p>The goal is not &quot;good academic English&quot; in the abstract, but <em>the way this author writes</em>, already tested by peer review.</p>
</blockquote>
<p>Two more beliefs shaped the design:</p>
<ol>
<li><strong>Tacit knowledge does not scale.</strong> What lives only in a researcher&#39;s intuition cannot be delegated, taught, or audited. Turning it into checklists makes it all three.</li>
<li><strong>Never let the model invent.</strong> Every research question must be derivable from theory and prior evidence; every borrowed citation is verified against the literature before it enters the manuscript. When evidence is missing, the skill says so explicitly rather than guessing.</li>
</ol>
<h2>The four standards</h2>
<p>Every edit must satisfy four standards, each with an operational definition and a checklist. Adapted excerpts:</p>
<blockquote>
<p><strong>Academic English conventions</strong> — formal register with systematic hedging; strict terminology management: define terms at first use, keep abbreviations consistent, operationalize core constructs before using them; explicit connectives; APA style throughout.</p>
</blockquote>
<blockquote>
<p><strong>Logical soundness</strong> — a macro &quot;funnel&quot; introduction that narrows step by step (background → construct → theoretical grounding → empirical evidence → gap → research questions); mirrored sections, so every RQ gets matching Results and Discussion subsections; and micro paragraph loops: topic sentence → evidence → interpretation, with explicit transitions between paragraphs.</p>
</blockquote>
<blockquote>
<p><strong>Problem consciousness</strong> — every RQ must be backed by theoretical grounding <em>and</em> empirical evidence, with an explicit gap statement (&quot;no studies have yet examined…&quot;). Questions without literature support are forbidden; if a reviewer proposes one, check the literature first — otherwise it goes to limitations and future directions.</p>
</blockquote>
<blockquote>
<p><strong>Directness and clarity</strong> — conclusion-first paragraphs, short subject–verb distance, statistics tucked into parentheses, parallel structures for contrasts, no redundancy and no rare words. (Reviewers once flagged <em>praxis-theory nexus</em> and <em>lacunae</em>; the checklist now bans such vocabulary outright.)</p>
</blockquote>
<p>A few checklist items show how concrete this gets:</p>
<ul>
<li>Tense consistency: past tense for methods, present tense for universal claims</li>
<li>No unsupported claims: conclusions must carry their evidence</li>
<li>Every statistic reported with df / F / p and an effect size</li>
<li>One edit may ripple across the paper — adding a discussion paragraph means re-checking whether the introduction sets it up</li>
</ul>
<h2>Two working modes</h2>
<p><strong>Mode 1 — reviewer-driven revision.</strong> Read the whole manuscript first (never just the flagged passages), register every comment, and classify each one: substantive objection, supplementary suggestion, surface edit, or commentary. Then respond by strategy — accept, partially accept, or decline with reasons — and execute every edit under the four standards.</p>
<p><strong>Mode 2 — autonomous revision, review first.</strong> Before touching the text, the assistant simulates peer review against the same standards, producing numbered comments that cite specific passages, split into Major and Minor:</p>
<blockquote>
<p>Run several sub-agents in parallel as simulated reviewers — one prioritizing theory, one methods, one language — to obtain a comment set closer to real review.</p>
</blockquote>
<p>Only then does revision begin, answering simulated comments exactly as real ones would be answered.</p>
<h2>The response letter is a genre</h2>
<p>Response letters have their own rhetoric, and the skill encodes mine: every reply opens with thanks; conclusions come before details; revised locations are named precisely (<em>Sections 4.1–4.2</em>, <em>Table 1</em>, <em>P. 8</em>); complex issues are organized as First / Second / Third. The most valuable pattern is the graceful refusal:</p>
<blockquote>
<p>Thank the reviewer and acknowledge the value of the suggestion → state concretely why it was not adopted (space limits, beyond the current scope, better suited to a follow-up study) → convert the concern into a limitation → thank again.</p>
</blockquote>
<p>Refusals can even cite theory: one rejection was justified with Nicol and Macfarlane-Dick&#39;s feedback model to explain the intended role of GenAI in the design. Tone stays professional, respectful, and humble — but with a spine.</p>
<h2>Discipline</h2>
<p>The skill ends where good craft always ends — with constraints:</p>
<blockquote>
<p>Make only the changes that are necessary; do not rewrite what the author did not ask to change. Never fabricate or alter data, statistics, or references. After editing, cross-check terminology, numbering, figures, citations, and numbers across the whole manuscript.</p>
</blockquote>
<p>If you have your own revision habits — and after a few review cycles you will — consider writing them down. A method you can hand to a machine is a method you truly understand.</p>
]]></content:encoded>
  </item>
</channel>
</rss>
