← Back to Blog
Building in Public

Discovery Over Assumptions: When Investigating First Saves Days

christian crumlish•
A robot navigating a maze by investigating the correct path first, completing in minutes what guessing would take days.
“This way!”

Discovery Over Assumptions: When Investigating First Saves Days

October 15

Wednesday morning at 7:42 AM, my Chief Architect and I began Sprint A2 planning. Five issues scheduled over two days.

By 10:51 AM, we’d discovered three of those issues were already complete. By 5:00 PM, we’d completed what should have been 12–17 hours of work in 15 minutes by questioning a version number that didn’t exist.

The pattern: Investigate thoroughly. Question assumptions. Discover work is 75% done. Complete efficiently.

This is the story of saving days by verifying before implementing — and why “discovery over assumptions” compounds into massive time savings, at least when compared with projections made in ignorance of our earlier, nearly completed work.

The first “already complete” moment

Chief Architect reviewing Sprint A2 scope: CORE-TEST-CACHE #216 scheduled as first item.

Quick investigation: Issue already complete. Removed from sprint.

Time saved: 30 minutes of unnecessary work.

The real “culprit” was our incomplete tracking of finished work in the past.

But this set the pattern for Wednesday: Check thoroughly before assuming work is needed.

The second “already complete” moment

Issue #142: Add get_current_user() method to NotionMCPAdapter.

Code Agent begins Phase -1 investigation. 25 minutes later: Discovery.

The functionality already exists:

  • self._notion_client.users.me() used in test_connection() (line 110)
  • self._notion_client.users.me() used in get_workspace_info() (line 135)

The “problem”: Not that functionality was missing. That it wasn’t exposed as a public method.

Solution: Extract existing pattern. Create public method wrapping what already works.

Phase 1 implementation: 3 minutes (estimated 20 minutes)

Not building from scratch. Not researching APIs. Not testing approaches. Just: expose what works.

The 75% pattern strikes again. Code isn’t missing. It’s buried.

Total time for Issue #142: 78 minutes (vs estimated 70 minutes). But the work was extraction, not creation.

The third “already complete” moment

Issue #136: Remove hardcoding from Notion integration.

Lead Developer begins verification instead of reimplementation. 15 minutes later: Discovery.

Verification results:

  • ✅ Hardcoded IDs removed: 0 in production code
  • ✅ Config schema implemented: NotionUserConfig + ADR-027
  • ✅ Code refactored: Evolved into better architecture
  • ✅ Backward compatibility: Graceful degradation
  • ✅ Documentation updated: Comprehensive & excellent
  • ✅ Tests passing: 10/11 (91%, 1 skipped for real API)

Child issues verified:

  • #139 (PM-132): Config loader CLOSED ✅
  • #143: Refactoring complete (implicit) ✅
  • #141: Testing/docs complete ✅

My reflection at 10:30 AM: “If I had properly read these parents and children before I might have saved us all some time!”

Honest self-assessment. The work was complete. I just hadn’t verified it properly.

Time saved by verification: An entire day of reimplementation.

The version-confusion saga

Issue #165: Upgrade Notion SDK to version 5.0.0 for API 2025–09–03 support.

Phase −1 estimate: 12–17 hours for migration (breaking changes expected).

Code Agent begins investigation. Tries to upgrade: pip install notion-client>=5.0.0

Error: Version 5.0.0 doesn’t exist on PyPI.

The natural impulse: Assume you’re searching wrong. Check package name. Try different queries. Spend hours debugging your approach.

The correct response: Question the requirement.

Investigation reveals:

  • TypeScript SDK: Uses 5.0.0 versioning
  • Python SDK: Latest is 2.5.0 (August 2025)
  • Issue description: Conflated API version (2025–09–03, correct) with SDK version (5.0.0, incorrect)

The confusion: Two different things both called “version.”

  • API version: 2025–09–03 (the date-based API versioning)
  • SDK version: 2.5.0 for Python, 5.0.0 for TypeScript

Resolution: Upgrade Python SDK 2.2.1 → 2.5.0, add API version parameter.

Finding eliminated: Hours of searching for non-existent package.

This was somewhere between ordinary confusion and special way LLMs sometimes misread their own summaries.

Philosophy validated: When instructions seem wrong, verify reality. Don’t assume your understanding is broken.

Systematic scope reduction

With version confusion resolved, Code Agent continues investigation.

Original estimate: 2–3 hours for SDK upgrade (assuming breaking changes).

Investigation reveals: NO breaking changes in SDK 2.2.1 → 2.5.0.

Changes are all additive:

  • Python 3.13 support added
  • File upload capabilities added
  • Token format cosmetic improvements

Revised scope: 30–45 minutes for SDK + API version.

But there’s more. The API version implementation required understanding a subtle detail…

The ClientOptions discovery

Phase 1-Extended: Add API version 2025–09–03 support.

Testing reveals critical API requirement:

Dict format fails:

Client(auth=key, options={"notion_version": "2025-09-03"})

Error: “API token invalid”

Object format succeeds:

Client(auth=key, ClientOptions(notion_version="2025-09-03"))

Works perfectly.

Not documented in common examples. Found through systematic testing.

The distinction: SDK expects ClientOptions object instance, not dict with same keys.

15-minute discovery prevented hours of authentication debugging.

When APIs reject valid values with authentication errors, suspect object type mismatch, not credential problems.

Actual implementation time: 15 minutes (vs original 2–3 hour estimate).

Efficiency: 12x faster than original estimate.

Method: Verify assumptions → reduce scope to essentials → execute surgically.

No can-kicking

With SDK upgrade easier than expected, I made a decision.

“I am ok with proceeding AND we should also address the data source id issue after that (and not kick the can further). We are already getting off pretty light today!”

Remember: I am an inchworm.

Context: Phase 1-Extended (data_source_id implementation) was originally scheduled for Sprint A3.

But we were ahead of schedule. SDK upgrade took 15 minutes instead of hours.

Use extra time to complete more work, not to relax.

Result: Full Phase 1-Extended completed same day.

The bonus discovery at 5:00 PM: Workspace already migrated to multi-source databases! The get_data_source_id() call returned immediately: 25e11704-d8bf-8022-80bb-000bae9874dd

No hypothetical code. All tested with production state. Immediately ready.

Triple-enforcement: Belts, suspenders, and rope

During the day, another small process issue surfaced. The pre-commit routine (run fix-newlines.sh before committing) was getting lost post-compaction.

At 5:44 PM, I observed: “I thought we had a script routine we run now before committing?” (I really get frustrated when I think we’ve solved a problem but we failed to make it repeatable habit.)

The problem: Single-point documentation doesn’t work when agents are stateless.

My direction: “Let’s do all three options, as belts, suspenders, and rope :D”

Three independent layers implemented:

Layer 1 — Belt (BRIEFING-ESSENTIAL-AGENT.md): Critical section added after role definition. First thing agents see when they read briefing.

Layer 2 — Suspenders (scripts/commit.sh): Executable wrapper script. Run one command: ./scripts/commit.sh. Autopilot mode—script handles fix-newlines.sh → git add -u → ready to commit.

Layer 3 — Rope (session-log-instructions.md): Pre-Commit Checklist section. Visible during session logging when agents document their work.

Philosophy: Important processes need redundant discovery mechanisms.

If agent misses one touchpoint, catches at another. Routine becomes unavoidable across multiple entry points.

Verification: Used routine for next commit. Success on first try. ✅

Impact:

  • Before: Pre-commit fails → auto-fix → re-stage → re-commit (2x work)
  • After: Run fix-newlines.sh first → commit succeeds (1x work)

Discoverability: Unavoidable. Can’t miss all three touchpoints.

This is mature process design: making important work impossible to skip by providing multiple discovery paths.

Honest issue triage

Evening testing of Issue #215 (error handling) revealed an issue: IntentService initialization failure (LLM service not registered).

The investigation: Is this caused by our Phase 1 changes?

Code Agent’s assessment: Pre-existing issue, not caused by Phase 1.

The triage:

  • validation_error() function: Working correctly ✅
  • internal_error() function: Working correctly ✅
  • HTTP status codes: Fixed properly (was 200, now 422/500) ✅
  • IntentService initialization: Pre-existing bug, documented

No hiding. No claiming causation without evidence. Clear separation between new work and inherited issues.

Result: Honest technical debt documentation enabling proper prioritization.

My decision at 9:44 PM: “Call it a night, pick up tomorrow fresh.”

What the numbers reveal

Wednesday’s accounting:

Issues completed: 4 (#142, #136, #165 Phase 1, #109)

Issues started: 1 (#215 Phase 0–1)

Time saved by verification:

  • TEST-CACHE: 30 minutes (already complete)
  • Issue #136: Full day (verified complete vs reimplemented)
  • Issue #142: Creation time vs extraction time
  • Issue #165: 12–17 hours estimate → 15 minutes actual (12x faster)

Tests added: 13 for #142, 40+ for #215

Code deleted: 22,449 bytes (github_agent.py) + 190 lines (router complexity)

Architecture improvements: Router 451 → 261 lines (42% reduction)

Session duration: 7:42 AM — 9:44 PM (~14 hours duration, but only an hour or so of my attention in aggregatk)

But the numbers don’t capture the pattern: Three “already complete” discoveries saved multiple days of unnecessary implementation.

The version confusion resolution saved hours of searching for non-existent packages.

The ClientOptions discovery saved hours of authentication debugging.

The methodology: Investigate first. Question assumptions. Discover reality. Then implement surgically.

The 75% pattern strikes again

All three “already complete” moments demonstrate the pattern: Most code you encounter is 75% complete, then abandoned.

Issue #142: Functionality existed in two places, just needed exposure as public method.

Issue #136: Complete through child issues (#139, #143, #141), just never formally verified and closed.

TEST-CACHE: Already done, just not communicated.

The work wasn’t missing. It was:

  • Buried in existing code
  • Completed through other issues
  • Done but not documented
  • Implemented but not exposed

Investigation finds what assumptions miss.

Time saved Wednesday: Multiple days of reimplementation through systematic verification.

What verification before implementation looks like

Wednesday demonstrated a specific methodology:

Step 1: Read issue description thoroughly

Step 2: Investigate current state (don’t assume it’s broken)

Step 3: Verify assumptions (especially version numbers, requirements)

Step 4: Check child issues and related work

Step 5: Question requirements that seem wrong

Step 6: Reduce scope to actual gaps

Step 7: Implement surgically

The pattern applies broadly:

Before adding a feature: Does similar functionality exist?

Before upgrading a library: What actually changed between versions?

Before debugging authentication: Check object types, not just values

Before starting implementation: Are child issues already complete?

Every hour spent investigating prevents days spent reimplementing.

The “when instructions seem wrong” principle

The version confusion saga (5.0.0 doesn’t exist) demonstrates an important principle:

When instructions contradict reality, verify reality is wrong before assuming your understanding is broken.

Natural impulse: “I must be searching wrong.” Correct response: “Does this version actually exist?”

The investigation sequence:

  1. Try to install version 5.0.0
  2. Error: Version doesn’t exist
  3. Check PyPI manually
  4. Confirm: Python SDK latest is 2.5.0
  5. Question: Why does issue say 5.0.0?
  6. Discover: TypeScript SDK uses 5.0.0, Python uses 2.x
  7. Resolve: Issue description conflated API version with SDK version

This isn’t about assuming instructions are wrong. It’s about verifying when reality contradicts instructions.

The cost of questioning: Minutes to verify. The cost of not questioning: Hours searching for non-existent packages.

Wednesday’s efficiency came from systematic reality-checking.

What Wednesday teaches about assumptions

The three “already complete” discoveries, version confusion resolution, and ClientOptions discovery all share a pattern: Assumptions hide reality.

Assumed: TEST-CACHE needs implementation

Reality: Already complete

Assumed: get_current_user() needs building from scratch

Reality: Functionality exists, needs exposure

Assumed: Issue #136 needs reimplementation

Reality: Complete through child issues

Assumed: SDK 5.0.0 exists and has breaking changes

Reality: Python uses 2.5.0, no breaking changes

Assumed: Dict format should work for options

Reality: SDK requires ClientOptions object

The methodology that works: Question everything. Verify before implementing. Accept 15 minutes of investigation over days of unnecessary work.

My self-assessment at 10:30 AM captured it: “If I had properly read these parents and children before I might have saved us all some time!”

Honest acknowledgment. The verification tools existed. I just needed to use them systematically.

The cumulative effect of small process improvements

Wednesday added another layer to the compound process improvements:

Sunday (Oct 12): Pre-commit hooks catching issues before push

Monday (Oct 13): Weekly audit + metrics script (self-maintaining docs)

Tuesday (Oct 14): Pre-commit newline fix (2–3 minutes per commit)

Wednesday (Oct 15): Triple-enforcement (belts, suspenders, rope)

Each improvement builds on previous work:

  • Pre-commit hooks need newline fixes
  • Newline fixes need discoverable routine
  • Discoverable routine needs triple-enforcement

(These process improvements tend to emerge organically from friction points.)

The result: Process becoming systematically more efficient through accumulated small improvements.

Impact compounds. Each fix saves time forever. Each enforcement layer makes important work harder to skip.

What comes next

Thursday: Continue Sprint A2 with remaining items.

But Wednesday established important patterns:

Discovery over assumptions: Three “already complete” moments saved days

Question version numbers: 5.0.0 vs 2.5.0 saved hours

Systematic scope reduction: 12–17 hours → 15 minutes (12x faster)

Triple-enforcement: Important processes unavoidable

Honest triage: Pre-existing vs caused-by clearly separated

The methodology validated: Investigate thoroughly, question assumptions, discover reality, implement surgically.

The efficiency gained: Multiple days saved through systematic verification.

The process matured: Triple-enforcement making important work impossible to skip.

The pattern recognized: Work is 75% complete more often than assumed. Verify before creating.

Wednesday proved what systematic investigation enables: discovering you’re mostly done and finishing efficiently rather than starting from scratch unnecessarily.

← Back to BlogAlso available on Medium