Open the console

SDK

Sessions and files

A conversation, read from where the agent wrote it, and the files that travel with it. sessions wraps the sessions API and artifacts the artifacts API.

Conversations

A session is ADK's own, stored in your agent's database, and these methods read it through ADK's session service — so a transcript is exactly what the agent saw, and it does not depend on the run history being available. Sessions and runs explains how a conversation and its runs relate.

Request
from elan_sdk import Elan

elan = Elan.from_env()      # every call uses the client's project unless you pass project=

session = elan.sessions.create(
    "hotel-concierge",
    session_id="sess-ada-1",
    user="ada",
    state={"plan": "pro"},
    title="Late checkout for room 412",   # what a list shows; the agent never sees it
)

for s in elan.sessions.list("hotel-concierge", user="ada"):    # newest first
    print(s.id, s.title, s.state_keys)

one = elan.sessions.get("hotel-concierge", "sess-ada-1", user="ada")
print(one.state)

elan.sessions.delete("hotel-concierge", "sess-ada-1", user="ada")
MethodWhat it does
create(target, *, session_id, state, title, user, project)Starts a conversation. The id is yours to choose, or ADK mints one; an id already in use is a Conflict, never a silent reuse.
list(target, *, user, project)The conversations with one agent, newest first. Each carries state_keys rather than its whole state.
get(target, session_id, *, user, project)One conversation's id, title, state and last update, without its events.
events(target, session_id, *, user, recent, after, project)Everything in the conversation, as the events the live stream sends.
turns(target, session_id, *, user, project)The messages alone: each person's and each agent reply's text, built from events().
run(target, session_id, *, user, project)A workflow run's inputs, steps and pause, as a plain dictionary shaped for a UI rather than a typed model.
delete(target, session_id, *, user, project)The conversation and every event in it. Not recoverable.

TypeScript has the same seven methods, with everything after the target and session id in an options object: sessions.events(target, id, { user, recent, after }).

  • A conversation lives in a project, so every method needs one: the client's own, or project on the call. With neither, the call fails before any request — a ValueError in Python, a plain Error in TypeScript.
  • user is one of your end users, by your own id, and needs a tenant admin credential; without it every method reads the caller's own conversations, which for a service user is usually none. Create and invoke for the same person: a conversation you create as yourself is not the one an end_user_id invocation runs in.
  • Starting state takes conversation keys only. app:, user:, temp: and agentkit: keys are refused with a BadRequest naming the key. To change state later, send state_delta with a turn — see Runtime.
  • A Conflict saying “redeploy it once” means exactly that. The agent was deployed before its history could be read; one redeploy makes it readable and changes nothing else. An agent that is not deployed in the project is a NotFound.

Reading history

events() returns the same AgentEvent objects the stream delivers, so the code that renders a live run renders a reopened one unchanged. A person's message is a started event; each run ends with a finished, or a paused that ends_the_run recognizes, so a fold ends each turn the way it does live; and a view the agent showed comes back as a surface event where it appeared.

Request
events = elan.sessions.events("hotel-concierge", "sess-ada-1",
                              user="ada", recent=200)
for event in events:
    print(event.invocation_id, event.kind, event.text)

for turn in elan.sessions.turns("hotel-concierge", "sess-ada-1", user="ada"):
    print(turn.role, turn.text)

steps = elan.sessions.run("event-planner", "run-5f2a8c1d")   # a workflow's steps and pause
  • History is not the whole stream. typing, queued and action_requested are only ever live, so they are not in it, and segment is empty.
  • recent and after page a long conversation. recent keeps the newest N, from 1 to 10,000; after keeps events at or after a time in seconds since the epoch — inclusive, so the boundary event comes back again. seq is numbered per run within each response, so drop repeats by emitted_at rather than by seq.
  • How one run ended and what it cost is a different question, answered by runs.get() and usage.

Files

A file's bytes never pass through the platform. You ask for one transfer to be authorized; the platform signs a URL for exactly that object, size and content type; the bytes go straight between your client and storage. Files and multi-modal covers how an agent then reads one.

Request
from pathlib import Path

ticket = elan.artifacts.upload(
    "hotel-concierge",
    session_id="sess-ada-1",
    filename="invoice.pdf",
    data=Path("invoice.pdf"),
    content_type="application/pdf",
    user="ada",
)
print(ticket.filename, ticket.version)

names = elan.artifacts.list("hotel-concierge", session_id="sess-ada-1", user="ada")
url = elan.artifacts.download_url("hotel-concierge", session_id="sess-ada-1",
                                  filename="invoice.pdf", user="ada")
elan.artifacts.delete("hotel-concierge", filename="invoice.pdf",
                      session_id="sess-ada-1", user="ada")

Four scopes, chosen by the filename

The same prefixes an agent's own load_artifact uses, so a name from one side works unchanged on the other.

FilenameScopeWho writes it
report.pdfThis conversation.You, the agent.
user:prefs.jsonThis end user, across their conversations.You, the agent.
agent:tone.mdThis agent, shared by all of its users.A tenant admin. Agents only read it.
project:policy.mdEvery agent in the project.A tenant admin. Agents only read it.

The methods

  • Python upload() authorizes and sends in one call, from bytes or a Path, and returns the UploadTicket it used. TypeScript has no upload(): call declareUpload() and send the bytes with putBytes(ticket, body), which is a function rather than a method so the client's credential cannot travel with it.
  • declare_upload() is for bytes you do not hold — a file a browser picked. It returns a URL, a method and headers; send the headers exactly as given, because every one of them is signed. The size you declare is the size storage will accept, at most 512 MiB, and the URL lives fifteen minutes. A browser PUT also needs your origin declared under artifacts.origins in elan.yaml, or it fails as an opaque network error.
  • download_url() (downloadUrl) returns a URL that lives five minutes. Omit version for the newest; attachment, on by default, makes a browser save it under its real name. A missing file is a NotFound.
  • list() returns every filename the conversation can see, prefixes intact, so a name goes straight back into download_url().
  • delete() in Python and remove() in TypeScript remove a file and every version of it. Deleting from a shared scope needs a tenant admin, as writing to one does.

user names one of your end users here too, by your own id. Filenames are checked locally first — an empty name, a second prefix or an escape from the scope is a BadRequest before any request is made — and a deployment with no artifact storage answers NotWired.

Listing only the shared files

The route treats the session id as optional, so that the agent: and project: files can be listed on their own. Both clients make it a required argument of list() anyway. Pass an empty string: both drop an empty parameter rather than sending it.

Request
# An empty session id is dropped from the request, so this lists the
# agent: and project: files only.
shared = elan.artifacts.list("hotel-concierge", session_id="")

Reading this as a model? llms.txt indexes every page, and each page has a “Copy for AI” control.