Skip to content
Conduit

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

JustSteveKing/taskgocommunitylistedunreviewed

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, 2026

taskgo

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

CommandWhat it does
add <title>...create a task (--parent makes it a subtask)
listlist 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)
statussummary
activitywho changed what, and when
projects / projects newprojects
reindexrebuild state.json from the Markdown
notifydesktop notifications for due and overdue work
completion <shell>shell completion script

When agents are involved:

CommandWhat it does
agentswhich agents are connected right now
claimswhich tasks an agent is working on at this moment
questionstasks where an agent asked you something and stopped
answer <ref> <text>...answer it, and unblock the agent
history init / log / savekeep the data directory under Git
undorevert 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:

taskscreate_task get_task update_task complete_task reopen_task add_note
finding thingslist_tasks search_tasks get_overdue get_today
projectslist_projects create_project
presenceclaim_task release_task list_claims
asking youask_human check_answer list_questions
subtasksbreak_down_task get_subtasks
historyget_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 4focus views, projects, agents, tasks
tab / shift+tabcycle panels
h / lmove between the side panels and the tasks
j k, ctrl+d ctrl+u, g Gmove
spacecomplete / reopen
aanswer the question an agent is waiting on
s pcycle status / priority
nnew task, in the selected project
Nnew subtask of the selected task
eopen the Markdown file in $EDITOR
ddelete, with confirmation
/filter by title
rreload 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.

↑↓ navigate openesc close