Problem
Agents currently reach output surfaces through ad-hoc calls:
- Google Sheets: direct
gws CLI invocation
- Slack: direct
bin/squads-slack invocation
- Gmail/WhatsApp: no pattern exists yet
This makes it hard to add new surfaces, test delivery, or retry on failure. There's no single place to observe what was delivered and when.
Proposal
A thin output-surface library (src/lib/output-surfaces.ts) that agents call with a uniform interface:
```ts
// Agent calls one function, library routes to the right surface
await deliver({
surface: 'sheets', // or 'slack' | 'gmail' | 'whatsapp'
content: { ... },
destination: { spreadsheetId: '...', range: 'A1' },
});
```
Surfaces to support
| Surface |
Status |
Implementation |
| Google Sheets |
proven (gws CLI) |
wrap existing gws drive +write pattern |
| Slack |
proven (bin/squads-slack) |
wrap existing script |
| Gmail |
untested |
gws gmail send or equivalent |
| WhatsApp |
not started |
research Twilio vs WhatsApp Business API |
Why this matters
Paying customers are non-terminal — they don't run the CLI. They receive outputs where they already work. The output-surface library is the delivery layer that makes "customer gets value without opening a terminal" possible.
Acceptance criteria
Problem
Agents currently reach output surfaces through ad-hoc calls:
gwsCLI invocationbin/squads-slackinvocationThis makes it hard to add new surfaces, test delivery, or retry on failure. There's no single place to observe what was delivered and when.
Proposal
A thin output-surface library (
src/lib/output-surfaces.ts) that agents call with a uniform interface:```ts
// Agent calls one function, library routes to the right surface
await deliver({
surface: 'sheets', // or 'slack' | 'gmail' | 'whatsapp'
content: { ... },
destination: { spreadsheetId: '...', range: 'A1' },
});
```
Surfaces to support
gws drive +writepatterngws gmail sendor equivalentWhy this matters
Paying customers are non-terminal — they don't run the CLI. They receive outputs where they already work. The output-surface library is the delivery layer that makes "customer gets value without opening a terminal" possible.
Acceptance criteria
deliver()function accepts surface + content + destination