crewship chat
A chat session is the thread that backs everycrewship run or crewship ask. The top-level chat <chat-id> command prints the whole transcript; the subcommands let you list recent chats per agent, upload attachments, and add emoji reactions. Defined in cmd/crewship/cmd_chat.go.
crewship login). All of them also require a selected workspace except chat stream, which resolves tenancy from the chat row itself and so needs only a token and a chat id. The transcript hits GET /api/v1/chats/{id}/messages?limit=500 — the cap is server-side and not paged today.
Subcommands
Flags
chat <chat-id>
The transcript renders assistant /
model messages through the markdown ANSI renderer; user and system messages stay plain. Each block carries a coloured role · timestamp header (yellow for user, green for assistant, dim for system).
chat attach <chat-id> <path>
The upload is one multipart POST to
POST /api/v1/agents/{agentId}/chats/{chatId}/attachments. The server caps the body at 25 MB; the CLI assembles the multipart body in memory rather than streaming because the bound is small.
The crew has to be running. The file lands in the agent’s /output tree, which a provisioned crew owns as uid 1001 — the server cannot create attachments/<chat-id>/ there itself, so it writes through the crew container as that uid. On a stopped crew the upload returns 409 with failed to save attachment: the agent's output directory is owned by the crew runtime; … start the crew and retry. Start the crew and re-run the attach; nothing partial is left behind, and no attachment metadata row is recorded for an upload that did not land.
Each upload gets its own directory. The file lands at attachments/<chat-id>/<attachment-id>/<filename> — the readable filename is still the last segment, and the id in front of it is what makes the location unique. Attaching two different files called evidence.pdf gives you two attachments and two blobs, each with its own checksum; neither replaces the other. Attaching the same file twice is a retry and resolves to the attachment that already exists.
Both facts are printed by chat attachments list, which is also where you get the id that chat attachments delete takes.
chat attachments list <chat-id>
Calls
GET /api/v1/agents/{agentId}/chats/{chatId}/attachments. Columns are ID / FILENAME / SIZE / SHA256 (first 12 hex characters — the full digest is in --format json) / AGENT PATH / CREATED. An upload that did not finish is never listed.
chat attachments delete <chat-id> <attachment-id>
Calls
DELETE /api/v1/agents/{agentId}/chats/{chatId}/attachments/{attachmentId}. Removes the stored file and then the metadata row.
Idempotent: deleting an attachment that is already gone succeeds, because what you asked for is already true. This is the only way to reclaim a chat attachment’s bytes short of deleting the whole chat — chat blobs are not content-addressed (the path is what the agent is told to open), so they sit outside the background reclaim sweep that collects issue attachments.
Like the upload, the removal goes through the crew container, so a stopped crew refuses it with failed to delete attachment: …. Nothing is lost — the row is kept and re-running the command after starting the crew completes the deletion.
chat read <chat-id>
Calls
PUT /api/v1/agents/{agentId}/chats/{chatId}/read. Your read cursor advances to now: UNREAD in chat list drops to 0 for the session, and the “agent replied” inbox item for it flips to read. Read state is per user — marking a shared group chat read doesn’t touch anyone else’s badge.
chat rename <chat-id> <title>
Calls
PATCH /api/v1/agents/{agentId}/chats/{chatId} with {"title": "..."}.
Quote the title — it is one argument, not the rest of the line.
The server normalises what you send before storing it: the whole title is folded
onto one line (newlines and tab runs become a single space), control characters
and invisible formatting characters are stripped, and the result is trimmed and
capped at 200 characters (characters, not bytes — an emoji or a Czech title
gets the same length as an English one). A title that is empty after that, or
longer than the cap, is refused with 400 rather than silently truncated.
Who may rename: the chat’s creator always, and anyone who can edit the agent for
anyone else’s chats — the same gate as chat delete.
Chat <chat-id> renamed to "<title>". — the title in the message
is the stored one, so normalisation is visible. --format json / yaml /
ndjson emit {"id":"<chat-id>","title":"<stored title>"}.
chat steer <chat-id>
The message POSTs to
POST /api/v1/chats/{chatId}/steer. If a run is in flight, the message is held and applied on the next turn rather than interrupting the running one (live mid-turn injection is a planned follow-up).
chat stream <chat-id>
Reads
GET /api/v1/chats/{chatId}/stream. The run’s text lands on stdout and
nothing else does, so crewship chat stream c_abc123 > reply.md captures the
reply and only the reply; thinking, tool calls and stream notices go to stderr.
A run that ends in an error exits non-zero.
Only a workspace-scoped token is needed — not a selected workspace. The route
resolves tenancy from the chat itself, so CREWSHIP_TOKEN plus a chat id is enough.
Any run that has a chat can be watched this way — a chat message, crewship run,
crewship ask, a routine’s agent_run step, a webhook trigger or a scheduled
agent. A run executed with no chat row has no session channel to attach to, and a
delegated sub-agent’s output is reported as part of the delegating agent’s turn
rather than as a stream of its own.
--format ndjson (or json) switches to passthrough: every server frame is
printed verbatim, one JSON object per line. That is the mode to script against.
chat participants add <chat-id> <user-id>
crewship chat create <agent-slug-or-id>
Create a chat session on an agent without sending it a message. Calls
POST /api/v1/agents/{agentId}/chats with origin: CLI; the session starts
empty and ACTIVE.
Every other way to get a session — ask, run, routine iterate — creates it
on the way to a model call, so none of them can produce one on an instance with
no provider credential. chat create needs none, which makes it the way to set
up scripted fixtures and to exercise chat stream’s idle contract
(stream.open → stream.end / no_active_run) on a workspace where nothing
has ever run.
The chat id goes to stdout in every format, so it can be captured directly:
crewship chat delete <chat-id>
Delete a chat session, its messages, and read cursors. The operation is
irreversible and requires confirmation unless --yes is supplied. The agent
is resolved automatically; use --agent to select it explicitly.
Human output is
Chat <chat-id> deleted. Machine output is
{"chat_id":"<chat-id>","status":"deleted"} with --format json (or the
equivalent YAML object with --format yaml).
crewship chat react remove <chat-id> <message-id> <emoji>
Remove your emoji reaction from a message. Aliases are rm and delete.
Removed <emoji> from message <message-id>. Repeating the operation is safe
because reactions are idempotent per chat, message, emoji, and user.
Examples
Print a transcript
List an agent’s recent chats
LAST ACTIVITY (newest message, not creation time). UNREAD counts messages you haven’t seen — your own don’t count; - means fully read. The ORIGIN column distinguishes chats spawned by crewship run / crewship ask (CLI) from the web UI — handy when grep-ing for terminal sessions to attach a file to.
Catch up on a reply you missed
Attach a file
List and remove a chat’s files
--format json when you want the full digest to compare against what an agent reported reading.
Pass --agent when the auto-lookup is ambiguous (the chat lives on more than one agent in your visible set) or when you’ve already pre-resolved the slug.
React to a message
(chat, message, emoji, user) — add and remove can be called blindly without first checking list.
Manage group-chat participants
OWNER/ADMIN — otherwise the server returns 403. Adding a user who isn’t in the workspace returns 400 user is not a member of this workspace, and removing the chat owner returns 400 cannot remove the chat owner.
Common errors
chat <id> not found in any agent's recent sessions—chat attachauto-lookup walked every visible agent and didn’t find the chat. Pass--agentto short-circuit the search.bad --since: ...— the value isn’t a Go duration (1h,24h) or an RFC 3339 timestamp.open <path>: no such file or directory—chat attachcouldn’t open the local file.
See also
crewship run— start an agent run (creates the chat).crewship ask— one-shot prompt (also creates a chat, taggedorigin=CLI).crewship session— browser session management, unrelated noun.- Conversations API — the underlying endpoints.