-
Multi-user Shiny isolation:
codeagent_app(client_factory = function(session) ...)now creates a freshCodeagentClient/Chat inside every Shiny session; the no-client default uses this safe path automatically. Passing a pre-builtclientremains the explicitly single-user compatibility path. Data Shield protected values now live in a session-scopedDataShieldStatethat may be shared among that user's chat threads without cross-user index leakage. -
Data Shield P0/P0.5 (opt-in, default OFF):
codeagent_client(data_shield = ...)andinstall_data_shield()wrap all registered tool functions so bulk row-level results are replaced with a shape summary before reaching the LLM.register_protected_data()adds deterministic high-entropyvalue_matchprotection for targeted single-value leaks that the row-cap cannot detect. Includes deterministic, live-LLM, and runtime-upload Shiny examples. Protected values now live in an explicit per-sessionDataShieldState; one browser session may share it across chat threads without any cross-user index leakage. P1 adds the automatically registeredDescribeDatatool: strict safe metadata only (schema, sensitivity, missing presence, measure/open ranges and k-supported low-cardinality labels), with no distributions, counts, raw rows, or free-text examples. -
Backend permission gate for host tools: new exported
install_permission_gate(chat, permission_mode, rules, tools, ask_fn, tool_meta)lets a harness-only client (register_tools = FALSE) put the central permission gate over host-attached tools (the gate is otherwise only installed by.register_all_tools()). The gate now also passes the tool-callidtoask_fns that accept it (ask_fn(name, input, id = NULL)), so hosts can match an approval prompt to theon_tool_requestpreview; legacy(name, input)ask_fns are unchanged. -
Backend embedding (Contract v1): documented, stable surface for hosting codeagent as a backend engine. New exported
register_tool_meta(name, capability)lets host apps declare a custom tool's capability (read/write/exec/net) so the central permission gate governs it (an undeclared tool previously defaulted toreadand was allowed ungated). New exportedtool_result(value, kind, payload)builds a typed display card (text/table/image/code/diff/error) that reaches theon_tool_result$displaycallback and the Shiny app. Adds thebackend-integrationvignette + a reference example (inst/examples/backend_integration_demo.R) + a contract guard test. -
Concurrent sub-agents (opt-in via
settings$async_subagents, default OFF): theAgenttool can run asynchronously so multiple sub-agent delegations requested in a single turn execute concurrently (via ellmer'stool_mode = "concurrent") on the async streaming paths (CLI REPL / Shiny app). Sync one-shotcodeagent()transparently falls back to sequential sub-agents, since async (promise-returning) tools are invalid underChat$chat(). Also adopts ellmer 0.4.2 (set_model(),chat_posit()). -
Background sub-agents (opt-in via
settings$background_agents, default OFF; requiresmirai): a newBackgroundAgenttool delegates a task to a fire-and-forget sub-agent running in amiraidaemon and returns immediately (non-blocking). Its result is polled and surfaced back to the model on a later turn via the system reminder, mirroring Claude Code's async agents. Users can also spawn and inspect background agents directly with the/bg <task>and/bgstatusslash commands (REPL and Shiny).
First public release. codeagent is an R-native reimplementation of a
command-line coding agent, built on ellmer and btw. It provides the agent
harness (loop, tools, permissions, compaction, hooks, skills) plus a CLI REPL
and a shiny user interface.
Three environment variables have been renamed to remove vendor-specific names.
Update your .Renviron / settings.json env block accordingly:
| Old | New | Meaning |
|---|---|---|
CODEAGENT_DEFAULT_SONNET_MODEL |
CODEAGENT_MODEL |
Everyday main model |
CODEAGENT_DEFAULT_OPUS_MODEL |
CODEAGENT_HEAVY_MODEL |
High-capability model |
CODEAGENT_SMALL_FAST_MODEL |
CODEAGENT_FAST_MODEL |
Cheap/fast model |
Tier aliases used in /model and codeagent.md also changed:
"sonnet" → "main", "opus" → "heavy", "haiku" → "fast".
CODEAGENT_MODEL now serves dual purpose: it sets both the default model and
the "main" tier alias (previously CODEAGENT_MODEL and
CODEAGENT_DEFAULT_SONNET_MODEL were separate; they are now merged).
-
Default permission mode is now
"default"across all entry points (CLI, Shiny, ink). Previously all CLI subcommands defaulted to"bypass". Write operations (file edits, shell commands) now prompt for approval unless you explicitly opt into bypass mode. -
-y/--yolo— new global flag for the CLI that enables bypass mode (skips all permission prompts). Equivalent to Claude Code's--dangerously-skip-permissions. Short-hand:-y.codeagent -y # bypass REPL codeagent app -y # bypass Shiny app codeagent run "q" -y # bypass one-shot query -
codeagentwithout a subcommand now starts the interactive REPL directly (equivalent tocodeagent chat). Previously a subcommand was required. -
-p/--print-mode— new flag for one-shot non-interactive output.codeagent "query"orcodeagent -p "query"runs a single query and exits. -
-mnow means--model(breaking). The old-m/--modealias has been removed. Use-y/--yolofor bypass mode instead. -
ink_ui()gainsyolo = FALSEparameter. WhenTRUE, setsINK_YOLO=1so the codeagent backend runs in bypass mode. Theinkaiterminal command also accepts-y/--yolo.ink_ui("codeagent", yolo = TRUE) # bypass # or from terminal: inkai codeagent -y # bypass -
INK_YOLOenv var — set to"1"to enable bypass mode in ink when launching theinkaicommand directly:INK_YOLO=1 inkai codeagent. -
codeagent_app(permission_mode = "default")unchanged — Shiny was already correct; passpermission_mode = "bypass"explicitly when needed. -
R/cli_dispatch.R— new internal helpers.ca_resolve_mode()and.ca_dispatch()expose CLI dispatch logic as testable pure functions.
| Old | New |
|---|---|
codeagent chat |
codeagent |
codeagent chat -m bypass |
codeagent -y |
codeagent -m bypass |
codeagent -y |
ink_ui("codeagent") (was bypass) |
ink_ui("codeagent", yolo = TRUE) for bypass |
inkai codeagent (was bypass) |
inkai codeagent -y for bypass |
-
codeagent_stream_async()— new exported function. Streams one agent turn asynchronously (coro::asyncpromise). Runs the full turn pipeline (compaction, system-reminder injection, session save, cost tracking viaget_cost(include="last")). Fires typed callbacks:on_delta,on_thinking,on_tool_request(pre-gate, from stream chunk),on_tool_result(with typeddisplaycontract from.adapt_tool_result()),on_error,on_usage. Supportsstream_controllerfor cancellation andtool_modefor concurrent tool execution. -
codeagent_stream()— synchronous wrapper aroundcodeagent_stream_async()usinglater::run_now()to pump the event loop. Handles Ctrl+C gracefully (cancels the stream viastream_controller, does not re-throw the interrupt condition). Intended for CLI and ink frontends. -
Turn pipeline helpers (
R/turn_pipeline.R, internal):.turn_setup()consolidates compaction + resource replacement + system-reminder injection into one call..turn_teardown()consolidates session save + usage +cost_last. Both are now shared by console, Shiny, and ink. -
Shiny system-reminder injection fixed.
server_chat.R'sstream_tasknow injects the<system-reminder>block (date/iteration/cwd/memory) on every turn, matching the behaviour of the console REPL andagent_loop(). -
Console Ctrl+C repair.
codeagent_console()now creates astream_controllerper turn and catchesinterruptconditions, cancelling the stream gracefully. Previously Ctrl+C could corrupt the chat state. -
Callback deduplication.
.register_repl_tool_callbacks()is now guarded by.chat_once()to prevent stacking display callbacks ifcodeagent_console()is called more than once on the same chat object. -
.patch_interrupted_chat()retired. Removed from all call sites. ellmer 0.4.0+ (#840) and 0.4.1+ (#643) handle orphaned tool requests andAssistantPartialTurnautomatically. -
inkAssistantUItool cards upgraded.ink_reply_stream()now callscodeagent_stream()when available (full turn pipeline + display contract).on_tool_resultreceives a richdisplayfield (title/kind/payload) instead of a plain string. Theink_server()initialises per-sessionCompactionController/ContentReplacementState/session_idso turns are properly managed.
- Task DAG.
team_coordinate()gainsblocked_by— task dependencies given by 1-based index. A task is only claimed once all its blockers aredone, so workers respect ordering while still parallelising independent tasks. Cyclic graphs are rejected up front. The shared board's claim is now dependency-aware and atomic (BEGIN IMMEDIATE). - Worktree isolation + crash recovery.
worktree = TRUEruns each worker in its own git worktree;board_reclaim_stale()(wired into the worker loop) resets a crashed worker's timed-out task back to pending so its dependents are never blocked forever. - Event-driven board.
board_watch()(built on thewatcherpackage) reacts to board changes without polling, powering an event-driven coordinator / live board view (falls back to polling when watcher is unavailable). - LLM-lead coordinator.
team_lead(goal, max_rounds =)faithfully ports Claude Code's COORDINATOR_MODE: a lead model decomposes the goal into a task DAG, the work-stealing team runs it, then the lead reviews results and either finishes or adds a follow-up round (bounded loop; decompose/review/coordinate steps are injectable for testing). - Live dashboard.
team_dashboard(db_path)is a standalone Shiny app that monitors a running team's board in real time — task table (coloured by status), a progress bar, and the inter-agent message log.
-
Instant startup. The UI shell now renders immediately; the slower tool + skill registration runs in the background behind a prominent "Initializing codeagent…" overlay, with the chat input gated until it completes. Pass a bare
ellmer::Chattocodeagent_app()for this lazy path. -
Skill metadata disk cache.
list_skills_meta()now caches parsed skill metadata on disk (<config>/cache/skills/, keyed by cwd + aSKILL.mdmtime/count signature), so the slash typeahead and skill tool are near-instant on every launch after the first (a disk hit skips the ~20s directory scan). The cache self-invalidates when anySKILL.mdchanges or a skill is added/removed. -
Single-file viewer. Clicking a file in the Files tree now opens it in one static, scrollable "File" tab (code / Markdown / image / CSV) with a filename header and close button, replacing the old per-file tabs that could overflow and cover the tab strip.
-
keyring integration (
R/keyring.R): Optional API key storage via the OS credential store (keyringpackage).setup.Roffers the keyring as an alternative to~/.Renvironwhen the backend is available. Includes.keyring_available()(session-cached probe),.keyring_store_key()with graceful fallback to~/.Renviron, and.keyring_get_key(). On headless/server environments the keyring backend probe returnsFALSEand all functions degrade silently to the existing~/.Renvironpath. -
webfakes agent integration tests (
tests/testthat/test-webfakes-agent.R): 12 tests that mock the LLM API endpoint withwebfakes, exercising the full agent loop — tool dispatch (Read, Write, Bash), permission gate (bypass vs plan), error recovery (HTTP 500), and skill invocation — without hitting a real LLM. -
Explicit tool names:
bash_tool(),read_tool(),write_tool(),edit_tool(),multi_edit_tool(),glob_tool(),grep_tool(),ls_tool()now passname=toellmer::tool()so the model can refer to tools by their canonical names (Bash, Read, Write, Edit, MultiEdit, Glob, Grep, LS).
- Agentic loop (
agent_loop()) with max-turns, token budget, verification, and error recovery (prompt-too-long, rate-limit, network, and auth handling). - Seven-mode permission system:
default,plan,accept_edits,bypass,dont_ask,auto, andbubble(sub-agent decisions bubble to the parent). Fine-grained rules match on tool arguments (e.g.Bash(npm run test *)). - Twelve-event hook system covering tool, permission, message, and lifecycle
events, configurable declaratively from
settings.json. - Five-level context compaction (snip, session memory, full summary, prompt fallback, context collapse).
- System prompt with tone, task, convention, tool-use, and R-specific guidance.
- Core tools:
Bash,Read,Write,Edit,MultiEdit,Glob,Grep,LS. RunRexecutes R code behind the permission gate; with sandboxing enabled it runs in an isolatedcallrsubprocess with a scrubbed environment (secrets hidden), no.Renvironreload, and a wall-clock timeout.btwtool groups (docs, git, pkg, env, etc.), web fetch and search, notebook tools, task and persistent-todo tools.- Optional codebase retrieval via
ragnar(vector + keyword search).
- Sub-agents via
agent_tool(), with optional git-worktree isolation and persistent "sidechain" sessions. - Parallel teams:
team_run()(fixed fan-out) andteam_coordinate()(work-stealing over a shared SQLite board with inter-agent messaging), both capped to the container's CPU quota viaparallelly. - Plan-mode tools let the model enter and exit read-only planning mid-turn.
- Sessions saved as JSONL with lossless tool-call preservation; fork, rename,
tag, resume, and rewind (
truncate_chat_turns()//rewind). - Auto-memory persisted across sessions, with relevance selection by a small fast model.
settings.jsonconfiguration mirroring command-line agents:envblock, model tiers, permissions, hooks, MCP servers, sandbox, effort level, and more.- Model switching mid-conversation (
switch_model()), lossless where possible.
- CLI:
codeagentexecutable withrun,chat/repl,app,skills,mcp, andinfosub-commands; the REPL streams output, shows tool activity, and renders reasoning blocks. shinyapp (codeagent_app()) with tool cards, session management, and theme options.- MCP server (
codeagent_mcp_server(), stdio and HTTP) and MCP client (register_mcp_client(), stdio) for external tool interoperability.