Files
oikos/plans/done/2026-07-14-unified-agent-indicator.md
dtoro e3a0326c78 docs: codebase review + documentation maintenance pass
Full review (plans/2026-07-17-codebase-review-and-cleanup.md) covering Go,
web SPA, and docs. Applied low-risk doc/tooling fixes; code refactors and
dead-code deletions are listed as actionable recommendations pending approval.

Doc fixes:
- AGENTS.md: remove ghost of retired request_execution (contradicted the
  retire notice above it); fix knowledge/wiki/ -> archive/knowledge/;
  replace brittle counts (33 tools, 36 docs, 20 checks) with pointers to
  source; drop point-in-time dates.
- OIKOS.md: fix broken plan link (now in done/); 001-011 -> 001-020;
  15 MCP tools -> pointer; replace hardcoded knowledge counts.
- README.md: 15 tools -> pointer; fix wails plan link (now in done/);
  complete internal/ package list (add checkdefaults, observability, safego);
  add cmd/desktop/ to repo layout.
- commands.md, page-templates.md: fix broken links; HERMES.md -> NOMOS.md.

Plans housekeeping:
- Move 4 done 2026-07-14 plans from plans/ to plans/done/.
- Reconcile plans/index.md: add the 2 missing 2026-07-14 entries and the
  2 missing 2026-07-15 done entries; add this review.
- Fix stale plan path in migrations/020 comment.

New docs:
- docs/index.md and docs/operations/README.md (folder READMEs per
  writing-style.md).

Tooling:
- web/package.json: add check/typecheck/lint scripts + svelte-check devDep.
- Makefile: desktop-package version now reads from VERSION file instead of
  hardcoded 0.1.0.

VERSION 0.7.6 -> 0.7.7 (patch: docs + tooling only).
2026-07-17 22:04:54 +02:00

4.7 KiB

2026-07-14 — Unified agent activity indicator

Status: Planned

Current state — three separate indicators

Component Location Shows
Loading dots (Chat.svelte:146) Inline in assistant bubble 3 bouncing dots when no text/tools yet
ToolCallGroup trigger row Inline in assistant bubble "12 tools" with spinner
Activity bar Bottom of message list "Agent is responding…" / "Working" / Continue button

All three overlap. The operator sees dots → then a tool count → then the activity bar — three different visual styles for the same thing: "the agent is working."

Target: single indicator appended to conversation

One row, always the last item in the message list, that replaces the loading dots, ToolCallGroup summary, and activity bar. Think of it like a system message appended at the end of the conversation.

Behavior

User: "audit fleet"
Assistant: "I'll check all hosts. Here's the plan..."  ← full message bubble

┌─ Agent is auditing… ───────────────────┐
│ ◉ apt update on lxc:jellyfin          │  ← spinner + current action
└────────────────────────────────────────┘

... agent finishes ...

Assistant: "Done. 19 LXCs have pending updates."  ← next message bubble

The indicator:

  • Appears when the agent starts working (first tool_use event or streaming=true)
  • Updates its description with the current tool name in flight
  • Collapses/disappears when the turn ends (done event or streaming=false)
  • If there were tools, shows a brief completion summary for 3 seconds then fades
  • During auto-continuation (polling picks up new messages), reappears if the agent did tool calls

States

State Icon Description
Thinking ◉ pulse "Agent is thinking…"
Planning ◉ pulse "Building plan…"
Researching ◉ pulse "Researching …"
Executing ◉ spinner "<tool_name> …"
Done Fades out after 3s

Data source

The description comes from the most recent tool_use event's name + args. If no tools yet, show generic "thinking" message. The derived toolTimeline store already has this data.

Implementation

1. New component: AgentIndicator.svelte

Props: active: boolean, lastTool: ToolCallResult | null, toolCount: number

Renders a single compact row:

<div class="activity-indicator">
  <LoaderCircle class="animate-spin" /> <!-- or CheckIcon when done -->
  <span>{label}</span>
</div>

label is derived:

const label = $derived.by(() => {
  if (!active) return ''
  if (!lastTool) return 'Agent is thinking…'
  const args = lastTool.args ?? {}
  switch (lastTool.name) {
    case 'set_goal': return 'Setting goal…'
    case 'propose_plan': return 'Building plan…'
    case 'search_knowledge': return `Researching: ${args.query ?? ''}`
    case 'get_entity': return `Looking up ${args.slug_or_id ?? ''}`
    case 'run': return `${args.purpose ?? 'Running command…'}`
    case 'list_lxcs': return 'Listing containers…'
    case 'update_plan_step': return 'Updating progress…'
    case 'upsert_knowledge': return 'Recording knowledge…'
    case 'complete_task': return 'Wrapping up…'
    default: return `${lastTool.name}…`
  }
})

2. Chat.svelte changes

  • Remove activity bar from bottom of messages
  • Replace the 3 bouncing dots {#if msg.tools.length === 0} with nothing (the indicator covers this)
  • Add <AgentIndicator> after the {#each} loop, before messagesEnd
  • The indicator shows when $streaming || liveStatus === 'executing'
  • Pass lastTool from $toolTimeline — the last tool_use entry

3. Remove activity bar code

Delete the {#if $currentSession && $messages.length > 0} block at the bottom (already moved once, now deleted entirely — replaced by AgentIndicator).

4. Remove loading dots

In Chat.svelte, remove the 3 bouncing dots block:

{:else if msg.tools.length === 0}
  <div class="flex items-center gap-1.5 py-1 text-sm text-muted-foreground">
    <span class="size-1.5 animate-bounce rounded-full bg-current ...">...</span>
  </div>

Verification

  • Send "status" → indicator appears "Agent is thinking…" → agent responds → indicator fades
  • Send "check updates on jellyfin" → indicator shows "Researching…" → "Listing containers…" → "Running apt list…" → fades
  • Auto-continuation fires → indicator reappears with current tool → fades when done
  • Scroll up during agent work → indicator stays at bottom of message list (it's just a message)
  • Error during agent work → indicator shows "Error: …" with X icon