> ## 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.

# Existing services

# Embed in Existing Service

Add agent capabilities to an Express app you already have — without restructuring your codebase.

<Info title="Building from scratch?">
  Use [`agent.start()`](your-first-agent) instead. It handles everything internally.
</Info>

## Why Embed?

You have an existing service — maybe an API, a webhook handler, or a background worker. You want to add AI capabilities without:

* Rewriting your application structure
* Running a separate agent process
* Managing two deployments

`attach()` lets you keep your existing Express app and add agent capabilities as a layer on top.

## Quick Start

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

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

// Your existing routes stay as-is
app.get('/api/tickets', async (_req, res) => { /* ... */ });

// Create an agent
const agent = new Agent({
  name: 'ticket-agent',
  description: 'Manages support tickets.',
  public_url: 'https://tickets.example.com',
});

// Add capabilities
agent.define_capability({
  name: 'get_ticket',
  description: 'Look up a ticket by ID.',
  parameters: {
    type: 'object',
    properties: {
      ticket_id: { type: 'number' },
    },
    required: ['ticket_id'],
  },
  handler: async (args) => {
    return await db.find(args.ticket_id);
  },
});

// Attach to your app
const handle = await attach(app, {
  agent,
  mesh: {
    client_id: process.env.SAVANT_CLIENT_ID!,
    client_secret: process.env.SAVANT_CLIENT_SECRET!,
  },
});

app.listen(4000);
```

## What `attach()` Does

One call wires up everything:

1. **A2A endpoints** — Mounts `/.well-known/agent-card.json` (agent discovery) and `/send` (capability invocation) on your Express app
2. **Mesh registration** — Authenticates with the mesh, registers your agent's capabilities, and starts heartbeating to stay discoverable
3. **Triggers** — Activates triggers already registered on the agent via `agent.add_triggers()`
4. **Graceful shutdown** — Listens for `SIGTERM` and `SIGINT` to disconnect cleanly before your process exits

## Attach Configuration

### AttachConfig

<ParamField body="agent" type="object" required>
  An Agent instance with capabilities already defined. Create with `new Agent(\{ name, description, public_url? \})`.
</ParamField>

<ParamField body="mesh" type="object">
  Mesh connection credentials. If omitted, the agent runs locally without mesh registration — useful for development and testing.

  <Expandable title="properties">
    <ParamField body="client_id" type="string">
      Client ID from the Svantic dashboard (Settings → API Keys).
    </ParamField>

    <ParamField body="client_secret" type="string">
      Client secret from the Svantic dashboard.
    </ParamField>
  </Expandable>
</ParamField>

## Triggers

Triggers let external events fire prompts into the mesh. When a trigger fires, the prompt template is filled with payload data and sent to the mesh for processing.

### Webhook Triggers

Receive HTTP POST requests from external systems and convert them into mesh prompts. Use this to integrate with Zendesk, Stripe, GitHub, Slack, or any system that sends webhooks.

#### WebhookTrigger

<ParamField body="type" type="string" required>
  Trigger type identifier. Must be `"webhook"`.
</ParamField>

<ParamField body="event" type="string" required>
  Event name for logging and identification. Example: `"ticket_created"`.
</ParamField>

<ParamField body="path" type="string" required>
  URL path where the webhook will be mounted. External systems POST to this endpoint. Example: `"/webhooks/zendesk"`.
</ParamField>

<ParamField body="prompt" type="string" required>
  Prompt template sent to the mesh. Use double-brace placeholders for dynamic values. Example: `"New ticket \"subject\" (ID: ticket_id). Triage it."`.
</ParamField>

<ParamField body="payload_map" type="object">
  Maps payload fields to prompt variables using JSONPath dot-notation.
</ParamField>

```typescript theme={null}
agent.add_triggers([
  {
    type: 'webhook',
    event: 'ticket_created',
    path: '/webhooks/zendesk',
    prompt: 'New ticket "{{subject}}" (ID: {{ticket_id}}). Triage it.',
    payload_map: {
      ticket_id: '$.ticket.id',
      subject: '$.ticket.subject',
    },
  },
]);
```

When Zendesk POSTs to `/webhooks/zendesk`, the `payload_map` extracts `ticket.id` and `ticket.subject` from the JSON body, fills them into the prompt, and sends it to the mesh.

### Schedule Triggers

Run prompts on a cron schedule. Use this for daily summaries, periodic checks, or scheduled maintenance tasks.

#### ScheduleTrigger

<ParamField body="type" type="string" required>
  Trigger type identifier. Must be `"schedule"`.
</ParamField>

<ParamField body="event" type="string" required>
  Event name for logging and identification. Example: `"daily_summary"`.
</ParamField>

<ParamField body="cron" type="string" required>
  Cron expression (5 fields: minute hour day-of-month month day-of-week). Example: `"0 9 * * MON-FRI"`.
</ParamField>

<ParamField body="prompt" type="string" required>
  Prompt sent to the mesh when the schedule fires. Example: `"Summarize all open tickets and send to Slack."`.
</ParamField>

```typescript theme={null}
agent.add_triggers([
  {
    type: 'schedule',
    event: 'daily_summary',
    cron: '0 9 * * MON-FRI',  // 9:00 AM, Monday through Friday
    prompt: 'Summarize all open tickets and send the report to Slack.',
  },
]);
```

Common cron patterns:

* `0 9 * * *` — Every day at 9:00 AM
* `0 9 * * MON-FRI` — Weekdays at 9:00 AM
* `*/15 * * * *` — Every 15 minutes
* `0 0 1 * *` — First day of every month at midnight

### Emit Triggers

Listen for in-process events fired by your own application code. Use this when your app detects something that needs AI handling — like an SLA warning, a fraud signal, or a customer escalation.

#### EmitTrigger

<ParamField body="type" type="string" required>
  Trigger type identifier. Must be `"emit"`.
</ParamField>

<ParamField body="event" type="string" required>
  Event name. Your code fires this event using handle.emit(). Example: `"sla_warning"`.
</ParamField>

<ParamField body="prompt" type="string" required>
  Prompt template sent to the mesh. Use double-brace placeholders. Example: `"Ticket ticket_id is approaching SLA breach. Take action."`.
</ParamField>

```typescript theme={null}
// Register the trigger on the agent
agent.add_triggers([
  {
    type: 'emit',
    event: 'sla_warning',
    prompt: 'Ticket {{ticket_id}} is approaching SLA breach. Take action.',
  },
]);

// Fire it from anywhere in your app
handle.emit('sla_warning', { ticket_id: 4521 });
```

## The AgentHandle

`attach()` returns an `AgentHandle` for runtime interaction with the mesh.

### AgentHandle

<ParamField body="emit" type="function">
  Fire-and-forget: emit an event that triggers a prompt to the mesh. Does not wait for a response. Signature: `(event: string, payload: object) => void`.
</ParamField>

<ParamField body="ask" type="function">
  Request-response: emit an event and wait for the mesh to process it. Returns the result. Times out after 30 seconds. Signature: `(event: string, payload: object) => Promise<unknown>`.
</ParamField>

<ParamField body="detach" type="function">
  Clean shutdown: stops cron timers, removes event listeners, disconnects from mesh, and closes the agent. Call this before your process exits. Signature: `() => Promise<void>`.
</ParamField>

<ParamField body="mesh" type="object">
  The underlying MeshConnector instance, or null if mesh config was omitted. Use to check connection status or access advanced mesh features.
</ParamField>

<ParamField body="agent" type="object">
  The underlying Agent instance.
</ParamField>

```typescript theme={null}
const handle = await attach(app, config);

// Fire-and-forget: emit an event, don't wait for response
handle.emit('sla_warning', { ticket_id: 4521 });

// Request-response: emit and wait for the mesh result
const result = await handle.ask('analyze_ticket', { ticket_id: 4521 });
console.log(result);

// Check mesh connection
if (handle.mesh?.connected) {
  console.log('Connected to mesh');
}

// Clean shutdown
await handle.detach();
```

## Full Example

An Express service with existing routes, agent capabilities, and multiple trigger types:

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

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

// ============================================
// Existing routes — unchanged
// ============================================
app.get('/api/health', (_req, res) => res.json({ ok: true }));

app.get('/api/tickets/:id', async (req, res) => {
  res.json(await db.find(req.params.id));
});

app.post('/api/tickets', async (req, res) => {
  const ticket = await db.create(req.body);
  res.json(ticket);
});

// ============================================
// Agent setup
// ============================================
const agent = new Agent({
  name: 'ticket-service',
  description: 'Ticket management with automatic triage and escalation.',
  public_url: process.env.PUBLIC_URL ?? `http://localhost:${process.env.PORT ?? 4000}`,
});

agent.define_capability({
  name: 'get_ticket',
  description: 'Look up a ticket by ID. Returns subject, status, priority, and history.',
  parameters: {
    type: 'object',
    properties: { 
      ticket_id: { type: 'number', description: 'The ticket ID' } 
    },
    required: ['ticket_id'],
  },
  handler: async (args) => {
    const ticket = await db.find(args.ticket_id);
    if (!ticket) throw new Error(`Ticket ${args.ticket_id} not found`);
    return ticket;
  },
});

agent.define_capability({
  name: 'update_priority',
  description: 'Update the priority of a ticket.',
  parameters: {
    type: 'object',
    properties: {
      ticket_id: { type: 'number', description: 'The ticket ID' },
      priority: { 
        type: 'string', 
        enum: ['low', 'normal', 'high', 'urgent'],
        description: 'New priority level'
      },
    },
    required: ['ticket_id', 'priority'],
  },
  handler: async (args) => {
    return await db.update(args.ticket_id, { priority: args.priority });
  },
});

agent.define_capability({
  name: 'list_open_tickets',
  description: 'List all open tickets, optionally filtered by priority.',
  parameters: {
    type: 'object',
    properties: {
      priority: { 
        type: 'string', 
        enum: ['low', 'normal', 'high', 'urgent'],
        description: 'Filter by priority (optional)'
      },
      limit: { type: 'number', default: 20 },
    },
  },
  handler: async (args) => {
    return await db.list({ status: 'open', ...args });
  },
});

// ============================================
// Register triggers on the agent
// ============================================
agent.add_triggers([
  // Webhook: Zendesk notifies us when a ticket is created
  {
    type: 'webhook',
    event: 'ticket_created',
    path: '/webhooks/zendesk',
    prompt: 'New ticket "{{subject}}" (ID: {{ticket_id}}, priority: {{priority}}). Analyze the content and update priority if needed.',
    payload_map: {
      ticket_id: '$.ticket.id',
      subject: '$.ticket.subject',
      priority: '$.ticket.priority',
    },
  },
  // Schedule: Daily summary every weekday morning
  {
    type: 'schedule',
    event: 'morning_summary',
    cron: '0 9 * * MON-FRI',
    prompt: 'Generate a summary of all open tickets grouped by priority. Send to the #support Slack channel.',
  },
  // Emit: Our code detects SLA risk
  {
    type: 'emit',
    event: 'sla_warning',
    prompt: 'Ticket {{ticket_id}} is {{minutes_remaining}} minutes from SLA breach. Escalate or resolve.',
  },
]);

// ============================================
// Custom trigger handler (optional)
// ============================================
agent.on_trigger = async (ctx) => {
  // ctx has: prompt, trigger, payload, mesh
  // return { skip: true } to block delivery
  // return { prompt: 'override' } to change the prompt
  // return { metadata: {...} } to attach metadata
};

// ============================================
// Attach agent to Express
// ============================================
const handle = await attach(app, {
  agent,
  mesh: {
    client_id: process.env.SAVANT_CLIENT_ID!,
    client_secret: process.env.SAVANT_CLIENT_SECRET!,
  },
});

// ============================================
// Use emit trigger from your own code
// ============================================
app.post('/api/sla-check', async (req, res) => {
  const tickets = await db.list({ status: 'open' });
  
  for (const ticket of tickets) {
    const minutes_remaining = calculate_sla_remaining(ticket);
    if (minutes_remaining < 30) {
      handle.emit('sla_warning', { 
        ticket_id: ticket.id, 
        minutes_remaining 
      });
    }
  }
  
  res.json({ checked: tickets.length });
});

// ============================================
// Start server
// ============================================
const port = Number(process.env.PORT ?? 4000);
app.listen(port, () => {
  console.log(`Ticket service running on port ${port}`);
});
```

## When to Use This

| Scenario                                          | Recommendation                                   |
| ------------------------------------------------- | ------------------------------------------------ |
| Adding AI capabilities to an existing Express app | Use `attach()`                                   |
| Need webhook or cron triggers                     | Use `attach()`                                   |
| Want fire-and-forget or request-response patterns | Use `attach()`                                   |
| Building a new standalone agent                   | Use [`agent.start()`](your-first-agent)          |
| Need full control over connection lifecycle       | Use manual `agent.expose(app)` + `MeshConnector` |
| Local development without mesh                    | Omit `mesh` config — agent runs standalone       |

`attach()` is a convenience wrapper. Under the hood it calls `agent.expose(app)` and creates a `MeshConnector` — the same primitives you'd use manually.

## What's Next

* [Connecting Agents](connecting-agents) — Discover and call other agents
* [Telemetry](telemetry) — Traces, spans, and observability
* [Write Your Agent](your-first-agent) — Build a standalone agent from scratch
