Skip to main content

crewship chat

A chat session is the thread that backs every crewship 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.
Auth: every subcommand requires an authenticated session (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.
Human output is 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.
If the connection drops mid-run the command reconnects on a bounded backoff and resumes from the last sequence number it printed, so a blip neither loses nor duplicates output. Full frame reference: Watching agent runs.

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.openstream.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.
The command takes exactly the chat ID, message ID, and emoji; it has no command-specific flags. On success it prints Removed <emoji> from message <message-id>. Repeating the operation is safe because reactions are idempotent per chat, message, emoji, and user.

Examples

List an agent’s recent chats

Rows are ordered by 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

Two files with the same name are two attachments with two checksums and two paths — which is the point of the id segment. Use --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

Server-side reactions are idempotent per (chat, message, emoji, user)add and remove can be called blindly without first checking list.

Manage group-chat participants

Adding the first participant flips the chat to group visibility, after which the agent only replies when @mentioned. Mutations require the caller to be the chat owner or a workspace 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 sessionschat attach auto-lookup walked every visible agent and didn’t find the chat. Pass --agent to 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 directorychat attach couldn’t open the local file.

See also