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.

  1. Clone the repository:

    git clone https://github.com/tomatyss/taskter.git
    cd taskter
    
  2. Build the project:

    cargo build --release
    

    The executable will be located at target/release/taskter.

  3. Install the executable: You can make taskter available system-wide by copying it to a directory in your system's PATH. For example, on macOS or Linux:

    sudo cp target/release/taskter /usr/local/bin/taskter
    

    Alternatively, you can use cargo install:

    cargo install --path .
    

    This will install the taskter executable in your Cargo bin directory (~/.cargo/bin/), which should be in your PATH.

Docker

If you prefer to use Docker, you can build and run Taskter without installing Rust locally.

  1. Build the Docker image:

    docker build -t taskter .
    
  2. Run the application:

    docker compose run --rm taskter --help
    

    If 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
qQuit the application
/ or TabNavigate between columns
/ Navigate between tasks in a column
h / lMove a selected task to the next or previous column
nCreate a new task
uEdit the selected task
dDelete the selected task
aAssign an agent to the selected task
rUnassign the selected task's agent
cAdd a comment to the selected task
LView project logs
AList available agents
OShow 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

  1. 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.
  2. 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 optional call_id. Tool calls are executed synchronously on the host.
  3. Tool execution – Built-in tools are dispatched through tools::execute_tool. Any failure is surfaced as an agent failure with the tool error message.
  4. Loop – Providers receive the tool result (including call_id wiring for multi-turn APIs) and the process repeats until a final text response arrives.
  5. Logging – High-level events are appended to .taskter/logs.log. Raw provider requests and responses are mirrored to .taskter/api_responses.log for 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 namePurposeRequired argumentsNotes
run_bashExecute a shell command inside the project directorycommand (string)Returns trimmed stdout; non-zero status bubbles up as a failure
run_pythonExecute inline Python and return stdoutcode (string)Uses the system Python interpreter
project_filesRead, create, update, or search text filesaction; create/read/update: path; update: content; search: queryUses the supplied path verbatim (no sandbox); alias file_ops
get_descriptionRetrieve the project description text from .taskter/description.mdnoneHandy for planning/reporting agents
send_email / emailSend email via SMTPto, subject, bodyRequires .taskter/email_config.json; email is an alias
taskter_taskProxy to taskter task … CLIargs (array of strings)Invoke task subcommands (add, list, assign, execute, etc.)
taskter_agentProxy to taskter agent … CLIargs (array of strings)Manage agents programmatically
taskter_okrsProxy to taskter okrs … CLIargs (array of strings)Add or list OKRs
taskter_toolsProxy to taskter tools listargs (array of strings)Usually ["list"]; useful for self-inspection
web_searchFetch a DuckDuckGo summaryquery (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.log for a chronological record of agent starts, tool invocations, and outcomes.
  • Provider payloads.taskter/api_responses.log stores 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 0 even 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 tool
  • description – a short explanation of what the tool does
  • parameters – 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.model starts with gemini.
    • Env var: GEMINI_API_KEY
    • Code: src/providers/gemini.rs
  • OpenAI: selected when agent.model starts with gpt-4, gpt-5, gpt-4o, gpt-4.1, o1, o3, o4, or omni.
    • Env var: OPENAI_API_KEY
    • Code: src/providers/openai.rs
    • APIs:
      • Chat Completions: used for models like gpt-4o and gpt-4o-mini. Tools are passed as {"type":"function","function":{...}} and responses carry choices[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} in output[] and you must append both the function_call and a function_call_output item with the same call_id.
    • Optional overrides:
      • OPENAI_BASE_URL to point at a proxy (https://api.openai.com by default)
      • OPENAI_CHAT_ENDPOINT / OPENAI_RESPONSES_ENDPOINT for full URL control
      • OPENAI_REQUEST_STYLE=chat|responses to force the request format
      • OPENAI_RESPONSE_FORMAT containing either a JSON blob (e.g. {"type":"json_object"}) or shorthand (json_object)
  • Ollama: selected when agent.model starts with ollama:, ollama/, or ollama-.
    • Env var: OLLAMA_BASE_URL (defaults to http://localhost:11434)
    • Code: src/providers/ollama.rs
    • Uses the local /api/chat endpoint and mirrors the Chat Completions tool schema.

Configure a Provider

  • Choose a model string when creating/updating an agent (e.g. gemini-2.5-pro, gpt-4.1, o1-mini, or ollama:llama3).
  • Set the provider explicitly when running CLI commands by passing --provider gemini|openai|ollama. To clear a stored provider, use taskter agent update --provider none …; new agent creation does not accept none. 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_URL if your daemon listens somewhere other than http://localhost:11434.

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.

  1. 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()),
        ]
    }
}
}
  1. Register it in select_provider inside src/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)
    }
}
}
  1. 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.rs and src/providers/openai.rs as complete reference implementations.

OpenAI Responses: Tool Calling Flow

The Responses API differs from Chat Completions. A typical multi‑turn flow:

  1. Send input as a list (start with user):
    [
      {"role":"user", "content":[{"type":"input_text","text":"Use run_bash to echo hello"}]}
    ]
    
  2. Model returns an output array which can include {"type":"function_call", "name":"run_bash", "arguments":"{\"command\":\"echo hi\"}", "call_id":"call_123"}.
  3. 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"}
    
  4. Call the Responses API again with the expanded input and the same tools. The model will produce a final message with output_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 the web_search tool. Defaults to https://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.