The agent marks
View .mdA 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.
What a visitor gets
Section titled “What a visitor gets”- 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.
Take it
Section titled “Take it”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.
1. React or Next, rendered on the server
Section titled “1. React or Next, rendered on the server”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%20CabinPHP:
$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 secho $answer['html'] ?? '';Django (a template tag or the view):
answer = requests.get(url, timeout=3).json() # cache for 300 scontext["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.
3. Build time: agentMarksHtml
Section titled “3. Build time: agentMarksHtml”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.
4. No server at all: mount in the browser
Section titled “4. No server at all: mount in the browser”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.
The briefs
Section titled “The briefs”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.
The served list
Section titled “The served list”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.
Restyle it
Section titled “Restyle it”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.
Build your own on useAgentMarks
Section titled “Build your own on useAgentMarks”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.
The data functions
Section titled “The data functions”agentMarksList(input): the compiled-in list for a page, ornullfor 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, ornullwhen it breaks a rule.servedAgentsUrl(apiBaseUrl, input, host): the address of the served list.agentMarksHtml(input)andrenderAgentMarksHtml({ 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.