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 :doc:`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.
.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 :doc:`../tutorials/example-displays`): ``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**. .. list-table:: :header-rows: 1 :widths: 22 28 50 * - Surface - Component - Status * - **Mission execution chat** - ``AgencyExecutionView`` - Shipped — compose continues the thread via ``POST /api/execution//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//`` (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 :doc:`development/ui/frontend` (``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..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..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: :ref:`agency-bus-presence`. Example missions ---------------- See :doc:`tutorials/example-missions`. 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``.