Move 2026-07-12-wails-desktop-app plan to done/
Phase 0 deployed 2026-07-12. Phases 1.0–1.4 implemented 2026-07-13. Plan complete.
This commit is contained in:
378
plans/2026-07-13-mcp-tool-apps-custom-chat-renderers.md
Normal file
378
plans/2026-07-13-mcp-tool-apps-custom-chat-renderers.md
Normal file
@@ -0,0 +1,378 @@
|
|||||||
|
# 2026-07-13 — MCP tool apps: custom in-chat renderers
|
||||||
|
|
||||||
|
**Status:** Planned
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Today, every MCP tool call renders identically — a collapsed JSON box in
|
||||||
|
`ToolCallGroup.svelte` with raw `{args, result}` dumps. The operator has to
|
||||||
|
expand and read JSON to understand what the agent did or found. This plan
|
||||||
|
introduces **tool renderers** — per-tool Svelte components that render rich,
|
||||||
|
purpose-built UI inline in the conversation, while unmatched tools stay
|
||||||
|
collapsed.
|
||||||
|
|
||||||
|
Concrete examples: `get_entity("host:hubris")` renders as a compact entity card
|
||||||
|
with type badge, health dot, and key attributes — not as 30 lines of JSON.
|
||||||
|
`get_health_summary` renders as colored health bars. `list_lxcs` renders as a
|
||||||
|
sortable table. The operator reads the chat, not raw tool output.
|
||||||
|
|
||||||
|
## UX principle: inline by default when matched
|
||||||
|
|
||||||
|
Today:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────┐
|
||||||
|
│ 🔧 3 tools: get_entity, │ ← collapsed
|
||||||
|
│ get_health_summary, ... │
|
||||||
|
│ ┌───────────────────────┐ │
|
||||||
|
│ │ {"slug":"host:hubris",│ │ ← raw JSON when expanded
|
||||||
|
│ │ "type":"host",...} │ │
|
||||||
|
│ └───────────────────────┘ │
|
||||||
|
└─────────────────────────────┘
|
||||||
|
The fleet is healthy. 42 hosts...
|
||||||
|
```
|
||||||
|
|
||||||
|
Target:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────┐
|
||||||
|
│ ● host:hubris [host] 🟢 │ ← entity card, inline
|
||||||
|
│ healthy · 2 min ago │
|
||||||
|
│ IP: 10.13.13.1 │
|
||||||
|
└─────────────────────────────┘
|
||||||
|
┌─────────────────────────────┐
|
||||||
|
│ ████████████ healthy 42 │ ← health bar, inline
|
||||||
|
│ ███ degraded 3 │
|
||||||
|
│ ██ down 1 │
|
||||||
|
└─────────────────────────────┘
|
||||||
|
┌─────────────────────────────┐
|
||||||
|
│ 🔧 1 tool: list_entities │ ← unmatched → still collapsed
|
||||||
|
└─────────────────────────────┘
|
||||||
|
The fleet is healthy...
|
||||||
|
```
|
||||||
|
|
||||||
|
Tools with a custom renderer appear **inline** as their own card in the message
|
||||||
|
flow, between the user bubble and the assistant's markdown text. Tools without
|
||||||
|
a renderer stay in the collapsed `ToolCallGroup`. This naturally separates
|
||||||
|
"rich, informative" tools from "utility/plumbing" tools.
|
||||||
|
|
||||||
|
### During streaming (tool in progress)
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────┐
|
||||||
|
│ ◌ host:hubris │ ← skeleton while tool_result
|
||||||
|
│ [host] ⠋ loading… │ hasn't arrived yet
|
||||||
|
└─────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
Skeleton/spinner state shown while `type === 'tool_use'`; full card on `tool_result`.
|
||||||
|
The per-tool card has the same chrome as ToolCallGroup: spinner → check/X based
|
||||||
|
on completion and error state.
|
||||||
|
|
||||||
|
### Error state
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────┐
|
||||||
|
│ ✕ get_entity │
|
||||||
|
│ Entity not found: "bad" │
|
||||||
|
└─────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Layer 1 — Server annotation (`internal/mcp/server.go`)
|
||||||
|
|
||||||
|
Each tool handler that warrants a custom renderer adds a `__renderer` key to
|
||||||
|
its result JSON. This is a plain string field — no protocol changes, no new
|
||||||
|
content types.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Before:
|
||||||
|
return queryEntity(ctx, pool, slug), nil
|
||||||
|
|
||||||
|
// After:
|
||||||
|
r := queryEntity(ctx, pool, slug)
|
||||||
|
r["__renderer"] = "entity_card"
|
||||||
|
return jsonResult(r), nil
|
||||||
|
```
|
||||||
|
|
||||||
|
Renderer IDs by tool:
|
||||||
|
|
||||||
|
| Tool | `__renderer` | Component |
|
||||||
|
|------|-------------|-----------|
|
||||||
|
| `get_entity` | `entity_card` | EntityCard.svelte |
|
||||||
|
| `whoami` | `entity_card` | EntityCard.svelte |
|
||||||
|
| `explain` | `entity_card` | EntityCard.svelte |
|
||||||
|
| `get_health_summary` | `health_summary` | HealthSummary.svelte |
|
||||||
|
| `list_lxcs` | `lxc_list` | LXCList.svelte |
|
||||||
|
| `list_entities` | `entity_table` | EntityTable.svelte |
|
||||||
|
| `search_knowledge` | `knowledge_results` | KnowledgeResults.svelte |
|
||||||
|
| `get_entity_knowledge` | `knowledge_results` | KnowledgeResults.svelte |
|
||||||
|
| `query_metrics` | `metric_chart` | MetricChart.svelte |
|
||||||
|
| `get_blast_radius` | `blast_radius` | BlastRadius.svelte |
|
||||||
|
| `get_change_history` | `change_log` | ChangeLog.svelte |
|
||||||
|
| `get_agent_activity` | `activity_log` | (reuses ChangeLog.svelte) |
|
||||||
|
| `get_state_snapshot` | `fleet_snapshot` | FleetSnapshot.svelte |
|
||||||
|
|
||||||
|
Non-rendered tools (stay collapsed): `get_relations`, `list_my_secrets`,
|
||||||
|
`get_service_status`, `tail_log`, `get_lxc_state`, `ping_service`,
|
||||||
|
`get_patterns`, `get_skills`, `http_get`, `preflight`, `get_signal_history`,
|
||||||
|
`get_execution_status`, `get_audit_trail`, `get_trend`,
|
||||||
|
`get_event_timeline`, `upsert_knowledge`, `update_entity_attributes`,
|
||||||
|
`create_relationship`, `run`, `request_execution`.
|
||||||
|
|
||||||
|
Gated execution tools (`run`, `request_execution`) already have
|
||||||
|
`InlineApproval.svelte` rendering in Chat.svelte — they stay in the collapsed
|
||||||
|
group so the approval card remains prominent at the message level.
|
||||||
|
|
||||||
|
### Layer 2 — Renderer registry (`web/src/lib/tool-renderers.ts`)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import type { ComponentType, SvelteComponent } from 'svelte'
|
||||||
|
import type { ToolCallResult } from '$lib/stores/chat'
|
||||||
|
|
||||||
|
export interface ToolRenderer {
|
||||||
|
match: (tool: ToolCallResult) => boolean
|
||||||
|
component: ComponentType<{ tool: ToolCallResult }>
|
||||||
|
}
|
||||||
|
|
||||||
|
const registry: ToolRenderer[] = []
|
||||||
|
|
||||||
|
export function registerToolRenderer(r: ToolRenderer) {
|
||||||
|
registry.push(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getToolRenderer(tool: ToolCallResult): ToolRenderer | undefined {
|
||||||
|
return registry.find(r => r.match(tool))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Match strategy: check `tool.name` against known tool names AND check
|
||||||
|
`tool.result?.__renderer` if the result is an object. The tool-name path
|
||||||
|
handles the streaming case (tool_use arrives before tool_result); the
|
||||||
|
`__renderer` path handles cases where the same tool can return different
|
||||||
|
shapes (not applicable today but future-proof).
|
||||||
|
|
||||||
|
```ts
|
||||||
|
function hasRenderer(tool: ToolCallResult, id: string): boolean {
|
||||||
|
if (tool.name === id) return true
|
||||||
|
if (tool.result && typeof tool.result === 'object' && tool.result.__renderer === id) return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Layer 3 — Auto-registration via Vite glob (`web/src/lib/renderers/index.ts`)
|
||||||
|
|
||||||
|
Each renderer file exports a Svelte component and an `init()` call:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// web/src/lib/renderers/entity-card.ts
|
||||||
|
import EntityCard from './EntityCard.svelte'
|
||||||
|
import { registerToolRenderer } from '$lib/tool-renderers'
|
||||||
|
|
||||||
|
export function init() {
|
||||||
|
registerToolRenderer({
|
||||||
|
match: (t) => hasRenderer(t, 'entity_card'),
|
||||||
|
component: EntityCard
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`web/src/lib/renderers/index.ts` imports and calls `init()` for every renderer
|
||||||
|
module. In `web/src/main.ts`, a single `import './lib/renderers'` wires
|
||||||
|
everything. Adding a new renderer means: create `Foo.svelte` + `foo.ts` with
|
||||||
|
`init()`, add the import to `index.ts`. No changes to ToolCallGroup or Chat.
|
||||||
|
|
||||||
|
### Layer 4 — ToolCallGroup split (`web/src/lib/components/ToolCallGroup.svelte`)
|
||||||
|
|
||||||
|
`ToolCallGroup` today receives `tools: ToolCallResult[]` and renders all of them.
|
||||||
|
Change: add an exported `unmatched` derived prop, and move the per-tool
|
||||||
|
rendering to Chat.svelte so inline cards appear in the message flow.
|
||||||
|
|
||||||
|
**New ToolCallGroup props:**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
let { tools, unmatched, active = false }: {
|
||||||
|
tools: ToolCallResult[]
|
||||||
|
unmatched: ToolCallResult[]
|
||||||
|
active?: boolean
|
||||||
|
} = $props()
|
||||||
|
```
|
||||||
|
|
||||||
|
`tools` is the full list (for the summary: "3 tools"); `unmatched` is the
|
||||||
|
subset without renderers (for the collapsed body). The summary bar says
|
||||||
|
"1 tool" if only one unmatched tool exists, or "1 tool + 2 cards" to
|
||||||
|
acknowledge the inline ones.
|
||||||
|
|
||||||
|
**Chat.svelte changes (lines 114-133):**
|
||||||
|
|
||||||
|
```svelte
|
||||||
|
{:else}
|
||||||
|
<div class="flex w-full flex-col gap-2">
|
||||||
|
<!-- Inline tool cards (matched renderers) -->
|
||||||
|
{#each msg.tools as tool (tool.id)}
|
||||||
|
{@const renderer = getToolRenderer(tool)}
|
||||||
|
{#if renderer}
|
||||||
|
<renderer.component {tool} />
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
<!-- Unmatched tools → collapsed group -->
|
||||||
|
<ToolCallGroup
|
||||||
|
tools={msg.tools}
|
||||||
|
unmatched={msg.tools.filter(t => !getToolRenderer(t))}
|
||||||
|
active={$streaming && i === $messages.length - 1}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Text + approvals (unchanged) -->
|
||||||
|
...
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Layer 5 — Renderer component contract
|
||||||
|
|
||||||
|
Every renderer component receives a single `tool: ToolCallResult` prop.
|
||||||
|
|
||||||
|
Component responsibilities:
|
||||||
|
- **Loading state** (`tool.type === 'tool_use'`): render a skeleton/spinner with
|
||||||
|
the tool name and relevant args
|
||||||
|
- **Success state** (`tool.type === 'tool_result' && !tool.error`): render the
|
||||||
|
rich card
|
||||||
|
- **Error state** (`tool.type === 'tool_result' && tool.error`): render error
|
||||||
|
with a compact message
|
||||||
|
- **No result** (tool completed but result is null/empty): render a minimal card
|
||||||
|
with just the tool name + checkmark
|
||||||
|
|
||||||
|
Self-contained card: a border, padding, and the same size feel as the
|
||||||
|
existing approval cards. Each card is independent — no shared state between
|
||||||
|
renderers.
|
||||||
|
|
||||||
|
## New components
|
||||||
|
|
||||||
|
| Component | Matches | Visual |
|
||||||
|
|-----------|---------|--------|
|
||||||
|
| `EntityCard` | `get_entity`, `whoami`, `explain` | Slug + type badge + health dot + state + key attrs (IP, version) + "last checked" relative time |
|
||||||
|
| `HealthSummary` | `get_health_summary` | Horizontal stacked bar: green=healthy, amber=degraded, red=down, with counts |
|
||||||
|
| `LXCList` | `list_lxcs` | Compact table: name, host, IP, health dot, status |
|
||||||
|
| `EntityTable` | `list_entities` | Sortable table (reuses EntityTable.svelte from KB page) |
|
||||||
|
| `KnowledgeResults` | `search_knowledge`, `get_entity_knowledge` | Result cards: title + excerpt + tags |
|
||||||
|
| `MetricChart` | `query_metrics` | Sparkline or small bar chart of bucketed values |
|
||||||
|
| `BlastRadius` | `get_blast_radius` | Entity list grouped by distance (direct → 1 hop → 2 hops) |
|
||||||
|
| `ChangeLog` | `get_change_history`, `get_agent_activity` | Timeline of recent entries with timestamps |
|
||||||
|
| `FleetSnapshot` | `get_state_snapshot` | Summary grid: healthy/degraded/down counts + drift count |
|
||||||
|
|
||||||
|
Some already exist partially: `EntityTable.svelte` and `EntityGraph` are used
|
||||||
|
on the KB page — the renderer can wrap or reuse them.
|
||||||
|
|
||||||
|
## File changes
|
||||||
|
|
||||||
|
| File | Change |
|
||||||
|
|------|--------|
|
||||||
|
| `internal/mcp/server.go` | Add `__renderer` to result JSON for ~12 tools |
|
||||||
|
| `web/src/lib/tool-renderers.ts` | New — registry + match helpers |
|
||||||
|
| `web/src/lib/renderers/index.ts` | New — imports all renderer init modules |
|
||||||
|
| `web/src/lib/renderers/entity-card.ts` | New — register `EntityCard` |
|
||||||
|
| `web/src/lib/renderers/EntityCard.svelte` | New — entity card component |
|
||||||
|
| `web/src/lib/renderers/health-summary.ts` | New — register `HealthSummary` |
|
||||||
|
| `web/src/lib/renderers/HealthSummary.svelte` | New — health bar component |
|
||||||
|
| `web/src/lib/renderers/lxc-list.ts` | New — register `LXCList` |
|
||||||
|
| `web/src/lib/renderers/LXCList.svelte` | New — LXC table component |
|
||||||
|
| `web/src/lib/renderers/knowledge-results.ts` | New — register `KnowledgeResults` |
|
||||||
|
| `web/src/lib/renderers/KnowledgeResults.svelte` | New — search results component |
|
||||||
|
| `web/src/lib/renderers/metric-chart.ts` | New — register `MetricChart` |
|
||||||
|
| `web/src/lib/renderers/MetricChart.svelte` | New — sparkline component |
|
||||||
|
| `web/src/lib/renderers/blast-radius.ts` | New — register `BlastRadius` |
|
||||||
|
| `web/src/lib/renderers/BlastRadius.svelte` | New — blast radius component |
|
||||||
|
| `web/src/lib/renderers/change-log.ts` | New — register `ChangeLog` |
|
||||||
|
| `web/src/lib/renderers/ChangeLog.svelte` | New — timeline component |
|
||||||
|
| `web/src/lib/renderers/fleet-snapshot.ts` | New — register `FleetSnapshot` |
|
||||||
|
| `web/src/lib/renderers/FleetSnapshot.svelte` | New — snapshot grid component |
|
||||||
|
| `web/src/lib/components/ToolCallGroup.svelte` | Add `unmatched` prop; render only unmatched tools in body; adjust summary text |
|
||||||
|
| `web/src/pages/Chat.svelte` | Filter matched tools to inline cards; pass unmatched to ToolCallGroup |
|
||||||
|
| `web/src/main.ts` | Add `import './lib/renderers'` |
|
||||||
|
|
||||||
|
## Phases
|
||||||
|
|
||||||
|
### Phase 1 — Infra + first renderer
|
||||||
|
|
||||||
|
1. Create `tool-renderers.ts` registry
|
||||||
|
2. Create renderer directory + index
|
||||||
|
3. Modify `ToolCallGroup.svelte` to accept `unmatched` prop
|
||||||
|
4. Modify `Chat.svelte` to dispatch inline cards
|
||||||
|
5. Add `__renderer` to `get_entity`, `whoami`, `explain` in server.go
|
||||||
|
6. Create `EntityCard.svelte` — the flagship renderer
|
||||||
|
7. Test: ask Nomos "tell me about host:hubris"
|
||||||
|
8. Verify: entity card renders inline, unrelated tools stay collapsed
|
||||||
|
|
||||||
|
### Phase 2 — Remaining renderers
|
||||||
|
|
||||||
|
Add in priority order:
|
||||||
|
1. `HealthSummary` + `get_health_summary` annotation
|
||||||
|
2. `LXCList` + `list_lxcs` annotation
|
||||||
|
3. `KnowledgeResults` + `search_knowledge` / `get_entity_knowledge` annotation
|
||||||
|
4. `BlastRadius` + `get_blast_radius` annotation
|
||||||
|
5. `EntityTable` + `list_entities` annotation
|
||||||
|
6. `ChangeLog` + `get_change_history` / `get_agent_activity` annotation
|
||||||
|
7. `FleetSnapshot` + `get_state_snapshot` annotation
|
||||||
|
8. `MetricChart` + `query_metrics` annotation
|
||||||
|
|
||||||
|
### Phase 3 — Polish
|
||||||
|
|
||||||
|
1. Smooth streaming: renderer shows skeleton on `tool_use`, renders on `tool_result`
|
||||||
|
2. Error states: renderer shows compact error card (not raw JSON)
|
||||||
|
3. Mobile: renderers stack full-width on narrow viewports
|
||||||
|
4. Accessibility: renderer cards have proper ARIA labels
|
||||||
|
|
||||||
|
## Risks / open questions
|
||||||
|
|
||||||
|
1. **Tool name vs `__renderer` mismatch**: If the server adds `__renderer` but
|
||||||
|
the frontend hasn't deployed yet, the field is silently ignored and the
|
||||||
|
tool falls back to the collapsed group. No breakage. Vice versa (frontend
|
||||||
|
expects a renderer that the server doesn't emit): match falls through to
|
||||||
|
collapsed group. Graceful degradation in both directions.
|
||||||
|
|
||||||
|
2. **Streaming jank**: During a turn, tool_use events arrive before tool_result.
|
||||||
|
The renderer sees `type: 'tool_use'` initially (shows skeleton), then Svelte
|
||||||
|
reactivity updates when the store merges `tool_result`. The card transitions
|
||||||
|
from skeleton → rich. This is the same model as ToolCallGroup's
|
||||||
|
spinner → check transition. No new complexity.
|
||||||
|
|
||||||
|
3. **Multiple tools of same type**: A single agent turn might call `get_entity`
|
||||||
|
twice. Each gets its own inline card — natural, no dedup needed.
|
||||||
|
|
||||||
|
4. **Card explosion**: If the agent calls 15 tools in one turn and 12 of them
|
||||||
|
match renderers, the chat gets 12 inline cards before any text. This could
|
||||||
|
be noisy if the agent is chatty. Mitigations: (a) renderers are compact
|
||||||
|
(3-4 lines), (b) the collapsed group still exists for unmatched tools,
|
||||||
|
(c) we can add a per-turn limit later ("show top 3, collapse rest") but
|
||||||
|
start simple. The agent in practice calls 3-6 tools per turn; 12 is
|
||||||
|
an edge case.
|
||||||
|
|
||||||
|
5. **Security**: Renderers receive tool result data that already passed through
|
||||||
|
the SSE stream (authenticated). No new attack surface. Renderers render
|
||||||
|
data, not HTML — Svelte's auto-escaping handles XSS. No `{@html}` in any
|
||||||
|
renderer unless explicitly sanitized.
|
||||||
|
|
||||||
|
6. **Persistence**: Tool results are already persisted in the DB (via Nomos's
|
||||||
|
incremental message persistence). On session reload,
|
||||||
|
`mergeToolCalls()` → `toChatMessages()` reconstructs `ToolCallResult[]`
|
||||||
|
including `result`. The `__renderer` field survives the roundtrip because
|
||||||
|
it's part of the result JSON, which is stored as-is. No schema change.
|
||||||
|
**Verified**: `cmd/nomos/continue.go` marshals tool results to JSONB
|
||||||
|
without filtering fields — `__renderer` passes through transparently.
|
||||||
|
|
||||||
|
7. **The gated-execution tools** (`run`, `request_execution`): These already
|
||||||
|
surface as `InlineApproval` cards at the message level via
|
||||||
|
`extractApprovals()` in `chat.ts`. They deliberately stay in the collapsed
|
||||||
|
group — don't add a renderer for them. The approval card is the rich UI.
|
||||||
|
|
||||||
|
## Cost / effort
|
||||||
|
|
||||||
|
| Area | Estimate |
|
||||||
|
|------|----------|
|
||||||
|
| Server annotations | ~20 lines across ~12 tools |
|
||||||
|
| Registry + infra | ~50 lines (tool-renderers.ts + Chat.svelte + ToolCallGroup changes) |
|
||||||
|
| First renderer (EntityCard) | ~60 lines |
|
||||||
|
| Remaining 8 renderers | ~40-80 lines each |
|
||||||
|
| **Total** | ~500 lines, 1-2 sessions |
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
# 2026-07-12 — Wails desktop application
|
# 2026-07-12 — Wails desktop application
|
||||||
|
|
||||||
**Status:** In Progress — Phase 0 (0.1-0.4, 0.6) done, verified live in a
|
**Status:** Done — Phases 0.0–0.6 deployed to production (mac-mini, commit
|
||||||
local browser test, and **deployed to production** (mac-mini, commit
|
`0c0f35a`, 2026-07-12). Phases 1.0–1.4 implemented (commit `5d6d9e9`,
|
||||||
`0c0f35a`, 2026-07-12). Phase 1 (Wails shell) not started.
|
2026-07-13) — pushed to main.
|
||||||
|
|
||||||
**Production deploy (2026-07-12):** merged to `main`, picked up by the
|
**Production deploy (2026-07-12):** merged to `main`, picked up by the
|
||||||
2-minute deploy poller (`scripts/deploy.sh`: pg_dump backup → rebuild →
|
2-minute deploy poller (`scripts/deploy.sh`: pg_dump backup → rebuild →
|
||||||
@@ -14,7 +14,7 @@ went sideways, open an investigation.
|
|||||||
| 2026-07-08 | [Liveness, drift, and UX cohesion](2026-07-08-liveness-drift-and-ux-cohesion.md) | In Progress — Phase 5 deferred |
|
| 2026-07-08 | [Liveness, drift, and UX cohesion](2026-07-08-liveness-drift-and-ux-cohesion.md) | In Progress — Phase 5 deferred |
|
||||||
| 2026-07-10 | [General gated execution: unlimited actions, gated by risk](2026-07-10-general-gated-execution.md) | In Progress — enum retirement + auto-act revival still open |
|
| 2026-07-10 | [General gated execution: unlimited actions, gated by risk](2026-07-10-general-gated-execution.md) | In Progress — enum retirement + auto-act revival still open |
|
||||||
| 2026-07-11 | [Nomos agent code review: gaps and improvement plan](2026-07-11-nomos-agent-code-review.md) | In Progress — only C1 (unauthenticated nomos gateway) still open, deferred |
|
| 2026-07-11 | [Nomos agent code review: gaps and improvement plan](2026-07-11-nomos-agent-code-review.md) | In Progress — only C1 (unauthenticated nomos gateway) still open, deferred |
|
||||||
| 2026-07-12 | [Wails desktop application](2026-07-12-wails-desktop-app.md) | In Progress — Phase 0 done and deployed to production (2026-07-12), Phase 1 not started |
|
| 2026-07-11 | [Nomos agent code review: gaps and improvement plan](2026-07-11-nomos-agent-code-review.md) | In Progress — only C1 (unauthenticated nomos gateway) still open, deferred |
|
||||||
|
|
||||||
## Done
|
## Done
|
||||||
|
|
||||||
@@ -44,6 +44,7 @@ See [`done/`](done/) for executed plans:
|
|||||||
| 2026-07-11 | [Concurrent task execution: safety + throughput + frontend correctness](done/2026-07-11-concurrent-task-execution.md) |
|
| 2026-07-11 | [Concurrent task execution: safety + throughput + frontend correctness](done/2026-07-11-concurrent-task-execution.md) |
|
||||||
| 2026-07-11 | [UI review: information architecture, usability, and best practices](done/2026-07-11-ui-review-ia-usability.md) |
|
| 2026-07-11 | [UI review: information architecture, usability, and best practices](done/2026-07-11-ui-review-ia-usability.md) |
|
||||||
| 2026-07-11 | [Task completion safety net: every live task is stuck "Running"](done/2026-07-11-task-completion-safety-net.md) |
|
| 2026-07-11 | [Task completion safety net: every live task is stuck "Running"](done/2026-07-11-task-completion-safety-net.md) |
|
||||||
|
| 2026-07-12 | [Wails desktop application](done/2026-07-12-wails-desktop-app.md) |
|
||||||
|
|
||||||
## Conventions
|
## Conventions
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user