---
title: Apps that work with their agent
description: Keep an open app current when an agent changes its data, and pass the user's actions and context back to the chat.
sidebar:
  label: Apps that work with their agent
  order: 6
---

## What the app and agent can do together

**Show agent changes without a reload.** If an agent changes the app from another chat, device, or scheduled action, an open view can update as soon as the change arrives.

**Give the next agent turn useful context.** The app can tell a connected agent which item the user selected, what the user can see, or which action they just took. This adds context without posting a chat message.

**Start a follow-up from the app.** A button can send a user message into the connected chat, such as asking the agent to plan work from the project on screen.

**Keep the standalone app useful.** A direct visit has no connected agent thread. The app can detect that case, keep its normal controls working, and hide actions that need a chat.

## Technical

Use the injected `window.charming` runtime for live updates and host communication. The [Browser runtime API](/docs/technical-reference/browser-runtime) defines every method and failure shape.

### Copy this prompt for your agent

```text
Make my Charming app work with its connected agent. Read
https://charm.ing/docs/guides/agent-connected-apps.md first. Keep the
open UI current when the agent changes data, and pass useful user
actions or view state back to the agent. Ask me before adding any
button that posts a new message to the chat.
If you haven't built a Charming app before, read
https://charm.ing/docs/build-mcp.md for MCP or
https://charm.ing/docs/build-http.md for HTTP before you start.
```

### Refresh when the agent changes data

Subscribe once after the app renders. An operation event names the operation and includes its unwrapped result when the JSON response body is no larger than 32 KiB (32 kibibytes). If `result` is `null`, refetch unless `null` is a valid result for that operation. A reconnect event always requires a full refetch.

```js
const api = window.charming.api('project-board');

async function refresh() {
  render(await api.getBoard());
}

void refresh();

const unsubscribe = window.charming.onStateChange(async (event) => {
  if (event.source === 'reconnect-resync') {
    await refresh();
    return;
  }

  if (event.op === 'moveTask' && event.result) {
    updateTaskCard(event.result);
  } else {
    await refresh();
  }
});
```

Change only the affected DOM nodes when the event result has enough data. A full rerender can discard text the user is typing, focus, and selection. Keep the unsubscribe function if the UI can remove or replace the subscribed view.

### Send context without starting a turn

Use `updateContext()` for the latest state and `recordAction()` for a short history of user actions. Both are dropped when no agent host is connected.

```js
function selectProject(project) {
  showProject(project);

  window.charming.updateContext({
    selectedProject: { id: project.id, name: project.name },
  });

  window.charming.recordAction('selected-project', {
    projectId: project.id,
  });
}
```

Send the least context the agent needs. Do not send credentials, private browser state, or data that the next agent turn does not need.

### Send an explicit follow-up

Use `sendFollowUp()` only after a clear user action. It posts a user message and can start a model turn, unlike the silent context methods.

```js
const planButton = document.querySelector('[data-plan-project]');

function syncAgentControls() {
  planButton.hidden = !window.charming.isConnected;
}

syncAgentControls();
window.charming.onConnectionChange(syncAgentControls);

planButton.addEventListener('click', () => {
  window.charming.sendFollowUp('Plan the next three tasks for the selected project.');
});
```

`onConnectionChange()` passes the new boolean value to its callback, but a callback can also read `isConnected` as shown above. It does not run once at subscription time, so set the initial state yourself.

### Open links through the host

Use `openLink()` for an external HTTP or HTTPS URL. It asks an embedded chat host to open the link and falls back to a safe new tab during direct browsing.

```js
docsButton.addEventListener('click', () => {
  window.charming.openLink(project.docsUrl);
});
```

### Contract details

See [Live state](/docs/technical-reference/browser-runtime#live-state) for event sources, result limits, and reconnect behavior. See [Agent host connection](/docs/technical-reference/browser-runtime#agent-host-connection) for connection state, message rate limits, context merge behavior, and action history.

## Related

- [Browser runtime API](/docs/technical-reference/browser-runtime)
- [Data storage](../capabilities/data-storage)
- [Privacy and sharing](../capabilities/privacy-and-sharing)
- [Fix and improve your app](fix-and-improve-your-app)

Found a bug or need a feature? [Tell us](/docs/capabilities/feedback) with `submit_feedback` or `POST /app/{id}/feedback`. Your feedback shapes what we build next.
