Chat demo walkthrough
This is a script, not a reference. It takes a seeded install from “the chat page exists” to “I have seen the tree, both kinds of chip, a questionnaire, an attachment, ⌘K and the CLI half — and I have seen four of them refuse”. Each step leaves behind what the next one needs, so run it in order the first time. A demo that only shows the happy path teaches nothing about the product, so the refusals are a step rather than a footnote. The reference pages behind each step are Chat & Sessions, Ask forms, Conversation Search and Chat Surface Limits. This page assumes none of them.The running order
Before you start
State these to your audience. Three of them are the reason a demo falls over. A seeded install, and a login.crewship seed prints the
account it creates. It makes seven demo agents across four crews — comfortably
under the twelve-agent fan-out cap, so nothing in this walkthrough disappears
from the tree. On a workspace with more than twelve agents, read
the thirteenth agent
first: agents past the cap show a thread count of 0 whether or not they have
history, and this walkthrough would be demonstrating that instead.
The CLI, pointed at the same server. Nothing defaults to it:
/output/<agent-slug>/attachments/…, so the agent must have a crew_id — an
agent with none answers 404 Agent not found on upload, not a friendlier error.
It does not need a running crew. The server writes the bytes host-side
first, and only replays the write through the crew container when the host write
comes back with a permission error — which is what happens once a crew has been
provisioned and chowned its trees to uid 1001. So a never-provisioned crew
uploads fine, and a provisioned-then-stopped crew answers 409 with the remedy
in the message. Know which of the two you are standing on before you promise
either.
A form configured on the agent. The questionnaire step has nothing to show
until one exists. Step 0 checks.
Step 0 — Check what the seed gave you
Read the agent
crewship agent get casey shows neither, your install predates that work — the rest of the
walkthrough still runs, but configure them yourself first with the two commands
below. Everything after step 0 is written against the seeded casey /
bug-report pair.If they are absent, set them yourself
@a-file.json, because a form with five fields and a
template is not something anyone types on a command line.Note who did not get any
riley deliberately has neither. Keep the tab open — the contrast is step 2.Step 1 — /chat is a surface, not a drawer
Open /chat. This is the index: one left column, recent threads across every
agent on the right, the agent roster beneath them. It holds no WebSocket —
picking a thread is what opens a live connection, on the page below.
The tree
/chat and on /chat/<agent>, built
out of the same sidebar kit /routines and /issues use. Search on top, status
facets under it, agents under that, and each agent’s threads as its own
children — not as a folder you have to open first.The agent filter
useState; everybody feels a page transition.The chevron is separate — it opens and closes the branch and does nothing else,
and so do ← / → on the row.Two things reach past the filter on purpose: the search box (a search box that
cannot see six of seven agents is a lie) and the status facets, which count what
the narrowed list can show and name whose they are in the section header, so the
number never changes meaning in silence.A thread that names itself
casey and click Start a conversation — the row a
filtered agent with no threads shows. It opens /chat/casey and posts nothing;
arriving is not sending.Type a real sentence and send it. Something you will search for in step 5:chats.title had been in the schema since the first migration
with nothing writing it, so every thread in every list used to read “Untitled
session” — which makes a list of conversations useless the moment there is more
than one.Three properties worth saying out loud:- It fires once. Rename a thread yourself and nothing overwrites it — not the next message, not a reload.
- It never blocks the send. The message goes first; the title follows as a separate request. If that request fails, nothing is reported and the session stays untitled, which is where it already was.
- A message that yields nothing usable leaves the thread untitled.
???is not a name, and a thread called…is worse than one with no name.
?session= parameter, and step 6 needs
it.Step 2 — The two kinds of chip
Chips render under an empty conversation, so open a new session oncasey
(New session in the header). After the first turn they come back as
follow-ups under an assistant reply, three at a time.
Show the fallback first
/chat/riley in another tab. Four chips: Help me get started · What can
you do? · Show me your skills · Run a quick task.That is the built-in role pack, and it is what every unconfigured agent has
always shown. Ten seconds on it is worth it, because it is what the next tab
replaces.Then the authored ones
casey: three questions somebody wrote for this agent’s actual job, and
one chip that is not a question at all.The rule the rail exists to enforce
A chip that opens something must not look like a chip that sends something.A question chip sends its text the instant it is clicked. A form chip opens a sheet and sends nothing. Somebody who taps Report a bug expecting a form and finds a message already on its way to the agent has been lied to — and it is not recoverable, because the message is gone.So the form chip is marked four ways, and none of them is a colour:
+N,
which opens the full catalogue upward, out of the rail — the rail sits
directly above the composer, and a panel opening downward would cover the input
it is offering to fill.Step 3 — The questionnaire, end to end
Open it
Fill it
- What went wrong —
The credential picker shows no brands on a phone - Steps to reproduce — three numbered lines
- Severity — leave it; the field carries a default and the sheet seeds it
- First seen — leave it empty on purpose
Read the preview
First seen: line. An unanswered optional
field takes its whole line with it, label included. That is the only rule
here you cannot read off the template.Submitting sends an ordinary user message, so the person sending it is entitled
to read it first — and an author writing a template meets a broken one here,
while writing it, instead of in somebody’s transcript.Answer the photo field, and send
photo field with its own upload control. Drop
any small image on it.Note what the composer does while that control is on screen: it hides its own
chip list. There is one attachment list per session, and two views of it read as
two attachments — so the sheet wins, because that is where the question is. The
chip under the field is the field’s answer, and removing it there is editing
that answer.Press Send.Read the provenance line
Step 4 — An attachment, and what the agent is told about it
Uploading puts bytes in the container. It does not tell the agent they are there. That was the bug, and the fix is the thing to show.Attach from the composer
- It is an ordinary user message. No new envelope, no adapter-specific framing, nothing the agent has to be trained for — which is why it works across every CLI adapter.
- First person, past tense. Anything imperative — “Read the following
file”,
<attachments>…— reads as an injected system directive, and an adapter hardened against exactly that shape is entitled to ignore it. - The relative path, not the absolute one. The agent’s working directory
is
/output/<agent-slug>/, soattachments/<chat>/<id>/<file>opens as-is, and the crew slug stays out of the user’s own transcript. - One path per line, unquoted. Spaces, quotes and brackets in filenames are common; the line break is the only delimiter none of them can forge.
Send a photo with no caption
The camera
capture="environment" next to accept="image/*" is what makes a phone open the
rear camera instead of the document picker. Without it, the only route to a
photo is camera app → gallery → file browser: three screens for the most obvious
thing anyone does from a phone. The mobile composer used to be a bare input with
no attachments at all, so the one device that actually has a camera was the one
surface that could not send a picture.The upload is not a second code path. Camera, paperclip, drop and paste all run
the same uploader, with the same size guard, the same failure toast and the same
Retry.Step 5 — ⌘K finds a phrase, not a name
Go back to/chat — the index, which mounts no chat panel — and press ⌘K
(Ctrl+K elsewhere). Type a phrase from step 1:
/chat/<agent>
mounts the chat’s own command palette, that palette bound ⌘K as well, and
neither listener stopped the other — so one press opened both dialogs stacked.
The chat palette is ⌘/ now (or the Commands button in the chat header),
and ⌘K means the same thing on every route./chat/<agent-slug>?session=<chat-id> — the same deep link a chat notification
uses, which is why the session stays a query parameter and never becomes a path
segment.
What makes this group different from every other group in the palette:
- It asks the server on every keystroke — debounced, and each keystroke cancels the request before it. Every other group is fetched once when the palette opens and filtered in the browser. Messages are the largest thing in a workspace and a match is a phrase rather than a name.
- Two characters is the minimum before anything is sent.
- A failure renders nothing at all. Nobody explicitly ran this search, so it never raises an error: if the search mirror is not configured on the deployment, the group simply does not appear.
- The search is from-now-on. Only turns recorded after the mirror was configured are indexed, and there is no backfill. Searching for a phrase you said four minutes ago in step 1 is the demo that always works.
Step 6 — The same surface, without a browser
Three commands. The point of each one is different.Render a form with no browser
--var first_seen= was given, so that line is gone — the drop-the-whole-line
rule from step 3, seen from outside. And note one honest difference from the
sheet: the sheet seeds a field’s default, the CLI does not. --var severity=Major is here because the CLI answers only what you tell it.Output is plain text on stdout with no framing, so it pipes into a file, a diff
or a review comment.Rename a thread from the terminal
chat_renamed, so an open session list in another tab follows without polling.And the row does not move. A rename is not activity, so the server does not
bump last_activity_at and the client splices only the title into the row it
already holds — fixing a name never shoves a thread to the top of a list ordered
by recency.The title in the success message is the stored one, so the server’s
normalisation is visible: folded onto one line, control and invisible formatting
characters stripped, trimmed.List what is attached
<attachment-id> segment
demonstrates itself: two attachments, two checksums, two paths, neither replacing
the other. The filename used to be the last segment and the second upload
overwrote the first.The ID column is what crewship chat attachments delete takes, and that delete
is the only way to reclaim a chat attachment’s bytes short of deleting the whole
chat — chat blobs are not content-addressed, because the path is what the agent
is told to open, so they sit outside the background reclaim sweep.--agent skips the lookup. Without it the CLI walks every visible agent asking
for its chats until it finds the id.Step 7 — Four refusals, on purpose
Each is one action, and each shows a different standard.A form that will not submit
A refused upload
big.bin exceeds 25 MB — and no chip appears, because nothing was attached.
The same bound is enforced again server-side, which is what the CLI meets:409 with the remedy in the message. That
one leaves an error chip on screen with a Retry, keeps the bytes in the
browser so Retry has something to send, and refuses the Send while it is there:
a composer that visibly holds a file and does nothing when you press Send was
the original defect. It needs a stopped crew to produce and is not scripted
here.A title the server will not store
A list the server will not take
casey keeps the three questions from step 0. That is the standard the
suggested-prompt and ask-form caps all meet: refuse loudly, name what was wrong,
write nothing.What this walkthrough leaves out, and why
Say these rather than letting somebody find them. There is no interactive approval card in chat. Approvals are decided on/approvals. AskUserQuestion renders deliberately inert in the transcript, and
a test pins that it must not look clickable. Two event names are reserved for an
in-chat decision card so that whoever builds it emits the agreed one — the card
itself does not exist.
The chip and form funnel is measured, and the measurement goes nowhere. The
event vocabulary ships with a bounded in-memory buffer and no network transport
at all — no fetch, no beacon, no socket — because PRIVACY.md promises that no
usage analytics leave the install. Nothing in the console or the CLI reads that
buffer, so there is nothing to put on a screen. See
Chat interaction events for the vocabulary and the
reasoning; two of its session events are declared and not yet emitted, because
their call sites live in files another workstream owns.
The one silent cap has no screen affordance. Past twelve agents, the
thirteenth agent’s threads are never fetched and its row reads 0 —
indistinguishable from an agent nobody has talked to. Do not demo on such a
workspace without saying so first. Chat Surface Limits
is the honest list of every bound and whether anything tells you.
Two render bounds truncate silently. One substituted answer over 2000
characters, and a finished form message over 32000, are both cut with no
warning. A valid form cannot reach the message bound, so in practice this is a
very long textarea answer — ask-preview shows the truncated result exactly,
which is the only place an author can see it before a user does.
Suggested prompts and ask forms are not on the create path.
POST /api/v1/agents ignores both keys and returns 201 — the columns are left
null. Everything that sets them, including the seed and crewship apply, does it
with a follow-up PATCH.
See also
- Chat & Sessions — the surface, the URL state, the right panel
- Ask forms — field types, constraints, and where each rule is enforced
- Chat Surface Limits — every bound, and whether anything tells you
- Conversation Search — scopes, isolation and the 400-agent bound
- Chat interaction events — what is measured and where it stops
crewship seed— what a fresh workspace starts withcrewship chat— rename, attach, attachments list, steercrewship agent—--suggested-prompts,--ask-forms,ask-previewcrewship conversation— the CLI half of ⌘K