mcp server
taskgo
Local-first task manager for humans and AI agents. Tasks are plain Markdown files, shared by a CLI, a TUI, and an MCP server
Set up
Runs on your computer. Pick your agent and operating system.
This project does not publish an install method we can turn into a config. Follow the README instead.
README
synced Oct 21, 2026taskgo
A local-first task manager for humans and agents.
Tasks are plain Markdown files. A CLI, a TUI and a local MCP server all read and write the same files, so a task an agent creates and a task you type are identical on disk. No database, no daemon, no account.
taskgo add Fix the login redirect --project web --tag auth --due tomorrow
taskgo list
taskgo done login # resolve by unique title substring
Install
go install github.com/JustSteveKing/taskgo@latest
Or download a binary from the releases.
Linux and macOS. Windows fails at compile time rather than silently: agent
liveness is decided by signalling the MCP server's pid, taskgo edit shells out
through sh -c, and notifications are notify-send with a systemd user timer.
Notifications are Linux-only either way.
Tab completion
taskgo completion install
It works out your shell from $SHELL (or name it: bash, zsh, fish)
and writes the script where that shell loads completions from, so there is
nothing to source by hand. bash needs bash-completion installed. For zsh it
checks the folder is on your fpath and prints the lines to add to
~/.zshrc if not, or adds them with --yes. taskgo completion uninstall
removes it.
Why plain files
The point is that you can always check. cat a task, grep the directory,
fix a mistake in your editor, git diff what changed. A system you are going
to hand write access to an AI agent should not also be a black box — the file
layout is the audit surface, and the activity log records whether each change
came from a human or an agent.
Storage
~/.local/share/taskgo/
├── config.json
├── state.json # derived index + id counter — rebuildable
├── activity.jsonl # append-only log — NOT rebuildable
├── projects/<name>.json
└── tasks/<id>.md
A task file:
---
id: 47
title: Fix login redirect
status: todo # todo | doing | blocked | done
priority: high # low | normal | high | urgent
due: "2026-08-25"
project: web
tags:
- auth
parent: 12 # optional — makes this a subtask of #12
created: 2026-08-22T10:14:00Z
updated: 2026-08-22T10:20:00Z
---
Free-form notes, in Markdown.
Three properties that make this trustworthy
Markdown is canonical; state.json is derived. Writes update the Markdown
first. Edit a task file by hand, run taskgo reindex, and the change is picked
up — that round trip is a supported workflow, not an accident.
The one thing in state.json that is not derived is the id counter. The files
on disk only know the ids that still exist, so rebuilding from them alone would
wind the counter back over deleted ids and issue them twice — and the activity
log would then record two different tasks under one id. The counter is a
high-water mark: reindex takes the furthest of the files, the previous index
and the activity log, and never moves it backwards.
Every write is atomic. Temp file, fsync, rename, fsync the directory. Rename is atomic within a filesystem, so a reader never sees a partial file — which is why reads take no lock at all. Only writers lock.
The activity log is append-only and never rebuilt. It records things that
happened, which cannot be derived from current state. That is why it is a
separate file from the index: reindex regenerates state.json and must not
be able to destroy history.
Commands
| Command | What it does |
|---|---|
add <title>... | create a task (--parent makes it a subtask) |
list | list tasks (--all --project --status --tag --due --overdue --today --waiting --tree --parent --search) |
show <ref> | one task in full, notes included |
done / reopen <ref> | change completion |
edit <ref> | change fields, or open the file in $EDITOR with no flags |
note <ref> <text>... | append a note without touching what is there |
delete <ref> | remove a task (asks first) |
status | summary |
activity | who changed what, and when |
projects / projects new | projects |
reindex | rebuild state.json from the Markdown |
notify | desktop notifications for due and overdue work |
completion <shell> | shell completion script |
When agents are involved:
| Command | What it does |
|---|---|
agents | which agents are connected right now |
claims | which tasks an agent is working on at this moment |
questions | tasks where an agent asked you something and stopped |
answer <ref> <text>... | answer it, and unblock the agent |
history init / log / save | keep the data directory under Git |
undo | revert the last change |
<ref> is an exact task id or a unique case-insensitive title substring.
It is deliberately not an id prefix: with sequential ids 4 is both a
complete id and a prefix of 47, so there is no rule that resolves it safely.
An ambiguous title errors and lists the candidates rather than guessing —
silently completing the wrong task is worse than making you retype.
Every command takes --json for machine-readable output.
Configuration
None required. Optionally ~/.local/share/taskgo/config.json:
{
"defaultProject": "web",
"editor": "nvim"
}
TASKGO_DATA_DIR overrides the store location and always wins over the config
file, so scripts and tests can pin it.
Working alongside agents
An agent connected over MCP is not just another writer of task files. Four things make its work visible while it is happening, rather than only afterwards in the activity log.
taskgo agents # who is connected
taskgo claims # what they are working on right now
taskgo questions # what they are stuck on and need from you
Presence is claimed, not inferred. An agent takes a lease on a task when it
starts work — automatically on its first write, or deliberately via claim_task
— and the lease expires. Activity tells you who last touched a task; a claim
tells you who is on it now, and the two are genuinely different questions. An
agent that created ten tasks and stopped would otherwise look busy on all ten.
Connected is separate from working. An agent that is reading, planning or
waiting on you holds no claim at all, and would be invisible if the roster were
derived from claims. taskgo agents lists sessions; entries carry the server's
pid, so one killed outright disappears rather than lingering as a lie.
An agent that cannot decide asks instead of guessing. The question is stored
on the task, so it shows up as ? in taskgo list, in the TUI, and in
taskgo questions:
#12 Migrate the session store
claude asks: Keep the old cookies working, or force everyone to log in again?
answer with: taskgo answer 12 "..."
Answering appends the whole exchange to the task's notes, so the Markdown keeps the conversation and not just the outcome. The agent is not blocked while it waits — it is expected to go and do something else and check back.
Big work gets broken down. Subtasks are a parent: field, so
taskgo list --tree nests them and a parent shows progress (2/5) rather than
sitting at "in progress" for two days.
Agents are told all of this at connect time, in the instructions the MCP server
sends — see internal/mcpserver/instructions.go.
History and undo
The activity log records what happened but cannot reverse any of it, which is a thin guarantee for a system an agent can write to unattended. Since the storage is already plain text designed to diff, Git turns it into a complete revertible history for almost nothing.
taskgo history init # once, opt-in
taskgo history log
taskgo undo # revert the last change
git log reads like the activity log, because the commit subject is built from
the same event:
77f1053 agent: complete #1 — Existing task
e72c798 agent: create #2 — Created by the agent
e005ed3 human: create #1 — Existing task
Three decisions worth knowing:
- Opt-in. Silently turning your data directory into a Git repository is not something a task manager should do unasked.
- Committing is best-effort. A Git failure never turns a successful task write into an error — the task file is already on disk by then, and a broken repository must not make taskgo unusable.
- Undo records the reversal rather than rewriting history. "This was undone" is itself worth keeping, and an agent's mistake together with its correction tells you more than the mistake never having appeared.
Runtime state (.lock, claims.json, sessions.json, notified.json) is
ignored. It is not history.
Hand edits arrive without going through the store, so they never trigger a
commit; taskgo history save records them. taskgo undo refuses while
anything is uncommitted, rather than sweeping your edit into the revert.
Development
go build ./... && go vet ./... && go test -race ./...
Go 1.25 or later (the MCP SDK sets that floor). CI runs the same three commands
on Linux and macOS, plus a gofmt check and a build that asserts Windows still
fails with the platform message.
The store's tests cover the things that break silently: Markdown round-trip
fidelity (including a notes body that itself contains a --- line), reindex
faithfulness, and concurrent writers getting distinct ids.
If you are an AI agent working on this repository, read AGENTS.md — it covers
the architecture and the invariants this README only shows the outside of.
That last one earned its keep immediately — flock is advisory and per
process, so it serialises the CLI against a running MCP server but does not
serialise goroutines inside one process. The store therefore holds an
in-process mutex as well; the two locks guard different things.
MCP
claude mcp add taskgo -- taskgo mcp
Twenty-one tools:
| tasks | create_task get_task update_task complete_task reopen_task add_note |
| finding things | list_tasks search_tasks get_overdue get_today |
| projects | list_projects create_project |
| presence | claim_task release_task list_claims |
| asking you | ask_human check_answer list_questions |
| subtasks | break_down_task get_subtasks |
| history | get_activity |
The server also sends instructions at initialize, so a connecting agent knows the workflow — claim before working, ask rather than guess, break big work down, note what you did — before its first tool call, rather than inferring one from twenty-one tool descriptions.
Every change an agent makes lands in the same Markdown files the CLI reads, so
it shows up in taskgo list immediately — and every one is recorded as
agent in the activity log, which is how you tell later who did what.
Tools take task ids, never titles. An agent wanting "the login task" should
call search_tasks first and use the id it gets back; resolving a fuzzy
reference inside a tool call would mean guessing on the agent's behalf, and
completing the wrong task is not something the agent can undo.
The server holds no cached index — it re-reads on every call, so a task you add from the CLI is visible to the agent on its next request.
TUI
taskgo tui
Laid out like the lazy* family: numbered side panels narrowing what the main panel shows, a detail pane that follows the cursor, and a footer whose keys change with the focused panel.
╭─ 1 Views ────╮╭─ 4 Tasks (9) ───────────────────────────╮
│ All ││ ? 2 Ship the MCP server @taskgo 3d late│
│ Needs you ││ 5 Fix the contact form @website │
│ Today ││ ... │
│ Overdue ││ │
╰──────────────╯╰──────────────────────────────────────────╯
╭─ 2 Projects ─╮╭─ Detail — #2 ───────────────────────────╮
│ (all) ││ Ship the MCP server │
│ infra 3 ││ todo · normal · 3d late · @taskgo │
╰──────────────╯│ │
╭─ 3 Agents ───╮│ claude asks: │
│ claude #2 ││ Should this ship behind a flag? │
╰──────────────╯╰──────────────────────────────────────────╯
9 open · 10 total · 2 overdue · 3 today · 1 waiting on you
j/k move · space done · a answer · n new · ? help · q quit
| Key | |
|---|---|
1 2 3 4 | focus views, projects, agents, tasks |
tab / shift+tab | cycle panels |
h / l | move between the side panels and the tasks |
j k, ctrl+d ctrl+u, g G | move |
space | complete / reopen |
a | answer the question an agent is waiting on |
s p | cycle status / priority |
n | new task, in the selected project |
N | new subtask of the selected task |
e | open the Markdown file in $EDITOR |
d | delete, with confirmation |
/ | filter by title |
r | reload now |
? | help |
Views, projects and agents compose rather than override, so "Overdue" plus a project shows that project's overdue work, and selecting an agent narrows to what it holds. The selection bar dims on unfocused panels, so it is always obvious which cursor the keyboard is driving.
It re-reads the store every couple of seconds while idle, so a task an agent creates over MCP appears without you touching anything — but not while you are typing a filter or a new title, because being two seconds stale beats having the list move under your cursor.
If the layout overflows the bottom of your terminal, the terminal is
probably reporting more rows than it draws — a known problem on fractionally
scaled displays, where btop and friends overflow identically. TestViewFitsTerminal
in internal/tui checks the arithmetic against a range of sizes, so you can
tell the two apart.
Status
Storage, CLI, MCP server, TUI, agent presence, questions, subtasks, Git history,
notifications and completions all work. Tag a v* release and GoReleaser
publishes Linux and macOS binaries.
Every package is tested. The cmd tests drive the real command tree the way a
user would, which is only safe because flag values live on a per-invocation
struct rather than in package-level variables — otherwise one test's
--data-dir would leak into the next. The TUI is driven the same way: Update
is a pure function of (model, message), so keystrokes can be sent without a
terminal and the assertion made against what reached the disk.