Browse the docs · Make your app work with agents
docs / guide

Make your app work with agents

An agent in Mod is told what is happening on every turn (awareness), and can do whatever its tools allow (hands). Your app adds to both with the same calls its view already makes. This guide walks through each with one example: a sales board, Leads, where a person edits a table of deals.

Mod decides:

  • Each thing the agent is told is sent only when it changed, and the person sees the same record in the bar above the composer.
  • An action marked to ask never takes effect without the person's yes. A missing connection raises a request instead of failing.
  • What is addressed to the person is theirs, and is never sent to an agent.

You decide: your view's line, your actions and which of them ask first or need a connection, and what your app says happened.

1. Say what is on screen

const app = mod.app({
  state: { rows: [1, 40], selected: null },
  describe: (s) =>
    `rows ${s.rows[0]} to ${s.rows[1]} of 212` +
    (s.selected ? ` · ${s.selected} selected` : ''),
})

app.set({ selected: 'B5' })   // the line follows the state

The line rides on the person's next message, so the agent reads Leads (focused) · rows 1 to 40 of 212 · B5 selected. A pane with a line is told in words and carries no screenshot. When a picture says more, give your own with snapshot: () => chart.toBlob().

2. Let the agent do what the person does

mod.app({
  state,
  describe,
  actions: {
    select: { description: 'Select a cell', run: (input, ctx) => ctx.set({ selected: input.cell }) },
    send: { description: 'Send the reply', ask: 'always', review: (i) => `Send reply to ${i.to}`, run: sendReply },
    postToSlack: { description: 'Post the Q4 summary', needs: ['slack'], run: post },
  },
})

td.onclick = () => app.run('select', { cell: 'B5' })

The agent runs the same table with run_action. ctx.by tells you which of you ran it, so you can draw the agent's selection as its own. Your actions show up in what the agent can do on your pane, marked when they ask first or need a connection.

3. Say what happened

mod.activity.add({ type: 'deal.stageChanged', subject: deal.id, line: `${deal.name} moved to Negotiation` })

The agent's next turn carries it, ranked by how close it is to the session's work, and the person sees it in the bar's Activity tab. Comments are Mod's own events; you do not add them.

4. Ask, and ask for what you need

const r = await mod.asks.create({ text: 'Archive 40 newsletters?', options: ['Archive', 'Keep'] })
await mod.grants.request({ connect: 'slack', why: 'to post the Q4 summary', unlocks: 'postToSlack' })
mod.capabilities.onChange((c) => (slackButton.hidden = !c.connected.includes('Slack')))

An ask resolves when the person answers. A grant request is raised once per need and waits under Needs you.

5. Address someone

await mod.notifications.add({ to: { person: personId }, line: '3 replies waiting', ref: inbox.id })
await mod.notifications.add({ to: { session: sessionId }, line: 'Acme replied' })

To a person, it sits under Needs you in their bar and turns the activity chip amber. To a session, it reaches that session's next turn.

6. Declare what is not code

A call's modes and tools go in your package.json's mod.agent block (formerly mod.callAgent, still read):

"mod": {
  "dataType": "callNote",
  "agent": {
    "modes": { "takeNotes": { "label": "Take notes", "on": "Take notes is on.", "off": "Take notes is off." } },
    "tools": [{ "name": "mark_call_item", "mode": "takeNotes", "record": "callNote", "description": "Mark a decision." }]
  }
}