Architecture Documentation
Generated from archledger records. Do not edit this generated file directly.
Introduction and Goals
Current canonical storage boundary
The current canonical design uses the schema-3 Ledgercore project at .ledger/ledger.toml with Taskledger configuration at .ledger/task/config.toml. Taskledger mounts are separated into authoritative durable data, checkout-local runtime, diagnostic logs, and rebuildable cache indexes. Canonical initialization never creates .taskledger/ or root taskledger.toml; those names are legacy migration inputs only.
Taskledger operational state is resolved outside the application source repository by default. .ledger/ledger.local.toml is a machine-local override and .taskledger/ is ignored as legacy residue. The legacy .taskledger layout described in older sections remains compatibility and migration material, not the canonical storage design.
Taskledger is a task-first durable state layer for staged coding work. It provides a Python CLI and library that manages the full lifecycle of coding tasks: creation, planning, user approval, implementation, validation, and completion.
The system is designed for use by both human developers and automated coding agents. Its primary goals are:
Durable task state: Every task, plan, run, lock, todo, and validation check is persisted as a Markdown record with YAML front matter in the project Ledgercore data mount. State survives process restarts, context switches, and handoffs between actors.
Explicit lifecycle gates: Transitions between stages (draft -> planning -> plan_review -> approved -> implementing -> implemented -> validating -> done) are enforced by policy decisions. User approval is required before implementation begins. Validation checks gate completion.
Fresh-context handoffs: Agents and humans can create, claim, and close handoff records that capture enough context (task state, plan, todos, questions, lock status) for a fresh process to continue work without reading the entire history.
Machine-readable output: Every CLI command supports
--jsonfor structured output with a stable envelope shape (ok,command,result_type,result,events,warnings) and deterministic exit codes.Opaque cross-ledger references: Task-ledger task, plan, file, and link references remain opaque strings. Taskledger does not interpret ledgercore global refs, archledger IDs, or other external system identifiers.
The canonical workflow is:
task -> plan -> approval -> implement -> validate -> done
This workflow is the product contract, not decoration. Deviations from this flow require explicit user decisions or repair commands.
Requirements Overview
Title |
Priority |
Source |
Stakeholders |
Quality goals |
|---|---|---|---|---|
Durable task lifecycle state |
must |
|||
Explicit lifecycle gates with user approval |
must |
|||
Fresh-context handoffs for agent continuation |
must |
Quality Goals
Stakeholders
Title |
Contact |
Expectations |
|---|---|---|
Coding agents |
deterministic exit codes, stable json envelope, fresh context handoff continuation, task first workflow, human approval gate |
|
Human developers |
human approval workflow, concise human rendering, plan review and acceptance, acceptance criterion waivers, monitor overview |
Architecture Constraints
Taskledger operates under several fixed constraints that shape its architecture:
Python 3.10+ with minimal dependencies: The runtime depends only on
typer,click,PyYAML,tomli(Python <3.11), andledgercorefor atomic I/O, JSON I/O, YAML I/O, front matter parsing, and cross-ledger ref parsing. No database, no network server, no external service is required.File-system canonical storage: All durable state lives in the project Ledgercore mounts configured by
.ledger/ledger.toml, as Markdown files with YAML front matter. This makes state inspectable, diffable, and version-controllable alongside source code.CLI-first with machine-readable JSON output: The primary interface is the
taskledgerCLI command. Every command supports--jsonfor structured output. The JSON envelope shape and exit codes are part of the public contract.Skills outside the package: Agent skill files (for example
skills/taskledger/SKILL.md) live outside the Python package and are never packaged as Python package data. The package provides the CLI and library; skill distribution is separate.Project-local configuration: Each project has its Ledgercore manifest at
.ledger/ledger.tomland ledger-specific configuration under.ledger/. There is no global state or central server.Source-first architecture records: Arc42 architecture records are stored in
.ledger/archledger/dataand are the source of truth fordocs/architecture.md.docs/architecture_taskledger_split.mdis a concise human-maintained summary. Skills stay outside both the Python package and the archledger build output.Python 3.10+ with minimal ledgercore-backed dependencies
Impact: Limits runtime to Python 3.10+ with three dependencies; no database or native extensions.
Notes: Runtime dependencies are limited to
typer,click,PyYAML,tomli(Python <3.11 only), andledgercore(for atomic I/O, JSON I/O, YAML I/O, front matter parsing, and cross-ledger ref parsing). This constraint ensures easy installation in constrained environments (CI, containers, Termux) and avoids dependency conflicts with host projects. The trade-off is that features like full-text search use pure-Python implementations rather than native libraries.
File-system canonical storage with sidecar summary index
Impact: State is file-based; query performance depends on index rebuilds.
Notes: All durable state is stored as Markdown files with YAML front matter in
.taskledger/. This makes state human-readable, diffable in Git, and inspectable without taskledger. The trade-off is that query performance depends on file scanning andtask_sidecars.jsonsummary index rebuilding rather than a database engine. The sidecar index is maintained in place by per-task sidecar writes and rebuilt from canonical records on miss.
CLI-first with machine-readable JSON output
Impact: JSON envelope shape and exit codes are public API contracts; breaking changes require version bumps.
Notes: The CLI is the primary interface. Every command supports
--jsonfor machine-readable output with a stable envelope shape (ok,command,result_type,result,events,warnings) and deterministic exit codes. This enables agent harnesses and CI pipelines to consume output programmatically without parsing human text.
Skills must stay outside the Python package
Impact: Skill distribution is separate from package distribution; skill installation is a distinct step.
Notes: Agent skill files (e.g.,
skills/taskledger/SKILL.md) live outside thetaskledgerPython package. They are not packaged as package data, not loaded viaimportlib.resources, and not distributed via PyPI. This separates the concerns of tool functionality (the package) from agent integration instructions (the skill).
Context and Scope
Taskledger operates as a self-contained tool within a software development project. It interacts with four categories of external actors:
Agent harnesses (pi, codex, chatgpt, and similar) invoke taskledger CLI commands to create tasks, propose plans, log implementation changes, run validation checks, and manage handoffs. They consume
--jsonoutput.Human developers use the CLI directly in terminals for task creation, plan review, approval, lock management, and inspection (
status,context,next-action,doctor,monitor).CI systems may invoke taskledger for status checks, validation, snapshot/export operations, and
taskledger tracebundles.Python library consumers import from
taskledger.api.*to programmatically manage tasks without the CLI subprocess.
The system boundary is the .taskledger/ directory and the taskledger.toml config file at the project root. Everything inside .taskledger/ is taskledger-owned state. Everything outside is the host project source code. Taskledger does not depend on any external services, databases, or network endpoints. It reads the host project file system for search/symbol operations but does not modify files outside .taskledger/.
Business Context
Technical Context
Solution Strategy
Taskledger uses a layered architecture with clear dependency direction: upper layers depend on lower layers, never the reverse.
CLI Layer (
taskledger/cli*.py) — Typer commands that parse arguments, resolve task references, call service functions, and render output (human text or JSON).API Layer (
taskledger/api/*.py) — Stable public wrappers that mirror the CLI surface for programmatic use.Services Layer (
taskledger/services/*.py) — Orchestration logic: lifecycle flows (planning, implementation, validation), handoff rendering, doctor checks, plan input validation, plan review, snapshot capture, dashboard assembly, and navigation.Domain Layer (
taskledger/domain/*.py) — Pure data models, state enums, normalization, and policy decisions. No I/O, no file system access.Storage Layer (
taskledger/storage/*.py) — File system operations: front matter read/write, atomic writes, lock files, sidecar index rebuilds, migrations. Storage delegates low-level primitives toledgercore.
Key architectural choices:
Markdown and YAML front matter as canonical format — Each record (task, plan, run, lock, handoff, code review, etc.) is stored as a
.mdfile with YAML front matter metadata and a Markdown body. This makes state human-readable and Git-friendly.UUID-backed task bundles with numeric aliases — Canonical task bundles live in UUIDv7-named directories under the Ledgercore data mount. UUIDs anchor storage identity and cross-task relationships;
task-####remains the user-facing alias derived from the identity inventory.Sidecar indexes as derived caches — The
task_sidecars.jsonsummary index lives in the configured rebuildable indexes mount and is rebuilt from canonical Markdown records. Per-task sidecar writes update the index in place.Policy-based gate decisions — All lifecycle transitions go through functions in
taskledger/domain/policies.pythat returnDecisionobjects withallowed,code,message, andexit_code. This keeps gate logic testable and separate from I/O.Atomic file writes — All writes use
atomic_write_text(write to temp, fsync,os.replace) fromledgercore.Editable plan input with preflight —
taskledger plan checkparses editable plan input throughtaskledger/services/plan_input.py, applies worker-pipeline validation, and returns indexed issues before any plan upsert.Implementation workspace snapshots —
taskledger/services/workspace_snapshot.pycaptures Git and content snapshots when an implementation run starts, andvalidate startblocks when the workspace diverges.implement snapshot refresh --reason ...records new snapshots with an audit trail.Opaque cross-ledger references — Task refs, plan versions, file links, and external handoff targets are stored as opaque strings. Taskledger does not interpret ledgercore global refs, archledger IDs, or other external system identifiers.
Read-model reuse —
view,status,tree, andmonitorconsume service-level read models. These presentations are read-only and do not bypass lifecycle services.
Maintenance
docs/architecture.md is generated from Archledger source records. Do not edit it directly.
Edit:
.ledger/archledger/data/profiles/arc42/sections/*.mdfor section content and.ledger/archledger/data/records/**/*.mdfor individual records.Regenerate: Run
archledger buildto regeneratedocs/architecture.md.Verify: Run
archledger checkandpytest -q tests/test_docs_and_skill.py tests/test_service_boundaries.pyafter changes.Authoritative source: The Archledger records under
.ledger/archledger/dataare the single source of truth for architecture documentation.docs/architecture_taskledger_split.mdis a concise human-maintained summary.
Strategy Items
Layered architecture: CLI → Services → Domain → Storage
Drivers: decoupled layers, testable services, fast agent integration Constraints: no domain I/O, storage uses ledgercore primitives, bounded CLI filesystem use Related ADRs: adr-0046, adr-0047, adr-0048, adr-0049, adr-0050, adr-0051
Strategy
The codebase is organized into five layers with target dependency direction: CLI (taskledger/cli*.py) -> API (taskledger/api/) -> Services (taskledger/services/) -> Domain (taskledger/domain/) + Storage (taskledger/storage/). The Domain layer has no I/O dependencies. Storage owns canonical taskledger persistence and atomic record I/O, delegating low-level primitives (atomic writes, JSON I/O, YAML I/O, front matter parsing, cross-ledger ref parsing) to ledgercore. Other layers may perform bounded filesystem operations for external inputs and outputs (reports, Git sync, search, CLI file arguments). CLI should prefer API wrappers for public workflows, but current sanctioned exceptions are tracked in tests/test_service_boundaries.py.
Trade-offs
Clear separation of concerns enables focused testing per layer.
Service modules can grow large since they orchestrate across domain and storage.
No formal dependency injection; layer boundaries are enforced by convention and the
test_service_boundaries.pytest.
Markdown/YAML front matter as canonical records
Drivers: human readable state, git diffable records, no database dependency Constraints: strict front matter validation, ledgercore front matter parsing, storage layout 6 Related ADRs: adr-0046, adr-0050
Strategy
Each persistent record (task, plan, run, lock, handoff, event, etc.) is stored as a .md file with YAML front matter for structured metadata and a Markdown body for free-form content. This format is human-readable, Git-diffable, and editable without taskledger. The front matter serialization is handled by taskledger/storage/frontmatter.py.
Trade-offs
Slower to parse than JSON or SQLite for large datasets.
State is transparent and version-controllable — a core design goal.
Schema evolution requires careful front matter validation (
_require_contract,_string_value, etc.).
JSON indexes as rebuildable derived caches
Drivers: fast list and query operations, canonical records remain source of truth Constraints: summary index rebuildable on miss, index repair after out of band changes Related ADRs: adr-0047
Strategy
The task sidecar summary index is a derived cache stored in the configured rebuildable indexes mount, not alongside canonical task data. It is keyed by stable UUID identity and rebuilt from canonical Markdown records by taskledger repair index; human-readable task aliases remain available in the read models. Per-task sidecar writes update the summary through update_sidecar_summary in taskledger/storage/sidecar_index.py. taskledger doctor indexes checks for staleness.
Trade-offs
Avoids the complexity of a query engine on front matter files.
Indexes can become stale if writes bypass taskledger (for example manual edits).
taskledger doctor indexesandtaskledger repair indexaddress this.The summary index is a JSON document with a schema version and an
object_typefield; it is rebuildable from canonical records.
Policy-based lifecycle gate decisions
Drivers: testable gate logic, user only actions enforced Constraints: PolicyContext is gathered explicitly, no I/O in policy functions Related ADRs: adr-0048
Strategy
All lifecycle transitions are validated through pure functions in taskledger/domain/policies.py that return Decision objects (allowed, code, message, exit_code). Policies have no I/O — they receive PolicyContext (task, lock, run) and return a decision. This makes gate logic fully testable without file system setup.
Trade-offs
Policy functions must receive all context explicitly (no lazy loading from storage).
Very testable:
test_domain_policies.pyandtest_lifecycle_policies.pycover the full decision surface.Services must gather the right context before calling policies.
Atomic file writes for durability
Drivers: durability of canonical records, no partial writes Constraints: ledgercore atomic primitives, fsync overhead, TASKLEDGER_TEST_FAST_IO escape hatch Related ADRs: adr-0046
Strategy
All file writes go through the ledgercore atomic primitives used by taskledger/storage/atomic.py: write to a temp file in the target directory, flush plus fsync, then os.replace for atomic rename. Directory fsync follows. Lock creation uses atomic_create_text with O_CREAT | O_EXCL for exclusive creation. These patterns prevent partial or corrupt writes on crash.
Trade-offs
Slightly slower than direct writes due to temp file plus fsync overhead.
Can be disabled for testing via the
TASKLEDGER_TEST_FAST_IOenvironment variable.Guarantees that readers always see complete, valid files.
Building Block View
The top-level building block is the taskledger system, decomposed into five black-box components:
CLI Layer — Handles command parsing, task reference resolution, and output rendering. Command families cover the canonical lifecycle plus
review,config, task archive operations, transfer and sync, diagnostics, themonitorobserver, and thepipelineoverlay.API Layer — Provides stable Python function wrappers around service operations.
Services Layer — Orchestrates lifecycle flows, plan input validation, plan review, handoffs, snapshot capture, navigation, doctor checks, worker pipelines, archival, code-review evidence, event logging, exports, dashboard assembly, and ready-work inspection.
Domain Layer — Defines models, state machines, and policy decisions.
Storage Layer — Manages file system persistence and layout. Low-level primitives (atomic writes, JSON, YAML, front matter, refs) are delegated to
ledgercore.
Data flows strictly downward: CLI -> Services -> Domain + Storage. The API layer calls Services directly. The Domain layer has no dependencies on Storage or Services.
Each task is stored as a task bundle directory at <data-root>/ledgers/<ledger_ref>/tasks/<uuidv7>/, containing task.md and its sidecar collections. The UUIDv7 is the stable storage identity; task-#### remains the derived user-facing task alias. Mutations append immutable TaskEvent records to the ledger-level events/ directory. Action and event logging is enabled by default; set the project event-logging configuration to disable new event records. Existing records remain readable regardless. Task and sidecar indexes are derived caches in the configured rebuildable indexes mount.
Whitebox taskledger system
Motivation
Taskledger decomposes into core layers with downward dependency flow. This isolates persistence, business rules, orchestration, and public interfaces. Low-level persistence primitives are delegated to ledgercore, but the layer boundary above it stays in taskledger/storage/.
Contained building blocks
CLI Layer (
block-0030) — Typer commands, argument parsing, output renderingAPI Layer (
block-0031) — Stable Python function wrappersServices Layer (
block-0032) — Lifecycle orchestration, handoffs, plan input, plan review, snapshots, inspectionDomain Layer (
block-0033) — Models, state machines, policies (no I/O)Storage Layer (
block-0034) — File system persistence, atomic writes, sidecar index, layout
Important interfaces
CLI -> Services: function calls with
workspace_rootplus task referencesServices -> Domain: policy functions take
PolicyContext, returnDecisionServices -> Storage: record CRUD operations through
task_store.pyfunctions and the sidecar indexAPI -> Services: direct function calls mirroring CLI behavior
Storage -> ledgercore: atomic I/O, JSON I/O, YAML I/O, front matter parsing, cross-ledger ref parsing
Level 1
CLI Layer
Parent: block-0029 Interfaces: Location:
Handles command parsing via Typer, task reference resolution (--task option, active task default), and output rendering (human text or JSON envelope via cli_common.py). Command families include the canonical lifecycle plus review, config, task archive operations, transfer and sync, diagnostics, the monitor observer, the pipeline overlay, and ref cross-ledger helpers.
Source refs: taskledger/cli.py, taskledger/cli_common.py, taskledger/command_inventory.py, and the focused taskledger/cli_*.py registration modules.
API Layer
Parent: block-0029 Interfaces: Location:
Stable Python function wrappers under taskledger/api/ that mirror the CLI surface for programmatic use. Each module (tasks, plans, handoff, locks, task runs, sync, reviews, search, etc.) exposes functions that accept workspace paths and return dictionaries matching the JSON output shape. The API layer calls Services directly.
Services Layer
Parent: block-0029 Interfaces: Location:
Orchestrates lifecycle flows by coordinating Domain policies and records with Storage. Focused services own planning (planning_flow.py, plan_input.py, plan_review.py, plan_lint.py, plan_materialization.py), implementation (implementation_flow.py, workspace_snapshot.py), validation (validation_flow.py), handoffs (handoff.py, handoff_lifecycle.py, worker_context.py), doctor checks (doctor.py, doctor_checks/), navigation (navigation.py, next_action_model.py, next_action_payload.py, ready_work.py), worker pipelines (worker_pipeline.py, workflow_guidance.py), archival (task_archive.py, task_export.py, task_reports.py), code-review evidence (code_review.py), event logging (task_events.py, agent_transcripts.py, agent_logging.py, change_tracking.py, check_tracking.py, event_logging.py), exports and dashboard assembly (dashboard.py, tree.py, monitor.py). tasks.py remains the compatibility-oriented lifecycle facade while ownership is progressively extracted into focused modules.
Domain Layer
Parent: block-0029 Interfaces: Location:
Data models, state enums, normalization, and policy decisions without storage I/O. Besides lifecycle and sidecar records, the domain defines append-only code-review records. State transitions remain in states.py; policy decisions in policies.py return structured Decision objects. The canonical storage layout version is TASKLEDGER_STORAGE_LAYOUT_VERSION in taskledger/domain/states.py.
Storage Layer
Parent: block-0029 Interfaces: Location:
File system persistence for canonical records. Each task lives in the current Ledgercore data mount at ledgers/<ledger_ref>/tasks/<uuidv7>/, with task.md and independently addressable sidecars including plans, runs, locks, todos, questions, changes, checks, handoffs, links, and code reviews. UUIDv7 is the stable storage identity; task-#### is a derived user-facing alias resolved through the identity inventory. Layout-5 numeric bundles migrate deterministically to layout 6 before mutation; read-only access does not migrate. Ledger-level collections hold events, introductions, releases, and other shared records. Task and sidecar indexes are derived caches in the configured rebuildable indexes mount. Atomic write primitives, YAML I/O, front matter parsing, and ref parsing are delegated to ledgercore. Project configuration edits use structured TOML handling rather than ad hoc text replacement.
Runtime View
The runtime view traces the main operational scenarios through the system:
Task lifecycle — A task is created in
draft, moves toplanning(lock acquired, run started), thenplan_reviewafter a plan is proposed. The user approves ->approved. Implementation starts ->implementing, finishes ->implemented. Validation starts ->validating, passes ->done.Plan input flow —
taskledger plan startopens planning.plan guidancereports the active project planning profile.plan templatewrites a fresh plan skeleton;plan check --file plan.mdruns the preflight parser intaskledger/services/plan_input.pywithout mutating state.plan upsert --file plan.md(or--from-answers) persists the plan;plan lint --version Nsurfaces blocking issues;plan review --version Nproduces the approval brief;plan accept --version N --note ...records the user-only decision.Implementation workspace snapshot —
implement startcaptures a Git and content snapshot intaskledger/services/workspace_snapshot.py.validate startblocks when the current workspace diverges from the implementation snapshot.implement snapshot refresh --reason ...records a new snapshot with an audit trail and is the normal recovery path when validation is blocked by snapshot mismatch.Lock lifecycle — Starting a stage (planning, implementation, validation) acquires a lock and creates a run. Locks have lease timers and heartbeats. Stale locks require explicit break flow with audit trail.
Handoff flow — A worker creates a handoff with generated context (task state, plan, todos, questions, lock info). Another worker claims it, optionally transferring the lock. The handoff is closed when the receiving worker completes.
Doctor checks — Inspects lock/run consistency, front matter integrity, index staleness, and storage layout version. Reports diagnostics with severity, code, and repair hints.
Validation evidence flow — Acceptance criteria from the accepted plan are checked during the validation stage. Evidence is recorded per criterion with pass/fail/warn status. Latest-check-wins semantics apply; mandatory criteria gate completion.
Code-review evidence — A reviewer records append-only review evidence against an implementation run, handoff, worker step, working tree, or commit. This is evidence attached to the task, not a new lifecycle stage.
Task lifecycle: create through done
Trigger: User or agent runs taskledger task create.
Flow:
task create→ TaskRecord persisted indraftstage withtask.createdeventplan start→ Lock acquired, planning run started, stage →planningplan propose→ PlanRecord persisted, todos materialized, stage →plan_reviewplan approve(user-only) → Stage →approved, lock released, run finishedimplement start→ Lock acquired, implementation run started, stage →implementingimplement log/implement finish→ Changes logged, todos completed, stage →implementedvalidate start→ Lock acquired, validation run started, stage →validatingvalidate check/validate finish→ Criteria checked, stage →done(orfailed_validation)
Result: Task reaches done with all todos complete and all mandatory criteria passed. Events trail the full history.
Key policy checks: can_start_planning, plan_propose_decision, plan_approve_decision, implementation requires accepted plan, validation requires finished implementation run.
Lock acquisition, heartbeat, and release
Trigger: Service calls _start_run (e.g., plan start, implement start, validate start).
Flow:
Check no existing active lock for the task
Create
TaskLockrecord viaatomic_create_text(exclusive file creation)Create
TaskRunRecordwith statusrunningAppend
lock.acquiredandrun.startedeventsUpdate task stage to active stage (
planning/implementing/validating)Heartbeat updates
last_heartbeat_aton the lockOn finish: run status →
finished, lock removed,lock.releasedevent appended
Stale lock handling:
lock_is_expiredchecks lease expiryrepair lockrequires explicit user action, recordsbroken_at,broken_by,broken_reasondoctordetects lock/run mismatches
Key source: taskledger/services/tasks.py (_start_run), taskledger/storage/locks.py, taskledger/domain/lock.py.
Handoff creation and claiming
Trigger: Worker runs taskledger handoff create.
Flow:
Generate context body: task state, accepted plan, todos, questions, lock/run status, implementation summary, validation status
Create
TaskHandoffRecordwith mode (planning/implementation/validation/review/full), lock policy (none/retain/release/transfer), intended actor and harnessPersist handoff record, append
handoff.createdeventAnother worker runs
handoff claim→ status →claimed, optional lock transferWorker completes work, runs
handoff close→ status →closed
Key source: taskledger/services/handoff.py (context generation), taskledger/services/handoff_lifecycle.py (claim/close/transfer), taskledger/services/worker_context.py (context assembly).
Editable plan input preflight
Trigger: Agent or user edits a plan file and runs taskledger plan check and taskledger plan upsert.
Flow:
plan check --file plan.md-> Pure preflight parser intaskledger/services/plan_input.pyreturns aplan_input_checkpayload withpassed, indexedissues, and parsed counts. No state mutation.Worker-pipeline configuration in
taskledger.tomlis consulted to validateworker_steptodo tags when present.plan upsert --file plan.md(or--from-answers) runs the same parser, persists the plan as aPlanRecord, and materializes todos.plan lint --version Nruns structural lint over the proposed plan body and front matter.plan review --version Nrenders the approval brief used by the user-only approval command.
Result: Plan is persisted with no parse errors, no lint errors, and no stale answers. Approval can proceed.
Key source: taskledger/services/plan_input.py, taskledger/services/plan_lint.py, taskledger/services/plan_review.py, taskledger/cli_plan.py.
Doctor integrity check
Trigger: User runs taskledger doctor.
Flow:
Resolve project paths and locator
Check storage layout version against current version
Scan all tasks: verify front matter validity, lock/run consistency, active task state
Check index staleness (comparing index contents against canonical records)
Check for stale locks (expired leases)
Collect structured diagnostics with severity, code, message, and repair hints
Report diagnostics to user (human text or JSON)
Key source: taskledger/services/doctor.py, taskledger/services/doctor_checks/task_checks.py, taskledger/services/doctor_checks/project_scan.py, taskledger/services/doctor_checks/migration_checks.py.
Branch-scoped ledger selection and fork
Trigger: Developer creates a long-lived Git branch and runs taskledger ledger fork REF.
Flow:
ledger fork feature-a→ Creates a new ledger directory under.taskledger/ledgers/feature-a/Commits the updated
taskledger.toml(with newledger_ref) with the branch workDefault commands now read/write only the current ledger under
.taskledger/ledgers/<ledger_ref>/Tasks in different ledgers are isolated; duplicate logical task IDs are expected across ledgers
ledger adopt --from feature-a task-0030copies a branch-local task into the current ledger when merging
Result: Each long-lived branch has its own isolated task ledger. The active ledger is determined by ledger_ref in taskledger.toml.
Key source: taskledger/cli_ledger.py, taskledger/storage/ledger_config.py, taskledger/storage/task_store.py.
Implementation workspace snapshot lifecycle
Trigger: taskledger implement start captures a workspace snapshot; taskledger validate start enforces it before validation runs.
Flow:
implement start-> Acquires implementation lock, creates an implementation run, callscapture_workspace_content_snapshotintaskledger/services/workspace_snapshot.py, and persists snapshot metadata on the run.Implementation finishes via
implement finish. The snapshot is the source of truth for what was implemented.validate start-> Callscompare_implementation_snapshot. If the current workspace diverges, validation is blocked with areason_codesuch ascontent_snapshot_mismatchorlegacy_snapshot_mismatch.validate start --refresh-implementation-snapshot --reason "..."->refresh_implementation_snapshotrecords a new snapshot, appends animplementation.snapshot.refreshedevent, and resumes the normal validation flow.implement snapshot refresh --reason "..."is the same recovery path when called outsidevalidate start.
Result: Validation only proceeds when the current workspace matches the implementation snapshot; otherwise an explicit refresh with audit trail is required.
Key source: taskledger/services/workspace_snapshot.py, taskledger/services/implementation_flow.py, taskledger/services/validation_flow.py, taskledger/cli_implement.py.
Migration, index repair, and doctor interaction
Trigger: A developer updates a project using layout-5 numeric task bundles and a mutating Taskledger command needs canonical layout 6.
Flow:
Before mutation, storage checks the project layout and inventories task identities. Read-only commands continue to read without migrating.
The migration maps legacy task aliases to deterministic UUIDv7 identities while preserving ordering, ordinal gaps, and reserved or incomplete allocations.
The migration verifies that the source is safe to transform; mixed layouts, active locks, and unresolved repository conflicts block automatic migration rather than being guessed through.
Canonical task records and sidecars are moved into UUID-named task bundle directories with recovery information retained.
Layout metadata is advanced to version 6 and UUID-keyed derived indexes are rebuilt.
doctorverifies canonical records, indexes, locks, and runs after migration.
Result: The first mutation uses UUIDv7 task directories while numeric task aliases remain available to users. Read-only access does not change the legacy layout.
Key source: taskledger/storage/task_directory_migration.py, taskledger/storage/task_identity.py, taskledger/storage/task_store.py, taskledger/services/doctor.py.
Worker pipeline guided handoff
Trigger: Agent runs taskledger next-action and receives worker_pipeline.next_step in the response.
Flow:
next-action→ Returnsworker_pipeline.next_stepwithstep_id,context_command, andhandoff_commandhintspipeline show→ Displays the configured worker pipeline steps and their mapping to lifecycle stagescontext --worker spec-reviewer→ Renders a worker-specific context for the spec reviewer stephandoff create --worker code-reviewer→ Creates a handoff with mode and context derived from the worker step configurationPlan todos may be tagged with
worker_stepto associate implementation steps with specific pipeline workersplan template --with-worker-pipeline→ Generates plan template with worker-tagged todo sections
Result: Worker pipeline provides an advisory overlay that guides fresh-context handoffs through sequential worker steps (spec-reviewer, implementer, code-reviewer). It does not add new lifecycle gates — the task lifecycle remains the authoritative workflow.
Key source: taskledger/services/worker_pipeline.py, taskledger/cli_pipeline.py, taskledger/services/handoff.py.
Deployment View
Taskledger is a single-node, file-system-based tool. Deployment consists of:
Installation:
pip install taskledger(PyPI) or localpip install -e .Project initialization:
taskledger initcreatestaskledger.tomland.taskledger/in the project rootRuntime: The CLI runs as a Python process, reading and writing the project
.taskledger/directory. No daemon, no server.CI integration: taskledger commands can be run in CI pipelines for status checks, validation, snapshot and export operations, and
taskledger tracebundlesAgent integration: Agent harnesses invoke taskledger CLI commands as subprocess calls
Local development deployment
Node: Developer workstation or CI runner.
Software:
Python 3.10+
taskledger (pip installed)
Host project with
taskledger.tomlconfig
Storage:
.taskledger/directory in project root (Markdown and YAML front matter files)task_sidecars.jsonsummary index under.taskledger/ledgers/<ledger_ref>/Project config at
taskledger.toml
Network: None required.
Installation: pip install taskledger or pip install -e . from source. Single entry point: taskledger CLI.
Cross-cutting Concepts
Cross-cutting concerns that span multiple layers:
Actor metadata: Every mutation carries an
ActorRef(type: agent/user/system, name, role, session, harness). Decisions distinguish user-only actions (approval, waiver) from agent actions.JSON output envelope: All CLI commands emit a consistent JSON envelope with
ok,command,result_type,result,events, andwarningsfields when--jsonis passed. Error envelopes addcode,message,details, andremediation.YAML front matter serialization: All canonical records use YAML front matter (
---delimited) for metadata and Markdown for body. Serialization and deserialization live intaskledger/storage/frontmatter.py, backed byledgercore.Atomic file writes: All file writes go through
ledgercoreatomic primitives (temp file, flush, fsync,os.replace, directory fsync) to prevent corruption.Action and event logging (default-on): Mutations append immutable
TaskEventrecords to the ledger-levelevents/directory under.taskledger/ledgers/<ledger_ref>/. Action and event logging is enabled by default; set[event_logging] enabled = falseto disable new records. Existing records remain readable. Source:taskledger/storage/events.py,taskledger/services/task_events.py,taskledger/domain/event.py.Exit code taxonomy: Errors map to stable exit codes (0=success, 1=generic, 2=bad input, 3=workflow rejection, 4=lock conflict, 5=missing, 6=storage, 7=validation failed).
Opaque cross-ledger references: Task refs, plan versions, file links, and handoff targets remain opaque strings. Taskledger does not interpret ledgercore global refs, archledger IDs, SpecWeave feature paths, or other external system identifiers.
Read-model reuse:
view,status,tree, andmonitorcommands consume service-level read models. These presentations are read-only and do not bypass lifecycle services.Editable plan input:
taskledger plan checkis a pure preflight parser intaskledger/services/plan_input.py. It rejects malformed YAML, normalizes thedescriptionalias totext, and surfaces indexed issues before any plan upsert.plan lintruns additional structural checks;plan reviewrenders the approval brief.
Actor metadata and role semantics
Every mutation carries an ActorRef (type: agent/user/system, name, role, session ID, harness ID) and optionally a HarnessRef (harness identity, kind, capabilities). The system distinguishes user-only actions (plan approval, criterion waivers, dependency waivers) from agent actions. Actor metadata is persisted in locks, runs, events, and handoff records for audit trails. Source: taskledger/domain/actor.py (ActorRef, HarnessRef), taskledger/domain/states.py (ActorType, ActorRole, HarnessKind).
JSON output envelope contract
When --json is passed, every CLI command emits a JSON envelope: {"ok": bool, "command": str, "result_type": str, "result": ..., "events": [...], "warnings": [...]}. On error, the envelope includes an error object with code, message, details, and remediation fields. This shape is a public API contract tested by tests/test_json_contracts.py. Exit codes map deterministically to error categories and are part of the public contract.
YAML front matter serialization
All canonical records are .md files with YAML front matter (--- delimited) containing structured metadata and a Markdown body. Read and write are handled by read_markdown_front_matter and write_markdown_front_matter in taskledger/storage/frontmatter.py, backed by ledgercore.frontmatter. Models implement to_dict() and from_dict() for serialization. Schema version and object type fields enforce contract integrity on read.
Atomic file writes
All file writes go through atomic_write_text (temp file, flush, fsync, os.replace, directory fsync) or atomic_create_text (O_CREAT | O_EXCL for lock files). These primitives come from ledgercore.atomic and are re-exported by taskledger/storage/atomic.py. This prevents partial writes on crash. Test environments can disable fsync via TASKLEDGER_TEST_FAST_IO=1 for speed.
Append-only event log
Mutations append an immutable TaskEvent record to the ledger-level events/ directory under .taskledger/ledgers/<ledger_ref>/. Action and event logging is enabled by default; set [event_logging] enabled = false in taskledger.toml to disable new event records. Existing records remain readable regardless of the setting. Events are never modified or deleted. Each event has a deterministic ID, name (for example task.created, plan.approved, lock.acquired, validation.check.logged, code_review.recorded, implementation.snapshot.refreshed), timestamp, and actor metadata. Events support audit trails, monitor activity, handoff context, and task transcript output. Duplicate event detection prevents re-appending on retry. Source: taskledger/storage/events.py, taskledger/services/task_events.py, taskledger/services/agent_logging.py, taskledger/services/agent_transcripts.py, taskledger/domain/event.py.
Exit code taxonomy
Stable exit codes for CLI and error classification: 0 (success), 1 (generic failure), 2 (bad input), 3 (workflow rejection - invalid transition, approval required, dependency blocked), 4 (lock conflict - stale lock requires break), 5 (not found or no active task), 6 (storage error or data integrity), 7 (validation failed). Defined in taskledger/domain/states.py and taskledger/errors.py. Agents and CI pipelines rely on specific codes for automation.
Editable plan input preflight
Editable plan input is a preflight parser in taskledger/services/plan_input.py. It accepts YAML front matter with goal, files, test_commands, expected_outputs, acceptance_criteria, and todos, plus optional generation_reason and waiver reason fields. Criterion description is normalized to text as a compatibility alias. Worker-pipeline configuration in taskledger.toml validates worker_step todo tags. taskledger plan check --file plan.md returns a plan_input_check payload with passed, summary, indexed issues, and parsed counts. plan upsert consumes the same parser; plan lint runs additional structural checks; plan review renders the approval brief.
Implementation workspace snapshot
When taskledger implement start is called, taskledger/services/workspace_snapshot.py captures a Git and content snapshot of the implementation workspace. The content snapshot (worktree-content:v1) records per-path status, kind, size, and content hash for files outside .taskledger/. The implementation run persists workspace_git_commit, workspace_dirty, workspace_diff_hash, workspace_status_hash, workspace_snapshot_at, workspace_content_hash, workspace_paths_hash, workspace_entry_count, workspace_snapshot_format, and workspace_snapshot_ref. taskledger validate start calls compare_implementation_snapshot and blocks validation when the current workspace diverges. The sanctioned recovery path is taskledger implement snapshot refresh --reason "...", which records a new snapshot, appends an implementation.snapshot.refreshed event, and returns a structured payload with old_snapshot and new_snapshot details.
Durable code-review evidence
Code review is durable evidence attached to a task. A record captures result, summary/body, reviewer and harness, implementation run, worker step, handoff, and optional Git working-tree or commit metadata. Review records are append-only and may be recorded after a task reaches done. They do not create a lifecycle stage, reopen completed work, replace acceptance criteria, or weaken validation completion rules. Source: taskledger/domain/review.py, taskledger/services/code_review.py. Test coverage: tests/test_code_reviews.py.
Architecture Decisions
Key architecture decisions documented as ADR records:
ADR-1: Markdown and YAML front matter as canonical format (not JSON, not SQLite)
ADR-2: Sidecar summary index as derived rebuildable cache (not authoritative)
ADR-3: Explicit lifecycle gates with policy decisions (not free-form state)
ADR-4: Typer CLI framework (not argparse, not Click directly)
ADR-5: Task bundle directory layout (not single-file index)
ADR-6: External skill packaging (skills outside the Python package)
Markdown/YAML front matter as canonical format
Document version: 6
Context
Need a storage format for task state that is durable, human-readable, and version-controllable. Agents and humans need to inspect state without running taskledger.
Decision
Store all records as Markdown files with YAML front matter (--- delimited). Metadata (ID, type, status, dates) goes in YAML; free-form content goes in Markdown body.
Consequences
Positive: State is Git-diffable, human-readable, and editable in any text editor.
Positive: No database dependency.
Negative: Parsing is slower than binary formats for large datasets.
Negative: Schema evolution requires careful validation on read.
Alternatives considered
SQLite: Faster queries but opaque binary format, harder to version-control and inspect.
Pure JSON: Easier parsing but not human-friendly for long-form content (plan bodies, handoff contexts).
Single JSON index file: Rejected due to merge conflicts and scalability concerns.
Sidecar summary index as derived rebuildable cache
Document version: 6
Context
Listing tasks, locks, and dependencies requires scanning many front matter files. Need a way to speed up queries without adding a database.
Decision
Maintain JSON index files under .taskledger/indexes/ as derived caches. They are rebuilt from canonical records by taskledger repair index and checked by doctor indexes. They are never the source of truth.
Consequences
Positive: Fast list/query operations without parsing all front matter files.
Positive: Indexes can always be rebuilt from canonical source.
Negative: Indexes can become stale after manual edits or crashes.
Negative:
taskledger repair indexmust be run after out-of-band changes.
Alternatives considered
No indexes (always scan files): Simpler but too slow for large projects.
SQLite index: More robust but adds complexity and dependency.
In-memory cache: Lost on process restart, not durable.
Explicit lifecycle gates with policy decisions
Document version: 6
Context
Without lifecycle gates, agents could skip review and implement without approval. Need enforceable transitions between task stages.
Decision
Implement lifecycle gates as pure policy functions in taskledger/domain/policies.py. Each gate function takes a PolicyContext (task, lock, run) and returns a Decision (allowed, code, message, exit_code). The state machine in states.py defines ALLOWED_STAGE_TRANSITIONS. Services call policies before mutating state.
Consequences
Positive: Gate logic is fully testable without I/O setup.
Positive: User-only actions (approval, waivers) are enforced at the policy level.
Negative: Services must gather full context before calling policies.
Alternatives considered
Free-form state changes: Rejected — agents could bypass review.
Database triggers: Rejected — adds database dependency and complexity.
CLI-only validation: Rejected — API and programmatic users would bypass gates.
Typer CLI framework
Document version: 6
Context
Need a CLI framework that supports subcommand groups, type-annotated parameters, and integrates with Click ecosystem extensions.
Decision
Use Typer (built on Click) for the CLI. Typer provides type-annotated parameters, subcommand groups, and automatic help generation. The root app in cli.py registers nested Typer apps for each command family: task, plan, question, implement, validate, todo, intro, file, link, require, lock, handoff, release, storage, sync, doctor, repair, migrate, actor, harness, ledger, pipeline, review, config, and ref.
Consequences
Positive: Clean type-annotated parameter definitions with
Annotatedtypes.Positive: Click ecosystem compatibility (middleware, testing).
Negative: Typer and Click are both runtime dependencies.
Alternatives considered
Pure Click: More verbose parameter definitions, no type annotation inference.
Argparse: Standard library but lacks subcommand ergonomics and type inference.
Docopt: Declarative but harder to compose nested subcommands.
Task bundle directory layout
Document version: 6
Context
Tasks need individually addressable Markdown records and sidecar collections for plans, runs, locks, todos, questions, changes, checks, handoffs, links, reviews, artifacts, and audit data. Task directories are canonical storage keys, while task references and relationships must survive branches, imports, and changing display numbers.
Decision
Use a UUIDv7 directory for each task bundle under the current Ledgercore data mount: <data-root>/ledgers/<ledger_ref>/tasks/<uuidv7>/. Each bundle contains task.md and its task-local sidecar collections. UUID is the immutable storage identity and the authoritative cross-task reference.
Keep task-#### as the user-facing CLI reference and display alias. Derive aliases from UUID order across live identities, tombstones, and reserved or incomplete allocations; aliases can change after a merge or import. Tombstones remain in the inventory so deleting or quarantining a task does not collapse later aliases. JSON task, dependency, sidecar, and lock indexes are derived caches and can be rebuilt from canonical records.
Storage layout 6 migrates layout-5 numeric task bundles to deterministic UUIDv7 directories before the first mutation. Read-only commands do not migrate. Migration preserves legacy ordering and reservations, is recoverable, and blocks mixed layouts or unresolved repository conflicts rather than guessing.
Consequences
Positive: Independent branch task creation uses distinct filesystem paths and avoids numeric-directory merge conflicts.
Positive: Task relationships retain stable UUID identity while commands continue accepting numeric aliases.
Positive: Sidecar records remain independently addressable and indexes remain rebuildable.
Negative: Numeric display aliases are derived and can change after merge or import.
Negative: Layout migration requires preflight and recovery handling.
Alternatives considered
Keep numeric task directory names: Independent branches can create colliding paths.
Use UUIDs as the user-facing CLI IDs: Stable, but unnecessarily discards the established short numeric task-reference UX.
Store all task state in one JSON index or a database: Harder to inspect, version-control, and merge.
External skill packaging
Document version: 6
Context
Agent skill files (SKILL.md) provide integration instructions for coding agents. Need to decide where they live relative to the Python package.
Decision
Skills live under skills/taskledger/ in the repository, outside the taskledger Python package. They are not packaged as Python package data, not loaded via importlib.resources, and not distributed via PyPI as part of the package.
Consequences
Positive: Clean separation between tool functionality and agent integration instructions.
Positive: Skills can be versioned independently and distributed through different channels.
Negative: Skill installation is a separate step from package installation.
Alternatives considered
Package skills as package data: Coupling skill version to package version, harder to update independently.
Load skills via importlib.resources: Adds packaging complexity and coupling.
Quality Requirements
Quality requirements that gate architectural decisions:
Data integrity: Atomic writes and strict front matter validation prevent corrupt state. Partial writes are impossible due to
os.replacesemantics.CLI exit code contract: Exit codes are stable and tested. Agents and CI pipelines rely on specific codes for automation.
JSON envelope stability: The JSON output shape (
ok,command,result_type,result) is a public API contract. Breaking changes require explicit versioning.Lifecycle gate correctness: Every stage transition is validated by policy functions with full test coverage of error paths.
Export/import round-trip: Archives preserve all state. Import into a fresh workspace reproduces the original taskledger state exactly.
Editable plan input preflight:
taskledger plan checkreturns a structured payload withpassed,summary, indexedissues, and parsed counts. Lint and review are gate layers; preflight catches them earlier.Implementation snapshot fidelity:
validate startblocks when the current workspace diverges from the implementation snapshot.implement snapshot refresh --reason ...is the only sanctioned recovery path and records an audit trail.
Quality Requirements Overview
Title |
Category |
Measure |
Scenarios |
|---|---|---|---|
Data integrity: atomic writes and front matter validation |
reliability |
||
CLI exit code contract stability |
reliability |
||
JSON envelope output stability |
reliability |
||
Lifecycle gate correctness |
reliability |
||
Export/import round-trip fidelity |
reliability |
Quality Scenarios
Risks and Technical Debt
Known risks and areas of technical debt:
Storage scaling with many tasks: Each task is a directory with multiple sidecar files. Very large projects (hundreds of tasks) may see slowdowns in list and query operations because the
task_sidecars.jsonsummary index is rebuilt from file scans on miss and updated on writes. The per-task sidecar index keeps the common read path fast but does not eliminate directory overhead.Migration surface between storage versions: The storage layout is currently v3 (
TASKLEDGER_STORAGE_LAYOUT_VERSIONintaskledger/domain/states.py). Migration code intaskledger/storage/migrations.pyadds complexity. Future format changes must maintain backward compatibility or ship migration steps.Service boundary erosion: Some service modules (notably
tasks.pyand a few focused modules) have grown large. The service layer has no formal interface contracts; boundaries are enforced by convention and thetests/test_service_boundaries.pywhitelist indocs/service_boundary_whitelist.md.Implementation snapshot drift: Workspace snapshots are a strong invariant but require explicit refresh after environment changes.
taskledger next-actionandvalidate startsurface mismatch diagnostics; the recovery path isimplement snapshot refresh --reason ....Editable plan input compatibility:
descriptionis accepted as an alias fortexton acceptance criteria; strict mode promotes unknown-field findings to errors to prevent silent structured-data loss.Growing dependency surface: The runtime dependency set is small (
typer,click,PyYAML,tomli,ledgercore) but new dependencies must be justified by core features rather than optional presentations.
Risk Overview
Title |
Severity |
Probability |
Mitigation |
Notes |
|---|---|---|---|---|
Storage scaling with many tasks |
medium |
medium |
Run repair index after bulk changes; consider task archival for completed work. |
Each task is a directory with multiple sidecar files. Projects with hundreds of tasks may see slowdowns in list and query operations due to file system scanning. The |
Migration surface between storage versions |
medium |
medium |
Doctor checks detect version mismatches; migration checks flag incompatible records. |
The current storage layout is v6 ( |
Service boundary erosion |
medium |
medium |
test_service_boundaries.py whitelist tracks allowed cross-module imports and fails on violations. |
Some service modules (notably |
Growing dependency count |
medium |
medium |
Small dependency set (typer, PyYAML, tomli); each justified by a core feature. |
The runtime dependency set is small ( |
Glossary
Domain terms used throughout taskledger. The full glossary lives in the per-term glossary-NNNN records under .ledger/archledger/data/records/glossary/. Each term is stored as a record with term, definition, and reference metadata rather than in this section body.
Term |
Definition |
|---|---|
Task |
The primary unit of work with a managed lifecycle through planning, implementation, and validation stages. |
Plan |
A proposed implementation plan with acceptance criteria that gates implementation start. |
Run |
A record of an active work session (planning, implementation, or validation) paired with a lock. |
Lock |
A concurrency control mechanism preventing simultaneous actors on the same task stage. |
Handoff |
A context transfer record enabling a different actor to continue work from where the previous actor left off. |
Todo |
A concrete implementation step materialized from the accepted plan; gates implementation completion. |
Acceptance Criterion |
A testable condition that gates task completion during validation. |
Actor |
The entity performing an action, classified as agent, user, or system. |
Harness |
The execution environment running taskledger (agent harness, manual terminal, or CI). |
Stage |
A position in the task lifecycle state machine (draft, planning, plan_review, approved, etc.). |
Sidecar |
A collection of related records (todos, links, plans, etc.) attached to a task. |
Front Matter |
The YAML metadata block at the top of a canonical record file, delimited by —. |
Worker Pipeline |
An optional advisory overlay that guides fresh-context handoffs through sequential worker steps. |