Cogitator
Getting Started

Cogitator Studio

A local debugger for agent projects: chat, approvals, traces and costs, workflow runs and time-travel forks.

Cogitator Studio: a local debugger for Cogitator agent projects. Chat with every agent of a project, approve the tool calls that need it, read the trace and the cost of each run, run workflows and rerun them from a node, and fork a run from any step to see what a different input or tool result changes.

Start it

In a project created by create-cogitator-app or cogitator init:

pnpm dev:studio          # the script every generated project has
npx cogitator dev        # the same, from @cogitator-ai/cli

It opens http://localhost:4321 (the next free port when it is taken). The studio finds the agents, workflows and swarms in src/cogitator.ts, the registry generated projects have: export the runtime as cogitator and the rest as agents, workflows and swarms.

What it does

  • Overview: the project at a glance, with its runs, success rate, spend in tokens and dollars, p50 and p95 latency, activity over the last two weeks, and every agent and workflow with the model it runs on and how often it ran.
  • Chat with any agent: streamed answers rendered as Markdown with highlighted code and LaTeX math (\(...\), \[...\] and $$...$$, a single $ stays a dollar), reasoning, tool calls as one line each that opens to its arguments and result, errors, stop, and threads grouped by day. A project without memory gets one from the studio, so threads keep their conversation, also after a restart.
  • Approvals: a tool call with requiresApproval waits in the chat with its arguments until you approve it, or reject it with a reason the model sees.
  • Traces: every run as a waterfall of model calls, tool calls and the runs agent tools start, nested under the call that started them, with the time, the tokens and the cost of each model call (priced from the model registry, unpriced for unknown models) and the total, counted live while the run goes. Click a span for its details: cached and reasoning tokens, the finish reason, the instructions, prompt and response of a model call, the arguments and the result of a tool call. The studio asks the runtime for this content for its own runs only (captureContent on its observer), so other observers of the project, such as an OTLP exporter, still get spans without it.
  • Workflows: run one with an input state, watch the status and time of each node on its graph, read what each node returned (text an agent wrote is rendered as Markdown, the raw JSON is a click away), and rerun from a node, keeping what the nodes before it produced.
  • Swarms and agent teams: run a swarm of the registry with an input, its agent runs nested under it with their traces and costs. A team shows as a live graph while it runs: the lead and its members with their status, their turns and the task each works on, an arrow for every pair that exchanged tasks or messages, the shared task list in pending, in progress and completed columns (click a task for its result), and the messages between the agents, marked as coming from agents, not from the user. The studio builds a fresh swarm from the configuration in swarms for each run, with @cogitator-ai/swarms from the project.
  • Todos: a run of an agent with todos: true shows its todo list in a Todos panel, updated live each time the agent calls write_todos, and from the start of a run that goes on from a list kept in its thread, see Todo Lists.
  • Memory tree: the Tree button next to a chat (or T) opens a panel with every branch of the thread as its memory keeps it. Forks and replays branch off where they went on from, a branch a studio fork started is labelled fork, events (a model change, a pause for approval, an interruption) show in line, the active branch is highlighted and its last entry is marked next message, the one the next message goes after. Each entry links to the run that wrote it. The panel shows the newest 500 entries, and a memory without branches shows its entries as a list.
  • Rewind and fork a thread: every entry of the memory tree gets two buttons on hover, to rewind the thread to it or fork it into a new thread from there, with the files of the agent's workspace when it keeps file checkpoints, see Rewind and fork.
  • Time-travel forks: pick a step of a finished run (before each tool call), change the input, add context or replace a tool result, and run on from there. The original and the fork are shown side by side, with the tool calls that differ highlighted. A fork of a run that wrote to memory goes on a branch of its thread from where the run started, so it shows in the memory tree and never in the conversation it split from.
  • History: runs and threads are kept in .cogitator/studio/ as JSON files, searchable by input, output and error and filtered by agent, workflow or kind. Runs a stopped studio left behind are marked failed.
  • Keyboard: ⌘K (Ctrl+K) opens a command palette over agents, workflows, the whole run history and actions, / searches the page, G O and G R go to the overview and the runs, J and K walk the run list and the trace, N starts a thread, T opens the memory tree, and ? lists every shortcut.
  • Themes: light and dark, following the system until you pick one, which the browser remembers.

Rewind and fork

Each entry of the memory tree has two buttons on hover or focus, Rewind here (entries of the active branch) and Fork here (any entry). Both need a memory that keeps branches, the studio's own included.

  • Rewind here opens a card that first runs a dry run and shows what changes: how many entries leave the active branch, the memory items that go stale, a run that goes on in the thread, and the paused or durable runs that are dropped. Apply rewinds for real, the active leaf moves back and the next message goes on from there. The turns that left the branch stay in the chat, faded and marked rewound. A thread of the studio's own memory is kept whole, every branch entry by entry (ids, metadata and file checkpoints included) with its active leaf and its own metadata, after every chat, rewind and fork, so a restart replays exactly the branch it was on, and a fork replays the threads it was forked from first, so its file rewinds still find where its checkpoints come from. A thread a run goes on in refuses the rewind, the card then offers Abort the run and rewind.
  • Fork here makes a new thread that holds the branch up to the entry, titled Fork of ..., and opens it. With Restore files checked, the card first shows a dry run of the files the fork restores, in the agent's workspace, which the thread it came from works in too, and offers to stop a run in the way. The new thread links back to the thread it was forked from and shows the messages it starts from above its own turns. The thread it came from stays as it is.
  • Restore files shows in both cards when the runtime has files to restore at the entry, picked as a rewind picks them: a file checkpoint of a user message at or before the entry, or of the one right after it (the end of a turn): turn checkpoints: true on the runtime, the agent or the run, and give the agent a workspace that can snapshot (LocalWorkspace and the sandbox workspaces can). The box is unchecked by default. Checked, the dry run lists the files the rewind writes back and the files it deletes, and Apply or Fork restores the agent's workspace to that point, after a snapshot of the files as they were (safetySnapshotId in the answer) to undo it. User messages with a checkpoint have checkpoint: true in the tree, and every entry a rewind can restore files at has files: exact for the files as they were there, before for the files from before its turn.

Scripts reach the same through POST /api/threads/:id/rewind with { entryId, files?, memory?, dryRun?, force? } (a RewindSummary with the updated thread) and POST /api/threads/:id/fork with { entryId, files? } (a ForkSummary with the new thread). A bad body answers 400, as does a point the runtime cannot rewind or fork at (no checkpoint there, an entry off the active branch), an unknown thread 404, and a thread a run goes on in 409.

How it works

browser  ──HTTP, SSE──▶  studio server (cogitator dev)  ──IPC──▶  runtime host (child process)
  React UI                 run store in .cogitator/studio           your project, loaded with tsx
                           file watcher                             cogitator.observe(), TimeTravel,
                                                                    WorkflowManager
  • The runtime host is a child process that loads .env and src/cogitator.ts with tsx and runs the project with its own runtime, memory and tools. It watches every run through cogitator.observe(), so runs that agent tools start on the same runtime are seen too, and links them to the request that started them with AsyncLocalStorage. Forks use the project's TimeTravel with checkpoints kept on disk; workflows run on a WorkflowManager with file checkpoints, which is what reruns from a node start from.
  • The server keeps the runs and threads, prices model calls, puts nested runs under the tool call whose span was open when they started, and pushes every change to the browser over server-sent events.
  • When a file under src/, cogitator.yml, .env, package.json or tsconfig.json changes, the host is started again with the new code: the server and the browser stay, runs that were going are marked failed with the reason.

Security

The server listens on 127.0.0.1 and answers only requests for localhost, so another site cannot reach it through DNS rebinding, and refuses cross-site posts. --host 0.0.0.0 (or any other address) opens it to the network and requires the random token printed in its URL; the token is kept in an HttpOnly, SameSite=Strict cookie after the first visit.

API

import { startStudio } from '@cogitator-ai/studio';

const studio = await startStudio({ projectDir: process.cwd(), port: 4321 });
console.log(studio.url);
await studio.ready();
// ...
await studio.close();
OptionDefaultWhat it does
projectDirrequiredThe project to run
host127.0.0.1Interface to listen on; anything else needs the token
port4321Port, the next free one unless strictPort
tokenrandom beyond loopbackToken requests must carry
watchtrueReload the project when its files change
studioDir.cogitator/studioWhere the history lives
logReceives the project's output
pricemodel registryPrices a model call from its model and tokens

The HTTP API the UI uses is open to scripts as well: GET /api/state, GET /api/runs?target=&kind=&q=, GET /api/runs/:id, GET /api/threads/:id, GET /api/threads/:id/tree for the memory tree of a thread, POST /api/threads/:id/rewind and POST /api/threads/:id/fork to rewind a thread or fork it at an entry, POST /api/chat, POST /api/runs/:id/stop, POST /api/approvals/:id, POST /api/runs/:id/fork, POST /api/workflows/:key/run, POST /api/runs/:id/rerun, POST /api/swarms/:key/run ({ input }), GET /api/stats for the totals of the overview, and GET /api/events for the event stream. The shapes are the types exported from the package (RunRecord, ThreadRecord, ThreadTree, TreeEntry, StudioStats, StudioEvent and the rest). A RunRecord carries memory (MemoryPlace: threadId, branchPoint, leafId) when its run wrote to memory. A ThreadRecord lists the runs a rewind took off its branch in rewoundRuns, and a forked one carries forkOf (threadId, entryId) and base, the messages it starts from. Every RunRecord has a revision that each change of the run raises, and token and reasoning events carry the revision they bring it to: a client that mixes snapshots with the event stream keeps the copy with the higher one. A swarm run has kind: 'swarm', and a team run carries team (TeamRecord: its agents, tasks, assignments and messages), which applyTeamEvent builds from the team:* events of the swarm.

On this page