Skip to content

docs(creating-cloud-persona): note Slack digest threading pattern#80

Merged
khaliqgant merged 2 commits into
mainfrom
docs/cloud-persona-slack-threading
Jun 22, 2026
Merged

docs(creating-cloud-persona): note Slack digest threading pattern#80
khaliqgant merged 2 commits into
mainfrom
docs/cloud-persona-slack-threading

Conversation

@khaliqgant

@khaliqgant khaliqgant commented Jun 22, 2026

Copy link
Copy Markdown
Member

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.

  • Notes: added a bullet covering the pattern — post a compact count header, then thread each detail message via slackClient().post(channel, body, { replyTo: headerRef }) (the cloud orders the reply after the header delivers and sets thread_ts, no parent-receipt round-trip). Includes the lower-level messages.write({ parentRef }) equivalent, the ts-based reply(channel, ts, text) path for app_mention, and the idempotency caveat (don't throw once the header has posted — a retry duplicates it; claim dedupe state when the header lands).
  • Reference: synced the vendored hn-monitor/agent.ts to its current threaded postFreshStories implementation, 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

Review in cubic

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>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +27 to +29
* 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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'.

Comment on lines +95 to +97
const context = posts.length
? posts.map((p) => `### Posted ${p.postedAt}\n${p.digest}`).join('\n\n')
: 'No Hacker News digests have been posted yet.';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
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.';

Comment on lines +119 to +122
const titles = posts
.flatMap((p) => p.stories.map((s) => `- ${s.title} ${s.url}`))
.slice(0, 15)
.join('\n');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
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));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
return posts.sort((a, b) => (a.postedAt < b.postedAt ? 1 : -1));
return posts.sort((a, b) => (b.postedAt ?? '').localeCompare(a.postedAt ?? ''));

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

Open in Devin Review

Comment on lines +27 to +29
* 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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)
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +42 to +43
if (isRelaycastMessageEvent(event)) {
await handleInboxMessage(ctx, event);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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>
@khaliqgant

Copy link
Copy Markdown
Member Author

Addressed the review:

  • ttlDays discrepancy: the vendored hn-monitor/persona.json was a stale snapshot at ttlDays: 7; synced it to the live agent's 30, matching the ~30-day rolling-recall comment.
  • Defensive reads (agent.ts): optional-chain postedAt/digest in the prompt context, default stories → [] and title/url in the fallback titles, and sort with localeCompare on a string fallback so a malformed/old-schema record can't crash or misorder. Applied to the live agent (feat: conversational Slack chat bots (inbox-buddy + joke-bot) + @mention wake-gate agents#85) and re-synced here so the reference matches.
  • Bumped creating-cloud-persona package version 1.0.6 → 1.0.7.

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@khaliqgant, we couldn't start this review because you've reached your PR review rate limit.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a2b3a49e-505a-45bf-84e9-241f5707411e

📥 Commits

Reviewing files that changed from the base of the PR and between 08fb4a0 and 541f08d.

📒 Files selected for processing (4)
  • prpm.json
  • skills/creating-cloud-persona/references/agents/hn-monitor/agent.ts
  • skills/creating-cloud-persona/references/agents/hn-monitor/persona.json
  • skills/creating-cloud-persona/references/workforce-cloud-persona-notes.md
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch docs/cloud-persona-slack-threading

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@agent-relay-code

Copy link
Copy Markdown
Contributor

Review complete. I installed deps and ran the repo's canonical tsc --noEmit typecheck, which fails. The failures are semantic (wrong runtime API) and not safe to auto-fix, so I left no edits and discarded the generated node_modules/lockfiles. Here is my review.


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 skills/creating-cloud-persona/references/agents dependencies and ran the package's canonical typecheck (tsc --noEmit, the same tsc the test script also invokes). It fails:

hn-monitor/agent.ts(12,3): error TS2305: Module '"@agentworkforce/runtime"' has no exported member 'isCronTickEvent'.
hn-monitor/agent.ts(13,3): error TS2305: Module '"@agentworkforce/runtime"' has no exported member 'isRelaycastMessageEvent'.
hn-monitor/agent.ts(76,10): error TS2314: Generic type 'AgentEvent' requires 2 type argument(s).

agent.ts is in the tsconfig include glob (*/agent.ts), so this blocks the build for the whole reference package.

Blocking findings (leave for author — semantic, not auto-fixable)

1. Non-existent runtime imports — agent.ts:10-16

isCronTickEvent and isRelaycastMessageEvent are not exported by @agentworkforce/runtime (^3.0.49, verified against the installed dist/index.d.ts and the vendored references/workforce/packages/runtime). Events are discriminated by event.source ('cron' | 'github' | 'linear' | 'slack' | 'notion' | 'jira') and event.type, which is exactly what every other agent in this folder does (linear-slack/agent.ts:67, review/agent.ts:82, vendor-monitor/agent.ts:16, etc.). There is also no 'relaycast' event source in the runtime. The cron branch should use if (event.source !== 'cron') return;.

2. AgentEvent used as a bare type — agent.ts:76

AgentEvent<Tr, S> is a 2-arg generic (define-agent.d.ts:32); it's the narrowed handler event type, not a standalone event type. A free helper should be typed WorkforceEvent (the runtime's exported union, used by the other agents) — not AgentEvent.

3. The threading mechanism doesn't exist on the Slack client — agent.ts:132-138, 169-175 + workforce-cloud-persona-notes.md

The real catalog client (@relayfile/relay-helpers/dist/slack.d.ts) is:

post(channel: string, text: string): Promise<{ channel: string; ts: string }>;
reply(channel: string, threadTs: string, text: string): Promise<{ channel: string; ts: string }>;

post() takes only two args (no opts/replyTo) and returns no ref. The PR's local SlackPoster interface declares post(channel, text, opts?: { replyTo?: string }): Promise<{ ts; ref? }>, which happens to typecheck because the real client is assignable to the looser interface — but at runtime:

  • head.ref is always undefined,
  • the third arg to post() is ignored,

so client.post(channel, body, { replyTo: head.ref }) (line 173) posts the body as a separate top-level message, not a thread reply. The entire pattern this PR documents ({ ts, ref } + replyTo + "server-side ordered dispatch, no parent-receipt round-trip"), including the new workforce-cloud-persona-notes.md bullet and the slackClient({ writebackTimeoutMs: 0 }).messages.write({ parentRef }) lower-level claim, describes an API the client does not implement. The actual threading primitive is reply(channel, parentTs, text) keyed off the parent's ts (as linear-slack/agent.ts:134 and review/agent.ts:615 do) — which requires waiting for the header's ts, contradicting the PR's central "no round-trip" claim. This needs an author decision and is not something I can safely rewrite.

4. event.expand('full') is not part of the event API — agent.ts:82

There is no expand() method on WorkforceEvent/WorkforceProviderEvent; provider events carry payload directly (types.d.ts:35). Other agents read event.payload (see linear-slack/agent.ts:72readSlackMessage(event.payload)). As written this would fail at runtime even if the imports were fixed.

5. Inbox/DM path is unreachable — agent.ts:40-45 vs hn-monitor/persona.ts

The handler now branches on an inbound message event, but the persona declares no trigger for messages — only schedules (in agent.ts) and a slack scope (persona.ts:18, which mounts the VFS but does not wake the agent). Per the linear-slack reference, an agent only wakes for messages when it declares a triggers.slack block. Without one, handleInboxMessage is dead code. (Resolving this means editing the persona — an architecture decision; flagging rather than changing.)

6. ttlDays documentation contradicts the config — agent.ts:27-29, 104, 178, 199

Comments and the LLM prompt string repeatedly say the retention window is ttlDays (30) / "most recent ~30 days", but persona.ts:44 / persona.json:44 set ttlDays: 7. One side is wrong. I did not "fix" this because (a) line 104 is a user-facing prompt string (changing it is semantic) and (b) it's ambiguous whether the persona should be 30 or the docs should say 7 — author's call.

Advisory Notes

  • None. All findings above are within this PR's stated purpose (the hn-monitor threading change and its companion notes bullet). No unrelated refactors are recommended.

Addressed comments

  • No bot or human reviewer comments were present in the harness context (.workforce/ contains only pr.diff, changed-files.txt, context.json; no review/comment artifacts). Nothing to reconcile.

Why no auto-edits

The only mechanical-looking item (the ttlDays comment) overlaps a behavior-bearing prompt string and an ambiguous source-of-truth, and the three CI errors can only be cleared by substituting the correct runtime API (semantic logic + the unresolved threading-API question in finding #3). Per review policy I left the code unchanged and removed the node_modules/package-lock.json I generated for the typecheck so nothing extraneous is committed. The working tree is clean.

This PR is not ready: the required typecheck/test command fails and a core behavioral claim is incorrect. A human (the author) needs to rework findings #1#5 before merge.

@khaliqgant khaliqgant merged commit b726596 into main Jun 22, 2026
2 checks passed
@khaliqgant khaliqgant deleted the docs/cloud-persona-slack-threading branch June 22, 2026 09:37
@agent-relay-code

Copy link
Copy Markdown
Contributor

pr-reviewer could not complete review for #80 in AgentWorkforce/skills.
The review harness exited with code 1.
No review was posted; this needs operator attention.

1 similar comment
@agent-relay-code

Copy link
Copy Markdown
Contributor

pr-reviewer could not complete review for #80 in AgentWorkforce/skills.
The review harness exited with code 1.
No review was posted; this needs operator attention.

@agent-relay-code

Copy link
Copy Markdown
Contributor

ℹ️ 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 #80docs/cloud-persona-slack-threading

Summary

The PR rewrites the hn-monitor reference agent to (a) thread Slack digests under a count header, (b) add an inbox/DM Q&A path over the last ~30 days of stored digests, and bumps the skill version + notes. I verified everything against the current checkout by installing the pinned deps in skills/creating-cloud-persona/references/agents and running the canonical tsc --noEmit (the repo's typecheck script).

Blocking finding — PR fails the typecheck CI check

The reference agents are type-checked (references/agents/package.json"typecheck": "tsc --noEmit"; tsconfig include covers */agent.ts). After npm install, tsc --noEmit reports 3 errors, all in this PR's file, against the pinned @agentworkforce/runtime (resolved 3.0.52, range ^3.0.49):

hn-monitor/agent.ts(12,3): error TS2305: Module '"@agentworkforce/runtime"' has no exported member 'isCronTickEvent'.
hn-monitor/agent.ts(13,3): error TS2305: Module '"@agentworkforce/runtime"' has no exported member 'isRelaycastMessageEvent'.
hn-monitor/agent.ts(76,10): error TS2314: Generic type 'AgentEvent' requires 2 type argument(s).

The rest of the reference agents typecheck cleanly, so these errors are introduced by this PR. Root cause — the new code targets an event API the runtime does not expose:

  • isCronTickEvent / isRelaycastMessageEvent don't exist. dist/index.d.ts exports no event type-guards. Event narrowing in this runtime is by the source discriminator — which is exactly what the pre-PR code did (if (event.source !== 'cron') return;). There is no 'relaycast'/inbox source in WorkforceEventSource ('cron' | 'github' | 'linear' | 'slack' | 'notion' | 'jira').
  • AgentEvent is AgentEvent<Tr, S> (2 type params, from define-agent.d.ts), not a bare type. handleInboxMessage(... event: AgentEvent) is therefore invalid.
  • event.expand('full') is not on the event type. expand exists only as a raw gateway-envelope field (shim.d.ts: expand?: unknown), never as a method on WorkforceEvent/AgentEvent. Even after fixing the imports, agent.ts:82 would not type-check.

I did not auto-fix this. It is not a mechanical/lint/import-order change — the inbox Q&A feature depends on a relay-inbox event shape (isRelaycastMessageEvent, event.expand, a relaycast source) that isn't present in the pinned runtime, so making it compile requires a design decision about the correct API (or a runtime version that provides one). That's a behavior/architecture call for the author, not a reviewer rewrite. Per policy I'm leaving the code unchanged and raising it here.

Recommended author actions (pick one): confirm and import the real relay-inbox event API from the runtime version that ships it (and bump the dep range accordingly), or revert the inbox path and keep only the threading change, or gate the inbox branch on event.source using a shape that exists today. Either way, run npm install && npm run typecheck in references/agents before re-pushing.

Non-blocking observations (not changed)

  • persona.json adds relay.inbox: ["@self"] and bumps timeoutSeconds 120→1800 and ttlDays 7→30. These are JSON config, not covered by tsc (hn-monitor has no persona.ts), so they don't affect CI. The relay.inbox field's validity depends on the same inbox feature as above; worth confirming it's a recognized persona field at deploy time.
  • The threading/idempotency logic and withTimeout fallback read fine as example code; I'm not flagging them since they can't be reached until the typecheck issue is resolved.

Addressed comments

  • No bot or human review comments were present in the provided PR metadata (context.json has no comments array; no separate comments file). Nothing to reconcile.

Verification notes

  • Ran npm install + npx tsc --noEmit in references/agents; reproduced the 3 errors on the current checkout. Confirmed no typecheck errors exist outside hn-monitor/agent.ts.
  • Made no source edits. Removed the stray package-lock.json created by npm install; node_modules is gitignored. git status is clean — the working tree matches the PR with no extra artifacts to be pushed.

The PR does not need a human handoff yet because a required check (typecheck) fails on the current code and the fix requires author design input — not printing READY.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant