diff --git a/.gitignore b/.gitignore
index 6cae97504c..c4e17613b7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -171,3 +171,4 @@ jj-workflow.mdc
 third_party
 
 baml_repl_history.txt
+trace_events_debug.json
diff --git a/fern/01-guide/05-baml-advanced/runtime-observability.mdx b/fern/01-guide/05-baml-advanced/runtime-observability.mdx
new file mode 100644
index 0000000000..9a0b3ffec8
--- /dev/null
+++ b/fern/01-guide/05-baml-advanced/runtime-observability.mdx
@@ -0,0 +1,1447 @@
+---
+title: Runtime Observability
+---
+
+
+This feature was added in 0.210.0
+
+
+When running multi-step workflows, you need to be able to get information about
+the running workflow. You might need this information to show incremental
+results to your app’s users, or to debug a complex workflow combining multiple
+LLM calls.
+
+BAML makes this possible though a notification system that connects variables in your
+BAML Workflow code to the Python/TypeScript/etc client code that you used to
+invoke the workflow.
+
+## Using Markdown blocks to track execution
+
+Markdown Blocks are automatically tracked when you run BAML
+workflows, and your client code can track which block is currently executing. In
+the following example, your client can directly use the markdown headers to
+render the current status on a status page:
+
+```baml BAML
+struct Post {
+  title string
+  content string
+}
+
+// Browse a URL and produce a number of posts describing
+// its what was found there for our marketing site.
+function MakePosts(source_url: string, count: int) -> Post[] {
+  # Summarize Source
+  let source = LLMSummarizeSource(source_url);
+
+  # Determine Topic
+  let topic = LLMInferTopic(source);
+
+  # Generate Marketing Post Ideas
+  let ideas: string[] = LLMIdeas(topic, source);
+
+  # Generate posts
+  let posts: Post[] = [];
+  for (idea in ideas) {
+
+    ## Create the post
+    let post = LLMGeneratePost(idea, source);
+
+    ## Quality control
+    let quality = LLMJudgePost(post, idea, source);
+    if (quality > 8) {
+      posts.push(post);
+    }
+  }
+}
+```
+
+## Track Block Notifications
+
+You can watch block notifications from your client code.
+
+When you generate client code from your BAML code, we produce watchers
+that allow you to hook in to its execution.
+
+
+
+
+In your client code, you can bind notifications to callbacks:
+
+```python Python
+ # baml_client/watchers.py
+from typing import TypeVar, Generic, Callable, Union
+
+class BlockNotification:
+    """
+    Notification fired when entering or exiting a markdown block
+    """
+    block_label: str
+    event_type: str   # "enter" | "exit"
+
+class MakePosts:
+    """Watcher for MakePosts function"""
+
+    def on_block(self, handler: Callable[[BlockNotification], None]) -> None:
+        """Register a handler for block notification"""
+        pass
+```
+
+```python Python
+ # app.py
+ from baml_client.sync_client import { b }
+ from baml_client.types import Notification
+ import baml_client.watchers
+
+ def Example():
+   # Get a watcher with the right type for your MakePosts() function.
+   watcher = watchers.MakePosts()
+
+   # Associate the block notification with your own callback.
+   watcher.on_block(lambda notif: print(notif.block_label))
+
+   # Invoke the function.
+   posts = b.MakePosts("https://wikipedia.org/wiki/DNA", {"watchers": watcher})
+   print(posts)
+```
+
+
+
+In your client code, you can bind notifications to callbacks:
+
+```typescript
+// baml_client/watchers.ts
+export interface BlockNotification {
+  block_label: string;
+  event_type: "enter" | "exit";
+}
+
+export interface MakePosts {
+  // Register a handler for block notifications.
+  on_block(handler: (notif: BlockNotification) => void): void;
+}
+```
+
+```typescript
+ // index.ts
+ import { b, watchers } from "./baml-client"
+ import type { Notification } from "./baml-client/types"
+
+ async function Example() {
+   // Get a watcher with the right type
+   // for your MakePosts() function.
+   let watcher = watchers.MakePosts()
+
+   // Associate the block notification with your own callback.
+   watcher.on_block((notif) => {
+     console.log(notif.block_label)
+   });
+
+   // Invoke the function.
+   const posts = await b.MakePosts(
+     "https://wikipedia.org/wiki/DNA",
+     {"watchers": watcher}
+   )
+   console.log(posts)
+ }
+```
+
+
+
+In your client code, you can consume notifications via channels:
+
+```go
+// baml_client/watchers.go
+package watchers
+
+type BlockNotification struct {
+    BlockLabel string    `json:"block_label"`
+    EventType  string    `json:"event_type"` // "enter" | "exit"
+}
+
+type MakePosts struct {
+    blockNotifications chan BlockNotification
+    // ... additional notification channels are initialized elsewhere.
+}
+
+func NewMakePosts() *MakePosts {
+    return &MakePosts{
+        blockNotifications: make(chan BlockNotification, 100),
+    }
+}
+
+// BlockNotifications provides block execution updates as a channel.
+func (c *MakePosts) BlockNotifications() <-chan BlockNotification {
+    return c.blockNotifications
+}
+```
+
+```go
+// main.go
+package main
+
+import (
+    "context"
+    "fmt"
+    "log"
+
+    b "example.com/myproject/baml_client"
+    "example.com/myproject/baml_client/watchers"
+)
+
+func main() {
+    ctx := context.Background()
+
+    // Get a watcher with the right channels
+    // for your MakePosts() function.
+    watcher := watchers.NewMakePosts()
+
+    // Consume block notifications asynchronously so updates are printed as they arrive.
+    go func() {
+        for blockNotif := range watcher.BlockNotifications() {
+            fmt.Println(blockNotif.BlockLabel)
+        }
+    }()
+
+    // Invoke the function.
+    posts, err := b.MakePosts(ctx, "https://wikipedia.org/wiki/DNA", &b.MakePostsOptions{
+        Watchers: watcher,
+    })
+    if err != nil {
+        log.Fatal(err)
+    }
+    fmt.Printf("%+v\n", posts)
+}
+```
+
+
+
+## Track variables with `@watch`
+
+Variable updates can also be tracked with notifications. To mark an update for tracking,
+attach `@watch` to the variable declaration (or update its options) so the runtime knows to emit changes.
+
+```baml BAML
+let foo = State { counter: 0 } @watch;
+foo.counter += 1; // *** This triggers a notification
+```
+
+
+    ```python
+    watcher.vars.foo(lambda st: my_state_update_handler(st))
+    ```
+
+    ```typescript
+    watcher.on_var_foo((st) => my_state_update_handler(st))
+    ```
+
+    ```go
+    // Consume notifications from the watcher.FooNotifications channel
+    for st := range watcher.FooNotifications() {
+        handleState(st)
+    }
+    ```
+
+
+Updates can be tracked automatically or manually, depending on the `@watch` options you choose.
+Automatic tracking will emit notifications any time a variable is updated. Manual tracking
+only emits notifications after updates that you specify.
+
+### Auto Tracking
+
+
+
+Let’s see how we would use this capability to automatically track the progress of our
+marketing post
+generation workflow:
+
+```baml BAML
+function MakePosts(source_url: string) -> Post[] {
+  let source = LLMSummarizeSource(source_url);
+  let topic = LLMInferTopic(source);
+  let ideas: string[] = LLMIdeas(topic, source);
+  let posts_target_length = ideas.len();
+
+  let progress_percent: int = 0 @watch; // *** Watch marker used here.
+
+  let posts: Post[] = [];
+  for ((i,idea) in ideas.enumerate()) {
+    let post = LLMGeneratePost(idea, source);
+    let quality = LLMJudgePost(post, idea, source);
+    if (quality > 8) {
+      posts.push(post);
+    } else {
+      posts_target_length -= 1;
+    }
+    // *** This update will trigger notifications visible to the client.
+    progress_percent = i * 100 / posts_target_length
+  }
+}
+```
+
+### Watch parameters
+
+The variable tracking can be controled in several ways.
+
+ - `@watch(when=MyFilterFunc)` - Only emits when `MyFilterFunc` returns `true`
+ - `@watch(when=manual)` - Never auto emit (only emit when manually triggered)
+ - `@watch(skip_def=true)` - Emits every time the variable is updated, but not on initialization
+ - `@watch(name=my_channel)` - Emits notifications on a channel you spceify (default is the variable name)
+
+The filter functions you pass to `when` should take two parameters. It will be called every
+time an value is updated. The first parameter is the previous version of the value, and the
+second is the new version. With these two parameters, you can determine whether the notification should
+be emitted or not (often by comparing the current to the previous, for deduplication).
+
+If you do not specify a filter function, BAML deduplicates automatically emitted notifications for you.
+You could replicate the same behavior by using `@watch(when=MyFilterFunc)` where `MyFilterFunc`
+is defined as:
+
+```baml BAML
+function MyFilterFunc(prev: MyObject, curr: MyObject) -> bool {
+    !(prev.id() == curr.id()) || !(baml.deep_eq(prev, curr))
+}
+```
+
+### Manual Tracking
+
+Sometimes you want no automatic tracking at all. For example, if you are building up a complex
+value in multiple steps, you may not want your application to see that value while it is still
+under construction. In that case, use `@watch(when=manual)` when initializing the variable, and
+call `.watchers.$notify()` on the variable when you want to manually trigger a notification.
+
+
+```baml BAML
+function ConstructValue(description: string) -> Character {
+  let character = Character { name: "", age: 0, skills: [] } @watch(when=manual);
+  character.name = LLMChooseName(description);
+  character.age = LLMChooseAge(description);
+  character.skills = LLMChooseSkills(description);
+  character.watchers.$notify() // *** Only notify when done building the character.
+}
+```
+
+### Sharing a Channel
+
+Sometimes you want multiple variables to send update notifications on the same channel, for example,
+if you want a single view of all the state updates from multiple values in your BAML code,
+because you will render them into a single view in the order that they are emitted.
+
+```baml BAML
+function DoWork() -> bool {
+  let status = "Starting" @watch(name=updates);
+  let progress = 0 @watch(name=updates, skip_def=true);
+  for (let i = 0; i < 100; i++) {
+    progress = i; // *** These updates will apear on the `updates` channel.
+  }
+  status = "Done";
+  return true;
+}
+```
+
+## Receiving Notifications from Client Code
+
+
+
+
+
+When you generate a BAML client for our original function, your Python SDK will
+include a `MakePosts` watcher class. This class contains configurable callbacks
+for all your tracked variables. For example, it contains callbacks for `progress_percent`
+because we marked that variable with `@watch`. The callbacks will receive an `int` data payload,
+because `progress_percent` is an `int`.
+
+```python
+# baml_client/watchers.py
+
+T = TypeVar('T')
+
+class VarNotification(Generic[T]):
+    """
+    Notification fired when a watched variable is updated
+    """
+    value: T
+    timestamp: str
+    function_name: str
+
+class MakePostsVarsCollector:
+    progress_percent: Callable[[VarNotification[int]], None]
+
+class MakePosts:
+    """Watcher for MakePosts function"""
+    vars: MakePostsVarsCollector
+```
+
+```python
+# app.py
+from baml_client.sync_client import { b }
+from baml_client.types import Notification
+import baml_client.watchers
+
+def Example():
+  # Get a watcher with the right type
+  # for your MakePosts() function.
+  watcher = watchers.MakePosts()
+
+  # Track the progress_percent variable updates
+  watcher.vars.progress_percent(lambda percent: print(f"Progress: {percent}%"))
+
+  # Invoke the function.
+  posts = b.MakePosts("https://wikipedia.org/wiki/DNA", {"watchers": watcher})
+  print(posts)
+```
+
+
+
+
+When you generate a BAML client for this function, its `MakePosts` watcher
+will accept callbacks for `progress_percent` because we marked that variable with
+`@watch`, and the callbacks will receive an `int` data payload, because
+`progress_percent` is an `int`.
+
+```typescript
+// baml_client/watchers.ts
+import { VarNotification } from "./types"
+
+export interface MakePosts {
+  on_var_progress_percent(callback: (percent: number) => void): void
+}
+
+export function MakePosts(): MakePosts {
+  return {
+    on_var_progress_percent(callback: (percent: number) => void): void {
+      // Implementation details
+    }
+  }
+}
+```
+
+```typescript
+// index.ts
+import { b, watchers } from "./baml-client"
+import type { VarNotification } from "./baml-client/types"
+
+async function Example() {
+  // Get a watcher with the right type
+  // for your MakePosts() function.
+  let watcher = watchers.MakePosts()
+
+  // Track the progress_percent variable updates
+  watcher.on_var_progress_percent((percent) => {
+    console.log(`Progress: ${percent}%`)
+  });
+
+  // Invoke the function.
+  const posts = await b.MakePosts(
+    "https://wikipedia.org/wiki/DNA",
+    {"watchers": watcher }
+  )
+  console.log(posts)
+}
+```
+
+
+
+
+In your client code, you can track these watched variables by constructing the
+generated watcher and reading from the channels it exposes.
+
+```go
+// baml_client/watchers.go
+package watchers
+
+import "time"
+
+type BlockNotification struct {
+    BlockLabel string    `json:"block_label"`
+    EventType  string    `json:"event_type"` // "enter" | "exit"
+    Timestamp  time.Time `json:"timestamp"`
+}
+
+type VarNotification[T any] struct {
+    VariableName string    `json:"variable_name"`
+    Value        T         `json:"value"`
+    Timestamp    time.Time `json:"timestamp"`
+    FunctionName string    `json:"function_name"`
+}
+
+type MakePosts struct {
+    blockNotifications           chan BlockNotification
+    progressPercentNotifications chan VarNotification[int]
+}
+
+func NewMakePosts() *MakePosts {
+    return &MakePosts{
+        blockNotifications:           make(chan BlockNotification, 100),
+        progressPercentNotifications: make(chan VarNotification[int], 100),
+    }
+}
+
+// BlockNotifications returns block execution updates.
+func (c *MakePosts) BlockNotifications() <-chan BlockNotification {
+    return c.blockNotifications
+}
+
+// ProgressPercentNotifications streams progress_percent variable updates.
+func (c *MakePosts) ProgressPercentNotifications() <-chan VarNotification[int] {
+    return c.progressPercentNotifications
+}
+```
+
+```go
+// main.go
+package main
+
+import (
+    "context"
+    "fmt"
+    "log"
+
+    b "example.com/myproject/baml_client"
+    "example.com/myproject/baml_client/watchers"
+)
+
+func main() {
+    ctx := context.Background()
+
+    // Get a watcher with the right channels
+    // for your MakePosts() function.
+    watcher := watchers.NewMakePosts()
+
+    // Consume block notifications and progress updates concurrently.
+    go func() {
+        for block := range watcher.BlockNotifications() {
+            fmt.Printf("Block: %s\n", block.BlockLabel)
+        }
+    }()
+
+    go func() {
+        for percent := range watcher.ProgressPercentNotifications() {
+            fmt.Printf("Progress: %d%%\n", percent.Value)
+        }
+    }()
+
+    // Invoke the function.
+    posts, err := b.MakePosts(ctx, "https://wikipedia.org/wiki/DNA", &b.MakePostsOptions{
+        Watchers: watcher,
+    })
+    if err != nil {
+        log.Fatal(err)
+    }
+    fmt.Printf("%+v\n", posts)
+}
+```
+
+
+
+For details about the types of notifications, see [BAML Language Reference](/ref/baml_client/watchers)
+
+# Streaming
+
+If updates to variables tagged with `@watch` include large amounts of data that you want to start
+surfacing to your application before they are done being generated, you want to use
+the streaming notification interface. Streaming notifications are available for all `@watch` variables,
+but they are generally only useful when assigning a variable from the result of
+an LLM function. All other streamed notifications will return their values in a single
+complete chunk.
+
+```baml BAML
+function DescribeTerminatorMovies() -> string[] {
+  let results = [];
+  for (x in [1,2,3]) {
+    let movie_text = LLMElaborateOnTopic("Terminator " + std.to_string(x)) @watch;
+    results.push(movie_text);
+  }
+  return results;
+}
+
+function LLMElaborateOnTopic(topic: string) -> string {
+  client GPT4
+  prompt #"
+    Write a detailed 500-word analysis of {{ topic }}.
+    Include plot summary, themes, and cultural impact.
+  "#
+}
+```
+
+This function will take a while to run because it calls an LLM function
+three times. However, you can stream the results of each of these calls
+to start getting immediate feedback from the workflow as the LLM generates
+text token by token.
+
+The streaming listeners are available in client code under a separate streaming
+module that mirrors the structure of the regular watchers.
+
+
+
+
+```python
+# baml_client/watchers.py
+from typing import TypeVar, Generic, Callable
+from baml_client.types import BamlStream, VarNotification
+
+T = TypeVar('T')
+
+class VarNotification(Generic[T]):
+    """
+    Notification fired when a watched variable is updated
+    """
+    variable_name: str
+    value: T
+    timestamp: str
+    function_name: str
+
+class BlockNotification:
+    """
+    Notification fired when entering or exiting a markdown block
+    """
+    block_label: str
+    event_type: str   # "enter" | "exit"
+
+class MakePostsVarsCollector:
+    progress_percent: Callable[[VarNotification[int]], None]
+
+class DescribeTerminatorMovies:
+    """Watcher for DescribeTerminatorMovies function with both regular and streaming notifications"""
+
+    def on_block(self, handler: Callable[[BlockNotification], None]) -> None:
+        """Register a handler for block notifications"""
+        pass
+
+    def on_var_movie_text(self, handler: Callable[[VarNotification[str]], None]) -> None:
+        """Register a handler for movie_text variable updates"""
+        pass
+
+    def on_stream_movie_text(self, handler: Callable[[BamlStream[VarNotification[str]]], None]) -> None:
+        """Register a handler for streaming movie_text variable updates"""
+        pass
+```
+
+```python
+# app.py
+from baml_client.sync_client import b
+import baml_client.watchers as watchers
+
+def example():
+    # Create the unified watcher
+    watcher = watchers.DescribeTerminatorMovies()
+
+    # Track streaming updates for the main watched variable
+    def handle_movie_text_stream(stream):
+        for notif in stream:
+            print(f"Streaming movie text: {notif.value}")
+
+    watcher.on_stream_movie_text(handle_movie_text_stream)
+
+    # Invoke the function with watchers
+    results = b.DescribeTerminatorMovies({"watchers": watcher})
+    print("Final results:", results)
+```
+
+
+
+
+```typescript
+// baml_client/watchers.ts
+import { BamlStream, VarNotification } from "./types";
+
+export interface BlockNotification {
+  block_label: string;
+  event_type: "enter" | "exit";
+}
+
+export interface VarNotification {
+  variable_name: string;
+  value: T;
+  timestamp: string;
+  function_name: string;
+}
+
+export interface DescribeTerminatorMovies {
+  // Regular notification handlers
+  on_block(handler: (notif: BlockNotification) => void): void;
+  on_var_movie_text(handler: (notif: VarNotification) => void): void;
+
+  // Streaming notification handlers
+  on_stream_movie_text(handler: (stream: BamlStream>) => void): void;
+}
+
+export function DescribeTerminatorMovies(): DescribeTerminatorMovies {
+  return {
+    on_block(handler: (notif: BlockNotification) => void): void {
+      // Implementation details
+    },
+    on_var_movie_text(handler: (notif: VarNotification) => void): void {
+      // Implementation details
+    },
+    on_stream_movie_text(handler: (stream: BamlStream>) => void): void {
+      // Implementation details
+    }
+  }
+}
+```
+
+```typescript
+// index.ts
+import { b, watchers } from "./baml-client"
+
+async function example() {
+  // Create the unified watcher
+  let watcher = watchers.DescribeTerminatorMovies()
+
+  // Track streaming updates for the main watched variable
+  watcher.on_stream_movie_text(async (stream) => {
+    for await (const notif of stream) {
+      console.log(`Streaming movie text: ${notif.value}`)
+    }
+  })
+
+  // Invoke the function with watchers
+  const results = await b.DescribeTerminatorMovies({"watchers": watcher})
+  console.log("Final results:", results)
+}
+```
+
+
+
+
+```go
+// baml_client/watchers.go
+package watchers
+
+import "time"
+
+type BlockNotification struct {
+    BlockLabel string    `json:"block_label"`
+    EventType  string    `json:"event_type"` // "enter" | "exit"
+    Timestamp  time.Time `json:"timestamp"`
+}
+
+type VarNotification[T any] struct {
+    VariableName string    `json:"variable_name"`
+    Value        T         `json:"value"`
+    Timestamp    time.Time `json:"timestamp"`
+    FunctionName string    `json:"function_name"`
+}
+
+type DescribeTerminatorMovies struct {
+    blockNotifications      chan BlockNotification
+    movieTextNotifications  chan VarNotification[string]
+    movieTextStreams        chan (<-chan VarNotification[string])
+}
+
+func NewDescribeTerminatorMovies() *DescribeTerminatorMovies {
+    return &DescribeTerminatorMovies{
+        blockNotifications:      make(chan BlockNotification, 100),
+        movieTextNotifications:  make(chan VarNotification[string], 100),
+        movieTextStreams:        make(chan (<-chan VarNotification[string]), 10),
+    }
+}
+
+func (c *DescribeTerminatorMovies) BlockNotifications() <-chan BlockNotification {
+    return c.blockNotifications
+}
+
+func (c *DescribeTerminatorMovies) MovieTextNotifications() <-chan VarNotification[string] {
+    return c.movieTextNotifications
+}
+
+// MovieTextStreams produces a stream-of-streams for watched movie_text updates.
+func (c *DescribeTerminatorMovies) MovieTextStreams() <-chan (<-chan VarNotification[string]) {
+    return c.movieTextStreams
+}
+```
+
+```go
+// main.go
+package main
+
+import (
+    "context"
+    "fmt"
+    "log"
+
+    b "example.com/myproject/baml_client"
+    "example.com/myproject/baml_client/watchers"
+)
+
+func main() {
+    ctx := context.Background()
+
+    // Create the unified watcher
+    watcher := watchers.NewDescribeTerminatorMovies()
+
+    // Track block notifications and single-value updates concurrently.
+    go func() {
+        for block := range watcher.BlockNotifications() {
+            fmt.Printf("Block: %s\n", block.BlockLabel)
+        }
+    }()
+
+    go func() {
+        for movieText := range watcher.MovieTextNotifications() {
+            fmt.Printf("Variable movie text: %s\n", movieText.Value)
+        }
+    }()
+
+    // Track streaming updates using the channel-of-channels pattern.
+    go func() {
+        for stream := range watcher.MovieTextStreams() {
+            go func(inner <-chan watchers.VarNotification[string]) {
+                for notif := range inner {
+                    fmt.Printf("Streaming movie text: %s\n", notif.Value)
+                }
+            }(stream)
+        }
+    }()
+
+    // Invoke the function with watchers
+    results, err := b.DescribeTerminatorMovies(ctx, &b.DescribeTerminatorMoviesOptions{
+        Watchers: watcher,
+    })
+    if err != nil {
+        log.Fatal(err)
+    }
+    fmt.Printf("Final results: %+v\n", results)
+}
+```
+
+
+
+## Combining Regular Notifications and Streaming
+
+You can use both regular notifications and streaming notifications together in a single unified watcher to get comprehensive observability:
+
+
+
+
+```python
+from baml_client.sync_client import b
+import baml_client.watchers as watchers
+
+def comprehensive_example():
+    # Create unified watcher
+    watcher = watchers.DescribeTerminatorMovies()
+
+    # Regular notifications for workflow progress
+    watcher.on_block(lambda block: print(f"Block: {block.block_label}"))
+    watcher.vars.movie_text(lambda movie_text: print(f"Variable movie text: {movie_text.value}"))
+
+    # Streaming notifications for real-time content
+    def handle_stream(stream):
+        for notif in stream:
+            print(f"Streaming content: {notif.value}")
+
+    watcher.on_stream_movie_text(handle_stream)
+
+    # Use single watchers parameter
+    results = b.DescribeTerminatorMovies({"watchers": watcher})
+```
+
+
+
+
+```typescript
+import { b, watchers } from "./baml-client"
+
+async function comprehensiveExample() {
+    // Create unified watcher
+    let watcher = watchers.DescribeTerminatorMovies()
+
+    // Regular notifications for workflow progress
+    watcher.on_block((block) => console.log(`Block: ${block.block_label}`))
+    watcher.on_var_movie_text((movieText) => console.log(`Variable movie text: ${movieText.value}`))
+
+    // Streaming notifications for real-time content
+    watcher.on_stream_movie_text(async (stream) => {
+        for await (const notif of stream) {
+            console.log(`Streaming content: ${notif.value}`)
+        }
+    })
+
+    // Use single watchers parameter
+    const results = await b.DescribeTerminatorMovies({ watchers: watcher })
+}
+```
+
+
+
+
+```go
+func comprehensiveExample() {
+    ctx := context.Background()
+
+    // Create unified watcher
+    watcher := watchers.NewDescribeTerminatorMovies()
+
+    // Regular notifications for workflow progress
+    go func() {
+        for block := range watcher.BlockNotifications() {
+            fmt.Printf("Block: %s\n", block.BlockLabel)
+        }
+    }()
+    go func() {
+        for movieText := range watcher.MovieTextNotifications() {
+            fmt.Printf("Variable movie text: %s\n", movieText.Value)
+        }
+    }()
+
+    // Streaming notifications for real-time content
+    go func() {
+        for stream := range watcher.MovieTextStreams() {
+            go func(inner <-chan watchers.VarNotification[string]) {
+                for notif := range inner {
+                    fmt.Printf("Streaming content: %s\n", notif.Value)
+                }
+            }(stream)
+        }
+    }()
+
+    // Use single Watchers parameter
+    results, err := b.DescribeTerminatorMovies(ctx, &b.DescribeTerminatorMoviesOptions{
+        Watchers: watcher,
+    })
+    if err != nil {
+        log.Fatal(err)
+    }
+}
+```
+
+
+
+# Usage Scenarios
+
+## Track notifications from subfunctions
+
+When your main workflow calls other BAML functions, you can track notifications from those subfunctions as well. If `MakePosts()` calls `Foo()`, and `Foo()` contains variables tagged with `@watch` or markdown blocks, the client invoking `MakePosts()` can subscribe to those subfunction notifications through dedicated records in the watcher.
+
+Consider this example where `MakePosts()` calls a helper function:
+
+```baml BAML
+function MakePosts(source_url: string) -> Post[] {
+  let posts = GeneratePostsWithProgress(source_url);
+  return posts;
+}
+
+function GeneratePostsWithProgress(url: string) -> Post[] {
+  # Analyzing content
+  let content = LLMAnalyzeContent(url);
+
+  let progress_status = "Starting generation" @watch;
+
+  # Generate posts
+  let posts = [];
+  for (i in [1,2,3]) {
+    progress_status = "Generating post " + i.to_string();
+    posts.push(LLMGeneratePost(content, i));
+  }
+
+  return posts;
+}
+```
+
+
+
+
+```python
+# baml_client/watchers.py
+
+class GeneratePostsWithProgress:
+    """Watcher for GeneratePostsWithProgress function"""
+
+    def on_block(self, handler: Callable[[BlockNotification], None]) -> None:
+        """Register a handler for block notifications from this function"""
+        pass
+
+    def on_var_progress_status(self, handler: Callable[[VarNotification[str]], None]) -> None:
+        """Register a handler for progress_status variable updates"""
+        pass
+
+class MakePosts:
+    """Watcher for MakePosts function"""
+
+    def __init__(self):
+        self.function_GeneratePostsWithProgress = GeneratePostsWithProgress()
+```
+
+```python
+# app.py
+from baml_client.sync_client import b
+import baml_client.watchers as watchers
+
+def example():
+    # Create the main watcher
+    watcher = watchers.MakePosts()
+
+    # Subscribe to subfunction notifications
+    watcher.function_GeneratePostsWithProgress.vars.progress_status(
+        lambda e: print(f"Subfunction progress: {e.value}")
+    )
+
+    watcher.function_GeneratePostsWithProgress.on_block(
+        lambda e: print(f"Subfunction block: {e.block_label}")
+    )
+
+    # Invoke the function
+    posts = b.MakePosts("https://wikipedia.org/wiki/DNA", {"watchers": watcher})
+    print(posts)
+```
+
+
+
+
+```typescript
+// baml_client/watchers.ts
+
+export interface GeneratePostsWithProgress {
+  on_block(handler: (notif: BlockNotification) => void): void;
+  on_var_progress_status(handler: (notif: VarNotification) => void): void;
+}
+
+export interface MakePosts {
+  function_GeneratePostsWithProgress: GeneratePostsWithProgress;
+}
+
+export function MakePosts(): MakePosts {
+  return {
+    function_GeneratePostsWithProgress: {
+      on_block(handler: (notif: BlockNotification) => void): void {
+        // Implementation details
+      },
+      on_var_progress_status(handler: (notif: VarNotification) => void): void {
+        // Implementation details
+      }
+    }
+  }
+}
+```
+
+```typescript
+// index.ts
+import { b, watchers } from "./baml-client"
+
+async function example() {
+  // Create the main watcher
+  let watcher = watchers.MakePosts()
+
+  // Subscribe to subfunction notifications
+  watcher.function_GeneratePostsWithProgress.on_var_progress_status((e) => {
+    console.log(`Subfunction progress: ${e.value}`)
+  })
+
+  watcher.function_GeneratePostsWithProgress.on_block((e) => {
+    console.log(`Subfunction block: ${e.block_label}`)
+  })
+
+  // Invoke the function
+  const posts = await b.MakePosts("https://wikipedia.org/wiki/DNA", {"watchers": watcher})
+  console.log(posts)
+}
+```
+
+
+
+
+```go
+// baml_client/watchers.go
+package watchers
+
+import "time"
+
+type BlockNotification struct {
+    BlockLabel string    `json:"block_label"`
+    EventType  string    `json:"event_type"`
+    Timestamp  time.Time `json:"timestamp"`
+}
+
+type VarNotification[T any] struct {
+    VariableName string    `json:"variable_name"`
+    Value        T         `json:"value"`
+    Timestamp    time.Time `json:"timestamp"`
+    FunctionName string    `json:"function_name"`
+}
+
+type GeneratePostsWithProgress struct {
+    blockNotifications          chan BlockNotification
+    progressStatusNotifications chan VarNotification[string]
+}
+
+func newGeneratePostsWithProgress() *GeneratePostsWithProgress {
+    return &GeneratePostsWithProgress{
+        blockNotifications:          make(chan BlockNotification, 100),
+        progressStatusNotifications: make(chan VarNotification[string], 100),
+    }
+}
+
+func (c *GeneratePostsWithProgress) BlockNotifications() <-chan BlockNotification {
+    return c.blockNotifications
+}
+
+func (c *GeneratePostsWithProgress) ProgressStatusNotifications() <-chan VarNotification[string] {
+    return c.progressStatusNotifications
+}
+
+type MakePosts struct {
+    blockNotifications                    chan BlockNotification
+    progressPercentNotifications          chan VarNotification[int]
+    FunctionGeneratePostsWithProgress *GeneratePostsWithProgress
+}
+
+func NewMakePosts() *MakePosts {
+    return &MakePosts{
+        blockNotifications:           make(chan BlockNotification, 100),
+        progressPercentNotifications: make(chan VarNotification[int], 100),
+        FunctionGeneratePostsWithProgress: newGeneratePostsWithProgress(),
+    }
+}
+
+func (c *MakePosts) BlockNotifications() <-chan BlockNotification {
+    return c.blockNotifications
+}
+
+func (c *MakePosts) ProgressPercentNotifications() <-chan VarNotification[int] {
+    return c.progressPercentNotifications
+}
+```
+
+```go
+// main.go
+package main
+
+import (
+    "context"
+    "fmt"
+    "log"
+
+    b "example.com/myproject/baml_client"
+    "example.com/myproject/baml_client/watchers"
+)
+
+func main() {
+    ctx := context.Background()
+
+    // Create the main watcher
+    watcher := watchers.NewMakePosts()
+
+    // Consume subfunction streams as well as top-level updates.
+    go func() {
+        for block := range watcher.FunctionGeneratePostsWithProgress.BlockNotifications() {
+            fmt.Printf("Subfunction block: %s\n", block.BlockLabel)
+        }
+    }()
+
+    go func() {
+        for status := range watcher.FunctionGeneratePostsWithProgress.ProgressStatusNotifications() {
+            fmt.Printf("Subfunction progress: %s\n", status.Value)
+        }
+    }()
+
+    // Invoke the function
+    posts, err := b.MakePosts(ctx, "https://wikipedia.org/wiki/DNA", &b.MakePostsOptions{
+        Watchers: watcher,
+    })
+    if err != nil {
+        log.Fatal(err)
+    }
+    fmt.Printf("%+v\n", posts)
+}
+```
+
+
+
+## Track values across names
+
+In JavaScript and Python, values can be referenced by multiple names, and
+updating the value through one name will update it through the other names,
+too.
+
+```python Python
+x = { "name": "BAML" }           # Make a python dict
+y = x                            # Alias x to a new name
+y["name"] = "Baml"               # Modify the name
+assert(x["name"] == "Baml")      # The original value is updated
+```
+
+The same rule applies to variables tagged with `@watch`. Anything causing a change to a value will
+cause the value to emit a notification to listeners that subscribe to the original
+variable.
+
+```baml BAML
+let x = Foo { name: "BAML" } @watch;   // Make a BAML value that auto-emits
+let y = x;                             // Alias x to a new name
+y.name = "Baml";                      // Modify the new name => triggers notification
+
+let a: int = 1 @watch;                  // Make a tracked BAML value
+let b = a;                             // Alias a to a new name
+b++;                                   // Modify the new name => No new notification
+                                       //   (see Note below)
+```
+
+
+    Changes through a separate name for simple values like ints and strings,
+    on the other hand, wil not result in notifications being emitted, because when you
+    assign a new variable to an old variable holding plain data, the new variable
+    will receive a copy of the data, and modifying that copy will not affect
+    the original value.
+
+    As a rule of thumb, if a change to the new variable causes a change to the
+    old value, then the original variable will emit a notification.
+
+
+## Track values that get packed into data structures
+
+If you put a value into a data structure, then modify it through that data structure,
+the value will continue to emit a notification.
+
+```baml BAML
+let x = Foo { name: "BAML" } @watch; // Make a tracked BAML value
+let y = [x];                        // Pack x into a list
+y[0].name = "Baml";                // Modify the list item => triggers notification
+```
+
+Reminder: In Python and TypeScript, if you put a variable `x` into a list, then
+modify it through the list, printing `x` will show the modified value. So
+modifying `x` through `y[0]` above will also result in a notification being emitted.
+
+## Track variables across function calls
+
+When you pass a `@watch` variable to a function, there are two possible outcomes
+if the called function modifies the variable:
+
+1. The modifications will be remembered by the system, but only the final
+   change to the variable will be emitted, and that will only happen when
+   the function returns. **OR:**
+1. The modification will immediately result in the firing of a notification.
+
+You get to choose the behavior based on the needs of your workflow. If the function
+is doing some setup work that makes multiple changes to the watched value to build
+it up to a valid result before the function returns, use Option 1 to hide the notifications
+from all those intermediate states. But if the sub-function is part of a workflow
+and you are using notifications to track all updates to your workflow's state, use Option 2
+to see all the intermediate updates in real time.
+
+
+    The notification-emission behavior of Option 1 differs from the rule of thumb given
+    above about Python and TypeScript.
+    We offer two steparate options because there are legitimate cases where you would
+    not want the intermediate states to be emitted - for example if they violate
+    invariants of your type.
+
+
+To choose between modes, annotate the parameter with `@watch` in the function signature.
+
+```baml BAML
+function Main() -> int {
+  let state = Foo {
+    name: "BAML",
+    counter: 0,
+  } @watch; // Track state updates automatically
+  ReadState(state);
+  ChangeState(state);
+  0
+}
+
+// This function uses Option 1, `state` in `Main()` will only fire one
+// notification, when the function returns, even through `s` is modified twice.
+function ReadState(state: Foo) -> Foo {
+  state.counter++;
+  state.counter++;
+}
+
+// This function uses Option 2, the `s` parameter is
+// marked with `@watch`, so `state` in `Main()` will fire two notifications,
+// one for each update of `s`.
+function ChangeState(s: Foo @watch) -> null {
+  s.counter++;
+  s.name = "Baml";
+}
+```
+
+# Comparison with other observability systems
+
+The `@watch` system differs from many observability systems by focusing on automatic updates
+and typesafe notification listeners. The ability to generate client code from your BAML
+programs is what allows us to create this tight integration.
+
+Let's compare BAML's observability to several other systems to get a better understanding
+of the trade-offs.
+
+## Logging and printf debugging
+
+The most common way of introspecting a running program is to add logging statements in
+your client's logging framework. Let's compare a simple example workflow in native
+Python to one instrumented in BAML.
+
+```python Python
+import logging
+from typing import List, Dict, Any
+
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+def LLMAnalizeSentiment(message: str) -> str: pass  # Assumed function
+def LLMSummarizeSentiments(sentiments: List[str]) -> str: pass  # Assumed function
+
+class Response(BaseModel):
+  sentiments: string[]
+  summary: string
+
+def analyze_sentiments(phrases: List[str]) -> Response:
+    logger.info(f"Starting analysis of {len(phrases)} phrases")
+
+    sentiments = []
+    for i, phrase in enumerate(phrases, 1):
+        logger.info(f"Analyzing phrase {i}/{len(phrases)}")
+        sentiment = LLMAnalizeSentiment(phrase)
+        sentiments.append({"phrase": phrase, "sentiment": sentiment})
+
+    logger.info("Generating summary")
+    summary = LLMSummarizeSentiments([s["sentiment"] for s in sentiments])
+
+    logger.info("Analysis complete")
+    return Response(sentiments=sentiments, summary=summary)
+```
+
+With BAML's block notifications, we don't need to mix explicit logging with the workflow
+logic. When a logged notification needs extra context (such as the index of an item being
+processed from a list), we can use a `@watch` variable.
+
+```baml BAML
+function LLMAnalyzeSentiment(message: string) -> string { ... }
+function LLMSummarizeSentiments(message: string) -> string { ... }
+
+class Response {
+  sentiments string[]
+  summary string
+}
+
+function AnalyzeSentiments(messages: string[]) -> Response {
+    let status = "Starting analysis of " + messages.length().to_string() + " messages" @watch;
+
+    sentiments = []
+    for i, message in enumerate(messages, 1):
+        status = `Analyzing message ${i}/${messages.len()}`
+        sentiments.push(LLMAnalizeSentiment(message))
+
+    status = "Generating summary";
+    summary = LLMSummarizeSentiments([s["sentiment"] for s in sentiments])
+
+    status = "Analysis complete"
+    return Response(sentiments=sentiments, summary=summary)
+}
+```
+
+## Vercel AI SDK Generators
+
+In Vercel's AI SDK, you can use TypeScript generators to yield incremental updates during tool execution. The calling code can consume these yielded values to provide real-time feedback to users.
+
+```typescript TypeScript (Vercel AI SDK)
+import { UIToolInvocation, tool } from 'ai';
+import { z } from 'zod';
+
+export const weatherTool = tool({
+  description: 'Get the weather in a location',
+  inputSchema: z.object({ city: z.string() }),
+  async *execute({ city }: { city: string }) {
+    yield { state: 'loading' as const };
+
+    // Add artificial delay to simulate API call
+    await new Promise(resolve => setTimeout(resolve, 2000));
+
+    const weatherOptions = ['sunny', 'cloudy', 'rainy', 'snowy', 'windy'];
+    const weather =
+      weatherOptions[Math.floor(Math.random() * weatherOptions.length)];
+
+    yield {
+      state: 'ready' as const,
+      temperature: 72,
+      weather,
+    };
+  },
+});
+```
+
+The calling code streams these incremental updates by consuming the generator chunks:
+
+```typescript TypeScript (Consuming streaming yields)
+import { streamText } from 'ai';
+import { openai } from '@ai-sdk/openai';
+
+const stream = streamText({
+  model: openai('gpt-4'),
+  tools: { weather: weatherTool },
+  messages: [{ role: 'user', content: 'What is the weather in New York?' }],
+});
+
+// Stream individual yielded values as they arrive
+for await (const chunk of stream) {
+  if (chunk.type === 'tool-call-streaming-start') {
+    console.log('Weather tool started...');
+  } else if (chunk.type === 'tool-result') {
+    // Each yield from the generator appears here
+    if (chunk.result.state === 'loading') {
+      console.log('Weather lookup in progress...');
+    } else if (chunk.result.state === 'ready') {
+      console.log(`Weather: ${chunk.result.weather}, Temperature: ${chunk.result.temperature}°F`);
+    }
+  } else if (chunk.type === 'text-delta') {
+    // Stream the AI's text response
+    process.stdout.write(chunk.textDelta);
+  }
+}
+```
+
+This pattern provides a great mix of streaming and type safety. It differs architecturally
+from the pattern in BAML, where Workflow logic is separated from notification handling logic.
+
+In BAML, functions and return values are meant for composing Workflow logic, while notifications
+are meant for communicating state back to your application. In the AI SDK, return values
+are used directly.
+
+**Key differences:**
+
+**Vercel AI SDK Generators:**
+- Manual yield statements at specific points in your tool execution
+- Generic streaming through the AI SDK's protocol
+- Tool-level progress updates handled by the framework
+- Updates tied to tool execution lifecycle
+
+**BAML's `@watch`:**
+- Automatic notification generation from variable assignments
+- Typesafe notification listeners generated from your workflow code
+- Fine-grained control over exactly what business logic gets tracked
+- Updates tied to your specific domain logic and variable names
+
+
+## Mastra `.watch()`
+
+Mastra provides a `.watch()` method for monitoring workflow execution in real-time. Let's compare a workflow monitoring example using Mastra's approach to one using BAML's `@watch` system.
+
+```typescript TypeScript (Mastra)
+// Mastra approach - watching workflow steps externally
+const workflow = mastra.createWorkflow(...)
+const run = await workflow.createRunAsync()
+
+run.watch((event) => {
+  console.log(`Step ${event?.payload?.currentStep?.id} completed`)
+  console.log(`Progress: ${event?.payload?.progress}`)
+})
+
+const result = await run.start({ inputData: { value: "initial data" } })
+```
+
+With BAML's `@watch` system, you mark variables directly in your workflow logic and get typesafe notification listeners generated for you.
+
+Both approaches enable real-time workflow monitoring. But Mastra's `watch()` function contains
+a more limited number of fields - telling you only about the Workflow stage you are in, not
+specific values being processed.
+
+## Comparison Table
+
+| Feature            | Printf | Mastra | BAML |
+| ---                | ---    | ---    | ---  |
+| Real-time          | 🟢     | 🟢     | 🟢   |
+| Streaming          | ⚪     | 🟢     | 🟢   |
+| Debug levels       | 🟢     | ⚪     | ⚪   |
+| Value subscription | ⚪     | ⚪     | 🟢   |
+| Typed listeners    | ⚪     | ⚪     | 🟢   |
diff --git a/fern/03-reference/baml_client/runtime-observability.mdx b/fern/03-reference/baml_client/runtime-observability.mdx
new file mode 100644
index 0000000000..3043942dd7
--- /dev/null
+++ b/fern/03-reference/baml_client/runtime-observability.mdx
@@ -0,0 +1,459 @@
+---
+title: Runtime Observability
+---
+
+
+This feature was added in 0.210.0
+
+
+The BAML runtime observability system allows you to receive real-time callbacks about workflow execution, including block progress and variable updates. This enables you to build responsive UIs, track progress, and access intermediate results during complex BAML workflows.
+
+## Notification Types
+
+### VarNotification
+
+Represents an update to a watched variable in your BAML workflow.
+
+
+
+```python
+from typing import TypeVar, Generic
+from baml_client.types import VarNotification
+
+T = TypeVar('T')
+
+class VarNotification(Generic[T]):
+    """
+    Notification fired when a watched variable is updated
+    
+    Attributes:
+        variable_name: Name of the variable that was updated
+        value: The new value of the variable
+        timestamp: ISO timestamp when the update occurred
+        function_name: Name of the BAML function containing the variable
+    """
+    variable_name: str
+    value: T
+    timestamp: str
+    function_name: str
+
+# Usage examples:
+# VarNotification[int] for integer variables
+# VarNotification[str] for string variables  
+# VarNotification[List[Post]] for complex types
+```
+
+
+
+```typescript
+import type { VarNotification } from './baml-client/types'
+
+interface VarNotification {
+  /**
+   * Notification fired when a watched variable is updated
+   */
+  
+  /** Name of the variable that was updated */
+  variableName: string
+  
+  /** The new value of the variable */
+  value: T
+  
+  /** ISO timestamp when the update occurred */
+  timestamp: string
+  
+  /** Name of the BAML function containing the variable */
+  functionName: string
+}
+
+// Usage examples:
+// VarNotification for integer variables
+// VarNotification for string variables
+// VarNotification for complex types
+```
+
+
+
+```go
+package types
+
+import "time"
+
+// Since Go doesn't have user-defined generics, we generate specific types
+// for each watched variable in your BAML functions
+
+// For a variable named "progress_percent" of type int
+type ProgressPercentVarNotification struct {
+    // Name of the variable that was updated
+    VariableName string `json:"variable_name"`
+    
+    // The new value of the variable
+    Value int `json:"value"`
+    
+    // Timestamp when the update occurred
+    Timestamp time.Time `json:"timestamp"`
+    
+    // Name of the BAML function containing the variable
+    FunctionName string `json:"function_name"`
+}
+
+// For a variable named "current_task" of type string  
+type CurrentTaskVarNotification struct {
+    VariableName string `json:"variable_name"`
+    Value string `json:"value"`
+    Timestamp time.Time `json:"timestamp"`
+    FunctionName string `json:"function_name"`
+}
+
+// For a variable named "completed_posts" of type []Post
+type CompletedPostsVarNotification struct {
+    VariableName string `json:"variable_name"`
+    Value []Post `json:"value"`
+    Timestamp time.Time `json:"timestamp"`
+    FunctionName string `json:"function_name"`
+}
+```
+
+
+
+### BlockNotification
+
+Represents progress through a markdown block in your BAML workflow.
+
+
+
+```python
+from baml_client.types import BlockNotification
+
+class BlockNotification:
+    """
+    Notification fired when entering or exiting a markdown block
+    
+    Attributes:
+        block_label: The markdown header text (e.g., "# Summarize Source")
+        block_level: The markdown header level (1-6)
+        event_type: Whether we're entering or exiting the block
+        timestamp: ISO timestamp when the event occurred
+        function_name: Name of the BAML function containing the block
+    """
+    block_label: str
+    block_level: int  # 1-6 for # through ######
+    event_type: str   # "enter" | "exit"
+    timestamp: str
+    function_name: str
+```
+
+
+
+```typescript
+import type { BlockNotification } from './baml-client/types'
+
+interface BlockNotification {
+  /**
+   * Notification fired when entering or exiting a markdown block
+   */
+  
+  /** The markdown header text (e.g., "# Summarize Source") */
+  blockLabel: string
+  
+  /** The markdown header level (1-6) */
+  blockLevel: number
+  
+  /** Whether we're entering or exiting the block */
+  eventType: "enter" | "exit"
+  
+  /** ISO timestamp when the event occurred */
+  timestamp: string
+  
+  /** Name of the BAML function containing the block */
+  functionName: string
+}
+```
+
+
+
+```go
+package types
+
+import "time"
+
+type BlockNotificationType string
+
+const (
+    BlockNotificationEnter BlockNotificationType = "enter"
+    BlockNotificationExit  BlockNotificationType = "exit"
+)
+
+type BlockNotification struct {
+    // The markdown header text (e.g., "# Summarize Source")
+    BlockLabel string `json:"block_label"`
+    
+    // The markdown header level (1-6)
+    BlockLevel int `json:"block_level"`
+    
+    // Whether we're entering or exiting the block
+    EventType BlockNotificationType `json:"event_type"`
+    
+    // Timestamp when the event occurred
+    Timestamp time.Time `json:"timestamp"`
+    
+    // Name of the BAML function containing the block
+    FunctionName string `json:"function_name"`
+}
+```
+
+
+
+## Usage Examples
+
+### Tracking Variable Updates
+
+
+
+```python
+from baml_client import b, watchers
+from baml_client.types import VarNotification
+
+def track_progress(notif: VarNotification[int]):
+    print(f"Progress updated: {notif.value}% at {notif.timestamp}")
+
+def track_current_task(notif: VarNotification[str]):
+    print(f"Now working on: {notif.value}")
+
+# Set up variable tracking
+watcher = watchers.MakePosts()
+watcher.vars.progress_percent(track_progress)
+watcher.vars.current_task(track_current_task)
+
+# Run the function
+posts = await b.MakePosts("https://example.com", {"watchers": watcher})
+```
+
+
+
+```typescript
+import { b, watchers } from './baml-client'
+import type { VarNotification } from './baml-client/types'
+
+const trackProgress = (notif: VarNotification) => {
+  console.log(`Progress updated: ${notif.value}% at ${notif.timestamp}`)
+}
+
+const trackCurrentTask = (notif: VarNotification) => {
+  console.log(`Now working on: ${notif.value}`)
+}
+
+// Set up variable tracking
+const watcher = watchers.MakePosts()
+watcher.on_var_progress_percent(trackProgress)
+watcher.on_var_current_task(trackCurrentTask)
+
+// Run the function
+const posts = await b.MakePosts("https://example.com", { watchers: watcher })
+```
+
+
+
+```go
+package main
+
+import (
+    "fmt"
+    b "example.com/myproject/baml_client"
+    "example.com/myproject/baml_client/watchers"
+    "example.com/myproject/baml_client/types"
+)
+
+func trackProgress(notif *types.ProgressPercentVarNotification) {
+    fmt.Printf("Progress updated: %d%% at %s\n", 
+        notif.Value, notif.Timestamp.Format("15:04:05"))
+}
+
+func trackCurrentTask(notif *types.CurrentTaskVarNotification) {
+    fmt.Printf("Now working on: %s\n", notif.Value)
+}
+
+func main() {
+    ctx := context.Background()
+    
+    // Set up variable tracking
+    watcher := watchers.NewMakePosts()
+    
+    go func() {
+        for notif := range watcher.ProgressPercentNotifications() {
+            trackProgress(notif)
+        }
+    }()
+    
+    go func() {
+        for notif := range watcher.CurrentTaskNotifications() {
+            trackCurrentTask(notif)
+        }
+    }()
+    
+    // Run the function
+    posts, err := b.MakePosts(ctx, "https://example.com", &b.MakePostsOptions{
+        Watchers: watcher,
+    })
+    if err != nil {
+        log.Fatal(err)
+    }
+}
+```
+
+
+
+### Tracking Block Progress
+
+
+
+```python
+from baml_client import b, watchers
+from baml_client.types import BlockNotification
+
+def track_blocks(notif: BlockNotification):
+    indent = "  " * (notif.block_level - 1)
+    action = "Starting" if notif.event_type == "enter" else "Completed"
+    print(f"{indent}{action}: {notif.block_label}")
+
+# Set up block tracking
+watcher = watchers.MakePosts()
+watcher.on_block(track_blocks)
+
+# Run the function
+posts = await b.MakePosts("https://example.com", {"watchers": watcher})
+```
+
+
+
+```typescript
+import { b, watchers } from './baml-client'
+import type { BlockNotification } from './baml-client/types'
+
+const trackBlocks = (notif: BlockNotification) => {
+  const indent = "  ".repeat(notif.blockLevel - 1)
+  const action = notif.eventType === "enter" ? "Starting" : "Completed"
+  console.log(`${indent}${action}: ${notif.blockLabel}`)
+}
+
+// Set up block tracking
+const watcher = watchers.MakePosts()
+watcher.on_block(trackBlocks)
+
+// Run the function
+const posts = await b.MakePosts("https://example.com", { watchers: watcher })
+```
+
+
+
+```go
+func trackBlocks(notif *types.BlockNotification) {
+    indent := strings.Repeat("  ", notif.BlockLevel - 1)
+    action := "Starting"
+    if notif.EventType == types.BlockNotificationExit {
+        action = "Completed"
+    }
+    fmt.Printf("%s%s: %s\n", indent, action, notif.BlockLabel)
+}
+
+func main() {
+    ctx := context.Background()
+    
+    // Set up block tracking
+    watcher := watchers.NewMakePosts()
+    
+    go func() {
+        for notif := range watcher.BlockNotifications() {
+            trackBlocks(notif)
+        }
+    }()
+    
+    // Run the function
+    posts, err := b.MakePosts(ctx, "https://example.com", &b.MakePostsOptions{
+        Watchers: watcher,
+    })
+    if err != nil {
+        log.Fatal(err)
+    }
+}
+```
+
+
+
+## Generated Watcher API
+
+
+
+When you run `baml generate`, BAML analyzes your functions and creates type-safe notification handlers with generic types:
+
+```python
+# For a function with `@watch let progress: int = 0`
+watcher.vars.progress(callback: (notif: VarNotification[int]) -> None)
+
+# For a function with `@watch let status: string = "starting"`  
+watcher.vars.status(callback: (notif: VarNotification[str]) -> None)
+
+# For all markdown blocks
+watcher.on_block(callback: (notif: BlockNotification) -> None)
+```
+
+The generic `VarNotification[T]` type provides compile-time type safety, ensuring your notification handlers receive the correct data types.
+
+
+
+When you run `baml generate`, BAML analyzes your functions and creates type-safe notification handlers with generic types:
+
+```typescript
+// For a function with `@watch let progress: int = 0`
+watcher.on_var_progress(callback: (notif: VarNotification) => void)
+
+// For a function with `@watch let status: string = "starting"`  
+watcher.on_var_status(callback: (notif: VarNotification) => void)
+
+// For all markdown blocks
+watcher.on_block(callback: (notif: BlockNotification) => void)
+```
+
+The generic `VarNotification` interface provides compile-time type safety, ensuring your notification handlers receive the correct data types.
+
+
+
+When you run `baml generate`, BAML analyzes your functions and creates specific types for each watched variable (since Go doesn't have user-defined generics):
+
+```go
+// Separate types generated for each watched variable
+type ProgressVarNotification struct {
+    VariableName string
+    Value int
+    Timestamp time.Time
+    FunctionName string
+}
+
+type StatusVarNotification struct {
+    VariableName string
+    Value string  
+    Timestamp time.Time
+    FunctionName string
+}
+
+// Corresponding notification channels
+watcher.ProgressNotifications() <-chan *types.ProgressVarNotification
+watcher.StatusNotifications() <-chan *types.StatusVarNotification
+watcher.BlockNotifications() <-chan *types.BlockNotification
+```
+
+Each watched variable gets its own dedicated notification type, providing the same type safety as generics while working within Go's constraints.
+
+
+
+## Best Practices
+
+1. **Performance**: Keep notification handlers lightweight. They run sequentially in
+   a separate thread from the rest of the BAML runtime
+1. **Error Handling**: Always include error handling in notification callbacks
+1. **Naming**: Use descriptive names for watched variables to generate clear notification handler names
+
+## Related Topics
+
+- [Runtime Observability Guide](/guide/baml-advanced/runtime-observability) - Learn how to use notifications in workflows
+- [Collector](/ref/baml_client/collector) - Comprehensive logging system
\ No newline at end of file
diff --git a/fern/docs.yml b/fern/docs.yml
index 24a7296ead..e74c33024c 100644
--- a/fern/docs.yml
+++ b/fern/docs.yml
@@ -412,6 +412,9 @@ navigation:
           - page: Modular API
             icon: fa-regular fa-cubes
             path: 01-guide/05-baml-advanced/modular-api.mdx
+          - page: Runtime Observability
+            icon: fa-regular fa-headset
+            path: 01-guide/05-baml-advanced/runtime-observability.mdx
       - section: Boundary Cloud
         contents:
           # - section: Functions
@@ -688,6 +691,9 @@ navigation:
             path: 01-guide/05-baml-advanced/client-registry.mdx
           - page: OnTick
             path: 03-reference/baml_client/ontick.mdx
+          - page: Runtime Observability
+            slug: watchers
+            path: 03-reference/baml_client/runtime-observability.mdx
           - page: Multimodal
             slug: media
             path: 03-reference/baml_client/media.mdx