Overview
Taskter is a powerful, terminal-based Kanban board designed for developers, project managers, and anyone who prefers to manage their tasks directly from the command line. Built with Rust, Taskter is a lightweight and efficient tool that helps you keep track of your projects without leaving the terminal.
Key Features
- Kanban Board: Visualize your workflow with ToDo, In-Progress, and Done columns.
- Task Management: Add, edit, delete, and manage tasks with simple commands.
- Agent System: Automate tasks using LLM-based agents with tool-calling capabilities.
- Project Tracking: Keep track of your project's description, objectives, and key results (OKRs).
- Operation Logs: Maintain a log of all operations performed on the board.
- Interactive TUI: A user-friendly terminal interface for easy navigation and task management.
This book serves as the official documentation for Taskter, providing a comprehensive guide to its features and usage. Whether you're a new user or a seasoned developer, this guide will help you get the most out of Taskter.
Installation
You can install Taskter from prebuilt packages or build from source.
Homebrew
brew tap tomatyss/taskter
brew install taskter
Linux packages
Prebuilt .deb archives are generated using cargo deb and can be downloaded
from the GitHub release page. Install them with dpkg -i:
sudo dpkg -i taskter_0.1.0_amd64.deb
For Alpine Linux there is an APKBUILD script in packaging/apk/ which can be
used with abuild -r to produce an apk package.
Build from Source
To build Taskter from source, you need to have Rust and Cargo installed.
-
Clone the repository:
git clone https://github.com/tomatyss/taskter.git cd taskter -
Build the project:
cargo build --releaseThe executable will be located at
target/release/taskter. -
Install the executable: You can make
taskteravailable system-wide by copying it to a directory in your system'sPATH. For example, on macOS or Linux:sudo cp target/release/taskter /usr/local/bin/taskterAlternatively, you can use
cargo install:cargo install --path .This will install the
taskterexecutable in your Cargo bin directory (~/.cargo/bin/), which should be in yourPATH.
Docker
If you prefer to use Docker, you can build and run Taskter without installing Rust locally.
-
Build the Docker image:
docker build -t taskter . -
Run the application:
docker compose run --rm taskter --helpIf you plan to use the Gemini integration for agents, you'll need to pass your API key as an environment variable:
GEMINI_API_KEY=<your_key> docker compose run --rm taskter --help
CLI Usage
Taskter exposes multiple subcommands. Run taskter --help to see the available options. The README lists common workflows.
Quick Start
This section provides a quick overview of how to get started with Taskter.
1. Initialize the board
First, navigate to your project's directory and initialize the Taskter board:
taskter init
This will create a .taskter directory to store all your tasks, agents, and project data.
All operation logs are written to .taskter/logs.log. Inspect this file directly
or run taskter logs list to view the history.
2. Create an agent
Next, create an agent to help you with your tasks. For this example, we'll create a simple agent that can run bash commands:
taskter agent add --prompt "You are a helpful assistant that can run bash commands." --tools "run_bash" --model "gemini-pro"
You can list all available agents using:
taskter agent list
You can list the built-in tools with:
taskter tools list
3. Create a task
Now, let's create a task for your agent to complete:
taskter task add -t "List files in the current directory" -d "Use the ls -la command to list all files and folders in the current directory."
You can see all your tasks by running:
taskter task list
4. Assign the task to an agent
Assign the newly created task to your agent:
taskter task assign --task-id 1 --agent-id 1
To remove the agent later:
taskter task unassign --task-id 1
5. Execute the task
Finally, execute the task:
taskter task execute --task-id 1
The agent will now run the task. If it's successful, the task will be marked as "Done". You can view the board at any time using the interactive UI:
taskter board
TUI Guide
Taskter's interactive Terminal User Interface (TUI) provides a visual way to manage your Kanban board. To launch it, run:
taskter board
Keybindings
The TUI is controlled with keyboard shortcuts. Here is a list of the available keybindings:
| Key(s) | Action |
|---|---|
q | Quit the application |
← / → or Tab | Navigate between columns |
↑ / ↓ | Navigate between tasks in a column |
h / l | Move a selected task to the next or previous column |
n | Create a new task |
u | Edit the selected task |
d | Delete the selected task |
a | Assign an agent to the selected task |
r | Unassign the selected task's agent |
c | Add a comment to the selected task |
L | View project logs |
A | List available agents |
O | Show project OKRs |
? | Show available commands |
When a task is selected, you can press Enter to view its details, including the full description, any comments, and the assigned agent ID.
Agent System
Taskter supports LLM-based agents that can be assigned to tasks. Agents are model-agnostic via a provider layer. Gemini is the default provider today, and additional providers can be added without changing the agent loop. See Model Providers.
This chapter explains how the runtime coordinates models and tools, how to inspect runs, and how to extend the system.
Execution Flow
- Bootstrap history – Taskter combines the agent’s system prompt with the selected task (title plus description, when available) and asks the resolved provider for the next action.
- Provider response – Providers return either:
Text: a final message, which is recorded in the task comment log and marks execution as successful.ToolCall: name, arguments, and optionalcall_id. Tool calls are executed synchronously on the host.
- Tool execution – Built-in tools are dispatched through
tools::execute_tool. Any failure is surfaced as an agent failure with the tool error message. - Loop – Providers receive the tool result (including
call_idwiring for multi-turn APIs) and the process repeats until a final text response arrives. - Logging – High-level events are appended to
.taskter/logs.log. Raw provider requests and responses are mirrored to.taskter/api_responses.logfor debugging.
If the provider requires an API key and none is present in the environment, Taskter enters offline simulation mode. Agents that include the send_email tool are treated as successful with a stubbed comment; other agents fail and explain that the required tool is unavailable. This keeps tests deterministic while signalling that a real API key is needed for end-to-end execution.
Creating an Agent
You can create an agent using the agent add subcommand. You need to provide a prompt, a list of tools, and a model.
taskter agent add --prompt "You are a helpful assistant." --tools "project_files" "run_bash" --model "gemini-2.5-pro"
The --tools option accepts either paths to JSON files describing a tool or the name of a built-in tool. Built-in tools live under tools/ in the repository, and their declarations are bundled into the binary.
You can display the registry at any time with:
taskter tools list
Built-in Tool Reference
| Tool name | Purpose | Required arguments | Notes |
|---|---|---|---|
run_bash | Execute a shell command inside the project directory | command (string) | Returns trimmed stdout; non-zero status bubbles up as a failure |
run_python | Execute inline Python and return stdout | code (string) | Uses the system Python interpreter |
project_files | Read, create, update, or search text files | action; create/read/update: path; update: content; search: query | Uses the supplied path verbatim (no sandbox); alias file_ops |
get_description | Retrieve the project description text from .taskter/description.md | none | Handy for planning/reporting agents |
send_email / email | Send email via SMTP | to, subject, body | Requires .taskter/email_config.json; email is an alias |
taskter_task | Proxy to taskter task … CLI | args (array of strings) | Invoke task subcommands (add, list, assign, execute, etc.) |
taskter_agent | Proxy to taskter agent … CLI | args (array of strings) | Manage agents programmatically |
taskter_okrs | Proxy to taskter okrs … CLI | args (array of strings) | Add or list OKRs |
taskter_tools | Proxy to taskter tools list | args (array of strings) | Usually ["list"]; useful for self-inspection |
web_search | Fetch a DuckDuckGo summary | query (string) | Respects SEARCH_API_ENDPOINT; requires outbound network access |
Assigning an Agent to a Task
Once you have created an agent, you can assign it to a task using the assign subcommand:
taskter task assign --task-id 1 --agent-id 1
Unassigning an Agent
Remove an agent from a task without executing it:
taskter task unassign --task-id 1
Executing a Task
To execute a task with an assigned agent, use the execute subcommand:
taskter task execute --task-id 1
When a task is executed, the agent will attempt to perform the task. If successful, the task is marked as "Done". If it fails, the task is moved back to "To Do", unassigned, and a comment from the agent is added.
In the interactive board (taskter board), tasks assigned to an agent will be marked with a *. You can view the assigned agent ID and any comments by selecting the task and pressing Enter.
Updating an Agent
Use the agent update command to modify an existing agent's configuration:
taskter agent update --id 1 --prompt "New prompt" --tools "taskter_task" --model "gemini-pro"
All three options are required and the previous configuration is overwritten.
Debugging Agent Runs
- High-level activity – Inspect
.taskter/logs.logfor a chronological record of agent starts, tool invocations, and outcomes. - Provider payloads –
.taskter/api_responses.logstores JSON requests and responses for each step; this is invaluable when bringing up a new provider or debugging schema issues. - CLI status – CLI commands currently exit with status
0even when an agent reports a failure. Rely on the printed message or the comment added to the task (along with the logs above) to detect unsuccessful runs.
Creating Custom Tools
Agents can use custom tools in addition to the built-in ones. A tool is defined in a JSON file with three required fields:
name– the unique identifier for the tooldescription– a short explanation of what the tool doesparameters– JSON Schema describing the arguments
An example tool definition:
{
"name": "say_hello",
"description": "Return a greeting for the provided name",
"parameters": {
"type": "object",
"properties": {
"name": { "type": "string", "description": "Name to greet" }
},
"required": ["name"]
}
}
Save this JSON to a file, for instance hello_tool.json, and pass the path to
taskter agent add:
taskter agent add --prompt "Be friendly" --tools "./hello_tool.json" --model "gemini-pro"
You can mix file paths and built-in tool names in the --tools list.
Scheduling Agents
Taskter can run agents automatically based on cron expressions. A scheduler daemon reads the agent configuration and executes the assigned tasks at the defined times.
Setting a Schedule
Use the agent schedule set command to assign a cron expression to an agent. The expression is parsed in the America/New_York timezone.
# Run every minute
taskter agent schedule set --id 1 --cron "0 * * * * *"
Pass --once to remove the schedule after the first run.
Listing and Removing
List all scheduled agents with:
taskter agent schedule list
Remove a schedule:
taskter agent schedule remove --id 1
Running the Scheduler
Start the scheduler loop with:
taskter scheduler run
The scheduler will execute agents at the configured times and update tasks just as if task execute was run manually.
When multiple tasks are assigned to the same agent, the scheduler now runs them
concurrently so long-running jobs don't block each other.
Model Providers
Taskter’s agent system is model‑agnostic. A provider layer adapts the neutral agent loop to a specific LLM API. Providers convert between Taskter’s message history and the provider’s wire format, and translate responses into either a text completion or a tool call.
All provider requests and responses are mirrored to .taskter/api_responses.log
so that you can inspect the exact JSON being exchanged when debugging a new
integration.
Built-in Providers
- Gemini (default): selected when
agent.modelstarts withgemini.- Env var:
GEMINI_API_KEY - Code:
src/providers/gemini.rs
- Env var:
- OpenAI: selected when
agent.modelstarts withgpt-4,gpt-5,gpt-4o,gpt-4.1,o1,o3,o4, oromni.- Env var:
OPENAI_API_KEY - Code:
src/providers/openai.rs - APIs:
- Chat Completions: used for models like
gpt-4oandgpt-4o-mini. Tools are passed as{"type":"function","function":{...}}and responses carrychoices[0].message.tool_calls[]. - Responses API: used for
gpt-4.1,gpt-5,o-series, and Omni models. Input is an item list; tool calls arrive as{"type":"function_call", name, arguments, call_id}inoutput[]and you must append both thefunction_calland afunction_call_outputitem with the samecall_id.
- Chat Completions: used for models like
- Optional overrides:
OPENAI_BASE_URLto point at a proxy (https://api.openai.comby default)OPENAI_CHAT_ENDPOINT/OPENAI_RESPONSES_ENDPOINTfor full URL controlOPENAI_REQUEST_STYLE=chat|responsesto force the request formatOPENAI_RESPONSE_FORMATcontaining either a JSON blob (e.g.{"type":"json_object"}) or shorthand (json_object)
- Env var:
- Ollama: selected when
agent.modelstarts withollama:,ollama/, orollama-.- Env var:
OLLAMA_BASE_URL(defaults tohttp://localhost:11434) - Code:
src/providers/ollama.rs - Uses the local
/api/chatendpoint and mirrors the Chat Completions tool schema.
- Env var:
Configure a Provider
- Choose a model string when creating/updating an agent (e.g.
gemini-2.5-pro,gpt-4.1,o1-mini, orollama:llama3). - Set the provider explicitly when running CLI commands by passing
--provider gemini|openai|ollama. To clear a stored provider, usetaskter agent update --provider none …; new agent creation does not acceptnone. When no provider is stored Taskter falls back to model-name heuristics. - Export the provider’s API key environment variable before running agents.
- Gemini:
export GEMINI_API_KEY=your_key_here - OpenAI:
export OPENAI_API_KEY=your_key_here - Ollama does not require an API key. Optionally set
OLLAMA_BASE_URLif your daemon listens somewhere other thanhttp://localhost:11434.
- Gemini:
If no valid API key is present, Taskter falls back to an offline simulation. No
real tool calls are made: agents that include the send_email tool return a
stubbed success comment, while all other agents are marked as failed so you can
spot the missing credentials.
Add a New Provider
Implement the ModelProvider trait and register it in select_provider.
- Create a file under
src/providers/, e.g.my_provider.rs:
#![allow(unused)] fn main() { use serde_json::{json, Value}; use anyhow::Result; use crate::agent::Agent; use super::{ModelAction, ModelProvider}; pub struct OpenAIProvider; impl ModelProvider for OpenAIProvider { fn name(&self) -> &'static str { "openai" } fn api_key_env(&self) -> &'static str { "OPENAI_API_KEY" } fn build_history(&self, agent: &Agent, user_prompt: &str) -> Vec<Value> { vec![json!({ "role": "user", "content": [ {"type": "text", "text": format!("System: {}\\nUser: {}", agent.system_prompt, user_prompt)} ] })] } fn append_tool_result(&self, history: &mut Vec<Value>, tool: &str, _args: &Value, tool_response: &str) { history.push(json!({ "role": "tool", "content": [{ "type": "tool_result", "name": tool, "content": tool_response }] })); } fn tools_payload(&self, agent: &Agent) -> Value { // Map our FunctionDeclaration to OpenAI tools schema json!(agent.tools.iter().map(|t| { json!({ "type": "function", "name": t.name, "description": t.description, "parameters": t.parameters, "strict": true }) }).collect::<Vec<_>>()) } fn endpoint(&self, _agent: &Agent) -> String { "https://api.openai.com/v1/responses".to_string() } fn request_body(&self, history: &[Value], tools: &Value) -> Value { json!({ "model": "gpt-4.1", "input": history, "tools": tools }) } fn parse_response(&self, v: &Value) -> Result<ModelAction> { if let Some(tc) = v["output"][0].get("tool_calls").and_then(|x| x.get(0)) { let name = tc["function"]["name"].as_str().unwrap_or_default().to_string(); let args = tc["function"]["arguments"].clone(); return Ok(ModelAction::ToolCall { name, args }); } // Fallback to text content let text = v["output_text"].as_str().unwrap_or("").to_string(); Ok(ModelAction::Text { content: text }) } fn headers(&self, api_key: &str) -> Vec<(String, String)> { vec![ ("Authorization".into(), format!("Bearer {}", api_key)), ("Content-Type".into(), "application/json".into()), ] } } }
- Register it in
select_providerinsidesrc/providers/mod.rs:
#![allow(unused)] fn main() { pub fn select_provider(agent: &Agent) -> Box<dyn ModelProvider + Send + Sync> { let model = agent.model.to_lowercase(); if model.starts_with("gemini") { Box::new(gemini::GeminiProvider) } else if model.starts_with("gpt-") { Box::new(openai::OpenAIProvider) } else { Box::new(gemini::GeminiProvider) } } }
- Set the API key and choose a matching model:
export OPENAI_API_KEY=your_key
# Example agent
taskter agent add --prompt "Be helpful" --tools run_bash --model my-model --provider openai
Notes
- The agent loop is neutral: it asks a provider for one step, executes a tool if requested, and appends the result via the provider to maintain the correct message format.
- Providers must ensure tools are represented in the target API’s expected
schema and that responses are robustly parsed into
ModelAction. - See
src/providers/gemini.rsandsrc/providers/openai.rsas complete reference implementations.
OpenAI Responses: Tool Calling Flow
The Responses API differs from Chat Completions. A typical multi‑turn flow:
- Send input as a list (start with user):
[ {"role":"user", "content":[{"type":"input_text","text":"Use run_bash to echo hello"}]} ] - Model returns an
outputarray which can include{"type":"function_call", "name":"run_bash", "arguments":"{\"command\":\"echo hi\"}", "call_id":"call_123"}. - Execute the tool, then append to your input list:
{"type":"function_call","call_id":"call_123","name":"run_bash","arguments":"{\"command\":\"echo hi\"}"}, {"type":"function_call_output","call_id":"call_123","output":"hello"} - Call the Responses API again with the expanded
inputand the sametools. The model will produce a finalmessagewithoutput_text.
Taskter automates these steps inside the provider, including the call_id wiring and multi‑turn loop.
Data Files
Taskter keeps all project state inside a .taskter directory. This folder is created when you run taskter init.
The following files are stored there and are automatically updated by Taskter.
board.json
Holds the Kanban board in JSON format. The file contains all tasks with their status, descriptions and assigned agent. It is rewritten whenever you add, edit or complete tasks from the CLI or TUI.
agents.json
Stores the list of agents. Each agent entry records the system prompt, available tools, model and optional schedule. The file is created on demand and modified by the various agent subcommands.
okrs.json
Contains your objectives and key results. Commands under taskter okrs load and save this file.
logs.log
Plain text log with timestamps. New lines are appended when you run logs add or when agents execute tasks.
description.md
Markdown file describing the project. taskter init creates a placeholder that you can edit manually or through the TUI.
email_config.json
Optional email credentials used by the send_email tool. Place it in the directory if agents need to send messages. The exact keys are documented in the Configuration chapter.
Configuration
This chapter describes runtime configuration files and environment variables used by Taskter.
Email configuration file
Email-based tools expect credentials in .taskter/email_config.json inside your project. Every agent reads the same file.
{
"smtp_server": "smtp.example.com",
"smtp_port": 587,
"username": "user@example.com",
"password": "secret",
"imap_server": "imap.example.com", // optional
"imap_port": 993 // optional
}
Currently only the SMTP fields are used by the send_email tool. The IMAP keys are accepted for future extensions. No default values are provided, so fill in the details that match your mail provider.
If the file is missing the tool outputs Email configuration not found. When Taskter runs without a GEMINI_API_KEY, email tools are skipped entirely so the file is not required for tests.
Environment variables
Agents use a provider abstraction. Each provider defines its own API key env var:
GEMINI_API_KEY— API key for the Gemini provider.OPENAI_API_KEY— API key when using an OpenAI provider (if added).SEARCH_API_ENDPOINT— custom endpoint for theweb_searchtool. Defaults tohttps://api.duckduckgo.com.
Export the relevant variable directly in your shell or via Docker Compose. For Gemini:
export GEMINI_API_KEY=your_key_here
Contributing
Contributions are welcome! This guide will help you get started with setting up your development environment and submitting your changes.
Development Environment
To contribute to Taskter, you'll need to have Rust and Cargo installed. If you haven't already, follow the instructions at rust-lang.org.
Once you have Rust set up, clone the repository and navigate to the project directory:
git clone https://github.com/tomatyss/taskter.git
cd taskter
Pre-commit checks
Before committing any changes, please run the pre-commit script to ensure your code is formatted, linted, and passes all tests:
./scripts/precommit.sh
You can also automatically apply formatting and Clippy suggestions with:
./scripts/fix_lints.sh
You can also set this up as a pre-commit hook to run automatically:
ln -s ../../scripts/precommit.sh .git/hooks/pre-commit
Documentation
The documentation is built with mdBook. To contribute to the docs, edit the Markdown files under the docs/src/ directory.
When you're ready, open a pull request with your changes. The GitHub Actions workflow will automatically build and publish the book when your changes are merged into the main branch.