Skip to content

feat(nemo-agents): add Fabric (spec-v1) email-phishing example - #1117

Open
walston wants to merge 7 commits into
mainfrom
astd-370-372-fabric-phishing-example/nwalston
Open

feat(nemo-agents): add Fabric (spec-v1) email-phishing example#1117
walston wants to merge 7 commits into
mainfrom
astd-370-372-fabric-phishing-example/nwalston

Conversation

@walston

@walston walston commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Ports the email-phishing analyzer to a Platform-native nemo-agents-spec-v1
example under plugins/nemo-agents/examples/email-phishing-fabric/.

The NAT example proxy-passes classification to an opaque MCP server — beyond the
reach of trace views, prompt/hyperparameter tuning, and evaluation. This example
restructures it as a deepagents orchestrator that delegates the verdict to a
phishing subagent and calls a deterministic extract_iocs MCP tool. The
prompt and model live in agent.yaml (tunable), and the subagent task + tool
call each emit a trace span. The proxy-pass classifier becomes a Platform-visible,
tunable, evaluable step.

Changes

  • agent.yaml — deepagents orchestrator + a declarative_subagent
    (phishing-analyzer) with its own system_prompt and a loose YAML verdict
    (is_likely_phishing/confidence/indicators/explanation) the top-level
    model parses; extract_iocs wired as a harness_native stdio MCP server.
  • extract_iocs MCP tool — pure-regex URL/domain extraction (ported from the
    email-security-analyst example) served over stdio by the
    email-phishing-iocs-mcp console script; unit-tested.
  • Sender-inclusive inputdata/build_dataset.py assembles an email
    column (From:/Subject:/body) so the sender (a top phishing tell, and an
    extract_iocs input) reaches the model; the NAT eval fed body only. The eval
    config uses question_key: email.
  • Registered as a uv workspace member so email_phishing_fabric resolves.

Verification

  • agent.yaml validates against AgentConfig (nemo-agents-spec-v1, extra="forbid") and translates to a typed Fabric config via translate_agent_config.
  • extract_iocs tests: 8 passed.
  • ruff check / ruff format / ty clean; pre-commit (incl. DCO) passed.

Not exercised here: a live create/deploy/invoke against a running Platform
(needs NVIDIA_API_KEY + deepagents adapter runtime). Eval judge weights/prompt
are starters to tune per the evaluator plugin.

Tickets

Resolves the ASTD-370 restructuring decision (deepagents orchestrator + sub-agent),
ASTD-371 (include sender in analyzed input), and ASTD-372 (author the spec-v1
agent.yaml).

Summary by CodeRabbit

  • New Features
    • Added an email-phishing analysis agent example that produces phishing or benign verdicts.
    • Added optional extraction of URLs, domains, and related indicators from email content.
    • Added dataset preparation and evaluation configurations with configurable scoring and model settings.
    • Added local and container deployment guidance.
  • Documentation
    • Added setup, configuration, tuning, evaluation, and dataset regeneration instructions.
  • Tests
    • Added coverage for indicator extraction, normalization, deduplication, and empty inputs.

@walston
walston requested review from a team as code owners August 5, 2026 22:59
@github-actions github-actions Bot added the feat label Aug 5, 2026
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a Fabric email-phishing agent example with DeepAgents orchestration, MCP-based IOC extraction, dataset generation, evaluation settings, tests, documentation, packaging, and workspace registration.

Changes

Email phishing Fabric example

Layer / File(s) Summary
Analysis orchestration and MCP wiring
plugins/nemo-agents/examples/nemo-agent-config/email-phishing-agent/agent.yaml, plugins/nemo-agents/examples/nemo-agent-config/email-phishing-agent/mcps/iocs.py, plugins/nemo-agents/examples/nemo-agent-config/email-phishing-agent/pyproject.toml
Defines the phishing-analysis workflow, deterministic IOC extraction, stdio MCP exposure, model settings, telemetry, workspace paths, and console entry point.
Deterministic IOC extraction and validation
plugins/nemo-agents/examples/nemo-agent-config/email-phishing-agent/tests/test_extract_iocs.py
Tests URL and domain extraction, normalization, punctuation handling, sender domains, deduplication, ordering, and empty inputs.
Dataset evaluation and example documentation
plugins/nemo-agents/examples/nemo-agent-config/email-phishing-agent/data/build_dataset.py, plugins/nemo-agents/examples/nemo-agent-config/email-phishing-agent/email-phishing-eval.yml, plugins/nemo-agents/examples/nemo-agent-config/email-phishing-agent/README.md, pyproject.toml
Builds sender-inclusive email records, configures judge-based evaluation, documents deployment and validation, and adds the example to the uv workspace.

Sequence Diagram(s)

sequenceDiagram
  participant DatasetBuilder
  participant DeepAgentsOrchestrator
  participant PhishingAnalyzer
  participant IOCMCP
  participant Evaluator
  DatasetBuilder->>DeepAgentsOrchestrator: assembled email
  DeepAgentsOrchestrator->>PhishingAnalyzer: analysis request
  PhishingAnalyzer->>IOCMCP: extract_iocs(text)
  IOCMCP-->>PhishingAnalyzer: URLs and domains
  PhishingAnalyzer-->>DeepAgentsOrchestrator: YAML verdict
  DeepAgentsOrchestrator-->>Evaluator: generated verdict
  Evaluator->>Evaluator: compare verdict with label
Loading

Possibly related PRs

Suggested reviewers: yamini, tylersbray

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding a Fabric spec-v1 email-phishing example.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch astd-370-372-fabric-phishing-example/nwalston

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@plugins/nemo-agents/examples/email-phishing-fabric/data/build_dataset.py`:
- Around line 42-47: Update assemble_email to validate the trimmed sender value
and raise an error when the sender is missing or blank, before constructing or
writing the message. Preserve the existing subject and body handling while
ensuring invalid rows cannot be reported as successfully processed.

In `@plugins/nemo-agents/examples/email-phishing-fabric/email-phishing-eval.yml`:
- Line 34: Update dataset generation in build_dataset.py to validate that every
subject used by id_key is unique, failing generation when duplicates are
detected; alternatively, configure id_key to use an existing stable unique
identifier instead of subject. Ensure the configured id_key and generated
dataset preserve uniqueness.

In `@plugins/nemo-agents/examples/email-phishing-fabric/README.md`:
- Around line 12-16: Update the diagram code fence in the README to specify the
text language, changing the untyped fence around the orchestrator diagram to a
text fence while leaving its contents unchanged.
- Around line 1-3: Update the README with a Prerequisites section before the
description, listing uv, the nemo CLI, Platform access, the deepagents adapter,
NVIDIA_API_KEY, and the evaluation model variable; add a Next Steps section
after Status with links for dataset generation, evaluation, and deployment
guidance.
- Around line 57-59: Update the README’s multiline --input example for
email-phishing-fabric-deployment to use Bash ANSI-C quoting with $'...', so each
\n sequence becomes an actual newline before the CLI receives the input.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: c80b94c1-1896-4dcf-bea9-5f3bf0d54fe0

📥 Commits

Reviewing files that changed from the base of the PR and between 96cb643 and 6415d38.

⛔ Files ignored due to path filters (2)
  • plugins/nemo-agents/examples/email-phishing-fabric/data/smaller_test.csv is excluded by !**/*.csv
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (9)
  • plugins/nemo-agents/examples/email-phishing-fabric/README.md
  • plugins/nemo-agents/examples/email-phishing-fabric/agent.yaml
  • plugins/nemo-agents/examples/email-phishing-fabric/data/build_dataset.py
  • plugins/nemo-agents/examples/email-phishing-fabric/email-phishing-eval.yml
  • plugins/nemo-agents/examples/email-phishing-fabric/pyproject.toml
  • plugins/nemo-agents/examples/email-phishing-fabric/src/email_phishing_fabric/iocs.py
  • plugins/nemo-agents/examples/email-phishing-fabric/src/email_phishing_fabric/mcp_server.py
  • plugins/nemo-agents/examples/email-phishing-fabric/tests/test_extract_iocs.py
  • pyproject.toml

dataset:
_type: csv
file_path: data/smaller_test.csv
id_key: subject

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

uv run python - <<'PY'
import csv
from collections import Counter
from pathlib import Path

path = Path(
    "plugins/nemo-agents/examples/email-phishing-fabric/data/smaller_test.csv"
)

with path.open(newline="", encoding="utf-8") as handle:
    subjects = [row["subject"] for row in csv.DictReader(handle)]

duplicates = {
    subject: count
    for subject, count in Counter(subjects).items()
    if count > 1
}

if duplicates:
    for subject, count in duplicates.items():
        print(f"{count} rows use subject: {subject!r}")
    raise SystemExit(1)
PY

Repository: NVIDIA-NeMo/nemo-platform

Length of output: 203


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- local instructions ---'
find .. -name AGENTS.local.md -print

printf '%s\n' '--- target files ---'
git ls-files \
  'plugins/nemo-agents/examples/email-phishing-fabric/**' \
  | sed -n '1,160p'

printf '%s\n' '--- target configuration ---'
nl -ba plugins/nemo-agents/examples/email-phishing-fabric/email-phishing-eval.yml \
  | sed -n '1,90p'

printf '%s\n' '--- dataset columns and row counts ---'
python3 - <<'PY'
import csv
from collections import Counter
from pathlib import Path

root = Path("plugins/nemo-agents/examples/email-phishing-fabric")
for path in sorted(root.rglob("*.csv")):
    with path.open(newline="", encoding="utf-8") as handle:
        rows = list(csv.DictReader(handle))
    print(path, "rows=", len(rows), "columns=", list(rows[0]) if rows else [])
    if rows and "subject" in rows[0]:
        duplicates = {
            value: count
            for value, count in Counter(row["subject"] for row in rows).items()
            if count > 1
        }
        print("  subject_duplicates=", duplicates)
PY

printf '%s\n' '--- id_key and subject usage ---'
rg -n -S 'id_key|subject|smaller_test' \
  plugins/nemo-agents/examples/email-phishing-fabric \
  | sed -n '1,240p'

Repository: NVIDIA-NeMo/nemo-platform

Length of output: 945


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path

files = [
    Path("plugins/nemo-agents/examples/email-phishing-fabric/email-phishing-eval.yml"),
    Path("plugins/nemo-agents/examples/email-phishing-fabric/data/build_dataset.py"),
    Path("plugins/nemo-agents/examples/email-phishing-fabric/README.md"),
]
for path in files:
    print(f"--- {path} ---")
    for number, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
        print(f"{number:4}: {line}")
PY

python3 - <<'PY'
import csv
from collections import Counter
from pathlib import Path

root = Path("plugins/nemo-agents/examples/email-phishing-fabric")
for path in sorted(root.rglob("*.csv")):
    with path.open(newline="", encoding="utf-8") as handle:
        rows = list(csv.DictReader(handle))
    print(f"--- {path}: {len(rows)} rows ---")
    print("columns:", list(rows[0]) if rows else [])
    for field in ("subject", "id", "id_key"):
        if rows and field in rows[0]:
            duplicates = {
                value: count
                for value, count in Counter(row[field] for row in rows).items()
                if count > 1
            }
            print(f"{field}_duplicates:", duplicates)
PY

rg -n -S 'id_key|subject|smaller_test|record identity|unique' \
  plugins/nemo-agents/examples/email-phishing-fabric \
  | sed -n '1,240p'

Repository: NVIDIA-NeMo/nemo-platform

Length of output: 12852


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- repository id_key references ---'
rg -n -S 'id_key' . \
  -g '!**/.git/**' \
  | sed -n '1,260p'

printf '%s\n' '--- dataset loader and identity handling candidates ---'
rg -n -S 'id[_ -]?key|record[_ -]?id|unique.*id|duplicate|dedup|DataFrame' . \
  -g '*.py' -g '*.ts' -g '*.tsx' -g '*.js' -g '*.yml' -g '*.yaml' \
  | sed -n '1,320p'

printf '%s\n' '--- source dataset referenced by build_dataset.py ---'
python3 - <<'PY'
import csv
from collections import Counter
from pathlib import Path

path = Path(
    "plugins/nemo-agents/examples/email-phishing-analyzer"
) / "src/nat_email_phishing_analyzer/data/smaller_test.csv"

if not path.exists():
    print(f"missing: {path}")
else:
    with path.open(newline="", encoding="utf-8") as handle:
        rows = list(csv.DictReader(handle))
    print(f"{path}: {len(rows)} rows; columns={list(rows[0]) if rows else []}")
    for field in ("subject", "id"):
        if rows and field in rows[0]:
            print(
                field,
                {
                    value: count
                    for value, count in Counter(row[field] for row in rows).items()
                    if count > 1
                },
            )
PY

Repository: NVIDIA-NeMo/nemo-platform

Length of output: 50381


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- repository id_key references ---'
rg -n -S 'id_key' . \
  -g '!**/.git/**' \
  | sed -n '1,260p'

printf '%s\n' '--- dataset loader and identity handling candidates ---'
rg -n -S 'id[_ -]?key|record[_ -]?id|unique.*id|duplicate|dedup|DataFrame' . \
  -g '*.py' -g '*.ts' -g '*.tsx' -g '*.js' -g '*.yml' -g '*.yaml' \
  | sed -n '1,320p'

printf '%s\n' '--- source dataset referenced by build_dataset.py ---'
python3 - <<'PY'
import csv
from collections import Counter
from pathlib import Path

path = (
    Path("plugins/nemo-agents/examples/email-phishing-analyzer")
    / "src/nat_email_phishing_analyzer/data/smaller_test.csv"
)

if not path.exists():
    print(f"missing: {path}")
else:
    with path.open(newline="", encoding="utf-8") as handle:
        rows = list(csv.DictReader(handle))
    print(f"{path}: {len(rows)} rows; columns={list(rows[0]) if rows else []}")
    for field in ("subject", "id"):
        if rows and field in rows[0]:
            print(
                field,
                {
                    value: count
                    for value, count in Counter(row[field] for row in rows).items()
                    if count > 1
                },
            )
PY

Repository: NVIDIA-NeMo/nemo-platform

Length of output: 50381


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import csv
from collections import Counter
from pathlib import Path

paths = [
    Path("plugins/nemo-agents/examples/email-phishing-analyzer")
    / "src/nat_email_phishing_analyzer/data/smaller_test.csv",
    Path("web/packages/studio/public/sample-agents/email-phishing-analyzer/eval.yml"),
]
for path in paths:
    print(f"--- {path} ---")
    if path.suffix == ".csv":
        with path.open(newline="", encoding="utf-8") as handle:
            rows = list(csv.DictReader(handle))
        print("rows:", len(rows))
        print("columns:", list(rows[0]) if rows else [])
        if rows and "subject" in rows[0]:
            counts = Counter(row["subject"] for row in rows)
            print("duplicate_subjects:", {k: v for k, v in counts.items() if v > 1})
    else:
        for number, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
            if "dataset:" in line or "file_path:" in line or "id_key:" in line:
                print(f"{number}: {line}")
PY

printf '%s\n' '--- focused evaluator documentation and schema references ---'
rg -n -S -m 120 \
  'id_key:|id_key\b|CSV.*dataset|dataset.*CSV|record identity|unique.*(record|row|id)' \
  plugins/nemo-agents \
  sdk/python/nemo-platform/src/nemo_platform/beta/evaluator \
  docs \
  -g '*.md' -g '*.py' -g '*.yml' -g '*.yaml' \
  | sed -n '1,220p'

Repository: NVIDIA-NeMo/nemo-platform

Length of output: 2048


Validate subject uniqueness during dataset generation.

smaller_test.csv currently has five unique subjects, but data/build_dataset.py does not enforce this invariant. Fail generation on duplicate subjects or use a stable unique ID for id_key.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/nemo-agents/examples/email-phishing-fabric/email-phishing-eval.yml`
at line 34, Update dataset generation in build_dataset.py to validate that every
subject used by id_key is unique, failing generation when duplicates are
detected; alternatively, configure id_key to use an existing stable unique
identifier instead of subject. Ensure the configured id_key and generated
dataset preserve uniqueness.

Comment on lines +1 to +3
# Email Phishing Analyzer — Fabric example (`nemo-agents-spec-v1`)

A Platform-native port of the email-phishing analyzer. Unlike the NAT ReAct

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add the required prerequisite and Next Steps sections.

Add Prerequisites before the description. List uv, the nemo CLI, Platform access, the deepagents adapter, NVIDIA_API_KEY, and the evaluation model variable. Add Next Steps after Status with links to dataset generation, evaluation, and deployment guidance.

As per coding guidelines, Markdown pages must list prerequisites at the top and include a Next Steps section at the end. Based on learnings, the missing-section exception applies only when no enforced convention exists; this review includes an explicit convention.

Also applies to: 69-75

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/nemo-agents/examples/email-phishing-fabric/README.md` around lines 1
- 3, Update the README with a Prerequisites section before the description,
listing uv, the nemo CLI, Platform access, the deepagents adapter,
NVIDIA_API_KEY, and the evaluation model variable; add a Next Steps section
after Status with links for dataset generation, evaluation, and deployment
guidance.

Sources: Coding guidelines, Learnings

Comment thread plugins/nemo-agents/examples/nemo-agent-config/email-phishing-agent/README.md Outdated
Comment on lines +57 to +59
nemo agents invoke --agent-deployment email-phishing-fabric-deployment \
--input "From: it-support@paypa1-secure.example\nSubject: Verify your account\n\nYour account is locked. Confirm your password at http://paypa1-secure.example/login"
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- local instructions ---'
find .. -name AGENTS.local.md -print
printf '%s\n' '--- target file ---'
cat -n plugins/nemo-agents/examples/email-phishing-fabric/README.md | sed -n '1,110p'
printf '%s\n' '--- invoke implementation references ---'
rg -n --glob '!**/node_modules/**' 'agents invoke|--input|input' plugins/nemo-agents | head -200

Repository: NVIDIA-NeMo/nemo-platform

Length of output: 29984


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- CLI invoke path ---'
sed -n '120,225p' plugins/nemo-agents/src/nemo_agents_plugin/cli.py
sed -n '1420,1475p' plugins/nemo-agents/src/nemo_agents_plugin/cli.py
printf '%s\n' '--- deployment invocation call graph ---'
rg -n -C 4 'agent_deployment|deployment.*invoke|invoke.*deployment|/invoke|messages.*content' plugins/nemo-agents/src/nemo_agents_plugin
printf '%s\n' '--- shell behavior probe ---'
python3 - <<'PY'
import subprocess
script = r'''
printf '%s' "From: a\nSubject: b\n\nBody" | od -An -t x1
printf '%s' $'From: a\nSubject: b\n\nBody' | od -An -t x1
'''
print(subprocess.run(["bash", "-c", script], text=True, capture_output=True, check=True).stdout)
PY

Repository: NVIDIA-NeMo/nemo-platform

Length of output: 18137


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
import subprocess

script = r'''
printf '%s' "From: a\nSubject: b\n\nBody" |
  python3 -c 'import sys; print(repr(sys.stdin.read()))'
printf '%s' $'From: a\nSubject: b\n\nBody' |
  python3 -c 'import sys; print(repr(sys.stdin.read()))'
'''
result = subprocess.run(["bash", "-c", script], text=True, capture_output=True, check=True)
print(result.stdout, end="")
PY

Repository: NVIDIA-NeMo/nemo-platform

Length of output: 226


Use ANSI-C quoting for the multiline input. Bash passes \n literally, and the CLI forwards it unchanged. Use $'From: ...\nSubject: ...'.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/nemo-agents/examples/email-phishing-fabric/README.md` around lines 57
- 59, Update the README’s multiline --input example for
email-phishing-fabric-deployment to use Bash ANSI-C quoting with $'...', so each
\n sequence becomes an actual newline before the CLI receives the input.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
plugins/nemo-agents/examples/email-phishing-fabric/README.md (1)

42-48: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Keep this page in one Diátaxis quadrant.

Shape and Tune explain architecture. Run is a how-to. Move the architecture content to an explanation page, or keep this README as one how-to with cross-links.

As per coding guidelines, each documentation page must fit one Diátaxis quadrant.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/nemo-agents/examples/email-phishing-fabric/README.md` around lines 42
- 48, Move the architecture-oriented prose in the README’s Run section out of
this how-to page and keep this page focused only on running the example. Update
the section around extract_iocs so it no longer explains the deepagents adapter,
command resolution, or deployment-mode behavior, and instead cross-link that
material to the appropriate explanation page while preserving the run
instructions here.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@plugins/nemo-agents/examples/email-phishing-fabric/README.md`:
- Around line 57-93: The README’s usage documentation must provide parallel CLI
and Python SDK workflows in tabbed local and container alternatives. Update the
local and container sections around the shown `nemo agents` commands to use the
project’s tab-set documentation convention, adding equivalent Python SDK
examples for each variant while preserving the existing CLI commands and
deployment details.
- Around line 50-66: Update the local CLI instructions before the `nemo agents
create` commands to direct users to follow `SETUP.md`, set
`NMP_BASE_URL=http://localhost:8080`, verify Platform readiness, and run
commands via `uv run nemo` or an activated `.venv`. Apply the same setup and
invocation guidance to the additional local command blocks, ensuring the MCP
console script is available on PATH.

---

Nitpick comments:
In `@plugins/nemo-agents/examples/email-phishing-fabric/README.md`:
- Around line 42-48: Move the architecture-oriented prose in the README’s Run
section out of this how-to page and keep this page focused only on running the
example. Update the section around extract_iocs so it no longer explains the
deepagents adapter, command resolution, or deployment-mode behavior, and instead
cross-link that material to the appropriate explanation page while preserving
the run instructions here.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 98592611-ae81-48b8-b795-d33728b112f2

📥 Commits

Reviewing files that changed from the base of the PR and between 6415d38 and 216b003.

📒 Files selected for processing (2)
  • plugins/nemo-agents/examples/email-phishing-fabric/README.md
  • plugins/nemo-agents/examples/email-phishing-fabric/agent.yaml
🚧 Files skipped from review as they are similar to previous changes (1)
  • plugins/nemo-agents/examples/email-phishing-fabric/agent.yaml

Comment thread plugins/nemo-agents/examples/nemo-agent-config/email-phishing-agent/README.md Outdated
Comment on lines +57 to +93
```bash
nemo agents create --name email-phishing-fabric \
--agent-config plugins/nemo-agents/examples/email-phishing-fabric/agent.yaml
nemo agents deploy --agent email-phishing-fabric --name email-phishing-fabric-deployment
nemo agents invoke --agent-deployment email-phishing-fabric-deployment \
--input "From: it-support@paypa1-secure.example
Subject: Verify your account

Your account is locked. Confirm your password at http://paypa1-secure.example/login"
```

### Container (`--mode docker` / `k8s`)

A deployment container does **not** have this example installed, so a local
`uv pip install` cannot reach it. Bake the package into an image with
`nemo agents package` — project mode (`--pyproject`) runs `uv pip install .`,
which provides the `email-phishing-iocs-mcp` console script — then deploy that
image:

```bash
nemo agents package \
--agent plugins/nemo-agents/examples/email-phishing-fabric/agent.yaml \
--pyproject plugins/nemo-agents/examples/email-phishing-fabric/pyproject.toml \
--tag email-phishing-fabric:local

nemo agents create --name email-phishing-fabric \
--agent-config plugins/nemo-agents/examples/email-phishing-fabric/agent.yaml
nemo agents deploy \
--agent email-phishing-fabric \
--name email-phishing-fabric-deployment \
--mode docker \
--image email-phishing-fabric:local
```

For Kubernetes, publish the image
(`nemo agents package ... --publish --registry <registry>`) and pass the
published image to `nemo agents deploy --mode k8s --image <image>`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Provide the required tabbed alternatives.

This section provides only CLI examples and uses separate headings for local and container variants. Add equivalent Python SDK examples and put the local/container variants in tab sets, or link to dedicated pages that provide both interfaces.

As per coding guidelines, documentation must provide Python SDK and CLI examples in tab sets for parallel workflows.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/nemo-agents/examples/email-phishing-fabric/README.md` around lines 57
- 93, The README’s usage documentation must provide parallel CLI and Python SDK
workflows in tabbed local and container alternatives. Update the local and
container sections around the shown `nemo agents` commands to use the project’s
tab-set documentation convention, adding equivalent Python SDK examples for each
variant while preserving the existing CLI commands and deployment details.

Source: Coding guidelines

@walston

walston commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

Live-validated --mode subprocess on a running platform (real NVIDIA_API_KEY): create → deploy → invoke returns a correct YAML verdict (is_likely_phishing: true, 0.96, catches the typo-squatted sender domain). Adapter event graph + LangGraph checkpointer confirm the orchestrator delegates to the phishing-analyzer subagent and extract_iocs is actually called (checkpointer holds extract_iocs + its {urls, domains} output), routed through IGW. Container (docker/k8s) package path and Studio path still to validate.

walston added 2 commits August 6, 2026 09:03
Port the email-phishing analyzer to a Platform-native nemo-agents-spec-v1
agent: a deepagents orchestrator that delegates classification to a phishing
subagent and calls a deterministic extract_iocs MCP tool. The prompt and
model live in agent.yaml (tunable) and each step emits a trace span, replacing
the opaque MCP-proxy classifier.

- extract_iocs ported as a stdio MCP console tool (pure regex, unit-tested)
- sender-inclusive input: assembled From:/Subject:/body 'email' column so the
  sender (a top phishing tell) reaches the model and extract_iocs
- eval config uses question_key: email
- registered as a workspace member so its package resolves

ASTD-370, ASTD-371, ASTD-372

Signed-off-by: Nathan Walston <nwalston@nvidia.com>
The extract_iocs stdio MCP tool is a console script that Fabric launches as a
parallel child process, resolving the command on PATH. The runtime that must
contain it differs by deploy mode:

- subprocess (default): runs locally from the repo .venv (sys.executable,
  inherits PATH); the example is a workspace member so uv sync --all-packages
  already provides the console script — no image needed.
- docker/k8s: the container lacks the package; bake it in with
  nemo agents package --pyproject (uv pip install .), then deploy --mode
  docker/k8s --image (--publish --registry for k8s).

Replaces the misleading local 'uv pip install + bare deploy' instruction and
the agent.yaml comment. Addresses review P1 (deployed agent could not start
its MCP server under container modes).

Signed-off-by: Nathan Walston <nwalston@nvidia.com>
@walston
walston force-pushed the astd-370-372-fabric-phishing-example/nwalston branch from 216b003 to f6f054b Compare August 6, 2026 16:07
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@plugins/nemo-agents/examples/email-phishing-fabric/README.md`:
- Around line 68-109: Update the README’s Status section to state that the
subprocess path, subagent delegation, and extract_iocs were live-validated,
while Docker/Kubernetes packaging remains unvalidated. Clearly label the
container deployment commands under the Container section as unvalidated, unless
those workflows are actually tested before publishing.

In
`@plugins/nemo-agents/examples/email-phishing-fabric/src/email_phishing_fabric/iocs.py`:
- Around line 27-29: Update the URL extraction logic using _URL_RE and urlsplit
to catch ValueError from malformed candidates, including fullwidth-slash cases,
so untrusted content cannot fail the tool. Require a non-empty hostname before
adding either the URL or its domain, and add a regression test covering the
malformed candidate.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 4532af6e-33a8-4f66-b9f3-160c4121913d

📥 Commits

Reviewing files that changed from the base of the PR and between c71ca67 and f6f054b.

⛔ Files ignored due to path filters (2)
  • plugins/nemo-agents/examples/email-phishing-fabric/data/smaller_test.csv is excluded by !**/*.csv
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (9)
  • plugins/nemo-agents/examples/email-phishing-fabric/README.md
  • plugins/nemo-agents/examples/email-phishing-fabric/agent.yaml
  • plugins/nemo-agents/examples/email-phishing-fabric/data/build_dataset.py
  • plugins/nemo-agents/examples/email-phishing-fabric/email-phishing-eval.yml
  • plugins/nemo-agents/examples/email-phishing-fabric/pyproject.toml
  • plugins/nemo-agents/examples/email-phishing-fabric/src/email_phishing_fabric/iocs.py
  • plugins/nemo-agents/examples/email-phishing-fabric/src/email_phishing_fabric/mcp_server.py
  • plugins/nemo-agents/examples/email-phishing-fabric/tests/test_extract_iocs.py
  • pyproject.toml
🚧 Files skipped from review as they are similar to previous changes (6)
  • pyproject.toml
  • plugins/nemo-agents/examples/email-phishing-fabric/email-phishing-eval.yml
  • plugins/nemo-agents/examples/email-phishing-fabric/data/build_dataset.py
  • plugins/nemo-agents/examples/email-phishing-fabric/agent.yaml
  • plugins/nemo-agents/examples/email-phishing-fabric/pyproject.toml
  • plugins/nemo-agents/examples/email-phishing-fabric/src/email_phishing_fabric/mcp_server.py

Comment on lines +68 to +109
### Container (`--mode docker` / `k8s`)

A deployment container does **not** have this example installed, so a local
`uv pip install` cannot reach it. Bake the package into an image with
`nemo agents package` — project mode (`--pyproject`) runs `uv pip install .`,
which provides the `email-phishing-iocs-mcp` console script — then deploy that
image:

```bash
nemo agents package \
--agent plugins/nemo-agents/examples/email-phishing-fabric/agent.yaml \
--pyproject plugins/nemo-agents/examples/email-phishing-fabric/pyproject.toml \
--tag email-phishing-fabric:local

nemo agents create --name email-phishing-fabric \
--agent-config plugins/nemo-agents/examples/email-phishing-fabric/agent.yaml
nemo agents deploy \
--agent email-phishing-fabric \
--name email-phishing-fabric-deployment \
--mode docker \
--image email-phishing-fabric:local
```

For Kubernetes, publish the image
(`nemo agents package ... --publish --registry <registry>`) and pass the
published image to `nemo agents deploy --mode k8s --image <image>`.

Evaluate against the sender-inclusive dataset:

```bash
nemo agents evaluate run \
--eval-config plugins/nemo-agents/examples/email-phishing-fabric/email-phishing-eval.yml \
--agent email-phishing-fabric
```

## Status

Structurally validated: `agent.yaml` passes `AgentConfig` (`nemo-agents-spec-v1`)
and translates to a typed Fabric config; `extract_iocs` is unit-tested. A live
create/deploy/invoke against a running Platform (with `NVIDIA_API_KEY`) is the
next step and is not exercised here. Eval judge weights/prompt are starters —
tune per your evaluator plugin.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- local instruction files ---'
find .. -name AGENTS.local.md -print
printf '%s\n' '--- target and nearby files ---'
git ls-files 'plugins/nemo-agents/examples/email-phishing-fabric/*' | sed -n '1,120p'
printf '%s\n' '--- README lines 1-140 ---'
cat -n plugins/nemo-agents/examples/email-phishing-fabric/README.md | sed -n '1,140p'
printf '%s\n' '--- concise repository status ---'
git status --short
printf '%s\n' '--- recent commit subjects ---'
git log --oneline -8

Repository: NVIDIA-NeMo/nemo-platform

Length of output: 7273


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- remote and branch metadata ---'
git remote -v
git branch --show-current
git show -s --format=fuller f6f054b2
printf '%s\n' '--- documentation commit summary ---'
git show --stat --oneline f6f054b2
printf '%s\n' '--- documentation commit diff ---'
git diff f6f054b2^ f6f054b2 -- plugins/nemo-agents/examples/email-phishing-fabric/README.md
printf '%s\n' '--- example configuration ---'
cat -n plugins/nemo-agents/examples/email-phishing-fabric/pyproject.toml
cat -n plugins/nemo-agents/examples/email-phishing-fabric/agent.yaml
printf '%s\n' '--- package/deploy command definitions and references ---'
rg -n --glob '*.py' --glob '*.ts' --glob '*.tsx' --glob '*.md' \
  'agents package|--pyproject|def package|class.*Package|agents deploy|--mode docker|--mode k8s' \
  plugins src docs .github 2>/dev/null | sed -n '1,240p'

Repository: NVIDIA-NeMo/nemo-platform

Length of output: 22006


🏁 Script executed:

#!/bin/bash
set -e
sha=f6f054b245c2e8329c47c0705205a226234a957e
printf '%s\n' '--- pull requests associated with commit ---'
if command -v gh >/dev/null 2>&1; then
  gh api "repos/NVIDIA-NeMo/nemo-platform/commits/$sha/pulls" \
    --header 'Accept: application/vnd.github+json' \
    --jq '.[] | {number, title, state, body, html_url}'
else
  curl -fsSL -H 'Accept: application/vnd.github+json' \
    "https://api.github.com/repos/NVIDIA-NeMo/nemo-platform/commits/$sha/pulls" |
    jq '.[] | {number, title, state, body, html_url}'
fi
printf '%s\n' '--- package CLI definition ---'
cat -n plugins/nemo-agents/src/nemo_agents_plugin/cli.py | sed -n '270,390p'
printf '%s\n' '--- package project-mode implementation ---'
cat -n plugins/nemo-agents/src/nemo_agents_plugin/cli.py | sed -n '640,715p'
printf '%s\n' '--- deployment option validation ---'
cat -n plugins/nemo-agents/src/nemo_agents_plugin/cli.py | sed -n '900,975p'
printf '%s\n' '--- package documentation ---'
cat -n plugins/nemo-agents/README.md | sed -n '150,225p;270,290p;385,415p'
printf '%s\n' '--- package CLI tests around --pyproject ---'
cat -n plugins/nemo-agents/tests/unit/test_container.py | sed -n '1660,1850p'

Repository: NVIDIA-NeMo/nemo-platform

Length of output: 31330


🏁 Script executed:

#!/bin/bash
set -e
pr=1117
base="https://api.github.com/repos/NVIDIA-NeMo/nemo-platform"
headers=(-H 'Accept: application/vnd.github+json')
printf '%s\n' '--- current pull request body and metadata ---'
curl -fsSL "${headers[@]}" "$base/pulls/$pr" |
  jq '{number,state,title,updated_at,body,commits,changed_files}'
printf '%s\n' '--- issue comments ---'
curl -fsSL "${headers[@]}" "$base/issues/$pr/comments?per_page=100" |
  jq -r '.[] | "\(.created_at) \(.user.login):\n\(.body)\n---"'
printf '%s\n' '--- review summaries and bodies ---'
curl -fsSL "${headers[@]}" "$base/pulls/$pr/reviews?per_page=100" |
  jq -r '.[] | "\(.submitted_at) \(.user.login) [\(.state)]:\n\(.body)\n---"'

Repository: NVIDIA-NeMo/nemo-platform

Length of output: 18666


Align the README with the validation scope.

The subprocess path was live-validated, including subagent delegation and extract_iocs. Docker/Kubernetes packaging remains unvalidated. Update Status and mark the container commands as unvalidated, or test them before publishing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/nemo-agents/examples/email-phishing-fabric/README.md` around lines 68
- 109, Update the README’s Status section to state that the subprocess path,
subagent delegation, and extract_iocs were live-validated, while
Docker/Kubernetes packaging remains unvalidated. Clearly label the container
deployment commands under the Container section as unvalidated, unless those
workflows are actually tested before publishing.

Source: Coding guidelines

…nt-config/

Move the Fabric (spec-v1) email-phishing example from the top-level examples
dir into nemo-agent-config/email-phishing-agent/, alongside calculator-agent —
that directory is where nemo-agents-spec-v1 (Fabric) examples live; the
top-level dir holds NAT examples. Adopt the sibling's conventions:

- flat mcps/ package (mcps.iocs) instead of src/ layout; extract_iocs util +
  FastMCP server combined; console script email-phishing-iocs
- package nemo-agent-config-example-email-phishing; mcp>=1.28.1,<2
- agent renamed email-phishing-agent; mcp url email-phishing-iocs (default
  harness_native exposure); ATOF telemetry like the sibling
- build_dataset.py source path fixed for the new depth

Addresses review: the -fabric suffix was redundant and the example was
misfiled at the top level.

Signed-off-by: Nathan Walston <nwalston@nvidia.com>
@walston

walston commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

Relocated the example to plugins/nemo-agents/examples/nemo-agent-config/email-phishing-agent/ (was top-level email-phishing-fabric). That dir is where spec-v1/Fabric examples live (sibling: calculator-agent); top level is NAT. Adopted sibling conventions: flat mcps/ package, console email-phishing-iocs, agent email-phishing-agent, ATOF telemetry. Re-verified: AgentConfig validate + Fabric translate, ty/ruff clean, extract_iocs 8/8, and mcps.iocs/mcps.calculator coexist as namespace packages.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
plugins/nemo-agents/examples/nemo-agent-config/email-phishing-agent/mcps/iocs.py (1)

38-57: ⚠️ Potential issue | 🟠 Major

Handle malformed URL candidates before calling urlsplit.

The URL regex accepts candidates such as https://exa/mple.com. urlsplit() raises ValueError for this input, so one crafted email can make the MCP tool fail. Catch ValueError around parsing and hostname access, skip candidates without a hostname, and add a regression test.

Proposed fix
-        parsed = urlsplit(url)
-        hostname = parsed.hostname
+        try:
+            parsed = urlsplit(url)
+            hostname = parsed.hostname
+        except ValueError:
+            continue
+        if not hostname:
+            continue

This repeats the unresolved malformed-URL finding from the previous review.

#!/bin/bash
set -euo pipefail

target="plugins/nemo-agents/examples/nemo-agent-config/email-phishing-agent/mcps/iocs.py"
tests="plugins/nemo-agents/examples/nemo-agent-config/email-phishing-agent/tests/test_extract_iocs.py"

rg -n -C 6 'urlsplit|_URL_RE|hostname|def extract_iocs' "$target"
rg -n -i 'malformed|fullwidth|ValueError|exa' "$tests" || true

uv run python - <<'PY'
from urllib.parse import urlsplit

try:
    urlsplit("https://exa/mple.com")
except ValueError:
    pass
else:
    raise SystemExit("expected ValueError")
PY
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@plugins/nemo-agents/examples/nemo-agent-config/email-phishing-agent/mcps/iocs.py`
around lines 38 - 57, Update extract_iocs so each URL candidate is parsed with
urlsplit inside ValueError handling, skipping malformed candidates and any
result without a hostname while preserving valid URL and domain extraction. Add
a regression test in test_extract_iocs.py covering a candidate such as
https://exa/mple.com and confirming the tool does not fail.
🧹 Nitpick comments (2)
plugins/nemo-agents/examples/nemo-agent-config/email-phishing-agent/agent.yaml (1)

69-79: 🩺 Stability & Availability | 🔵 Trivial

Verify the MCP command in packaged deployments.

agent.yaml resolves email-phishing-iocs from PATH. The reported validation covers subprocess mode, but it does not prove that Docker or Kubernetes images install the console script and preserve the MCP handshake. Run a packaged smoke test that invokes extract_iocs.

Based on the PR objectives, Docker/Kubernetes packaging remains unvalidated.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@plugins/nemo-agents/examples/nemo-agent-config/email-phishing-agent/agent.yaml`
around lines 69 - 79, Add a packaged-deployment smoke test for the MCP server
configured by the iocs entry in agent.yaml, covering Docker or Kubernetes
packaging rather than only subprocess mode. Build/package the image, verify the
email-phishing-iocs console script is available on PATH, and invoke extract_iocs
through the stdio MCP handshake to confirm the tool works end to end.
plugins/nemo-agents/examples/nemo-agent-config/email-phishing-agent/README.md (1)

1-3: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Keep the README within one Diataxis quadrant.

The page combines architecture explanation, tuning reference, and how-to instructions. Choose one primary quadrant, move other material to linked pages, add Prerequisites before the overview, and add Next Steps after Status.

As per coding guidelines, each documentation page must use one Diataxis quadrant, list prerequisites at the top, and include Next Steps at the end.

Also applies to: 35-41, 109-117

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@plugins/nemo-agents/examples/nemo-agent-config/email-phishing-agent/README.md`
around lines 1 - 3, Refocus the README on a single Diataxis quadrant, moving
architecture and tuning reference material to linked documentation pages while
retaining only the chosen page type’s content. Add a Prerequisites section
before the overview, and add a Next Steps section after Status with links to the
relocated material or follow-up guidance.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@plugins/nemo-agents/examples/nemo-agent-config/email-phishing-agent/email-phishing-eval.yml`:
- Around line 5-7: Update the commented evaluation command to use the active
email-phishing-agent example path and agent name instead of
email-phishing-fabric, and rename the associated output directory from the stale
identifier to email-phishing-agent to keep generated artifacts consistent.

---

Outside diff comments:
In
`@plugins/nemo-agents/examples/nemo-agent-config/email-phishing-agent/mcps/iocs.py`:
- Around line 38-57: Update extract_iocs so each URL candidate is parsed with
urlsplit inside ValueError handling, skipping malformed candidates and any
result without a hostname while preserving valid URL and domain extraction. Add
a regression test in test_extract_iocs.py covering a candidate such as
https://exa/mple.com and confirming the tool does not fail.

---

Nitpick comments:
In
`@plugins/nemo-agents/examples/nemo-agent-config/email-phishing-agent/agent.yaml`:
- Around line 69-79: Add a packaged-deployment smoke test for the MCP server
configured by the iocs entry in agent.yaml, covering Docker or Kubernetes
packaging rather than only subprocess mode. Build/package the image, verify the
email-phishing-iocs console script is available on PATH, and invoke extract_iocs
through the stdio MCP handshake to confirm the tool works end to end.

In
`@plugins/nemo-agents/examples/nemo-agent-config/email-phishing-agent/README.md`:
- Around line 1-3: Refocus the README on a single Diataxis quadrant, moving
architecture and tuning reference material to linked documentation pages while
retaining only the chosen page type’s content. Add a Prerequisites section
before the overview, and add a Next Steps section after Status with links to the
relocated material or follow-up guidance.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 184d58cd-1942-4a96-b45a-32592020baeb

📥 Commits

Reviewing files that changed from the base of the PR and between f6f054b and 0ea8c8c.

⛔ Files ignored due to path filters (2)
  • plugins/nemo-agents/examples/nemo-agent-config/email-phishing-agent/data/smaller_test.csv is excluded by !**/*.csv
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • plugins/nemo-agents/examples/nemo-agent-config/email-phishing-agent/README.md
  • plugins/nemo-agents/examples/nemo-agent-config/email-phishing-agent/agent.yaml
  • plugins/nemo-agents/examples/nemo-agent-config/email-phishing-agent/data/build_dataset.py
  • plugins/nemo-agents/examples/nemo-agent-config/email-phishing-agent/email-phishing-eval.yml
  • plugins/nemo-agents/examples/nemo-agent-config/email-phishing-agent/mcps/iocs.py
  • plugins/nemo-agents/examples/nemo-agent-config/email-phishing-agent/pyproject.toml
  • plugins/nemo-agents/examples/nemo-agent-config/email-phishing-agent/tests/test_extract_iocs.py
  • pyproject.toml

Parts table, port-it steps, and mirrored Platform-CLI / Studio usage sections
(same five beats: register, deploy, invoke, observe, tune & evaluate). Studio
create/deploy beats marked pending the gallery tile (ASTD-08).

Signed-off-by: Nathan Walston <nwalston@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@plugins/nemo-agents/examples/nemo-agent-config/email-phishing-agent/README.md`:
- Around line 51-53: Add a “Next Steps” section immediately after the Status
section in the email-phishing-agent README, including cross-links to the related
registration, evaluation, and trace-inspection documentation. Ensure this
section is the final content on the page.
- Around line 31-33: Move the “Prereqs” line from the “Use it in Platform (CLI)”
section to directly after the README introduction, before all other
documentation sections, while preserving its existing prerequisite details.
- Around line 12-30: Keep this README focused on a single Diataxis documentation
type by retaining either the component reference or the porting guidance, not
both. Move the other section to a dedicated documentation page and add a clear
cross-link between the pages, preserving the existing details and examples.
- Around line 36-39: Validate the documented Docker/Kubernetes deployment,
Studio workflow, and evaluation-judge tuning steps in the email-phishing-agent
README before presenting them as supported usage. If they cannot be tested, move
the affected instructions near the untested-workflows note into an explicitly
marked draft or unverified section, including the blocks around Deploy, Tune &
evaluate, and the corresponding Studio/evaluation steps.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 363bb4a3-9503-4d28-b7ca-2140db67067e

📥 Commits

Reviewing files that changed from the base of the PR and between 0ea8c8c and 4c28abc.

📒 Files selected for processing (1)
  • plugins/nemo-agents/examples/nemo-agent-config/email-phishing-agent/README.md

Comment on lines +12 to +30
## Parts

| Path | What | Why |
|---|---|---|
| `agent.yaml` | The `nemo-agents-spec-v1` config: harness, sub-agent, model, MCP server, telemetry | The single tunable surface — prompts + hyperparameters |
| `mcps/iocs.py` | `extract_iocs` (pure regex) + FastMCP stdio server | The one real tool; URL/domain extraction incl. the sender |
| `pyproject.toml` | Packages `mcps/`; exposes console `email-phishing-iocs` | Makes the tool resolvable at runtime |
| `data/smaller_test.csv` | Labeled emails with an assembled `email` column (`From:`/`Subject:`/body) | Eval input; keeps the sender (a top phishing tell) |
| `data/build_dataset.py` | Rebuilds that column from the upstream NAT dataset | Regenerate after changing the assembly |
| `email-phishing-eval.yml` | Eval config; `question_key: email` | Scores verdicts against the `label` column |
| `tests/test_extract_iocs.py` | Unit tests for the tool | Guards the extractor |

## Port it (to your own agent)

1. **Copy** this directory to `nemo-agent-config/<your-agent>/`.
2. **Rename** in `pyproject.toml` (`name`, `[project.scripts]` console), `agent.yaml` (`name`, `project`, and `mcp.servers.<n>.url` → your console), and your tool in `mcps/`.
3. **Register** as a workspace member: add the path to root `pyproject.toml` `members`, then `uv sync --all-packages` (installs your console into `.venv` for local runs).
4. **Point** `data/` + `email-phishing-eval.yml` at your dataset.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Keep this page in one Diataxis quadrant.

Lines 12-30 combine component reference content with porting instructions. Keep this README as one documentation type. Move the other content to dedicated pages and add cross-links.

As per coding guidelines, each documentation page should fit one Diataxis quadrant.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@plugins/nemo-agents/examples/nemo-agent-config/email-phishing-agent/README.md`
around lines 12 - 30, Keep this README focused on a single Diataxis
documentation type by retaining either the component reference or the porting
guidance, not both. Move the other section to a dedicated documentation page and
add a clear cross-link between the pages, preserving the existing details and
examples.

Source: Coding guidelines

Comment thread plugins/nemo-agents/examples/nemo-agent-config/email-phishing-agent/README.md Outdated
Comment thread plugins/nemo-agents/examples/nemo-agent-config/email-phishing-agent/README.md Outdated
Comment on lines +51 to +53
## Status

Live-validated for `--mode subprocess` (deploy → invoke → correct verdict; trace + `extract_iocs` call confirmed). Not yet exercised: container (`docker`/`k8s`) packaging, the Studio *Create Example* tile (ASTD‑08), and eval judge tuning (weights/prompt are starters).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add a Next Steps section.

Add cross-links to related registration, evaluation, and trace-inspection documentation after the status section.

As per coding guidelines, each documentation page must end with a Next Steps section with cross-links.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@plugins/nemo-agents/examples/nemo-agent-config/email-phishing-agent/README.md`
around lines 51 - 53, Add a “Next Steps” section immediately after the Status
section in the email-phishing-agent README, including cross-links to the related
registration, evaluation, and trace-inspection documentation. Ensure this
section is the final content on the page.

Source: Coding guidelines

walston added 3 commits August 6, 2026 10:25
Lead with a Parts table that pairs each file with what to change, an explicit
cross-file 'keep in sync' note (console name; workspace member), and a compact
mirrored Platform/Studio run to validate the swap.

Signed-off-by: Nathan Walston <nwalston@nvidia.com>
…torial

Single-quadrant tutorial (prerequisites-first, numbered steps with expected
outcomes, Next Steps) mirroring the calculator-agent sibling; plain markdown
(these example READMEs render on GitHub, not Sphinx, so no MyST tab-sets). Move
the 'swap it for your own' how-to into CUSTOMIZE.md, cross-linked from Next
Steps. Addresses CodeRabbit Diataxis/prereqs/next-steps/fence/quoting findings.

Signed-off-by: Nathan Walston <nwalston@nvidia.com>
- iocs.py: guard urlsplit() against unparseable netlocs
- build_dataset.py: reject blank sender; fail on duplicate subjects (id_key)
- email-phishing-eval.yml: replace stale email-phishing-fabric identifiers

Signed-off-by: Nathan Walston <nwalston@nvidia.com>
def test_url_and_its_host_are_both_reported():
result = extract_iocs("Click http://malicious-link.example.com/claim to continue.")
assert result["urls"] == ["http://malicious-link.example.com/claim"]
assert "malicious-link.example.com" in result["domains"]
def test_sender_domain_is_found_in_a_from_line():
# The sender is a top phishing tell; extract_iocs must surface its domain.
result = extract_iocs("From: security-alerts@bank-verify.example.net\nVisit corp.example.org")
assert "bank-verify.example.net" in result["domains"]
# The sender is a top phishing tell; extract_iocs must surface its domain.
result = extract_iocs("From: security-alerts@bank-verify.example.net\nVisit corp.example.org")
assert "bank-verify.example.net" in result["domains"]
assert "corp.example.org" in result["domains"]
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor
Suite Lines Covered Line Rate Branch Rate
Unit Tests 31260/39870 78.4% 62.8%
Integration Tests 18200/37822 48.1% 20.6%

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants