Skip to content
Merged
Show file tree
Hide file tree
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
3 changes: 2 additions & 1 deletion adapters/linear/doc.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// Package linear provides a Linear app-actor adapter for Chat SDK Go.
//
// The adapter participates as a Linear app-owned actor on App-Actor Client
// Credentials (a Single-Install Adapter). It supports two Linear interaction
// Credentials (a Single-Install Adapter by default; see Multi-tenant installs
// below for serving many organizations). It supports two Linear interaction
// models, both reached through the small chat.Adapter interface and the opaque,
// versioned Thread ID:
//
Expand Down
29 changes: 17 additions & 12 deletions adapters/slack/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,12 @@
//
// The install credential rides as the adapter-specific slack.SlackInstall payload
// on chat.Install.Credential (a Platform Escape Hatch for credentials): the
// per-workspace bot token plus an optional bot user id used for tenant-correct
// self-filtering. Thread Handle reconstruction (out-of-webhook posting) resolves
// the same way, keyed by the Platform Tenant decoded from the opaque Thread ID.
// per-workspace bot token plus the bot user id used for tenant-correct
// self-filtering. The bot user id is optional in the type but should be treated
// as required: without it the bot's own posts are not filtered, and a
// subscribed thread can loop on its own replies. Thread Handle reconstruction
// (out-of-webhook posting) resolves the same way, keyed by the Platform Tenant
// decoded from the opaque Thread ID.
//
// # Message history (ADR 0009)
//
Expand All @@ -41,15 +44,17 @@
//
// # Rate-limit retry (ADR 0005)
//
// Outbound posts are hardened against Slack throttling by default. The adapter
// wraps its Slack Web API call site (chat.postMessage / chat.postEphemeral /
// conversations.open) with bounded retry on a Slack 429 (honoring the Retry-After
// header, in seconds) and the ratelimited API error. Retry is bounded three ways:
// an attempt cap (Options.RetryPolicy.MaxAttempts), a cumulative backoff ceiling
// (MaxElapsed), and the caller's context deadline. The single load-bearing
// invariant is that in-line synchronous retry never sleeps past the caller's
// context deadline, so retry under the default DispatchSync stays inside Slack's
// 3-second ack window and cannot trigger a platform redelivery storm.
// Outbound calls are hardened against Slack throttling by default. The adapter
// wraps every Slack Web API call (including auth.test, chat.postMessage,
// chat.postEphemeral, conversations.open, views.open, and history reads) and
// every response_url post with bounded retry on a Slack 429 (honoring the
// Retry-After header, in seconds) and the ratelimited API error. Retry is
// bounded three ways: an attempt cap (Options.RetryPolicy.MaxAttempts), a
// cumulative backoff ceiling (MaxElapsed), and the caller's context deadline.
// The single load-bearing invariant is that in-line synchronous retry never
// sleeps past the caller's context deadline, so retry under the default
// DispatchSync stays inside Slack's 3-second ack window and cannot trigger a
// platform redelivery storm.
//
// The RetryPolicy is per-adapter platform config in Options, never Runtime
// Options. Its zero value is a conservative default that keeps MaxElapsed under
Expand Down
6 changes: 5 additions & 1 deletion docs/how-to/choose-a-state-backend.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,12 @@ import (
chatredis "github.com/coder/chat/state/redis"
)

redisOptions, err := redis.ParseURL(os.Getenv("REDIS_URL")) // e.g. redis://127.0.0.1:6379/0
if err != nil {
return err
}
redisState, err := chatredis.New(ctx, chatredis.Options{
Client: redis.NewClient(&redis.Options{Addr: os.Getenv("REDIS_ADDR")}),
Client: redis.NewClient(redisOptions),
Prefix: "mybot", // see "One namespace per bot application" below
})
```
Expand Down
5 changes: 3 additions & 2 deletions docs/how-to/deferred-dispatch.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ bot, err := chat.New(ctx,

- `Dispatch: chat.DispatchDeferred` turns on ack-then-work. The default is
`chat.DispatchSync`, which runs the handler before acknowledging.
- `DetachTimeout` bounds how long a detached handler may run after the webhook
request has ended.
- `DetachTimeout` (required under `DispatchDeferred`; `DefaultRuntimeOptions()`
leaves it at zero, so `chat.New` fails until you set it) bounds how long a
detached handler may run after the webhook request has ended.
- `Concurrency: chat.ConcurrencyQueue` is the natural companion: while a
detached handler holds the thread lock, follow-up events on the same thread
wait instead of being dropped, and only the most recent superseded follow-up
Expand Down
8 changes: 4 additions & 4 deletions docs/how-to/linear-agent-sessions.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ if err := la.UpdateSession(ctx, ev.Thread.ID(), linear.AgentSessionUpdateInput{
{Title: "Reproduce the bug", Status: "pending"},
{Title: "Fix and test", Status: "pending"},
},
ReplacePlan: true,
}); err != nil {
return err
}
Expand Down Expand Up @@ -148,9 +147,10 @@ This check only runs when the stop event reaches your handler, and events on
one thread are serialized by the thread lock — a stop arriving while a
handler is still running cannot preempt it (`ConcurrencyDrop` discards it on
conflict; `ConcurrencyQueue` delivers it only after the in-flight handler
returns). The ADR 0012 force/steerability hook that would allow preemption is
staged behind the deferred-dispatch coordination design work, so **Linear's
Stop control cannot cancel in-flight work through this adapter today**. What
returns). The runtime has no preemption: the ADR 0012 force/steerability hook
that would allow it is rejected for v0.x by
[ADR 0015](../adr/0015-runtime-coordination.md), so **Linear's Stop control
cannot cancel in-flight work through this adapter**. What
you can do: structure long
sessions as short handler turns (each turn checks `StopRequested` on the
event that started it before doing more work — `confirmStop` above is exactly
Expand Down
2 changes: 1 addition & 1 deletion docs/linear-agent-capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ hatch rather than typed helpers.
| Typed activity helpers | Supported | `PostThought`, `PostAction`, `PostElicitation`, `PostError`; `Thread.Post` creates the `response` activity. |
| Agent-to-human signals | Supported | `auth` and `select` signals with metadata pass through `CreateAgentActivity` / `PostElicitation`. |
| Human-to-agent stop signal | Supported | `RawMessageFrom(ev.Message)` exposes `Signal` / `StopRequested()`; see the routing caveat below. |
| Session updates | Supported | `UpdateSession` sets `externalUrls` and replaces the session plan array. |
| Session updates | Supported | `UpdateSession` replaces `externalUrls` or adjusts them with `AddExternalURLs` / `RemoveExternalURLs`, and replaces the session plan array. |
| GraphQL escape hatch | Supported | `GraphQL` (single-install) and `GraphQLForTenant` (multi-tenant) reuse adapter auth and token refresh, surface GraphQL errors, and never expose tokens. |
| Proactive agent session creation | Supported | `CreateSessionOnIssue` / `CreateSessionOnComment` (plus `ForTenant` variants) wrap `agentSessionCreateOnIssue` / `agentSessionCreateOnComment`; the returned `CreatedAgentSession` carries the adapter's opaque `ThreadID` ([#47](https://github.com/coder/chat/issues/47)). |
| Repository suggestions | Supported | `SuggestRepositories` wraps `issueRepositorySuggestions` with typed candidates and confidence-scored results ([#48](https://github.com/coder/chat/issues/48)). |
Expand Down
11 changes: 6 additions & 5 deletions docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,10 +239,11 @@ inbound webhook request context before the platform acknowledgement. For
long-running work, opt in to `DispatchDeferred` (ack-then-work,
[ADR 0002](adr/0002-async-dispatch.md)): the dedupe and lock prelude runs
before the acknowledgement, then the handler runs on a detached work context
with automatic lock lease renewal, bounded by `DetachTimeout`. Under deferred
dispatch `MaxDetached` bounds admitted-but-incomplete deliveries; a delivery
arriving at the bound is rejected with `chat.ErrAdmissionRejected` before
acknowledgement and before dedupe marking ([ADR 0015](adr/0015-runtime-coordination.md)).
with automatic lock lease renewal, bounded by `DetachTimeout` (which must be
positive under deferred dispatch). Under deferred dispatch `MaxDetached`
bounds admitted-but-incomplete deliveries; a delivery arriving at the bound is
rejected with `chat.ErrAdmissionRejected` before acknowledgement and before
dedupe marking ([ADR 0015](adr/0015-runtime-coordination.md)).
The [deferred dispatch guide](how-to/deferred-dispatch.md) covers enabling
it and writing handlers for the detached context.

Expand Down Expand Up @@ -325,7 +326,7 @@ The runtime implements all five upstream-aligned concurrency strategies
- `ConcurrencyDebounce`: each new routed event supersedes the previous
waiter; only the final event in a `DebounceInterval` quiet period
dispatches, and superseded events are observable. Requires deferred
dispatch.
dispatch and a `DetachTimeout` longer than `DebounceInterval`.
- `ConcurrencyConcurrent`: no thread lock at all; every event dispatches in
its own execution, bounded by `MaxConcurrent`.
- `ConcurrencyBurst`: routed events for a scope collect for a `BurstWindow`,
Expand Down
4 changes: 2 additions & 2 deletions docs/tutorials/slack-bot.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ discover the bot's own identity. If the token is wrong you find out now, not
on the first message. When the bot is up you should see a log line like:

```text
level=INFO msg=listening addr=:8080
2026/09/23 14:00:00 INFO listening addr=:8080
```

## Step 5: Expose The Bot To Slack
Expand Down Expand Up @@ -165,7 +165,7 @@ this, only mentions ever reach it:
2. In **Event Subscriptions**, add the `message.channels` bot event.
3. Reinstall the app from **OAuth & Permissions**.

(If you set up `message.im` in Step 2, you can skip this and test the
(If you subscribed to `message.im` in Step 6, you can skip this and test the
follow-up flow in a direct message instead.)

Then open `examples/slack-hello-world/main.go` and replace the
Expand Down
22 changes: 14 additions & 8 deletions examples/linear-agent-hello-world/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,20 +151,25 @@ Expected behavior:
Thinking...
```

5. The app posts the final response:
5. The app posts a `search-codebase` action and adds a **Draft PR** external
link to the session.
6. The app posts the final response, a bold `hello from Linear app actor`
line followed by a note inviting a follow-up prompt.

```text
hello from Linear app actor
```
If the prompt text is exactly `deploy`, the app instead asks a `select`
question ("Which environment should I target?", `staging` or `prod`), and
your next follow-up is read as the answer.

6. Send a follow-up prompt in the same Linear agent session.
7. The example routes it to `OnSubscribedMessage`, posts another ephemeral
thought, and replies with:
7. Send a follow-up prompt in the same Linear agent session.
8. The example routes it to `OnSubscribedMessage`, posts the ephemeral thought
`Reading your follow-up...`, and replies with:

```text
Follow-up received: YOUR_MESSAGE
```

A follow-up carrying Linear's stop signal gets a stop confirmation instead.

## Dogfooding Evidence

Before claiming a live Linear dogfood passed, capture screenshots or video of:
Expand All @@ -179,7 +184,8 @@ Before claiming a live Linear dogfood passed, capture screenshots or video of:

- State is in memory, so subscriptions and dedupe data are lost when the process
exits.
- Use Redis or Postgres runtime state for production deployments.
- Use Redis, Postgres, or NATS JetStream runtime state for production
deployments.
- Linear request signatures are verified with `LINEAR_WEBHOOK_SECRET`.
- Client credentials are exchanged during adapter startup and refreshed lazily
before Linear API calls.
Expand Down
13 changes: 5 additions & 8 deletions examples/slack-hello-world/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ mentioned in a channel, or messaged directly if you enable the DM event, it
replies with portable Markdown: `**hello** _world_`.

This example requires `CHAT_DEMO_IN_MEMORY_STATE=1` because state is lost on
restart. Use `examples/slack-redis-state` or `examples/slack-postgres-state` for
durable Slack apps.
restart. Use `examples/slack-redis-state`, `examples/slack-postgres-state`, or
`examples/slack-nats-state` for durable Slack apps.

## Slack App Setup

Expand Down Expand Up @@ -45,12 +45,9 @@ In **Event Subscriptions**:
https://YOUR_PUBLIC_HOST/webhooks/slack
```

3. Subscribe to these **Bot User Events**:

```text
app_mention
message.im
```
3. Subscribe to the `app_mention` **Bot User Event**. Also add `message.im`
if you want direct messages to reach the bot (it needs the `im:history`
scope).

4. Save changes.

Expand Down
11 changes: 4 additions & 7 deletions examples/slack-nats-state/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,9 @@ In **Event Subscriptions**:
https://YOUR_PUBLIC_HOST/webhooks/slack
```

3. Subscribe to these **Bot User Events**:

```text
app_mention
message.im
```
3. Subscribe to the `app_mention` **Bot User Event**. Also add `message.im`
if you want direct messages to reach the bot (it needs the `im:history`
scope).

4. Save changes.

Expand All @@ -77,7 +74,7 @@ Treat both values like passwords.

## Run NATS

From this example directory:
From the repository root:

```sh
cd examples/slack-nats-state
Expand Down
11 changes: 4 additions & 7 deletions examples/slack-postgres-state/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,9 @@ In **Event Subscriptions**:
https://YOUR_PUBLIC_HOST/webhooks/slack
```

3. Subscribe to these **Bot User Events**:

```text
app_mention
message.im
```
3. Subscribe to the `app_mention` **Bot User Event**. Also add `message.im`
if you want direct messages to reach the bot (it needs the `im:history`
scope).

4. Save changes.

Expand All @@ -74,7 +71,7 @@ Treat both values like passwords.

## Run Postgres

From this example directory:
From the repository root:

```sh
cd examples/slack-postgres-state
Expand Down
11 changes: 4 additions & 7 deletions examples/slack-redis-state/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,9 @@ In **Event Subscriptions**:
https://YOUR_PUBLIC_HOST/webhooks/slack
```

3. Subscribe to these **Bot User Events**:

```text
app_mention
message.im
```
3. Subscribe to the `app_mention` **Bot User Event**. Also add `message.im`
if you want direct messages to reach the bot (it needs the `im:history`
scope).

4. Save changes.

Expand All @@ -74,7 +71,7 @@ Treat both values like passwords.

## Run Redis

From this example directory:
From the repository root:

```sh
cd examples/slack-redis-state
Expand Down
Loading