taskgraph

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 4, 2026 License: MIT Imports: 23 Imported by: 0

README

Taskgraph

Go Version CI

Taskgraph is a local-first task manager for people and coding agents. It stores one workspace in a SQLite file and exposes the same application layer through:

  • a local web app and JSON HTTP API;
  • an MCP server over stdio;
  • a Go package;
  • portable JSON backup and Markdown, JSON, and CSV import/export.

Install

go install github.com/beremaran/taskgraph/cmd/taskgraph@latest

Release artifacts (Electron desktop app, Codex plugin) are on the Releases page.

Start the local app with:

make dev

Then open http://localhost:8080. The default development database is .taskgraph.sqlite in the repository root.

Build the Codex plugin and Electron macOS desktop app with:

make package

This writes an expanded Codex plugin, an Electron Taskgraph.app bundle, and zip archives under dist/. Both packages use the same workspace in the operating system's app-data directory by default. Set TASKGRAPH_WORKSPACE to make both use another SQLite file.

Choose the guide that matches your role:

  • Agent setup: build and install the Codex plugin, select a workspace, and run an agent work cycle.
  • App guide: start the desktop or web app and use its planning, queue, monitoring, and backup views.
  • Technical usage guide: domain model, API and MCP reference, recovery, and development details.

Useful commands:

go run ./cmd/taskgraph mcp --workspace .taskgraph.sqlite --agent-name codex --capability go
go run ./cmd/taskgraph backup --workspace .taskgraph.sqlite backup.json
go run ./cmd/taskgraph doctor --workspace .taskgraph.sqlite
make test
make e2e
make package

The product and architecture specification lives in docs/GOAL.md and docs/ARCHITECTURE.md. The local HTTP contract is checked in at docs/openapi.yaml; current limits are listed in docs/deviations.md.

Frontend API path and request types are generated from that contract during yarn --cwd web build; run yarn --cwd web types:api after contract edits.

Contributing

See CONTRIBUTING.md for how to build, test, report issues, and submit changes.

License

Taskgraph is released under the MIT License. Copyright (c) 2026 Berke Arslan.

Documentation

Index

Constants

View Source
const SchemaVersion = 2

Variables

View Source
var (
	ErrNotFound          = errors.New("NOT_FOUND")
	ErrAmbiguous         = errors.New("AMBIGUOUS_REFERENCE")
	ErrVersionConflict   = errors.New("VERSION_CONFLICT")
	ErrDependencyCycle   = errors.New("DEPENDENCY_CYCLE")
	ErrHierarchyCycle    = errors.New("HIERARCHY_CYCLE")
	ErrNoReady           = errors.New("NO_READY_ITEM")
	ErrClaimConflict     = errors.New("CLAIM_CONFLICT")
	ErrInvalidTransition = errors.New("INVALID_STATE_TRANSITION")
	ErrPathConflict      = errors.New("PATH_CONFLICT")
)
View Source
var WebAssets embed.FS

WebAssets keeps the release binary self-contained. The development build refreshes web/dist before compiling the command.

Functions

func ParseQuery

func ParseQuery(ref string) (any, error)

Types

type ActivityInput

type ActivityInput struct {
	Actor       string   `json:"actor,omitempty"`
	Operations  []string `json:"operations,omitempty"`
	EntityTypes []string `json:"entity_types,omitempty"`
	EntityID    string   `json:"entity_id,omitempty"`
	After       string   `json:"after,omitempty"`
	Before      string   `json:"before,omitempty"`
	Cursor      string   `json:"cursor,omitempty"`
	Limit       int      `json:"limit,omitempty"`
}

type App

type App struct {
	// contains filtered or unexported fields
}

func Open

func Open(path string, cfg Config) (*App, error)

func (*App) ActivityPage

func (a *App) ActivityPage(in ActivityInput) (map[string]any, error)

ActivityPage provides filterable transaction summaries. Cursor pagination is revision based because a workspace has exactly one transaction per revision.

func (*App) Agents

func (a *App) Agents() ([]map[string]any, error)

func (*App) ArchiveItem

func (a *App) ArchiveItem(ref any, expectedVersion int, includeDescendants bool) (map[string]any, error)

ArchiveItem hides an item and its descendants from normal search and scheduling. Archive is separate from workflow status, so restoring it never changes status.

func (*App) Backup

func (a *App) Backup() ([]byte, error)

func (*App) Changes

func (a *App) Changes(after int64, limit int) ([]map[string]any, error)

func (*App) ClaimNext

func (a *App) ClaimNext(scope Scope, lease int, expected, idempotency string) (ClaimResult, error)

func (*App) Close

func (a *App) Close() error

func (*App) CommitRestore

func (a *App) CommitRestore(data []byte, expectedRevision int64, confirmation string) (map[string]any, error)

CommitRestore checks the live revision, saves a local pre-restore backup, and then replaces the workspace. The confirmation string must be RESTORE.

func (*App) Context

func (a *App) Context(ref any) (ContextBundle, error)

func (*App) CreateItems

func (a *App) CreateItems(in CreateInput) (map[string]any, error)

func (*App) CreateSavedView

func (a *App) CreateSavedView(name, mode, query, project string) (map[string]any, error)

func (*App) CurrentRevision

func (a *App) CurrentRevision() int64

func (*App) DeleteSavedView

func (a *App) DeleteSavedView(id string, expectedVersion int) (map[string]any, error)

func (*App) Doctor

func (a *App) Doctor() ([]string, error)

Doctor runs read-only integrity checks and returns human-readable findings.

func (*App) ExplainBlocked

func (a *App) ExplainBlocked(ref any) ([]Reason, error)

func (*App) ForceReleaseClaim

func (a *App) ForceReleaseClaim(id string, expectedRevision int64, confirmation string) (map[string]any, error)

ForceReleaseClaim is the local administrator escape hatch for a claim whose token is unavailable. It requires both an exact revision and an explicit confirmation string, and records the release in the audit log.

func (*App) GetGraph

func (a *App) GetGraph(ref any, max int) (GraphResult, error)

func (*App) GetGraphWithOptions

func (a *App) GetGraphWithOptions(ref any, opt GraphOptions) (GraphResult, error)

GetGraphWithOptions preserves the legacy slice while letting clients narrow rendered edges. Hierarchy context remains included for navigation safety.

func (*App) Import

func (a *App) Import(source, format string, dryRun bool) (map[string]any, error)

func (*App) ItemTimeline

func (a *App) ItemTimeline(ref any, limit int) (ItemTimeline, error)

ItemTimeline returns notes and auditable changes for the item. It is bounded so the drawer stays responsive on large local workspaces.

func (*App) ListClaims

func (a *App) ListClaims(activeOnly bool, limit int) ([]map[string]any, error)

func (*App) ManageClaim

func (a *App) ManageClaim(id, token, action string, lease int) (Claim, error)

func (*App) MarkdownExport

func (a *App) MarkdownExport() (string, error)

MarkdownExport is a readable snapshot. It deliberately includes stable IDs in HTML comments so a later import can preserve identity when available.

func (*App) MoveItem

func (a *App) MoveItem(ref any, parentRef any, slug string, expectedVersion int) (map[string]any, error)

func (*App) MutateBatch

func (a *App) MutateBatch(in BatchInput) (map[string]any, error)

MutateBatch validates every expected version before changing anything. All supported mutations share one revision and either commit together or roll back together.

func (*App) MutateRelationship

func (a *App) MutateRelationship(r RelationshipInput) (map[string]any, error)

func (*App) PatchItem

func (a *App) PatchItem(ref any, in PatchInput) (map[string]any, error)

func (*App) PeekNext

func (a *App) PeekNext(scope Scope, maxAlt, maxBlocked int) (NextResult, error)

func (*App) PreviewRestore

func (a *App) PreviewRestore(data []byte) (RestorePreview, error)

PreviewRestore validates the envelope, loads it into a temporary database, and runs the same integrity checks used by doctor. It never changes the live workspace.

func (*App) ProjectTreePage

func (a *App) ProjectTreePage(ref, cursor string, limit int, includeArchived bool) (map[string]any, error)

ProjectTreePage is a bounded, stable read model for a project hierarchy. It avoids the global search limit and adds tree-only status summaries without changing Item's backwards-compatible JSON shape.

func (*App) Ready

func (a *App) Ready(it *Item) ([]Reason, bool)

func (*App) RecentActivity

func (a *App) RecentActivity(limit int) ([]map[string]any, error)

func (*App) RecordEntry

func (a *App) RecordEntry(ref any, entryType, body string) (map[string]any, error)

func (*App) ReorderItem

func (a *App) ReorderItem(ref any, beforeRef, afterRef any) (map[string]any, error)

func (*App) ReportWork

func (a *App) ReportWork(ref any, status, reason string) (map[string]any, error)

func (*App) Resolve

func (a *App) Resolve(ref any) (*Item, string, error)

func (*App) Restore

func (a *App) Restore(data []byte) error

func (*App) RestoreItem

func (a *App) RestoreItem(ref any, expectedVersion int, includeDescendants bool) (map[string]any, error)

func (*App) SavedViews

func (a *App) SavedViews() ([]map[string]any, error)

func (*App) Search

func (a *App) Search(query string, limit int) ([]*Item, error)

func (*App) SearchPage

func (a *App) SearchPage(in SearchInput) (map[string]any, error)

SearchPage adds stable cursors and structured filters without changing the old MCP-facing Search signature.

func (*App) SessionID

func (a *App) SessionID() string

func (*App) Settings

func (a *App) Settings() (map[string]any, error)

func (*App) Undo

func (a *App) Undo(revision int64) (map[string]any, error)

Undo safely reverses simple field edits while rejecting anything changed by a later revision. It never guesses at an old hierarchy or relationship graph.

func (*App) UpdateSavedView

func (a *App) UpdateSavedView(id string, expectedVersion int, name, mode, query, project string, layout any) (map[string]any, error)

func (*App) UpdateSettings

func (a *App) UpdateSettings(in map[string]any) (map[string]any, error)

UpdateSettings accepts the settings resources used by the desktop client in one atomic request. It intentionally keeps workspace preferences as JSON so new appearance keys do not force a migration.

func (*App) UpdateWorkspaceName

func (a *App) UpdateWorkspaceName(name string) error

func (*App) WorkspaceID

func (a *App) WorkspaceID() string

func (*App) WorkspaceRevision

func (a *App) WorkspaceRevision() (int64, error)

func (*App) WorkspaceSummary

func (a *App) WorkspaceSummary() (map[string]any, error)

WorkspaceSummary returns the small, bounded read model used by the overview.

type BatchInput

type BatchInput struct {
	Mutations []BatchMutation `json:"mutations"`
	DryRun    bool            `json:"dry_run,omitempty"`
}

type BatchMutation

type BatchMutation struct {
	ID                 string         `json:"id"`
	ExpectedVersion    int            `json:"expected_version"`
	Operation          string         `json:"operation"`
	Set                map[string]any `json:"set,omitempty"`
	Parent             any            `json:"parent,omitempty"`
	Before             any            `json:"before,omitempty"`
	After              any            `json:"after,omitempty"`
	IncludeDescendants bool           `json:"include_descendants,omitempty"`
}

type BlockedExample

type BlockedExample struct {
	Item    *Item    `json:"item"`
	Reasons []Reason `json:"reasons"`
}

type Capability

type Capability struct {
	Capability           string `json:"capability"`
	AppliesToDescendants bool   `json:"applies_to_descendants,omitempty"`
}

type Claim

type Claim struct {
	ID         string `json:"id"`
	Token      string `json:"token,omitempty"`
	ClaimedAt  string `json:"claimed_at"`
	ExpiresAt  string `json:"expires_at"`
	RenewAfter string `json:"renew_after"`
	Active     bool   `json:"active"`
}

type ClaimResult

type ClaimResult struct {
	Item        *Item        `json:"item"`
	Claim       Claim        `json:"claim"`
	Explanation *Explanation `json:"explanation"`
	Revision    int64        `json:"revision"`
}

type Config

type Config struct {
	WorkspaceID  string
	ActorName    string
	AgentName    string
	Capabilities []string
	MaxClaims    int
}

type ContextBundle

type ContextBundle struct {
	Item         *Item          `json:"item"`
	Ancestors    []*Item        `json:"ancestors"`
	Children     []*Item        `json:"children"`
	Dependencies []Relationship `json:"dependencies"`
}

type CreateInput

type CreateInput struct {
	Items          []CreateSpec        `json:"items"`
	Relationships  []RelationshipInput `json:"relationships,omitempty"`
	Atomic         bool                `json:"atomic"`
	DryRun         bool                `json:"dry_run"`
	IdempotencyKey string              `json:"idempotency_key,omitempty"`
}

func ParseImport

func ParseImport(source, format string) (CreateInput, []string, error)

ParseImport converts the supported text formats into the same create command used by the API and MCP adapters. It does not touch the database.

type CreateSpec

type CreateSpec struct {
	ClientRef            string         `json:"client_ref"`
	Parent               any            `json:"parent,omitempty"`
	Kind                 string         `json:"kind"`
	Title                string         `json:"title"`
	Slug                 string         `json:"slug,omitempty"`
	Status               string         `json:"status,omitempty"`
	ExecutionMode        string         `json:"execution_mode,omitempty"`
	ChildGate            string         `json:"child_gate,omitempty"`
	CompletionPolicy     string         `json:"completion_policy,omitempty"`
	Description          string         `json:"description,omitempty"`
	Priority             int            `json:"priority,omitempty"`
	DueAt                string         `json:"due_at,omitempty"`
	NotBefore            string         `json:"not_before,omitempty"`
	AcceptanceCriteria   []string       `json:"acceptance_criteria,omitempty"`
	Tags                 []string       `json:"tags,omitempty"`
	RequiredCapabilities []Capability   `json:"required_capabilities,omitempty"`
	CustomFields         map[string]any `json:"custom_fields,omitempty"`
}

type Criterion

type Criterion struct {
	ID       string `json:"id"`
	Text     string `json:"text"`
	State    string `json:"state"`
	OrderKey int64  `json:"order_key"`
}

type Explanation

type Explanation struct {
	SelectedBecause []string       `json:"selected_because"`
	Rank            map[string]any `json:"rank"`
}

type GraphOptions

type GraphOptions struct {
	MaxItems  int      `json:"max_items,omitempty"`
	Hops      int      `json:"hops,omitempty"`
	Direction string   `json:"direction,omitempty"`
	EdgeTypes []string `json:"edge_types,omitempty"`
	Strength  string   `json:"strength,omitempty"`
}

type GraphResult

type GraphResult struct {
	Nodes     []*Item        `json:"nodes"`
	Edges     []Relationship `json:"edges"`
	Truncated bool           `json:"truncated"`
}

type Item

type Item struct {
	ID               string         `json:"id"`
	Version          int            `json:"version"`
	LastRevision     int64          `json:"last_revision"`
	WorkspaceID      string         `json:"workspace_id,omitempty"`
	ParentID         *string        `json:"parent_id"`
	ProjectID        string         `json:"project_id"`
	Kind             string         `json:"kind"`
	Title            string         `json:"title"`
	Slug             string         `json:"slug"`
	Path             string         `json:"path"`
	Depth            int            `json:"depth"`
	OrderKey         int64          `json:"order_key"`
	Status           string         `json:"status"`
	EffectiveState   string         `json:"effective_state"`
	ExecutionMode    string         `json:"execution_mode"`
	ChildGate        string         `json:"child_gate"`
	CompletionPolicy string         `json:"completion_policy"`
	Description      string         `json:"description,omitempty"`
	Priority         int            `json:"priority"`
	DueAt            *string        `json:"due_at,omitempty"`
	NotBefore        *string        `json:"not_before,omitempty"`
	AssigneeActorID  *string        `json:"assignee_actor_id,omitempty"`
	ArchivedAt       *string        `json:"archived_at,omitempty"`
	CreatedAt        string         `json:"created_at"`
	UpdatedAt        string         `json:"updated_at"`
	Acceptance       []Criterion    `json:"acceptance_criteria,omitempty"`
	Tags             []string       `json:"tags,omitempty"`
	CustomFields     map[string]any `json:"custom_fields,omitempty"`
}

type ItemEntry

type ItemEntry struct {
	ID        string `json:"id"`
	ItemID    string `json:"item_id"`
	Actor     string `json:"actor"`
	EntryType string `json:"entry_type"`
	Body      string `json:"body"`
	CreatedAt string `json:"created_at"`
}

type ItemTimeline

type ItemTimeline struct {
	Entries  []ItemEntry      `json:"entries"`
	Activity []map[string]any `json:"activity"`
}

type NextResult

type NextResult struct {
	Selected        *Item            `json:"selected"`
	Explanation     *Explanation     `json:"explanation,omitempty"`
	Alternatives    []*Item          `json:"alternatives"`
	BlockedExamples []BlockedExample `json:"blocked_examples"`
	Revision        int64            `json:"workspace_revision"`
}

type PatchInput

type PatchInput struct {
	ExpectedVersion       int              `json:"expected_version"`
	Set                   map[string]any   `json:"set,omitempty"`
	Unset                 []string         `json:"unset,omitempty"`
	TagOperations         []map[string]any `json:"tag_operations,omitempty"`
	AcceptanceOperations  []map[string]any `json:"acceptance_operations,omitempty"`
	CustomFieldOperations []map[string]any `json:"custom_field_operations,omitempty"`
	IdempotencyKey        string           `json:"idempotency_key,omitempty"`
}

type Reason

type Reason struct {
	Code           string `json:"code"`
	Message        string `json:"message"`
	Item           *Item  `json:"item,omitempty"`
	BlockingItem   *Item  `json:"blocking_item,omitempty"`
	UnresolvedLeaf *Item  `json:"unresolved_leaf,omitempty"`
}

type Relationship

type Relationship struct {
	ID       string `json:"id"`
	Type     string `json:"type"`
	From     string `json:"from"`
	To       string `json:"to"`
	Strength string `json:"strength,omitempty"`
	Waived   bool   `json:"waived"`
}

type RelationshipInput

type RelationshipInput struct {
	Operation string `json:"operation,omitempty"`
	ID        string `json:"id,omitempty"`
	Type      string `json:"type"`
	From      any    `json:"from"`
	To        any    `json:"to"`
	Strength  string `json:"strength,omitempty"`
	Reason    string `json:"reason,omitempty"`
}

type RestorePreview

type RestorePreview struct {
	Format            string         `json:"format"`
	SchemaVersion     int            `json:"schema_version"`
	WorkspaceID       string         `json:"workspace_id"`
	WorkspaceRevision int64          `json:"workspace_revision"`
	RowCounts         map[string]int `json:"row_counts"`
	ActiveClaims      int            `json:"active_claims"`
	Warnings          []string       `json:"warnings"`
}

type Scope

type Scope struct {
	Under           any  `json:"under,omitempty"`
	IncludeArchived bool `json:"include_archived,omitempty"`
}

type SearchInput

type SearchInput struct {
	Query           string   `json:"query,omitempty"`
	ProjectID       string   `json:"project_id,omitempty"`
	Statuses        []string `json:"statuses,omitempty"`
	Kinds           []string `json:"kinds,omitempty"`
	Priorities      []int    `json:"priorities,omitempty"`
	Readiness       string   `json:"readiness,omitempty"`
	Claims          string   `json:"claims,omitempty"`
	Overdue         *bool    `json:"overdue,omitempty"`
	IncludeArchived bool     `json:"include_archived,omitempty"`
	Sort            string   `json:"sort,omitempty"`
	Cursor          string   `json:"cursor,omitempty"`
	Limit           int      `json:"limit,omitempty"`
}

SearchInput is the bounded, cursor based search contract used by the desktop client. The legacy Search method remains available for MCP compatibility.

Directories

Path Synopsis
cmd
taskgraph command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL