Examples / Community forum digest

Community forum digest

Every weekday morning, take the newest threads off a community forum's listing page, drop the ones earlier runs already covered, read each new thread once, and post one summary to Slack. A short pipeline does the reading; the model only writes the digest.

Web pagesJavaScriptYour AI modelsSlack

What you tell the assistant

Every weekday at 8am Toronto time, read the new threads on https://community.northwind.example/latest, skip any we've already covered, and post a short digest of what people are asking about to #community.

Why it works

  • One web.extract call takes the thread links off the listing page, and a for_each step reads each thread once. No model reads a page it doesn't need to.
  • The workflow remembers the threads it has covered, so each run reads only what is new and a quiet morning costs no model tokens at all.
  • A thread that fails to load is recorded and the rest carry on, and a run interrupted halfway picks up at the next thread rather than starting over.
Sign in to build this

What it builds

Trigger
Schedule
0 8 * * 1-5 · America/Toronto
tool
Recall covered threads
builtin.recall
tool
Read the listing
web.extract
javascript
Keep new threads
→ threads, covered
for_each
Read each thread
each of fresh.output.threads · web.extract
llm
Write the digest
fast
if threads.output.succeeded > 0
tool
Post to
slack.post_message
tool
Remember covered threads
builtin.remember
View the definition
trigger:
  kind: schedule
  cron: "0 8 * * 1-5"
  timezone: America/Toronto
steps:
  - key: seen
    name: Recall covered threads
    kind: tool
    tool: builtin.recall
    args: { key: covered_threads }
  - key: listing
    name: Read the listing
    kind: tool
    tool: web.extract
    args:
      url: https://community.northwind.example/latest
      markdown: false
      links: false
      selectors: { threads: "a.topic-title" }
  - key: fresh
    name: Keep new threads
    kind: javascript
    inputs: { threads: "{{ steps.listing.output.matches.threads }}", seen: "{{ steps.seen.output.value }}" }
    outputs: { threads: array, covered: array }
    code: |
      function main({ threads, seen }) {
        const known = new Set(seen || []);
        const fresh = (threads || []).filter(t => t.href && !known.has(t.href)).slice(0, 10);
        console.log(`${fresh.length} new of ${(threads || []).length} threads`);
        return { threads: fresh, covered: [...fresh.map(t => t.href), ...(seen || [])].slice(0, 300) };
      }
  - key: threads
    name: Read each thread
    kind: for_each
    over: "{{ steps.fresh.output.threads }}"
    continue_on_error: true
    step:
      kind: tool
      tool: web.extract
      args:
        url: "{{ item.href }}"
        markdown: false
        links: false
        selectors: { posts: ".topic-post .cooked" }
        limit: 8
  - key: digest
    name: Write the digest
    kind: llm
    model: fast
    if: steps.threads.output.succeeded > 0
    system: You write short, plain digests for a community team. No preamble.
    prompt: |
      Here are new threads from our community forum, each with its opening post and first replies.
      For each, write one line: what the person needs, and whether anyone has answered yet, with its link.
      Then one line on any theme that comes up more than once.

      {{ steps.threads.output.items }}
  - key: post
    name: Post to #community
    kind: tool
    tool: slack.post_message
    args:
      channel: "#community"
      text: "*New on the forum*\n{{ steps.digest.output.text }}"
  - key: remember
    name: Remember covered threads
    kind: tool
    tool: builtin.remember
    depends_on: [post]
    args: { key: covered_threads, value: "{{ steps.fresh.output.covered }}" }

A sample run

What each step produces

Recall covered threads tool

succeeded
Calling builtin.recall
{
  "key": "covered_threads",
  "found": true,
  "value": [
    "https://community.northwind.example/t/csv-export-times-out/4120"
  ],
  "updated_at": "2026-10-07T12:00:41Z"
}

Read the listing tool

succeeded
Calling web.extract
Extracted https://community.northwind.example/latest · 3 threads
{
  "url": "https://community.northwind.example/latest",
  "title": "Latest topics - Northwind Community",
  "status": 200,
  "truncated": false,
  "matches": {
    "threads": [
      {
        "text": "Webhook retries after a 410?",
        "href": "https://community.northwind.example/t/webhook-retries-after-a-410/4131"
      },
      {
        "text": "SSO with Okta groups",
        "href": "https://community.northwind.example/t/sso-with-okta-groups/4128"
      },
      {
        "text": "CSV export times out",
        "href": "https://community.northwind.example/t/csv-export-times-out/4120"
      }
    ]
  },
  "notes": []
}

Keep new threads javascript

succeeded
2 new of 3 threads
{
  "threads": [
    {
      "text": "Webhook retries after a 410?",
      "href": "https://community.northwind.example/t/webhook-retries-after-a-410/4131"
    },
    {
      "text": "SSO with Okta groups",
      "href": "https://community.northwind.example/t/sso-with-okta-groups/4128"
    }
  ],
  "covered": [
    "https://community.northwind.example/t/webhook-retries-after-a-410/4131",
    "https://community.northwind.example/t/sso-with-okta-groups/4128",
    "https://community.northwind.example/t/csv-export-times-out/4120"
  ]
}

Read each thread for_each

succeeded
Running tool for each of 2 items
Item 1 of 2 · https://community.northwind.example/t/webhook-retries-after-a-410/4131
Extracted https://community.northwind.example/t/webhook-retries-after-a-410/4131 · 1 posts
Item 2 of 2 · https://community.northwind.example/t/sso-with-okta-groups/4128
Extracted https://community.northwind.example/t/sso-with-okta-groups/4128 · 2 posts
Finished 2 items: 2 succeeded, 0 failed
{
  "items": [
    {
      "url": "https://community.northwind.example/t/webhook-retries-after-a-410/4131",
      "title": "Webhook retries after a 410? - Northwind Community",
      "status": 200,
      "matches": {
        "posts": [
          {
            "text": "Our endpoint returns 410 for deleted orders but Northwind keeps retrying for a day. Is there a way to stop retries for a specific status?"
          }
        ]
      }
    },
    {
      "url": "https://community.northwind.example/t/sso-with-okta-groups/4128",
      "title": "SSO with Okta groups - Northwind Community",
      "status": 200,
      "matches": {
        "posts": [
          {
            "text": "Can Okta groups map to Northwind roles, or only to teams?"
          },
          {
            "text": "Groups map to teams today. Roles are on the roadmap for Q1."
          }
        ]
      }
    }
  ],
  "errors": [],
  "count": 2,
  "succeeded": 2,
  "failed": 0,
  "skipped": 0
}

Write the digest llm

succeeded
{
  "text": "• Webhook retries after a 410: wants retries to stop on 410 for deleted orders. Unanswered. https://community.northwind.example/t/webhook-retries-after-a-410/4131\n• SSO with Okta groups: asks whether groups can map to roles. Answered: teams only for now. https://community.northwind.example/t/sso-with-okta-groups/4128\nTheme: nothing repeats today.\n"
}

Post to tool

succeeded
Calling slack.post_message
{
  "ok": true,
  "channel": "C05COMMUNITY",
  "ts": "1791460861.000300"
}

Remember covered threads tool

succeeded
Calling builtin.remember
{
  "key": "covered_threads",
  "bytes": 214,
  "updated_at": "2026-10-08T12:01:02Z"
}

Trigger payload

{
  "scheduled_at": "2026-10-08T12:00:00Z"
}

Turn one person's AI process into the company's.

Murmurator is invite-only. Already invited? Sign in and build something real.