Skip to content
SurogateDocs

Sessions

Every conversation is a session, fully recorded and inspectable. This chapter is about reading and using session data deliberately — what to look at, what to filter on, what to extract for training and evaluation.

For the operator’s lighter walk-through, see Work mode → Review what your agent did.

The TOOLS tab with a call expanded to its arguments and result
TabUse it for
THREADThe conversation as the user saw it. Markdown renders; artifacts display inline; files are downloadable.
EVENTSFull ordered log of every event — user messages, LLM responses, tool calls, governance decisions. Your primary debugging surface.
TOOLSFiltered to tool.call + tool.result. Fastest way to answer “what did the agent actually do?”
SKILLSWhich skills loaded into context, which the LLM invoked. Drift between the two is a signal: the skill loaded but didn’t fire = trigger keywords don’t match, or description is wrong.
POLICIESGovernance denials (policy.denied), plus allowed calls when the deployment enables governance.log_allowed. AI-disclosure events (disclosure.presented / disclosure.confirmed) appear in EVENTS.
METADATACounters (turns, tokens, cost, duration), the 7 quality flags, the agent’s frozen config snapshot at session start.

All six are Develop-mode tabs. Work mode’s Sessions page shows only THREAD, TOOLS and SKILLS — open a session from Dev → Sessions when you need the audit three.

The Sessions list page has filters for: agent, status (Active / Completed / Failed / Archived), and chips for each quality flag (Denied, Overridden, Crashed, 👍, 👎). Filters compose into URL params, so you can bookmark a filtered view.

These flip when corresponding events fire during the session:

FlagSet by
👍 / 👎User feedback in the THREAD view
policy.deniedGovernance gate blocked a tool call
harness.crashThe agent’s runtime crashed mid-session (rare)
saga.compensatedA multi-step rollback fired
expert.overrideBase LLM rejected an expert’s answer
expert.endorseBase LLM accepted an expert’s answer

Use them to slice traffic. Combine on the Sessions list — “sessions in the last 7 days where saga compensated AND the user thumb-downed.”

The THREAD tab: the conversation as the user saw it

Each row is a typed event with a JSON payload. For the canonical list of event types and what their payloads contain, see Event types.

Failure-mode triage:

  • Agent didn’t search the KB → SKILLS tab confirms the relevant skill loaded; if it did, check whether the trigger keywords match the user’s phrasing.
  • Agent searched but got bad results → TOOLS tab, expand tool.result for kb_read_page. Often the KB content is the problem, not the agent.
  • Agent looped → check iteration count in METADATA; the last few llm.response events usually reveal the repetition pattern.
  • Tool was blocked → POLICIES tab; the denial reason is recorded.
  • Saga rolled back → look at saga.step_failed (the trigger) and the saga.compensate events that followed.
DestinationWhere
Copy session URLShareable within your tenant
Export events JSONFull event log as a .json file. Used for audits or offline analysis.
Add to datasetCaptures the session into a versioned dataset. Used to build training corpora — see Datasets.

Channel determines who can see / interact with the session:

ChannelSession boundary
WebEach “New chat” click. End user owns it.
Slack DMOne DM thread = one session.
Slack channelEach @mention starts a thread-scoped session.
TelegramDM thread or forum topic.
Website widgetOne per visitor browser session (resumed via localStorage). Capped by Session Message Cap if set.
API channelEach POST /v1/api/prompts = one session. Service-account owned; no inbox.

They have channel="api", no interactive user (service-account ownership) and no inbox surface. The service-account token fixes the agent — the request body carries no agent selector. Poll for the terminal status, then read the log:

python
import json, requests, time
BASE = "https://ops.surogate.ai/v1/api"
H = {"Authorization": f"Bearer {sa_token}"}
# Recognised body fields: prompt, idempotency_key, metadata.
resp = requests.post(f"{BASE}/prompts", headers=H, json={"prompt": "Analyse Q1.csv"})
session_id = resp.json()["session_id"]
while True:
status = requests.get(f"{BASE}/sessions/{session_id}", headers=H).json()["status"]
if status in ("completed", "failed"):
break
time.sleep(5)
# Events are served as SSE — there is no JSON-list endpoint under /v1/api/.
answer, etype = None, None
with requests.get(f"{BASE}/sessions/{session_id}/events", headers=H, stream=True) as r:
for line in r.iter_lines(decode_unicode=True):
if line.startswith("event:"):
etype = line.split(":", 1)[1].strip()
if etype in ("session.done", "stream.timeout"):
break
elif line.startswith("data:") and etype == "llm.response":
data = json.loads(line.split(":", 1)[1])
answer = data["message"].get("content") or answer
print(answer)

Three traps in that flow:

TrapWhat is actually true
Unknown body keysagent_slug, context and friends are accepted and silently ignored — the session still runs against the token’s agent.
The final answerAn llm.response event, text at data.message.content. There is no assistant.final type. A turn emits several llm.response events (the intermediate ones narrate before a tool call), so take the last one.
Stream terminationsession.done is emitted for completed / archived only. A failed session never terminates the stream — it idles until the 300s cap and closes with stream.timeout. A run longer than the cap needs a reconnect with ?after=<last event id>.

Service-account tokens are honoured only under the /v1/api/ prefix — every other /v1/ path rejects them with a 403, and a user JWT is rejected on /v1/api/ in turn.

See Use cases → API channel for batch jobs for an end-to-end recipe.

Show the workspace and editor in your own app

Section titled “Show the workspace and editor in your own app”

An API session has no file pane, so its agent gets no office_read or office_edit and answers no comment mentions. If your app shows the session’s workspace and opens its office documents beside its own chat, say so when you create the session with the service-account token:

http
POST /v1/api/sessions
{"config": {"file_pane": true}}

The agent then edits documents live and answers mentions as it does in the web chat (see Office documents). Only POST /v1/api/sessions takes the flag; a session started by POST /v1/api/prompts has no file pane.

To open a document in the editor, ask for its editor config on behalf of the person who will see it:

RequestWhat it does
GET /v1/api/sessions/{id}/workspace/editor?path=<path>&theme=lightReturns document_server_url, the signed editor config, and mentionable_users (the agent, where it answers comments). theme is light or dark; view=true opens it read-only
POST /v1/api/sessions/{id}/workspace/editor/mentionTells the chat that a comment mentioned the agent. Body: path, comment_id, message and emails, from the editor’s onRequestSendNotify event. Answers 202

Both need the header X-Surogate-Acting-User-Id: the person’s id in your app, at most 128 characters, without :. X-Surogate-Acting-User-Name (URL-encoded, at most 200 characters) is the name the editor shows; without it, the id is shown. Load <document_server_url>/web-apps/apps/api/documents/api.js, open the editor with new DocsAPI.DocEditor(<element id>, config), and answer its onRequestUsers event with mentionable_users. The @invergent/agent-chat-react SDK does all of this through its adapter’s getWorkspaceEditorConfig and notifyWorkspaceEditorMention.

An API client sets a goal by posting a user.define_outcome event — not by sending /goal … as a prompt:

http
POST /v1/api/sessions/{session_id}/events
json
{
"events": [
{
"type": "user.define_outcome",
"description": "Fix every failing test in tests/",
"rubric": {
"type": "text",
"content": "The final response includes the passing pytest command"
},
"max_iterations": 5
}
]
}

The endpoint is deliberately narrow: user.define_outcome is the only accepted type (anything else is a 422), and rubric is mandatory in exactly the {"type": "text", "content": "…"} shape. max_iterations defaults to 20 and is clamped to 1–20.

Do not send a user message as well. The handler persists the outcome on sessions.config['outcome'], appends the kickoff message itself, reactivates the session if it had already finished, and enqueues it — an extra message double-starts the work. See Goals for the evaluator’s verdicts and the iteration budget.

A session can have children (sub-agents it starts with agent, or a project’s threads). The session detail shows:

  • The Running panel in the sidebar — live sub-agent tree, auto-refreshes every 4s while children are active
  • Per-child links — click to drill into the child’s own session
  • The parent’s delegation.* events showing what each child did

GET /v1/sessions/{id}/tree returns the full descendant graph (up to 200 nodes). It has no /v1/api/ alias, so it is JWT-only. Use it to build your own dashboards over sub-agent fan-out.

Sessions are kept indefinitely by default — they’re the source of truth for compliance and training-data extraction. To delete a specific one: METADATA tab → Delete (soft delete; the row stays for audit but disappears from default views).