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:
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_inputSTART 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.
| Status | Meaning | What to do |
|---|---|---|
queued / running | Work in flight | Subscribe to stream_url or poll read_url |
awaiting_input | Paused on a human question | Render the pause; POST the answer to resume_url |
complete | Final answer on the run | Read answer.answer_text + answer.content_blocks |
error / canceled | Dead | Surface the error; do not resume |
START
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):
{
"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
| Field | Required | What it does |
|---|---|---|
input | yes | The ask, in plain language. |
chat_id | no | Continue an existing Chat. |
name | no | Name a NEW Chat. Omitted: auto-titled from the input. |
agent | no | Pin the turn to a named agent (library slug like researcher, or your own workspace agent). |
config | no | The knobs below. |
Config (the only knobs)
| Field | Default | Notes |
|---|---|---|
depth | standard | quick | standard | deep. A real investigation tier — see Depth below. |
actions_allowed | [] | Outbound actions this run may fire (e.g. email_member). Empty = none. |
write_outputs | false | Allow living-doc commits. |
write_memory | false | Allow long-term memory commits. |
on_question | auto | pause (interactive), auto (proceed on judgment), none (headless fail-fast). |
external_search | false | Allow paid market fan-outs (deep forces this on). |
include_divergence | false | Fuse the internal-vs-market divergence map (deep forces this on). |
max_cost_cents | chat default | Cost 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:
| Tier | Model | Turn budget | Toolset | Reach for it when |
|---|---|---|---|---|
quick | fast | ~10 | lean (fast data reads + fast search) | a focused lookup you want back quickly; no fan-out, no delegation. |
standard | default | ~50 | full | the everyday default — the full toolset at the profile's own model. |
deep | most capable | ~75 | full, and external_search + include_divergence are forced on | a 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:
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):
# 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:
{
"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 blocks — text, 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:
| Verb | What it does |
|---|---|
GET /chats | List chats, newest activity first (status, limit, offset). |
GET /chats/:id | One chat: snapshot, runs with read handles, active runs, pending human asks (each with its resume_url). |
GET /chats/:id/runs/:run_id | The READ endpoint from the START handles (supports ?wait_ms=). |
PATCH /chats/:id | Rename: { "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):
| Action | Role |
|---|---|
start_chat | START — same body as POST /chat (input, optional chat_id / name / agent / config); returns the handles immediately. |
chat_status | READ — { chat_id, run_id, wait_ms? }; the same snapshot as read_url, wait_ms long-polls up to 30s. |
respond | ANSWER — { run_id, response } where response matches the pause schema chat_status surfaced. |
cancel_chat | Cancel 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
| Caller | write_memory | write_outputs | on_question | external_search |
|---|---|---|---|---|
| Bare API / MCP key | false | false | auto | false |
| Console (session JWT) | true | false | pause | true |
| Routine fire | per recipe | usually true | none | per recipe |
The server default is the bare-API row; the console and routines set their own knobs explicitly on each START.