Foundations ended on two-step chains — read from one tool, write to another. Patterns starts with three steps: read, transform, write. The middle step is pure Python — no API call. That's where the value lives.
# Step 1 — read
result = toolset.execute_action(Action.GMAIL_FETCH_EMAILS, {"max_results": 1})
messages = result.get("messages", [])
# Step 2 — transform (pure Python)
subject = messages[0].get("subject", "(no subject)") if messages else "(empty)"
row = [subject.upper(), len(subject)]
# Step 3 — write
toolset.execute_action(Action.GOOGLETASKS_CREATE_TASK, {
"tasklist_id": tasklist_id,
"title": f"chain: {row[0]} (len={row[1]})",
})The transform is just normal Python?
Always. Tools give you data; tools accept data; in between is plain Python. Uppercase, slice, sum, sort, build a dict — all the primitives from earlier tracks. The chain is read_dict → python_transform → write_dict, and the only "new" thing in week 1 is getting fluent at the shape.
What goes wrong in a 3-step chain?
Each step has its own failure mode. Step 1 returns nothing (empty inbox). Step 2 hits a KeyError because the response shape isn't what you assumed. Step 3 fails because the tasklist_id is wrong. Today we just compose them. Day 6 covers what specific errors look like, and week 2 wraps each step in retry.
The canonical automation shape:
# 1. Read — pull data from tool A
result_a = toolset.execute_action(Action.A, {...})
data = result_a.get("key", default)
# 2. Transform — pure Python
transformed = some_function(data)
# 3. Write — push to tool B
toolset.execute_action(Action.B, {... transformed ...})Foundations' two-step chain (read A → write B) ships data 1:1. The transform in the middle is where you add value:
subject.upper() — normalizelen(subject) — measure[m for m in messages if "alert" in m["subject"]] — filtersum(amounts) — aggregate{"from": m["from"], "subject": m["subject"]} — reshapeWithout the transform, you're just shuffling bytes between APIs. With it, you're computing.
Each step's output crosses a boundary into the next. Never assume the shape:
# step 1 → step 2
messages = result.get("messages", []) # list, even if API returned nothing
first = messages[0] if messages else None # don't crash on empty
# step 2 → step 3
if first:
subject = first.get("subject", "(no subject)") # default for missing field
transformed = subject.upper()
else:
transformed = "(empty)"The reflex: .get(key, default) on every read across a step boundary. Cheap, hides shape drift.
You'll write chains where the transform is 30 lines of Python and the read/write are 2 lines each. That's normal — and good. The Python you're writing is the intent of the script. The tool calls are just the I/O.
The shape generalizes:
| Read | Transform | Write |
|---|---|---|
| Gmail fetch | extract subjects | append to Sheet |
| Sheet read | filter rows | create Calendar events |
| Calendar list | summarize counts | send email |
| Tasks list | dedup titles | update tasks |
Week 1 walks through variations on this shape. Every reliability pattern in week 2 wraps it.
Foundations ended on two-step chains — read from one tool, write to another. Patterns starts with three steps: read, transform, write. The middle step is pure Python — no API call. That's where the value lives.
# Step 1 — read
result = toolset.execute_action(Action.GMAIL_FETCH_EMAILS, {"max_results": 1})
messages = result.get("messages", [])
# Step 2 — transform (pure Python)
subject = messages[0].get("subject", "(no subject)") if messages else "(empty)"
row = [subject.upper(), len(subject)]
# Step 3 — write
toolset.execute_action(Action.GOOGLETASKS_CREATE_TASK, {
"tasklist_id": tasklist_id,
"title": f"chain: {row[0]} (len={row[1]})",
})The transform is just normal Python?
Always. Tools give you data; tools accept data; in between is plain Python. Uppercase, slice, sum, sort, build a dict — all the primitives from earlier tracks. The chain is read_dict → python_transform → write_dict, and the only "new" thing in week 1 is getting fluent at the shape.
What goes wrong in a 3-step chain?
Each step has its own failure mode. Step 1 returns nothing (empty inbox). Step 2 hits a KeyError because the response shape isn't what you assumed. Step 3 fails because the tasklist_id is wrong. Today we just compose them. Day 6 covers what specific errors look like, and week 2 wraps each step in retry.
The canonical automation shape:
# 1. Read — pull data from tool A
result_a = toolset.execute_action(Action.A, {...})
data = result_a.get("key", default)
# 2. Transform — pure Python
transformed = some_function(data)
# 3. Write — push to tool B
toolset.execute_action(Action.B, {... transformed ...})Foundations' two-step chain (read A → write B) ships data 1:1. The transform in the middle is where you add value:
subject.upper() — normalizelen(subject) — measure[m for m in messages if "alert" in m["subject"]] — filtersum(amounts) — aggregate{"from": m["from"], "subject": m["subject"]} — reshapeWithout the transform, you're just shuffling bytes between APIs. With it, you're computing.
Each step's output crosses a boundary into the next. Never assume the shape:
# step 1 → step 2
messages = result.get("messages", []) # list, even if API returned nothing
first = messages[0] if messages else None # don't crash on empty
# step 2 → step 3
if first:
subject = first.get("subject", "(no subject)") # default for missing field
transformed = subject.upper()
else:
transformed = "(empty)"The reflex: .get(key, default) on every read across a step boundary. Cheap, hides shape drift.
You'll write chains where the transform is 30 lines of Python and the read/write are 2 lines each. That's normal — and good. The Python you're writing is the intent of the script. The tool calls are just the I/O.
The shape generalizes:
| Read | Transform | Write |
|---|---|---|
| Gmail fetch | extract subjects | append to Sheet |
| Sheet read | filter rows | create Calendar events |
| Calendar list | summarize counts | send email |
| Tasks list | dedup titles | update tasks |
Week 1 walks through variations on this shape. Every reliability pattern in week 2 wraps it.
Create a free account to get started. Paid plans unlock all tracks.