Skip to content

Design constraint for change-detection / diff_jobs: handle first-run (no baseline) explicitly #21

@prPMDev

Description

@prPMDev

Context

Future feature on roadmap: change detection / temporal tracking. External reviewer flagged a common anti-pattern in stateful MCP tools that we should design around before implementation:

The diff_jobs tool will silently return empty diffs on first run (no baseline = no changes). Seen this bite people multiple times with stateful MCP tools.

The anti-pattern

When a stateful tool (diff, watch, poll) runs with no prior state, the instinct is to return "0 changes." Looks successful. LLM treats it as "nothing to report." User sees empty result, tries again, same thing. Agent loops on empty diffs before giving up or giving misleading answers.

The root issue: an LLM can't distinguish "truly zero changes" from "no baseline to compare against." Both look like success with empty data.

Design requirements for the eventual `diff_jobs` (or equivalent)

1. First-run behavior must NOT return empty-success

Two acceptable options:

Option A: Auto-record baseline on first `fetch_jobs` call

  • Every `fetch_jobs` call writes a timestamped snapshot to disk/cache
  • `diff_jobs` compares current fetch against most recent prior snapshot
  • First run of `diff_jobs` finds a baseline automatically (assuming user has run `fetch_jobs` at least once)
  • Pro: simplest UX, always works
  • Con: storage grows; needs cleanup strategy

Option B: Explicit "no baseline" error

  • `diff_jobs` returns `{status: "error", error: {code: "no_baseline", message: "No baseline stored. Run fetch_jobs first to establish one."}}`
  • Clearly signals initialization dependency
  • Pro: no implicit state, explicit contract
  • Con: user-hostile on first run

Option C (likely best): Hybrid

  • `fetch_jobs` snapshots opt-in via a `record_baseline: true` arg (default false to avoid unwanted writes)
  • `diff_jobs` returns explicit error if no baseline, with guidance on how to record one
  • Clear agency: user or AI decides when to establish state

2. Tool description MUST teach the dependency

LLMs don't infer initialization order from tool names. The `diff_jobs` description must explicitly say:

USE WHEN: comparing current job postings against a prior snapshot.
PREREQUISITE: a baseline must exist. Baselines are recorded automatically on `fetch_jobs` calls with `record_baseline: true`, or via `record_baseline` tool directly.
IF NO BASELINE: this tool returns a clear error with instructions. Do NOT loop — follow the error guidance.

3. Document explicitly which tool establishes state

If we add a `record_baseline` or `save_snapshot` tool, its relationship to `diff_jobs` must be in BOTH tool descriptions. Cross-reference like:

  • `record_baseline.description`: "Call before `diff_jobs` to establish a comparison baseline..."
  • `diff_jobs.description`: "Requires a prior `record_baseline` call or a `fetch_jobs` with snapshot enabled."

Generalized principle for future stateful tools

Stateful tools in MCP must either:

  1. Auto-initialize on first use (hide the state from the AI), or
  2. Return explicit init-required errors with guidance (make the state legible to the AI)

Empty-success on missing state is always wrong. LLM can't distinguish it from legitimate empty result.

Acceptance criteria when this ships

  • First-run behavior is explicit, not silent-empty
  • Tool description teaches the prerequisite chain
  • Error messages include specific next-step guidance
  • Integration test verifies first-run behavior specifically

Credit

External reviewer feedback. Captured as a design constraint so we don't rediscover it the hard way.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestintegrationMCP, API, external tool connectivity

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions