NRT Agentic AI Michigan State University

HANDBOOK · DEMOS 10–13 AS ONE SYSTEM

The research system

The four demo guides argue why the system is shaped the way it is. This page is the manual: every file, what it does, how to set it up from nothing, how the pieces connect, and — the part the guides skip entirely — how to keep it running once the demo is over.

All demos Demo 10 Demo 11 Demo 12 Demo 13

This handbook covers the earlier research demos (previously 10–13) — the synthetic Obsidian vault, the Zotero bibliography, the join between them, and the funding radar. They all still work and nothing here has moved.

The new demo 10 is a different, narrower package: papers, citations and a Word document with live Zotero fields, built on your library rather than a synthetic one. It carries its own setup guide and does not need this handbook. Paper writing with Zotero →

Contents

  1. What the four parts are
  2. Prerequisites, and how to check them
  3. Where to put everything
  4. The files that matter
  5. How information moves
  6. First run, step by step
  7. Daily: a paper arrives
  8. Weekly: the two routines
  9. Monthly: drift and decisions
  10. Adding each kind of new data
  11. What goes wrong over time
  12. Troubleshooting

1. What the four parts are

Four packages. They were built to be run in order and they share one body of literature on purpose, so each can teach its own lesson instead of re-explaining the papers.

DemoThe thingThe question it answers
10An Obsidian vault Are the notes right? Documents arrive, notes get written. What stops a note from being wrong?
11A Zotero library Can you cite what you have? Three views of one library that disagree in five places.
12Both, side by side Do they still agree? The join between them, and what ten months of ordinary use does to it.
13A funding radar Is this worth an afternoon? An agent layer over a pipeline you already run.

You can run any one on its own. If you only do one, do Demo 10 — it is the foundation and the other three assume its vocabulary.

The single idea underneath all four

Three ways to control what an agent does, in increasing order of force. Every demo is an application of this and nothing else:

MechanismWhere it livesHow strong
ContextCLAUDE.md A request. Usually obeyed. Nothing enforces it. Good for conventions, judgement, taste.
Permission.claude/settings.json A refusal. Checked before the tool runs, client-side. The agent cannot argue with it.
Capabilitya subagent’s tools: list Absence. The tool is not there. Nothing to refuse, nothing to work around.

When something in your own setup goes wrong, the useful question is always which of these three should have caught it? — and the answer is usually the one you did not write.

2. Prerequisites, and how to check them

Run all four checks before unzipping anything. Each one tells you what a pass looks like and what to do about a fail.

Claude Code

claude --version
Pass: a version number, e.g. 2.1.x.
Fail: command not found → you have not finished Week 3 setup. Field Guide Part 8, slides 62–65, or the Setup section on this site.

pdftotext — needed by all four

pdftotext -v
Pass: pdftotext version 24.x (it prints to stderr, which is normal).
Fail: macOS brew install poppler · Ubuntu/Debian sudo apt install poppler-utils · Windows: install inside WSL.
Demo 10 survives without it — Claude Code reads short PDFs natively, up to 20 pages per call. Demos 11–13 will be slow and expensive without it, because they search inside documents repeatedly.

git — this is not optional

git --version
Pass: any version from the last decade.

You will git init inside every one of these folders before letting an agent near them. It is the single most useful line in all four packages and it costs nothing.

Python 3 — Demo 13 only

python3 --version
Pass: 3.9 or later. No packages needed — the exporter uses only the standard library.

Obsidian and Zotero — both optional

Neither is required for any demo. This surprises people, so it is worth being blunt about:

Install them when you want to use this on your own material. For the demos, they are scenery.

3. Where to put everything

Make one parent folder and unzip all four inside it. Keeping them together matters for Demo 12, which uses a library and a vault side by side.

mkdir -p ~/research-system
cd ~/research-system

unzip ~/Downloads/Demo10_obsidian_research_vault.zip
unzip ~/Downloads/Demo11_zotero_bibliography.zip
unzip ~/Downloads/Demo12_zotero_obsidian_claude.zip
unzip ~/Downloads/Demo13_funding_radar.zip

ls
You should see four directories:
Demo10_obsidian_research_vault
Demo11_zotero_bibliography
Demo12_zotero_obsidian_claude
Demo13_funding_radar

Each package has the same shape, which is worth learning once:

Demo1X_name/ ├── README.md # what this is and why. read first. ├── SETUP.md # step by step. the prompts to paste. ├── MAINTENANCE.md # how to live with it afterwards. ├── ANSWER_KEY.md # every planted defect. SPOILER — read last. └── <workdir>/ # vault/ or library/ or workspace/ or radar/ ├── CLAUDE.md # the contract — what you want the agent to do ├── .claude/ │ ├── settings.json # what the agent CANNOT do. enforced. │ └── agents/*.md # the subagents └── ... # the actual material
Open ANSWER_KEY.md last. It lists every planted defect. Reading it first turns the exercise into a reading comprehension test, which is not the point.

4. The files that matter

Each package has 25–45 files. Six of them do the work. Everything else is material for the agent to chew on.

Throughout, these labels mean:

YOU EDIT  yours to change, and the point of the exercise is changing it    GENERATED  written by a script or an agent; safe to delete and remake    NEVER TOUCH  the system of record; a deny rule stops the agent and should stop you too

The six that matter, in every package

FileWhat it actually does
CLAUDE.mdYOU EDIT Loaded into context automatically every time you run claude in that folder. It is how you say "notes go in papers/", "read past the abstract", "never state a number that is not in the source". It is a request, not a rule — the agent usually obeys and sometimes does not, and noticing when it does not is half of what these demos teach.
.claude/settings.jsonYOU EDIT The deny list. Checked before a tool runs, so no amount of prompting gets around it. This is what protects .obsidian/, your Zotero library, your own synthesis notes, and a pipeline database. Deny beats ask beats allow; first match wins; an allow cannot carve an exception out of a deny.
.claude/agents/*.mdYOU EDIT Subagents. Frontmatter sets name, description (which is what decides when it gets used), tools (an allowlist — omit it and the agent inherits everything), and model. The prose below the frontmatter is its brief.
_templates/*.mdYOU EDIT The shape of the output. Changing a template changes every note written afterwards, which is the cheapest lever in the whole system.
inbox/YOU EDIT Where new material lands. This is the folder you will actually use day to day. Drop a PDF in, run a prompt.
the system of recordNEVER TOUCH pdfs/, zotero/, pipeline/radar.db, .obsidian/. Different in each demo; always denied; always for the same reason — a record an agent can edit is no longer a record.

Demo 10 — the vault, annotated

vault/ ├── CLAUDE.md YOU EDIT the note contract + the growth rules ├── .claude/settings.json YOU EDIT denies .obsidian/, pdfs/, topics/, rm ├── .claude/agents/ │ ├── librarian.md intake. document → note. has Write. │ ├── skeptic.md cross-check. NO Write tool. │ └── curator.md connects new reading to topics/. writes proposals only. ├── inbox/ YOU EDIT 10 raw documents. drop new ones here. ├── papers/ GENERATED one note per paper. the real database. ├── pdfs/ NEVER the files themselves, renamed ├── web/ GENERATED processed clips ├── topics/ YOURS ONLY what you think. Edit AND Write denied. ├── proposals/ GENERATED curator's suggested changes to topics/ ├── wave2/ 2 documents that arrive later (prompt 8) └── _templates/ YOU EDIT paper.md, proposal.md

The one to understand: topics/ is denied to every agent, and proposals/ exists because of that. The agent can tell you your synthesis note is out of date and hand you the exact replacement lines; it cannot apply them. That is deliberate — a synthesis note you accepted is yours, and one that appeared while you were not looking is not.

Demo 11 — the library, annotated

library/ ├── CLAUDE.md YOU EDIT ├── .claude/settings.json YOU EDIT the library itself is read-only ├── .claude/agents/ │ ├── scout.md finds candidates. no Write. │ └── citecheck.md verifies the bibliography. no Write. ├── My Library.bib NEVER Better BibTeX export ├── My Library.csv NEVER Zotero CSV — carries the ITEM KEY ├── storage/ NEVER one folder per item, named with its 8-char key │ ├── 4J8KQW2M/...pdf │ └── 9FGM2XPQ/...pdf ← the orphan. no .bib entry claims it. ├── bib/ GENERATED your output goes here, and only here └── _templates/ YOU EDIT bibliography.md, review_outline.md

The one to understand: the three views — the .bib, the .csv and storage/ — disagree in five places, and the Key column of the CSV is the only stable identifier. Cite keys are regenerated whenever you fix metadata; titles get retyped; filenames carry years that turn out to be wrong.

Demo 12 — the join, annotated

workspace/ ├── CLAUDE.md YOU EDIT one contract covering two owners ├── .claude/settings.json YOU EDIT ├── .claude/agents/ │ ├── linker.md repairs the join. may change ONE field. │ └── drift.md reports divergence. NO Write tool. ├── zotero/ NEVER the Demo 11 library, unchanged └── vault/ ├── papers/ 7 notes written over 10 months ├── topics/ YOURS ONLY ├── proposals/ GENERATED └── _templates/paper.md YOU EDIT note the zotero_key field

The one to understand: a single frontmatter field, zotero_key, is the entire integration. Two of the seven notes have one that resolves. The other five show the five ways ten months of ordinary use breaks the link.

Demo 13 — the radar, annotated

radar/ ├── CLAUDE.md YOU EDIT the order of work: eligibility before science ├── .claude/settings.json YOU EDIT denies pipeline/, profile/, inbox/, .env* ├── .claude/agents/ │ ├── recurrence.md have I decided this before? NO Write. │ ├── triage.md is this worth an afternoon? │ └── fitmemo.md writes the memo. refuses for ineligible calls. ├── profile/research_profile.md YOU EDIT ★ interests AND constraints ├── inbox/ YOU EDIT 6 solicitations. drop new ones here. ├── pipeline/ NEVER │ ├── radar.db what EXISTS. the scraper owns it. │ └── export_vault.py db → notes. one way. preserves decisions. ├── opportunities/ GENERATED + your decision blocks, preserved ├── memos/ GENERATED the deliverable └── _templates/fit_memo.md YOU EDIT

The one to understand: profile/research_profile.md has two halves, and the second one — uncommitted FTE, indirect rate, smallest viable project, cost-share appetite, down-select timing — is what decides. Four of the five traps in that demo are caught by that table alone.

5. How information moves

Every one of these systems is the same three-stage shape. Learn it once:

  SYSTEM OF RECORD          →    YOUR NOTES          →    THE DELIVERABLE
  what exists                    what you decided         what you send

  pdfs/ zotero/ radar.db         papers/ topics/          bib/ memos/
  ─────────────────────          ───────────────          ───────────────
  NEVER written by an agent      agent writes;            agent writes
  deny rules enforce it          you own topics/          freely

Three rules hold it together, and they are the same three in all four demos:

  1. Information flows left to right, never right to left. Notes never write back into the library. The vault never edits the database. If you want the record changed, you change it in the tool that owns it — Zotero, your scraper — by hand.
  2. Regeneration must be safe. Anything marked GENERATED can be deleted and rebuilt. The corollary is the thing to watch for in your own setups: any generator that touches a file you also write into must preserve your part verbatim. Demo 13's exporter does this, and an earlier version of it silently destroyed decision blocks on every run until a verification pass caught it.
  3. The verifier cannot write. skeptic, citecheck, drift and recurrence all ship with no Write tool. A verifier that can edit is a verifier you cannot trust, because it can make its own findings disappear.

6. First run, step by step

Demo 10, from a clean unzip. The same pattern applies to the other three; only the prompts differ.

Step 1 — get into the working directory

cd ~/research-system/Demo10_obsidian_research_vault/vault
ls
Expected:
CLAUDE.md   _templates  inbox  papers  pdfs  proposals  topics  wave2  web

If you see README.md and SETUP.md instead, you are one level too high. cd vault.

This matters more than it looks. CLAUDE.md and .claude/ are picked up from the directory you launch claude in. Start one level up and the agent runs with no contract and no deny list — it will happily do the thing the demo is about preventing.

Step 2 — make the undo button

git init
git add -A
git commit -m "before the agent"
Expected: 35 files changed, ... insertions(+)

From here on, git diff reviews everything the agent did and git checkout . undoes all of it. Do this in your own vault before you point an agent at five years of notes. It is worth more than every configuration file in these packages combined.

Step 3 — read the contract before running anything

cat CLAUDE.md
cat .claude/settings.json
ls .claude/agents/
Expected: three agents — librarian.md, skeptic.md, curator.md.

Two minutes here saves twenty later. You are looking for: where notes go, what the agent is forbidden to touch, and which agent has which tools.

Step 4 — look at the raw material

ls -la inbox/
Expected: ten files with realistic download-garbage names — 1-s2.0-S0022283624001147-main.pdf, scan0043.pdf, mps-12-44.pdf, three .md clips, a .csv and a .docx.

Check the scan behaves as advertised before you start:

pdftotext inbox/scan0043.pdf - | wc -c
Expected: 0 or 1. That file has no text layer — it is a photocopy. How the agent handles that is one of the things being tested.

Step 5 — start Claude Code

claude
Expected: the prompt, and a line naming the working directory. Type /status if you want to confirm which project it thinks it is in.

Then work through the nine prompts in SETUP.md, one at a time, reading the output before pasting the next one. The demo does not work as a batch — most of the findings are in what the agent says between steps.

Step 6 — check what actually changed

git diff --stat
git status --short topics/
Expected: changes in papers/, web/ and proposals/. topics/ empty.
If topics/ shows changes, the deny rule is not working — check you launched claude from inside vault/ and that .claude/settings.json is valid JSON (python3 -m json.tool .claude/settings.json).

7. Daily: a paper arrives

This is the loop you will actually run, and it takes about two minutes once the folders exist.

The 30-second version

cd ~/research-system/Demo10_obsidian_research_vault/vault
cp ~/Downloads/whatever-i-just-downloaded.pdf inbox/
claude

then:

File everything in inbox/.

That is it. The contract in CLAUDE.md carries the conventions, so you do not re-explain them. Then, when you have a minute:

Use the curator subagent on the note you just wrote.

which tells you which of your topic notes it bears on, what it contradicts, and what gap it opens — as a proposal in proposals/, for you to accept or not.

The habit that makes it worth doing

Fill in ## My take yourself, the same day. Every template has that section and no agent writes in it. Two sentences is enough. Six months from now it is the only part of the note you will trust without re-reading the paper, because it is the only part you wrote.

A vault where that section is always empty is a vault of summaries, and summaries are what you could have generated at any time. The notes are not the asset; your reaction to them is.

What not to do daily

8. Weekly: the two routines

Friday, ten minutes — verify

cd ~/research-system/Demo10_obsidian_research_vault/vault
claude
Use the skeptic subagent to cross-check everything added this week
against the sources in pdfs/.

Then skim the report for one thing only: claims in notes that are not in the source. Everything else can wait. That is the error that compounds, because a wrong number in a note becomes a wrong number in a draft becomes a wrong number in a submission.

git add -A && git commit -m "week of $(date +%Y-%m-%d)"

Friday, five more minutes — decide

Open proposals/. For each one: accept it by pasting the lines into the topic note yourself, or write one line under ## Rejected reasons saying why not.

A rejected proposal with a reason is worth as much as an accepted one. It stops the same suggestion coming back next month, and the reason is often more interesting than the proposal.

Then delete the proposal file. proposals/ should be near-empty most of the time; a folder with forty pending proposals is a folder nobody reads.

9. Monthly: drift and decisions

Reconcile the library against the notes — Demo 12's job

Once a month, or after any session of cleaning up Zotero:

cd ~/research-system/Demo12_zotero_obsidian_claude/workspace
claude
Use the drift subagent on the whole workspace.

Read the last section of its report first — which conclusions rest on a note I just flagged? A broken link is bookkeeping; a belief resting on one is the reason to run this at all.

What to expect in a real vault: drift accumulates at roughly the rate you edit Zotero. If you fixed metadata on ten items this month, expect ten stale notes. This is normal and is not anyone's mistake.

Re-export the radar — Demo 13's job

cd ~/research-system/Demo13_funding_radar/radar
python3 pipeline/export_vault.py
Expected: a line per note, new for ones that did not exist, kept decision=pass for ones where you had already decided. Then N new, M refreshed. Decisions preserved.
Verify this the first time you use it on your own data. Write a decision into a note, re-run the exporter, and confirm the text is still there. A generator that silently eats your writing is the worst possible bug in a system like this, and it is invisible until the day you need what it ate.

Then, before reading any new solicitation:

Use the recurrence subagent. Which calls in this cycle are reissues
of ones I have already decided on?

Quarterly: is the profile still true?

profile/research_profile.md decays silently. Your FTE changes, a student graduates, the indirect rate goes up, a collaborator leaves. Nothing in the system notices, and a stale constraints table produces confidently wrong triage.

Four lines to re-check every few months: uncommitted FTE, indirect rate, smallest viable project, collaborators in place.

10. Adding each kind of new data

Six kinds of thing arrive in real use. Here is where each goes.

A PDF of a paper

cp paper.pdf ~/research-system/.../vault/inbox/

Then "File inbox/paper.pdf." The librarian renames it into pdfs/, writes a note in papers/, and fills in the frontmatter. Keep the download name — the agent should not need it to be tidy, and if it does, that is a finding.

A web page

Use the Obsidian Web Clipper and save it into inbox/. It uses your logged-in browser, so it gets past paywalls and institutional logins that an agent cannot. Capture in the browser, process in the agent — do not ask the agent to be your scraper.

Then "File the new clip in inbox/." It lands in web/, citing the paper note it refers to.

A scanned PDF with no text layer

Same place. Check first:

pdftotext scan.pdf - | wc -c

If that prints a number under about 100, there is no text layer. Say so in the prompt — "this is a scan, read it as images" — because Claude Code can read PDF pages visually, 20 at a time, but will waste a step discovering the extraction is empty. For a long scan, OCR it first with ocrmypdf.

A fresh Zotero export

In Zotero: select the collection → right-click → Export Collection → Better BibTeX, tick Export Files. Then again as CSV. Replace all three — the .bib, the .csv and storage/ — as a set.

Replace them together or not at all. A new .csv against an old storage/ produces exactly the broken-attachment symptoms Demo 11 is built from. After any re-export, run Demo 12's drift pass — that is precisely when new drift appears.

A funding solicitation

cp ~/Downloads/NSF-27-xxx.pdf ~/research-system/.../radar/inbox/

Then "Use the recurrence subagent" first, and only then "Use the triage subagent on inbox/NSF-27-xxx.pdf." That order is not decoration: checking the decision log costs one prompt and can save the whole afternoon.

Saved web pages work the same way. If you have both the program page and the solicitation PDF, put both in — they often disagree, and finding out which is authoritative is part of the job.

Your own data — a spreadsheet of measurements

Into inbox/, and say what the units are in the prompt. The most common real failure in these demos is comparing numbers in different units without noticing, and the fix is one sentence from you at the start.

11. What goes wrong over time

Nothing here fails loudly. Four things decay quietly, and each has a tell:

What rotsHow you noticeWhat to do
The join
notes ↔ library
A note cites a year that does not match its PDF. Or Demo 12's drift starts reporting more each month. Run drift monthly, and always after editing Zotero. Repair with linker, which may change only the key field.
The profile
Demo 13
Triage verdicts stop matching your judgement — it recommends things you would decline. Re-read the constraints table quarterly. It is four numbers and they all change.
CLAUDE.md It grows. Once it is long, the agent follows the first half and drifts on the rest. Keep it under roughly one screen. When a rule gets ignored repeatedly, that is the signal to move it from CLAUDE.md into settings.json — from a request to a refusal. That migration is the single most useful maintenance habit in this whole system.
The decision log decision: undecided on things whose deadline has passed. Decide, even if the decision is "pass, no reason recorded". An empty log is the state Demo 13 diagnoses: 90 items, 0 votes.

The rule of thumb for all of it

When the agent does something you did not want:

  1. Was it a convention? → a line in CLAUDE.md. Cheap, flexible, not enforced.
  2. Should it have been impossible? → a deny rule in settings.json. Enforced, and it will not be argued with.
  3. Should this agent never have been able to? → remove the tool from its tools: list. Absolute.

Most people only ever write the first kind and then wonder why it keeps happening.

12. Troubleshooting

SymptomCause and fix
The agent ignores CLAUDE.md entirely You launched claude from the wrong directory. It loads from the folder you start in. cd into vault/, library/, workspace/ or radar/ — not the package root.
It edits a file that should be denied Same cause nine times out of ten. Otherwise check the JSON parses: python3 -m json.tool .claude/settings.json. One trailing comma disables the whole file silently.
Subagent not found The file must be in .claude/agents/ with YAML frontmatter and a name: that matches what you typed. Check with head -6 .claude/agents/*.md.
A subagent refuses to launch An entry in its tools: list does not resolve — usually an MCP tool for a server that is not configured. Remove the entry or configure the server.
It will not read a PDF Either no text layer (pdftotext file.pdf - | wc -c → 0; tell it to read as images) or over 10 pages, where it must read in ranges. For anything long, pdftotext -f 1 -l 12 file.pdf - first.
Everything is slow and expensive pdftotext is missing, so it is reading whole PDFs as images repeatedly. Install poppler.
It summarises abstracts instead of reading tables The failure the demos exist to show. Add an explicit instruction to CLAUDE.md and name the sections it must open. If that does not hold, run the verifier pass separately — a dedicated agent with one job beats a longer instruction.
The exporter wiped my notes Should not happen — Demo 13's preserves the ## Decision block verbatim and that is tested. git checkout . gets them back, which is why step 2 is git init. If you wrote your own exporter, this is the bug to look for first.
Obsidian shows frontmatter as raw text Turn on Properties in the right sidebar, or Settings → Editor → Properties in document → Visible.
Obsidian will not open the folder Point it at the folder containing .obsidian/ — vault/, not the package root. "Trust author" when asked.
I want to start over git checkout . && git clean -fd inside the working directory. Back to the shipped state, with your git history intact.

A standing caveat. In all four packages the material is verified — every planted defect was checked mechanically against the files. The agent's behaviour was not measured: the packages were assembled on a machine without Claude Code, so the expectations in the guides are predictions. If your run differs, your run is the data. Everything in the demos is synthetic — the papers, the library, the solicitations and their deadlines do not exist. Do not cite or act on any of it.