Skip to the content.

fak task manager concept

internal/taskmgr is the process-local runtime state surface for “what is this process doing right now?” It is separate from:

The task manager is the live, in-process fold: a long-running fak front door can create a Manager, mark tasks and steps as work advances, and expose Manager.Snapshot() to an operator or health endpoint.

Task intent and live execution are deliberately separate. Author outcome, scope, dependencies, acceptance, witness, and placement first using the shift-left task-organization contract; this manager then owns ephemeral attempt state. Do not make operators reconstruct a durable leaf contract from PID, heartbeat, budget, lease, or scheduler telemetry.

Snapshot model

Each snapshot carries:

ETA is deliberately absent when it would be guesswork: no known total, no positive progress, elapsed time of zero, or a terminal task/step.

Liveness is an in-loop progress heartbeat, not a process scan. Beat marks that the task or step body is still advancing; SetProgress also counts as a beat. Running records with no beats are idle; records with a recent beat are live; records whose last beat is older than the manager’s liveness timeout are stalled. Terminal records return to idle while preserving beat metadata for diagnostics.

CLI proof surface

fak task sample emits the same snapshot shape for the current command process:

go run ./cmd/fak task sample --json --task build --step tests --concept verify --done 2 --total 10 --unit phase

Human output is available without --json:

go run ./cmd/fak task sample --task build --concept observe

This command is a sample/export surface, not a scheduler. The load-bearing API is the internal/taskmgr.Manager type.

fak task handoff is the completion-to-next-work gate. It reads a fak.task-handoff.v1 JSON file and refuses unless the record carries:

Dry-run mode prints the stable GitHub issue create/update plan. --live is the only mode that calls gh, and each next step is deduped by an HTML marker in the issue body.

Generation-aware handoffs can also add generation metadata to each next_steps entry:

When present, fak task handoff renders those fields into the generated issue body and adds generation plus the specific gen/* label to the planned issue. Generation remains orthogonal to priority, shared trunk, and runtime feature gates: the label says which product horizon owns the evidence; it never creates a branch, changes issue priority, or exposes runtime code.

The planned hardening for generated follow-up issues is tracked in docs/notes/NEXT-STEP-SCOPING-GUARDS-2026-06-30.md: default issue creation should require explicit current state, in-scope and out-of-scope boundaries, done condition, witness, route, and acceptance gate before a machine-created issue becomes dispatchable.

Minimal handoff input:

{
  "schema": "fak.task-handoff.v1",
  "current_state": "The implementation is committed; the remaining proof is a live issue sync smoke.",
  "task": {
    "task_id": "task_push_next",
    "title": "Push next work",
    "state": "done",
    "witness": {
      "verified_state": "verified_done",
      "source": "commit-audit",
      "sha": "deadbeef"
    }
  },
  "next_steps": [
    {
      "key": "task_push_next/live-smoke",
      "title": "Run live task handoff issue sync smoke",
      "body": "Exercise `fak task handoff --live` against a disposable follow-up issue.",
      "reason": "Dry-run planning is covered; live gh behavior still needs an operator-owned witness.",
      "generation": "gen/next",
      "promotion_evidence": ["The live smoke proves task handoffs can feed dispatch safely."],
      "demotion_evidence": ["Generated follow-ups increase ambiguity or duplicate existing work."],
      "invalidating_assumptions": ["The generated issue body stops being enough for the next worker to resume."],
      "generation_non_goals": ["Do not treat gen/next as a branch or runtime exposure flag."],
      "labels": ["agent-handoff"]
    }
  ]
}

Embedding shape

m := taskmgr.NewManager()
task, _ := m.StartTask(taskmgr.TaskSpec{
    TaskID: "release",
    Title:  "Build release",
    Total:  10,
    Unit:   "phase",
})
step, _ := task.StartStep(taskmgr.StepSpec{
    StepID:  "tests",
    Concept: "verify",
    Total:   4,
    Unit:    "suite",
})

_ = task.SetProgress(2, 10, "phase")
_ = step.SetProgress(1, 4, "suite")
_ = step.Beat()
snapshot := m.Snapshot()

The package is stdlib-only and has injectable clock/resource sampling, so tests can prove elapsed-time, resource-delta, and ETA math without sleeping.

Non-goals in this rung

This is not yet a durable task service, a distributed scheduler, a process monitor for other PIDs, or a fleet-level progress oracle. Those can be built on top by publishing snapshots through the existing gateway, shared-task, or a2a surfaces.