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/instructions

  • is_customized — when True, 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 summary

  • list_running_work — running executions and specialist waits (all secretary sessions)

  • list_available_agents / list_available_missions / list_catalog_tools

  • get_execution_status

  • read_agency_help

  • internet_search (Tavily) / read_webpage

Delegation:

  • dispatch_mission / cancel_execution

  • create_specialist_agent — allowlisted tools only

System surfaces:

  • tools.secretary.show_notification — secretary.notify on the user WebSocket

  • tools.secretary.open_window / close_window — Agency Bus desktop.<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 + open media). Not a kiosk assign.

  • tools.secretary.get_desktop / update_desktop — validated UserPreferences.desktop subset

  • tools.icon.generate_agency_icon — 128×128 flat white dock glyph only

  • tools.image.generate_image — color picture (256–1024, default 768). ComfyUI first; pass model=fal-ai/flux-lora for FAL FLUX (needs FAL_KEY). Prompt in English. Saves to Photos; optional display puts 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: FAL fal-ai/flux-lora/image-to-image, then local ComfyUI SD 1.5 img2img. Omit photo_id to edit whatever is on the Media player. strength 0.35 keeps identity; 0.85 remakes. Two or more photo_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; omit photo_id for 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; Counter sendAction resumes), set_display_property, import_display_image, show_image_on_display. Specialists use create_display_view / update_display_view. Names/slugs/namespaces are enough; do not pass Channels groups. Skip protected displays and views. wait_for_execution pauses 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

AgencyExecutionView

Shipped — compose continues the thread via POST /api/execution/<id>/message/; collapsible tool steps; HITL via user channel overlay (not compose while interrupted / running).

Agent test chat

AgencyAgentTest

Shipped — ephemeral multi-turn on /channel/agent/<uuid>/ (catalog dev).

Secretary chat

AgencyAgentInterface

Primary entry: first desktop icon (AgencyAgentFace, 128×128 portrait + CSS eyes/mouth; speaking = South Park jaw) opens singleton secretary (also agent.me). Body is the active-agent chat (persistent Thread; Veronica by default). Live face is desktop chrome, not the shell drawer or app bar. No dedicated app-bar nabla button — agent-driven attention uses the popup stack (secretary.notify, HITL, task progress). Other Agents may pin as desktop faces later. Not a second shell drawer. Do not confuse with the example channel-chat “Chat” 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_help via Veronica

  • Dispatch mission — list_available_missions + dispatch_mission

  • Open Help window — open_window(window='help')

  • Close Help window — close_window(window='help')

  • Generate tool icon — pictogram + generate_agency_icon

  • Fantasy display wallpaper — generate_image then show_media (named kiosk only if the operator named a screen)

Related backlog: agency-workspace/todo/20-veronica-secretary.md.