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".
- 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
fetch— code Steps: Use Node to capture RSS and parse it into JSON.classify— classifier Steps: Label each news item with a category label.summarize— single Steps: Generate Chinese summaries only for AI classes.compile— code Steps: Put together a whole Markdown.review— manual_approval: Pause to let me take a look before sending.deliver— sub_workflow: reuses the built-in Slack delivery module.
Step 1 · Create a New Workflow
- The main navigation point on the left is "Workflow" → "New Workflow" in the upper right corner.
- Name:
daily-tech-digest, description:每天早 8 点的科技新闻摘要. - 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):
agents:
writer:
preset: writer
overrides:
max_iterations: 3
provider: openai_api_key # 引用凭据中心里的 KeyDescription: 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:
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: $.articlesA 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.
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
- 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: categoryThe 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
- id: summarize
type: single
agent: writer
idempotent: true
condition: "{{classifier.category}} == 'ai'"
task: |
用一段中文(不超过 60 字)概括下面这条 AI 新闻:
{{steps.fetch.data.articles[0].title}}Description:
conditionLet 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
- 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: $.markdownThis 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
- id: review
type: manual_approval
title: "请审阅今日 AI 摘要"
body: "{{steps.compile.data.markdown}}"
approvers: [] # 空 = 团队所有 Admin 都可审批
notify: [in_app] # 也可以 slack / email / feishuThis 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
- 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-deliverIt 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_urlReference 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:
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.
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
- Click "Execute", Cancel Check dry-run to start execution.
- 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.
- 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.
- Click "Approve" when satisfied. The workflow automatically continues to deliver and the Slack message is sent.
- After running, return to the workflow details page to see the token/cost statistics of this execution.
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
- "Scheduling Management" on the left → "New Schedule".
- Target workflow: daily-tech-digest; Type: cron; Expression:
0 0 8 * * ?; Time zone: Asia/Shanghai. - "Parameter Defaults" can be empty - our variables all have reasonable default values.
- Save. The "next trigger time" will show 8:00 tomorrow.
Workflow YAML Full Version
Expand to see complete 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
- Read through 8 Step Types — Usage, fields, and combination constraints of each
- Learn to debug with breakpoints — Save 80% of troubleshooting time when workflow becomes complex
- Modularization and Reuse — When you have the second and third workflows, extract the common logic into sub_workflow