Veronica — built-in secretary
Veronica is Agency’s built-in secretary and reference operator. She is not a
throwaway demo agent: boot seeds her with stable system_key identity,
maintained instructions, and example missions that upgrade automatically until
an operator customizes them.
Role
Veronica coordinates the agent OS for a signed-in operator:
Explain Agency capabilities via Help and
read_agency_help.Discover agents, missions, and executions through secretary tools.
Dispatch missions and surface UI (Help window, Displays, notifications) via audited tools.
Drive kiosk slides through
agency.tools.display(assign a View, mutate the Scope).Ask before consequential or ambiguous actions.
Product code resolves the user’s active agent (UserPreferences.active_agent).
Veronica is the bootstrap fallback when no preference is set and she is available.
Inference
Boot keeps her on a working chat model. When Ollama answers /api/tags with
at least one non-embedding model, uncustomized Veronica stays local-first.
If Ollama is down, Tailscale-unreachable, GPU-less, or embed-only, she uses
gpt-4o-mini (needs OPENAI_API_KEY). Customized instructions are never
overwritten; her model is only moved if the current one cannot run.
The System window Info tab (GET /api/system/info/) has three sibling
collapsibles: Versions (Agency), Inference (Ollama + GPUs from the NVIDIA
container, ComfyUI version when COMFYUI_URL is set), and System (closed:
Host + Python). GPUs are whatever Ollama sees. A line is appended to
system.log when the GPU snapshot changes (System → Logs). Django 500s log a
traceback there too.
Shipped content
Agents and missions shipped with Agency carry:
system_key— stable identity (e.g.veronica,example.agency_help)shipped_content_hash— hash of shipped prompt/instructionsis_customized— whenTrue, boot will not overwrite operator edits
Boot upsert (agency.shipped) creates rows on first install and upgrades
unchanged shipped content on deploy. Customized rows are left intact.
Secretary tools
High-level tools live in agency.tools.secretary and require runtime context
bound during ThreadRunner.invoke (user_id, agent_id, thread_id,
execution_id). They never accept ambient admin parameters.
Catalog Tool.id is the Python definition with a leading agency. stripped
(tools.secretary.title_user_photo). The LLM still calls the Python function
name (title_user_photo) — OpenAI/Ollama tool names cannot contain dots.
Externals keep their full import path.
Context and discovery:
get_operator_context— active agent, in-flight work, recent executions, desktop summarylist_running_work— running executions and specialist waits (all secretary sessions)list_available_agents/list_available_missions/list_catalog_toolsget_execution_statusread_agency_helpinternet_search(Tavily) /read_webpage
Delegation:
dispatch_mission/cancel_executioncreate_specialist_agent— allowlisted tools only
System surfaces:
tools.secretary.show_notification—secretary.notifyon the user WebSockettools.secretary.open_window/close_window— Agency Busdesktop.<address>.visibility.changed(Help, Missions, Photos, Media, Map, Displays, Views; a Display name pops a live preview; a View name opens the editor)tools.secretary.show_media— image or YouTube on the operator Media player (desktop.media.now_playing.changed+ openmedia). Not a kiosk assign.tools.secretary.get_desktop/update_desktop— validatedUserPreferences.desktopsubsettools.icon.generate_agency_icon— 128×128 flat white dock glyph onlytools.image.generate_image— color picture (256–1024, default 768). ComfyUI first; passmodel=fal-ai/flux-lorafor FAL FLUX (needsFAL_KEY). Prompt in English. Saves to Photos; optionaldisplayputs it on one named kiosk (never every screen). Use this for maps, posters, “más colores / más grande”.tools.image.edit_image— change Photos / Media pictures. One photo: FALfal-ai/flux-lora/image-to-image, then local ComfyUI SD 1.5 img2img. Omitphoto_idto edit whatever is on the Media player.strength0.35 keeps identity; 0.85 remakes. Two or morephoto_ids(chat attachments, comma-separated): FLUX Kontext multi — “put this robot on the motorcycle”. No Comfy fallback for compose. Saves a new Photos row and ``show_media``s it.tools.secretary.title_user_photo— set a title on a library photo (chat attachments are already saved as Untitled before the model sees them; omitphoto_idfor the latest chat upload)tools.display.*— kiosk operator surface (see Example display views (boot seed)):inspect_displays,commission_display_view(secretary; waits for Viewwright),create_display_scope,assign_display(one named screen),assign_displays(named list + required shared scope; refuses*/ all / protected),wait_for_display_action(persist wait; CountersendActionresumes),set_display_property,import_display_image,show_image_on_display. Specialists usecreate_display_view/update_display_view. Names/slugs/namespaces are enough; do not pass Channels groups. Skipprotecteddisplays and views.wait_for_executionpauses until another Execution finishes.
UI projection
Chat surfaces (operator UX)
Agency converges on a Cursor-like agent chat in the browser — not a generic chatbot product, but the same interaction pattern: turns, tool/process steps, follow-ups, and optional Dev raw — bound to durable Executions and Threads.
Surface |
Component |
Status |
|---|---|---|
Mission execution chat |
|
Shipped — compose continues the thread via |
Agent test chat |
|
Shipped — ephemeral multi-turn on |
Secretary chat |
|
Primary entry: first desktop icon ( |
See Frontend components (agency-ui) (execution-view, agent-test) and
agency-workspace/todo/20-veronica-secretary.md (mission chat UX closed: todo/history/2026-08.md;
Phase 6 operator entry = desktop face; app-bar nabla cancelled).
agency.ui.push publishes frames to user-{id}; UserConsumer.agency_frame
forwards them to the SPA. secretary.notify frames route to the popup stack;
ui.* frames follow the schema event contract.
Live presence
The face now reflects work, not only conversation. The Mission runtime
publishes agent.<id>.presence.changed on Agency Bus, so Veronica visibly
thinks while a Run calls a model and acts while it uses a tool — previously she
only moved when you were chatting with her and stood still during real work.
Presence is keyed by Agent, so every portrait of the same Agent animates
together and a second Agent has its own state. It is ephemeral: a reload shows
idle until the next event. The mouth remains a local rendering effect driven
by arriving text. When the operator asks her to smile or show an emotion,
set_face_expression publishes agent.<id>.face.changed and the face
stays until they ask for another expression (or an optional timeout).
Expressions (set_face_expression / bus payload):
neutral — neutra
happy — sonrisa
sad — tristeza
angry — enfado
afraid — miedo
surprised — asombro
disgusted — asco
focused — concentración
thinking — pensando
questioning — duda
concerned — preocupación
tired — cansancio
sleeping — dormida
Contract and states: Agent presence slice.
Example missions
See Example missions (boot seed). Shipped examples (namespace mission.example.*;
explorer labels Example · {name}):
Agency Help —
read_agency_helpvia VeronicaDispatch mission —
list_available_missions+dispatch_missionOpen Help window —
open_window(window='help')Close Help window —
close_window(window='help')Generate tool icon — pictogram +
generate_agency_iconFantasy display wallpaper —
generate_imagethenshow_media(named kiosk only if the operator named a screen)
Related backlog: agency-workspace/todo/20-veronica-secretary.md.