Suggestions (caco suggest)

The suggest subsystem turns recent Cacophony activity and local node state into a set of LLM-generated, structured suggested operations that an operator can triage and optionally run. It is node-local: suggestions are generated from the local node's context and persisted per node, not replicated as an authoritative resource the way beads are.

Overview

Running caco suggest gathers a bounded, scope-aware snapshot of recent activity (nodes, projects, recent beads, chat, feed events, and the agent inventory plus mute state), asks the configured LLM for a structured set of suggested operations, persists the set locally, and opens an interactive picker so you can review and optionally run individual options.

Crucially, suggesting never runs anything. Generation only produces and persists suggestions to triage; executing an option is always a separate, explicit action.

Core invariants

  1. Suggesting never runs anything. Generating a set only produces and persists suggestions. Execution is a separate explicit user action.
  2. Run-once unless allow_multiple_runs. Each item runs at most once unless explicitly marked allow_multiple_runs. Enforcement is authoritative on the node that runs the option; cross-node convergence is best-effort.
  3. All surfaces read through the daemon /suggest* endpoints. The CLI picker, TUI, web, and companion surfaces use the daemon endpoints rather than a private path or direct filesystem access.
  4. Context scope and the action space are independent. A request's scope narrows what context is gathered (global, a project, a node); it never constrains what may be suggested or run. The action space stays global.
  5. Persistence + resume. Sets persist under <root>/suggest/<uuid>/ so they can be browsed and resumed without a new LLM call.

Suggestion item schema

Each suggestion item carries uuid (server-assigned), name (required), reason (required), type (caco_cli, bash, or caco_action), the type payload (args or script), allow_multiple_runs (default false), run state (run_count, last_run_at, last_run_status), and parse_valid.

parse_valid is annotated client-side for caco_cli / caco_action items (the daemon cannot call the CLI's validator without a dependency cycle); bash items are not-applicable. Invalid items are flagged, not dropped, and shown non-runnable. The top-level set carries uuid, scope, prompt, created_at, and the suggestions list.

Endpoints (node-local)

The generate endpoint is node-local: it does not forward-if-not-primary; it always serves locally. The run endpoint enforces a high-risk DENY backstop (mirroring the managed-worker PreToolUse deny list) before run-once, executes node-locally in a dedicated tmux session (tmux -L caco-suggest-<uuid>), captures logs under runs/<option_uuid>/, and returns 403 suggest_run_denied_high_risk for denied items and 409 suggest_run_already_run for run-once-exhausted items. A suggested command containing @othernode simply uses ordinary caco @node runtime forwarding at execution time; the suggest layer itself does not forward runs.

Cross-node sharing (best-effort, feed)

Generation stays node-local, but the generating node emits the set onto the replicated feed best-effort so peers persist it as they see it: a SuggestSetCreated feed event is replicated and receivers ingest and materialize the set into their local <root>/suggest/<uuid>/ layout (deduped by uuid, skipping self-originated events); a SuggestRun feed event is live-status only — receivers update displayed run-state and never execute on ingest; and authoritative run-state convergence rides the bounded full-state daemon sync (the most recent sets plus per-item run-state metadata, mirroring the bounded choice_history shape). Run logs are never replicated; they stay node-local and are read on (or via @) the running node. So a suggestion can be triggered on one node and resolved on any other node that received it, while logs remain where the run actually happened.

Configuration

The top-level suggest config section is config-only:

suggest:
  model: smart      # LLM alias resolved via llm.<alias> (default "smart")
  default_n: 5      # default number of suggestions to request

suggest.model references an alias in the shared llm.* map rather than redefining a provider/model inline, so the same model configuration can be reused across features.

Surfaces

See also the normative contract in cacophony-spec section 25 (Suggest Subsystem).