Docs

Chat

Consumer-facing guide to POST /chat — the door for asking Amdahl anything that needs a multi-step, server-side investigation. (Renamed from Search: Chat is the multi-turn agentic lane; the name Search now belongs to the synchronous fast-search endpoint, POST /search — reach for that when you want data back in one blocking call rather than a full agent run.) Every ask opens (or continues) a named Chat and runs one Master agent turn over your workspace data: warehouse queries, customer-voice themes, knowledge base, long-term memory, and (when enabled) external market search. For dispatching sub-agents inside a run see Delegating to sub-agents; for the human-in-the-loop pause see Asking a human.

The lifecycle

Chat has exactly one lifecycle on every surface:

code
START  →  { chat_id, run_id, status, stream_url, read_url, resume_url }
   │
   ├─ SUBSCRIBE (SSE on stream_url)   live events: tools, delegates, content blocks, pauses
   ├─ READ (GET read_url)             snapshot anytime; ?wait_ms= long-polls up to 30s
   └─ ANSWER (POST resume_url)        only when status is awaiting_input

START never blocks for the final answer. There is no stream flag and no synchronous mode — streaming is how you watch a run that already started, and "just give me the answer" is a client-side wait: START, then poll read_url until the run settles. Treat the three returned URLs as opaque handles.

StatusMeaningWhat to do
queued / runningWork in flightSubscribe to stream_url or poll read_url
awaiting_inputPaused on a human questionRender the pause; POST the answer to resume_url
completeFinal answer on the runRead answer.answer_text + answer.content_blocks
error / canceledDeadSurface the error; do not resume

START

bash
curl -X POST https://api.amdahl.com/api/platform/v1/chat \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "What are the top objections in enterprise deals this quarter?",
    "name": "Enterprise objections",
    "config": { "depth": "standard" }
  }'

Response (immediately — the run is queued, not finished):

json
{
  "success": true,
  "chat_id": "3f0e2a1b-…",
  "run_id": "9a8b7c6d-…",
  "status": "queued",
  "stream_url": "/api/platform/v1/conversations/3f0e2a1b-…/turns/9a8b7c6d-…/stream",
  "read_url": "/api/platform/v1/chats/3f0e2a1b-…/runs/9a8b7c6d-…",
  "resume_url": "/api/platform/v1/conversations/3f0e2a1b-…/turns/9a8b7c6d-…/resume"
}

Pass chat_id instead of name to continue an existing Chat — the new turn shares the Chat's memory (its substrate) with every earlier turn.

Body fields

FieldRequiredWhat it does
inputyesThe ask, in plain language.
chat_idnoContinue an existing Chat.
namenoName a NEW Chat. Omitted: auto-titled from the input.
agentnoPin the turn to a named agent (library slug like researcher, or your own workspace agent).
confignoThe knobs below.

Config (the only knobs)

FieldDefaultNotes
depthstandardquick | standard | deep. A real investigation tier — see Depth below.
actions_allowed[]Outbound actions this run may fire (e.g. email_member). Empty = none.
write_outputsfalseAllow living-doc commits.
write_memoryfalseAllow long-term memory commits.
on_questionautopause (interactive), auto (proceed on judgment), none (headless fail-fast).
external_searchfalseAllow paid market fan-outs (deep forces this on).
include_divergencefalseFuse the internal-vs-market divergence map (deep forces this on).
max_cost_centschat defaultCost ceiling for a NEW chat; the platform default applies when omitted.

There is no stream, model, temperature, evidence, or as_of field. The model is picked by the depth tier, not by you. Evidence (citations and the query behind every data-backed block) is always attached — it is not a toggle. As-of / time-travel is not a Chat knob: an interactive Chat always reads your current data; historical "as we understood it on date X" reads belong to workflow backtests. Unknown fields are rejected with a structured invalid_argument error so a typo never silently changes behavior.

Depth

depth is a genuine investigation tier — the platform sets the model, the turn budget, and the toolset for you:

TierModelTurn budgetToolsetReach for it when
quickfast~10lean (fast data reads + fast search)a focused lookup you want back quickly; no fan-out, no delegation.
standarddefault~50fullthe everyday default — the full toolset at the profile's own model.
deepmost capable~75full, and external_search + include_divergence are forced ona hard, multi-part question worth decomposing and cross-checking; the run is told to break the ask into sub-questions and self-verify every figure before answering.

You never pass a model — the tier picks it. deep costs more and takes longer; it is the tier to reach for when correctness matters more than speed.

Watching a run

Subscribe (SSE) — the same event stream the console renders:

bash
curl -N -H "Authorization: Bearer $API_KEY" \
  "https://api.amdahl.com$STREAM_URL"

Or poll — wait_ms long-polls up to 30 seconds per request, returning the same body a plain GET returns (never a second START shape):

bash
# repeat until status is complete | awaiting_input | error | canceled
curl -H "Authorization: Bearer $API_KEY" \
  "https://api.amdahl.com$READ_URL?wait_ms=30000"

The READ body carries status, the pause payload when awaiting input, usage, and the answer envelope:

json
{
  "data": {
    "run": {
      "chat_id": "…",
      "run_id": "…",
      "status": "complete",
      "pending_input": null,
      "answer": {
        "answer_text": "Enterprise objections cluster around…",
        "content_blocks": [
          { "id": "b1", "type": "text", "markdown": "…", "presented_at": "…" },
          {
            "id": "b2",
            "type": "chart_spec",
            "chart_type": "bar",
            "encoding": { "x": "objection", "y": "n" },
            "query": { "kind": "data.query", "surface": "interactions", "sql": "SELECT …" },
            "data": [{ "objection": "pricing", "n": 42 }],
            "presented_at": "…"
          }
        ]
      },
      "usage": { "tokens": { "input": 51234, "output": 2210 }, "estimated_cost_cents": 12, "turns_used": 7 }
    }
  }
}

Answers are content blocks

Answers are an ordered list of typed content blockstext, callout, citation, table, chart_spec, metric — not one opaque string (answer_text is the flattened markdown for logs and dumb clients). Data-backed blocks (table, chart_spec, query-backed metric) always carry both the declared query (re-run it live via data.query for current numbers) and the snapshot data (exactly what the agent saw when it presented the block), on every surface. That pair is what powers Snapshot / Live / Diff rendering: show the snapshot instantly, re-run the query for live values, diff the two over time.

Blocks arrive mid-run as content_block events on the SSE stream and reconcile into the terminal answer.content_blocks in presentation order. Ignore block types you do not recognize — the catalog grows additively.

Answering a human question

With on_question: "pause", a run that calls ask_a_human lands in awaiting_input: the READ body's pending_input carries the structured question (multiple choice always includes an Other write-in, or free form). Answer by POSTing the resume body to resume_url — the standard turn resume described in Asking a human. The SSE stream stays open across the pause. With the default on_question: "auto", runs never pause — the agent states its assumption and proceeds.

Chats

Chats are the named threads runs ride on. List, inspect, and rename them:

VerbWhat it does
GET /chatsList chats, newest activity first (status, limit, offset).
GET /chats/:idOne chat: snapshot, runs with read handles, active runs, pending human asks (each with its resume_url).
GET /chats/:id/runs/:run_idThe READ endpoint from the START handles (supports ?wait_ms=).
PATCH /chats/:idRename: { "name": "…" }.

Every turn in a Chat shares one substrate — the scratch memory context.remember / context.query_substrate write and read — so follow-up asks build on earlier findings, and any sub-agents dispatched inside a run share it too.

Over MCP

The same lifecycle is on the Amdahl MCP server as four actions on the agents tool, never blocking a full run inside one tool call (MCP tool calls have a hard time ceiling; a deep Chat can take minutes):

ActionRole
start_chatSTART — same body as POST /chat (input, optional chat_id / name / agent / config); returns the handles immediately.
chat_statusREAD — { chat_id, run_id, wait_ms? }; the same snapshot as read_url, wait_ms long-polls up to 30s.
respondANSWER — { run_id, response } where response matches the pause schema chat_status surfaced.
cancel_chatCancel one run — { run_id, reason? }.

The check-in loop: start_chat → poll chat_status (or read the chat://<id>/runs/<run_id> MCP resource) until the run settles → respond if it paused on a question. Chat reads are also MCP resources: chat://list and chat://<id>. Scopes: conversations:write to start, conversations:read to read, workflows:write to respond/cancel — all on the customer-agent key bundle (existing keys were backfilled).

Per-surface defaults

Callerwrite_memorywrite_outputson_questionexternal_search
Bare API / MCP keyfalsefalseautofalse
Console (session JWT)truefalsepausetrue
Routine fireper recipeusually truenoneper recipe

The server default is the bare-API row; the console and routines set their own knobs explicitly on each START.