Persona API v0.1¶
Note
Join the discussion on this GitHub issue!
Context¶
The existing jupyter_ai_persona_manager v0.0 API fulfills the original vision
of AI personas in Jupyter AI as generic, named entities available in each
chat that process messages that @-mention them. AI personas are implemented
via the BasePersona class, which is initialized once per chat, per persona.
Its main API is simply a process_message(self, message: Message) method that
defines how the agent handles a message that @-mentions it. The chat API is
accessed from the self.ychat attribute. This remarkably minimal definition
provides developers with extreme flexibility in what a persona can be: a
persona can be a tutor that responds with guided explanations and no tool
access, or a structured AI workflow, or a full agentic assistant with tool
calling and MCP server integration.
Our most mature and feature-rich implementations of AI personas live in the
jupyter_ai_acp_client package, which provides BaseAcpPersona as a general
template for defining Agent Client Protocol (ACP) agents as AI personas.
BaseAcpPersona inherits from BasePersona, orchestrates the ACP agent
subprocess in __init__(), and overrides the process_message() to delegate
message handling to a unified ACP client that speaks to agents and writes back
to the chat. AI personas built on ACP (e.g. CodexAcpPersona) then derive
from BaseAcpPersona while specifying an agent executable, persona name +
avatar, and optional auth logic. We have used the personas abstraction to
great success here, integrating 8 frontier agents (Claude, Kiro, Copilot,
Gemini, Goose, Codex, OpenCode, Mistral Vibe) in less than 2 months.
Motivation¶
In recent community calls and at the Q2 2026 Developer Summit, users and developers identified three key gaps in the current v0.0 API:
Creating new personas requires writing Python code. Defining a custom persona requires writing a Python file with a class that subclasses
BasePersona. Even though we offer a way to do this locally, the barrier remains too high for most users. Users want to be able to just say “re-use the Claude harness, but call this persona ‘Researcher’, and give it these tools” in some kind of no-code format (.yaml,.md, etc.).An AI persona’s identity, model, or context cannot be configured at runtime.
BasePersonaprovides no API for this. Users can set an AI persona’s model and MCP servers by editing agent-specific configuration files and.jupyter/mcp_settings.json, but this only works for ACP agents, and changes only take effect after restarting the server.AI personas rely on shared paths for skills and MCP servers. The lack of context isolation between AI personas powered by the same agent harness makes it challenging to define AI personas with unique capabilities for specific use-cases. For example, users may want to define an MCP tools working with sensitive data, which should only be available to an AI persona powered by a local on-prem model with instructions to sanitize outputs.
Proposal Summary¶
In v0.1, we will deconstruct the persona into separate, well-defined concepts, and lift everything except the agent harness out of the class definition:
Persona class — defines the agent harness (e.g. Claude, Codex, OpenCode). The class may also define what defaults apply when no configuration is provided.
Model (instance-level) — specifies the model provider, model ID, and model URL.
Context (instance-level) — specifies skills paths, MCP servers, and system prompt for the agent.
Identity (instance-level) — specifies the name and avatar shown in the chat.
Options (instance-level) — specifies additional per-agent settings, e.g. planning/agent mode, effort, hyperparameters. This will be left deliberately unstructured as it will be used as a way to pass per-instance settings that may be specific to an AI persona class.
BasePersona will continue to define the agent harness in its class
definition. However, a persona’s model, context, identity, and options will
all become instance-level attributes defined by dedicated Pydantic models
accepted in the constructor.
To enable future work on making model selection and persona configuration more
intuitive, the new BasePersona interface in v0.1 will also provide a complete
API for updating these instance-level attributes at runtime. Here is the new
BasePersona interface being proposed for v0.1 compared to v0.0, with the less
important methods hidden:
class BasePersona(ABC, LoggingConfigurable):
# ─── Lifecycle methods ────────────────────────────────
def __init__(
self,
*args,
ychat: "YChat",
model: PersonaModel | None = None,
context: PersonaContext | None = None,
identity: PersonaIdentity | None = None,
options: dict | None = None,
**kwargs,
): ...
async def shutdown(self): ...
# ─── Process message ────────────────────────────────
async def process_message(self, message: Message) -> None: ...
# ─── Runtime update APIs ────────────────────────────────
def update_model(self, model: PersonaModel) -> None: ...
def update_context(self, context: PersonaContext) -> None: ...
def update_identity(self, identity: PersonaIdentity) -> None: ...
def update_options(self, options: dict) -> None: ...
class BasePersona(ABC, LoggingConfigurable):
# ─── Lifecycle methods ────────────────────────────────
def __init__(
self,
*args,
ychat: "YChat",
**kwargs,
): ...
async def shutdown(self): ...
# ─── Process message ────────────────────────────────
async def process_message(self, message: Message) -> None: ...
Note
In this proposal, we are not re-defining personas to only be agents. Persona classes are free to ignore any or all of these instance-level attributes and leave their corresponding methods unimplemented. For non-agents, the persona class may define an “LLM runtime” or “AI workflow template” instead of an agent harness. This proposal preserves the generality of the AI persona as a concept.
In the next section, we will show examples that better convey why the new APIs proposed here close the gaps identified in v0.0 from a developer and user perspective.
Examples¶
Example 1: Defining agentic AI personas powered by local models¶
In v0.0, to define an AI persona that uses local models, you need to use an open-source agent harness (only OpenCode or Goose currently), then set the model ID and skills at some agent-specific settings path. Once you do all of that, Jupyter AI initializes your persona like this (shortened for brevity):
# initializes '@OpenCode' that just reads from your local OpenCode config
OpenCodeAcpPersona(ychat=ychat)
# cannot add another instance of `OpenCodeAcpPersona`
In v0.0, you can only invoke this persona as @OpenCode since the persona’s
identity is defined by the class. Since each persona class maps to exactly one
persona instance per chat, there’s no way to create another AI persona built on
OpenCode that uses a different model and skillset.
The v0.1 API will allow developers to reuse the harness defined in an existing class to create a new AI persona, with a different model, context, identity, and options. In v0.1, personas can be initialized like this:
# initializes '@MyAgent', your custom general-purpose agent
OpenCodeAcpPersona(
ychat=ychat,
model=PersonaModelSpec(model_id="anthropic/claude-opus-4-8"),
identity=PersonaIdentity(name="Researcher", avatar="<some-url-or-b64-img>"),
context=PersonaContext(
skills=".jupyter/skills/*",
mcp_servers=[...]
),
options={"temperature": 0.8},
)
# initializes '@SensitiveDataAnalyst', a self-hosted agent for sensitive data
OpenCodeAcpPersona(
ychat=ychat,
model=PersonaModelSpec(model_id="ollama/llama-3.1", model_url="http://localhost:11434"),
identity=PersonaIdentity(name="SensitiveDataAnalyst", avatar="<some-url-or-b64-img>"),
context=PersonaContext(
skills=".jupyter/sensitive-data-skills/*",
mcp_servers=[McpServer(name="Sensitive data tools", ...)]
),
options={"temperature": 0.2},
)
This would create 2 AI personas in your chat: @MyAgent and
@SensitiveDataAnalyst. These AI personas are built on the same agent harness
defined in the OpenCodeAcpPersona class, but have separate identities,
models, contexts, and options.
Example 2: No-code path to defining a custom persona¶
It is clear from the above example that if we are just re-using a persona class,
there is no need to create a Python module to define an AI persona. Since all of the
arguments are serializable, they can be represented in any no-code format that
can express dictionaries / key-value pairs. Markdown files with YAML
frontmatter are well-suited for this while being easily readable. The v0.1
architecture enables a future where .persona.md files can be used to define
AI personas by reusing an existing AI persona class.
The use-case described above in Example 1 can then be created simply by
creating two new Markdown files under .jupyter/personas, with no code changes
required.
---
persona_class: OpenCodeAcpPersona
model:
model_id: anthropic/claude-opus-4-8
identity:
name: Researcher
avatar: researcher.svg
context:
skills:
- .jupyter/skills/*
mcp_servers:
- name: Research tools
type: http
url: http://localhost:3001/mcp
options:
temperature: 0.8
---
You are a research assistant specialized in scientific literature review.
Synthesize findings across papers, identify gaps, and suggest next steps.
Always cite your sources.
---
persona_class: OpenCodeAcpPersona
model:
model_id: ollama/llama-3.1
model_url: http://localhost:11434
identity:
name: SensitiveDataAnalyst
avatar: sensitive-data.svg
context:
skills:
- .jupyter/sensitive-data-skills/*
mcp_servers:
- name: Sensitive data tools
type: stdio
command: python
args: ["-m", "sensitive_data_mcp"]
options:
temperature: 0.2
---
You are a data analyst working with sensitive datasets. Always sanitize
outputs before presenting them. Never include raw PII in your responses.
Prefer aggregations and anonymized summaries.
After these personas are defined and saved as files, they will appear
automatically in new chats. They will appear in existing chats after a user
runs /refresh-personas.
Technical Details¶
Pydantic Models¶
Note
The structure of these models is subject to change in future versions; it is likely that we will need to add or update fields to align with what makes sense in practice.
from pydantic import BaseModel
from typing import Optional
class PersonaModel(BaseModel):
"""Defines which LLM a persona uses."""
model_id: str
model_url: Optional[str] = None
class PersonaContext(BaseModel):
"""Defines the context available to a persona."""
system_prompt: str | None
mcp_servers: list[McpServerStdio | McpServerHttp] = []
skills_paths: list[str] = []
class PersonaIdentity(BaseModel):
"""Defines how a persona appears in the chat."""
name: str
avatar: str | None = None
description: str | None = None
Other required updates¶
Out of brevity, the proposal summary did not exhaustively list all of the technical changes needed to make this feature work. Here is a rough sketch of the other requirements not yet described:
PersonaManagerwill need new methods to support loading Markdown files as personas, and call these automatically on__init__()or when/refresh-personasis called.Currently,
persona.idjust uses the module path and the class name to create the ID. This is not unique if we allow multiple personas deriving from the same class to co-exist in the same chat, so we need to make a breaking change in how persona IDs are generated automatically.We need to come up with some way to allow users to disable the “default persona instance”. Support use cases where a user builds personas on top of
OpenCodeAcpPersona, but does not want@OpenCodeas a persona.To take advantage of the new APIs introduced in
BasePersona, we need to provide some way in the UI to update the model, context, identity, and options. We will focus on making the right API first before diving into the UI decisions.We need to make chats portable. Right now each persona avatar is served from the server on a special API route, so these avatars get lost if the chat is shared to another user without that persona installed. Both persona and user identities should be encoded and saved into a chat directly so AI chats are portable and shareable.
Acknowledgements¶
Thank you to everyone who attended the Jupyter AI community calls and events to drive deep discussion and collaboration on this topic, especially Matt, Carl, Sanjiv, Jordi, and Fernando!
Note
Join the discussion on this GitHub issue!