A task manager that is genuinely yours — for one person, a household, a team, and a provider who runs it for thousands of others — built with the seriousness of infrastructure: nothing is lost, nothing leaks between workspaces, every guarantee can be checked.
- One model for a life and a client roster. "Buy milk" and a release plan live in the same five levels — Hub → Collection → Task → Work Package → Activity — without becoming two products.
- Yours to run. One image and PostgreSQL,
docker compose up, the full feature set, no licence key that switches anything off, nothing that phones home. The same image runs a multi-tenant platform on Kubernetes. - Everything you can click, a script and an agent can do too — through a REST API, an MCP server, webhooks, n8n and Zapier, and a rule engine, under the same permissions, recorded in the same trail. AI is optional and can be switched off.
- It keeps your work safe and says so. Backups to a target you choose, encrypted and restored in a drill; a chained audit trail that verifies; deletion that announces itself and can be stopped; a health report that names what is missing instead of failing silently.
- Private by design. The boundary between workspaces is held by the database; a person's rights over their data are answered in the product, on time; everything can be exported, always.
- Offline, and nothing lost. Clients keep working without a network, and concurrent changes are merged without losing anyone's work.
- Every person sees what is theirs to use — a control that may not be used is not shown — in any language, time zone and text direction.
- Open source under Apache-2.0.
The vision behind this, the people it is for and the settings it must work in are in
docs/vision/.
The architecture is fully documented. docs/README.md says where each kind of
knowledge lives; start with the vision (why), the
use cases (what must be true for a person) and
arc42 (how it is built). AGENTS.md is how to work
here, for a person or a coding agent.
One Go module, one container image, several process roles (api, worker, scheduler,
automation). The core (core/) is technology-free and knows only ports; REST, MCP, calendar,
mail, and webhooks are adapters. PostgreSQL is the only mandatory dependency and handles storage,
the job queue, the event outbox, full-text search, and tenant isolation (row level security).
Instead of four specialised level entities there is one generalised WorkItem aggregate root with
capability profiles — new levels and fields are configuration, not a migration.
The same repository also holds both first-party clients and the design system
(ADR-0027): the web UI under apps/webapp is an
inbound adapter like REST or MCP and ships inside the binary
(ADR-0028), and the project website lives under
apps/website.
git clone https://github.com/Jersyfi/hubtask.git && cd hubtask
cp deploy/docker/.env.example .env # set the secrets
docker compose -f deploy/docker/compose.yaml up -d
# API: http://localhost:8080/api/v1
# Meta: http://localhost:8080/api/v1/meta/capabilitiesKubernetes:
helm upgrade --install hubtask ./k8s -f ./k8s/values.yamlhubctl signs in with a personal access token and speaks the published contract — its types are
generated from api/openapi.yaml. Every command prints a table for a person, or, with --json,
the API's own payload for a pipe: exactly one document on standard output and every diagnostic on
standard error.
make build # bin/hubctl
echo "$TOKEN" | bin/hubctl auth login --url http://localhost:8080
HUB=$(bin/hubctl container create --type HUB --name "Personal" | awk 'NR==2 {print $1}')
bin/hubctl container create --type COLLECTION --parent "$HUB" --name "Errands"
bin/hubctl item create --collection "$COLLECTION" --type TASK --title "Buy milk"
bin/hubctl item complete "$ITEM"
bin/hubctl comment add "$ITEM" --body "Done on the way home"
bin/hubctl search milk
bin/hubctl trash ls
bin/hubctl watch # follow the change stream; Ctrl-C ends it
bin/hubctl sync pull --all > state.jsonl # the offline synchronisation, as a device: the cursor last
bin/hubctl sync push --file queue.jsonl # one mutation per line; one result per line back
bin/hubctl sync devices ls # and `sync devices forget <id>`
bin/hubctl sync snapshot --out state.ndjson --apply # the initial synchronisation as one stream; the cursor is the last line
bin/hubctl import trello board.json --hub "$HUB" --wait 5m # also csv, google-tasks, microsoft-todo; the report on the run
bin/hubctl due set "$ITEM" --at 2026-09-10 # a day; a timestamp is a moment instead
bin/hubctl remind add "$ITEM" --at -PT30M # half an hour before it is due
bin/hubctl recur set "$ITEM" --rule "FREQ=WEEKLY;BYDAY=MO" --zone Europe/Berlin
bin/hubctl template instantiate "$TEMPLATE" --collection "$COLLECTION" --anchor 2026-09-07
bin/hubctl view export "$VIEW" --format ICS --out week.ics
bin/hubctl calendar mint --view "$VIEW" # prints the feed URL once; it is the credentialThe automation surface, and the inbox it can act on:
RULE=$(bin/hubctl rule add --name "mail becomes a task" --trigger JUMBLE_ENTRY \
--run-as "$ACCOUNT" \
--action 'CONVERT_JUMBLE_ENTRY:{"collection_id":"'"$COLLECTION"'"}' | awk 'NR==2 {print $1}')
bin/hubctl rule test "$RULE" --event de.hubtask.jumble.entry.received.v1 # the dry run
bin/hubctl rule enable "$RULE" # a rule is written switched off, always
bin/hubctl rule runs --rule "$RULE" # what it has done, newest first
bin/hubctl rule run show "$RUN" # one run, step by step
bin/hubctl jumble intake rotate-token # the address the inbox accepts deliveries on, once
bin/hubctl jumble ls --status NEW
bin/hubctl jumble convert "$ENTRY" --collection "$COLLECTION"
bin/hubctl jumble dismiss "$ENTRY" # a state, not a deletion
WEBHOOK=$(bin/hubctl webhook add --url https://example.org/hooks \
--event de.hubtask.work.item.completed.v1 | awk 'NR==2 {print $1}')
bin/hubctl webhook deliveries "$WEBHOOK" --status FAILED
bin/hubctl webhook replay "$WEBHOOK" "$DELIVERY" # the same event id, so a repeat is recognisable
bin/hubctl webhook rotate-secret "$WEBHOOK" --grace 0 # 0 retires the old one at once
bin/hubctl events poll de.hubtask.work.item.completed.v1 --since "$CURSOR"What an operator does with an installation is the same client:
TARGET=$(bin/hubctl backup target add --name nightly --kind LOCAL --config path=daily | awk 'NR==2 {print $1}')
bin/hubctl backup target test "$TARGET"
bin/hubctl backup run --target "$TARGET" --follow --wait 30m
bin/hubctl backup ls --target "$TARGET" # what is actually lying at the target
bin/hubctl backup verify "$RUN" --follow # fails the command if it does not verify
bin/hubctl backup schedule set "$SCHEDULE" --trial # every FULL run is followed by an INSPECT of its own archive
bin/hubctl restore inspect --target "$TARGET" --archive "$ARCHIVE"
bin/hubctl restore run --target "$TARGET" --archive "$ARCHIVE" --mode NEW_TENANT --apply
# a dry run with a report until --apply is typed
bin/hubctl retention add --kind COMPLETED_ITEM --days 90 --action TRASH
bin/hubctl retention preview "$POLICY" # what it would take, and what stands in the way
bin/hubctl hold place --scope CONTAINER --id "$COLLECTION" --reason "the Meier proceedings"
bin/hubctl audit query --from 2026-08-01 --action auth.
bin/hubctl audit verify --from 2026-08-01 --to 2026-09-01 # red when the chain does not hold
bin/hubctl dsr create --kind ERASURE --subject "$ACCOUNT"
bin/hubctl dsr start "$CASE" --mode ANONYMIZE # the transition that runs the work
bin/hubctl job show "$JOB" --followErrors are the message catalogue's sentences rather than the problem document behind them — the server emits codes, never display text (ADR-0011). Which platforms the binary is supported on is in support-matrix.md §4.
| Area | Choice |
|---|---|
| Language | Go (≥ 1.27), net/http, log/slog |
| Database | PostgreSQL 16+ (pgx/v5, sqlc, goose) |
| API | OpenAPI 3.1 spec-first (oapi-codegen), RFC 9457, cursor pagination |
| Events | Transactional outbox, CloudEvents 1.0, optionally NATS JetStream |
| Automation | Declarative rules, conditions in CEL (cel-go) |
| Scheduling | RFC 5545 RRULE (rrule-go), PostgreSQL job queue |
| AI | MCP server; AI port with adapters for OpenAI-compatible APIs and Ollama |
| Object storage | S3 or any S3-compatible service, or a local volume |
| Observability | OpenTelemetry, Prometheus; dashboards, alert rules, and runbooks included |
| Resilience | Circuit breakers, bulkheads, load shedding, dead letter, idempotency throughout |
| Backup | Own logical archive format (JSON Lines + manifest), AES-256-GCM, adapters for S3/SFTP/FTPS/WebDAV/SMB/Azure/GCS/rclone/local |
| Offline sync | Delta sync via a change log, hybrid logical clocks, OR-sets, fractional indices |
| Clients | Svelte 5 + TypeScript; the webapp as a plain Vite SPA, served from inside the binary; Tauri 2 shells for desktop and mobile planned |
| Design system | Design tokens (W3C DTCG, Style Dictionary) as the single origin of every visual value; pnpm workspace |
| Deployment | Docker/Podman Compose, Helm for Kubernetes |
Hubtask is open source under the Apache License 2.0 — free for any use, commercial included, with the full feature set and no licence key (ADR-0080, licensing-editions.md). The name and the logo are covered by TRADEMARK.md, not by the licence. Donations fund the work — see the funding section.
Conventional Commits, trunk-based development, Definition of Ready/Done from engineering-guidelines.md. Architectural changes arrive as an ADR, not as a pull request without context. See CONTRIBUTING.md, SECURITY.md, and CODE_OF_CONDUCT.md.