lific
Local-first, lightweight issue tracker. Single Rust binary, SQLite-backed, MCP-native.
Install
Issue tracking for the agentic coding era.
One binary. One SQLite database (plus an attachments dir). MCP built in.
Your agent can write the code. What it can't do is remember: the plan dies with the context window, the TODO list rots in a markdown file, and the next session starts from zero. Lific is the missing memory: a self-hosted, single-binary issue tracker whose primary user is often an agent rather than a person.
Three numbers instead of adjectives:
- 31 MCP tools in 7,610 tokens. That's the measured size of the full
tools/listtool definitions (o200k tokenizer). Your entire tracker costs about as much context as one long file read. - One binary, 25 to 32 MB depending on platform. Embedded SQLite, embedded web UI, backups built in. The data set is just the database and a content-addressed
attachments/dir beside it (both covered by the automatic backups). No Docker, no Postgres, no reverse proxy, no daemon farm. Copy it to a server, point your agents at it, done. - 11 AI clients configured by one command.
lific connectwrites correct MCP config into OpenCode, Claude Code, Cursor, VS Code, Codex, Zed, and more. No hand-edited JSON.
Identifiers are human-readable everywhere: APP-42, never a UUID. They survive being spoken, logged, grepped, and pasted into a prompt.
60-second setup
cargo install lific # or grab a binary from the releases page
lific init # config, database and your admin account, then
# installs a background service and starts it.
# The server is now on :3456 and survives reboot.
lific connect # writes MCP config into your AI clients
That's the whole thing. lific init asks your name and how you want to sign in (login-free for a private local instance, or passwords), then sets everything up in your OS's standard locations (config in ~/.config/lific/, data in ~/.local/share/lific/ on Linux; macOS and Windows equivalents) so it works the same from any directory. Use lific init --here if you'd rather keep a directory-local instance (./lific.toml + ./lific.db). It registers the server with your OS service manager (a systemd user unit on Linux, a LaunchAgent on macOS, a per-user startup entry on Windows), so it isn't a process tied to your terminal: it's still running tomorrow. lific connect then detects the AI tools installed on your machine, lets you pick, mints a per-tool API key, and merges correct MCP config into each one without overwriting existing config. Restart your client and the Lific tools are there.
Manage the service anytime with lific service status | restart | stop | uninstall. Prefer a foreground process (containers, supervisors, debugging)? lific init --no-service skips the service and lific start runs the server in your terminal.
The web UI is at http://localhost:3456, and you are its administrator already. Login-free mode signs you in automatically; with passwords, sign in with the username init prints, which is your name in lowercase with hyphens (Ada Lovelace becomes ada-lovelace).
Verify any setup with:
lific doctor # green/yellow/red checks: config, database, server,
# OAuth discovery, and a real MCP round-trip
doctor exits nonzero if anything is actually broken, so agents and CI can gate on it.
What your agent can now do
- Resume a project in one call.
get_briefing(project="APP")returns active plans with their next step, blocked issues and what blocks them (issues, people, or date windows), date waits that have come due, the top workable and active issues, and the pages you name, in about 6,000 characters. Pass thesincecursor it gave you last time and it leads with what changed. - Ask "what can I work on right now?" in one call.
list_issues(project="APP", workable=true)returns only issues with every blocker resolved. Dependency-aware triage without a graph query. - Block on people and dates, not only issues.
link_issues(target="APP-9", relation_type="blocks", user="ada-lovelace")parks an issue until someone clears it;from="2026-09-28", until="2026-10-02", note="filing office"parks it until a window opens, then shows it as due and later overdue. - Keep a plan alive across sessions. Plans are persistent, nestable step trees. A fresh session calls
get_planand resumes exactly where the last one left off. NoMEMORY.md, no re-priming ritual. - Break work down and wire it up. Create issues, link blockers (
blocks,relates_to,duplicate), group them into modules, and mirror plan steps to real issues with two-way done/close sync. - Leave a real audit trail.
get_activityanswers "what changed while I was gone": who changed what, when, and through which tool. Passsinceto read forward from where you stopped. Every agent's work is attributed (more below). - Close with the proof attached.
update_issue(status="done", evidence="cargo test: 412 passed")saves the evidence as a verification comment in the same write as the close.get_issuemarks it[verification]and the web UI badges it, so whoever reviews the close can see what was actually checked. If the description has a- [ ]task list,get_issueandlist_issuesshow its progress (3/5), and closing with boxes still unchecked says how many. - Write docs where the issues live. Markdown pages in folders, with comments, labels, lifecycle status, and Mermaid diagrams. Design decisions stay next to the work they justify.
- Edit without resending.
edit_issue/edit_pagedo targeted find-and-replace, so updating one line of a long description doesn't cost the whole document in tokens. - Read long pages a section at a time. A page over 30,000 characters comes back as an outline with section sizes plus its opening, and
get_page(section="Current state")fetches just that part. Nothing is silently cut off by the harness. Coming back to a page later,get_page(since_seq=...)returns only the lines that changed. Writes that push a page past the budget say so, with a nudge to split it. - Take everything with you.
exportturns an issue, a page, or a whole project into portable markdown, no lock-in.
Every tool gets its own identity, and that's the point
lific connect mints a separate bot identity per tool, owned by your account (opencode-blake, cursor-blake, ...). When several agents work the same project, provenance is the primitive that keeps you sane:
- The audit log shows which harness made every change. "OpenCode closed APP-42", "Cursor edited the design page". All attributed to you, never blurred together.
- Revoke one tool without touching the others. Cursor misbehaving? Disconnect just its key and OpenCode keeps working.
- Scoped authority. Each bot inherits its owner's project access and nothing more.
This is the recommended way to connect agent harnesses.
Connecting AI tools
lific connect is the front door. It supports eleven clients out of the box:
opencode · claude-code · claude-desktop · cursor · vscode · codex · zed · gemini · windsurf · goose · crush
lific connect # interactive picker
lific connect --client opencode --client cursor --yes # non-interactive
lific connect --client claude-code --scope project # repo-local .mcp.json
lific connect --client zed --stdio # no server: direct SQLite over stdio
lific connect --dry-run --client vscode # preview without writing
Each client gets its native schema (mcpServers vs servers vs mcp, Codex TOML with an env-var token, Goose YAML; the quirks are handled). JSON configs are merged non-destructively; a file connect can't parse safely is left untouched and you get the exact snippet to paste instead.
Lific implements the full MCP authorization spec (RFC 9728 protected-resource metadata, dynamic client registration, PKCE), so OAuth-capable clients can connect with just the URL and complete auth in the browser:
lific connect --oauth --client opencode # writes a header-less config, mints nothing
opencode mcp auth lific # browser opens → sign in → approve
# or with any client's native command:
claude mcp add --transport http lific http://localhost:3456/mcp
The approval page asks which tool is connecting and mints that tool's bot identity under your account, the same kind lific connect creates, so OAuth-connected tools keep per-tool attribution and can be revoked one at a time. The difference is only where the credential comes from: the client runs the browser flow instead of connect writing a key into its config.
Headless / SSH / agents. No browser on the box? The device flow has you covered:
lific login # prints a URL + short code; approve on any device
lific login --non-interactive # agent mode: prints JSON {verification_uri, user_code,
# device_code, next_step} and exits immediately
lific login --complete <code> # finish later, from a script or a second session
Tokens are stored in your OS keyring (Secret Service / Keychain / Credential Manager), falling back to a 0600 file with a loud warning when no keyring exists.
Remote (Streamable HTTP):
{
"lific": {
"type": "remote",
"url": "http://localhost:3456/mcp",
"headers": { "Authorization": "Bearer your-api-key" }
}
}
Local (stdio, no server):
{
"lific": {
"type": "local",
"command": ["lific", "--db", "path/to/lific.db", "mcp"]
}
}
Without LIFIC_TOKEN in its environment, a stdio session runs as the instance operator; set it to an API key to act as a specific user or bot. Create keys anytime with lific key create --name my-key (add --user <name> to bind one to an account).
Go to Settings > Connected tools in the web UI. Pick your tool, click Connect, and paste the generated config snippet.
Each connection creates a bot identity tied to your account (the CLI's connect does the same). Changes show up attributed to you, tagged with which tool made them.
Plans: state that outlives the session
An agent's plan shouldn't die when its context does. A plan is an ordered, arbitrarily-nestable tree of steps that persists across sessions and compaction. Start a new session, call get_plan, and it's still there, ready to resume.
- Steps can mirror issues. Link a step to an issue and the two stay in sync while the plan is active: mark the issue
doneand the step checks itself; check the step and the issue becomesdone. Move the issue out ofdoneand the step unchecks, with a note of why. - Authored in one call.
create_planbuilds a full nested tree at once;edit_plan_stepandupdate_plan_stepkeep it current. - First-class in the UI. A Plans tab sits alongside Issues, Board, Modules, and Pages: a real tree view with done toggles, per-step markdown notes, issue chips, and an activity timeline.
- Fully tracked. Every plan and step change lands in the audit log, including the issue-driven cascades.
Issues stay flat and lateral; the hierarchy lives on the plan. It's the difference between an issue tracker and a project planner.
Built for agents, not just reachable by them
lific agents-mdwrites an idempotent, marker-delimited block into your repo'sAGENTS.mdtelling every agent that this project uses Lific: project identifier, CLI examples, and the workflow conventions.lific connectoffers to do this automatically in project context.- Session instructions. The MCP server ships its conventions in the
initializeresponse, so connected agents know how Lific wants to be used without you explaining it. - Self-onboarding. On a fresh database, the MCP tools tell the agent exactly how to bootstrap (
create a project first: manage_resource(...)) instead of returning an empty list. - Pipe-native CLI. Output auto-upgrades to JSON when piped, so
lific issue list --project APP | jqjust works, no--jsonneeded (though it's there). Prompts never hang a non-interactive caller; they fail fast and name the bypass flag. - Shell completions:
lific completion fish | source(bash, zsh, fish, powershell, elvish).
The CLI works directly against the database, with no server or auth required. Data commands also support an HTTP backend when you want the CLI to operate on a remote Lific instance:
lific project list
lific issue list --project APP
lific issue create --project APP --title "Fix login bug" --priority high
lific issue update APP-42 --status done
lific search "authentication" --project APP
# Remote mode (the same commands use the server API)
lific --backend http --url https://lific.example.com --api-key "$LIFIC_API_KEY" issue list --project APP
lific --backend http --url https://lific.example.com --api-key "$LIFIC_API_KEY" issue update APP-42 --status done
--backend http can read the URL and bearer key from LIFIC_URL and LIFIC_API_KEY. If no API key is supplied, it also uses the credential from lific login (LIFIC_TOKEN, keyring, or credential file). The default backend remains direct SQL; HTTP mode never opens the local database for data commands. Both backends print the same human-readable and JSON output. A remote project export downloads one archive file from the server rather than individual Markdown files.
MCP tools
All 31, in 7,610 tokens:
| Family | Tools |
|---|---|
| Issues | list_issues · get_issue · create_issue · update_issue · bulk_update · edit_issue · get_board |
| Relations & waits | link_issues · unlink_issues |
| Pages | get_page · create_page · update_page · edit_page |
| Plans | create_plan · get_plan · edit_plan_step · update_plan_step |
| Comments | add_comment · list_comments · edit_comment · delete_comment |
| Attachments | upload_attachment · get_attachment · list_attachments |
| Search & history | get_briefing · search · get_activity |
| Structure | list_resources · manage_resource · delete |
| Export | export (issue, page, or whole project by ID) |
Everything takes human-readable identifiers (project="APP", not project_id=7). The behaviors worth knowing about are covered in "What your agent can now do" above; for exact schemas, connect a client and read tools/list.
Features
| Category | What you get |
|---|---|
| Issue tracking | Status, priority, modules with icons, labels, relations, comments, board view, fuzzy search, sort by recent activity |
| Plans | Persisted, nestable step trees that outlive a session; steps mirror issues with two-way done/close sync |
| Documentation | Markdown pages in recursive folders, with comments, labels, lifecycle status, full-text search, and Mermaid diagrams |
| MCP interface | 31 tools, human-readable identifiers, compact schema, session instructions |
| Onboarding | One-command setup (lific init installs a background service), lific connect (11 clients), lific doctor, lific agents-md, shell completions |
| REST API | Resource endpoints, search, board view, and relationship/planning operations |
| Web UI | Markdown editing with live preview, drag-and-drop board, Mermaid and code-copy, dark/light theme |
| User accounts | Individual auth, per-tool bot identities, project membership and roles |
| Auth | OAuth 2.1 (PKCE, dynamic client registration, RFC 9728 discovery), RFC 8628 device flow, API keys, token revocation |
| Backups | lific dump / lific restore single-archive backups, plus automatic interval archives with retention |
| CLI | Scriptable issue/project/page/plan commands, TTY-aware JSON output, works with no server running |
| Single binary | No runtime dependencies, embedded SQLite, ~25 MB |
When Lific is the wrong tool
Honesty is cheaper than churn:
- You need enterprise team features. SSO/SAML isn't here, and neither is the sprint-estimate-gantt layer of project management. If you're coordinating forty humans, use Linear or Plane.
- You want issues as files in the repo. Lific is a database with an API, not markdown-in-git. If you want
git diffon your task list, a markdown-native tracker fits better. - You need distributed multi-writer sync. One Lific instance is one SQLite file: a single source of truth that's trivially backed up, not a CRDT. Multiple agents talk to one server; the server doesn't merge with other servers.
For one human directing several agents across personal projects (the thing it's built for), none of those trade-offs bite.
Authorization
Lific has project-scoped, default-deny authorization: viewer / maintainer / lead membership is enforced on project-scoped REST and MCP calls, including reads. Instance administrators and operator-trusted credentials intentionally bypass project membership checks, while instance-scoped endpoints have their own rules. Fresh installs (created on 2.0+) enforce it by default; instances upgraded from an earlier version keep it off until you opt in, so nothing changes under you on upgrade. Toggle it at runtime:
lific instance set --authz-enforced true # or false
With enforcement on, a newly created user sees nothing until they're granted membership. Manage access from the CLI (or the web UI's project members page):
lific member add --project LIF --user sam # viewer by default
lific member add --all --user sam --role maintainer # every project at once
lific member role -p LIF -u sam -r lead # change a role
lific member remove -p LIF -u sam
lific member list --project LIF
Forgotten password? The operator can reset one from the shell (this signs out all of that user's sessions):
lific user set-password --username sam
Auth can be turned off entirely for a private, local instance with required = false under [auth] in lific.toml. That is what lific init's login-free mode writes, together with host = "127.0.0.1". Credential-less requests then get admin-equivalent access; a presented-but-invalid token still fails loudly. The web UI signs you in automatically as the first admin instead of showing a login form. This is a config-file key on purpose (flipping it requires shell access, like minting an operator key), and it comes with a guard rail: the server refuses to start when server.host is not a loopback address, or when server.public_url points at anything other than a loopback or private-network host. Browser auto-login is refused under the same conditions.
Unbound API keys bypass authorization by design. A key with no user binding (from lific key create without --user, from lific connect on an instance with no human accounts, or the one lific start mints on a database with neither keys nor users) is operator-trusted: it can only be created by someone with shell access to the server, so it's treated as admin-equivalent even in enforced mode. That keeps an instance with no human accounts manageable from the shell. The threat the default guards against is a web-signup stranger's session/OAuth token, not the operator's own shell-minted key. Audit these keys any time with:
lific key list
Prefer per-tool bot identities (what lific connect mints when you have a user account) over unbound keys: a bot inherits its owner's project access and shows up in the audit log by name.
API-key verifier upgrades are a coordinated data-format change. The first successful use of an indexed legacy key rewrites its Argon2 verifier as sha256:v1; keys created or rotated by the new binary are also stored as sha256:v1. Older Lific binaries cannot authenticate those rows. Upgrade every server, CLI, and long-lived stdio process that shares the database together, and do not roll back to a binary that only understands Argon2 after any key has been created, rotated, or migrated. Active legacy rows without an indexed ID are revoked during upgrade. lific key list marks those rows UNSUPPORTED FORMAT / unsupported_format; rotate before reuse only for integrations that still need credentials. Rotation preserves the key's existing expiry and owner.
Configuration
lific.tomllific init generates this (login-free mode then sets host = "127.0.0.1" and required = false):
[server]
host = "0.0.0.0"
port = 3456
cors_origins = []
trusted_proxies = []
[database]
path = "lific.db"
[backup]
enabled = true
dir = "backups"
interval_minutes = 60
retain = 24
[log]
level = "info"
[auth]
allow_signup = true
required = true
[retention]
trash_days = 30
retention.trash_days is how long deleted issues, pages and comments stay restorable before they are purged; 0 keeps them forever. Signup is open by default, so anyone who can reach the server can create an ordinary account; close it with lific instance set --signups false or in the web UI's instance settings.
CLI flags (--db, --port, --host) override config values. Set server.public_url when exposing Lific beyond localhost; it becomes the OAuth issuer and the URL lific connect writes into client configs. server.trusted_proxies controls which peers may supply X-Forwarded-For or X-Real-IP; it defaults to none. Add only isolated proxy IPs/CIDRs you operate, and prevent direct clients from reaching that ingress.
Config is discovered in standard locations, first match wins:
--config <path>(used alone, no fallback)./lific.toml(current directory)- User config dir:
~/.config/lific/lific.tomlon Linux ($XDG_CONFIG_HOMErespected),~/Library/Application Support/lific/on macOS,%APPDATA%\lific\on Windows - System config dir:
/etc/lific/lific.tomlon Linux/BSD,/Library/Application Support/Lific/on macOS,%ProgramData%\lific\on Windows
A relative database.path always resolves against the config file's own directory, so the same config works no matter where the process starts. lific init --config /path/to/lific.toml and lific service install --config ... root the whole instance (config, database, service working directory) at that path.
Backup and restore
The data set is the database plus a content-addressed attachments/ dir beside it. lific dump packages both into one self-contained archive, taking a consistent DB snapshot via VACUUM INTO that is safe while the server is running:
lific dump # → ./lific_20260703_141500.tar.gz
lific dump --out /mnt/backups # directory → default filename inside it
Each archive contains the DB snapshot, every attachment blob, and a manifest.json (Lific version, schema version, sizes). Restoring is the mirror image. Stop the server first:
lific restore lific_20260703_141500.tar.gz # refuses to overwrite an existing db
lific restore lific_20260703_141500.tar.gz --force # moves the current db aside to lific.db.pre-restore-<ts>
Restores are staged (a failure leaves the original data dir untouched) and refuse archives created by a newer Lific; older archives are fine, and pending migrations apply on next start. Archives over the default limits (512 MiB database, 64 MiB per attachment, 1 GiB total, 10,000 entries) need --allow-large.
The automatic interval backups ([backup] in config) write the same .tar.gz artifact to the backup dir with rotation. External backup harnesses (restic, borg, cron) can either scoop up that dir or call lific dump as a pre-backup hook:
# e.g. restic pre-hook
lific dump --out /srv/backup-staging/lific.tar.gz && restic backup /srv/backup-staging
Building from source
The repository provides Rust, Bun, and the release toolchains through devenv. Approve the checkout, enter the shell, and use the task graph:
git clone https://github.com/VoidNullable/lific
cd lific
devenv allow
devenv shell
# inside the devenv shell:
devenv tasks run lific:debug-build
To activate Devenv automatically when changing into the checkout, add the
Devenv hook for your shell once (for zsh, eval "$(devenv hook zsh)"), then
run devenv allow in this checkout.
Release builds always build and embed the Svelte 5 web UI:
devenv --profile release-linux tasks run lific:release:x86_64-unknown-linux-gnu
Use release-darwin for macOS targets and release-windows-msvc for the
Windows MSVC cross-build on Linux. devenv build outputs.lific builds the
native, Nix-packaged release; it builds the locked frontend in an isolated
derivation and embeds it before compiling Rust. SQLite is bundled via
rusqlite; no system SQLite is required.
Docker (optional)
You don't need Docker to run Lific; it's one binary. The Dockerfile in the repo exists mainly so MCP directory indexers can build and verify the server, but it produces a working image (full web UI, distroless runtime) if a container fits your setup:
docker build -t lific .
docker run -p 3456:3456 -v lific-data:/data \
-e LIFIC_INIT_ADMIN_NAME="Your Name" \
-e LIFIC_INIT_ADMIN_PASSWORD="a long password" \
lific
The database lives at /data/lific.db, so mount a volume there to keep it. The image runs start --init-if-missing, which creates and migrates that file on first boot and does nothing on every boot after.
Both environment variables are required for that first boot, and they create the first admin. Leave either one out and the container refuses to start instead of creating an empty instance: an instance with no users has signup open, makes whoever loads the page first its administrator, and prints an unbound operator API key into the container log. An existing database ignores both variables, so they can stay in your compose file or machine config.
Authentication is required either way. First-boot init creates a database; it never turns auth off.
If an instance somehow ends up with no administrator, create one from inside the container (docker exec <container> ..., or fly ssh console on Fly):
lific --db /data/lific.db user create \
--username <name> --email <address> --password <password> --admin
Community
Questions, feedback, or a setup worth showing off? Join the Lific Discord. Release announcements land there too, and support questions get answered fastest in #support.
Contributing
Issues and PRs welcome. If you're planning something big, open an issue first so we can talk about it before you put in the work.
MCP Registry
Lific ships a registry manifest (server.json) for the official MCP Registry. Its canonical registry name:
mcp-name: io.github.VoidNullable/lific