ngn: the terminal harness¶
ngn puts a terminal interface on top of the nagents library. The same coding
harness powers interactive and headless use. Python extensions change the agent's
behavior, not just its appearance.
The terminal and headless CLI are included in published distributions. This guide describes the current checkout, which may include newer fixes; it is not an OpenCode feature-parity claim. Read the limitations and trust model below before using it on important workspaces, and check release availability.
Related pages: installation and environment choices,
complete configuration schema and recipes, and the
local web client, available with the web extra and bundled assets.
Shared provider connections¶
Use /provider to create or edit named provider connections, activate one, or
fetch its model IDs. The TUI and web UI share
$XDG_CONFIG_HOME/ngn/providers.json (default
~/.config/ngn/providers.json). Enter an environment-variable name such as
OPENAI_API_KEY or ${OPENAI_API_KEY}; ngn reads the value when it makes a
request and does not save it in that file. OpenAI also supports ngn's ChatGPT
device login or the library's local Codex discovery. Foundry can use the
optional azure-identity SDK's DefaultAzureCredential chain. Live voice and
backend settings belong to each connection. See the
configuration reference.
Install and launch¶
From the repository root, retain the existing Poetry workflow:
Alternatively, for a new local uv environment:
uv venv --python 3.13
uv pip install --python .venv/bin/python -e '.[tui]'
uv run --no-project --python .venv/bin/python ngn --demo
Do not overwrite a Poetry-managed .venv. Use Poetry or another explicitly
named local environment instead. The uv workflow needs neither uv sync nor
changes to an existing uv.lock. The installation page
explains activation and optional isolated tool installations. Commands written
as bare ngn below assume the intended environment is activated; otherwise use
poetry run ngn or uv run --no-project --python .venv/bin/python ngn.
Install the local source with the tui extra for the behavior documented
here, or select a published CLI release as described in the installation guide.
Textual is optional: importing nagents, using Agent, or running the headless
client does not import Textual.
The offline demo needs no API key and does not execute shell commands, change
workspace files, or contact a model. It also skips Python plugins, since even
trusted setup code could perform I/O. It does save demo conversations locally.
Send demo approval to inspect an approval dialog with a sample change preview.
Demo actions are explicitly labeled as previews, not completed real work.
For a real model, create a named connection in providers.json or the Provider
connections UI, set its referenced key in your shell environment, and launch:
poetry run ngn --provider-id my-openai --model gpt-6-luna
poetry run ngn --provider-id my-anthropic --model YOUR_MODEL_ID
poetry run ngn --workspace /path/to/project
poetry run ngn --continue
Use a model ID your provider account actually supports. Set base_url and
api_key_env inside the named provider entry for custom endpoints; store key
values outside configuration. Credentials are not copied from another
coding assistant's configuration, and ngn does not automatically load .env.
Use api: auto for provider-native defaults, or choose chat_completions,
responses, or messages for an endpoint supporting that route.
LiteLLM requires an explicit base_url and an API-key reference such as
api_key_env: LITELLM_API_KEY; it does not use ChatGPT OAuth or require a
gateway SDK in ngn. See provider recipes.
Explicit text deliveries¶
The interactive TUI exposes the builtin.tui channel for explicitly requested
text sends. Ordinary assistant replies already appear in the conversation.
An approved channel_send appears as a separate saved delivery alongside its
compact tool card; it does not create another assistant message or another
execution loop. Deliveries survive session reload.
The route is text-only: attachments, including text files, cause the entire send to be rejected. Existing profile restrictions and terminal approvals apply. See built-in TUI text delivery for discovery, history and lifecycle behavior.
Composer and commands¶
Typing / at the start of the composer opens suggestions immediately. Use
Up/Down or the mouse to select a command. Enter prefills its name; edit or add
arguments, then press Enter again to execute. The menu, Ctrl+P palette, and
/help all use the same live harness registry, including:
- Built-in actions such as
/new,/model,/agent,/tasks, and/queue. - Discovered skills as
/<name> [task]or/skill:<name> [task], loaded on demand through the Agent's skill loader. Workspace reads remain guarded; bundled skills load from package resources. Suggestions refresh metadata without activating skills or running scripts. - Trusted Python-plugin commands, labeled with their plugin source.
| Command | Purpose |
|---|---|
/help |
Show the live command registry. |
/new |
Start a new local conversation. |
/fork [title] |
In the TUI, copy the idle conversation into a new independent root and select it. Omit the title for <chat title> · fork 1, fork 2, etc. Saved context and attachments are copied; active work and approvals are not. |
/rename <title> |
In the TUI, persist a name of 1–80 printable characters for the current conversation. |
/sessions |
List and resume this workspace's saved conversations. |
/compact |
Compact active context with the configured strategy. |
/model [model] |
Show or change the runtime conversation model. |
/agent [name] |
Show or switch the built-in/custom profile. |
/context |
Inspect effective context and configuration. |
/plugins |
Inspect configured and loaded Python extensions. |
/skills [filter] |
Browse discovered skills and prefill a command without executing it. |
/tasks |
Open the agent tree, inspect conversations, and send follow-ups. |
/queue [resume\|clear] |
Inspect, resume, or explicitly discard queued input. |
/login, /logout |
Manage ngn's local OpenAI device login. |
/quit |
Close the client and clean up managed work. |
/help is the source of truth for commands available in the particular source build you installed.
Shift+Enter inserts a newline. Ctrl+J and Alt+Enter remain fallbacks for terminals that do not transmit Shift+Enter distinctly. Pasted multiline text is never submitted automatically. Tab cycles agent profiles by default, and Shift+Tab cycles backward. Agent switching waits until the current run is finished or stopped.
New prompts and plugin/skill commands submitted during work queue in FIFO order
by default. --submit-mode interrupt instead cancels and awaits the current run
and its children before starting the new input. Ordinary configuration commands
are not deferred behind an active run.
Escape stops current work and pauses the queue without discarding it. Empty Enter
or /queue resume resumes queued input; /queue inspects it and /queue clear
explicitly discards it. The queue is bounded to 20 inputs and is not persisted
across application exit. Completed file or shell actions are never rolled back.
ngn --submit-mode queue --tab-action agent
ngn --submit-mode interrupt --tab-action complete
ngn --tab-action focus
The equivalent JSON settings are "submit_mode": "queue" and
"tab_action": "agent". Tab alternatives are complete for command completion
and focus for ordinary widget navigation. They also accept NGN_SUBMIT_MODE
and NGN_TAB_ACTION environment defaults.
/resume is a synonym for /sessions and opens the session picker. Aliases are
resolved by the command registry before native dispatch, so they appear in
completion and help without being sent to the model. Extensions can register
synonyms with harness.commands.register_alias(alias, target).
Selection and clipboard¶
Drag to highlight text and release the mouse button to copy it automatically. This works for responses, tool output, code, and mouse selections in text fields; code indentation and line breaks are preserved. Double-click selections copy too. Choosing an agent or slash-command row is navigation, not text copying.
Ctrl+C copies selected text instead of cancelling work. Without selected text, it retains the normal cancel/exit behavior. Ctrl+Shift+C explicitly copies without cancelling; Command+C also works if the terminal forwards it to ngn. Keyboard selection in an editable field does not automatically replace the clipboard, so select-all followed by paste still works normally. Use Ctrl+C to copy that selection.
ngn sends the terminal's OSC 52 clipboard request and also uses a local clipboard
helper when available: pbcopy on macOS, wl-copy on Wayland, xclip or xsel
on X11, or PowerShell on Windows. This makes copying work in macOS Terminal,
which does not support Textual's OSC 52-only clipboard path. Helpers receive text
on stdin, not as a shell command or command-line argument. ngn never reads your
desktop clipboard automatically.
Over SSH, only terminal clipboard forwarding is used, not the remote desktop's clipboard. OSC 52 writing must be allowed by your terminal and, if applicable, tmux. If the terminal blocks it, configure its clipboard permissions or use the terminal's own selection/copy mechanism. Usual terminal paste shortcuts continue to work; pasted multiline text is never submitted automatically.
Compact tool activity¶
Tool cards start collapsed, including restored history and failed tools. Their single-line header shows the tool name, a short command or target hint when available, and a running, complete, failed, or cancelled label. Completed calls show their reported duration when available. Long names and hints are shortened to keep the outcome visible on narrow terminals.
Native shell results with a nonzero exit code or timeout show exit 7 (with the actual code) or timed out in the card and headless text output. This process outcome does not change the enclosing task's status or CLI exit code; the agent may recover and finish the requested work. Saved result strings are not parsed to infer a process outcome, and JSON events retain their original structured result and framework error fields.
Press F6 to switch from the composer to the conversation without changing
your scroll position, or back to your draft. In the conversation, PageUp/PageDown
scroll and Tab/Shift+Tab navigate tool headers and other controls. Tab in the
composer retains its configured agent/completion/focus behavior. The conversation
border and /help show the pane-focus shortcut.
Click a tool header, or focus it and press Enter or Space, to inspect the tool name, arguments, output, error, and diff. Press Enter, Space, or click again to collapse. Output and lifecycle updates never change your choice; failures stay visibly labeled without opening their details. While an expanded card's header is focused and visible, incoming details pause bottom-follow so the header stays available. Scrolling away does not pull the header back into view. Expansion choices last for the mounted card, not across session reloads. Details retain the existing 16,000-character output/argument preview limit with an explicit truncation marker; approval dialogs still show the exact proposal separately. In approval dialogs, PageUp/PageDown scroll the full proposal even with Deny or Allow once focused; paging does not change the decision focus. Deny remains the initial choice, and Escape denies. Closing an approval restores the previously focused control without scrolling; if that control is no longer available, focus returns to the composer.
Themes and motion¶
The message editor and its status footer share one borderless, filled surface. User messages use the same background, with paired foreground colors for contrast even when the surrounding terminal uses its native background.
ngn --theme terminal
ngn --theme graphite
ngn --theme ocean
ngn --theme ember
ngn --theme terminal --no-animations
terminal is the default. It uses ANSI default foreground/background resets, so
your terminal supplies those colors; it does not sample RGB values or rewrite
your terminal's palette. graphite is cool neutral, ocean uses blue/cyan, and
ember provides the warm palette. Graphite combines lilac/mint/rose, ocean uses
turquoise/blue/orchid, and ember uses terracotta/gold/mauve. User and assistant
turns, tools, task states, notices, headings, code, and diffs have distinct
semantic colors instead of sharing one accent. theme_background separately selects
"auto", "terminal", or "theme": follow the preset's automatic behavior,
retain the terminal background, or use the theme background. For example:
The equivalent CLI choices make background variants easy to compare:
ngn --theme ocean --theme-background theme
ngn --theme ocean --theme-background terminal
ngn --theme graphite --theme-background theme
ngn --theme ember --theme-background terminal
Native backgrounds use terminal defaults rather than attempting to sample the terminal's actual RGB color. Painted semantic text is tested for 4.5:1 contrast; native ANSI accent contrast depends on the terminal's own palette. Native selection uses reverse video, so arrow-key navigation remains visible without guessing whether the inherited background is light or dark.
Each preset has a subtle three-column ASCII activity animation at eight frames
per second; idle/error status states remain still. In the current checkout,
--no-animations, "animations": false in JSON, or NGN_ANIMATIONS=false
disables the indicator, animated scrolling, and input cursor blinking.
TEXTUAL_ANIMATIONS=none also disables these motions. Brief button-press feedback
remains. Theme selection also accepts JSON theme and NGN_THEME; background
selection has the NGN_THEME_BACKGROUND environment default. JSON overrides
NGN_* environment defaults. These are color/motion presets, not a graphical
Bot avatar or a separate team-view implementation.
Native background subagents¶
The agent can call delegate(prompt, agent="assistant"); assistant is the only
built-in agent. Other delegation targets must be explicitly configured. The caller
immediately receives a UUID task ID, a random two-word name such as quiet maple,
and running status.
The child runs as a native asyncio task while the parent continues its own work.
When the parent turn settles, completed results are batched into an explicitly
untrusted background notification, and the parent automatically runs again to
consider them. If children are still working, the harness waits for their results
without holding a model request open. The UI shows each child's task and response;
/tasks opens the current-session conversation inspector. Headless JSON includes
task_started, task_message, task_completed, task_notification, and
task_activity events,
with a final done only after the group settles. Task lifecycle events include
parent/child session IDs, parent task ID, depth, profile, follow-up number,
activation number, and trigger. Notification records identify the actual source
and recipient; they do not themselves assert that another execution completed.
task_activity wraps a child's published messages and tool evidence in a separate
payload with the task, parent, child session, activation, turn, sequence, and
timestamp. It is inspection data, not a main-assistant answer or root tool call.
Consumers must keep that scope when rendering concurrent children. Private
reasoning and provider request metadata are not included. Live voice and channel
output receive the parent's response rather than this inspection stream.
Queued completion events reach the UI before the next core model or tool-result event, even while the parent keeps working. Waiting for a tool result or a model event can delay that update; streamed shell output alone does not trigger it. Model notifications still wait for the parent turn boundary. If a parent hits a terminal error, already queued lifecycle events are emitted before cleanup, but no new model turn starts. A child that recovers from a recoverable provider error and finishes successfully returns its result normally.
Nested completion notifications target the immediate parent. Main can observe all task lifecycle events without receiving every grandchild result as a separate model instruction. When an eligible retained child is continued after its parent has finished, that parent is reactivated in its own conversation, synthesizes the update, and reports upward. Cancelled or unavailable ancestors are not silently bypassed. Automatic activations have their own activation number and trigger; they do not count as human follow-ups or reset the root execution budget.
This preserves provider protocol rules: the delegation call has exactly one
matching tool result (the started acknowledgement). Late completion is not a
second tool result and is not promoted to a system instruction. It is a new,
labeled user-role data notification at a safe turn boundary, using the same
Agent.run, hooks, context handling, and permissions as ordinary turns.
The built-in assistant uses normal guarded operation. Custom agents can select
read-only mode through their profile configuration.
Children have the same permission ceiling as their parent, not independent
authority. A read-only parent cannot obtain write or shell privileges by
delegating to assistant or a build-mode custom profile. Build-capable child actions
still need the ordinary guarded-tool approvals. An instruction or profile name
does not bypass this boundary.
max_subagent_depth defaults to 2 and accepts integers from 0 through 8.
Depth counts the root as 0, a child as 1, and a grandchild as 2. The default
therefore permits grandchildren, but not great-grandchildren. Set it to 0 to
disable delegation, or 1 to allow only direct children.
Depth is separate from concurrency and job budgets. Additional initial bounds
are three concurrent subagents and eight child executions per root user run,
shared across the entire descendant tree and including automatic continuations
and accepted human follow-ups. A quota-full delegation fails immediately instead
of waiting indefinitely for a slot; a parent still working or waiting for its
own children occupies a slot. Further limits are at most
min(max_tool_rounds, 12) tool rounds per child, a five-minute child timeout,
a 16,000-character delegation prompt, and
12,000-character results. These are implementation limits, not extra YAML knobs.
Children use separate sessions/providers and share the parent's credential
manager for in-process refresh coordination. They receive the delegated prompt
and applicable instruction context, not a copy of the full parent conversation.
Plugins are not re-imported in children. Children inherit the active model and
provider unless their profile or delegation supplies an explicit selection.
The delegate tool accepts model, a configured provider connection, and
compact_trigger, compact_tokens, and compact_messages choices. Compiled
Agent Designer definitions retain control of their provider and model.
Tasks are process-local, not durable jobs or persistent Bot identities. Cancelling or closing a run cancels and awaits its children; undelivered notifications are discarded, and task outcomes are not replayed after restart. Child transcripts remain in local storage but do not clutter the ordinary session picker.
The web client additionally owns a process-local
timer service for schedule_wakeup. It resumes the
scheduling root or child without keeping a browser request open. Ordinary
CLI/TUI instances do not own that service and reject timer requests explicitly.
Timers do not survive a server or pod restart.
Agent tree and follow-ups¶
The right rail contains a scrollable tree of running and finished agents. Nested
delegations appear under their parent, with status labels. Collapse branches with
Left/Right or the mouse; selection and expansion survive status updates. Ctrl+T
focuses the tree, and Enter or a click opens the selected conversation. On narrow
terminals, Ctrl+T opens the same tree inside the conversation inspector instead
of squeezing the main conversation. /tasks opens the inspector at any width.
The inspector shows saved user/assistant/tool messages and a per-agent follow-up draft. Use Up/Down to select, Left/Right to expand or collapse, and Tab or Enter to move into the draft. Ctrl+Enter or Continue Agent sends it. Selecting a node never sends a message. You can inspect and draft while work runs, but must wait for that child and its subtree to settle before continuing it. Cancelled tasks cannot be resumed; delegate a new task instead.
A follow-up continues the same child conversation and identity, not a new unrelated agent. Its message stays in that child's transcript; the parent receives a pickup notification and the child's returned result. If the parent is already running, it receives the notification at its next safe turn boundary. If idle, ngn waits for the child result and runs the parent to consider the exchange. It does not inject a second tool result or silently treat child output as higher-priority instructions.
Each identity permits eight explicit human follow-ups, and the root retains up to 64 identities for its process lifetime. Every continuation also consumes one of the current root run's eight execution slots. Full quotas and unsupported continuations are rejected explicitly. These handles do not survive restarting ngn, even though the underlying transcripts remain on disk.
The web task inspector also accepts messages while a child is running. These wait for its next complete response and tool batch, including any pending approval. Reading a child conversation never starts it or approves its tools. The web inspector preserves readable task records after restart; the workers themselves remain process-local.
Python clients use the same interfaces:
from contextlib import aclosing
tasks = harness.tasks.list() # Detached snapshots, including descendants.
messages = await harness.task_history(tasks[0].id, limit=100)
async with aclosing(harness.continue_task(tasks[0].id, "Check this case as well.")) as events:
async for event in events:
handle(event)
The history limit is 1..200; inspection does not run a model or repair an
unfinished tool block. During an existing root run, continue_task() yields an
acceptance notice; task events keep flowing through that run's existing consumer.
When idle, the returned stream owns execution and must be consumed or explicitly
closed, just like Harness.run().
Try demo subagents in ngn --demo for three genuine concurrent asyncio jobs
using clearly labeled, scripted offline responses. A live example is:
Delegate three independent read-only reviews of the README, test setup, and package configuration. Continue your own review, then combine the results.
Protocol references: OpenAI function-call correlation and Anthropic tool-result ordering.
Provider sign-in¶
ngn login chooses the provider and stores one active login. In a terminal it
shows a menu; name the method to skip it:
ngn login # interactive menu
ngn login chatgpt # ChatGPT/Codex device code (same as --device-auth)
ngn login openrouter # browser PKCE; paste the code OpenRouter shows
ngn login openai # hidden API-key prompt
ngn login anthropic --model MODEL-ID
ngn login gemini --model MODEL-ID
ngn login custom --base-url https://gateway.example/v1 --model local-model
ngn login --status
ngn logout
| Method | Provider | Notes |
|---|---|---|
chatgpt |
openai |
ChatGPT/Codex device login; subscription access, not general API access. |
openrouter |
openrouter |
Headless PKCE: open the printed link, approve ngn, and paste the single-use code shown by OpenRouter (valid for 10 minutes). ngn exchanges it for a user-controlled API key. |
openai |
openai |
OpenAI Platform API key with usage-based billing. Default model gpt-6-astra. |
anthropic |
anthropic |
Anthropic API key; enter a model ID when prompted. |
gemini |
gemini |
Google AI Studio API key; enter a model ID when prompted. |
custom |
openai_compatible |
Your own gateway, Ollama, or another compatible service; requires a base URL and model. --api selects the HTTP contract, and --provider accepts any advanced provider name. |
API keys are read with a hidden prompt, or from standard input with
--key-stdin, never from the command line. Passing --api-key-env NAME stores
only a reference to an environment variable instead of a key, which suits a
secret manager. A non-interactive ngn login without a method keeps the
historical ChatGPT device-code behavior.
Saved login credentials do not select an active connection. Pick a named
connection in providers.json or with --provider-id; ChatGPT device login
supplies credentials when that connection uses "auth": "chatgpt".
ngn login --status prints saved credential status without a network request.
The active login lives in $XDG_DATA_HOME/ngn/auth/login.json
(~/.local/share/ngn/auth/login.json by default), a plaintext credential
file protected by POSIX permissions (directory 700, file 600) next to the
ChatGPT store. Only one login is active: signing in to another provider replaces
the previous selection and key. Keys are never printed, logged, added to model
context, or rendered by status output. /logout or ngn logout removes ngn's
saved provider login and ChatGPT credentials; it does not touch other clients or
revoke remote sessions. Built-in file tools deny the entire credential
directory, and sign-in is disabled in offline demo mode.
OpenRouter browser sign-in¶
ngn login openrouter uses OpenRouter's headless PKCE flow: ngn prints an
openrouter.ai/auth address with a generated code challenge, you approve the
request in your browser, and OpenRouter displays a single-use authorization
code. Paste that code at the prompt. ngn exchanges it over HTTPS for a
user-controlled API key and stores it like any other API login; the code cannot
be reused and expires after 10 minutes. Only continue if you started the
sign-in yourself. The default model is openrouter/auto; change it with
--model or /model.
OpenAI device login¶
Start ngn without --demo, then enter /login. Select ChatGPT device login,
open the displayed OpenAI link, and enter the one-time code in your browser.
Only approve a code you requested yourself. Escape or Cancel stops waiting.
The terminal never asks for your ChatGPT password.
You can also use the headless login command:
OpenAI currently documents device authentication as beta. You may need to enable it in your ChatGPT security or workspace settings. Your account/workspace must have Codex access. If authorization is unavailable, use an OpenAI Platform API key instead; device login does not bypass account restrictions or turn a ChatGPT subscription into general API access.
ChatGPT sign-in uses https://chatgpt.com/backend-api/codex/responses, not the
ordinary API-key chat-completions endpoint. Choose a model available to your
account in config.json, /model, or --model. The underlying
OpenAIProvider lives in the library; interactive login, token refresh/storage,
and route selection live in the harness.
The auth field in the selected providers.json connection accepts:
auto: prefer a saved ChatGPT login for the default OpenAI endpoint withapi: auto; otherwise use the named key environment variable.api-key: always use the configured API-key environment variable.chatgpt: require the saved ChatGPT login and the Codex Responses route.codex: use local Codex discovery rather than the ngn device login.
Saved ChatGPT credentials are never sent to a custom base_url. Such endpoints
use API-key authentication, and explicitly combining auth: chatgpt with a
custom endpoint or an api override is rejected. Keep api: auto for
ChatGPT/Codex login, even though its dedicated route uses Responses. Device login
is disabled in offline demo mode.
Tokens are stored in $XDG_DATA_HOME/ngn/auth/openai.json, or
~/.local/share/ngn/auth/openai.json by default. This is a plaintext credential
file, protected by restrictive file/directory permissions on POSIX, not an
encrypted keyring. It is separate from the workspace and session database.
Refresh tokens rotate during use. Treat the entire file like a password: never
commit, attach, or paste it into bug reports. Built-in file tools deny access to
this store even when the workspace is your home directory.
/logout or ngn logout removes ngn's local saved login. It does not touch other
clients' credentials or revoke remote sessions. ngn never imports .codex or
OpenCode credential caches. Device codes are displayed only by the login UI or
explicit login command; they are not added to model context or conversation history.
Headless use¶
poetry run ngn run "Explain how tests are organized"
poetry run ngn run --json "Review the current implementation"
poetry run ngn run --demo --json "Show the interface event stream"
poetry run ngn sessions
poetry run ngn doctor
The same commands work through the local uv launcher, for example:
ngn run - reads a prompt from standard input. JSON mode emits one event per
line with schema_version: 1 and an event discriminator. It uses the same
tools, hooks, and permission checks as the TUI. Non-interactive approvals are
denied, never silently granted. An interactive ngn run asks for approval on
standard error. Control sequences in human-readable tool output are sanitized.
Before the prompt runs, ngn run prints a short header on standard error with
the effective provider, the effective model (a profile's model can supersede the
top-level model), and the configuration files that were actually loaded:
When no configuration file exists the header reads Config: built-in defaults,
and --demo adds a Mode: offline demo line. The header always goes to standard
error, so ngn run --json keeps standard output as pure JSON Lines. ngn doctor
lists the same files as Config files:.
--json is a run option, not an option for every subcommand. Human-readable
text streams to stdout; tool progress and approval prompts use stderr. JSON
mode writes versioned event records to stdout, with command errors/denials still
possible on stderr. Consumers should dispatch on event, tolerate additional
fields, and handle tool failures as well as the final response. Subagent events
include task_started, task_completed, and scoped task_activity records.
Exit codes are 0 for a completed command, 1 for a run that emits an agent
error, 2 for a configuration/usage failure, and 130 for interruption. A
completed agent run can contain a denied or failed tool that the model handled;
inspect tool-result events when automating a workflow.
CLI reference¶
Run without a subcommand for the TUI. Common options are accepted before or after the subcommand; prefer supplying each option only once.
| Command | Purpose |
|---|---|
ngn |
Full-screen terminal client. Requires the tui extra and a terminal. |
ngn run [--json] [prompt ...] |
Headless prompt execution. -, or no prompt with piped stdin, reads stdin. |
ngn serve |
React client and HTTP API for the harness. Install [web] with bundled assets, or build an editable checkout; see Local Web Client. |
ngn sessions |
List saved sessions for the selected workspace. |
ngn resume [SESSION_ID] |
Resume a saved session in the full-screen client. With no ID, choose from an interactive list. Requires a terminal; errors on an ID from another workspace. |
ngn doctor |
Initialize the harness and show configuration/extension diagnostics without an LLM request. Trusted plugins still execute. Use --demo to skip them. |
ngn login [METHOD] |
Start provider sign-in. Methods: chatgpt, openrouter, openai, anthropic, gemini, custom, or an advanced --provider name. Interactive menu in a terminal; ChatGPT device login when none is chosen non-interactively. |
ngn login --status |
Show ChatGPT and provider login status without a network request. |
ngn logout |
Remove ngn's saved ChatGPT and provider logins. |
ngn --version |
Print CLI/package version; the version alone does not prove an unreleased feature is present. |
ngn --help, ngn run --help |
Show options implemented by the installed source build. |
| Common option | Meaning |
|---|---|
--workspace PATH, -C PATH |
Select an existing workspace directory. |
--config PATH |
Load and explicitly trust a JSON file at any location. |
--trust-project |
Also trust <workspace>/.ngn/config.json. |
--provider-id NAME |
Select a named connection in providers.json. |
--model ID, -m ID |
Set top-level model selection. A profile's own model can supersede it on activation. |
--agent NAME, -a NAME |
Select the built-in assistant or an explicitly configured custom profile. |
--max-subagent-depth N |
Integer 0..8; default 2, with the root at depth 0. |
--theme NAME |
Select terminal, graphite, ocean, or ember. |
--theme-background auto\|terminal\|theme |
Choose preset-default, terminal-native, or preset background behavior. |
--no-animations |
Disable the busy indicator's motion, animated scrolling, and cursor blinking; brief button feedback remains. |
--submit-mode queue\|interrupt |
Control prompts submitted while work is active. |
--tab-action agent\|complete\|focus |
Select composer Tab behavior. |
--plugin REFERENCE |
Explicitly trust and append a Python extension; repeatable. |
--demo |
Use the offline demo. |
--continue, -c |
Resume the latest session in this workspace, if any. |
--resume ID |
Resume a specific session in this workspace. Mutually exclusive with --continue. |
Connection authentication, API, endpoint, and key-variable name belong in
providers.json; config.json holds general settings such as data_dir,
shell_timeout, limits, model selection, and profiles. Use the
configuration reference for types and validation, and
--help for the flags present in the particular source build.
Session storage¶
Sessions are scoped to the resolved workspace path. The default state root is
$XDG_DATA_HOME/ngn, falling back to ~/.local/share/ngn; each workspace uses a
hashed subdirectory containing sessions.db. Set data_dir to change the
session-state root. Moving a workspace or changing its resolved path can select
a different scope; --resume does not search unrelated workspaces.
ngn resume opens the full-screen client on a saved session. Pass a session ID
to resume it directly, or omit the ID to pick from an interactive list. Listing
and choosing are read-only: they never register an empty session, so the picker
shows only real conversations. --continue/--resume on ngn and ngn run
remain available for scripts and one-shot runs.
Demo conversations persist too. Queued prompts and running subagent jobs do not
survive application exit. Resuming history does not replay completed or uncertain
tool actions, and child transcripts are not ordinary session-picker entries.
Session databases can contain private prompts and workspace content; do not
commit or attach them to bug reports. The OpenAI credential store is separate
and is not relocated by data_dir.
Configuration¶
User configuration is ~/.config/ngn/config.json (or under $XDG_CONFIG_HOME).
Project configuration is <workspace>/.ngn/config.json and requires
--trust-project or explicit selection of that file with --config.
--config /any/path/settings.json explicitly trusts that file; its name and
location need not be .ngn/config.json. Settings are top-level JSON with no
ngn wrapper; named profiles live under a profiles object:
{
"providers": "./providers.json",
"model": "gpt-6-luna",
"agent": "assistant",
"shell_timeout": 60.0,
"max_subagent_depth": 2,
"profiles": {
"audit": {
"mode": "reviewer",
"instructions": "Prioritize regressions and missing tests."
}
}
}
Precedence is built-in defaults, NGN_* environment defaults, user JSON, trusted
project JSON, explicit JSON, then CLI flags. NGN_MODEL and NGN_PROVIDER_ID
are examples of environment defaults. Plugin
paths in JSON resolve relative to that file; explicit --plugin paths resolve
relative to the shell's current directory. data_dir in JSON is also relative
to that file, not the workspace. JSON plugin lists replace earlier lists; CLI
--plugin entries append. Profiles merge by name, but a later same-name entry
replaces the whole profile, with omitted fields resetting to profile defaults.
No literal API keys belong in JSON.
See the full configuration reference for every accepted field, exact types/defaults/ranges, provider recipes, and the distinction between configuration precedence and runtime profile model selection.
Default and workspace instructions¶
Every coding Harness starts with ngn's bundled
nagents/harness/instructions/AGENTS.md. The same Markdown resource ships in the
wheel and source distribution; it contains the default coding, verification, and
approval guidance. Framework maintainers can edit
src/nagents/harness/instructions/AGENTS.md to change those defaults without
editing Python prompt strings. Active workspace, profile, tool capabilities, and
delegation limits are added by the runtime.
For project-specific guidance, create or edit your workspace's AGENTS.md.
The Harness loads an existing root file automatically when initialized, including
when constructed programmatically; it never creates or overwrites this file.
Native file tools also load applicable nested AGENTS.md files when accessing
their paths. Nested guidance takes precedence within its subtree. These files
provide task context and cannot grant tools, bypass approvals, or change project
configuration trust. Ordinary delegated children receive the defaults once and
retain the applicable workspace guidance.
Agent Designer definitions keep their explicitly configured instructions and do
not implicitly load either coding-harness defaults or workspace AGENTS.md.
Skills¶
The Harness includes ngn-customize for the application's customization
interfaces and ngn-channels for external messaging integrations. Add workspace
skills as .ngn/skills/<name>/SKILL.md or .agents/skills/<name>/SKILL.md with
single-line name and description frontmatter. Workspace names take precedence
over bundled names.
The model sees escaped descriptive metadata in a separate system-level message
and calls skill(name) to load relevant instructions as tool-result task context.
The preview has a separate 10,000-estimated-token cap, complete sorted entries,
and an omission notice; omitted names remain in the full catalog for named loads.
Explicitly select one as user-level task context with $ngn-customize or
$ngn-channels, including ordinary web/Telegram messages. The web UI and TUI also
provide /skills [filter], /ngn-customize [task], and /skill:ngn-customize [task].
Short names yield to built-in or registered commands; the skill: form resolves
collisions. Arguments retain their whitespace, replace literal $ARGUMENTS in the
skill, or append as task text when no placeholder exists. Existing load budgets
and tool permissions still apply. Dollar mentions and slash commands are
ngn/client conventions, not universal Agent Skills syntax.
The catalog refreshes at incoming text boundaries, before each model request,
and before/after every tool invocation. Changes therefore become available during
the same turn without restarting ngn. Each load defaults to a 10,000-token
approximation based on UTF-8 bytes divided by four, with UTF-8-safe truncation
and a bounded aggregate allowance for explicit activation. Trusted YAML
skill_token_limit = 10000 or the NGN_SKILL_TOKEN_LIMIT environment default
configures this same allowance (integer 1–100,000). All discovery sources share
the top-level single-line metadata parser; extra and nested metadata is ignored. Loading adds task
context and executes no scripts; referenced resources require separate permitted
access. See Skills for authoring, library discoverers, loading results,
and batch/realtime limitations.
Python behavior extensions¶
Reloadable setup contract¶
The coding harness stages configured extension setup on initialization, at user
message boundaries, and unconditionally after each model response. Register tools
with harness.agent.register_tool or harness.agent.tool_registry, commands and
aliases with harness.commands, and return an AgentPlugin (or append one to
harness.agent.plugins). Setup may replace max_tool_rounds, streaming,
system_prompt, or compaction_strategy; these model behavior settings revert
when their extension is removed. workspace and a configuration snapshot are
available while staging. Setup must not replace host-owned providers, executors,
sessions, tasks, or approval handlers. An unsupported mutation rejects the entire
candidate and retains the last working tools, commands and MCP connections.
Retained tool closures forward to the live harness after setup, so they see the
current session and approval policy. Source files and package-relative imports
are compiled in private generation namespaces, including same-size edits made
within one filesystem timestamp tick. Unrelated absolute imports retain normal
Python ownership; host and framework modules are never evicted. Put owned resource cleanup in the async
AgentPlugin.aclose method; import/setup failures must clean up resources acquired
before a hook is registered. Trusted Python still has full process privileges;
this registration contract is not a sandbox.
A generation’s hooks remain attached to the response and calls that used it.
after_run cleans up that generation’s participation before retirement, and
aclose releases it. A replacement hook initializes with before_run before its
first model request in the continuing conversation; that return value does not
rewrite the existing user message. Use before_model for new request context.
Host-installed library hooks retain the normal once-per-run lifecycle below.
Register slash commands¶
Trusted setup functions can register a prompt template or an async handler:
from nagents.harness import CommandResult
async def status(harness, arguments):
return CommandResult(message=f"Current model: {harness.agent.provider.model}")
def setup(harness):
harness.commands.register(
"review-code",
"Review a selected file",
prompt="Review this file without editing it: $ARGUMENTS",
argument_hint="<file>",
requires_arguments=True,
)
harness.commands.register("local-status", "Show local status", handler=status)
Prompt templates replace only literal $ARGUMENTS; JSON/Python braces are not
interpreted. A CommandResult contains either a local message or a prompt
for the normal agent loop. Discovery does not execute handlers. Built-in names
and the skill: namespace are reserved, and duplicate names are rejected.
Plugin attribution is automatic while setup runs. Async handlers retain full,
trusted Python access; they are not sandboxed or automatically mediated as tools.
See examples/harness/commands.py in the checkout, or launch it with
ngn --plugin examples/harness/commands.py:setup and type /.
Change agent behavior¶
The supported interfaces live in nagents.extensions and are also exported
from nagents. They do not depend on the coding harness or TUI.
| Interface | Purpose |
|---|---|
AgentPlugin.before_run |
Transform an incoming message |
AgentPlugin.before_model |
Transform request-local messages, tools, and generation configuration |
AgentPlugin.after_model |
Observe each completed response, including final text, before calls execute |
AgentPlugin.after_tools |
Observe the completed tool batch before building the next request |
AgentPlugin.aclose |
Release a retired harness extension generation’s resources |
AgentPlugin.before_tool |
Transform tool arguments before the execution permission boundary |
AgentPlugin.after_tool |
Transform the tool result |
AgentPlugin.on_event |
Observe core events |
AgentPlugin.after_run |
Run lifecycle cleanup |
CompactionStrategy.should_compact |
Decide when custom compaction should run |
CompactionStrategy.compact |
Replace the active message context using arbitrary Python logic |
Hooks run in plugin order. A plugin receives a RunContext containing the agent,
session, user, and current round. This gives trusted code access to the underlying
library rather than a second, restricted imitation of its API.
The plugin chain is snapshotted at the start of a run. Event observers receive
copies; use transformation hooks to change behavior. Tool hooks must preserve the
call ID and name. Hook failures stop the run, and cleanup attempts every plugin's
after_run hook. These extension contracts currently apply to the text agent
loop; voice, batch, and run_simple() reject extension-enabled execution rather
than silently bypassing a permission boundary.
Request transformations and persistent compaction are different operations.
before_model affects the next request; it does not append its transformed
messages to the transcript. A compaction strategy returns a CompactionResult
containing replacement active messages. Those messages are stored atomically and
the active-history boundary advances without deleting older rows. Replacement
histories must keep tool calls and results paired.
The strategy receives active history without the configured system prompt. It
can trim complete turns, call its own summarizer, retain task state, retrieve
memory, or implement another algorithm. Manual compaction uses the same strategy.
The default ModelCompaction strategy summarizes history using the selected
provider. Configure its thresholds, prompt, or summarizer provider directly;
set compaction_strategy=None to disable compaction.
See the runnable example:
That extension installs a no-LLM recent-turn compactor, adds request-local verification instructions, and registers a Python tool. It deliberately discards old active context rather than summarizing it, making the distinction between algorithm replacement and prompt customization easy to inspect.
A harness extension exports setup(harness), synchronously or asynchronously.
It may configure harness.agent and return an AgentPlugin. Existing library
features remain accessible from this trusted Python entry point; for example,
an application can compose MCP clients and register their tools without adding
MCP implementation details to the terminal widgets.
Trust and permissions¶
- Global configuration and an explicitly selected
--configfile are trusted. - Project
.ngn/config.jsonis ignored unless--trust-projectis supplied or that file is explicitly selected with--config. Review it and referenced Python code before enabling it. --plugin path.py:setupexplicitly trusts that Python code. Installed-module entry points use the samemodule:setupnotation.- Plugins have the process's full privileges. They are not sandboxed, and may use the network or write files during setup. Only load code you trust.
- Built-in file tools restrict paths to the workspace, check symlinks, and guard sensitive paths. Changes require a preview and approval, with conflict checking to avoid replacing edits made while an approval is open.
- Shell execution is local, not sandboxed. An approved shell command can access anything your user account can access, including outside the workspace.
- The reviewer profile denies modifying tools and shell execution. Skills and system prompts are not the mechanism enforcing those restrictions.
- Skills provide task guidance on demand through the Agent loader. Workspace resources retain file-tool restrictions; bundled bodies use package resources. Loading grants no permissions and does not execute scripts or automatically read referenced files.
- Interruption stops managed work but never rolls back already completed actions. An uncertain tool outcome is not automatically replayed on resume.
No automatic Git commit, reset, checkout, push, or rollback is performed. Review the diff and run appropriate checks before accepting generated changes.
Customer test loop¶
- Start with
ngn --demo; try streaming,demo approval, denial, cancellation, a new session, and session resume. - Resize to a small terminal and confirm the composer and approval controls remain usable. Exercise multiline input and command discovery.
- Configure a real provider and ask for a read-only explanation of a disposable project. Check which tools and context were used.
- Ask for a small edit. Inspect the exact preview before approving, then review the resulting Git diff and run a relevant test.
- Load
examples/harness/custom_behavior.py:setupand inspect the active extension/compaction behavior. Try your own context transform or compactor. - Report the command, terminal size/emulator, provider/model, expected behavior, and the observed failure. Do not include API keys or secret file contents.
Scope¶
The first version focuses on a local coordinating agent with bounded subagents, Python behavior extensions,
coding tools, sessions, and a terminal/headless interface. It does not yet offer
remote attachment, persistent peer-agent groups, worktree isolation, LSP integration,
extension marketplaces, cross-process exactly-once tool execution, or a secure
sandbox for arbitrary plugins and shell commands. Realtime speech remains a
separate library workflow. Guarded filesystem tools and process-group shell
cleanup currently target POSIX; Windows is not yet a supported coding-tool host.
Skills use a lightweight single-line name and description frontmatter subset;
other Agent Skills metadata does not configure tool permissions. See the
skills guide for the standard
and host-specific choices. Ignore matching is a documented subset rather than
complete Git wildmatch semantics.
For the reasoning behind these boundaries and the upstream comparison, see Harness extensibility research.