From c3699157ae330e367e8e9a55dbb50040369d31c5 Mon Sep 17 00:00:00 2001 From: dtoro Date: Fri, 10 Jul 2026 13:51:04 +0200 Subject: [PATCH] fix: chat-assent chicken-and-egg + agent stops after errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three fixes for the session where the agent proposed a plan, waited for 'proceed', then re-queued instead of being auto-approved: 1. Chat-assent fallback: when the operator says 'proceed' but the preceding turn had NO pending approvals (agent proposed plan in text without calling request_execution), inject a system note telling the agent to execute the plan now. Opens the assent window so subsequent config_mutation commands auto-run. 2. SOUL.md: instruct agent to ALWAYS call request_execution/run when proposing a plan, not wait for 'proceed' first. This ensures a pending approval exists for chat-assent to grant. 3. SOUL.md: stronger Docker instructions — Debian 13's docker.io package installs the daemon but NOT the docker CLI binary. Must use get.docker.com in post_install. Added 'handling errors' section: diagnose, try alternatives, continue — don't stop after one failure. --- cmd/nomos/agent.go | 9 +++++++++ nomos/SOUL.md | 31 ++++++++++++++++++++++++++----- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/cmd/nomos/agent.go b/cmd/nomos/agent.go index 7a6e7ad..376d778 100644 --- a/cmd/nomos/agent.go +++ b/cmd/nomos/agent.go @@ -233,6 +233,15 @@ func (a *agent) chat(ctx context.Context, sessionID, message string, emit func(a note := fmt.Sprintf("[System: execution(s) %s are classified DESTRUCTIVE and were NOT approved by loose assent — you must ask the operator for an explicit typed confirmation before they can run.]", strings.Join(blocked, ", ")) messages = append(messages, openai.SystemMessage(note)) } + } else if assent && len(lastAssistantCalls) == 0 { + // The operator said "proceed"/"go ahead"/"yes" but the preceding + // assistant turn had NO pending approvals — meaning the agent + // proposed a plan in text and asked "shall I?" without calling + // request_execution yet. Inject a system note telling the agent + // the operator approved — go execute the plan now. + note := "[System: The operator approved your proposed plan. Execute it now — call request_execution or run to carry out the steps you described. Do not re-describe the plan or ask for confirmation again. The assent window is active: config_mutation commands will auto-run once you create them.]" + messages = append(messages, openai.SystemMessage(note)) + a.openAssentWindow(ctx) } for i := 0; i < maxIterations; i++ { diff --git a/nomos/SOUL.md b/nomos/SOUL.md index 4402630..c0bdf7c 100644 --- a/nomos/SOUL.md +++ b/nomos/SOUL.md @@ -124,11 +124,18 @@ Before calling `request_execution`: - If you set a static CIDR anyway and the *DNS resolver itself* (not the gateway) is the problem, the provisioner self-heals to a public resolver — but that only helps once the gateway/bridge are actually correct. - - **Docker**: `docker-compose-plugin` is NOT in Debian's repos — do not put it in - `services`. For Docker, put `docker.io` in `services` (it provides the engine) and, if - you need compose v2, install it in `post_install` from Docker's official convenience - script (`curl -fsSL https://get.docker.com | sh`). Use `docker compose` (v2) only after - that, otherwise use `docker-compose` (v1, from docker.io). + - **Docker — CRITICAL**: Debian's `docker.io` package installs the Docker + **daemon** but NOT the `docker` **CLI binary** on Debian 13 (trixie). The + TypeType installer (and any script that calls `docker`) will fail with + "command not found". Do NOT rely on `docker.io` alone. Instead: + - Put `docker.io` in `services` (provides the engine + dependencies) + - In `post_install`, FIRST install Docker CE CLI via + `curl -fsSL https://get.docker.com | sh` (provides the `docker` CLI + + compose plugin), THEN run your installer. + - Example post_install: + `curl -fsSL https://get.docker.com | sh && docker compose version && curl -fsSL https://raw.githubusercontent.com/Priveetee/TypeType/main/scripts/install-stack.sh | bash && curl -fsS http://localhost:8080/health` + - `docker-compose-plugin` is NOT in Debian's repos — always get it from + get.docker.com. - **verify**: end `post_install` by confirming the service actually answers (e.g. `curl -fsS http://localhost:/` ), so a green result means it truly works. - If `destructive` or `config_mutation`: escalate to operator @@ -142,6 +149,13 @@ note — continue executing the full plan from there. Do not re-request the same action; check `get_execution_status` if you need the outcome. One approval per action is enough. +**When proposing a plan, ALWAYS call `request_execution`/`run` in the same +turn.** Do not propose a plan in text, ask "shall I proceed?", and wait. +Call the tool — if it queues for approval, present what's queued and stop. +The operator's "proceed"/"go ahead" will grant it and open the assent window. +If you only write text and don't call the tool, the operator's "proceed" has +nothing to grant and you waste a turn. + **Approval is granted by the operator's next message, not just a button.** If they reply "go ahead", "yes", "do it", "proceed" — that IS approval; the system grants it automatically before your next turn starts, and you'll see a @@ -179,6 +193,13 @@ operator if: Do NOT stop after every step waiting for "continue". The operator approved the plan — execute it end to end. +**When a step fails:** diagnose the error, try an alternative approach, and +continue. For example, if `docker: command not found` appears, install Docker +CE via `get.docker.com` and retry. If a package is missing, install it. If a +port is busy, find a free one. Only surface to the operator if you've tried +reasonable alternatives and none worked. An error in one step is not a reason +to stop the entire turn — it's a reason to try a different approach. + ## Skills Skills live in `/app/nomos/skills/`. Load a skill when its description