Amendment §4 (docs/multi-project-orchestration-plan_amendments.md):
- Schema: budget_cycles default 5 -> 3 (per-row override remains
supported). Codified by test_budget_default_three_per_amendment_section4.
- cycle.py: when a spec phase emits spec_ambiguous, the cycle now rolls
back the claim's attempts increment before routing to awaiting_human.
A spec-ambiguous verdict is the spec-refiner raising a *question* to
the human; penalizing the autonomous budget for asking the question
is wrong. Budget resumes counting only on autonomous retries after
the human answers and the item returns to spec.
Codified by test_spec_ambiguous_does_not_consume_budget.
Live MySQL also ALTERed to apply the new default (the orchestrator's
schema.sql is the source of truth but the running DB needs the same
default for new rows).
Tests: 19/19 contract+unit pass against the live MySQL stack.
Bring the code to the plan: MySQL→Postgres 16 and cron→Taskiq (Python
BullMQ-equivalent over a Redis broker), with Postgres FOR UPDATE SKIP LOCKED
retained as the atomic claim. The per-tick "claim one item, run one phase"
model is unchanged.
Approved fixes folded in:
- claim_for_merge: delete the call to the non-existent state.claim_for_merge;
claim order is now review→build→spec (merge happens inside review on pass).
- loop-breaker: a non-pass verdict with attempts>=budget_cycles parks the row
as `blocked` + opens a human_issue + emits work.blocked (design §5/§16).
- spec_wrong: added to phases.VERDICTS and emitted by refine_spec when the
spec is missing required sections (routes to spec, not awaiting_human).
Driver: PyMySQL→psycopg3 sync (dict_row cursor, Jsonb() for JSONB). schema.sql
rewritten to PG16 (enums, JSONB, TIMESTAMPTZ, BIGSERIAL, BEFORE UPDATE trigger
replacing MySQL ON UPDATE). cli init guard-creates the DB and applies the whole
schema in one execute().
New src/damascus/tasks.py wires ListQueueBroker + TaskiqScheduler with a
run_cycle task (→ cycle.tick()) on a cron label. Dockerfile CMD runs the
worker; docker-compose adds redis:7 + an orchestrator-scheduler service.
Bugs found and fixed during verification:
- cycle.py/cli.py status file was hardcoded to /data; now uses settings.data_dir.
- redis-py 8.0.0 defaults socket_timeout=5s, which killed idle Taskiq workers
(indefinite BRPOP + uncaught TimeoutError). Broker now sets socket_timeout=None.
- docker-compose scheduler command pointed at :broker; fixed to :scheduler.
- tasks.py docstring referenced non-existent --concurrency; corrected to
--max-threadpool-threads.
Verified: schema idempotent against postgres:16; damascus init end-to-end;
19 contract+unit tests green; Taskiq worker kiq path advances a row; Taskiq
scheduler path (no damascus cycle call) drives spec→build→retry→blocked +
human_issue, proving the queue replaces cron and the loop-breaker via the queue.
Co-Authored-By: Claude <noreply@anthropic.com>