Skip to main content

Agent

Agent is an entity that is responsible for doing one job (for example being a shopping assistant). Here’s the simplest possible definition of an agent:
export default defineConfig({
  // ...
  agents: [
    {
      name: "simple_agent",
      runs: [
        {
          input: {
            schema: z.object({
              type: z.literal("message"),
              role: z.literal("user"),
              content: z.string(),
            })
          },
          output: {
            schema: z.object({
              type: z.literal("message"),
              role: z.literal("assistant"),
              content: z.string(),
            })
          }
        }
      ]
    }
  ]
});
We defined an agent named simple_agent. It has a single run type which input and output item schemas are defined. The Agent definition above is enough to create a session and add a bunch of items to it.
import { AgentView } from "agentview";

const av = new AgentView({
  apiKey: process.env.AGENTVIEW_API_KEY,
});

// Create a new session
const session = await av.createSession({ agent: "simple_agent" })

// Create a complete run with input, output and reasoning step
await av.createRun({
    sessionId: session.id,
    items: [
        { type: "message", role: "user", content: "Hello, I'm Bob" },
        { type: "reasoning", content: "Hmm, this is a very complex question..." },
        { type: "message", role: "assistant", content: "Hey, nice to meet you :)" }
    ],
    status: "complete"
})

const updatedSession = await av.getSession({ id: session.id })

console.log(updatedSession.runs[0].items)
If you run this script in a terminal you should see following output object printed:
[
    { type: "message", role: "user", content: "Hello, I'm Bob" },
    { type: "reasoning", content: "Hmm, this is a very complex question..." },
    { type: "message", role: "assistant", content: "Hey, nice to meet you :)" }
]
You can also open the Studio to see a new unstyled Session in your Personal Playground.

Data Model

Here’s a brief overview of the data model:
  • Agent is a configuration object, think of it as a class.
  • Session represents a multi-turn conversation with an agent, it’s an instance of Agent.
  • Run is a single turn of the conversation. It is composed of a list of arbitrary JSON objects called Items.
  • The first Item in a Run is considered to be the “input” item. It’s usually the user message.
  • The last Item in a successful Run is considered to be the “output” item. It’s usually the assistant message.
  • Items between input and output are called “steps”. They can be tool calls, server tools, reasoning blocks, hand-offs to other agents, etc.
  • Session might have multiple Run types, each with different input, output and step item schemas.

Session

Session represents a multi-turn conversation with the agent.

Create a session

const session = await av.createSession({ agent: "agent_name" })

Read session

const session = await av.getSession({ 
    id: "session_id"
})

console.log(session.items) // all session items
console.log(session.state) // session state
console.log(session.lastRun) // last run
console.log(session.lastRun.status) // last run status
console.log(session.runs) // all runs

Session Items

The core part of each session state is an ordered list of Session Items, or just Items. Items can be added via Runs (we explain Runs later in this chapter).
await av.createRun({
    items: [
        { type: "message", role: "user", content: "Hello, I'm Bob" },
        { type: "reasoning", content: "Hmm, this is a very complex question..." },
        { type: "message", role: "assistant", content: "Hey, nice to meet you :)" }
    ],
    sessionId: session.id
    status: "complete",
})
Item is a totally arbitrary object that represents that something happened in the history of our session and it needs to be stored for consistent continuation:
  • user or assistant messages
  • reasoning blocks
  • tools (function calls, MCP, server tools)
  • handoffs to other agents in multi-agent systems
  • any custom block
Items are append-only. It means that each time you update run with items they’ll be appended at the end of the list. Removing items is not possible. Each Item is displayed as a separate block in Studio, can have its own custom component, can be commentable, can have its custom scores, feedback etc. It’s worth highlighting that Item in AgentView is just a JSON, it can be anything. AgentView goal is to be as unopinionated as possible and therefore doesn’t force you into any pre-defined message formats. This allows to make it really framework-agnostic. For example, it’s trivial to just send items from AgentsSDK, Responses API, AI SDK, LangChain, without any trouble.

Session state

Sometimes it’s hard to represent a session state just with array of Items. For example, durign the course of the session you might create session memories, or additional artifacts needed for thread summarisation (to prevent context overflow). You might want to store them between runs, but, it’s unnatural to keep them as Items. For that purpose AgentView introduced the concept of the state. State is totally arbitrary object bounded to a session available via session.state.
await av.createRun({
    items: [
        { type: "message", role: "user", content: "Hello, I'm Bob. I have terrible acne, help me." },
        { type: "reasoning", content: "Thinking on the response..." },
        { type: "message", role: "assistant", content: "Use our Anti-Acne Super Cream, it will heal you." }
    ],
    state: {
        userName: "Bob",
        skincareProfile: "has terrible acne"
    }
    sessionId: session.id
    status: "complete"
})

const session = await av.getSession({ id })
console.log(session.state) // { userName: "Bob", skincareProfile: "..." }
You always get access to the last instance of the state. However, AgentView tracks the full history of the state changes so that you can copy the session up to a particular moment in time and continue it with different scenario.

Session Metadata

Sessions can have custom metadata. AgentView aims to be as flexible as possible, that’s why every object can be extended custom metadata. Imagine you’re building a shopping assistant which can run on the product page of e-commerce website. In that case each session must have product_id assigned. Let’s update our agent config:
export default defineConfig({
    // ...
    agents: [
        {
            name: "my_agent",
            metadata: {
                product_id: z.string()
            }
        }
    ]
});
metadata is an object with so called “metafields”. Each metafield has a key (name) and it’s Zod schema. Now when we create a new session we must provide product_id (since it’s not .optional()):
const session = await av.createSession({ 
    metadata: { 
        product_id: "beautiful_pants_123",
    }
})
If we tried to call createSession without providing product_id the createSession function would throw an error. By default, if metafield is not defined in metadata, it’s allowed to add it (no validation). If you want to override it:
export default defineConfig({
    // ...
    agents: [
        {
            name: "my_agent",
            metadata: {
                product_id: z.string(),
            },
            allowUnknownMetadata: false
        }
    ]
});

Updating metafields

You can change the value of metafield via av.updateSession:
await av.updateSession({
    id: session.id,
    metadata: {
        product_id: "new_updated_id"
    }
})
If you have multiple metafields, if you do not include them in the update, they will be left unchanged.

Clearing metafields

You can clear metafield by setting it to null:
await av.updateSession({
    id: session.id,
    metadata: {
        product_id: null
    }
})

Runs

You add items to a session via runs. Run is a special object helping in managing lifecycle of the session. Run represents a single turn of the conversation. It starts with a user action, usually just a user message, an input item. Then the any number of items might be produced, and in the end the output item must be added, which ends the run. Run in more complex systems will usually consist of multiple calls to the LLMs, tool calls, etc. Here’s how to create a run with a user message input message.
const run = await av.createRun({
    items: [
        { type: "message", role: "user", content: "Hello, I'm Bob" }
    ],
    sessionId: session.id
})

console.log(run.status) // in_progress

Run status

The run status can have one of the following values: in_progress, complete, failed or cancelled. After the run is created it automatically has in_progress value. When the run is in_progress, the session won’t accept any new runs. Below, the second av.createRun will result in an exception:
await av.createRun({
    items: [
        { type: "message", role: "user", content: "Hello, I'm Bob" }
    ],
    sessionId: session.id
})

await av.createRun({
    items: [
        { type: "message", role: "user", content: "Hello, I'm Bob" }
    ],
    sessionId: session.id
}) // throws AgentViewError, you can have only one in_progress run at a time!
This mechanism protects your agent endpoint from being called multiple times concurrently with the same input.

Adding items

You can call av.updateRun as many times as you want to keep adding items.
await av.updateRun({
    id: run.id,
    items: [
        { type: "reasoning", content: "Thinking..." },
        { type: "reasoning", content: "Thinking even harder..." }
    ]
})
We highly recommend using streaming and sending data to AgentView as soon as they appear. It allows for better resume logic later.

Ending Run

If the run finished successfully, just set the status to complete:
await av.updateRun({
    id: run.id,
    items: [
        { type: "message", role: "assistant", content: "Hey, nice to meet you :)" }
    ],
    status: "complete"
})
It’s possible that you agent might encounter an error during a run. You can set mark a run as failed easily:
const run = await av.updateRun({ 
    id: run.id,
    status: "failed",
    failReason: {
        message: "LLM model error.",
        details: ...
    }
})
Whenever user drops the connection or clicks a “stop” button, it’s technically not an error state. cancelled status is used to indicate this case:
await av.updateRun({
    id: run.id,
    status: "cancelled"
})
When the run is finished (complete, failed or cancelled), you can’t change its status nor add items anymore.

Retry runs

If the last session run was failed or cancelled, you can still create a new run. If you do it, the last run (the one that was failed or cancelled) will be removed from the history and the new run will be appended to the last successful run: Retry logic with keeping only the input message from the failed run:
if (session.lastRun === "failed") { // retry logic
    await av.createRun({
        items: [session.lastRun.items[0]], // the first item is the input message
        sessionId
    })
}

Resume runs

if (session.lastRun === "failed") { // retry logic
    await av.createRun({
        items: session.lastRun.items,
        sessionId
    })
}

Removing items

You can’t remove items from the session. The core foundation behind AgentView is that whenever you send an item to a client (or agentview), the end user saw it. If end user saw it, then anyone using AgentView Studio should also be able to see it, exactly as user did.

Run metadata

Similary to sessions, runs can be extended with custom metadata. It’s helpful when you want to store usage data, trace id for observability platform link, etc.
const run = await av.updateRun({ 
    id: "RUN_ID",
    metadata: {
        trace_id: "TRACE_ID", // for example Langfuse trace id
        cost: "0.15"
    }
})
Similar to sessions, by default unknown keys are allowed. You can change this behaviour by provide schema and settings allowUnknownMetadata:
export default defineConfig({
    // ...
    agents: [
        {
            name: "my_agent",
            runs: [
                {
                    metadata: {
                        trace_id: z.string()
                    },
                    allowUnknownMetadata: false,
                    // ...
                }
            ]
        }
    ]
});

Long running jobs

By default, the run will automatically be set as failed after 60 seconds of inactivity. Each time you call av.updateRun, the timer is reset. You can reset timer manually by calling av.keepAliveRun:
await av.keepAliveRun({ id: "run_id" })
You can also change default timeout by setting idleTimeout in the agent config:
export default defineConfig({
  // ...
  agents: [
    {
      name: "my_agent",
      runs: [
        {
          input: { /* ... */ },
          output: { /* ... */ },

          idleTimeout: 120, // 2 minutes
        }
      ]
    }
  ]
});