Session State
Mutable per-session state with agentic updates, demonstrated by Taskboard.
The Taskboard agent is a working task list that lives in one session. State persists across messages in the session. The agent updates it directly.
1from kern.agent import Agent23taskboard = Agent(4 id="taskboard",5 model=MODEL,6 db=agent_db,7 tools=[add_task, update_task_status, list_tasks, remove_task, get_summary],8 session_state={9 "tasks": [],10 "categories": ["general", "work", "personal"],11 },12 enable_agentic_state=True, # agent can mutate session_state13 add_session_state_to_context=True, # state is visible in the prompt14)Three flags do the work:
| Flag | Behavior |
|---|---|
session_state={...} | Initial state. Persisted to agno_sessions per (user_id, session_id). |
enable_agentic_state=True | Agent gets update_session_state tool. Can mutate the dict directly. |
add_session_state_to_context=True | State gets injected into the system prompt every turn. |
Session state vs memory vs dependencies
| Type | Scope | Mutability | Persistence |
|---|---|---|---|
session_state | One (user_id, session_id) | Mutable, agent updates | Yes, in agno_sessions |
| Memory | Per user_id across sessions | Mutable, agent or extractor updates | Yes, in agno_memories |
| Knowledge | Global (filterable) | Mutable, you load/upsert | Yes, in agno_knowledge |
| Dependencies | One run | Read-only | No, ephemeral |
Session state holds what the user is working on right now. Memory holds what's true about the user across sessions. Dependencies carry config for the current request.
CRUD via tools
The Taskboard agent ships its own CRUD tools instead of relying on update_session_state directly. This gives the model cleaner intent labels and lets you validate updates:
1from kern.tools import tool23@tool4def add_task(session_state: dict, title: str, category: str = "general") -> str:5 """Add a task to the board."""6 if category not in session_state["categories"]:7 return f"Unknown category: {category}"8 task = {"id": len(session_state["tasks"]) + 1, "title": title, "status": "open"}9 session_state["tasks"].append(task)10 return f"Added task #{task['id']}"1112@tool13def update_task_status(session_state: dict, task_id: int, status: str) -> str:14 """Mark a task as open, in_progress, or done."""15 for t in session_state["tasks"]:16 if t["id"] == task_id:17 t["status"] = status18 return f"Task #{task_id} → {status}"19 return f"Task #{task_id} not found"session_state gets injected into tool functions automatically (same way RunContext does for Injector). Mutations persist.
When to use session state
| Use case | Use |
|---|---|
| Working list the user is editing (tasks, items, choices) | session_state |
| Multi-turn form filling | session_state |
| Per-conversation scratchpad | session_state |
| Cross-session preferences | Memory or user profile |
| Per-request config (tenant, flags) | Dependencies |
If state should survive the user closing the chat and coming back next week, it belongs in memory or a database table the agent reads from, not in session state.
See it in action
1@Taskboard add a task: review the Q2 roadmap, category: work2@Taskboard add a task: book dentist appointment, category: personal3@Taskboard show me my tasks4@Taskboard mark task #1 as in_progress5@Taskboard summarize what I've got openEach turn updates session_state. The next turn sees the updated state in context. Refreshing the chat page reloads the state from agno_sessions.
Source: agents/taskboard/agent.py