Package 'corteza'

Title: AI Agent Runtime
Description: An agent runtime that gives Large Language Models (LLMs) from 'Anthropic' <https://www.anthropic.com/>, 'OpenAI' <https://openai.com/>, 'Moonshot' <https://www.moonshot.ai/>, and 'Ollama' <https://ollama.com/> direct access to a live R session with managed workspace state. Tools execute as R function calls with provenance tracking, and a deterministic retrieval system keeps relevant objects in context across turns. Three entry points: a shell command-line interface (CLI), a console read-eval-print-loop via chat(), and a Model Context Protocol (MCP) server via serve() for external clients.
Authors: Troy Hernandez [aut, cre] (ORCID: <https://orcid.org/0009-0005-4248-604X>), Grant McDermott [ctb] (ORCID: <https://orcid.org/0000-0001-7883-8573>), Jorge Krzyzaniak [ctb], cornball.ai [cph]
Maintainer: Troy Hernandez <[email protected]>
License: Apache License (>= 2)
Version: 0.8.0
Built: 2026-11-15 08:55:44 UTC
Source: https://github.com/cranhaven/cranhaven.r-universe.dev

Help Index


Add a tool-call observer to a session

Description

Observers run after every tool call (run, denied, or declined). They receive a single event list with fields:

Usage

add_observer(session, observer)

Arguments

session

A session environment from new_session.

observer

A function of one argument (the event list).

Details

  • call — the call list passed to policy().

  • decision — the policy decision for the call.

  • outcome — one of "ran", "deny", "declined".

  • result — the string returned to the LLM.

  • success — logical; TRUE only for "ran" with no tool error.

  • elapsed_ms — wall time including policy overhead.

  • turn_number — the session's tool-call counter.

Errors raised inside an observer are swallowed.

Value

The session, invisibly.

Examples

s <- new_session(provider = "anthropic")
add_observer(s, function(event) {
    # An observer is just a function of one argument; record the
    # outcome for inspection.
    message(event$outcome)
})
length(s$on_tool)

Expire unpinned audit payloads explicitly

Description

Never runs automatically. Only files in the named writer's payload store are eligible; event metadata and other writers' stores are left intact. Expiry decisions append to a separate maintenance stream before deletion. Event retention is a planning value until an explicit event-retention tool is used.

Usage

audit_payload_expire(store, days = 30, now = Sys.time(), dry_run = TRUE)

Arguments

store

A writer's payload store path from a payload reference.

days

Retain this many days, default 30.

now

Comparison time, default Sys.time().

dry_run

Report eligible hashes without deleting, default TRUE.

Value

Character vector of eligible (or deleted) hashes.

Examples

## Not run: 
audit_payload_expire(ref$store)

## End(Not run)

Pin or unpin an audit payload

Description

Pins keep incident/evaluation evidence through explicit expiry. Pinned bytes still count toward the store quota; capture reports quota omissions when full.

Usage

audit_payload_pin(ref, pin = TRUE)

Arguments

ref

Payload reference from an audit event.

pin

TRUE to pin, FALSE to remove the pin.

Value

The payload reference, invisibly.

Examples

## Not run: 
audit_payload_pin(ref)

## End(Not run)

Read an immutable audit payload

Description

Reads only a payload whose bytes still match its recorded SHA-256. Redaction affects the returned view, never the stored record. An omitted, expired, missing or damaged payload is an error, not an empty successful read.

Usage

audit_payload_read(ref, redact = TRUE, secrets = character())

Arguments

ref

Payload reference from an audit event or request manifest.

redact

Apply display redaction. Defaults to TRUE.

secrets

Additional known credential values to hide in the view.

Value

The decoded JSON object or UTF-8 text, as recorded.

Examples

## Not run: 
audit_payload_read(ref)

## End(Not run)

Read an audit event stream

Description

Uses the auto-log decoder, retaining malformed-line locations and separating a torn final line from interior damage. A missing terminal turn record means unresolved completion. Redaction changes this returned view only; callers can supply additional known credential values. It cannot detect all secrets.

Usage

audit_read(path, redact = TRUE, secrets = character())

Arguments

path

Path to a writer's JSONL stream.

redact

Apply display redaction, default TRUE.

secrets

Additional known credential values to hide.

Value

List with records, auto-run start/end compatibility fields and malformed-record diagnostics. Turn endings remain in records.

Examples

audit_read(tempfile())

Flush all in-memory matrix sessions to the pensar vault

Description

Walks the per-room session registry and archives each room's unarchived Matrix events via the pensar archive ingest. Archived events are consumed from the session ledger, and their Matrix event ids are persisted per room under CORTEZA_STATE_DIR so a restart's backfill is recognized rather than archived again. Silent no-op when pensar is not installed.

Usage

bot_archive_all(sessions, chat = NULL)

Arguments

sessions

A registry environment built by bot_run/bot_poll. Keys are room IDs, values are session environments carrying $transcript, the Matrix-visible event ledger.

chat

Optional chat.api client for room-name lookups. When NULL, the room ID is used as the source identifier.

Value

Integer count of rooms ingested, invisibly.

Examples

## Not run: 
# Requires a running Matrix session registry and the optional
# pensar package for the actual archive step.
reg <- new.env(parent = emptyenv())
bot_archive_all(reg)

## End(Not run)

Configure the Matrix channel for this host

Description

Logs in to a Matrix homeserver as the bot account, joins (or records) the target room, and writes credentials to tools::R_user_dir("corteza", "config")/matrix.json with file mode 0600. Call once per host. Model, provider, tools_filter, and auto_approve_asks are defaults the poll loop uses unless overridden at call time.

Usage

bot_configure(
  server,
  user,
  password,
  room,
  model = NULL,
  provider = "anthropic",
  tools_filter = NULL,
  auto_approve_asks = FALSE,
  bots = NULL,
  models = NULL,
  model_badge = c("never", "non_default", "always"),
  display_name = NULL,
  fallback = NULL
)

Arguments

server

Character. Homeserver base URL.

user

Character. Bot localpart or full Matrix ID.

password

Character. Bot password. Stored locally so the bot can re-authenticate if its access token is invalidated.

room

Character. Room ID or alias the bot should read and post to. If the bot has been invited but not joined, it will be joined.

model

Character or NULL. Default model name.

provider

Character. LLM provider: "anthropic", "anthropic_claude", "openai", "openai_codex", "moonshot", or "ollama".

tools_filter

Character vector or NULL. Passed to get_tools() to restrict which tools the bot can invoke. NULL allows all registered tools.

auto_approve_asks

Logical. Who rules on a tool call that needs approval. Rules in code read every call first, and what they catch (credentials, writes outside the project, elevated privileges, other machines, publishing, discarded work) is always put to a person by reaction in the room. When TRUE, the rest goes to a monitor: a read-only model in its own process that approves, refuses, or passes the call on to a person. When FALSE (default) a person is asked for the rest as well. Nothing is approved unseen. The monitor's model is set under supervisor in the user's corteza config; see vignette("configuration").

bots

Character vector or NULL. Full Matrix IDs of other known bot accounts. Their messages only get a reply when they mention this bot, and they are not counted as humans when deciding whether a room gets ungated replies (a room whose only non-bot member is one human is answered without a mention).

models

Character vector or NULL. Extra entries for the /model menu, each a "model provider" pair (e.g. "claude-sonnet-4-6 anthropic_claude"; a bare model name uses the default provider). The menu always lists the configured default and the live local Ollama inventory; this key adds hosted models that can't be discovered automatically.

model_badge

Character. When to show which model is answering: "never" (default, current behavior), "non_default" (only while a /model switch has moved a room session off the configured default – silence means the default, a badge means you are spending something else), or "always". When active, replies get a lightning-bolt first line naming the model and provider, and the bot renames itself to "<name> <bolt> <model>" so every message wears the model in its sender line. The display name is account-global: with sessions in several rooms, the most recent switch wins (the per-reply badge line is always room-accurate).

display_name

Character or NULL. Base display name the badge rename builds on. Defaults to the localpart of the bot's user id.

fallback

Character vector or NULL. What turn tries, in order, when the default provider refuses a request with a limit error (rate, usage, or quota). Same "model provider" shape as models, e.g. c("gpt-5.6-sol openai_codex", "claude-haiku-4-5 anthropic"). A provider that hit a limit is skipped for fallback_cooldown_minutes (config key, default 30). Set fallback_primary_retry_at to a weekly boundary such as "Mon 03:00" to retry the primary after its account resets. anthropic_claude and openai_codex are subscription providers; a fallback onto an API-key provider such as anthropic or openai is labeled as billable usage prominently in the reply. reasoning_effort, when configured, applies to every candidate. NULL (default) means a limit error is reported like any other error.

Details

Pre-CRAN releases stored the file at ~/.corteza/matrix.json; that path is still read for backward compatibility, but the next bot_configure() call writes to the new location.

Value

The saved configuration, invisibly.

Examples

## Not run: 
# Requires a real Matrix server and bot credentials. Configuration
# is written under tools::R_user_dir("corteza", "config").
bot_configure(
    server = "https://matrix.example.org",
    user = "bot",
    password = "secret",
    room = "!roomid:example.org"
)

## End(Not run)

One iteration of sync-and-reply

Description

Fetches new messages across all joined rooms and runs turn against each. Auto-joins any pending invites the bot has received. Replies are sent back to the originating room. On first run there is no saved sync token, so this call establishes a baseline and returns without processing history.

Usage

bot_poll(
  system = NULL,
  model = NULL,
  provider = NULL,
  tools_filter = NULL,
  timeout = 0L,
  sessions = NULL,
  hooks = NULL
)

Arguments

system

Character or NULL. System prompt override.

model

Character or NULL. Model override.

provider

Character or NULL. Provider override.

tools_filter

Character vector or NULL. Tool filter override.

timeout

Integer. Long-poll timeout in milliseconds. 0 returns immediately.

sessions

Environment from bot_new_session_registry() keyed by room_id, or NULL to build fresh sessions each call.

hooks

Named list of functions or NULL, as validated by bot_run_init; see its Hooks section. NULL asks the host nothing.

Details

Pass sessions = NULL (the default) for a stateless one-shot — each incoming message builds a fresh session. Pass a registry created by bot_new_session_registry() so a long-running bot_run keeps a separate history per room (conversations in different rooms don't cross-contaminate).

Value

An integer count of messages replied to, invisibly.

Examples

## Not run: 
# Single poll cycle against the configured Matrix homeserver.
bot_poll(timeout = 5000L)

## End(Not run)

Ask the running matrix bot to archive sessions to pensar

Description

Drops an archive.signal file in the corteza state directory. The next iteration of the long-poll loop in bot_run picks it up, runs bot_archive_all, and removes the file. Safe to call from any process or scheduler — systemd, Task Scheduler, launchd, cron, or a separate R session — without needing to know the bot's PID or share its memory.

Usage

bot_request_flush()

Value

The signal file path, invisibly.

Examples

# Writes a sentinel file under CORTEZA_STATE_DIR (or the package's
# R_user_dir data path). Redirect to a tempdir for the example so
# we don't touch persistent state.
old <- Sys.getenv("CORTEZA_STATE_DIR")
Sys.setenv(CORTEZA_STATE_DIR = file.path(tempdir(), "state"))
sig <- bot_request_flush()
file.exists(sig)
unlink(Sys.getenv("CORTEZA_STATE_DIR"), recursive = TRUE)
Sys.setenv(CORTEZA_STATE_DIR = old)

Run the Matrix adapter as a long-poll loop

Description

Creates one session up front and reuses it across polls so conversation history accumulates within the process lifetime. Intended as the entry point for a systemd user unit. A thin wrapper over bot_run_init plus a bot_run_step loop; call those two directly when an external scheduler needs to own the main process.

Usage

bot_run(
  timeout = 30000L,
  system = NULL,
  model = NULL,
  provider = NULL,
  tools_filter = NULL
)

Arguments

timeout

Integer. Long-poll timeout in milliseconds.

system

Character or NULL. System prompt override.

model

Character or NULL. Model override.

provider

Character or NULL. Provider override.

tools_filter

Character vector or NULL. Tool filter override.

Value

Never returns under normal operation. Crashes on fatal error so systemd can restart.

This is a bare bot: it runs with no hooks, so /clear archives and resets without filing the conversation anywhere, and a thread reply starts from the thread's own history. A host that wants segment rooms and rehydration drives the loop itself through bot_run_init and supplies them.

See Also

bot_run_init, bot_run_step

Examples

## Not run: 
# Run the Matrix bot loop -- typically launched by a systemd unit
# rather than from an interactive R session.
bot_run()

## End(Not run)

Initialize the Matrix long-poll state

Description

Performs everything bot_run does before its loop: builds the per-room session registry, catches up on invites that predate the saved sync token, and backfills recent room history into the registry. Returns an opaque state object to drive with bot_run_step.

Usage

bot_run_init(
  system = NULL,
  model = NULL,
  provider = NULL,
  tools_filter = NULL,
  hooks = NULL
)

Arguments

system

Character or NULL. System prompt override.

model

Character or NULL. Model override.

provider

Character or NULL. Provider override.

tools_filter

Character vector or NULL. Tool filter override.

hooks

Named list of functions or NULL. Policy the loop asks the host about; see Hooks. NULL, the default, means a bare bot: /clear archives and resets without filing anything, and a thread reply starts from the thread's own history.

Details

End-to-end encryption is not set up here. When the config sets e2ee, the chat.api Matrix adapter owns the Olm account and Megolm sessions and both directions of encrypted traffic, so there is no crypto state for this package to build or carry.

Use this with bot_run_step() when an external loop owns the main process and needs to interleave the Matrix poll with other work (a scheduler, a multiplexer, an embedding host). For a standalone bot, call bot_run, which wraps both.

Value

A list holding the session registry, startup session handle, archive-flush signal path, and the saved poll options. Pass it to bot_run_step.

Hooks

corteza runs a conversation. What becomes of one once it ends, and where a thread's earlier context comes from, belong to the host that runs the bot over time (cerebro files segments and folds them into topic rooms). Two slots, both optional:

on_clear(chat, cfg, room_id, title, vault_ref)

Called from the /clear handler once the conversation is archived, when the user said something in it and the clear was not inside a thread. chat is the live chat.api client, cfg the bot config, title the segment title corteza derived from the transcript's opening line, vault_ref the archive's vault-relative path (or NULL). Returns list(id, name) for a room it filed the conversation into, which the acknowledgement names, or NULL for none. An error is reported and treated as none: archive-and-reset is the critical path.

thread_context(chat, room_id, thread_root)

Called once per thread session before its first reply. Returns list(segment, vault) naming the archived conversation the thread continues, or NULL when it continues nothing; vault is the transcript's path, vault-relative or absolute. A result seeds the session with the transcript's tail.

See Also

bot_run_step, bot_run

Examples

## Not run: 
# Drive the loop yourself instead of calling bot_run():
state <- bot_run_init()
repeat bot_run_step(state, timeout = 30000L)

## End(Not run)

One Matrix long-poll iteration

Description

Polls /sync once (blocking up to timeout ms, returning early when a message arrives), runs the agent against any new messages and posts the replies, then services a pending archive-flush signal. Mutates the session registry held in state in place, so successive calls accumulate conversation history.

Usage

bot_run_step(state, timeout = 30000L)

Arguments

state

A state object from bot_run_init.

timeout

Integer. Long-poll timeout in milliseconds.

Value

Invisibly, the integer count of messages replied to this poll.

See Also

bot_run_init, bot_run

Examples

## Not run: 
state <- bot_run_init()
bot_run_step(state, timeout = 5000L)

## End(Not run)

Send a message to a Matrix room

Description

Send a message to a Matrix room

Usage

bot_send(text, room_id = NULL, msgtype = "m.text", markdown = FALSE)

Arguments

text

Character. Plain text body.

room_id

Character. Matrix room id. Defaults to cfg$room_id from the saved Matrix config (see bot_configure).

msgtype

Character. Matrix msgtype, default "m.text".

markdown

Logical. If TRUE, also send Matrix custom HTML derived from a conservative markdown subset.

Value

The event ID of the sent message.

Examples

## Not run: 
# Requires bot_configure() to have run.
bot_send("hello from corteza")

## End(Not run)

Start Interactive Chat

Description

Run a conversational agent inside your R session. Tools execute as direct function calls, no MCP server needed.

Usage

chat(
  provider = NULL,
  model = NULL,
  tools = NULL,
  session = NULL,
  max_turns = NULL,
  web_search = NULL
)

Arguments

provider

LLM provider: "anthropic", "openai", "moonshot", or "ollama". Defaults to the saved provider when resuming, otherwise the config value or "anthropic".

model

Model name. Defaults to the saved model when resuming with the same provider, otherwise the matching config value or provider default.

tools

Character vector of tool names or categories to enable. Categories: file, code, r, data, web, git, chat, memory. Use "core" for file+code+git, "all" for everything (default).

session

Session resume control. NULL (default) starts fresh, TRUE resumes the latest session, or a character session key to resume a specific session.

max_turns

Integer or NULL. Maximum LLM turns per user prompt before the loop stops with [Max turns reached]. NULL (default) reads getOption("corteza.max_turns"), then falls back to the session_setup default (50).

web_search

Logical or NULL. Enable provider-native (server-side) web search for the conversation. NULL uses the config default (on for supported providers); the model searches inside its own turn with no Tavily key required.

Value

The session object (invisibly).

Examples

if (interactive()) {
    # Start chatting with defaults from config
    chat()

    # Use a specific provider/model
    chat(provider = "ollama", model = "llama3.2")

    # Minimal tools for focused work
    chat(tools = "core")
}

Detect the preferred local Ollama model

Description

Walks getOption("corteza.local_models") (default c("gpt-oss:120b", "gpt-oss:20b")) and returns the first one that is currently installed in the local Ollama server. Returns NULL if Ollama is unreachable or none of the candidates are installed. Cached per R process.

Usage

default_local_model()

Value

Character scalar model name, or NULL.

Examples

# NULL when Ollama isn't running locally; a model name otherwise.
model <- default_local_model()
is.null(model) || is.character(model)

Install corteza CLI

Description

Install the corteza command-line tool to a directory in your PATH. On Unix (Linux, macOS) installs the Rscript shebang binary. On Windows installs a .cmd wrapper alongside the script so corteza works from cmd.exe / PowerShell.

Usage

install_cli(path = NULL, force = FALSE)

Arguments

path

Directory to install to. Default is ~/bin on Unix, tools::R_user_dir("corteza", "data")/bin on Windows.

force

Overwrite existing installation.

Details

Requires:

  • r (littler) for fast R script execution (Unix only — Windows uses Rscript).

  • The llm.api package for LLM connectivity

  • The corteza package itself

After installation, run corteza from any terminal (you may need to add the install directory to PATH; the function prints the PATH hint if it isn't already there).

Value

The installed script path, invisibly.

Examples

## Not run: 
install_cli()
install_cli("/usr/local/bin")

## End(Not run)

Deprecated: use bot_archive_all()

Description

Deprecated: use bot_archive_all()

Usage

matrix_archive_all(sessions, mx_sess = NULL, chat = NULL)

Arguments

sessions

A registry environment built by bot_run/bot_poll. Keys are room IDs, values are session environments carrying $transcript, the Matrix-visible event ledger.

mx_sess

Ignored. Kept so calls written for corteza 0.7.1 still run; pass chat to look up room names.

chat

Optional chat.api client for room-name lookups. When NULL, the room ID is used as the source identifier.

Value

See bot_archive_all.

See Also

bot_archive_all

Examples

## Not run: 
matrix_archive_all(sessions)  # use bot_archive_all()

## End(Not run)

Deprecated: use bot_configure()

Description

Deprecated: use bot_configure()

Usage

matrix_configure(
  server,
  user,
  password,
  room,
  model = NULL,
  provider = "anthropic",
  tools_filter = NULL,
  auto_approve_asks = FALSE,
  bots = NULL,
  models = NULL,
  model_badge = c("never", "non_default", "always"),
  display_name = NULL,
  fallback = NULL
)

Arguments

server

Character. Homeserver base URL.

user

Character. Bot localpart or full Matrix ID.

password

Character. Bot password. Stored locally so the bot can re-authenticate if its access token is invalidated.

room

Character. Room ID or alias the bot should read and post to. If the bot has been invited but not joined, it will be joined.

model

Character or NULL. Default model name.

provider

Character. LLM provider: "anthropic", "anthropic_claude", "openai", "openai_codex", "moonshot", or "ollama".

tools_filter

Character vector or NULL. Passed to get_tools() to restrict which tools the bot can invoke. NULL allows all registered tools.

auto_approve_asks

Logical. Who rules on a tool call that needs approval. Rules in code read every call first, and what they catch (credentials, writes outside the project, elevated privileges, other machines, publishing, discarded work) is always put to a person by reaction in the room. When TRUE, the rest goes to a monitor: a read-only model in its own process that approves, refuses, or passes the call on to a person. When FALSE (default) a person is asked for the rest as well. Nothing is approved unseen. The monitor's model is set under supervisor in the user's corteza config; see vignette("configuration").

bots

Character vector or NULL. Full Matrix IDs of other known bot accounts. Their messages only get a reply when they mention this bot, and they are not counted as humans when deciding whether a room gets ungated replies (a room whose only non-bot member is one human is answered without a mention).

models

Character vector or NULL. Extra entries for the /model menu, each a "model provider" pair (e.g. "claude-sonnet-4-6 anthropic_claude"; a bare model name uses the default provider). The menu always lists the configured default and the live local Ollama inventory; this key adds hosted models that can't be discovered automatically.

model_badge

Character. When to show which model is answering: "never" (default, current behavior), "non_default" (only while a /model switch has moved a room session off the configured default – silence means the default, a badge means you are spending something else), or "always". When active, replies get a lightning-bolt first line naming the model and provider, and the bot renames itself to "<name> <bolt> <model>" so every message wears the model in its sender line. The display name is account-global: with sessions in several rooms, the most recent switch wins (the per-reply badge line is always room-accurate).

display_name

Character or NULL. Base display name the badge rename builds on. Defaults to the localpart of the bot's user id.

fallback

Character vector or NULL. What turn tries, in order, when the default provider refuses a request with a limit error (rate, usage, or quota). Same "model provider" shape as models, e.g. c("gpt-5.6-sol openai_codex", "claude-haiku-4-5 anthropic"). A provider that hit a limit is skipped for fallback_cooldown_minutes (config key, default 30). Set fallback_primary_retry_at to a weekly boundary such as "Mon 03:00" to retry the primary after its account resets. anthropic_claude and openai_codex are subscription providers; a fallback onto an API-key provider such as anthropic or openai is labeled as billable usage prominently in the reply. reasoning_effort, when configured, applies to every candidate. NULL (default) means a limit error is reported like any other error.

Value

See bot_configure.

See Also

bot_configure

Examples

## Not run: 
matrix_configure(server, user, password, room)  # use bot_configure()

## End(Not run)

Deprecated: use bot_poll()

Description

Deprecated: use bot_poll()

Usage

matrix_poll(
  system = NULL,
  model = NULL,
  provider = NULL,
  tools_filter = NULL,
  timeout = 0L,
  sessions = NULL,
  crypto = NULL,
  hooks = NULL
)

Arguments

system

Character or NULL. System prompt override.

model

Character or NULL. Model override.

provider

Character or NULL. Provider override.

tools_filter

Character vector or NULL. Tool filter override.

timeout

Integer. Long-poll timeout in milliseconds. 0 returns immediately.

sessions

Environment from bot_new_session_registry() keyed by room_id, or NULL to build fresh sessions each call.

crypto

Ignored. Kept so calls written for corteza 0.7.1 still run; the chat transport handles encryption now.

hooks

Named list of functions or NULL, as validated by bot_run_init; see its Hooks section. NULL asks the host nothing.

Value

See bot_poll.

See Also

bot_poll

Examples

## Not run: 
matrix_poll()  # use bot_poll()

## End(Not run)

Deprecated: use bot_request_flush()

Description

Deprecated: use bot_request_flush()

Usage

matrix_request_flush()

Value

See bot_request_flush.

See Also

bot_request_flush

Examples

## Not run: 
matrix_request_flush()  # use bot_request_flush()

## End(Not run)

Deprecated: use bot_run()

Description

Deprecated: use bot_run()

Usage

matrix_run(
  timeout = 30000L,
  system = NULL,
  model = NULL,
  provider = NULL,
  tools_filter = NULL
)

Arguments

timeout

Integer. Long-poll timeout in milliseconds.

system

Character or NULL. System prompt override.

model

Character or NULL. Model override.

provider

Character or NULL. Provider override.

tools_filter

Character vector or NULL. Tool filter override.

Value

See bot_run.

See Also

bot_run

Examples

## Not run: 
matrix_run()  # use bot_run()

## End(Not run)

Deprecated: use bot_run_init()

Description

Deprecated: use bot_run_init()

Usage

matrix_run_init(
  system = NULL,
  model = NULL,
  provider = NULL,
  tools_filter = NULL,
  hooks = NULL
)

Arguments

system

Character or NULL. System prompt override.

model

Character or NULL. Model override.

provider

Character or NULL. Provider override.

tools_filter

Character vector or NULL. Tool filter override.

hooks

Named list of functions or NULL. Policy the loop asks the host about; see Hooks. NULL, the default, means a bare bot: /clear archives and resets without filing anything, and a thread reply starts from the thread's own history.

Value

See bot_run_init.

See Also

bot_run_init

Examples

## Not run: 
matrix_run_init()  # use bot_run_init()

## End(Not run)

Deprecated: use bot_run_step()

Description

Deprecated: use bot_run_step()

Usage

matrix_run_step(state, timeout = 30000L)

Arguments

state

A state object from bot_run_init.

timeout

Integer. Long-poll timeout in milliseconds.

Value

See bot_run_step.

See Also

bot_run_step

Examples

## Not run: 
matrix_run_step(state)  # use bot_run_step()

## End(Not run)

Deprecated: use bot_send()

Description

Deprecated: use bot_send()

Usage

matrix_send(text, room_id = NULL, msgtype = "m.text", markdown = FALSE)

Arguments

text

Character. Plain text body.

room_id

Character. Matrix room id. Defaults to cfg$room_id from the saved Matrix config (see bot_configure).

msgtype

Character. Matrix msgtype, default "m.text".

markdown

Logical. If TRUE, also send Matrix custom HTML derived from a conservative markdown subset.

Value

See bot_send.

See Also

bot_send

Examples

## Not run: 
matrix_send("hello")  # use bot_send()

## End(Not run)

Build a tool executor that routes through an MCP connection

Description

Returns a closure suitable for the tool_executor argument of turn. Each tool call is forwarded to the connected MCP server via llm.api::mcp_call.

Usage

mcp_tool_executor(conn)

Arguments

conn

An open MCP connection (from llm.api::mcp_connect).

Value

A function with signature function(name, args) that returns an MCP-format result list.

Examples

## Not run: 
# Needs an open MCP connection to a running corteza::serve().
conn <- llm.api::mcp_connect("tcp://localhost:7850")
executor <- mcp_tool_executor(conn)
s <- new_session(provider = "anthropic")
turn("Hello", s, tool_executor = executor)

## End(Not run)

Create a new turn session

Description

Returns an environment with sensible defaults. Adapters set channel- specific fields (e.g. approval_cb, tools_filter) before calling turn.

Usage

new_session(
  channel = c("cli", "console", "matrix"),
  history = NULL,
  model_map = NULL,
  provider = "anthropic",
  tools_filter = NULL,
  system = NULL,
  approval_cb = NULL,
  max_turns = 10L,
  verbose = FALSE,
  plan_mode = FALSE,
  web_search = NULL,
  base_url = NULL,
  max_tokens = NULL,
  reasoning_effort = NULL,
  thinking_budget_tokens = NULL,
  cache = NULL,
  compaction_prompt = NULL,
  context_edit = NULL
)

Arguments

channel

Character, one of "cli", "console", "matrix".

history

List of prior messages, or NULL.

model_map

Named list with cloud and local model names. Defaults to configured defaults.

provider

LLM provider passed to llm.api::agent.

tools_filter

Character vector passed to get_tools().

system

System prompt override (NULL for built-in default).

approval_cb

Function called when policy returns "ask". Signature: function(call, decision) -> TRUE|FALSE. Default denies (safe fallback).

max_turns

Maximum LLM turns per call.

verbose

Print tool call progress.

plan_mode

Logical. When TRUE, the session is in plan mode: the LLM is told to research and propose without acting, the policy engine denies write/exec tool calls (except exit_plan_mode), and exit_plan_mode is added to the tool list. A successful exit_plan_mode call flips this back to FALSE.

web_search

Logical or NULL. When TRUE and the provider supports it, turn enables llm.api::agent's provider-native (server-side) web search for the session: the model searches inside its own turn, no Tavily key required. NULL defers to turn's default (on for supported providers).

base_url

Character or NULL. Endpoint for the "openai_compatible" provider (OpenRouter, DeepSeek, a corporate gateway). turn applies it via llm.api::llm_base() for the duration of the agent call. NULL falls back to config$base_url, then llm.api's own OPENAI_COMPATIBLE_BASE_URL environment variable. Ignored for every other provider.

max_tokens

Integer or NULL. Per-response output-token budget, forwarded to llm.api::agent. NULL falls back to config$max_tokens, then llm.api's provider default (4096 on Anthropic). Agent turns that write long tool calls – big run_r bodies especially – need more than that default: an unset Anthropic budget can grow through bounded output retries. An explicit budget stays a ceiling unless config$output_retry_max_tokens permits a larger one. Exhausted recovery reports [Output truncated: max_tokens].

reasoning_effort

Character or NULL. How hard a reasoning model should think before answering, e.g. "low", "medium", "high". One setting, routed to whichever field the provider's wire uses: reasoning_effort on "openai" / "openai_codex", output_config$effort on "anthropic" / "anthropic_claude". Dropped for providers with no effort control. NULL falls back to config$reasoning_effort, then the provider default. Not validated against a fixed set, because the scales differ: Anthropic accepts "low", "medium", "high", "xhigh" and "max", and an unknown value is refused by the API with a message naming the alternatives.

thinking_budget_tokens

Integer or NULL. Extended-thinking budget for Anthropic models, forwarded to llm.api::agent for the "anthropic" and "anthropic_claude" providers only. Must be at least 1024 and below max_tokens, both enforced by llm.api. NULL falls back to config$thinking_budget_tokens, then the provider default (thinking off).

cache

Character or NULL. Anthropic prompt-cache TTL: "none", "5m" or "1h". Anthropic-only, and dropped for other providers so they never see a field their wire rejects. NULL falls back to config$cache, then llm.api's default ("none"). Caching is a billing setting, not a behavioural one: the model receives byte-identical input either way, so turning it on cannot change a result. llm.api (>= 0.1.9.6, the Imports floor) marks both the system prompt and the tail of the message history, so each request in an agent loop reads the previous request's context from cache and pays fresh input only for what was appended since; on a long tool loop that is most of the input bill. The floor is there because an older llm.api marks the system prompt alone and would silently deliver a fraction of that with this set. turn()$usage reports what was read and written.

compaction_prompt

Character or NULL. Optional domain-specific instructions for summarizing older history during context compaction. NULL uses corteza's general coding-agent summary brief. The prompt affects only the summarizer; the main agent system prompt is unchanged.

context_edit

NULL to inherit configuration, a logical enable switch, or a list with 'enabled' and 'cadence' ('"pressure"' or '"always"'). Context editing requires the session journal and a supported history shape.

Value

An environment holding the session state.

Examples

# Build a stateless session for the CLI channel without making any
# network calls. The returned environment carries history, the
# active provider/model, and the approval callback.
s <- new_session(channel = "cli", provider = "anthropic")
is.environment(s)
identical(s$provider, "anthropic")

Built-in progress observer that prints to stdout

Description

Prints one line per tool call suitable for an interactive REPL: " [tool] hint (N lines)\n". The hint is a short summary of the call (file path, code snippet, search pattern) computed by tool_hint().

Usage

observer_progress()

Value

A function to pass to add_observer.

Examples

obs <- observer_progress()
s <- new_session(provider = "anthropic")
add_observer(s, obs)

Evaluate policy for a tool call

Description

Returns a decision list(model, approval, reason). model is "cloud" or "local"; approval is "allow", "ask", or "deny".

Usage

policy(call, config = NULL)

Arguments

call

A list describing the tool call. See the file header in R/policy.R for the expected fields.

config

Optional config list from load_config(). When NULL (default), only the built-in tensor and user policy apply; this matches the historical policy(call) contract.

Details

When config is supplied, the project's approval_mode / dangerous_tools / per-tool permissions are overlaid on top of the default tensor: a tool the user has configured as "ask" or "deny" will have its decision promoted accordingly. Safety verdicts (credential paths, plan mode) still win because those represent invariants the user can't waive from config.

Both corteza::chat() and the CLI tool dispatch loop pass their session's config through here so the /permissions contract advertised by both surfaces is enforced consistently.

Value

A decision list with fields model, approval, reason.

Examples

# A bare-environment read_file call resolves under the default
# built-in policy without needing any session config.
decision <- policy(list(name = "read_file",
                        arguments = list(path = "DESCRIPTION")))
decision$approval

Start MCP Server

Description

Start the corteza MCP server. This exposes R tools to MCP clients like Claude Desktop, VS Code, or the corteza CLI.

Usage

serve(port = NULL, cwd = NULL, tools = NULL, expose_subagents = NULL)

Arguments

port

Port number for socket transport. If NULL, uses stdio transport.

cwd

Working directory for the server. Defaults to current directory.

tools

Character vector of tools or categories to enable. Categories: file, code, r, data, web, git, chat. Use "core" for file+code+git, "all" for everything (default).

expose_subagents

Whether MCP clients may call the subagent tools ('spawn_subagent', 'query_subagent', 'collect_subagent', 'list_subagents', 'kill_subagent'). 'NULL' (default) defers to the 'subagents$expose_over_mcp' config flag (itself FALSE by default); 'TRUE'/'FALSE' overrides it. Off by default because a spawned subagent runs its own agent loop and spends autonomously on the host's LLM credentials – an unattended MCP client could otherwise trigger unbounded cost the client never sees. When on, cumulative subagent spend is capped by 'subagents$mcp_spend_cap_usd' (default $5.00).

Details

The server supports two transport modes:

- **stdio** (default): For Claude Desktop and other MCP clients. Communication happens via stdin/stdout.

- **socket**: For the corteza CLI and R clients. Listens on a TCP port.

## Tools Provided

- 'read_file', 'write_file', 'replace_in_file', 'list_files', 'grep_files' - File operations - 'run_r' - Execute R code in the server session - 'bash' - Run shell commands - 'r_help' - Query package docs via saber (exports, function help) - 'installed_packages' - List installed packages - 'web_search' - Search the web via Tavily (requires TAVILY_API_KEY) - 'fetch_url' - Fetch web content - 'git_status', 'git_diff', 'git_log' - Git operations - 'chat', 'chat_models' - LLM chat (requires llm.api)

Value

NULL (runs until interrupted or client disconnects)

Examples

## Not run: 
# For Claude Desktop (stdio)
serve()

# For corteza CLI (socket) with all tools
serve(port = 7850)

# Minimal tools for small context models
serve(port = 7850, tools = "core")

# Specific categories
serve(port = 7850, tools = c("file", "git"))

## End(Not run)

Configure and construct a session for any channel

Description

Performs pre-turn setup common to all channels:

Usage

session_setup(
  channel = c("cli", "console", "matrix"),
  cwd = getwd(),
  provider = NULL,
  model = NULL,
  tools = NULL,
  system = NULL,
  approval_cb = NULL,
  history = NULL,
  load_project_context = TRUE,
  validate_api_key = TRUE,
  verbose = FALSE,
  max_turns = 50L,
  web_search = NULL
)

Arguments

channel

Character, one of "cli", "console", "matrix".

cwd

Working directory. Defaults to the current directory.

provider

Character or NULL. LLM provider override. NULL falls back to config$provider, then "anthropic".

model

Character or NULL. Model override. NULL falls back to config$model, then the provider default.

tools

Character vector, NULL, or the string "all". Tool filter passed through to get_tools(). NULL is treated as "all".

system

Character or NULL. System prompt. NULL auto-builds via the saber-backed context manifest when load_project_context = TRUE, otherwise left NULL (channel supplies its own).

approval_cb

Function or NULL. Approval callback for "ask" verdicts; see new_session.

history

List or NULL. Prior conversation messages to seed the session with (each entry a list with role and content).

load_project_context

Logical. When TRUE, auto-call the saber manifest and rendered system prompt. Channels that supply their own system prompt pass FALSE.

validate_api_key

Logical. When TRUE, error if the provider's API key env var is unset or empty.

verbose

Logical. Passed through to new_session.

max_turns

Integer. Passed through to new_session. Defaults to 50, a safety net for interactive channels where a multi-step request (read + edit + verify several files) can easily exceed the new_session() default of 10.

web_search

Logical or NULL. Provider-native web search toggle passed through to new_session. NULL falls back to config$web_search, then on by default for supported providers. The endpoint for the "openai_compatible" provider is read from config$base_url (falling back to the OPENAI_COMPATIBLE_BASE_URL environment variable); a missing endpoint or model errors here rather than at the first request.

Details

  1. Loads project + global corteza config from cwd.

  2. Resolves provider, model, and verifies the required API environment variable is set.

  3. Registers built-in skills and loads user/project skills and skill docs from tools::R_user_dir("corteza", "data")/skills and <cwd>/.corteza/skills.

  4. Loads skill packages declared in the config.

  5. Optionally builds the system prompt and source manifest via load_context_bundle(cwd).

  6. Returns a new_session() built from the above.

Value

A session environment from new_session, with an extra cwd field and, for an auto-built system prompt, a context_manifest field.

Examples

## Not run: 
# Requires the relevant provider API key in the environment.
s <- session_setup("cli", provider = "anthropic",
                   load_project_context = FALSE)
s$model

## End(Not run)

Install a skill from a path or URL

Description

Install a skill from a path or URL

Usage

skill_install(source, target_dir = NULL, force = FALSE)

Arguments

source

Path to skill directory or URL

target_dir

Installation directory. Default is tools::R_user_dir("corteza", "data")/skills.

force

Overwrite if exists

Value

Installed skill name

Examples

# Install into a throwaway directory rather than the user's
# R_user_dir, so this example doesn't mutate state.
src <- file.path(tempdir(), "demo_skill")
dir.create(src, showWarnings = FALSE)
writeLines(c(
               "---",
               "name: demo",
               "description: A demo skill",
               "---",
               "Demo body."
            ),
           file.path(src, "SKILL.md"))

dest <- file.path(tempdir(), "skills_lib")
skill_install(src, target_dir = dest)

unlink(src, recursive = TRUE)
unlink(dest, recursive = TRUE)

List installed skills

Description

List installed skills

Usage

skill_list_installed(skill_dir = NULL)

Arguments

skill_dir

Skills directory

Value

Data frame with skill info

Examples

# List skills from an empty tempdir; returns a zero-row data frame
# with the documented columns.
empty <- file.path(tempdir(), "empty_skills")
dir.create(empty, showWarnings = FALSE)
skill_list_installed(skill_dir = empty)
unlink(empty, recursive = TRUE)

Remove an installed skill

Description

Remove an installed skill

Usage

skill_remove(name, skill_dir = NULL)

Arguments

name

Skill name

skill_dir

Skills directory

Value

Invisible TRUE on success

Examples

# Install a demo skill into a tempdir, then remove it. The
# installed name is the source directory's basename.
src <- file.path(tempdir(), "demo_skill")
dir.create(src, showWarnings = FALSE)
writeLines(c("---", "name: demo_skill",
             "description: A demo skill", "---"),
           file.path(src, "SKILL.md"))
dest <- file.path(tempdir(), "skills_lib")
name <- skill_install(src, target_dir = dest)
skill_remove(name, skill_dir = dest)

unlink(src, recursive = TRUE)
unlink(dest, recursive = TRUE)

Run skill tests

Description

Executes test_*.R files in a skill directory.

Usage

skill_test(path, verbose = TRUE)

Arguments

path

Path to skill directory

verbose

Print test output

Value

List with passed, failed, errors

Examples

# A skill directory with no test_*.R files returns a zero-result
# summary rather than erroring.
p <- file.path(tempdir(), "skill_no_tests")
dir.create(p, showWarnings = FALSE)
skill_test(p, verbose = FALSE)
unlink(p, recursive = TRUE)

Collect the result of a previously-fired async subagent query.

Description

Pairs with 'subagent_query(..., wait = FALSE)'. Returns the reply text once the child finishes its turn, or NULL while the query is still running. Result is read exactly once: after a successful collect the pending slot is cleared, so the next async query can fire.

Usage

subagent_collect(id, wait = TRUE, timeout = 60L)

Arguments

id

Subagent identifier (UUID, prefix, or sequence number).

wait

If TRUE (default), block up to 'timeout' seconds waiting for the child to finish. If FALSE, poll once and return immediately.

timeout

Maximum seconds to block when 'wait = TRUE'; 'Inf' waits until the child replies. On timeout the child is left running; caller may collect again later or kill explicitly.

Value

Reply text (character) when ready; NULL when still running.

Examples

## Not run: 
id <- subagent_spawn("background research")
subagent_query(id, "what's in DESCRIPTION?", wait = FALSE)
# ... do other work ...
subagent_collect(id, wait = TRUE, timeout = 30)
subagent_kill(id)

## End(Not run)

Kill a subagent.

Description

Kill a subagent.

Usage

subagent_kill(id)

Arguments

id

Subagent identifier (UUID, prefix, or sequence number).

Value

Invisible TRUE if killed, FALSE if not found.

Examples

# Unknown id is a silent no-op (returns FALSE), so this is safe to
# run during R CMD check without a live subagent.
subagent_kill("no-such-id")

List active subagents.

Description

Returns a list of info objects per agent: id/seq/task/started_at/ time_remaining/pending plus model/age/cumulative usage and a best-effort live token count for idle agents ('NA' for busy).

Usage

subagent_list()

Value

List of subagent info objects.

Examples

# Empty when no subagent has been spawned yet -- safe to call any time.
subagent_list()

Query a subagent.

Description

Sends a prompt to a running subagent. Inside the child it runs through [turn()] with the child's persistent turn session: the LLM replies, any tool calls it makes resolve against the child's in-process skill registry, and history accumulates across queries.

Usage

subagent_query(
  id,
  prompt,
  wait = TRUE,
  timeout = 60L,
  return_name = NULL,
  report = FALSE
)

Arguments

id

Subagent identifier. Accepts the canonical UUID, a unique UUID prefix, or the per-session sequence number printed by 'subagent_list()' / '/agents'.

prompt

Prompt to send.

wait

If TRUE (default), block up to 'timeout' seconds for the child's reply and return the reply text. If FALSE, fire the prompt and return the canonical id invisibly; caller must collect via [subagent_collect()].

timeout

Maximum seconds to block when 'wait = TRUE'; 'Inf' waits until the child replies. On timeout the query stays pending: collect it later with [subagent_collect()] or end it with [subagent_kill()].

return_name

Optional single name or '.h_NNN' handle for a value the child should hand back. When set, the child must have left the result bound under that name (e.g. via 'run_r'); the resolved value is stashed in the parent handle store and the reply gains a '[stored as .h_NNN]' block referencing it. Requires a subagent with 'run_r' (the 'work' preset). For 'wait = FALSE' the name is captured now and applied when collected.

report

Logical. When TRUE, require a structured handoff with status, notes, findings, concerns, deviations, open questions, and feedback. The ordinary character reply remains the return value; the report is rendered into it for model callers and retained in its 'corteza_report' attribute for R callers.

Details

Both paths fire the prompt the same way, as a one-shot call on the child's 'callr::r_session'. With 'wait = FALSE' the call returns immediately and the parent collects the reply later with [subagent_collect()]. With 'wait = TRUE' the call collects on the parent's behalf, but only up to 'timeout' seconds: a child that has not replied by then is left running with the query pending, and the call returns NULL, so the wait is bounded by default ('Inf' opts out). A subagent can only carry one in-flight query at a time: firing a second one while the first is pending raises an error.

Value

Reply text (character) when 'wait = TRUE' and the child replied within 'timeout', with a handle block appended when 'return_name' resolved; NULL (invisibly) when the timeout elapsed and the query is still pending. Canonical id (character, invisibly) when 'wait = FALSE'.

Examples

## Not run: 
# Requires LLM credentials in the child's environment.
id <- subagent_spawn("read R/skill.R and summarize", preset = "minimal")
subagent_query(id, "what does this file do?", wait = TRUE)
subagent_kill(id)

## End(Not run)

Spawn a subagent.

Description

Starts a fresh 'callr::r_session' with corteza loaded and its tool registry set up. Stores the handle in the package-level registry keyed by subagent id.

Usage

subagent_spawn(
  task,
  model = NULL,
  tools = NULL,
  preset = NULL,
  parent_session = NULL,
  config = NULL
)

Arguments

task

Task description (stored for bookkeeping; not yet fed into an agent loop).

model

Optional model override (reserved for later use).

tools

Optional explicit tool filter (character vector). Overrides 'preset' when provided. Fixed for the lifetime of the child – cannot be expanded after spawn.

preset

Preset name (fixed for the lifetime of the child). '"investigate"' (default): 'read_file', 'grep_files', 'r_help', 'web_search', 'fetch_url'. '"work"': investigate + 'bash', 'write_file', 'replace_in_file', 'list_files', 'git_status', 'git_diff', 'git_log', 'run_r'. '"minimal"': 'read_file', 'grep_files'. '"monitor"': the read-only hall monitor used to supervise unattended auto runs – 'read_file', 'grep_files', 'list_files', 'git_status', 'git_diff', 'git_log', and provider-native web search explicitly off.

parent_session

Parent session object; read for nested-spawning control and session-key derivation.

config

Config list.

Details

Permissions: subagents have no interactive approval channel back to the parent or user. The child's 'approval_cb' denies by default and there is no mid-run escalation path. Whatever capability the child needs must be granted at spawn time through 'preset' or 'tools'. If a task may need shell, write, or network capability, pick a preset that includes it (or pass an explicit 'tools' list); otherwise the child should report that it is blocked rather than retry.

Value

Subagent ID (character).

Examples

if (interactive()) {
    # Spawns a callr::r_session child loaded with corteza; the
    # registry is in-memory and dies with the parent R session, so
    # we wrap in interactive() to keep R CMD check from leaving
    # children behind.
    id <- subagent_spawn("look up the package version",
                         preset = "minimal")
    subagent_kill(id)
}

Run one agent turn

Description

Sends prompt to the configured LLM with tool use enabled. Every tool call the LLM makes is routed through policy before being dispatched.

Usage

turn(prompt, session, tool_executor = NULL, tools = NULL)

Arguments

prompt

Character. User prompt.

session

A session environment created by new_session.

tool_executor

Function or NULL. Dispatcher with signature function(name, args) -> list. NULL uses the in-process call_skill path.

tools

List or NULL. Tool schemas to pass the LLM. NULL uses the in-process skill registry (filtered by session$tools_filter). Pass explicit schemas when running against a remote skill source.

Details

Tool dispatch is pluggable via tool_executor, but the CLI and chat() both leave it NULL: tools run in-process through the default call_skill dispatcher against the local skill registry. serve() is a separate MCP server for external clients only; it is not part of the CLI's tool path. Pass an explicit function(name, args) -> list executor only when dispatching tools somewhere other than the in-process registry.

When the provider refuses the request with a limit error (HTTP 429, 503, or 529, or a body naming a rate, usage, or quota limit) and the session has a fallback chain, the turn is retried on the next "model provider" entry in that chain. The chain comes from session$fallback (the Matrix config's fallback key, see bot_configure) or the cwd config's fallback key. A provider that hit a limit is skipped process-wide for fallback_cooldown_minutes (default 30); a configured fallback_primary_retry_at can hold the primary until a weekly reset boundary. Provider-native histories are bridged when the wire changes, and a partially completed tool run resumes from its captured results so no completed tool call runs twice. Fallback replies identify their actual model/provider; API-key fallbacks carry a prominent billable usage warning. Errors that are not limits are rethrown untouched.

Value

A list with reply (character), session (the updated session environment; also mutated in place), route (the actual model/provider and fallback credential mode), and termination (status done, incomplete, or cancelled, with a machine-readable reason), and audit (the stream path and canonical turn ID, or NULL when recording is disabled).

Examples

## Not run: 
# Requires ANTHROPIC_API_KEY (or the configured provider's key) and
# a network connection to the LLM.
s <- new_session(provider = "anthropic")
out <- turn("Say hello", s)
out$reply

## End(Not run)

Uninstall corteza CLI

Description

Remove the corteza command-line tool.

Usage

uninstall_cli(path = NULL)

Arguments

path

Directory where corteza is installed. Default matches install_cli(): ~/bin on Unix, tools::R_user_dir("corteza", "data")/bin on Windows.

Value

TRUE if removed, FALSE if not found, invisibly.

Examples

## Not run: 
uninstall_cli()

## End(Not run)

Serve AgentVoice for live voice clients

Description

Runs the gRPC service a fluffychat live-voice client talks to (AllocateVoice, Converse, ReportTurn). Blocks until interrupted. Run it as its own R process, next to – not inside – the room poll loop.

Usage

voice_serve(
  config = NULL,
  address = NULL,
  hooks = list(),
  poll_ms = 200L,
  max_events = Inf
)

Arguments

config

Config list as from bot_load_config(), or NULL to load it.

address

"host:port" to bind, or NULL for the config's voice.listen, or "127.0.0.1:7851".

hooks

Named list overriding side effects (tests/harnesses only): http, clock, run_turn, post, edit, members, ready.

poll_ms

Milliseconds each poll waits for events.

max_events

Stop after handling this many events (tests only; the default never stops).

Details

Requires the rgrpc and RProtoBuf packages (Suggests), and a media allocator named in the config: voice.allocator (the gpu.ctl base URL) plus voice.allocator_token (the service credential its front requires on every mint). Without either, AllocateVoice refuses and names the missing key.

Voice sessions generate in spoken register: their system prompt carries an instruction to write plain speakable prose (no markdown, numbers said aloud), because the reply is synthesized verbatim. voice.speech_style in the config replaces that instruction's text; typed room chat is unaffected either way.

Value

Invisibly, the number of events handled.