Skip to content

AI Assistant

The TUI has one AI conversation. What used to be three separate chats (:ai, :code, :pa) are now three agents the same conversation can wear — switching agents changes the toolset and system prompt in place, but the transcript, scratchpad, and saved-session identity carry over. :ai, :code, and :pa still work as before; they're now shortcuts for switching the one conversation onto a given agent rather than separate chat instances.

Agents

Command Aliases Agent
:ai :chat General-purpose assistant for exploring and understanding your cloud infrastructure — query resources, explain configurations, answer questions.
:code :dev Development-focused assistant with file system, git, shell, and Kubernetes tools. Use for infrastructure-as-code workflows, debugging, and automation.
:pa :assistant Personal-assistant / chief-of-staff agent: mission control, OpenProject tickets, Slack/email triage, standups.

Beyond these three built-ins, any mission-catalog role your mission repo (or .bnerd/agents/) defines — the same role files used by agent teams and by bnerd web — is also selectable by name. See Agent Profiles for the role schema, where roles are loaded from, and how their tool allowlists and permissions compose. A role file that fails to parse, or whose slug collides with a reserved name, is skipped with a warning printed to stderr at startup — the same warnings bnerd team list/describe/run print for the same files.

Switching agents: :agent

:agent

With no argument, lists every agent the conversation can switch to, marking the one currently active:

Agents:
  * chat
    code
    pa
    go-cli-dev
:agent <name>

Switches the conversation onto that agent. :agent takes the same names and aliases as :ai/:code/:pa/:dev/:assistant — :agent dev and :code land on the same place — plus any mission-catalog role slug (:agent go-cli-dev). An unrecognized name leaves the conversation on its current agent and prints the list of available names.

What carries over

Switching agents keeps the conversation itself — message history, the AI's scratchpad notes, and the session's saved-session identity all transfer to the new agent. Only the toolset and system prompt change. This means asking a question in :ai, then switching to :code to act on the answer, doesn't lose context the way starting a fresh chat would.

When a switch is refused

  • A stream or plan/approval turn in progress. Finish or cancel it first; a switch never interrupts an in-flight response.
  • The OpenAI-compatible backend is active. Carrying history across a switch requires exporting and re-importing the conversation, which only the Anthropic backend supports today. With ai-provider: openai configured, :agent/:code/:pa/etc. refuse the switch with a message explaining why, rather than silently dropping your conversation and starting the new agent from scratch.

Lean by default

The conversation starts on the chat agent — the lean, cloud-infra-only toolset — whether you launch with bnerd x or open the chat surface for the first time. bnerd pa is the one exception: it starts the conversation already on the pa agent. The larger toolsets code and pa bring (shell, git, filesystem, Slack/email, OpenProject) — and whatever a mission-catalog role's own tools: list adds — only enter the model's context once you actually switch to that agent with :agent, :code, :dev, :pa, or :assistant. Skills stay lazy on top of that: none of an agent's applicable skills are force-loaded just by switching to it — see Rules & Skills below.

Configuration

API Key

Set your AI provider API key:

anthropic-key: sk-ant-api03-...
export ANTHROPIC_API_KEY=sk-ant-api03-...
bnerd x --anthropic-key sk-ant-api03-...

Model Selection

Flag / Config Default Options
--ai-model / ai-model haiku haiku, sonnet, opus, or a full model ID

Safety Modes

Mode Description
read-only (default) AI can only read resources, no modifications
non-destructive AI can read and create/update, but not delete
full AI has full access to all operations

Set via --ai-mode flag or ai-mode in config.

OpenAI-Compatible Backends

bnerd supports OpenAI-compatible backends (e.g., Ollama, vLLM):

ai-provider: openai
openai-base-url: http://localhost:11434
openai-model: llama3.2
openai-key: ""  # May be optional for local backends

Agent switching needs the Anthropic backend

With ai-provider: openai, a conversation that has already started refuses agent switches: carrying history across a switch needs the Anthropic backend, and dropping the conversation instead is never the fallback. Start a new conversation on the agent you want — see When a switch is refused.

Token Budget

Limit tokens per AI session:

bnerd x --ai-token-budget 100000

Set to 0 (default) for unlimited.

Shell Access

By default, the AI can only run commands from an allowlist. To enable arbitrary shell access:

bnerd x --ai-shell-unrestricted

Warning

Use --ai-shell-unrestricted only in trusted environments. It allows the AI to execute any shell command.

Commands the default mode refuses

In default (allowlisted) mode shell_exec also refuses commands whose paths point at, or above, the secrets directory ~/.bnerd/secrets. That covers the directory itself, anything under it, and any ancestor of it — so ls ~, ls /home and any command touching ~/.bnerd are all rejected, whether the path is written literally, via ~/$HOME, through a symlink, or as a glob evaluated in one of those directories. Separately, any path-like argument containing a backslash or braces is refused outright, because sh -c strips escapes and expands {...} before the path checks would ever see the real target.

The reason is the credential file sink: tools like create_rgw_user and renew_rgw_key write S3 keys into ~/.bnerd/secrets precisely so they never enter the AI's context. A shell command that could read that directory would undo it.

--ai-shell-unrestricted is the escape hatch — the guard does not run at all in that mode, so those commands work again along with everything else.

Session Persistence

The conversation is automatically saved to the global session store (~/.bnerd/sessions/, see bnerd sessions) when you exit the TUI, under whichever agent it was wearing at the moment of exit. To restore your last conversation on startup:

bnerd x --ai-continue

Restore matches on working directory only — it picks the single newest saved conversation for the current directory (across all agents) and switches the TUI onto that conversation's agent, then re-imports the full conversation history, not just a summary — the AI picks up with complete context, as if the process had never stopped. This means bnerd x --ai-continue can land you on the pa agent (or a mission-catalog role) if that was the most recently used agent in this directory, and a conversation saved from bnerd web (with --continue) in the same directory is just as valid a restore candidate as one saved from the TUI.

If no matching session exists in the global store, --ai-continue falls back to the legacy per-directory session file (<workdir>/.bnerd/sessions/last-<agent>.json), which is still written on exit alongside the global store for compatibility.

Because there is one conversation, it is saved as a single session record whose profile field tracks whichever agent it last wore — switching agents mid-conversation doesn't fork it into a second record. As with the global store, --ai-continue is not scoped to the agent you launch in; it restores whichever saved conversation is newest for the directory.

Clearing History

Use :clear to reset the conversation history, regardless of which agent is active.

Working with a team

Ask the conversation to work with a team on something — e.g. "work with a team to add the /networks endpoint across the API and CLI" — and the agent calls team_start to launch a real, persistent agent team: a recipe slug (e.g. cloud-app) or an explicit roster (comma-separated role slugs) for an ad-hoc team, capped to read-only tools as a safety floor since there's no recipe-declared floor to inherit. Only one team can be active per conversation — asking to start a second one while a team is running is refused, with guidance to manage the existing roster instead of replacing it.

:team <slug> in the TUI is a shorthand for the same ask — it sends the literal turn Start the "<slug>" team recipe and coordinate it to completion. as an ordinary turn — so both paths land here. The conversation's own agent becomes the coordinator. It gets the driver's toolset bound directly into its existing tool registry — team_spawn_teammate, team_stop_teammate, team_replace_teammate, the task tools, team_send_message, team_status, team_pause/team_resume, team_finish to end the run, and team_delegate to hand coordination to a teammate (see Delegating coordination below) — and keeps talking to you as before, in the same window. See Agent Teams in the TUI for the live panel, task board, and focus mode that show the team while it runs.

A compact "Team status" block (roster, tasks, pending approvals) rides the agent's context every round, so it has an up-to-date picture even across :clear. Events the agent needs to react to — a teammate finishing a task, failing, or asking something — are injected as synthetic turns, marked · team update → coordinator in the transcript, and milestones render inline as they happen:

▸ spawned dev — go-cli-dev
✓ [dev] task #1 done: add the /networks endpoint
✔ team finished

Approvals stay yours

When a teammate wants to run a tool, has finished a requires_approval task, or has a question, it surfaces inline in the conversation, attributed to that teammate:

[dev] wants to run: rm old_file.go

You answer with the same keys as any other confirmation. If more than one arrives while you're mid-conversation, they queue and are presented one at a time; your own conversation's confirmations take priority over a team prompt, which resumes right after.

The agent can't approve on your behalf

No tool in the conversation's own registry resolves a pending teammate confirm, question, or task-completion approval — the coordinating agent can only summarize what's pending, never launder an approval through itself. You, answering inline, are the only way one gets resolved. (The operator MCP calls bnerd_team_approve / bnerd_team_answer / bnerd_team_task_approve apply to teams running in the same process as the MCP server — they cannot reach a team launched inside your TUI conversation.)

Anything still unanswered is denied when the team ends: finishing the team (team_finish), switching the conversation's agent or model, and /clear all settle open teammate requests as a denial rather than leaving a teammate blocked forever. /clear only clears the conversation — the team keeps running; team_finish and an agent/model switch end it.

Delegating coordination

Ask the conversation to hand coordination off — e.g. "delegate coordination to the reviewer" — and it calls team_delegate(role?, name?, brief) to make a teammate the delegated coordinator: it gets the driver toolset scoped to its own name, driver-addressed mail (anything sent team_send_message "to" the coordinator's own lead wire identity — see The bnerd team CLI for that naming holdover), and the attention stream that used to land in your conversation. role defaults to the recipe's lead: hint when omitted.

The conversation keeps its own driver toolset after delegating — it doesn't lose the ability to coordinate, it just stops being the one that reacts to routine team traffic (task claims, completions, teammate messages), which is the point: a long autonomous phase stops filling your context with milestones a delegate can absorb instead. You can still ask the conversation for team_status, or tell it to team_stop_teammate the delegate, at any time.

The quiescence nudge can still wake the conversation

The periodic "the team looks quiet — decide the next step" prod that keeps a stalled team moving always targets the conversation, even while delegated. Since the conversation still holds the full toolset, this can put it back to genuinely coordinating alongside the delegate (at most once per distinct task-board state) — this is the designed fallback, not a bug: it guarantees a quiet delegated team is never left with nobody to wake it. A follow-up to route the nudge to the delegate instead is on the roadmap.

Approvals are unaffected by delegation — see Approvals stay yours above; the delegate's toolset carries no approval-resolution tool either.

team_stop_teammate (or team_replace_teammate) on the delegate hands coordination back to the conversation — there's no separate "undelegate" tool. A delegate can't stop or replace itself (ask the conversation to do it), but it can call team_finish to end the whole team. If the delegate dies unexpectedly, coordination returns to the conversation automatically.

Delegation survives an interrupt/resume cycle: resuming a team that was delegated rebuilds the delegate with its driver toolset and prior history, and routes the reopened-board brief to its inbox rather than the conversation's. If the delegate's role no longer resolves in the mission catalog, the team resumes leaderless — coordination sits with the conversation, honestly, rather than pretending a member that couldn't come back is still driving. See Agent Teams → Delegated coordinator for the full picture, including the panel's [driver] tag and ambient "coordinated by" line (Agent Teams in the TUI).

Exit grace

Quitting the TUI (q or Ctrl-C) while a chat-driven team is running doesn't kill it outright. It runs the same bounded exit grace bnerd web uses on shutdown and bnerd team run uses at the end of its own run (see The bnerd team CLI → Exit behavior):

  1. Any pending teammate approval, confirm, or question is denied first — nobody can answer it once the terminal is going away, and an approval-parked teammate would otherwise look "busy" and eat the whole grace window for a decision nobody can make.
  2. Any teammate genuinely mid-turn gets up to 30 seconds to finish it.
  3. Every teammate's chat history is flushed to disk, the task board is persisted, the team is marked interrupted, and its lease is released.

This runs after the terminal is released, so it can't freeze the UI — but it does mean the shell prompt doesn't come back instantly. Two lines print to the terminal so the wait doesn't read as a hang:

team run-cloud-app: finishing up (up to 30s)…
team run-cloud-app: interrupted, 2 teammate(s) flushed to disk

Resuming an interrupted team

Reopening the conversation with bnerd x --ai-continue checks for a team that belongs to it and is still resumable — interrupted (the exit grace above), or running with a lease nobody is renewing any more (the owning process crashed hard before it could mark itself interrupted). If it finds one, it offers to resume it inline, before you type anything:

Team "cloud-app" was interrupted (3 agents, 2/5 tasks done). Resume? [y/n]
  • y rebuilds the team: the roster is respawned from the persisted role definitions with each teammate's history repaired (a dangling tool call left over from the interruption gets a synthetic result so the backend accepts it), every task still marked claimed is released back to the board (nothing stays claimed by a teammate that no longer exists yet), and a status brief is written into the coordinator's context so it knows it just inherited a half-finished team. A role that no longer resolves in the mission catalog — renamed or removed since the team was launched — is skipped non-fatally; it's named in that same brief rather than aborting the resume. You'll see:

    ▶ team "cloud-app" resumed — 2 teammate(s) back online
    
  • n declines: resume declined — team closed. The team is marked finished on disk so this offer won't come back for it.

  • Esc dismisses without deciding: resume offer dismissed — the team is still interrupted and will be offered again next time. Nothing is written — the offer returns the next time you restore this conversation.

If the team is still running with a fresh lease — another process currently owns it — nothing is offered; you just get an ownership notice:

team "tui-cloud-app-3f2a9b1c" is owned by another process (pid 48213) — its state is read-only here

The lease is a marker, not a lock

Nothing stops you reading (or, outside bnerd, editing) that team's state directory. The lease is consulted in exactly two places: this resume check, and team_start, which refuses to launch a team whose state directory is already being heartbeated by a different live process:

team "tui-cloud-app-3f2a9b1c" is already running for this conversation in
another process (pid 48213) — stop it there, or continue the work from that
process, rather than starting a second copy

TUI-launched teams only

This offer only exists for a team originally launched from a TUI conversation — its meta.json carries that conversation's session ID, which is what a restore matches against. A bnerd web-launched team, and a bnerd team run process (SIGINT/SIGTERM, including a terminal Ctrl-C, or a graceful server shutdown), both get marked interrupted the same way — but neither has a resume path back into it from anywhere, since neither is a TUI conversation this offer can match against — see bnerd web → Exit grace and The bnerd team CLI → Exit behavior.

Rules & Skills

The assistant's system prompt is layered with markdown-defined rules (guidance the model should generally follow) and skills (the same, plus optionally their own tools) — see Rules & Skills for the full model. Two chat commands manage them:

  • /skills — lists every loaded rule and skill, marked * (active this round), + (an inactive skill that applies to the current agent — the assistant can load it itself via its skill tool), or unmarked (everything else: another agent's entry, or an inactive rule, which only /skill load can bring back).
  • /skill load <name> / /skill unload <name> — manual override; unlike the assistant's own self-loading, this works on rules too.

Tool Calls

The AI assistant uses tools to interact with your infrastructure. Tool calls are logged and visible in the chat view, showing:

  • Tool name
  • Parameters
  • Status (running, success, error)
  • Output (expandable)

The available tools depend on the active agent and safety mode — see MCP Server > Available Tools for the full catalog.