ROOT CAUSE:
The bot would permanently softlock itself on UNKNOWN screens (like Trending Audio/Reels)
because 'is_trap' had NO time-decay, and UNKNOWN screen traps persisted forever.
Once 'press back' was trapped on an UNKNOWN screen, the bot was permanently blocked
from escaping any future UNKNOWN screen, causing endless restart loops.
FIXES:
1. Trap Time-Decay: 'is_trap' now forgives Qdrant-persisted traps older than 30 minutes.
This mirrors ContextMemory and prevents permanent dead-ends.
2. UNKNOWN Guard: 'learn_trap' now REFUSES to persist traps for UNKNOWN screens to Qdrant.
They only live in-memory to prevent breaking global navigation.
3. In-Memory Purge: 'force start instagram' now properly calls 'clear_traps()' to wipe
the planner's in-memory traps. Previously they survived restarts, immediately
re-trapping the bot.
Purged 6 ancient traps from Qdrant (one was 8 days old!).
TDD: 3 new tests, full suite 113/114 (1 infra flake)
FOLLOW PLUGIN SAFETY GUARD:
- Added pre-click XML guard: checks button text before clicking
- If button says 'Following'/'Requested'/'Message' → SKIP
(prevents opening dangerous Unfollow/Add-to-Favorites bottom sheet)
- Changed intent from 'tap Follow or Following button' to 'tap follow button'
- This was the root cause of adding users to Close Friends/Favorites
VLM VERIFICATION RESILIENCE:
- VLM false no longer shortcircuits to return False
- VLM verdict is now a SOFT SIGNAL that falls through to structural
delta verification as ground-truth tiebreaker
- Small local VLMs (7B llava) systematically return false for everything
- This was THE root cause of memory poisoning: every action got penalized
because VLM always said 'failed', regardless of actual screen state
- 22 total poisoned Qdrant entries purged across two sessions
ROOT CAUSE CHAIN:
VLM always says false → penalties on every action → confidence < 0.2 →
circuit breaker blocks ALL core actions → bot can't like/follow/comment
→ bot clicks random things → adds to favorites, opens bottom sheets
TDD: 4 new tests, full suite 110/111 (1 infra flake)
CLOSE FRIENDS GUARD:
- Replaced broken text-only detection ('enge freunde'/'close friend')
with STRUCTURAL resource-id marker detection:
* friendly_bubbles_component (feed posts)
* profile_header_close_friend (profile view)
* close_friends_badge (Reels)
* close_friends_star (green star)
- Kept legacy text fallback for edge cases
- TDD: 5 tests including meta-test enforcing structural detection
CONTEXT MEMORY CIRCUIT BREAKER:
- Old system: single failure permanently blocks action (confidence < 0.2 = dead)
- New system: time-based decay — failures older than 1 hour are auto-forgiven
and the poisoned entry is DELETED, allowing re-exploration
- This prevents catastrophic self-sabotage where the bot blocks its own
core actions (tap like, tap follow, tap comment) permanently
- Purged 14 poisoned entries from live Qdrant that were blocking ALL
fundamental interactions
- TDD: 2 meta-tests enforcing time-decay and no permanent blocking
Full suite: 106/107 passed (1 infra flake)
- Removed hardcoded (540,150) fallback in _plan_escape_via_llm
- Added _find_structural_dismiss_target: scans raw XML for clickable
dismiss/cancel/close buttons and extracts coords from bounds attrs
- LLM repeat detection now falls back to structural scan (not magic numbers)
- Temperature increases progressively when LLM is stubborn (0.1 → 0.7)
- Failure history injected as CRITICAL block into LLM system prompt
- Extended pre-commit test discovery to include tests/tdd/
- TDD: 7 new tests including meta-tests proving zero hardcoded coords
- Full suite: 100/100 passed
- Updated e2e_workflow_ctx in conftest to structurally parse screen_type
- Fixed StoryViewPlugin to gracefully handle pre-existing story screens
- Resolved 'LIE DETECTED' failure in story test suite
- Validated structural path transitions for story ring and story exit
P0-2: Kill blank_start: true in config — the nuclear option that wipes ALL learned
knowledge on every run. Replaced with memory_hygiene() selective amnesia that
prunes low-confidence entries while preserving high-confidence learned patterns.
P0-3: Purge all root-level garbage scripts (scratch.py, test_run.py, profile_dump.xml,
resp_dump.json, pytest_output.log, test_output.log, coverage.xml, coverage_e2e.json).
Updated .gitignore to prevent reaccumulation.
P2-2: Replace piecemeal BANNED_SCREENS/REQUIRED_MARKERS with complete Action
Compatibility Matrix (VALID_SCREENS whitelist). Every interaction intent now has
an explicit set of valid screens. Covers like, comment, follow, unfollow, save,
repost across all 14 screen types.
P2-3: Remove non-deterministic 'press back' transitions from HD Map topology for
OTHER_PROFILE, POST_DETAIL, and SEARCH_RESULTS. Add tab transitions to POST_DETAIL.
P2-4: Replace ScreenMemoryDB nuclear 200-entry wipe with LRU eviction.
store_screen() now accepts confidence parameter.
TDD: 22 new tests (all GREEN), 240 unit/tdd/integration passed, 0 regressions.
E2E: 288 passed (+2 fixed), 6 pre-existing LIE DETECTED failures.
- Raised ContentMemoryDB cache threshold from 0.95 to 0.98 to prevent cross-post poisoning.
- Implemented continuous resonance scoring by storing and retrieving 'resonance_score'
in ContentMemoryDB.
- ResonanceEngine now prioritizes high-fidelity raw scores from cache over
discrete 'high/medium/low' mapping.
TDD: 3 tests verifying threshold and scoring logic integrity.
- Fixed .gitignore to correctly handle __pycache__ even when inside whitelisted directories (like tests/).
- Moved pycache ignore patterns to the end of the file to ensure they take absolute precedence over any directory-level whitelists.
- Purged all previously tracked .pyc and __pycache__ files from the git index.
- Cleaned up .hypothesis and .pytest_cache artifacts.
This enforces 'Militärische Git-Disziplin' (Rule 7) and keeps the repo clean of environment-specific junk.
- Raised similarity_threshold from 0.90 to 0.95 in get_screen_type() to eliminate
POST_DETAIL false-positives caused by embedding saturation.
- Implemented purge_stale_screens() with 24h TTL to remove layout drift.
- Added collection size cap (200 points) to store_screen() with automatic wipe
when saturated to clear overfitting bias.
- Triggered purge_stale_screens() on ScreenIdentity initialization.
TDD: 3 new tests for threshold defaults and purge method integrity.
- device_emulator.py: Replace MagicMock(watcher) with explicit WatcherStub
that implements exact u2 watcher chain (when/click/start). Any API drift
now causes AttributeError instead of silent acceptance.
- test_system_sae.py: Inject XML into emulator state instead of bypassing
dump_hierarchy with lambda
- test_behavior_ad_guard.py: Pass XML to fixture, remove lambda override
- test_behavior_scrape_profile.py: Pass XML to fixture, remove lambda override
Result: ZERO unittest.mock imports, ZERO MagicMock instances,
ZERO dump_hierarchy lambdas across entire E2E suite (50 files).
Production bug 2026-05-02 22:19:
Intent 'post author username text (exclude bottom tabs)' contained the
word 'tab' in its parenthetical qualifier. The old check:
is_tab_intent = 'tab' in intent_lower
matched this intent as a TAB navigation intent, activating the Tab
Height Guard which excluded ALL nodes with center_y < 85% of screen.
This nuked the actual clips_author_username node at y=2012, leaving
only 4 irrelevant items for the VLM → bot stuck in a loop unable to
find the author username.
Fix: Replace substring match with precise regex patterns that only
match real tab navigation intents:
- 'tap profile tab', 'tap home tab' (tap + word + tab)
- 'profile tab', 'explore tab' (word + tab)
- 'tab ...' at start of intent
TDD: Red test added first, reproducing exact production state.
81 perception tests + 172 total tests pass.
CONTRACT 6: extract_json() — LLM Response Sanitizer (9 tests)
Previously COMPLETELY untested. Every single LLM response in the entire
system passes through this function. Tests:
- Clean JSON, markdown fences (json+plain), thinking block purge
- Text prefix before JSON, truncated JSON fuzzy recovery
- Garbage/empty/None resilience
CONTRACT 7: _visual_discovery Pre-Filters (8 tests)
The guards INSIDE _visual_discovery() that run BEFORE the VLM ever
sees the candidates. If they fail, the VLM gets garbage and hallucinates.
Tests:
- Area filter: tiny nodes (<200px²) and huge containers (>400000px²)
- SystemUI filter: Android system elements, notifications, battery
- Strict Button Guard: long text captions excluded for button intents
- Grid Item Guard: non-grid elements excluded, fallthrough on no match
- Author/Username Guard: nav tabs excluded for author intents
CONTRACT 8: verify_success Structural Delta (8 tests)
The post-click verification logic that decides if a click actually worked.
Tests:
- Toggle massive XML shift = navigation error (False)
- Toggle small diff = success (True)
- Toggle zero diff = inconclusive (None)
- Follow success with 'Requested' marker (private accounts)
- Follow success with German locale ('Abonniert')
- View post with clips_viewer marker (Reels)
- View post still on grid = inconclusive (None)
- Semantic gate blocks wrong toggle element (caption != like button)
Two critical test additions:
1. test_system_full_lifecycle.py — Full A-to-Z integration test:
Exercises the ENTIRE bot pipeline across 4 screen types (HomeFeed →
Explore → Post Detail → Profile) with a real InstagramEmulator state
machine. Validates plugin execution, session state accumulation,
cognitive stack survival, dopamine session timeout, and post-session
serialization. This is the single most important test in the suite.
2. test_system_coverage_gate.py — Critical Path Coverage Enforcement:
Reads coverage_e2e.json and enforces minimum coverage thresholds on
production-critical modules. Each threshold is tied to a real production
incident. If persistent_list.py drops below 20% coverage, the build
fails — because that's exactly how the sessions.json corruption
bug went undetected.
Usage: pytest tests/e2e --cov=GramAddict.core \
--cov-report=json:coverage_e2e.json
Critical paths enforced:
- session_state.py (>=50%): serialization crash prevention
- persistent_list.py (>=20%): persist() path must be tested
- dopamine_engine.py (>=40%): session timeout logic
- screen_identity.py (>=60%): screen identification
- spatial_parser.py (>=70%): UI element parsing
- intent_resolver.py (>=50%): click decision logic
- q_nav_graph.py (>=25%): navigation integrity
- device_facade.py (>=40%): device interface
Replaces reactive bug-specific tests with 4 structural CONTRACTS
that make entire categories of bugs impossible:
1. Serialization Contract (13 parametrized hostile payloads):
Proves SessionStateEncoder NEVER crashes regardless of what
gets injected onto args (datetime, lambda, bytes, inf, nan, etc.)
2. Persistence Contract:
Forces the REAL persist() path by removing PYTEST_CURRENT_TEST guard.
Validates write → read round-trip with poisoned sessions.
3. Production Parity Contract:
Scans ALL GramAddict source for pytest/test-mode divergence guards.
Any unaudited divergence point fails the build. New guards require
explicit justification in KNOWN_DIVERGENCES registry.
4. Import Integrity Contract:
Imports every single GramAddict module to catch syntax errors,
circular imports, and missing dependencies at test time.
This addresses the root systemic failure: tests were running in a
parallel universe where serialization was silently skipped, allowing
a datetime injection to corrupt sessions.json and kill production.
Root cause: bot_flow.py injected configs.args.global_start_time = datetime.now()
which polluted the args namespace. SessionStateEncoder blindly serialized
args.__dict__ via json.dump, which crashed mid-write on the datetime object,
leaving sessions.json truncated/corrupt. Every subsequent restart failed.
Fixes:
- Remove global_start_time from configs.args (only lives on engine class vars)
- Harden SessionStateEncoder with _sanitize_value() to convert any
non-JSON-serializable type (datetime, timedelta, arbitrary objects) to strings
- Add test_system_session_persistence.py with 4 tests covering the exact
production crash scenario (datetime injection → json.dumps → round-trip)
- Fix test_engine_timeout.py broken GoalExecutor module reference
Bug 1 — VLM Profile Tab Hallucination:
- VLM confused 'Profile' nav tab with post author username
- Added Author Tab Guard to filter_navigation_conflicts()
- Fixed mock LLM to use structural row_feed_photo_profile matching
instead of hardcoded username (was the test lie)
Bug 2 — Like Button 1-Byte Delta Kill:
- Toggle actions (like/save) produce 1-byte XML deltas
- GOAP's MIN_UI_CHANGE_BYTES=50 threshold killed them as 'no change'
- Split interaction gate: interactions use ANY xml diff, navigations
keep the 50-byte threshold
Bug 3 — Empty Username Silently Accepted:
- PostDataExtraction returned 'Post by @' with no warning
- Added 'username_missing' reliability flag to result dict
- Downstream consumers can now detect degraded data quality
Cleanup:
- Removed all debug print() statements from production code
- Replaced with structured logger.debug() calls
PRODUCTION FIX 2026-04-30 12:45:
The entire E2E suite was systematically lying by testing a shell of the bot:
1. FakeSAENormal was masking the actual SituationalAwarenessEngine, meaning structural perception failures (like the 'stuck on Other Profile' bug) were completely invisible to tests.
2. The e2e_cognitive_stack was hardcoded to return None for 80% of engines (NavGraph, ZeroEngine, Telepathic, Darwin, ActiveInference), causing plugins to silently bypass their core logic during tests.
3. get_screenshot_b64 and screenshot() returned None, preventing the VLM stack from even running structural fallback code.
Fix:
- Systematically stripped FakeSAENormal from all 29 E2E workflow tests.
- Built e2e_cognitive_stack_factory to instantiate the REAL Cognitive Stack (GrowthBrain, QNavGraph, ZeroLatencyEngine, etc.) just like in bot_flow.py.
- Patched ONLY the external LLM network requests in conftest.py via mock_llm_network_calls so that structural execution stays 100% real but avoids non-deterministic LLM timeouts.
- Injected a valid 1x1 black image for screenshots.
All 29 E2E tests now execute the true production logic path and pass in 49 seconds.
PRODUCTION BUG 2026-04-30 11:50:
VLM returned action_bar_button_back (desc='Back') for 'tap profile tab'
intent on OTHER_PROFILE screen. This caused account_switcher to navigate
AWAY from the profile instead of opening the account selector bottom
sheet, halting the session.
Root cause: _visual_discovery pre-filter did not exclude navigation
buttons (Back, Close) when the intent was for a tab element.
Fix:
- Added filter_navigation_conflicts() method to IntentResolver
- Guards tab intents by excluding nodes with 'back' in resource_id
or content_desc == 'Back'
- Does NOT apply for 'tap back button' or 'press back' intents
TDD:
- RED: test_intent_resolver_back_guard.py — 2 tests that call
filter_navigation_conflicts on real OTHER_PROFILE XML
- GREEN: filter_navigation_conflicts method + wired into _visual_discovery
- E2E: test_workflow_account_switch.py, test_workflow_vlm_tab_confusion.py
31/31 tests passing.
BEFORE: Giant monolith test files with duplicated device stubs, config
factories, and cognitive stack builders. The infrastructure was copy-pasted
into each file.
AFTER: Clean separation:
- conftest.py: E2EDeviceStub, e2e_device, e2e_cognitive_stack,
e2e_session, e2e_workflow_ctx fixtures
- test_workflow_permission_dialog.py: Permission dialog recovery
- test_workflow_foreign_app.py: Foreign app (Chrome) recovery
- test_workflow_normal_feed.py: Normal post processing
- test_workflow_plugin_integrity.py: Import + interface sanity
Each workflow file is <100 lines. Zero duplication. Every test uses
the shared e2e_workflow_ctx fixture which runs the EXACT code path
from bot_flow.py:942-971.
DELETED: test_full_workflow_hostile_env.py, test_plugin_chain_hostile_env.py
(monolith files replaced by the above)