Automate the routine parts without losing control of the risky ones
Schedule: trigger Session tasks on cron#
Creating one#
Create a Schedule directly from Settings' scheduled-tasks area, or let the agent create one through the schedule tool. When creating one, you specify:
- A cron expression.
- The target Session — Settings' entry point binds to a Session that already exists.
- The task to send.
- Whether it fires only once.
Where you see it#
Settings' scheduled-tasks area gives you a creation form, and shows:
- The cron expression.
- The target Session.
- The next fire time.
- The most recent execution status.
You can create, pause, resume, or delete a Schedule.
Execution boundary#
The Schedule dispatcher checks for due occurrences every 15 seconds, and only fires while the kxen app is actually running — it's not an OS-level cron. Once the app quits, it never starts a background executor on its own, so the actual fire time carries a delay of up to one tick plus scheduling overhead.
Every occurrence first persists a stable dispatch_id, then writes the message to the target Session's durable queue. Only once the message is acknowledged does a one-time Schedule get deleted, or a recurring Schedule advance next_fire. If the app quits mid-dispatch or a dispatch fails, kxen replays with the same delivery ID; the Session's append step deduplicates by stable message ID, so nothing is silently lost or duplicated.
If schedule.json gets corrupted, kxen keeps the original file and blocks further writes rather than interpreting the damage as an empty task list. You have to repair or restore that file before managing Schedules again.
Actually executing a queued message still checks:
- Whether the target Session still exists.
- Whether the Session already has an active run.
- Whether MRM has an available model resource.
- Whether the tool needs Approval.
These checks happen when the queued message actually executes — not necessarily at the instant of the 15-second tick. Schedule can never bypass a Session's concurrency protection, model resource limits, Safety, or Approval.
When to use it#
Schedule fits periodic checks, summaries, or maintenance tasks. For anything involving external publishing, deletion, or another meaningful write, keep a human confirmation step — don't treat a scheduled trigger as standing permission.
When you need to periodically run an independent Bot definition rather than deliver a message into a Session, use Bot Routine instead — it locks in the Bot's revision and permission snapshot, and can use isolated or Bot Conversation context.
Voice: speak into Composer#
Voice turns speech into Composer text. It's an input mechanism only — it never changes agent permissions or model routing.
Where you use it#
Composer's microphone button starts and stops voice input. Holding the spacebar for over 400ms also enters push-to-talk mode — release to stop.
Settings' Voice area configures the engine, fallback, locale, and transcription model. Composer's microphone menu lets you quickly switch the currently available engine.
Engines#
- Apple Speech framework.
- OpenAI-compatible audio transcription.
- xAI audio transcription.
- Any custom provider that declares audio capability.
The Apple engine is only available in the macOS desktop app, and only uses on-device recognizers that support on-device recognition. Provider engines need matching credentials and network access, and send your recording to whichever transcription service is actually selected.
Fallback#
You can configure an ordered fallback for your preferred engine. If the preferred engine is unavailable, kxen tries the next one in order, and Composer shows which engine actually ran.
When Apple is your preferred engine, adding a credentialed cloud provider to voice.fallback also means you're allowing that recording to be sent to that provider, upgrading the on-device result with a cloud-finished transcript. Just having saved provider credentials doesn't itself authorize an upload. With fallback empty, Apple recordings never buffer PCM for the cloud, and nothing leaves your machine.
Input behavior#
A partial transcript updates the current Session draft as it comes in. Switching Sessions stops the old recording — a late final transcript never gets written into the new Session.
If you send while still recording, Composer waits for the final merged transcript first, then sends the complete text. Pressing Enter repeatedly never produces two sends.
A recording that needs cloud transcription buffers at most 5 minutes, bounded jointly by a PCM sample-count cap and a WAV size cap, whichever comes first. Past that limit, it stops and refuses the upload — the Apple on-device path still keeps whatever local result it already has.
Cloud audio services usually bill by duration rather than by token. kxen persists a request declaration before uploading, and marks the token measurement UNKNOWN on completion or failure — it's never disguised as 0/0, and a recording that may already be billed is never automatically resent.
System permissions#
The Apple engine needs microphone and speech-recognition permission. If those aren't granted, the UI points you to the matching macOS system settings.
Web & Search: read known pages or discover sources#
webfetch and websearch are the agent's resident read-only tools. They read public material — they never operate a page or route around a target site's own permissions.
Web Fetch#
Web Fetch takes one explicit HTTP or HTTPS URL, waits up to 20 seconds, and streams and extracts up to 50,000 characters. Script and style tags are stripped from HTML; it never executes page JavaScript, and never provides a browser login session.
The HTTP client's automatic redirect is disabled — kxen follows up to 5 hops manually, re-checking scheme, URL, and the actual connection address at every hop. A missing or invalid Location, exceeding the hop count, a non-HTTP(S) scheme, a restricted address, a final non-success status, and transport errors are never disguised as an empty page.
Web Search#
Web Search takes a search term, and returns the engine that actually produced results, a source list, and an optional synthesized answer where the native search engine provides one. Search APIs, SearXNG, and DuckDuckGo each return up to 8 results per call; provider-native search keeps the citations from its own response.
Engines and fallback#
The current engine chain, in order:
- Search APIs: Tavily, Brave, Exa, Jina, Serper, SerpAPI, Google CSE, Firecrawl, and You.com.
- Provider-native search: Perplexity, xAI Grok, OpenAI, and Anthropic.
- Self-hosted SearXNG.
- Credential-free DuckDuckGo HTML search.
Your configured search.engine moves to the front of the chain. If that engine has no credentials, no configuration, or the request fails, kxen keeps trying the rest of your configured engines, finally falling back to DuckDuckGo; an unrecognized engine ID follows the default auto order. So naming a preferred engine doesn't restrict you to a single service — other configured search services may still receive your query on fallback.
The response marks which engine actually produced the results. One engine failing is never written up as an empty success; if every engine is unavailable, the tool returns a per-engine error summary.
Configuration and credentials#
Search only reads your personal configuration — the project's .kxen/config.toml can't change the search engine or expand network access.
[search]
engine = "brave"
google_cx = ""
searxng_url = ""API keys come from the local Auth store or the matching process environment — never written to TOML. Google CSE additionally needs google_cx or GOOGLE_SEARCH_CX; SearXNG needs searxng_url or SEARXNG_URL. Self-hosted SearXNG may use explicit loopback HTTP; every other remote endpoint must use HTTPS.
Provider-native search reuses your existing Perplexity, xAI, OpenAI, or Anthropic credentials. Search APIs use each service's own key, for example TAVILY_API_KEY, BRAVE_SEARCH_API_KEY, EXA_API_KEY, JINA_API_KEY, SERPER_API_KEY, SERPAPI_API_KEY, GOOGLE_SEARCH_API_KEY, FIRECRAWL_API_KEY, and YOU_API_KEY.
Resources and metering#
Every engine request waits up to 20 seconds; a call bound to a Goal is also constrained by its remaining wall-clock budget. Provider-native search first passes its Workspace's MRM admission, then writes a durable usage attempt before the network call. When the response includes token usage, it settles exactly; when it doesn't, it stays UNKNOWN.
The nine search APIs also write a durable usage attempt before the request. They may bill per request but don't return usage the kxen token ledger can express, so an already-started request settles as UNKNOWN. Timeouts, cancellations, and ambiguous transport failures never automatically resend a request that may already have been billed. SearXNG and DuckDuckGo don't enter paid-attempt metering at all.
Shared network and data boundary#
Search requests use a protected connector, and never inherit HTTP_PROXY, HTTPS_PROXY, or ALL_PROXY. Before a request goes out, kxen checks the target and every address that resolution returns this time — an ordinary public-network chain rejects loopback, private, link-local, CGNAT, and other restricted addresses. Response size is bounded, and a transport error never echoes back a full URL that contains a query API key.
Web Fetch's target URL and Search's query and results can enter the current model's context; a Search query is also sent to whichever third-party service the fallback chain actually tried. When you need to click, type, run JavaScript, or verify a real interface, use Browser instead.