> ## Documentation Index
> Fetch the complete documentation index at: https://docs.svantic.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Attach

# attach

```typescript theme={null}
import { attach } from '@svantic/sdk';
```

## What it is

`attach()` is the one-call glue between an Express app and an agent. A single call does the work of four otherwise-separate steps:

1. Mount the agent's A2A endpoints on your Express app (`expose`).
2. Activate triggers registered on the agent via `agent.add_triggers()` — webhooks become Express routes, schedules become cron timers, emit triggers become event-bus listeners.
3. Connect to Svantic (`MeshConnector.connect`), with automatic retries.
4. Install `SIGTERM` / `SIGINT` handlers that deregister cleanly.

It returns an [`AgentHandle`](#agenthandle) — a small surface for firing emit triggers and tearing the whole setup down.

## When to use it

Use `attach()` when you're embedding an agent into a service you already run:

* You have an existing Express app with middleware, routes, and lifecycle.
* You want triggers (webhooks, cron, emit) without writing the plumbing.
* You want a single object (`AgentHandle`) to own teardown.

For standalone agents that don't need to share an Express process, prefer `new Agent({...}).start()` — it's simpler.

## Functional usage

```typescript theme={null}
import express from 'express';
import { Agent, attach, type RuntimeTrigger } from '@svantic/sdk';

const app = express();
app.use(express.json());

// 1. Build the agent and its capabilities.
const agent = new Agent({
  name: 'orders',
  description: 'Order automation.',
  public_url: 'https://api.acme.com',
});
agent.define_capability({ /* fetch_order */ });
agent.define_capability({ /* cancel_order */ });

// 2. Register triggers on the agent. Forge generates these for you,
//    but you can hand-write them too.
agent.add_triggers([
  {
    type: 'webhook',
    path: '/hooks/stripe',
    prompt: 'Payment {{id}} of {{amount}} just settled; update the order.',
    payload_map: {
      id: 'data.object.id',
      amount: 'data.object.amount_total',
    },
  },
  {
    type: 'schedule',
    cron: '0 9 * * MON-FRI',
    prompt: 'Email ops the overnight order summary.',
  },
  {
    type: 'emit',
    event: 'order_cancelled',
    prompt: 'Order {{order_id}} was cancelled; issue the refund.',
  },
]);

// 3. Wire everything — A2A endpoints, triggers, Svantic connection.
const handle = await attach(app, {
  agent,
  mesh: {
    client_id: process.env.SVANTIC_CLIENT_ID!,
    client_secret: process.env.SVANTIC_CLIENT_SECRET!,
  },
});

app.listen(4000);

// 4. Fire emit triggers from anywhere in the service.
app.post('/orders/:id/cancel', async (req, res) => {
  await db.orders.cancel(req.params.id);
  handle.emit('order_cancelled', { order_id: req.params.id });
  res.sendStatus(204);
});

// 5. Teardown (attach also handles SIGTERM / SIGINT automatically).
await handle.detach();
```

See the [Attach to Express](../guides/attach-to-express) and [Triggers](../guides/triggers) guides for the end-to-end story.

## Signature

```typescript theme={null}
function attach(app: Express, config: AttachConfig): Promise<AgentHandle>
```

## `AttachConfig`

| Field   | Type                                                 | Purpose                                                                                         |
| ------- | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `agent` | `Agent`                                              | The agent to mount. Capabilities and triggers must be registered before calling `attach`.       |
| `mesh?` | `MeshConnectorConfig` (retries/retry-delay optional) | Svantic connection config. Omit to skip mesh registration entirely — useful for local-only dev. |

## `RuntimeTrigger`

One trigger definition. Every trigger carries a `prompt` template (with `&#123;&#123;…&#125;&#125;` placeholders) that is fired when the trigger matches.

| Field          | Type                                | Purpose                                                                                                                            |
| -------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `type`         | `'webhook' \| 'schedule' \| 'emit'` | Dispatcher kind.                                                                                                                   |
| `event?`       | `string`                            | For `emit` triggers — the event name.                                                                                              |
| `path?`        | `string`                            | For `webhook` triggers — the HTTP path mounted on the Express app (POST).                                                          |
| `cron?`        | `string`                            | For `schedule` triggers — a standard 5-field cron expression. See [cron matching](#cron_matches_nowexpr-now) for supported syntax. |
| `queue?`       | `string`                            | Reserved for future queue-backed triggers.                                                                                         |
| `verify?`      | `string`                            | Optional verification strategy (e.g. webhook HMAC). Name only — verification is plumbed by the Forge-generated code.               |
| `secret_env?`  | `string`                            | Name of the env var holding the verification secret.                                                                               |
| `payload_map?` | `Record<string, string>`            | Dot-notation extraction map applied before interpolation. E.g. `{ customer: 'event.data.customer.id' }`.                           |
| `prompt`       | `string`                            | Prompt template with `&#123;&#123;key&#125;&#125;` placeholders.                                                                   |

### Trigger kinds

* **webhook** — `attach()` mounts `app.post(trigger.path)`. The request body is the payload.
* **schedule** — fires every minute; runs when the cron expression matches the current minute.
* **emit** — listens on an internal event bus. Trigger with `handle.emit(event, payload)` or `handle.ask(event, payload)`.

## `AgentHandle`

Returned by `attach()`.

| Member                  | Type                    | Purpose                                                                                                                         |
| ----------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `emit(event, payload?)` | `void`                  | Fire an emit trigger without waiting for a result.                                                                              |
| `ask(event, payload?)`  | `Promise<unknown>`      | Fire an emit trigger and await the handler's return value. Rejects after 30 s.                                                  |
| `detach()`              | `Promise<void>`         | Stop crons, remove event listeners, disconnect from Svantic, close the agent. Removes the internal `SIGTERM`/`SIGINT` handlers. |
| `mesh`                  | `MeshConnector \| null` | The underlying connector, or `null` when `config.mesh` was omitted.                                                             |
| `agent`                 | `Agent`                 | The underlying agent.                                                                                                           |

`attach()` installs `SIGTERM` and `SIGINT` handlers that call `detach()` for you; you don't need to wire signal handling yourself.

## Exported helpers

### `interpolate_prompt(template, payload, payload_map?)`

Replace `&#123;&#123;key&#125;&#125;` placeholders in `template` with values from `payload`. When `payload_map` is provided, values are first extracted via dot-notation (e.g. `event.data.customer.id`) and flattened.

Unknown placeholders are left as-is (`&#123;&#123;key&#125;&#125;`) so they're easy to spot in logs.

### `cron_matches_now(expr, now?)`

Returns `true` when the cron expression matches the given instant (default: now). Supports:

* `*` wildcard,
* single values (`5`),
* comma lists (`0,15,30,45`),
* ranges (`9-17`),
* steps (`*/15`, `9-17/2`),
* weekday names (`MON`–`SUN`) in the day-of-week field.

Only 5-field cron (minute hour day-of-month month day-of-week) is supported.

## Example

```typescript theme={null}
import express from 'express';
import { Agent, attach } from '@svantic/sdk';

const app = express();
app.use(express.json());

const agent = new Agent({ name: 'orders', description: 'Order automation.' });
agent.define_capability({ /* … */ });

agent.add_triggers([
  {
    type: 'webhook',
    path: '/hooks/stripe',
    prompt: 'A payment for {{amount}} just settled; update the order.',
    payload_map: { amount: 'data.object.amount_total' },
  },
  {
    type: 'schedule',
    cron: '0 9 * * MON-FRI',
    prompt: 'Email the ops team the overnight order summary.',
  },
]);

const handle = await attach(app, {
  agent,
  mesh: {
    client_id: process.env.SVANTIC_CLIENT_ID!,
    client_secret: process.env.SVANTIC_CLIENT_SECRET!,
  },
});

app.listen(4000);

// later, from anywhere in the service:
handle.emit('order_cancelled', { order_id: 'ord_42' });
```

## See also

* [Triggers guide](../guides/triggers)
* [Attach to Express guide](../guides/attach-to-express)
* [`MeshConnectorConfig`](./types#meshconnectorconfig)
