Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions team/DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,46 @@ The tradeoff is that `HookProvider` exposure can leak implementation details for

- The capability requires responding to multiple distinct lifecycle events
- Users need to customize which events to subscribe to or add callbacks beyond base class defaults


## Provide Both Low-Level and High-Level APIs

**Date**: Jan 30, 2026

### Decision

When introducing new features, we SHOULD provide both low-level APIs that offer fine-grained control and high-level APIs that simplify common use cases. Low-level APIs serve as building blocks for power users and edge cases, while high-level APIs guide most users toward the happy path with opinionated defaults.

### Rationale

Our tenets emphasize being **extensible by design** and **simple at any scale**. These goals can appear to conflict: maximum extensibility often requires exposing implementation details, while simplicity favors hiding them. A tiered API approach resolves this tension.

**Low-level APIs** provide fine-grained control, building blocks for custom implementations, and escape hatches for edge cases. They require deeper understanding of the underlying concepts but enable use cases the high-level API doesn't anticipate.

**High-level APIs** provide opinionated defaults for common use cases and reduced cognitive load. They push users toward the pit of success — the easy path is also the correct path. Most users should reach for these first.

For example, `BidiAgent` exposes `send`/`receive` as low-level APIs. Using them directly requires managing concurrency, lifecycle, and event routing:

```python
await agent.start()

async def read_input():
while True:
event = await input_source.read()
await agent.send(event)

asyncio.create_task(read_input())

async for event in agent.receive():
...

await agent.stop()
```

The `run` method builds on these to abstract the complexity. Users provide IO callbacks and the method handles the rest:

```python
await agent.run(inputs=[audio_input], outputs=[audio_output, text_output])
```

This pattern aligns with **progressive disclosure** from UX design: show users what they need for common tasks while making advanced capabilities discoverable when needed.