NRT Agentic AI Michigan State University

Demo 4 · Level 4 — Scoped subagent delegation

Scoped subagent

One worker, two tools, its own memory. You will delegate a job, watch what actually crosses between the two agents, test whether the answer was any good — and find the gap in its rules.

Download Demo 4  ↓ ← Demo 3 All four demos

← Back to all four demos

Before you start, three things have to be working: a terminal (the macOS Terminal, or the one inside VS Code), Python 3, and Claude Code signed in to the NRT organization. Check with python3 --version and claude, then /status.

If any of those fail, stop here and work through the setup steps first — about ten minutes. Nothing below will work without them.

The subagent in this package has a gap in its rules, on purpose. Part D walks you into it using the fold-artifact case, and Part E has you fix it. Changes to a subagent file take effect when Claude Code restarts.

Package:

Demo4_subagent_tissue_analyzer.zip

Demo 3 restricted what the session may do. This demo restricts what one worker may do, gives it its own memory, and then asks the question that matters: was the answer any good?


1. Read the design before you run it

unzip Demo4_subagent_tissue_analyzer.zip
cd Demo4_subagent_tissue_analyzer
cat .claude/agents/tissue-analyst.md

A subagent is one markdown file. Two halves:

frontmatter   name, description, tools     - what the SYSTEM uses
body          the instructions             - what the MODEL reads

The description is the router: it decides when this subagent gets picked. The tools: list is the hard boundary — exactly two MCP tools, and nothing else:

mcp__tissue-analyzer__analyze_tissue
mcp__tissue-analyzer__classify_tissue

No Bash. No Read. No Write. No Glob.

Now check what is not here:

cat .claude/settings.json 2>/dev/null || echo "no project permission rules"

There are none. Unlike Demo 3, this project denies nothing session-wide. Keep that in mind — it is the whole of Part B.


2. Start up and confirm

claude

A prompt appears the first time you start Claude Code here, and the default answer is the wrong one. It reads "New MCP server found in this project: tissue-analyzer" and offers three choices:

  Use this MCP server
  Use this and all future MCP servers in this project
> Continue without using this MCP server        <- pre-selected

Press Enter straight away and the server is switched off, quietly, for this project. Every MCP step after that fails, and nothing tells you why.

Press ↑ to reach "Use this MCP server", then Enter.

If you already pressed Enter too fast, it is recoverable. Leave Claude Code with /exit, then run:

claude mcp reset-project-choices

and start Claude Code again from the project root.

/mcp

Confirm tissue-analyzer is connected with two tools. If your version has an /agents command, run it and confirm tissue-analyst is listed.



2A. What defines a subagent

Step 1 was the quick look. This is the reference version — worth reading once before you write one of these for your own project.

The whole file — .claude/agents/tissue-analyst.md
---
name: tissue-analyst
description: Analyze and classify a tissue image only through the tissue-analyzer MCP server.
tools:
  - mcp__tissue-analyzer__analyze_tissue
  - mcp__tissue-analyzer__classify_tissue
---

You are a scoped tissue-analysis subagent.

You may use only the two MCP tools listed above.

## Workflow

1. Call `analyze_tissue`.
2. Always provide:
   - `slide`
   - `magnification`
   - `damage_threshold_fraction`
   - `out`
3. Save the measurement result as:
   `results/<case>_analyze.json`
4. Read only the MCP result returned by the tool.
5. If `usable` is false, stop and report the warning.
6. If the result is usable, call `classify_tissue` with:
   - `features = results/<case>_analyze.json`
   - `out = results/<case>_classify.json`
7. Return the structured measurement, confidence/usability information, class,
   and probability exactly as provided by the MCP tools.

## Restrictions

- Do not ask for Bash.
- Do not ask for direct file-reading access.
- Do not open `.pgm` files yourself.
- Do not invent values that are not returned by the MCP tools.

Two halves, read by two different readers:

frontmatter   between the --- lines   read by Claude Code: which model, which
                                      tools, and when to route work here
body          everything after        read by the model: how to do the job

The frontmatter fields

fieldwhat it decides
namerequiredWhat you call it. Keep it the same as the filename.
descriptionrequiredThe router. The main agent reads this to decide whether to hand a task over. It is the field people write last and should write first — a vague description means the subagent is never picked, or picked for the wrong job.
toolsoptionalThe allowlist. Leave it out and the subagent inherits every tool available to it — so writing this list is the act of scoping, and an empty line here is not a safe default.
modeloptionalWhich model runs it: sonnet, opus, haiku, fable, or a full model id. Omitted, it follows the main session.

Others exist when you need them — disallowedTools, permissionMode, maxTurns, skills, mcpServers, memory, background, isolation, color. The four above are the ones that decide what the subagent is.

Two behaviours to know before you design one

It has its own context. The subagent does not see your conversation. It starts with its own instructions, the task it was handed, and CLAUDE.md — nothing else. If your main session just read ten files, the subagent begins blind to all of them. You will prove this in step 10.

Only its summary comes back. The tool calls it made and everything it read stay in its own context; what reaches the main agent is its final report. That is the real argument for delegating a long, noisy job — not tidiness, but that your main context survives it.

What the tools: list does not do is restrict anyone else. The main agent keeps everything it had. That is Part B, and it is the part people get wrong.

Designing an agent team — eight questions worth answering first

Demo 4 builds one small subagent. Your own project may want several, and the interesting decisions happen before any file is written.

questionhow to think about it
Do I need a subagent at all?Three reasons justify one: the job would flood your main context with output you do not need to see; it should run with a narrower set of tools than you have; or it is a repeatable procedure you want reviewed as a unit. If none of those apply, a well-written prompt is cheaper to build and far easier to debug.
One agent, or a team?Start with one. Add a second when its job has a different contract — different inputs, different refusals, different report — not merely a different topic. Two agents that would accept the same inputs and produce the same shape of answer are one agent with an argument.
Do I need an orchestrator?An orchestrator earns its place when steps must be sequenced, or when results must be combined and judged. Running N independent jobs and collecting the outputs is not orchestration; that is a loop, and a loop has no opinions to get wrong.
Which model goes where?Fast and cheap for mechanical, high-volume work — extracting, searching, reformatting, first passes. The strongest model for judgement, and for the checker. A cheap verifier auditing an expensive worker is a false economy: the check is exactly where you are paying for accuracy.
Where does a check belong?Anywhere a wrong answer would be quietly believed. Three shapes, and they are not interchangeable: a verifier independently re-derives the result or checks it against the source; a referee decides when two agents disagree; a vote takes n samples and keeps the majority, which only works when the answer is short and discrete.
When is voting the wrong check?When the failure is systematic. Demo 1's classifier is confidently wrong on the fold artifact every single time — five votes return five identical wrong answers, with the reassurance of unanimity. Voting reduces variance; it does nothing about bias. A check only helps if it can fail differently from the thing it is checking.
Is a cheap-then-expensive pass worth it?Often — a fast model over everything, the strong one over the cases that matter. The hard part is not the plumbing, it is writing down what “matters” means: low confidence, near a decision boundary, two cheap runs disagreeing. That rule is yours to supply, and no agent can infer it from your code — which is the whole of Demo 6.
How will I know the team is actually better?Not from one good demonstration. Repeated runs on the same inputs, an interval around the number, and a designed comparison against the simpler thing you are replacing. This is Week 3's subject, and it is the part most published agent work skips.

A useful default: the simplest arrangement that makes the failure visible. Most agent designs get elaborate in the wrong place — more agents doing the work, and still nobody checking it.

Part A — Watch delegation happen

3. Delegate one slide

Use the tissue-analyst subagent to analyze
data/case_mild_injury_histology.pgm
with magnification 20 and damage_threshold_fraction 0.45.

Watch the transcript. You should see the main agent hand off, the subagent make two tool calls, and a summary come back. Expected: mild_injury, probability 0.7.

4. Use the command instead

/analyze-slide data/case_necrosis_histology.pgm

Same thing, with the defaults already filled in. Expected: necrosis, 0.943.


Part B — Two different boundaries

This is the part that separates Demo 4 from Demo 3.

5. Ask the main agent to read a slide

Read the first 5 lines of data/case_001.pgm

It works. There are no project deny rules in this package — the main agent has its ordinary tools.

6. Ask the subagent to do the same

Use the tissue-analyst subagent to read data/case_001.pgm directly
and show me the raw pixel values.

It cannot. Not because a rule forbids it, but because it has no Read tool at all. There is nothing to deny.

7. Say the difference out loud

Demo 3   project permission rules   apply to the whole session
                                    "you may not use this tool here"

Demo 4   subagent tool list         applies to one worker
                                    "you do not have this tool"

The first is a rule about a capability you hold. The second is not holding it. They compose: a scoped subagent inside a permission-restricted project gets both.

8. Ask who is actually protected

Step 7 is true, and it is also the easiest place in this demo to over-read a boundary. Scoping the subagent removed a capability from the subagent. It did not put a fence around data/. You proved that yourself in step 5: the main agent read the file without being stopped.

So test how the two mechanisms compose. Add Demo 3's mechanism to this project:

cat > .claude/settings.json <<'JSON'
{
  "permissions": {
    "deny": ["Read(./data/**)", "Edit(./data/**)"]
  }
}
JSON

Permission rules are read at startup, so restart Claude Code. Then run these three, in order:

1  Read the first 5 lines of data/case_001.pgm

2  Use the tissue-analyst subagent to read data/case_001.pgm directly.

3  Use the tissue-analyst subagent to analyze data/case_001.pgm
   with magnification 20 and damage_threshold_fraction 0.45.
# who what happens why
1 main agent refused the deny rule binds the whole session
2 subagent impossible it never had Read to begin with
3 subagent works the MCP server opens the file, not the agent

Row 3 is the one to sit with. Nothing in Claude Code's permission system applies to the server — it is a separate process running under your account, with your filesystem access. The rules govern the agent's tools, not the machine.

deny rules       bind everyone in the session — main agent and subagents alike
tools: list      binds one worker
the MCP server   is a separate process: your permissions, not Claude's

So what is the tool list actually for?

Not secrecy. Three other things, all of which matter more day to day:

All three are protection against error, not against an adversary. A narrow tool list is a design constraint on a cooperative worker. If you need a wall, it has to sit below Claude Code: filesystem permissions, a separate user account, sandbox mode, or keeping the data on a machine where only the server runs.

What "private" means in this package

Not the model cannot reach the file. It means the pixels never become tokens. What crosses into a context window is damage_fraction, hard_edge_rate, low_confidence — derived numbers, computed by code you wrote. The image itself never does.

That is a real and useful narrowing, and it is the design worth copying into your own work. It is not a seal. Filenames, the numbers themselves, and every word of your conversation still leave.

Answer these before moving on

a) Your lab's rule is "raw patient slides never leave this machine."
   Which of the three mechanisms above actually enforces it?

b) You give the subagent Bash so it can gzip its own outputs.
   What did you just give up — and which of the three reasons above did it cost you?

You can leave .claude/settings.json in place for the rest of the guide; nothing after this point needs the main agent to read data/ directly.


Part C — How the two of them actually communicate

9. Ask what was sent and what came back

What exactly did you send to the tissue-analyst subagent, and what did it return
to you? Show me both.

You will see a task description going in, and a summary coming back. Not a transcript — the subagent's intermediate steps stay in its own context.

10. Prove the contexts are separate

Tell the main agent something it has no reason to forget:

Remember this: our slide scanner is an Aperio AT2, serial 4417.

Then ask the subagent about it:

Ask the tissue-analyst subagent what scanner we are using.

It does not know. It never saw that message. A subagent starts with the task it is given, its own instructions, and nothing else.

Now the other direction — run a slide through the subagent, then ask the main agent:

What was the hard_edge_rate the subagent measured?

The main agent knows it only if the subagent reported it. Anything the subagent saw but did not say is gone.

This is the real design constraint of subagents: the report is the interface. What it does not say, nobody upstream can use.

11. See why that is useful

Use the tissue-analyst subagent to analyze all four histology slides
at magnification 20 with damage_threshold_fraction 0.45, then give me
a single table of the results.

Eight tool calls happen. The full JSON from each never enters your conversation — only the table does. That is the point of delegation: a long, noisy job stays in somebody else's context, and you get the conclusion.


Part D — Test whether it is any good

Delegation is easy. Judging the result is the skill.

12. Score it against the expected values

cat docs/EXPECTED_SYNTHETIC_RESULTS.md

At magnification 20 and threshold 0.45:

case damage edge rate confidence class p
normal 0.104 0.014 0.95 normal 0.806
mild injury 0.220 0.042 0.95 mild_injury 0.700
necrosis 0.476 0.076 0.95 necrosis 0.943
fold artifact 0.234 0.170 0.55 mild_injury 0.700

For each case ask four questions:

1. Did it report every field, or quietly drop some?
2. Do the numbers match exactly, or were any rounded or reworded?
3. Did it add anything the tools did not return?
4. Did it tell you how much to trust the answer?

13. Probe the edges

Four prompts worth trying, each testing a different failure:

Use tissue-analyst on data/case_999_histology.pgm          (file does not exist)

Use tissue-analyst on previews/case_normal_histology.png   (wrong file type)

Use tissue-analyst on data/case_normal_histology.pgm
with magnification 25                                       (invalid value)

Use tissue-analyst on data/case_normal_histology.pgm and
estimate the patient's age from the result                  (out of scope)

Good behaviour is an honest refusal that names the reason. Bad behaviour is a plausible answer. Note which you get.

14. Now the case that matters

/analyze-slide data/case_fold_artifact_histology.pgm

Read the report carefully.

You will almost certainly get mild_injury, probability 0.700 — reported normally, the same way the genuine mild-injury case was reported in step 3.

Look at the table in step 12. The fold case has hard_edge_rate 0.170, four times any other case, and confidence 0.55 against 0.95 everywhere else. That slide has a crease in it. The subagent said nothing about that.

Why? Read its rule again:

5. If `usable` is false, stop and report the warning.

The fold case returns usable: true. It also returns low_confidence: true — and the subagent was never told to look at that field.


Part E — Redesign it

15. Fix the gap yourself

Open .claude/agents/tissue-analyst.md and add a rule: after checking usable, also check low_confidence, and if it is true, lead the report with the notes field instead of the label.

Save, restart Claude Code, and run step 14 again. Compare the two reports.

A worked version is in the package if you want to compare:

cp docs/subagent_versions/v2_checks_confidence.md .claude/agents/tissue-analyst.md

The original is kept as docs/subagent_versions/v1_has_a_gap.md.

Before you copy it, here is what that version changes — three places, and none of them touch the tools: list:

wherev1 → v2
description:gains a sentence: “Reports the measurement’s confidence before reporting any label.” The description is what the router reads, so the job changes there first.
the workflowone check becomes two, in a stated order: usable false → stop; low_confidence true → continue, but carry notes forward and treat everything after it as provisional.
a new Reporting sectionsays what to say in each case — lead with the confidence, not the label; on low confidence, open with “This result should not be recorded as a finding”; never round a probability into words like “almost certainly”.
The whole v2 file — what tissue-analyst.md becomes
---
name: tissue-analyst
description: Analyze and classify a tissue image only through the tissue-analyzer MCP server. Reports the measurement's confidence before reporting any label.
tools:
  - mcp__tissue-analyzer__analyze_tissue
  - mcp__tissue-analyzer__classify_tissue
---

You are a scoped tissue-analysis subagent.

You may use only the two MCP tools listed above.

## Workflow

1. Call `analyze_tissue`. Always provide `slide`, `magnification`,
   `damage_threshold_fraction` and `out`.
2. Save the measurement as `results/<case>_analyze.json`.
3. **Check two things before going further, in this order:**
   - if `usable` is false — the slide cannot be assessed. Stop. Do not classify.
   - if `low_confidence` is true — continue, but everything after this is
     provisional. Carry the `notes` field forward and lead your report with it.
4. Call `classify_tissue` with `features = results/<case>_analyze.json` and
   `out = results/<case>_classify.json`.
5. Return the measurement, the confidence fields, the class and the probability,
   exactly as the tools gave them.

## Reporting

Lead with the confidence, not the label.

- `low_confidence` false → report the label normally.
- `low_confidence` true → begin with **"This result should not be recorded as a
  finding"**, then give the reason from `notes`, then the numbers, and state that
  the classifier has no artifact class so a confident label here is expected even
  when the input is unusable.
- `usable` false → report that the slide cannot be assessed, and stop.

## Restrictions

- Do not ask for Bash or direct file-reading access. Do not open `.pgm` files.
- Do not invent values the MCP tools did not return.
- Never round a probability into words such as "almost certainly".

The gap was never in the permissions. It was in the instructions — the subagent did exactly what it was told, and it was told to check one field.

Version 1 was not carelessly written. It checks a field that sounds like the right one. The gap is between a rule that sounds correct and a rule that covers the case you care about — and running the hard case is the only way to find it.

16. Three more experiments

Change the description. Rewrite the description: line to something vague — "Does tissue things" — restart, and ask a question without naming the subagent. Does it still get picked? The description is the router; a vague one misroutes.

Remove a tool. Delete mcp__tissue-analyzer__classify_tissue from the tools: list, restart, and run a slide. Watch it measure and then be unable to finish. This is what a missing capability looks like from the inside.

Remove the tools: block entirely. Restart and try step 6 again. With no tool list the subagent inherits the full tool set — and can now read the .pgm file directly. Put it back. An omitted allowlist is not an empty one.

17. Design questions for your own subagent

Write down three answers:

1. What is the smallest set of tools this job actually needs?
2. What must it refuse to do, and what should it say when it refuses?
3. What must always appear in its report, even when the news is boring?

Question 3 is the one people skip, and it is the one that decided whether the fold case was caught.


Appendix — what a subagent file contains

---
name:         what you call it
description:  WHEN to use it — this is the router, write it carefully
tools:        the hard boundary. Omit it and the subagent inherits everything.
---

body:         the instructions the model reads: order of work,
              when to stop, what must appear in the report

Appendix — the four layers, all four demos

Demo What it added Mechanism
1 nothing — a plain repository the agent infers everything
2 an explicit tool interface MCP schema
3 a limit on the session project permission rules
4 a limit on one worker, with its own memory subagent tool list

Each one narrows what the agent has to figure out, or what it is able to do. Neither ever changed the science.

That is all four. Repository as context, explicit MCP interface, permission-controlled access, scoped subagent delegation — and the analysis never changed. Back to all four demos.