Chapters

DOC-05 / Technical reference · Chapter 04

Worksites, Jobs & Tasks — Data Model and API

Describes the worksite/job/task hierarchy in Synedre OS, the DB data model, canonical statuses/scopes, and the associated Python API.

Projects, work items & tasks

What it is for

The Synedre OS harness organises all development work into a hierarchy persisted in the database — 1 project = N work items = N tasks — so that the orchestrator (Atlas) and the agents can create, track, parallelise and close work without any out-of-database state. This page documents the data model, the Python interface that manipulates it, the seven-step creation procedure, the multi-session lock mechanism, and the status cascades (including the so-called "work-item-bis" resolution of a paused work item).

1. Data model

1.1 Hierarchy

Three nested levels structure all work:

  • Project — high-level steering unit
  • Work item — subset of a project, assignable to an agent
  • Task — atomic action within a work item

Three main Python entities encapsulate these levels. Each extends a common base class and exposes validation, creation and update methods. Two secondary entities manage respectively the multi-session locks and the inter-task dependencies.

Entity Role Primary key
Project Steering unit project identifier
Work item Subset of a project work item identifier
Task Atomic action task identifier
Auto-decomposition log Audit of automatic decomposition runs run identifier
Lock Mutex per project and per owner type (project identifier, owner type)
Task dependency Arc in the intra-work-item DAG dependency identifier

Migration note — Table naming is heterogeneous for historical reasons. Do not rename without a dedicated project.

1.2 Statuses

Canonical statuses are split by level to prevent cross-level validation errors.

Project statuses: planning, dev, test, paused, done, cancelled.

Work item statuses: discovery, planning, dev, paused, review, done, cancelled.

An alias grouping the union of both sets is retained for backward compatibility with certain internal modules. All new code must use the set specific to the relevant level.

The shared validation method accepts a status_set parameter: the Project entity passes it the project statuses, and the Work item entity passes it the work item statuses. An SQL INSERT with an out-of-enum status is rejected directly by the database through CHECK constraints placed on the two relevant tables.

Task statuses: todo, doing, done, paused, cancelled — a distinct vocabulary, with no CHECK constraint in the database at this time.

Canonical priorities are: P0, P1, P2, P3.

1.3 Scopes

The scope field discriminates the synchronisation perimeter (monolith or OSS) and conditions certain validations.

Project scope (column scope, nullable) — values authorised by a database constraint:

synedre | codemyshop-oss | codemyshop-enterprise | tenant | business | juridique | negociation | conseil

Warning — The internal documentation for the creation method only lists the first five scopes. The database authorises three additional ones (juridique, negociation, conseil), added for Atlas intents. The database takes precedence.

Task scope — validated both in code and by a database constraint:

synedre-internal | codemyshop-oss | codemyshop-enterprise | tenant-single | tenant-multi | infra | doctrine

An invalid task scope raises a ValueError at task creation time.

1.4 Notable fields

At the project level:

Field Role
mission_letter (free text) Structured mission letter (step 2 of the doctrine)
preprod_test_plan (free text) Pre-production test plan — Markdown or YAML v2 (presence of version: triggers automatic QA)
ship_command (short string) Exact closure command, e.g. ./ship synedre-os
auto_explode (boolean, default true) Per-project kill-switch for automatic discovery→implementation decomposition
mode_auto (boolean) Project autonomy V1 — the agent executes without validating each action
max_cost_eur (numeric) Per-project AI cost cap (NULL = unlimited)
client_id Reference to a client, or NULL for an internal project

At the work item level: notable fields include the codename and prompt of the assigned agent, the zone perimeter, the exit criteria, structured JSON blocks for context / decisions / findings, an inter-work-item dependency identifier (with cycle detection), the resolution identifier for the work-item-bis pattern (see §6), as well as metadata covering steering, contractual model, auto mode, repository paths and associated backlogs.

At the task level: notable fields include the assignee codename, the token estimate, the recommended model, the display position, the result of the last test, actual metrics (tokens, cost), start and end timestamps, and two visual fields (visual_intent and visual_url) used by the post-deployment visual verification pipeline — a NULL value indicates a non-visual task.

1.5 Agent recruitment — persistence

Agent recruitment onto a project (step 3 of the doctrine) is persisted in a dedicated table, distinct from the assignment field of an individual task (which only carries the assignment of a single task).

Column Role
Assignment identifier (PK) Primary key of the assignment
Project identifier Link to the project
Agent codename Recruited agent (e.g. Mitnick, Turing, Brunel…)
Role production / lead_production / validation / lead_validation
Position Display order
Assignment / unassignment date Assignment lifecycle
Notes Recruitment justification

The task→work-item cascade logic queries this table to detect the presence of a QA team: if an agent with a validation or lead_validation role is present, the cascade switches to a QA run rather than a simple status change (see §5).

1.6 Automatic decomposition log

A dedicated entity persists one audit record per invocation of the module that transforms the decisions of a discovery work item into implementation work items (phases A/B/C). One record equals one invocation. It is created with the status pending at the start of the pipeline, then updated at the end.

Status Meaning
pending Run in progress
success Implementation work items created
validation_failed LLM plan rejected by validation
llm_failed LLM API error
killswitched One of the three kill-switches interrupted execution

Key fields include the produced JSON plan, the number of work items created, token and cost metrics, and dry_run and force flags. Record retention is configurable (default: 90 days). A global daily quota is also configurable: if the number of runs in the last 24 hours exceeds it, the decomposition is marked killswitched without an error. The run history is accessible from the dashboard via the /chantier explode list command.

1.7 Task→task dependencies — intra-work-item DAG

The task dependency engine manages an N:M directed acyclic graph (DAG). Not to be confused with inter-work-item dependencies (a column on the Work item entity): these two mechanisms operate at distinct levels.

Semantics: a blocked task cannot start until the blocking task reaches the done status. A strong business constraint applies: both tasks must belong to the same work item — a cross-work-item dependency raises a dedicated application error (rejected in code, no SQL trigger).

Operation Conceptual method
Add a dependency add_dep(blocked, blocker)
List dependencies of a work item JOIN blocker/blocked with task titles
List blockers of a task Direct blockers of a given task
Check whether a task is unblocked is_tache_unblocked(id)
Remove a dependency Idempotent — no effect if non-existent

Cycle prevention: before any addition, a depth-first search (DFS) is performed from the blocking task through the existing graph. If the traversal reaches the blocked task, a cycle error is raised before the insert. A maximum-depth guard (100 levels) protects against corrupted DAGs.

A blocker with a cancelled status is considered resolved in the unblocking check, in order to avoid deadlocks. This behaviour also applies to selective auto mode, which only starts a task once all its blockers are resolved.

Atomic Creation of a Work Package

The creation method exposed by the project management engine is the sole canonical path for instantiating a work package: it atomically inserts the work package, its first work item, and at least one task, thereby preventing orphaned work packages from appearing.

Signature

The method accepts the following parameters:

  • codename: kebab-case identifier of the work package.
  • title: human-readable label of the work package.
  • first_travail (required): dictionary describing the first work item — codename, title, and optional fields such as priority, current phase, current task, next action, blockers.
  • first_taches (preferred, v3 doctrine): list of dictionaries describing the initial tasks. Each task carries at minimum a title and the codename of the assigned agent; optional fields enrich the description (priority, token estimate, estimated duration, description, position, recommended model, scope, visual intent, visual URL).
  • first_tache: singular form retained for backward compatibility — must not be combined with first_taches.
  • client_id: optional reference to the relevant tenant.
  • priority: priority level among P0, P1, P2 (default), P3.
  • scope, current_focus, notes: optional contextual fields.

The method returns a confirmation dictionary (see § Return Value below).

Blocking Validations

All validations are accumulated before the first INSERT; a single violation raises a ValidationError and cancels the entire operation:

  1. Kebab-case format of codename — regular expression ^[a-z0-9][a-z0-9\-]{2,62}[a-z0-9]$: lowercase letters, digits and hyphens, 4 to 64 characters.
  2. Codename uniqueness — an existence check is performed; if the codename is already in use, creation is rejected.
  3. Valid priority — must belong to {P0, P1, P2, P3}.
  4. Valid initial work item — codename and title must be provided and non-empty.
  5. At least one taskfirst_tache or first_taches is required; supplying both simultaneously is an error.
  6. Known agent — each assigned agent codename is verified against the active agent registry.
  7. At least two distinct agents if scope is of tenant type (v3 doctrine) — if the scope field begins with tenant, the set of assignees must contain at least two distinct codenames.

Non-blocking warning: the absence of a token estimate on a task generates a warning but does not prevent creation — an automatic estimator fills in the missing value (see § Estimation below).

Atomicity and Rollback

The insertion is performed within a single transaction (BEGIN … COMMIT) with the immediate-abort-on-error option. If a single INSERT fails, the entire transaction is rolled back: neither a work package without a work item, nor a work item without a task can exist. Work items and tasks reference their parent via a sub-select on the work package codename, guaranteeing foreign key resolution in insertion order.

Token Estimation and Recommended Model

If the recommended model is not explicitly provided for a task, it is computed automatically according to the following rule:

Condition Selected Model
Priority P0 or task in recurring failure (≥ 2 iterations in fail state) High-capacity model (e.g. opus)
Estimate ≥ 8,000 tokens or estimate absent High-capacity model (e.g. opus)
Estimate ≥ 1,500 tokens Intermediate model (e.g. sonnet)
Otherwise Lightweight model (e.g. haiku)

When creating a task outside the skeleton, if the token estimate is absent, the engine computes it via a dedicated estimator, then recomputes the recommended model on the persisted row.

Attached Skills and Tools

After insertion, the skeleton attaches the optional skills and tools declared per task. An unknown skill or tool does not raise an error: a warning is emitted and a proposal is queued for validation.

Return Value

The method returns a dictionary containing:

  • The internal identifiers of the created work package, work item, and tasks.
  • An id_tache field (backward compatibility, equal to the first in the list) and id_taches (full list, v3 doctrine).
  • A codenames object grouping the codenames of the work package, work item, and tasks.
  • A warnings list of non-blocking warnings emitted during creation.

Command-Line Interface

The project management engine exposes a CLI allowing atomic creation to be triggered by passing the JSON payload on standard input or via a file. The --dry-run option prints the constructed payload without performing any insertion. Additional verbs are available — list, display, work item creation, update, close, resolve — accessible via the --help option.

A guard hook detects any attempt at direct SQL insertion on the project engine tables made outside this facade, and emits a warning.

7-Step Work Package Creation Procedure

No work package may be created without following these steps in order. For work packages triggered by email, the dedicated facade executes steps 0a through 4 under constraint (blocking if attachment scan is not clean or fewer than 2 agents). It stops at the atomic skeleton creation (step 4) and performs neither step 2 (drafting the mission brief: the facade generates only generic task titles of the form @agent — analysis…) nor step 5 (post-skeleton update of the mission fields, scope, test plan, and delivery command). These two steps remain to be completed manually after the facade call.

Step Action Mechanism
0a Read the email request verbatim Read via the internal inbox API
0b Extract and scan attachments before any reading Attachment extraction, then antivirus/antimalware analysis. Non-clean verdict → immediate block
1 Audit of available agents Query of the active agent registry (codename, nickname, role, group)
2 Draft the mission brief Structure: OBJECTIVE / CONTEXT / SCOPE / DELIVERABLES / CONSTRAINTS / DOCTRINE, based on content read in steps 0a/0b
3 Explicit recruitment At least 2 distinct agents if scope is of tenant type
4 Atomic skeleton creation See § Atomic Creation
5 Post-skeleton update Persistence of the mission brief, scope, pre-production test plan, and delivery command via a named UPDATE on the codename

Email-Based Creation Facade

The email-based creation facade follows this flow:

  1. Email retrieval — call to the internal inbox API (never direct SQL).
  2. Attachment extraction and scan — if an attachment is not clean, the facade displays a blocking message and exits with code 2.
  3. Request summary — extraction of title, client, priority, and scope.
  4. Agent listing and recruitment — if fewer than 2 distinct agents are selected, blocking with exit code 3.
  5. Skeleton construction and insertion — one task per agent with generic titles, initial work item in discovery phase. Corresponds to doctrinal step 4. No mission brief update or doctrinal step 5 is performed.

Exit codes: 2 = non-clean attachment, 3 = fewer than 2 agents, 4 = validation error.

Two distinct control levels on agent count: the facade always requires at least 2 distinct agents, regardless of the declared scope. The atomic creation engine, for its part, only enforces this constraint when the scope field begins with tenant. The default scope tenant aligns the facade's blocking message, but does not condition its own check.

The facade creates a single initial work item in discovery phase. The transition to implementation work items remains manual or goes through the automatic work item explosion mechanism (see § Auto-explode).

Test Plan and Delivery Command Fields (Step 5)

These two fields are required for any non-trivial work package. In their absence, the work package update cascade records a non-blocking warning, but the "READY TO SHIP" banner cannot be validated. If the test plan is written in YAML v2 format (with a version: header), the cascade automatically triggers a Playwright test session in the background. An environment switch allows this automatic triggering to be disabled.

Multi-Session Locking

The locking mechanism allows N different work packages to be opened in parallel (N terminals or workers) while refusing two concurrent sessions on the same work package. This is not an ergonomic blocker, but a collision-prevention safety net.

Data Model

The lock relies on a dedicated table with a composite primary key (work package identifier, owner type). The owner type is constrained to two values:

  • user: interactive session (for example, an SSH session from a human operator).
  • worker: agent subprocess spawned by the automated task engine.

Both types may coexist on the same work package; each type contests only its own slot. The type is detected via an environment variable, with fallback to inspection of the parent process tree. The session identifier is resolved in order: Claude session environment variable > transcript path fingerprint > PID/user/host combination.

TTL and Acquisition

The time-to-live of an inactive lock is 30 minutes. Acquisition attempts an INSERT … ON CONFLICT … DO UPDATE that succeeds only if the session identifier matches the current holder or if the lock has expired (last activity more than 30 minutes ago). Otherwise, the method returns {acquired: False, owner: {…}}.

The lock is acquired automatically when retrieving the context of a work package. On refusal, a ChantierLockedError exception is raised, intercepted by the work package management skill. An environment switch allows the locking mechanism to be disabled entirely.

Available Operations

Operation Description CLI Option
Active lock inventory Lists live locks with their owner and last activity timestamp --locks
Release own lock Idempotent deletion of the lock corresponding to the current session --release <codename>
Reclaim lock (kick) Forces reclaim of the same-type slot, evicting the previous owner --force-claim <codename>
Keep lock alive Updates the activity timestamp to push back TTL expiration

Three Release Safety Nets

  1. Session-end hook — declared in the Claude hooks configuration, releases the lock promptly at the end of an interactive session.
  2. TTL cron task — runs every 5 minutes, removes locks whose last activity exceeds 30 minutes. Idempotent, always exits 0, silent if the database is unavailable or the table is absent. This is the true safety net against crashes, network outages, or abrupt process terminations.
  3. Manual force-claim — used as a last resort to reclaim an orphaned lock.

Status Cascades

Entity update methods are overloaded to propagate terminal statuses from one level to the next. The cascade is limited to one step per level — each step autonomously re-triggers its own cascade.

Task → job propagation logic

When all tasks of a job reach a terminal status (completed or cancelled) and at least one task is completed, the system evaluates whether a validation team is present on the project:

  • With a validation team — the reflex engine delegates to a quality-control subprocess. If the overall verdict is positive, the job transitions to the completed state. Otherwise, corrective tasks are created and the job remains active in its development phase.
  • Without a validation team — the transition to completed is performed directly.

Any exception raised by the quality-control subprocess is fail-safe: it is logged without blocking or modifying the job's state.

Job → project propagation logic

When all jobs of a project reach a terminal status and at least one is completed, the project is promoted to the test state (pre-production, pending review). This transition also triggers, in a non-blocking manner, the entropy detector described in the following section.

⚠️ The task → job transition is not always direct. As soon as a project has at least one agent assigned to a validation role, the cascade delegates to the quality-control subprocess rather than performing the bump immediately. The job transitions to the completed state only if the overall verdict is positive; otherwise, corrective tasks are generated and the job remains active. The direct bump occurs only when no validation team is configured.

The final test → completed transition remains manual — it is a human validation action performed after the ./ship delivery command, never triggered automatically by a cascade.

Cascade Natively Triggered by the Database (Incoming Messages)

Independently of the application-level cascades described above, a native database trigger automatically propagates the completed state at the data layer, transparently to the application layer:

  • This trigger fires after any status update on a project.
  • When a project transitions to completed for the first time, it automatically marks as resolved all incoming emails attached to that project that are not already resolved or classified as spam. The processing timestamp is recorded at the time of resolution.
  • Practical consequence: an incoming email may appear as resolved without any explicit application-level action having directly processed it. This behavior is normal and intentional — it should be kept in mind when debugging an email that has transitioned to the resolved state in an apparently autonomous manner.

Safeguard: Projects in Discovery Phase and Auto-Explosion

The job → project cascade includes a specific safeguard: if all completed jobs are still in the discovery phase, the project is not promoted to the test state. This safeguard prevents a newly created project — carrying only a single initial analysis job — from being promoted to pre-production as soon as the analysis is complete, before implementation jobs have been defined.

In this case, an auto-explosion mechanism may trigger to transform the discovery job into concrete implementation jobs. This mechanism is protected by three successive circuit breakers, evaluated in order:

  1. An environment variable allowing auto-explosion to be globally disabled.
  2. A per-project flag in the database (auto_explode = FALSE).
  3. A global flag activatable via a YAML configuration file.

If any of these three circuit breakers is active, the project remains in the planning state for manual review. Each invocation of the auto-explosion mechanism is audited in a dedicated log, with the following statuses:

  • killswitched — at least one circuit breaker blocked the operation;
  • success — implementation jobs were created successfully;
  • validation_failed — the plan proposed by the language model was rejected.

Resolve-Paused — The Companion Job Pattern

When a job is placed in the paused state, a companion job is created to handle the resolution of the blockage. This companion job references the blocked job. When the companion transitions to the completed state, the resolution mechanism automatically cleans up the blocked parent job before the job → project cascade is re-evaluated (since this cleanup modifies the count of terminal jobs in the project).

Effects produced when the companion job transitions to completed

  1. The blocked parent job transitions to the completed state via a direct update (bypassing cascade recursion).
  2. All tasks of the blocked parent job that are still pending are cancelled via a bulk update.
  3. The decision log is updated symmetrically on both the companion and the parent (full traceability).

Resolution mechanism safeguards

Condition Behavior
No parent job referenced Silent no-op (legacy path)
Parent job and companion on different projects Error raised — cross-project resolution is forbidden
Parent job already completed Idempotent no-op
Mutual reference cycle (A → B and B → A) Error raised — cycles are forbidden
Parent job not found (orphaned reference) Warning logged, operation ignored
Parent status outside {paused, dev, planning} Warning logged, operation ignored

Database constraints reinforce this safeguard: a partial index on the reference column, a constraint preventing a job from referencing itself, and a foreign key configured to set the reference to NULL upon parent deletion.

Pitfalls & Invariants

  • Multi-line columns and SQL readers — certain text fields (mission statement, notes, test plan, current focus, next action, current task) contain line breaks. Generic read functions based on a column separator fail on these fields: always use the dedicated CSV reader for any new query targeting these columns.
  • DB schema / entity declaration synchronization — adding a column to the database without adding it to the corresponding application entity's field list causes updates to that column to silently have no effect. Always verify consistency between the DB schema and the entity declaration during any migration.
  • Distinct statuses per level (since the split into two sets) — the valid statuses for a job are not the same as those valid for a project. A review status is permitted for a job but is out-of-enumeration for a project. Always pass the correct status set during validation. Never reuse the generic canonical set for new level-specific code.
  • Database status constraintsCHECK constraints are present in the database for both levels. A raw insert with an out-of-enumeration status will be rejected by the database, including in migration scripts.
  • Project scope: the database is authoritative — in case of discrepancy between the DB constraint and the application docstring, always defer to the DB constraint.
  • Visual fields on tasks — two optional fields allow a visual intent and a reference URL to be associated with a task. NULL means the task has no visual component. The post-deployment visual verification pipeline checks that the intent is correctly rendered. Do not omit these fields from the entity declaration, as this will cause a silent update regression (see the synchronization pitfall described above).
  • Database credentials — database access credentials are read exclusively from the environment variables in the root configuration file. They must never appear in plain text in the source code or in the database.
  • Two dependency mechanisms coexistjob → job dependencies are managed by a direct reference column on the job entity; task → task dependencies within a single job are managed by an N:M directed acyclic graph via a dedicated dependency entity. These two mechanisms are not interchangeable. The inter-job constraint on task dependencies is purely at the application level: corrupting it directly via SQL will not be caught by a trigger.
  • The auto-explosion log is a passive log, not a processing queue — entries in this log are created and updated by the explosion pipeline itself. Nothing consumes them. Do not use them to trigger or re-run an explosion operation.