Skip to content

Chat with a project

You drive a project by chatting with it. Aura reads files, edits inputs, runs the solver, and replies. One message plus everything it triggers is a turn.

const reply = await aura.chat(project.id, "Solve this and summarize the result.");
console.log(reply.content);

chat() connects to the project WebSocket, dispatches the message, and returns the completed reply. In an environment that blocks WebSockets it falls back to polling /chat/sync — same result, later. It gives up after five minutes; the timeout and the fallback’s poll interval are both adjustable.

Two details matter on a project several callers share:

  • You get your own reply. The match is on the dispatched message’s sequence number, not on a message count, so another caller’s turn is never handed back to you.
  • The wait is pinned to one conversation — the one your dispatch landed in. Activating or creating another conversation mid-turn does not redirect it.
const reply = await aura.chat(project.id, "Re-solve with a 30 minute budget.", {
conversationId, // omit to use the project's active conversation
pollIntervalMs: 2000,
timeoutMs: 15 * 60_000, // long solves need a longer wait
});

onDelta receives assistant text as it is generated, and onToolActivity fires as the agent starts and finishes tools. Both are no-ops on the poll fallback, so treat them as progress reporting rather than a delivery guarantee — chat() still returns the completed reply.

await aura.chat(project.id, "Solve this and summarize the result.", {
onDelta: (text) => process.stdout.write(text),
onToolActivity: (activity) => console.error(`[${activity.status}] ${activity.tool}`),
});

A project can hold several conversations. Without one named, chat uses the project’s active conversation.

const conversation = await aura.createConversation(project.id, "budget sweep");
await aura.chat(project.id, "Try a 20% larger fleet.", { conversationId: conversation.id });
const all = await aura.listConversations(project.id);
import { AuraTurnCancelledError } from "@strangeworks/aura-sdk";
const controller = new AbortController();
// e.g. a Ctrl-C handler — stop both sides
process.on("SIGINT", () => {
void aura.cancel(project.id, conversationId);
controller.abort();
});
const reply = await aura
.chat(project.id, "Solve this.", { conversationId, signal: controller.signal })
.catch((err) => {
if (err instanceof AuraTurnCancelledError) return err.partialContent;
throw err;
});

cancel(projectId, conversationId) returns whether anything was stopped. Omitting the conversation id cancels any running turn in the project, including one someone else started.

To avoid blocking, dispatch the message and read state on your own schedule.

const dispatched = await aura.sendMessage(project.id, { content: "Solve this." });
for (;;) {
const status = await aura.getAgentStatus(project.id);
if (!status.running) break;
await new Promise((r) => setTimeout(r, 2000));
}
const state = await aura.getConversationState(project.id, dispatched.message.conversation_id);
const reply = state.messages.at(-1);

aura chat --project <id> without --message opens a full-screen terminal UI: a pinned header, a scrolling transcript, and a composer at the bottom. It uses the alternate screen buffer, so your shell’s scrollback is untouched, and prints the command to resume the session on exit.

Key Does
enter Send
shift+enter (or alt+enter) Newline — compose a multi-line message
Recall history; move between lines within a multi-line message
pgup pgdn, wheel Scroll the transcript
esc Cancel the running turn, stay in the session
/help, /exit In-session commands — a leading / acts locally rather than sending a turn
ctrl-c twice, or exit Quit

The CLI reference has the full key list, including word motion, line editing and the quick-reply picker.

  • Read results — executions, output files, the solution.
  • Errors — the three ways a turn can end badly, and which is which.