Files
oikos/plans/2026-07-09-session-execution-and-ux-fixes.md
dtoro ea62d744ed
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled
feat: pct_create action, ToolCallGroup collapse+animation, inline chat approval
- Add pct_create to request_execution (MCP) and executeApprovedAction (httpapi)
  Parses JSON config: vmid, hostname, cores, memory, disk_gb, ip, gw, storage,
  template, privileged, nesting, mounts, nameserver, searchdomain. Creates
  entity (state=provisioning), hosts relationship, entity_status on success.
  Fixes action string parsing to use Index instead of SplitN (colons in JSON).

- Rewrite ToolCallGroup.svelte: bits-ui Collapsible replaces native <details>.
  Collapsed by default. Animated header shows live tool count + running tool
  name while streaming. Auto-expands during streaming, auto-collapses on done.

- Add InlineApproval component: parses 'execution UUID queued' from agent
  response, renders Approve/Deny buttons inline in chat, calls decideApproval.

- Document pct_create in nomos/SOUL.md with params, risk class, and approval flow.

- Add session-review skill at .agents/skills/session-review/SKILL.md.

- Add plan: 2026-07-09-session-execution-and-ux-fixes.md.
2026-07-09 11:15:28 +02:00

157 lines
7.9 KiB
Markdown

# 2026-07-09 — Session execution, UX, and learning improvements
**Status:** Planned
## Goal
Fix the hard blocker and UX issues found in the latest production Nomos session
(`b9c5de7c-e0b5-424c-9149-9fd45b2e7011` — "deploy TypeType as an LXC on strong").
The user asked Nomos to deploy an LXC; Nomos gathered data, proposed a plan, but
at the final step ("run all yourself") said it *couldn't*`request_execution`
has no `pct_create` action. The user's objective was not achieved.
## Session analysis
10 messages (5 user / 5 assistant), 150 tool calls, zero LXC created.
| Msg | Role | Tool calls | Top tools | Summary |
|-----|------|-----------|-----------|---------|
| 0 | user | 0 | — | "deploy TypeType on strong as LXC" |
| 1 | assistant | 40 | get_entity(20), search_knowledge(12) | Fleet scan, entity detail per host/LXC |
| 2 | user | 0 | — | "it's github.com/Priveetee/TypeType, use tube.hubris.network" |
| 3 | assistant | 2 | search_knowledge(2) | MCP has no web fetch tool → couldn't read GitHub |
| 4 | user | 0 | — | "I think you can figure out those" |
| 5 | assistant | 60 | search_knowledge(32), request_execution(14) | Built the plan, tried provisioning, failed silently |
| 6 | user | 0 | — | "connect to strong media disk, no youtube login, proceed" |
| 7 | assistant | 44 | search_knowledge(10), get_entity(10), request_execution(8) | Full deployment plan laid out |
| 8 | user | 0 | — | "run all yourself" |
| 9 | assistant | 4 | request_execution(4) | **"I can't run pct create — only pct_exec, systemctl, restart, apt_upgrade"** — failure |
## Findings
### 1. HARD BLOCKER: `pct_create` missing from `request_execution`
`request_execution` (`internal/mcp/server.go:264-376`) supports `restart`,
`systemctl`, `pct_exec`, `apt_upgrade`. The actuator has `ProvisionLXC()`
(`internal/actuator/actuator.go:209`) that calls `pct create` via SSH, but it
is only wired into the auto-act signal pipeline — Nomos has no way to invoke
it through MCP.
Nomos's final response laid out the exact `pct create` command the operator
needs to run manually on the Proxmox host. That's a dead end for the user, who
expected the agent to execute from chat.
**Fix:**
- Add `pct_create` action to `request_execution` handler.
- Wire it to the existing `ProvisionLXC()` function.
- Classification: `config_mutation` — requires operator approval. Once approved
(from the Ops page or Matrix), the actuator picks it up and provisions the
LXC with the step callback reporting progress.
- Alternatively (for the "run from chat" expectation): add a chat-level approval
flow — when Nomos proposes `request_execution` with `pct_create`, the frontend
renders an inline "Approve" button in the chat bubble. Operator clicks → it runs.
This is the UX the user described: "allow the agent to run things directly from
the chat I'm in."
### 2. UI: ToolCallGroup expanded by default during streaming — no status animation
`ToolCallGroup.svelte` uses `<details open>` when `active=true`. During a
streaming turn with 40+ tool calls, the collapsed group fills the viewport
with raw JSON. The summary header shows only a static icon and "N tools" text.
**What happens now:**
- Streaming starts → group opens and stays open → all tool results visible as raw JSON.
- When streaming ends → auto-collapses. No animation.
- Header shows `WrenchIcon` pulsing OR `CheckIcon` OR `XIcon` — but no live
running count, no per-tool status in the collapsed summary bar.
**What should happen:**
- Group starts **collapsed** by default. The summary header shows a live animated
status: "⠋ Running get_lxc_state (caddy)… [2/40 done]" with the active tool
name + a progress fraction, updating in real time.
- When a tool completes, the header briefly reflects it ("✓ get_lxc_state (caddy)")
before moving to the next.
- Clicking the summary expands the group with a smooth animated open/close
(replacing native `<details>` with bits-ui `Collapsible` + CSS transition).
- On load from history (not streaming), always starts collapsed.
**Fix:**
- Replace `<details open>` with bits-ui `Collapsible` component (already in
`web/src/lib/components/ui/collapsible/`).
- Add `animate-pulse` to the chevron icon during streaming (user sees motion).
- Add a `statusText` derived that shows the in-progress tool name + count.
- CSS animation: `Collapsible.Content` supports `forceMount` with transitions.
### 3. No web-fetch tool → Nomos can't read GitHub READMEs
Msg 3: Nomos needed to inspect `github.com/Priveetee/TypeType` to understand the
stack. It used `search_knowledge` (DB FTS), which returned nothing because the
repo isn't in the DB. The agent had no way to fetch external URLs.
The MCP has 27 tools (21 listed in AGENTS.md + 6 more added since), but none
for HTTP/web fetching. Nomos can only query the DB or execute SSH commands on
existing hosts.
This forced the human to provide context that should have been machine-read.
**Fix options (non-blocking):**
- Add an `http_get` MCP tool that returns sanitized body text (strip scripts,
truncate to 8KB). Rate-limited per-turn.
- Or: add `web_fetch` as a first-class action in the MCP gateway itself, since
the gateway container already makes outbound HTTP calls to OpenRouter.
### 4. Task: Bulk-tool awareness already in SOUL.md but not enough
The SOUL.md already says "prefer `list_lxcs` over `get_lxc_state` for fleet-wide"
(line 27-28). But msg 1 still made 40 calls. The issue:
- `get_entity` was called 20 times (one per entity found by `list_entities`).
`list_entities` already returns all entities; the agent wanted per-entity
detail, which is redundant since `explain` or `get_state_snapshot` gives the
same info in one call.
**Fix (already planned in `2026-07-09-chat-sessions-improvements.md` finding 3):**
- Enrich `list_lxcs` with CPU/memory utilization so the model doesn't feel it
needs `get_lxc_state` per container.
- Add `get_tools_summary` to SOUL.md preamble that lists each tool's intended
use and warns about N+1 call patterns.
### 5. Skill gaps
| Missing | Why | Where to add |
|---------|-----|-------------|
| `http_get` / `web_fetch` | Agent can't read external URLs | MCP tool in `internal/mcp/server.go` |
| `session-review` skill | No way to learn from failed sessions | `.agents/skills/session-review/SKILL.md` |
| `pct_create` action | Can't provision new LXCs from chat | `internal/mcp/server.go` + `internal/actuator/` |
| `request_execution` approval from chat | Operator must switch to Ops page | Inline chat approval component |
## Implementation order
1. **Add `pct_create` to `request_execution`** (`internal/mcp/server.go`) —
hard blocker, needed for the session's objective.
2. **ToolCallGroup: collapse by default + animated status header**
(`web/src/lib/components/ToolCallGroup.svelte`) — immediate UX win, the user
explicitly asked for this.
3. **Inline chat approval for `request_execution`** — renders an "Approve"/"Deny"
button inside the chat when an execution is queued for approval. Lets the
operator approve from the same chat.
4. **`session-review` local skill** — lives in `.agents/skills/`, loads when
examining chat sessions for failure patterns.
5. **`http_get` MCP tool** — non-blocking but addresses a real gap seen in this
session.
## Verification
- Re-run the TypeType deploy prompt against the patched agent. Confirm:
- Agent proposes plan as before (keep plan-proposal behavior).
- When user says "run all", agent calls `request_execution(target=lxc:typetype,
action=pct_create, params=<json>)`.
- Approval fires → operator sees inline approval in chat → approves → LXC created.
- ToolCallGroup: start a session that triggers 5+ tool calls. Confirm:
- Group starts collapsed.
- Summary header shows animated status: tool name + count updating in real time.
- Clicking expands smoothly.
- On session reload (history), stays collapsed.
- Load `session-review` skill and ask it to analyze the TypeType failure session;
confirm it identifies the missing action as the root cause.