太刀tachi-agent · 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.

01 · Service install

macOS: one command, starts at login.

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

--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).
Safe at rest
The plist is written 0600 so only your user can read it — provider keys stay private.
macOS service installshell
1export GATEWAY_TOKEN="change-me"
2tachi-agent service install --env-file .env # bootstrap launchd LaunchAgent
3# starts at login · restarts on crash
4# logs: ~/Library/Logs/tachi-agent/daemon.log
5
6tachi-agent service status # check whether the agent is running
7tachi-agent service uninstall # remove the plist + unload
Linux · tachi-agent.serviceenv
1[Unit]
2Description=tachi-agent daemon
3After=network.target
4
5[Service]
6ExecStart=/usr/bin/node /path/to/dist/daemon/index.js
7WorkingDirectory=/path/to/.tachi-agent
8EnvironmentFile=/path/to/.env
9Restart=always
10RestartSec=5
11
12[Install]
13WantedBy=multi-user.target
On Linux, run node dist/daemon/index.js under a systemd unit with Restart=always.
02 · Skills / recipes

Markdown recipes that shape what a run sees.

Drop a .md file in .tachi/skills/ (override with TACHI_SKILLS_DIR). The body is appended to the system prompt; frontmatter carries the name, a fail-closed tools allowlist, and a driver preset.

Driver precedence: --driver flag / /driver command > skill's driver field > TACHI_DRIVER.

Ready-made recipes in examples/skills/

repo-review.md
jury + Grok code analysis + dokoro decision record
researcher.md
search tools + cited-report prompt
nightly-digest.md
search + jury; driver: openai
fact-checker.md
Perplexity fact-check protocol
.tachi/skills/researcher.mdyaml
1---
2name: researcher
3description: Deep-research mode — searches and synthesizes a cited report.
4tools: tachibot_grok_search, tachibot_perplexity_ask, tachibot_nextThought
5driver: openai
6---
7You are a careful research assistant. Your output must be accurate and traceable.
activate a skillshell
1tachi-agent --skill researcher "explain the CAP theorem trade-offs"
2# or inside the interactive REPL
3# /skill researcher
03 · Interactive chat

Bare tachi-agent opens a REPL.

The prompt shows the active session: tachi [driver·skill] ›. History persists at ~/.tachi-agent/repl_history (capped at 1000 lines). Rewritten tasks from /jury, /search, /think are echoed as → task.

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 + conversation history)
/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)
Conversation continuity. Each REPL turn — and each Telegram chat — carries the prior user/assistant exchanges into the next run, locally and through the daemon, so follow-ups keep their referent. The window is capped (40 entries per session, re-capped by the orchestrator at 20 turns / 24k chars, most recent kept) and cleared by /reset. This is short-term session memory; dokoro stays the long-term memory across sessions.
04 · 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).

05 · 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.

crontab — nightly digest at 02:30, run on the OpenAI heartshell
1# crontab — nightly digest at 02:30, run on the OpenAI heart
230 2 * * * curl -s -X POST -H "Authorization: Bearer $TACHI_TOKEN" -H "Content-Type: application/json" \
3 -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 /tasksThe live queue: status, attempts, driver, answer/error per task.
GET /tasks/:idOne task in full — including the recorded error after a loud failure.
06 · Recurring schedules

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

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

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
Re-read on every tick, so hand edits apply without a restart; the daemon never writes to it. Last-run times live in .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.
.tachi/schedules.jsonjson
1{
2 "schedules": [
3 { "id": "morning-digest", "task": "compile the morning digest", "driver": "openai", "kind": "daily", "at": "07:00" },
4 { "id": "poll", "task": "check the feed for updates", "kind": "every", "everyMinutes": 30 }
5 ]
6}
07 · Multi-heart

One default brain. A different heart per task.

TACHI_DRIVER picks the daemon's default brain (ollama — local, private). A task's optional "driver" field overrides it for that task only.

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
local heart by default, GPT for the nightly jobshell
1# the daemon's default heart stays local
2export TACHI_DRIVER=ollama
3
4# this one task runs on GPT — for that task only
5curl -s -X POST -H "Authorization: Bearer $TACHI_TOKEN" -H "Content-Type: application/json" \
6 -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 with dokoro. Swapping the heart changes none of that.
08 · 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.

push outcomes to Telegram + Slackshell
1export TACHI_NOTIFY="telegram:123456789,slack:C0123ABC"
09 · Inspecting state

Everything an unattended run did, on disk.

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

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.
queue + run-log CLIshell
1export TACHI_DAEMON_URL="http://127.0.0.1:8787" GATEWAY_TOKEN="change-me"
2
3tachi-agent task add "compile the weekly report" --driver openai --max-attempts 5
4tachi-agent task list # id · status · attempt n/max · driver · excerpt
5tachi-agent task show <id> # one task in full — answer or recorded error
6tachi-agent runs log <run-id> # replay the durable JSONL event log of a run