# Phase 4 handoff: rich Twitter and Hacker News delivery to Zulip

## Instruction to the next agent

Resume the Phase 4 Twitter and Hacker News delivery work in the existing repository and working tree. Begin by inspecting the repository, phase plan, current configuration, and all staged, unstaged, and untracked changes. The previous Codex process was killed by the Linux OOM killer while attempting to resume a very large session, so partial work may exist even though an earlier assessment said the rich-formatting changes had not yet begun.

Treat the current working tree as authoritative. Preserve all existing work. Do **not** run `git reset`, `git clean`, discard changes, overwrite files wholesale, switch branches destructively, or assume unrelated dirty files are disposable. Do not deploy, restart production services, modify live jobs, or commit/push until the implementation has been inspected, tested, summarized, and explicitly approved by the owner.

## Project context

- Project: custom external OpenClaw channel plugin and Zulip integration.
- Primary plan: `docs/openclaw-zulip-integration-plan.md`.
- Current phase: Phase 4 — channels, topics, RSS, and proactive delivery.
- Repository/worktree reported previously: `/root/openclaw` on branch `master`, with substantial pre-existing changes from earlier integration phases and runtime testing.
- The repository was already heavily dirty during the last reliable assessment: 23 tracked files changed, with many generated artifacts. Re-check the current state; do not assume those counts remain accurate.
- OpenClaw core changes or another Zulip core patch are out of scope unless an explicit architecture review authorizes them. Continue using the external channel-plugin architecture.

## Production Zulip structure already verified

The following private Zulip channels, subscribers, topics, and numeric IDs were verified through the Zulip API:

| Purpose | Channel | Channel ID | Topic | Canonical destination |
| --- | --- | ---: | --- | --- |
| Twitter curator | `news` | 5 | `twitter` | `stream:5:twitter` |
| Hacker News curator | `news` | 5 | `hacker-news` | `stream:5:hacker-news` |
| Future RSS delivery | `rss` | 6 | `feed` | `stream:6:feed` |

- `news` and `rss` are private and not web-public.
- Subscribers are Yachint (numeric user ID `8`) and Mue (numeric user ID `10`).
- Critical failures and approvals should continue to use the owner DM (`user:8`) so they produce the intended private notification and badge behavior.
- RSS work is explicitly deferred until MonitorRSS data is exported as OPML. Do not implement or migrate RSS as part of this task.

## Existing behavior before the interrupted work

- Hacker News delivery to `news > hacker-news` was validated in production.
- Twitter delivery to `news > twitter` was subsequently observed in production.
- Discord remains a failure-only fallback; successful deliveries must not be duplicated to Discord.
- A single curator run should fan out safely. Do not create duplicate scheduled jobs that race shared curator state and risk duplicate or lost stories.
- Scheduled times remain unchanged in `Asia/Kolkata`:
  - Hacker News: 10:00, 13:00, 16:00, 19:00, 22:00.
  - Twitter: 10:05, 13:05, 16:05, 19:05, 22:05.
- The existing basic routing prompts were reported at:
  - Twitter: `/root/openclaw/openclaw-data/workspace/skills/twitter-curator/cron-rich-embed-prompt.txt`
  - Hacker News: `/root/openclaw/openclaw-data/workspace/skills/hn-curator/cron-watch-notification-prompt.txt`

Verify these paths and their present contents rather than assuming they are unchanged.

## Agreed presentation requirements

### Every scheduled update

Start with a concise, useful one-line summary suitable for the truncated text shown on an Apple Watch notification. Put the normal digest beneath it. Keep the summary meaningful without requiring the user to open Zulip—for example, communicate the number and nature of the most important updates rather than using a generic “new updates” label.

Preserve a sensible unread/badge count. The earlier preference was one digest message per scheduled run rather than one Zulip message per item, unless technical constraints require reconsideration and the trade-off is presented to the owner first.

### Twitter digest

For every included tweet, provide the following where the upstream source supplies it:

- Author display name and handle.
- Complete tweet text—not merely the curator's paraphrase.
- Reply, repost, like, and view counts.
- Tweet timestamp or an accurate human-readable age.
- Direct link to the tweet.
- Author avatar uploaded to Zulip and displayed inline.
- Linked-article URL in a form that lets Zulip render its normal webpage preview when supported.
- Attached tweet media where feasible under existing upload and size constraints.

Do not promise a native X/Twitter embed card: Zulip does not provide dependable Discord-style embed objects or arbitrary HTML/CSS. Use Zulip Markdown, Zulip-hosted uploads, named links, blockquotes, and native link previews.

### Hacker News digest

For every included story, provide:

- Story title and source domain.
- Points, comment count, submitter, and age.
- Separate named links for the original article and Hacker News discussion.
- A short “why it matters” summary.
- A normal Zulip article preview when the URL supports one, or an uploaded lead image when it can be obtained reliably and safely.

Avoid fabricating missing metadata. If a field is unavailable, omit it or mark it unavailable consistently.

## Delivery and reliability constraints

- Preserve the existing acknowledgement/deduplication semantics. Twitter was reported to have delivery acknowledgement; Hacker News should use equivalent acknowledgement before the richer format is relied upon.
- A formatting, avatar, preview, or image failure must not silently lose the entire batch.
- Determine and document the intended behavior for partial failures. Prefer graceful degradation: deliver a text-only item when media enrichment fails, while retaining retry-safe acknowledgement behavior.
- Do not acknowledge a batch as delivered before the accepted Zulip send returns a Zulip message ID.
- Preserve Discord as failure-only fallback without causing duplicate successful messages.
- Keep credentials, private diagnostics, bearer tokens, cookies, and private message contents out of logs and committed files.
- Confirm upload caching or deduplication for recurring Twitter avatars so repeated digests do not needlessly download and upload identical images.
- Bound all fetched content, media sizes, logs, and command output. The previous Codex thread grew to an 11 GiB rollout, likely due to repeated image/tool payloads, and was killed at approximately 10.7 GiB resident memory.

## Required first pass: recover and assess the working tree

Run read-only inspection first, adapting paths to the actual checkout:

```bash
cd /root/openclaw

git status --short --branch
git diff --stat
git diff --cached --stat
git log --oneline --decorate -n 12

git diff -- \
  openclaw-data/workspace/skills/twitter-curator/cron-rich-embed-prompt.txt \
  openclaw-data/workspace/skills/hn-curator/cron-watch-notification-prompt.txt
```

Then:

1. Read `AGENTS.md`, `CLAUDE.md`, the Phase 4 section of `docs/openclaw-zulip-integration-plan.md`, and any nearer repository instructions before modifying anything.
2. Locate all Twitter/HN curator implementation, prompt, schema, acknowledgement, upload, routing, tests, and documentation files using bounded searches.
3. Attribute current changes where possible: pre-existing Phase 1–3 work, generated/runtime artifacts, and any partial Phase 4 changes created immediately before the OOM.
4. Report the exact recovered state before continuing: changed files relevant to this task, what appears complete, what is partial, what is untouched, and any conflicts with the requirements above.
5. Continue from sound partial work rather than reimplementing it. Make focused edits only.

If the two prompt paths are outside the Git checkout or are runtime-managed copies, identify their source-of-truth templates and deployment mechanism. Do not edit only generated/live copies unless the project explicitly defines them as authoritative.

## Implementation sequence

1. Map the current Twitter and HN source payloads so every requested field is tied to real upstream data.
2. Define the deterministic Markdown output and Apple Watch summary format, including missing-field behavior and safe length bounds.
3. Implement media/avatar acquisition through bounded downloads and Zulip uploads, with caching and text-only fallback.
4. Implement or verify delivery acknowledgement, message-ID capture, deduplication, and partial-failure behavior.
5. Add focused unit/fixture tests for formatting, absent metadata, Unicode/long text, upload failure, link preview behavior, retry, and duplicate suppression.
6. Run static checks and targeted tests first. Do not run broad, unbounded log dumps or unrelated test suites without a reason.
7. Present a concise diff/test/deployment plan and obtain owner approval before touching live scheduled jobs.
8. After approval, deploy one curator at a time, perform controlled delivery tests, verify the exact Zulip destination and formatting, and confirm that Discord receives only failures.
9. Update `docs/openclaw-zulip-integration-plan.md` with verified results rather than anticipated results.

## Acceptance checklist

- [ ] Existing Phase 1–3 and unrelated dirty work remains intact.
- [ ] Apple Watch-friendly one-line summary precedes each digest.
- [ ] Twitter entries include complete sourced text, identity, available metrics/time, direct link, inline avatar, and article preview behavior.
- [ ] HN entries include title/domain, score/comments/submitter/age, separate article/discussion links, “why it matters,” and preview/lead-image behavior.
- [ ] Missing enrichment gracefully falls back to text.
- [ ] Successful sends return and persist a Zulip message ID before acknowledgement.
- [ ] Retries do not duplicate already accepted stories.
- [ ] Twitter goes only to `stream:5:twitter` on success.
- [ ] HN goes only to `stream:5:hacker-news` on success.
- [ ] Discord remains failure-only fallback.
- [ ] RSS remains unchanged.
- [ ] Credentials and private diagnostics are absent from logs and commits.
- [ ] Targeted tests and static checks pass.
- [ ] No production deployment, commit, or push occurs without explicit owner approval.

## Historical interruption note

The first rich-formatting attempt ran for roughly 138 minutes and ended when the model allocation was interrupted. A subsequent read-only assessment reported that no substantive rich-formatting changes had been made at that point. The user then authorized resuming the work, but the replacement Codex app-server was killed by Linux OOM while loading the old session. Therefore, do not infer implementation status solely from the transcript: inspect the current working tree and runtime-managed prompt files before deciding what remains.
