Files
oikos/plans/2026-07-14-activity-gaps.md
dtoro 2ed169d239
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled
Desktop App / Build Linux (amd64) (push) Has been cancelled
Desktop App / Attach to Release (push) Has been cancelled
fix stuck 'Agent is thinking' + flip activity to old-to-new
- AgentIndicator now shows only during stream or when running tools exist
  (not on session status=executing which never cleared)
- Activity timeline: oldest-first ordering (reads top-to-bottom naturally)
- Removed unused liveStatus derivation and currentTask import from Chat
- Plan: Phase A+B+C for activity gaps + plan-approve-once policy
2026-07-14 12:56:42 +02:00

160 lines
7.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 2026-07-14 — Session review + Activity timeline refinements
**Status:** Planned
## Session analysis: `722d8878` (2026-07-14T10:45)
"Fleet-wide audit: check all services, identify what needs updating"
### What happened
- User asked for fleet audit → agent called `set_goal` (session → executing)
- 82 tool calls in ONE turn: 58 `run`, 8 `get_entity`, 4 `get_relations`, etc.
- **No `propose_plan`** — no plan steps, entered executing directly
- **No `complete_task`** — agent produced a final text response but never closed the task
- Session stuck at `executing` with a text conclusion but no terminal state
### Gaps found
| # | Issue | Root cause |
|---|---|---|
| 1 | "Agent is thinking" stuck at end | `liveStatus === 'executing'` is true even after the turn ends. AgentIndicator shows generic message because there are no running activity entries to describe. |
| 2 | No plan proposed | Model skipped `propose_plan` — possibly because it found prior knowledge and skipped to execution. `setGoal` now sets `executing` directly (our fix), which makes `propose_plan` optional — but the sidebar "Plan" section shows "No plan yet" permanently. |
| 3 | Task never completed | Model produced final text but never called `complete_task`. The idle sweep (2 min now) will nudge, then auto-close. |
| 4 | Approvals invisible in Activity | 58 `run` calls, many requiring approval. These show in chat via InlineApproval but NOT in the Activity timeline. The operator has to switch to Ops page to track approvals. |
| 5 | Activity is newest-first | Timeline shows newest at top. Feels unnatural for a sequential log — bottom-scrolling with newest at bottom is more intuitive for "watching" what the agent does. |
| 6 | Knowledge recorded not shown | The agent recorded knowledge but it doesn't appear in Activity if it came from prior knowledge entries via `get_knowledge_content`. |
---
## Plan
### 1. Fix "Agent is thinking" stuck indicator
**Root cause:** AgentIndicator shows when `active={$streaming || liveStatus === 'executing'}`. After the turn completes, `liveStatus` is still `'executing'` and there are no running entries, so the label falls through to the generic `"Agent is thinking…"` fallback.
**Fix:** Change the active condition to only show when there's actual work:
```ts
active={$streaming || $activityLog.some((e) => e.status === 'running')}
```
This way it shows during streaming AND when there are running tools (auto-continuation), but NOT when the session is just "executing" with no active work.
Also: when the last assistant message has text AND no pending tool calls, auto-hide the indicator. The `liveStatus === 'executing'` check is too broad — it covers the entire session lifetime.
### 2. Approvals in Activity timeline
**What:** InlineApproval cards show in chat but not in Activity. Every `run` that queues an execution with "requires approval" should appear as an entry in the Activity timeline.
**How:**
- In `activity.ts`, detect tool results containing "requires approval" + execution ID
- Add `type: 'approval_pending'` entries with the execution ID, target, action, and status
- Poll execution status and update the entry (pending → approved → running → completed/failed)
- The InlineApproval component stays in chat for the Approve/Deny buttons
- Activity shows the full lifecycle: approval requested → approved → running → done
### 3. Old-to-new ordering
**Fix:** Remove `.sort((a, b) => b.timestamp - a.timestamp)` → change to `.sort((a, b) => a.timestamp - b.timestamp)` or no sort at all (entries are already added in chronological order).
This means the timeline reads top-to-bottom as the session unfolds. Currently `newest at top` means the "Goal" and "Plan" entries appear at the bottom, which is confusing.
### 4. Knowledge detection
**Fix:** Extend the knowledge detection in `activity.ts` to also catch `upsert_knowledge` calls from `tool_use` events (not just `tool_result`), so the entry appears as "running" while recording and then "done" when the result comes back.
### 5. Auto-hide indicator when turn ends with text
**Fix:** Detect when the last assistant message has text content AND there are no pending tool_use entries without matching tool_result. In that case, the turn is complete — don't show the indicator.
---
## Plan-approve-once (new policy)
### Problem
Today: agent calls `propose_plan` + `run` × 10 in the same turn. Each `run`
queues an individual approval. Operator sees 10 "requires approval" cards.
After operator types "yes", each one is individually approved, THEN the
assent window opens and future calls auto-run.
The operator shouldn't see per-action approvals when they already approved
the plan. The plan IS the approval. Individual actions within an approved
plan should auto-execute.
### Target
```
User: "audit fleet"
Agent: "Here's my plan: 1. List LXCs 2. Check apt on each 3. Report" ← proposes plan
[Proposed plan: 3 steps] [Approve plan?]
User: "approved"
Agent: ◉ Listing containers… ← auto-runs
◉ Checking apt on lxc:jellyfin… ← auto-runs
...
"Done. 19 LXCs have pending updates."
```
One approval for the plan. All actions within it auto-execute. No per-action
approval cards. Only re-approve when the agent calls `propose_plan` again
(significant plan change).
### How (server-side)
The classification logic in `internal/mcp/server.go:run()` needs to know whether
a plan-approval-assent-window is active for this session. Currently it checks
`autonomy_settings` for the assent window key. The change: when `propose_plan`
is called, pre-activate the window with a "plan-proposed" state. When the
operator approves, transition to "plan-active". `run` calls within an active
plan window auto-execute at `config_mutation` level.
Key change in `store.go:proposePlan()`:
```go
// Pre-record a plan-proposed window so that run calls know a plan is pending approval.
// Once approved, this becomes the full assent window.
key := planWindowKey(agentID, sessionID)
s.pool.Exec(ctx, `INSERT INTO autonomy_settings (key, value) VALUES ($1, 'proposed')
ON CONFLICT (key) DO UPDATE SET value = 'proposed'`, key)
```
Then in the `run` handler, check for `plan-active` OR `assent-active`:
```go
// If a plan window is active, this run call is part of the approved plan
// and config_mutation commands auto-execute without individual approval.
if planWindowActive(ctx, agentID, sessionID) { ... }
```
### How (frontend)
- Instead of 10 individual InlineApproval cards, show ONE "Approve plan?" card
- When approved, all queued `run` calls from the plan turn auto-grant
- Activity timeline shows plan approval as one entry: "✓ Plan approved — 12 actions"
- Subsequent `run` calls show with an "auto (plan)" badge instead of approval cards
---
## Implementation plan (consolidated)
### Phase A — Quick fixes (today)
| # | Fix |
|---|---|
| A1 | AgentIndicator: only show when `$streaming || hasRunningActivity` (not on `liveStatus === 'executing'`) |
| A2 | Activity timeline: old-to-new ordering |
| A3 | Knowledge entries: detect `tool_use` events for running state |
### Phase B — Approvals in Activity
| # | Fix |
|---|---|
| B1 | `activityLog`: add `approval_pending` / `approval_granted` / `execution_running` / `execution_done` lifecycle entries |
| B2 | Poll execution status and update Activity entries inline |
| B3 | InlineApproval stays in chat (needs operator action), but lifecycle tracked in Activity |
### Phase C — Plan-approve-once (policy change)
| # | Fix |
|---|---|
| C1 | Backend: plan window in `autonomy_settings`, checked by `run` handler |
| C2 | Frontend: single "Approve plan?" card instead of per-action cards |
| C3 | Backend: when plan is approved, auto-grant all pending executions from the plan turn |
| C4 | Activity: plan approval as single timeline entry with action count badge |