Skip to main content
DocsGetting StartedHands-On: Your First Workflow
Hands-on · 15 minutes

Hands-On: Build a Daily Summary Workflow from Scratch

Follow a specific business scenario to string together all the core concepts: pull data → classify → summarize → deliver.

This article walks through a specific business scenario from beginning to end, allowing all the previously learned concepts (workflow/step/agent/variables/classifier/sub_workflow/credentials) to fit into a real process. After 15 minutes, you will have the feeling of "I can do a workflow independently".

Before Starting
  • Already registered a Braidrun account and logged in. If you don’t know, look first Registration and Login.
  • There is an API Key for any of OpenAI / Anthropic / DeepSeek. You can still take the first few steps without it at all, but you will skip the LLM step when you actually run.
  • (Optional) A Slack Incoming Webhook URL - required if you want to experience the delivery process.

Business Scenario: Daily Technology News Summary

Requirements:

  • Automatically run at 8:00 every morning;
  • Catch 10 latest tech news from Hacker News / 36Kr / The Verge RSS;
  • Classify each item into three categories: "AI/Hardware/Others";
  • Generate 60-word Chinese summaries only for those items classified as AI;
  • Summarize it into Markdown and deliver it to the Slack channel;
  • Ask me to click "Confirm" before sending - to avoid delivering dirty data.

Steps to Disassemble Into Braidrun

  1. fetchcode Steps: Use Node to capture RSS and parse it into JSON.
  2. classifyclassifier Steps: Label each news item with a category label.
  3. summarizesingle Steps: Generate Chinese summaries only for AI classes.
  4. compilecode Steps: Put together a whole Markdown.
  5. reviewmanual_approval: Pause to let me take a look before sending.
  6. deliversub_workflow: reuses the built-in Slack delivery module.

Step 1 · Create a New Workflow

  1. The main navigation point on the left is "Workflow" → "New Workflow" in the upper right corner.
  2. Name: daily-tech-digest, description: 每天早 8 点的科技新闻摘要.
  3. Click "Create". Jump into the editor immediately.

The editor opens with two columns: canvas (DAG visualization) on the left and YAML on the right. Changes on either side will be synchronized to the other side in real time.

Step 2 · Declare Agent

Switch to the YAML column and add a section of agents at the top (after variables):

yaml
agents:
  writer:
    preset: writer
    overrides:
      max_iterations: 3
      provider: openai_api_key    # 引用凭据中心里的 Key

Description: The writer preset is specially optimized for Chinese writing. Overrides limit max_iterations to 3 so that the agent will not think too much about a summary (the summary itself is very simple).

For detailed explanation of preset, see Agent Configuration Guide.

Step 3 · fetch: Use a Code Step to Pull the RSS Feed

Continuing in the YAML, add fetch under steps:

yaml
steps:
  - id: fetch
    type: code
    language: node
    idempotent: true
    code: |
      const parser = new (await import('rss-parser')).default();
      const feeds = [
        'https://hnrss.org/frontpage',
        'https://36kr.com/feed'
      ];
      const articles = [];
      for (const url of feeds) {
        const feed = await parser.parseURL(url);
        feed.items.slice(0, 5).forEach(item => {
          articles.push({ title: item.title, url: item.link });
        });
      }
      return { articles };
    extract:
      articles: $.articles

A few key points:

  • language: node — Node 20 is one of the runtimes with built-in support.
  • extract — Pick out the articles field from the script's stdout JSON and reference it downstream using steps.fetch.data.articles.
  • idempotent: true — "Publishing news on the same day will most likely result in the same result" - this can be skipped when the service is restarted and resumed.
Sandbox For Code Steps

Your script will run in a disposable isolation sandbox with default 512MB of memory, 0.5 CPU core, and 300 second timeout. The network only allows outbound requests. You can safely fetch external data, but you cannot listen to the port; only /tmp is available for writing to disk.

Step 4 · Classify: Label Each News Item

yaml
  - id: classify
    type: classifier
    agent: writer
    task: |
      给下面这条新闻标题分一个类别:
      标题: {{steps.fetch.data.articles[0].title}}
    categories:
      - name: ai
        description: AI / LLM / 机器学习相关
      - name: hardware
        description: 硬件 / 芯片 / 设备
      - name: other
        description: 其它科技话题
    output_variable: category

The output of the classifier can be passed {{classifier.category}} Quote - It is the classification result of the previous news. We'll use it to do conditional branching in the next step.

For the complete syntax of variable references, see Variables And Expressions.

Step 5 · Summarize: Write a Summary Only for the AI Class

yaml
  - id: summarize
    type: single
    agent: writer
    idempotent: true
    condition: "{{classifier.category}} == 'ai'"
    task: |
      用一段中文(不超过 60 字)概括下面这条 AI 新闻:
      {{steps.fetch.data.articles[0].title}}

Description:

  • condition Let this step only be executed when the classification result is ai, other categories are skipped (status SKIPPED, not counted as a failure).
  • idempotent: true —— Pure summaries are idempotent, same input → same output.

Step 6 · compile: Assemble the Final Markdown

yaml
  - id: compile
    type: code
    language: node
    depends_on: [summarize]
    code: |
      const summaries = JSON.parse(process.env.STEP_OUTPUT_SUMMARIZE || '[]');
      const md = [
        '# 📰 今日科技 AI 摘要',
        '',
        ...summaries.map((s, i) => `${i + 1}. ${s}`)
      ].join('\n');
      return { markdown: md };
    extract:
      markdown: $.markdown

This step aggregates all upstream summarize results into a Markdown that can be sent directly. extract exposes it as steps.compile.data.markdown, which makes downstream references cleaner.

Step 7 · Review: Manual Confirmation Before Sending

yaml
  - id: review
    type: manual_approval
    title: "请审阅今日 AI 摘要"
    body: "{{steps.compile.data.markdown}}"
    approvers: []           # 空 = 团队所有 Admin 都可审批
    notify: [in_app]        # 也可以 slack / email / feishu

This is the simplest form of manual_approval - the process will be paused here and a pending approval will appear on the Approval Management page. You click "Approve" to proceed down the workflow; click "Reject" and the execution status changes to FAILED, and all subsequent steps are cancelled.

For more flexible approval configuration (approvers designation, notify channels, expiration), see Manual Approval.

Step 8 · Deliver: sub_workflow Calls the Platform’s Built-In Module

yaml
  - id: deliver
    type: sub_workflow
    module: dingyue-module-slack-deliver
    depends_on: [review]
    inputs:
      slack_webhook_url: "{{var:slack_webhook_url}}"
      message_text: "{{steps.compile.data.markdown}}"

Description:

  • dingyue-module-slack-deliver It is one of the built-in delivery modules that comes with the platform, encapsulating the entire routine of "assembly payload → send message → retry → write back status".
  • slack_webhook_url Reference the Slack Incoming Webhook URL configured in the variable - we will configure this in step 10.

Step 9 · Canvas Preview

Switch back to the left canvas bar, and you will see 6 nodes connected in a line by fetch → classify → summarize → compile → review → deliver. Double-click any node to expand its YAML fragment; drag the node to adjust its position—layout changes are visual only and do not affect the YAML.

Step 10 · Complete Credentials

Before running this workflow, you need to have:

  1. openai_api_key (LLM Key used by Agent)
    • Select api_key as the type, and paste the sk-... you got from OpenAI as the value.
  2. slack_webhook_url (Slack Incoming Webhook URL for delivery)
    • Select api_key or secret_text as the type and paste the Slack Incoming Webhook URL as the value.

After saving, the credential names referenced in the workflow correspond to this one-to-one and can be parsed at runtime.

Step 11 · Validation & Dry-Run

11.1 Verification

Click "Verify" at the top. The platform uses the complete agent parser to do a round-trip; if there is a syntax/reference error, the specific line number + reason will be given. Get all the red lines before taking the next step.

11.2 dry-run

Click "Execute", check dry-run in the dialog box, and click "Start Execute". This time:

  • fetch will actually run - it is difficult to judge the side effects of the code step, so we let it run by default (but the network sandbox is limited);
  • classify / summarize / compile - because it is LLM, simulated values are returned during dry-run;
  • review - automatic approval under dry-run (no real ammunition approval);
  • deliver - the module knows dry-run mode and will not actually send Telegram messages.

If successful, all steps will be green COMPLETED or gray SKIPPED. Go to the lower right area to view the "Input/Output/Variable Snapshot" of each step and confirm that the variables are all connected correctly.

Step 12 · Run It for Real

  1. Click "Execute", Cancel Check dry-run to start execution.
  2. The real-time event stream on the right begins to flash. Check to see if 10 items have been fetched after running fetch; label each item run by classify; summarize is only executed on the AI items.
  3. When running to review, the entire execution will be suspended and the status will change to PENDING_APPROVAL. Switch to "Approval Management" and you will see an item waiting for approval. Click "View" to read the Markdown draft.
  4. Click "Approve" when satisfied. The workflow automatically continues to deliver and the Slack message is sent.
  5. After running, return to the workflow details page to see the token/cost statistics of this execution.
My expectations for my first real run

It takes about 1–3 minutes (depending on the network) and costs about 5000–15000 tokens (about $0.01–0.03 according to openai gpt-4.1-mini). Below this range may be a failure to pull news, and above this range may be a reflection on summarizing.

Step 13: Schedule the Workflow to Run Daily

  1. "Scheduling Management" on the left → "New Schedule".
  2. Target workflow: daily-tech-digest; Type: cron; Expression: 0 0 8 * * ?; Time zone: Asia/Shanghai.
  3. "Parameter Defaults" can be empty - our variables all have reasonable default values.
  4. Save. The "next trigger time" will show 8:00 tomorrow.

Workflow YAML Full Version

Expand to see complete YAML
yaml
name: daily-tech-digest
version: "1.0.0"
description: 每天早 8 点的科技新闻摘要

recovery:
  autoResumeOnRestart: true
  policy: RESUME_FROM_LAST_INCOMPLETE

agents:
  writer:
    preset: writer
    overrides:
      max_iterations: 3
      provider: openai_api_key

steps:
  - id: fetch
    type: code
    language: node
    idempotent: true
    code: |
      const parser = new (await import('rss-parser')).default();
      const feeds = [
        'https://hnrss.org/frontpage',
        'https://36kr.com/feed'
      ];
      const articles = [];
      for (const url of feeds) {
        const feed = await parser.parseURL(url);
        feed.items.slice(0, 5).forEach(item => {
          articles.push({ title: item.title, url: item.link });
        });
      }
      return { articles };
    extract:
      articles: $.articles

  - id: classify
    type: classifier
    agent: writer
    task: |
      给下面这条新闻标题分一个类别:
      标题: {{steps.fetch.data.articles[0].title}}
    categories:
      - name: ai
      - name: hardware
      - name: other
    output_variable: category

  - id: summarize
    type: single
    agent: writer
    idempotent: true
    condition: "{{classifier.category}} == 'ai'"
    task: |
      用一段中文(不超过 60 字)概括下面这条 AI 新闻:
      {{steps.fetch.data.articles[0].title}}

  - id: compile
    type: code
    language: node
    depends_on: [summarize]
    code: |
      const summaries = JSON.parse(process.env.STEP_OUTPUT_SUMMARIZE || '[]');
      const md = [
        '# 📰 今日科技 AI 摘要',
        '',
        ...summaries.map((s, i) => `${i + 1}. ${s}`)
      ].join('\n');
      return { markdown: md };
    extract:
      markdown: $.markdown

  - id: review
    type: manual_approval
    title: "请审阅今日 AI 摘要"
    body: "{{steps.compile.data.markdown}}"
    notify: [in_app]

  - id: deliver
    type: sub_workflow
    module: dingyue-module-slack-deliver
    depends_on: [review]
    inputs:
      slack_webhook_url: "{{var:slack_webhook_url}}"
      message_text: "{{steps.compile.data.markdown}}"

You Will Already Know How to Do It After Doing It

  • Edit a complete workflow with YAML + Canvas dual columns
  • The actual writing methods of the five main modes of code steps, classifier, single, manual_approval, and sub_workflow
  • Different uses of variable references (var/steps/classifier/credentials)
  • validate / dry-run / real run / complete cron online cycle
  • How to cooperate between Credential Center and Workflow YAML

Next