docs(creating-cloud-persona): note Slack digest threading pattern#80
Conversation
Document the server-side threading pattern for multi-item Slack digests: post a
compact count header, then thread the detail under it via `post(channel, text,
{ replyTo: headerRef })` (no parent-receipt round-trip; cloud sets thread_ts),
with the idempotency caveat that you must not throw once the header has posted
(retry would duplicate it). Sync the vendored hn-monitor reference agent.ts to
its current threaded implementation (postFreshStories) cited by the note.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request updates the Hacker News monitor agent to support answering user questions via direct messages by recalling the last 30 days of posted digests, and introduces a Slack threading mechanism to post compact headers with threaded details. It also updates the workforce cloud persona notes to document these threading guidelines. The review feedback suggests aligning the memory TTL configuration in persona.json with the 30-day retention window, and adding defensive checks (such as optional chaining and robust sorting) when processing stored post records to prevent runtime crashes from malformed data.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| * A digest we posted, stored durably under `hn-monitor:post`. Memory `ttlDays` | ||
| * (30) gives the rolling retention window for free, so a recall returns roughly | ||
| * the last month of posts for the inbox Q&A path to answer over. |
There was a problem hiding this comment.
There is a discrepancy between this comment (which mentions a 30-day rolling retention window) and the actual configuration in 'persona.json', where 'memory.ttlDays' is set to '7'. To ensure the agent can recall the last month of posts as expected, please update 'persona.json' to set 'ttlDays' to '30'.
| const context = posts.length | ||
| ? posts.map((p) => `### Posted ${p.postedAt}\n${p.digest}`).join('\n\n') | ||
| : 'No Hacker News digests have been posted yet.'; |
There was a problem hiding this comment.
To prevent potential runtime errors or rendering 'undefined' values if the stored JSON in memory is malformed or missing fields (e.g., from older schema versions), use optional chaining and nullish coalescing operators.
| const context = posts.length | |
| ? posts.map((p) => `### Posted ${p.postedAt}\n${p.digest}`).join('\n\n') | |
| : 'No Hacker News digests have been posted yet.'; | |
| const context = posts.length | |
| ? posts.map((p) => "### Posted " + (p.postedAt ?? "Unknown") + "\\n" + (p.digest ?? "")).join("\\n\\n") | |
| : 'No Hacker News digests have been posted yet.'; |
| const titles = posts | ||
| .flatMap((p) => p.stories.map((s) => `- ${s.title} ${s.url}`)) | ||
| .slice(0, 15) | ||
| .join('\n'); |
There was a problem hiding this comment.
If a stored post record is malformed or missing the 'stories' field, 'p.stories.map' will throw a 'TypeError' and crash the fallback flow. Use optional chaining or a fallback empty array to make this more robust.
| const titles = posts | |
| .flatMap((p) => p.stories.map((s) => `- ${s.title} ${s.url}`)) | |
| .slice(0, 15) | |
| .join('\n'); | |
| const titles = posts | |
| .flatMap((p) => (p.stories ?? []).map((s) => "- " + (s.title ?? "Untitled") + " " + (s.url ?? ""))) | |
| .slice(0, 15) | |
| .join('\\n'); |
| // skip records that aren't valid JSON | ||
| } | ||
| } | ||
| return posts.sort((a, b) => (a.postedAt < b.postedAt ? 1 : -1)); |
There was a problem hiding this comment.
If 'postedAt' is missing or undefined on any record, the comparison 'a.postedAt < b.postedAt' can produce inconsistent sorting behavior. Using 'localeCompare' with a fallback string is safer and more robust.
| return posts.sort((a, b) => (a.postedAt < b.postedAt ? 1 : -1)); | |
| return posts.sort((a, b) => (b.postedAt ?? '').localeCompare(a.postedAt ?? '')); |
| * A digest we posted, stored durably under `hn-monitor:post`. Memory `ttlDays` | ||
| * (30) gives the rolling retention window for free, so a recall returns roughly | ||
| * the last month of posts for the inbox Q&A path to answer over. |
There was a problem hiding this comment.
🟡 Comments claim ttlDays is 30 but persona config sets it to 7, degrading the inbox Q&A feature
The new code consistently claims the memory TTL is 30 days (lines 28, 72, 104, 178), and the inbox Q&A feature is designed around having ~30 days of post history for answering user questions. However, both persona.json:44 and persona.ts:44 set ttlDays: 7. This means post records expire after only 7 days — the LLM prompt at line 104 tells the model it has "most recent ~30 days" of digests, but in practice it will only ever have ~7 days. Either the ttlDays in persona.json/persona.ts should be raised to 30 to match the feature design, or all comments and the LLM prompt should be corrected to say ~7 days.
Prompt for agents
The new inbox Q&A feature design assumes 30 days of post history, but persona.json and persona.ts both set ttlDays: 7. Either:
1. Update persona.json and persona.ts to set ttlDays: 30 to match the feature intent (the code comments at agent.ts lines 28, 72, 104, 178 all say 30 days), OR
2. Fix the comments and LLM prompt in agent.ts to say 7 days instead of 30.
Affected locations:
- persona.json line 44: ttlDays: 7
- persona.ts line 44: ttlDays: 7
- agent.ts line 28: comment says ttlDays (30)
- agent.ts line 72: docstring says ~30 days
- agent.ts line 104: LLM prompt says most recent ~30 days
- agent.ts line 178: comment says ttlDays (30)
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 72b4b1ebed
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (isRelaycastMessageEvent(event)) { | ||
| await handleInboxMessage(ctx, event); |
There was a problem hiding this comment.
Declare a relay inbox for the DM handler
This new branch is meant to answer relay DMs, but the hn-monitor persona still only declares the Slack integration and onEvent schedule config, with no relay/inbox declaration. In the vendored persona types, relay participation is off unless declared and relay.inbox is the config that subscribes the persona to inbox selectors, so a deployed hn-monitor only receives its cron schedule and this chat branch is unreachable. Add the appropriate relay inbox config, such as an @self inbox selector, or remove the dead chat path.
Useful? React with 👍 / 👎.
- Sync vendored hn-monitor reference to the current agent: defensive reads on stored post records (optional-chain postedAt/digest, default stories→[] and title/url, localeCompare sort) — addresses Gemini review on PR #80. - Sync vendored persona.json: memory.ttlDays 7 → 30, matching the agent's ~30-day rolling-recall comment (the stale snapshot had drifted). - Bump creating-cloud-persona package version 1.0.6 → 1.0.7. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Addressed the review:
|
|
Warning Review limit reached
More reviews will be available in 16 minutes and 26 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Review complete. I installed deps and ran the repo's canonical Review: PR #80 — "note Slack digest threading pattern"Verdict: Request changes. CI typecheck is RED and the PR's core threading mechanism does not match the real Slack client API. No safe mechanical fixes were available, so I made no code edits. CI status (reproduced locally)I installed
Blocking findings (leave for author — semantic, not auto-fixable)1. Non-existent runtime imports —
|
|
pr-reviewer could not complete review for #80 in AgentWorkforce/skills. |
1 similar comment
|
pr-reviewer could not complete review for #80 in AgentWorkforce/skills. |
|
ℹ️ pr-reviewer: review only — no file changes were applied to the PR (nothing to commit after review). The notes below are advisory and were not pushed. Review: PR #80 —
|
Documents the server-side Slack threading pattern in the cloud-persona notes so new digest agents thread their output instead of dumping one wall of text.
slackClient().post(channel, body, { replyTo: headerRef })(the cloud orders the reply after the header delivers and setsthread_ts, no parent-receipt round-trip). Includes the lower-levelmessages.write({ parentRef })equivalent, the ts-basedreply(channel, ts, text)path forapp_mention, and the idempotency caveat (don't throw once the header has posted — a retry duplicates it; claim dedupe state when the header lands).hn-monitor/agent.tsto its current threadedpostFreshStoriesimplementation, which the note cites.Pairs with the hn-monitor threading change in AgentWorkforce/agents#85 (modeled on internal-agents
x-reply-radar).🤖 Generated with Claude Code