Apple Flow

Goal: Stop outbound iMessage echoes (especially attributedBody fallback rows) from being re-processed as new inbound commands/tasks.

iMessage Echo Loop Hardening Implementation Plan

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: Stop outbound iMessage echoes (especially attributedBody fallback rows) from being re-processed as new inbound commands/tasks.

Architecture: Keep the current daemon flow, but harden two boundaries: (1) make outbound echo matching tolerant to partial/decoded text fragments, and (2) improve attributedBody decoding so we prefer stable human-readable runs over command-token-biased fragments. Validate with targeted regression tests plus full suite.

Tech Stack: Python 3.11, pytest, asyncio, SQLite chat.db ingestion, AppleScript-based iMessage egress


Task 1: Add Echo Fragment Regression Test

Files:

  • Modify: tests/test_egress.py
  • Test: tests/test_egress.py

Step 1: Write the failing test

def test_recent_outbound_matches_long_fragment_from_same_send(monkeypatch):
    sent_calls = []

    def fake_send(_recipient: str, _text: str) -> None:
        sent_calls.append((_recipient, _text))

    egress = IMessageEgress(max_chunk_chars=1200, suppress_duplicate_outbound_seconds=120)
    monkeypatch.setattr(egress, "_osascript_send", fake_send)

    full_text = (
        "Here is a long implementation answer that includes task: and project: tokens "
        "but should still be treated as one outbound assistant send. " * 25
    )
    egress.send("+15551234567", full_text)

    fragment = full_text[180:700]  # simulates attributedBody-decoded partial run
    assert egress.was_recent_outbound("+15551234567", fragment) is True

Step 2: Run test to verify it fails

Run: pytest tests/test_egress.py::test_recent_outbound_matches_long_fragment_from_same_send -v Expected: FAIL (fragment not currently matched as recent outbound)

Step 3: Write minimal implementation

# In IMessageEgress:
# - Track normalized outbound texts with timestamps.
# - In was_recent_outbound(), after exact fingerprint check, use
#   same-sender containment matching for long normalized strings.

Step 4: Run test to verify it passes

Run: pytest tests/test_egress.py::test_recent_outbound_matches_long_fragment_from_same_send -v Expected: PASS

Step 5: Commit

git add tests/test_egress.py src/apple_flow/egress.py
git commit -m "fix: suppress attributedBody echo fragments from outbound iMessages"

Task 2: Harden attributedBody Candidate Selection

Files:

  • Modify: src/apple_flow/ingress.py
  • Modify: tests/test_ingress_attributed.py
  • Test: tests/test_ingress_attributed.py

Step 1: Write the failing test

def test_decode_attributed_body_prefers_long_human_text_over_command_biased_fragment():
    long_text = "this is a long human sentence without command prefix " * 12
    short_command = "task: create project docs"
    blob = (
        b"\\x04\\x0bstreamtyped\\x08NSString\\x01\\x95\\x84\\x01"
        + short_command.encode("ascii")
        + b"\\x86\\x84\\x01\\x95\\x84\\x01"
        + long_text.encode("ascii")
        + b"\\x86\\x84"
    )
    decoded = IMessageIngress._decode_attributed_body(blob)
    assert long_text[:40].strip() in decoded

Step 2: Run test to verify it fails

Run: pytest tests/test_ingress_attributed.py::test_decode_attributed_body_prefers_long_human_text_over_command_biased_fragment -v Expected: FAIL (decoder currently favors command-token fragment)

Step 3: Write minimal implementation

# In _decode_attributed_body():
# - Remove heavy command-token bias from scoring.
# - Increase metadata penalties.
# - Prefer longer human-readable runs with whitespace/alpha signals.

Step 4: Run test to verify it passes

Run: pytest tests/test_ingress_attributed.py::test_decode_attributed_body_prefers_long_human_text_over_command_biased_fragment -v Expected: PASS

Step 5: Commit

git add src/apple_flow/ingress.py tests/test_ingress_attributed.py
git commit -m "fix: stabilize attributedBody fallback scoring"

Task 3: Verify Poll-Loop Echo Suppression Safety

Files:

  • Modify: tests/test_daemon_startup.py
  • Test: tests/test_daemon_startup.py

Step 1: Write the failing test

@pytest.mark.asyncio
async def test_poll_loop_ignores_fragment_echo_from_recent_outbound():
    # Arrange daemon with real IMessageEgress marker + inbound fragment message.
    # Assert orchestrator is never called for that row.

Step 2: Run test to verify it fails

Run: pytest tests/test_daemon_startup.py::test_poll_loop_ignores_fragment_echo_from_recent_outbound -v Expected: FAIL (fragment currently dispatches to orchestrator)

Step 3: Write minimal implementation

# If Task 1 implementation is complete, no daemon code changes may be needed.
# Keep this task to ensure behavior is exercised at poll-loop boundary.

Step 4: Run test to verify it passes

Run: pytest tests/test_daemon_startup.py::test_poll_loop_ignores_fragment_echo_from_recent_outbound -v Expected: PASS

Step 5: Commit

git add tests/test_daemon_startup.py
git commit -m "test: cover poll-loop suppression of attributedBody echo fragments"

Task 4: Full Verification + Live iMessage Smoke

Files:

  • Modify: docs/plans/2026-03-05-imessage-echo-loop-hardening.md (checklist/status only if needed)
  • Test: entire test suite + live runtime logs

Step 1: Run focused regressions

Run: pytest tests/test_egress.py tests/test_ingress_attributed.py tests/test_daemon_startup.py -q Expected: PASS

Step 2: Run full test suite

Run: pytest -q Expected: PASS

Step 3: Live send smoke test

Run:

PYTHONPATH=src python3 - <<'PY'
from apple_flow.egress import IMessageEgress
egress = IMessageEgress()
egress.send("+15416007167", "[apple-flow smoke] iMessage egress verification")
print("sent")
PY

Expected: send succeeds; daemon log shows outbound send and later echo suppression (no recursive task/project handling).

Step 4: Verify logs show no loop

Run: rg -n "Ignoring probable outbound echo|Handled rowid=.*kind=task|Unhandled iMessage dispatch failure" logs/apple-flow.err.log | tail -n 80 Expected: smoke echo is ignored; no immediate self-triggered task churn from smoke message.

Step 5: Commit

git add src/apple_flow/egress.py src/apple_flow/ingress.py tests/test_egress.py tests/test_ingress_attributed.py tests/test_daemon_startup.py docs/plans/2026-03-05-imessage-echo-loop-hardening.md
git commit -m "fix: harden iMessage echo suppression for attributedBody fallback rows"