// unattended operation

Standalone. It runs with nobody attached.

The daemon is the standalone foundation: a persistent task queue, a worker that drains it, recurring schedules, per-task driver selection, outcome notifications, and a durable per-run event log. On macOS, tachi-agent service install is the primary path — one command bootstraps a launchd LaunchAgent that starts at login and restarts on crash.

┌─ service install

macOS: one command, starts at login.

tachi-agent service install is the primary macOS daemon path. It reads env vars from your environment (or an --env-file), writes a 0600 plist under ~/Library/LaunchAgents, and bootstraps the service with launchctl.

bash — macOS service install
export GATEWAY_TOKEN="change-me"
tachi-agent service install --env-file .env   # bootstrap launchd LaunchAgent
# starts at login · restarts on crash
# logs: ~/Library/Logs/tachi-agent/daemon.log

tachi-agent service status    # check whether the agent is running
tachi-agent service uninstall # remove the plist + unload
  • --env-file <path> — reads GATEWAY_TOKEN + all TACHI_* vars from the file and bakes them into the plist.
  • --cwd <dir> — sets the daemon working directory (default ~/.tachi-agent).
  • The plist is written 0600 so only your user can read it — provider keys are safe at rest.

On Linux, run node dist/daemon/index.js under a systemd unit with Restart=always.

// skills / recipes

Markdown recipes that shape what a run sees.

Drop a .md file in .tachi/skills/ (override with TACHI_SKILLS_DIR). The body becomes the system prompt; frontmatter carries the name, tool allowlist, and driver preset.

.tachi/skills/researcher.md
---
name: researcher
description: Deep-research mode — searches and synthesizes a cited report.
tools: tachibot_grok_search, tachibot_perplexity_ask, tachibot_nextThought
driver: openai
---
You are a careful research assistant. Your output must be accurate and traceable.

Activate a skill from the CLI or inside the REPL:

bash — activate a skill
tachi-agent --skill researcher "explain the CAP theorem trade-offs"

# or inside the interactive REPL
# /skill researcher

Ready-made recipes live in examples/skills/: repo-review, researcher, nightly-digest, fact-checker. Copy them into your project's .tachi/skills/ or point TACHI_SKILLS_DIR at the directory directly. Driver precedence: --driver flag / /driver command > skill's driver field > TACHI_DRIVER.

┌─ interactive chat

Bare tachi-agent opens a REPL.

No arguments? You get an interactive session. The same /commands surface works in the REPL and in Telegram.

CommandWhat it does
/helpShow the command list
/toolsList the agent's available tools
/modelShow the current model
/statusShow session state (driver, skill, mode)
/driver <name>|offSet or clear the session driver
/skill <name>|offActivate or clear a skill bundle
/resetClear the full session (driver + skill)
/jury <question>Run a cross-model jury verdict via tachibot_jury
/search <query>Search with tachibot_grok_search or Perplexity
/think <question>Reason step by step over a question
/task add <text>Queue a task on the daemon
/task listList queued tasks
/task show <id>Show task detail
/schedule listList scheduled jobs
/exit, /quitLeave (REPL: Ctrl-D also exits)

The prompt shows the active session state: tachi [driver·skill] ›. History persists at ~/.tachi-agent/repl_history (capped at 1000 lines). Run tachi-agent doctor for a preflight check of your environment before the first session.

┌─ the foundation

A daemon under a supervisor. A queue that survives it.

Run node dist/daemon/index.js under launchd (macOS LaunchAgent with KeepAlive) or systemd (Restart=always). The supervisor keeps the process alive; the queue keeps the work alive.

The queue (.tachi/queue.json) is crash-safe: a task left running by a dead daemon is re-queued on restart with its spent attempt counted. Failed tasks retry with exponential backoff (default 3 attempts).

// the queue

Queue work from outside. External cron is the scheduler.

POST /tasks enqueues durable work — unlike POST /runs, a queued task survives restarts and retries with backoff. External cron is the scheduler of record for anything calendar-driven you want outside the agent:

crontab — nightly digest at 02:30, run on the OpenAI heart
# crontab — nightly digest at 02:30, run on the OpenAI heart
30 2 * * * curl -s -X POST -H "Authorization: Bearer $TACHI_TOKEN" -H "Content-Type: application/json" \
  -d '{"task":"summarize yesterdays inbox and post to slack","driver":"openai"}' http://127.0.0.1:8787/tasks
Method & pathPurpose
POST /tasks
{task, driver?, maxAttempts?}
Enqueue a durable task — survives restarts, retries with exponential backoff.
GET /tasks The live queue: status, attempts, driver, answer/error per task.
GET /tasks/:id One task in full — including the recorded error after a loud failure.
┌─ recurring schedules

A JSON file you edit by hand. Re-read live.

For self-contained recurrence, the daemon also evaluates .tachi/schedules.json (TACHI_SCHEDULES_FILE) every TACHI_SCHEDULES_POLL_MS (default 30s) and enqueues due entries into the same queue:

.tachi/schedules.json
{
  "schedules": [
    { "id": "morning-digest", "task": "compile the morning digest", "driver": "openai", "kind": "daily", "at": "07:00" },
    { "id": "poll", "task": "check the feed for updates", "kind": "every", "everyMinutes": 30 }
  ]
}
  • kind: "daily" + at: "HH:MM" — fires once per day, the first tick at/after that local time.
  • kind: "every" + everyMinutes: N — fires immediately on first sight, then every N minutes.
  • The file is yours. It's re-read on every tick, so hand edits apply without a restart, and the daemon never writes to it. Machine state (last-run times) lives in a separate .tachi/schedules-state.json, so a restart doesn't re-fire a schedule that already ran.
  • Fault-tolerant. Malformed entries are skipped with a stderr warning; a broken file never takes the daemon down.
// multi-heart

One default brain. A different heart per task.

TACHI_DRIVER picks the daemon's default brain (ollama by default — local, private). A task's optional "driver" field overrides it for that task only: keep the local heart for interactive chat, and send a nightly heavy job to "driver": "openai" or "openrouter".

DriverWhat it isNeeds
ollamaLocal model via Ollama — private, offline. The default.
hermesSelf-hosted OpenAI-compatible endpoint.HERMES_BASE_URL / HERMES_MODEL
openaiGPT via the OpenAI API.OPENAI_API_KEY
openrouterAny model behind OpenRouter.OPENROUTER_API_KEY
bash — local heart by default, GPT for the nightly job
# the daemon's default heart stays local
export TACHI_DRIVER=ollama

# this one task runs on GPT — for that task only
curl -s -X POST -H "Authorization: Bearer $TACHI_TOKEN" -H "Content-Type: application/json" \
  -d '{"task":"deep-audit the quarter logs","driver":"openai"}' http://127.0.0.1:8787/tasks
explicit by design

No silent fallback

There is no automatic routing and no silent substitution. A task naming an unknown or unconfigured driver (e.g. openai without OPENAI_API_KEY) fails loudly: the actionable error is recorded on the task, normal retry/backoff applies, and the failed task stays inspectable in the queue.

division of labor

Hearts ≠ council ≠ memory

The driver is the heart that runs the loop. Decision-making stays with the tachibot multi-model council tools; persistent memory stays dokoro. Swapping the heart changes none of that.

┌─ notifications

The agent reaches out. You don't poll.

Set TACHI_NOTIFY and the worker pushes every task outcome — success or failure — to those targets, using the existing TELEGRAM_BOT_TOKEN / SLACK_BOT_TOKEN.

bash — push outcomes to Telegram + Slack
export TACHI_NOTIFY="telegram:123456789,slack:C0123ABC"
// inspecting state

Everything an unattended run did, on disk.

WhereWhat
GET /tasks, GET /tasks/:idLive queue: status, attempts, driver, answer/error.
.tachi/queue.jsonThe queue on disk (survives restarts; readable JSON).
.tachi/runs/*.jsonlDurable per-run event log (TACHI_RUN_LOG_DIR) — every step, append-only. Post-hoc debugging for runs nobody watched.

The same visibility from the CLI — point it at the daemon with TACHI_DAEMON_URL + GATEWAY_TOKEN:

bash — queue + run-log CLI
export TACHI_DAEMON_URL="http://127.0.0.1:8787" GATEWAY_TOKEN="change-me"

tachi-agent task add "compile the weekly report" --driver openai --max-attempts 5
tachi-agent task list            # id · status · attempt n/max · driver · excerpt
tachi-agent task show <id>       # one task in full — answer or recorded error
tachi-agent runs log <run-id>    # replay the durable JSONL event log of a run