Skip to content
KismetKismetDevelopers
llms.txt

The agent marks

View .md

A visitor who plans with an AI assistant should be able to hand it the home or the collection they are looking at, from the page, in one click. Every Kismet site prints the same three marks beside share and save for that: Muse, ChatGPT and Instinct, each by its own mark. This guide is the SDK’s copy of what kismet.travel and the Kismet fixtures on WordPress sites print, so a site built on the SDK looks and behaves the same.

The division of labor: Kismet decides the agents, the site decides the pixels. Which agents are listed, in what order, where each mark links and what a click copies is data the SDK compiles in and Kismet also serves, so an agent added in Kismet reaches your site with no release beyond its mark. The site owns the placement and the styling.

  • A click copies a line to paste into that agent: the home (on a property page) or the collection, the dates and party size when they are chosen, and the page the visitor is on. A small note says it was copied, and for ChatGPT offers the way in. If the clipboard refuses, the note shows the line to select by hand. A click with a modifier key is left to the browser.
  • Each mark is a real link. Muse and Instinct link the brief the page’s host names for them; ChatGPT links the Kismet plugin. An agent reading the markup in a browser takes the address. An agent that fetches the page names an icon link by its SVG title, which every mark carries, and the name is also text inside the link.
  • No mark links the guest MCP endpoint. It answers POST only, and a link would send a fetching agent to a 405. The endpoint reaches an agent through its brief.
import { AgentMarks } from '@kismet-tech/sdk/react';
// A property page: "Book with" then the three marks.
<AgentMarks
kind="property"
collectionSlug="cascadia-getaways"
collectionName="Cascadia Getaways"
homeName={home.name}
checkIn={stay.checkIn}
checkOut={stay.checkOut}
guests={stay.guests}
/>
// A results page or a group page: "Plan with" then the marks.
<AgentMarks kind="collection" collectionSlug="cascadia-getaways" collectionName="Cascadia Getaways" />

Place it where your share and save controls are. On a property page that is the listing meta row; on a results or group page, the toolbar. The marks are drawn in currentColor, so they take the text colour of wherever you put them. On a phone beside share and save, pass hideLabel so the marks stand alone and the words stay in the markup.

kind decides the words: 'property' prints “Book with”, 'collection' and 'group' print “Plan with”. The dates and guests you pass are only written into the line that is copied and the served list’s request; the server HTML never names them, so a server render and the first client render agree.

Put it in your site: four ways, ranked by what an agent finds

Section titled “Put it in your site: four ways, ranked by what an agent finds”

An AI agent that fetches your page reads the server HTML and nothing else. So the question for each way in is whether the marks are in the HTML your server sends, or only in the DOM after script runs.

Way in In the server HTML Kismet can add an agent You write
<AgentMarks> in a React or Next page rendered on the server yes yes, after the visitor’s first intent; the compiled-in three at first paint one component
The served fragment, included by your server at render time yes yes, at every render one include
agentMarksHtml() at build time yes at your next build one call
Client-side mount (no server) no yes one script

Whatever you choose, keep the four facts an agent measures: every mark is an <a href>; its name is in the markup as the SVG <title> and as text inside the link; no address carries a session id or a quote token; nothing links the MCP endpoint.

import { AgentMarks } from '@kismet-tech/sdk/react';
export default function Listing({ home }) {
return (
<div className="listing-actions">
<ShareButton /> <SaveButton />
<AgentMarks kind="property" collectionSlug="cascadia-getaways" collectionName="Cascadia Getaways" homeName={home.name} />
</div>
);
}

It is a client component (it copies on click and reads the served list), imported from @kismet-tech/sdk/react, never from the main entry: render it from a page or a client component and Next renders its links on the server as usual, with no effect and no fetch on that path. The served list is read in the browser after the visitor’s first pointer, focus or touch on the marks.

2. Any server: include the served fragment

Section titled “2. Any server: include the served fragment”

The served list carries the same marks as one HTML fragment in its html field. Fetch it on your server at render time, cache it for the 300 seconds the response advertises, and print it where your share and save controls are. No script, no stylesheet: the marks are currentColor, and the class names (kismet-agent-marks, kismet-agent-marks-label, kismet-agent-mark, kismet-agent-mark-name) are hooks for your CSS.

GET https://api.ksmt.app/v1/public/agent-surface/collection/agents
?slug=cascadia-getaways&kind=property&host=www.your-site.com
&home=Riverbend%20Cabin

PHP:

$url = 'https://api.ksmt.app/v1/public/agent-surface/collection/agents?' . http_build_query([
'slug' => 'cascadia-getaways', 'kind' => 'property',
'host' => $_SERVER['HTTP_HOST'], 'home' => $home->name,
]);
$answer = json_decode(file_get_contents($url), true); // cache this for 300 s
echo $answer['html'] ?? '';

Django (a template tag or the view):

answer = requests.get(url, timeout=3).json() # cache for 300 s
context["agent_marks"] = mark_safe(answer.get("html", ""))

Next.js route handler or server component, without the React component:

const answer = await fetch(url, { next: { revalidate: 300 } }).then((r) => r.json());
return <div dangerouslySetInnerHTML={{ __html: answer.html }} />;

Pass host so Muse and Instinct link the brief your host names; pass kind, home, checkIn, checkOut, guests and page (origin and path only) so the paste line on each mark’s data-paste is written for the page. If the read fails, print agentMarksHtml(...) from the SDK instead, or nothing; never a hand-written brief address.

The fragment carries the paste line on data-paste but no click handler. To copy on click, attach one to .kismet-agent-mark in your own script, or leave the marks as links: each already opens the agent’s brief or the plugin.

For a static site (Astro, Hugo, Eleventy, a Rails view with no request-time fetch), the compiled-in list as a string, from @kismet-tech/sdk/server (edge safe, no React):

import { agentMarksHtml } from '@kismet-tech/sdk/server';
const html = agentMarksHtml({
kind: 'collection',
collectionSlug: 'cascadia-getaways',
collectionName: 'Cascadia Getaways',
briefs, // optional: the briefs your host names, if your build already knows them
});

It is byte-compatible with the served html, so you can render it at build time and swap in the served fragment at request time with no markup change. renderAgentMarksHtml({ label, kind, agents }) renders a served list you already fetched. A new agent reaches a build-time site at its next build.

import { createRoot } from 'react-dom/client';
import { AgentMarks } from '@kismet-tech/sdk/react';
createRoot(document.getElementById('agent-marks')!).render(
<AgentMarks kind="collection" collectionSlug="cascadia-getaways" />,
);

This is the fallback. A person sees the marks; an agent that fetches the page does not. Use it only where the page has no server, and say so in your own notes.

Muse and Instinct connect through a brief: a short page an agent reads to wire itself to the Kismet guest MCP. Which brief a host names, the collection’s own brand brief or the shared Kismet one, is decided by Kismet, never by a site. With nothing handed in, the marks link the collection’s For AI agents page (https://kismet.travel/c/<slug>/for-agents), which names the briefs, until the served list arrives with the exact ones.

If your server already knows the briefs for your host (a site that prints them in its footer or llms.txt), hand them in so the server HTML carries them from the first byte:

<AgentMarks kind="property" collectionSlug="cascadia-getaways" briefs={{ muse: museBriefUrl, instinct: instinctBriefUrl }} />

Only https:// addresses are accepted. Never write a brief address by hand: ask GET /v1/public/agent-surface/connector?host=<your host>&agent=muse (and agent=instinct) and print what it returns.

On the visitor’s first intent toward the marks (pointer, focus or touch), never on the render path, the component reads

GET https://api.ksmt.app/v1/public/agent-surface/collection/agents?slug=<collection>&kind=<kind>&host=<your host>

and shows that list in its order, with the exact briefs the host names and the line Kismet writes for the stay. The read is anonymous and public; it carries no cookie. An agent in the served list that the installed SDK has no mark for is left out until a release carries it. A served list that breaks a rule (a mark that would link an MCP endpoint, an address that carries a session id, a token or a marked endpoint) is refused whole, and the compiled-in list stays.

Point apiBaseUrl at a different Kismet API base when you are told to, or pass served={false} to show only the compiled-in agents.

className styles the group, markClassName each mark’s link, and noteClassName the note. The note hangs from the marks’ right edge by default (noteAlign="end", for marks at the right of a row such as beside share and save); pass noteAlign="start" for marks at the left of a row, such as a toolbar, so the note stays on a phone’s screen. The note is drawn on currentColor with white text; set --kismet-agent-marks-note-text on the group to change the text colour.

import { useAgentMarks, AgentMark } from '@kismet-tech/sdk/react';
function MyMarks() {
const { label, agents, wantServed, hand, note } = useAgentMarks({
kind: 'collection',
collectionSlug: 'cascadia-getaways',
collectionName: 'Cascadia Getaways',
});
return (
<div onPointerEnter={wantServed} onFocus={wantServed}>
<span>{label}</span>
{agents.map((agent) => (
<a key={agent.id} href={agent.href} title={`${label} ${agent.name}`} onClick={(e) => { e.preventDefault(); void hand(agent.id); }}>
<AgentMark id={agent.id} title={agent.name} />
<span className="sr-only">{agent.name}</span>
</a>
))}
{note && <span role="status">{note.text}</span>}
</div>
);
}

Whatever you render, keep the four facts a reader measures: every mark is an <a href>; its name is in the markup as the SVG <title> and as text inside the link; no address carries a session id or a quote token; and nothing links the MCP endpoint.

  • agentMarksList(input): the compiled-in list for a page, or null for a slug that is not shaped like one.
  • agentMarksRequest(input): the line in the visitor’s voice.
  • acceptServedAgents(json): the served list reduced to what the SDK can render, or null when it breaks a rule.
  • servedAgentsUrl(apiBaseUrl, input, host): the address of the served list.
  • agentMarksHtml(input) and renderAgentMarksHtml({ label, kind, agents }): the marks as an HTML string.

All of these are pure and exported from @kismet-tech/sdk/react and, without the React component, from @kismet-tech/sdk/server.