Skip to content

docs: add Sessions, Code Mode, headless/CI guide, and expand the CLI reference#3694

Open
aheritier wants to merge 14 commits into
mainfrom
docs/p1-coverage
Open

docs: add Sessions, Code Mode, headless/CI guide, and expand the CLI reference#3694
aheritier wants to merge 14 commits into
mainfrom
docs/p1-coverage

Conversation

@aheritier

Copy link
Copy Markdown
Contributor

What

Second wave of end-user documentation (P1), closing feature-coverage gaps:

  • New page — Sessions (docs/features/sessions/index.md): session storage (--session-db), resuming (--session <id>, creation-ordered relative offsets like -1), read-only sessions, /sessions browsing and tabs, session titles, and usage/cost tracking.
  • New page — Code Mode (docs/features/code-mode/index.md): what Code Mode is (code_mode_tools), when it helps, an example, and its security characteristics (nested tool calls bypass per-call permission dispatch; model-invoked handoff/transfer_task are unavailable in Code Mode while configured force_handoff still runs).
  • New guide — Running agents headless & in CI (docs/guides/headless/index.md): --exec, --json / structured output, --on-event, and a CI walkthrough built around --sandbox as the real isolation boundary (shell allow-listing is presented only as caveated defense-in-depth, not a security boundary), with a hardened GitHub Actions example.
  • Expanded CLI reference (docs/features/cli/index.md): documents docker agent getting-started, docker agent debug (+ subcommands), completion, opt-in self-update, and the --remote flag; marks --listen as hidden to match the code.
  • Small correctness fixes: MCP tool-name permission patterns corrected to the real <toolset>_<tool> form in docs/configuration/permissions/index.md; code_mode_tools cross-link in the Agents page; nav entries for the new pages in docs/data/nav.yml.

Notes

Docs-only. Every flag/field/command/subcommand was verified against current main source, and the CI guidance in particular was reworked and re-reviewed to avoid overstating what shell allow-listing provides. Passes the canonical front-matter check and markdownlint; branch is based on current main.

aheritier added 14 commits July 16, 2026 19:36
Documents session storage (session.db, --session-db), resuming
(--session with relative offsets or explicit IDs), read-only sessions,
the TUI session browser and tabs (restore_tabs), session titles
(title_model, docker agent debug title), and usage/cost tracking
(track_usage, cost:, and the fact that cumulative cost never decreases
across compaction). Wires the page into nav.yml under Features.
Documents code_mode_tools: running an agent's tools as JavaScript
functions callable from a single run_tools_with_javascript tool
instead of one model round-trip per tool call, when it helps, its
script/security limits, and its coarser tool-approval granularity
(individual tool calls made from inside the script bypass the
runtime's normal per-call permission checks). Fixes an inaccurate
one-liner for code_mode_tools in the Agent Config reference (it did
not format tool responses; it swaps in the JS-orchestration tool) and
turns it into a cross-link. Wires the page into nav.yml under
Features.
Adds command sections for `docker agent getting-started` (the
interactive tour), `docker agent debug` and its subcommands (config,
toolsets, skills, title, auth, oauth list/remove/login) as a
user-facing troubleshooting surface, and `completion`. Adds a
Self-update subsection cross-linking the existing installation-guide
walkthrough. Documents --remote's mutual exclusivity with the other
local-only run flags (session, session-db, sandbox, worktree*, record,
fake) as a normal flag, per maintainer decision. Verified every flag
against cmd/root/*.go on current main; the run flag table already
covered every other visible, non-hidden flag.
Task-oriented guide covering --exec basics, structured output for
machines (--json vs. structured_output), reacting to events via
--on-event, choosing an auto-approval strategy for unattended runs
(--yolo vs. permission allow-lists vs. the safe-auto shell policy,
with a security warning against blanket --yolo in CI), providing
secrets and disabling telemetry in CI (cross-linked to their existing
guides), and a GitHub Actions example that pulls an agent from an OCI
reference and gates its shell tool calls with a pre_tool_use hook.
Wires the page into nav.yml under Resources > Guides, after Go SDK.
…uidance

Local review of docs/p1-coverage flagged the headless guide as the most
broken page in the branch:

- --on-event silently no-ops under --exec: handleExecMode() returns
  before event hooks are parsed/installed (cmd/root/run.go), so the
  --exec examples never fired. Move the flag to the interactive/--lean
  path where hooks actually install, and show the --json/jq equivalent
  for a real --exec run.
- turn_end is not a real event type (pkg/runtime/event.go only emits
  stream_stopped on normal completion); message.delta doesn't exist
  either (assistant output is agent_choice). Fixed both.
- The 'hooks may finish after process exit' claim is unsafe: main.go
  calls os.Exit without waiting on any detached hook goroutine.
  Corrected to say hooks are killed with the process.
- The permission example used a top-level toolsets: sequence with no
  agents: block, which the schema rejects (toolsets is a named map at
  that level; at least one agent is required). Rebuilt as a valid,
  load-tested config.
- The GitHub Actions example's prefix-only bash regex claimed a
  read-only policy but let destructive suffixes through (e.g.
  'ls; rm -rf /tmp'), and claimed a CLI pre_tool_use hook overrides a
  pulled config 'regardless of what it declares' -- false: an explicit
  permission allow/deny returns before pre_tool_use hooks run
  (dispatcher.go), and CLI hooks are appended after config hooks, not
  layered on top of them (pkg/config/hooks.go). Replaced the hand-rolled
  regex with the runtime's own permissions + safer_shell(safe-auto)
  mechanism, corrected the precedence explanation, and added an honest
  caveat that command-string matching is not a sandbox.
- 'safer: true' only registers the safer_shell classifier; the policy
  it enforces still defaults to strict (asks on everything) unless
  safe-auto is explicitly pinned via a pre_tool_use hook entry
  (pkg/hooks/builtins/safer_shell.go, safer_shell_test.go). Documented
  the required pin alongside a new, load-tested example.

Adds examples/ci_safe_permissions.yaml (validated with
'docker agent run --dry-run') backing both the auto-approval section
and the GitHub Actions example, and lists it in examples/README.md.
- debug auth --json emits the full, unredacted JWT in its token field
  (cmd/root/debug_auth.go); only the text output truncates it. Add an
  explicit warning against pasting --json output into logs or bug
  reports.
- --mcp-oauth-redirect-uri (added by addRuntimeConfigFlags,
  cmd/root/flags.go) was missing from the runtime-flags table.
- --listen on 'docker agent run' is registered as MarkHidden (run.go)
  but was documented as an ordinary user-facing flag; note its hidden
  status, matching the treatment already given to the 'debug' command.
- --session's relative-ref description said '-1 = last'; sessions are
  ordered by creation time (ORDER BY created_at DESC in
  pkg/session/store.go, see store_test.go), not last-used, so resuming
  an older session never makes it the new -1. Reworded for accuracy.
- Relative session refs (-1, -2, ...) are creation-ordered, not
  most-recently-used: GetSessionSummaries ORDER BY created_at DESC
  (pkg/session/store.go, store_test.go). Resuming an older conversation
  does not make it the new -1; the next -1 still resolves to whichever
  session was created most recently. Reworded throughout.
- Automatic title generation is seeded with exactly the first message
  (pkg/app/app.go, generateTitle(ctx, []string{message})), not 'the
  first message or two', and the generator does not truncate to two
  messages (pkg/sessiontitle/generator.go). Manual /title regeneration
  does send every user message so far -- documented that distinction.
The page recommended keeping a destructive toolset on a separate,
non-Code-Mode agent 'reached via handoff or transfer_task'. Both tools
are handlerless (pkg/tools/builtin/handoff, .../transfertask) and, once
wrapped by Code Mode, are rejected at call time with 'tool "handoff"
is not available in code mode' (pkg/tools/codemode/exec.go,
pkg/teamloader/teamloader.go). A Code-Mode agent cannot delegate to
another agent at all, so the advice was unworkable. Replaced it with
the actual limitation: keep code_mode_tools off any agent that needs to
hand off or transfer to one holding a destructive toolset. The core,
already-correct claim that nested Code Mode calls bypass per-call
permission dispatch is unchanged.
The headless guide's CI security section previously centered on a
"safe-auto" shell auto-approval recipe (safer: true + a pinned
safer_shell hook) that never actually worked from the CLI:

- safer: true auto-injects its own argument-less safer_shell hook
  (defaults to strict); hook dedup keys on (type, command, args), so
  it never collapses with an explicitly pinned safer_shell entry and
  both run (pkg/hooks/builtins/builtins.go:203-216,
  pkg/hooks/executor.go:275-308). Aggregation then keeps the more
  restrictive verdict, so the pinned hook's allow never wins over the
  auto-injected hook's ask (executor.go:484-490,567-592), and the call
  is rejected outright under --exec --json (pkg/cli/runner.go:117-126).
- Even a hook that does return allow only bypasses the pipeline when
  the session's SafetyPolicy is already safe-auto, a field the HTTP
  API accepts on session create but that docker agent run has no flag
  for (pkg/session/session.go, pkg/api/types.go) - --yolo maps to
  unsafe, everything else defaults to strict.
- The GitHub Actions example's docker:github-official MCP toolset had
  no GITHUB_PERSONAL_ACCESS_TOKEN (pkg/config/gather.go:253-285,
  pkg/gateway/catalog_test.go) and, being unnamed, exposed tools under
  raw names like get_file_contents rather than the mcp:github:*
  prefix its own permission patterns assumed (pkg/tools/mcp/mcp.go:
  46-93,617-652).
- --worktree was listed alongside --sandbox as a hard boundary; it
  only isolates checkout/branch state, the shell toolset still runs
  as a native host process either way (pkg/tools/builtin/shell/
  shell.go:187-195, pkg/worktree/worktree.go).
- os.Exit was said to terminate in-flight --on-event hooks with the
  process; hooks run via exec.CommandContext(context.Background(),
  ...) and main.go exits without waiting on or signaling them
  (cmd/root/run_event_hooks.go:77-90, main.go:15-26), so a hook can
  outlive the process.

Rework the section around an honest isolation model instead:
--sandbox (docs/configuration/sandbox/index.md) is now presented as
the actual isolation boundary for untrusted/autonomous CI agents,
paired with --yolo when the blast radius needs containing. Permission
allow-lists and safer_shell are demoted to defense-in-depth, with an
explicit, verified example of how unspaced compound operators
(cmd;evil, cmd&&evil, cmd|evil) bypass safer_shell's safe-list
(safer_shell.go:248-275,391-418: containsShellSeparator only catches
space-padded operators, and a bare "..." wildcard pattern like
"echo ..." then swallows the unspaced tail). The broken safe-auto
recipe and the unauthenticated GitHub MCP toolset are removed; the
GitHub Actions example now uses --yolo (justified qualitatively,
since a fixed shell allow-list can't cover an open-ended review task)
with a callout to add --sandbox where the runner supports it.

examples/ci_safe_permissions.yaml demonstrated exactly the broken
safe-auto recipe, so it is deleted rather than patched, along with its
examples/README.md entry.
Code Mode (docs/features/code-mode/index.md): a configured
force_handoff target still runs deterministically after a natural
stop regardless of Code Mode (pkg/runtime/loop.go:937-951) - it's
applied by the runtime outside the tool-call dispatch path Code Mode
replaces. Only the model *choosing* to call handoff/transfer_task as
a tool fails in Code Mode; qualify the 'cannot delegate at all' claim
accordingly.

Sessions (docs/features/sessions/index.md): automatic session-title
generation calls the title model directly
(pkg/app/app.go:502-505, pkg/sessiontitle/generator.go:134-165)
without going through the usage-tracking call sites
(pkg/runtime/harness.go, pkg/runtime/streaming.go), so it is not
folded into the session's cumulative cost/token totals. Clarify that
'every model call' means every tracked conversation/compaction call.
The sandbox VM matching the current workspace/mount set is retained
and reused across runs (pkg/sandbox/sandbox.go Ensure(); docs
configuration/sandbox/index.md #how-it-works), not torn down after
each session as "disposable" implied.

Also document that --sandbox already injects --yolo by default
unless the user passes their own --yolo flag (cmd/root/sandbox.go
dockerAgentArgs; sandbox_test.go), so the earlier example already
runs unattended and --yolo=false is the opt-out. Drop the redundant
paired-flag example that implied --yolo was required alongside
--sandbox.
…attern

actions/checkout@v4 defaults `persist-credentials: true` and its
token defaults to `${{ github.token }}`, so write capability tracked
the repo's default GITHUB_TOKEN permissions rather than the "no
repo-write token" claim made just below the example. Add a top-level
`permissions: contents: read` block and `persist-credentials: false`
on the checkout step so the job provably never holds or persists a
write-capable token, and update the surrounding prose to match.

Also fix the optional GitHub MCP paragraph: MCP tool names are
`<toolsetname>_<tool>` with an underscore, not the colon-style
`mcp:github:get_*` this page still showed (pkg/tools/mcp/mcp.go
Tools(); mcp_test.go). A toolset named `github` exposes
`github_get_issue`, not `mcp:github:get_issue`.
…ames

Same colon-style tool-name syntax as the headless guide's GitHub MCP
paragraph pre-existed here. MCP tool names are `<toolsetname>_<tool>`
with an underscore (pkg/tools/mcp/mcp.go Tools(); mcp_test.go), so a
toolset named `github` exposes `github_get_*`/`github_list_*`/etc.,
never `mcp:github:*`. Fix only the pattern syntax in the examples;
the rest of the page is unchanged.
The "Cost never decreases" callout still said the total updates
"after every model call" and that /cost reflects "everything the
session has actually spent" -- contradicting the corrected line just
above it. Automatic title generation calls the model directly outside
session usage accounting (pkg/app/app.go Run(); pkg/sessiontitle/
generator.go generateOnce()), so it isn't part of that total. Qualify
the callout to the same tracked conversation/compaction calls, and
call out that title-generation calls are excluded.
@aheritier
aheritier requested a review from a team as a code owner July 16, 2026 18:33
@aheritier aheritier added area/docs Documentation changes kind/docs Documentation-only changes labels Jul 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/docs Documentation changes kind/docs Documentation-only changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant