Skip to content
ALC

Unattended work

Drop tasks in a queue, let cron drain them in isolation while you are away, and land what survived the checks.

Unattended Mode has four elements. ALC supplies all of them:

ElementWhat it isIn ALC
SourceWhere the task comes fromA YAML task queue in .alc/queue/
TriggerWhat starts italc tick from cron, or alc serve --webhook
SandboxIsolated environmentA git worktree on a throwaway branch
GateHow the result is reviewedYour checks, then alc land

Filling the queue

alc enqueue ship "add the changelog entry for 0.42"
alc enqueue db "add an index for the orders lookup" --kind specialist
FlagEffect
--kind flow|specialistWhat to dispatch (default: flow)
--engine NAMEEngine for this task
--isolate / --no-isolateIsolate in a worktree (default: on)
--id IDShort slug so another task can depend on this one
--depends-on IDId of a task this one builds on (repeatable)
--touches PATHFile path or glob this task will edit (repeatable)
--priority NTie-breaker among tasks ready in the same wave, higher first
--from-file PATHBatch-enqueue from a file
--jsonPrint the written filenames as JSON

alc enqueue writes the task file directly, with no planning turn. It takes a Flow, a Specialist, or — with --kind run — a bare Blueprint, which is the shape most queued tasks start as: chore-sized. A queued Blueprint runs as a one-stage flow, so its report, archive and Mix Health attribution look exactly like everything else's. That is the difference from alc conduct --enqueue, which spends an engine turn deciding what to write.

Ordering and safety

The drain is waved: it runs everything whose dependencies have merged, then the next wave, and so on. Two mechanisms decide the order.

--depends-on is an explicit precedence you declare. --touches is the one that actually protects you: tasks whose touched paths overlap are serialized automatically, so two demands never edit the same file concurrently. Declare --touches and you rarely need --depends-on.

--priority only breaks ties within a wave. It can never move a task ahead of one it depends on.

Batching

alc enqueue ship --from-file tasks.txt

A .jsonl file takes one JSON object per line, with task required and the other keys optional. A plain text file takes one task per line; blank lines and # comments are skipped.

Draining it

alc tick                      # process everything pending once, then exit
alc tick --concurrency 4      # up to 4 isolated tasks in parallel

A drain processes every pending task, and each one can take up to four model turns (one attempt plus max_repairs, which defaults to 3). So a cron entry's ceiling per pass is pending tasks × 4 turns — bounded by how much you enqueue, not by the drain. Set notify.on_budget_exceeded before you leave one running overnight.

FlagEffect
--concurrency NProcess up to N queue tasks in parallel, each in its own worktree
--engine NAMEOverride the engine for every demand in this drain
--allow-dirtySilence the dirty working-tree notice

alc tick is designed to be called from cron: it drains once and exits, rather than running as a daemon.

Only tasks with isolate: true — in a git repository — run concurrently. Everything else is forced serial, with a note saying how many.

--allow-dirty only quiets the warning. The run proceeds either way, and never commits your uncommitted work.

Each processed task is archived to done/ with its report alongside it.

Scheduling it

alc schedule install tick --every 15m
alc schedule install cycle deliver --every 1h
alc schedule list
alc schedule remove tick

This generates and manages the crontab entry for you. Install is idempotent — running it twice never produces two entries — and remove is scoped to ALC's own marker, so it never touches a line you wrote by hand. Where no crontab binary is available, it prints the line to paste.

Handling failures

A failed task is archived like any other. Re-enqueue it with the failure feedback appended, so the next drain fixes the specific reason rather than starting over:

alc retry                     # list the outstanding failures
alc retry <stem>              # re-enqueue one
alc retry --all               # re-enqueue all of them
alc retry --json

The stem is the done/ filename stem, which alc retry with no arguments prints.

Automatic retries

Two Manifest fields turn this into a policy rather than a chore:

max_task_retries: 2           # 0 (default) = off
retry_strategy: immediate     # or: deferred

immediate drains the retry in the same pass, bounded by the cap, so a transient failure is fixed promptly and works under alc tick. deferred waits for the next pass.

A retry carries the whole lineage: how many attempts have happened, and the root stem every attempt in the chain descends from.

Getting told

# .alc/manifest.yaml
notify:
  on_task_failed: ["scripts/notify.sh"]
  on_merge_conflict: "https://hooks.example.com/alc"
  on_loop_stopped: ["scripts/notify.sh"]
  on_budget_exceeded: ["scripts/notify.sh"]

Each hook is either a command — an argv list, run with the JSON payload on stdin — or a webhook URL, which is POSTed the JSON payload. There are no per-service adapters, because you already know how to fan a command or a URL out to Slack, email or a pager.

Delivery never raises. A broken notify hook cannot fail a run.

Checking on it

alc status                    # pending tasks, outstanding failures,
alc status --json             # loop states, unmerged branches

alc status always exits 0, which makes it safe to wrap in a monitoring script that cares about the payload rather than the exit code.

alc audit --since 7d          # what actually happened over a window

An HTTP door

For triggers that are not cron, alc serve --webhook is a minimal HTTP server in front of signal intake and the enqueue path:

alc serve --webhook --port 8787 --token "$ALC_WEBHOOK_TOKEN"
RoutePurpose
POST /signalIngest a typed real-usage signal
POST /enqueueWrite a queue task
GET /healthLiveness

It validates and writes only, and never executes anything. alc tick or alc loop --once drains what lands, on its own turn. That separation is deliberate: an external caller can create demand, but it cannot make the control plane skip a step.

--webhook is required — it is the only mode alc serve offers today, so the command reads as an explicit choice rather than an accidental default. --token is a bearer token checked on every request. Omit it and the port answers unauthenticated requests, with a warning on stderr.

Landing what survived

A drain leaves branches. Nothing merges into your work without the conditions being met — see Isolation and landing for exactly which, and for alc land.

alc land                      # list the unmerged branches
alc land --all                # integrate them

Next