[ai] Route oversized tasks through durable splits

This commit is contained in:
John Preston
2026-08-23 10:20:43 +04:00
parent 03f59cd642
commit d428defec0
15 changed files with 1412 additions and 134 deletions

View File

@@ -52,14 +52,21 @@ The canonical lifecycle is deliberately small:
- `todo` with `claimed_by: null` is ready shared work;
- `in-progress` with this `checkout_tag` is this checkout's one active task;
- `blocked` with this tag is a rare published unfinished boundary;
- `split-required` with this tag is a published performer result awaiting one
scheduler-owned split transaction;
- `approved` is the only completed terminal state.
After routing, the retired source has no live state: its sealed `split.yaml`
records all successors and the optional implementation carrier. This is
terminal history, not another runnable status.
`Start` atomically assigns an unclaimed task and changes it to `in-progress`.
Normal phase artifacts remain local and uncommitted in the slot worktree.
`Approve` publishes all final AI artifacts and state in one commit. `Block` is
permitted only for a genuine exhausted task blocker,
not for an interrupted agent session. Never publish `Claim`, phase checkpoint,
or `Resume` commits. Existing claimed `todo` records from the older workflow
not for an interrupted agent session. `Split-required` publishes the proposal
and seals any retained source work for the scheduler transaction. Never publish
`Claim`, phase checkpoint, or `Resume` commits. Existing claimed `todo` records from the older workflow
remain startable but do not justify creating new reservations.
Never infer ownership from an inbox receipt. Do not steal work from another
@@ -76,8 +83,18 @@ the queue. A repeated pre-commit race may remain pending for the next
invocation; record that marker as attempted and do not spin. If a task is
already active, its expected local phase files make the shared AI slot unsafe
for consolidation: freeze and finish that active task first, then recover all
pending markers at its clean canonical `Approve` or `Block` boundary before
selecting more work.
pending markers at its clean canonical `Approve`, `Block`, or completed split
routing boundary before selecting more work.
After recovering pending consolidations and before freezing a new batch, route
this checkout's `own_split_required` task, when any, through the dedicated split
worker below. Refresh the queue afterward and freeze the resulting replacement
tasks at the front of the initial batch regardless of a scope hint; they replace
checkout work that hints cannot exclude. Record that startup split for the
invocation summary. A split-required task is exclusive checkout work: do not
start, retry, or resume another task while it remains unrouted. If its retained
source state cannot be transferred safely, stop with that state intact rather
than skipping it.
## Interpret scope hints
@@ -87,7 +104,8 @@ preference such as "payments tasks first" reorders the shared tasks recorded
in the startup batch; it does not exclude the others. A restriction such as
"only the payments tasks" or "all tasks except projects X and Y" records only
matching unclaimed shared tasks.
Hints never exclude this checkout's active, blocked, or legacy-reserved work.
Hints never exclude this checkout's active, split-required, blocked, or
legacy-reserved work.
A restrictive hint that matches no shared task does not make an existing
queue look idle or permit inbox processing.
@@ -113,7 +131,9 @@ these invocation-local values in the scheduler plan:
- ordered `batch_task_ids`, initially equal to the initial batch;
- empty `discovered_task_ids`;
- empty `consolidation_mappings` and `consolidation_receipts`;
- empty `attempted_blocked`.
- empty `attempted_blocked`;
- `split_records`, initialized with any startup split transaction and otherwise
empty.
Do not write a batch file, claim the whole batch, or publish reservations.
Queue refreshes update task state but never add ordinary task ids to the
@@ -170,14 +190,15 @@ would record from the startup snapshot:
- only matching unclaimed tasks under a restrictive hint.
Record dependency-waiting tasks too. Finish the active task to an approved,
genuinely blocked, or global-hard-stop boundary, then continue through ready
recorded tasks one at a time. Only an explicit request to run just the active
genuinely blocked, split-required, or global-hard-stop boundary, route any
split before continuing, then continue through ready recorded tasks one at a
time. Only an explicit request to run just the active
task produces a one-item active batch.
### Mode 2: drain the existing queue snapshot
When there is no active task but any `own_blocked`, `own_todo`, or
`unclaimed_todo` task exists, choose `queue` mode. Record:
When there is no active or split-required task but any `own_blocked`,
`own_todo`, or `unclaimed_todo` task exists, choose `queue` mode. Record:
- every own blocked and legacy-reserved task, regardless of the hint;
- every unclaimed task under no hint or a preference, ordered with preferred
@@ -194,8 +215,9 @@ recorded batch drains.
### Mode 3: bootstrap from the inbox
Choose `inbox` mode only when `own_in_progress`, `own_blocked`, `own_todo`,
and `unclaimed_todo` were all empty in the initial snapshot. Work owned by
Choose `inbox` mode only when `own_in_progress`, `own_split_required`,
`own_blocked`, `own_todo`, and `unclaimed_todo` were all empty in the initial
snapshot. Work owned by
another checkout is not work this checkout can drain and does not enter its
batch.
@@ -226,8 +248,12 @@ claimed concurrently, blocked by an external dependency, or otherwise
unavailable.
Before publishing any new canonical `Start` commit, require a clean Telegram
source checkout with clean submodules and no unrelated untracked files. Do not
require a Telegram executable, portable account, desktop, Docker daemon, or
source checkout with clean submodules and no unrelated untracked files. The one
exception is the checkout-owned first replacement whose `carried_from` field
and source `split.yaml` designate it as the implementation carrier: start it
with the retained source state intact, and let the helper revalidate the sealed
worktree before transferring task refs. Do not require a Telegram executable,
portable account, desktop, Docker daemon, or
other instrument before assessment selects it. The performer gates every
selected instrument before using it and records an unavailable platform or
stage precisely instead of preventing unrelated task work from starting.
@@ -240,7 +266,18 @@ resumption handoff. There must be at most one active task. Stop on an active
task outside the batch instead of silently expanding the batch or stealing
ownership.
### 2. Retry recorded blocked work
### 2. Start a carried implementation
Otherwise select the first ready checkout-owned `todo` task in
`batch_task_ids` whose `carried_from` field names a retired split task. It must
be the first replacement and match that task's `implementation_carrier`.
Start it with the normal helper command. The helper verifies the source
worktree seal, transfers the old task's base/green/run refs, publishes the
carrier's canonical `Start`, and removes the obsolete refs. If any check fails,
hard-stop with both the carrier and retained source state intact; do not start
another batch task around it.
### 3. Retry recorded blocked work
Otherwise select the first ready task in `own_blocked` whose id is in
`batch_task_ids` and not in `attempted_blocked`. Readiness means every
@@ -261,7 +298,7 @@ genuine new `Block` boundary under the validation below. A test-campaign cap,
`TEST_FLAW`, blank/missing evidence, or another recoverable harness failure is
not genuine and does not consume this invocation's blocked retry.
### 3. Start recorded reserved work
### 4. Start recorded reserved work
Otherwise select the first ready legacy `todo` task already owned by this
checkout whose id is in `batch_task_ids`, and start it:
@@ -275,7 +312,7 @@ The resulting canonical `Start` commit changes it to `in-progress`. Leave
legacy reservations with unfinished dependencies untouched and consider later
ready batch work.
### 4. Start recorded shared work
### 5. Start recorded shared work
Otherwise select the first ready unclaimed `todo` task whose id is in
`batch_task_ids`, using the order recorded at startup. Start it with the same
@@ -286,11 +323,12 @@ A concurrent start may mean another checkout won the task. Never overwrite
shared state or replace it with a task outside the batch; refresh and continue
with another recorded id.
### 5. Stop normally
### 6. Stop normally
Stop when none of these batch-scoped conditions exist:
- this checkout's active batch task;
- a ready recorded carried implementation;
- a ready recorded blocked task not attempted in this invocation;
- a ready recorded legacy-reserved task;
- a ready recorded unclaimed task.
@@ -327,9 +365,9 @@ Source checkout: <source_root>
AI slot worktree: <slot_worktree>
Checkout tag: <checkout_tag>
Task: <task-id>
Own this task until it is approved, genuinely blocked, or reaches a global
hard stop. You may use the bounded leaf delegation required by the skill. Do
not select or start another task.
Own this task until it is approved, genuinely blocked, split-required, or
reaches a global hard stop. You may use the bounded leaf delegation required by
the skill. Do not select or start another task.
```
The performer is stateful. Never duplicate it. Poll at no more than 60-second
@@ -342,23 +380,42 @@ After it returns, require one of:
canonical AI master;
- source checkout clean and task exceptionally `blocked` on canonical master,
with exact unverified behavior;
- a pre-source `Scope: split-required` or review-convergence
`RESCOPE_REQUIRED` boundary with `work/split-proposal.md`, leaving the task
`in-progress` and all source/task recovery intact; or
- task `split-required` on canonical AI master with `work/split-proposal.md`
and `work/carried-work.json`, leaving every retained source change and task
ref sealed for transfer; or
- a clearly reported global hard stop, leaving the task `in-progress` and all
task-scoped local state recoverable for the next invocation.
A rescope boundary stops task performance and is not retried, approved, blocked,
or routed as an ordinary discovered follow-up. The scheduler is the queue
authority: it independently checks that the proposal contains separately
shippable/testable boundaries and a coherent dependency graph before any
dedicated rescope transaction or human handoff. It never treats the performer's
proposal alone as permission to delete source or rewrite shared task state. On
a later invocation, a performer that finds the same unresolved boundary returns
it immediately without rerunning planning, review, builds, or tests. Inbox
planning prevents most new oversized tasks; replacing an already-active task
and deciding how to salvage its source requires that explicit scheduler-owned
transaction, not ordinary discovery routing.
A rescope result stops task performance and is not retried, approved, blocked,
or routed as an ordinary discovered follow-up. The performer's publication is
the durable request for the scheduler-owned transaction below; it is not
permission to delete source or flatten several successors into one alias.
## Route a split-required result
When a batch performer publishes `split-required`, immediately spawn one fresh
split worker with `fork_turns: "none"`. Give it `source_root`, `slot_worktree`,
`checkout_tag`, the source task id, and the current ordered batch. Tell it not
to delegate and to read
`.agents/skills/continue/references/split-required-task.md` completely. It may
inspect Telegram source and edit/publish AI task, project, dependency, and
receipt state; it must not edit, reset, stash, commit, build, or test Telegram
source.
Validate its canonical `Split <source-id>` result and refreshed queue. Replace
the source id in `batch_task_ids` at its existing position with the ordered
replacement ids, removing duplicates; leave `initial_batch_task_ids` unchanged.
Append the source, replacements, carrier, and receipt to `split_records`. The
replacements are part of this invocation because they replace an existing batch
member, not because later queue refreshes normally expand the frozen batch.
When a carrier exists it must be first, checkout-owned `todo`, and name the
source in `carried_from`; select it through the carried-implementation step
before any blocked, reserved, or shared task. Starting it performs the sealed
source-ref transfer. When no carrier exists, all replacements are ordinary
unclaimed `todo`. A split publication race is retried normally. A semantic
conflict, unavailable remote, changed worktree seal, or incoherent carrier is a
global hard stop with the source result and implementation left recoverable.
Before accepting a canonical test block, read `work/result.md` and
`work/test.md`. It is genuine only when the verdict is not `TEST_FLAW`, does
@@ -543,7 +600,8 @@ this invocation, and continue without treating the optimization as a blocker.
If the worker created a commit that cannot be published safely, preserve it and
hard-stop exactly as for discovery routing.
At every clean canonical `Approve` or `Block` boundary, process any older
At every clean canonical `Approve`, `Block`, or completed split routing
boundary, process any older
pending marker deferred by an active startup task before selecting more work,
then process the marker just created by that task's routing. Attempt each marker
at most once per invocation.
@@ -562,7 +620,8 @@ Return one compact summary: invocation mode, initial batch ids, discovered ids
added to the batch, inbox receipt if processed, tasks approved, exceptionally
blocked tasks with exact unverified behavior and retry status, recorded tasks
left queued, unrelated new tasks deferred to the next invocation, routed
discoveries, infrastructure-limited coverage gaps recorded but not routed,
discoveries, split-required sources with ordered replacements and carriers,
infrastructure-limited coverage gaps recorded but not routed,
consolidation no-merge results or receipts, old-to-new mappings, the net
task-count saving, archived projects, any discarded interrupted-worker leftovers,
elapsed time, and why the loop stopped. Make any global hard stop or unsafe

View File

@@ -0,0 +1,111 @@
# Route a split-required task
Run this only from the `continue` scheduler for one task already published as
`status: split-required`. It is a deep task-routing transaction, not task
implementation. Inspect Telegram source and the retained implementation, but
do not edit, reset, stash, commit, build, or test Telegram source.
## Read the retained task
Read the source task's complete `task.md`, project context, dependencies,
`work/context.md`, `work/plan.md`, `work/split-proposal.md`, `work/result.md`,
and `work/carried-work.json`. Inspect the complete retained source diff and
the relevant adjacent code. The proposal is input, not a set of titles to copy
blindly: refine it into the smallest independently shippable and independently
testable product boundaries.
Each replacement must have one useful outcome, a direct acceptance oracle, and
enough self-contained context to run without this routing session. Split at
network, persistence, engine, ownership/lifetime, lifecycle, UI, platform, or
evidence boundaries when they can stand on their own. Keep inseparable APIs and
their only callers together. Add a final integration task only when integration
has behavior not already proved by the component tasks.
Preserve the source task's project by default. A replacement may leave that
project only when it remains coherent, implementable, and testable with the
project changes absent. Preserve the source task's existing dependencies where
their code is still required. Order replacement dependencies by actual shipped
code or behavior, not by the order in which the plan happened to mention them.
## Preserve existing implementation
`work/carried-work.json` records whether source implementation exists and seals
the owned working diff. When it says `implementation: retained`, designate
exactly one first replacement as the implementation carrier. Shape that task so
the whole retained diff is inside a coherent boundary it can inspect, correct,
review, build, and test. If the diff spans the future slices, a real first task
that stabilizes and proves the shared foundation is appropriate; an unreviewed
checkpoint or a task whose only result is storing the patch is not.
The carrier's `task.md` must name the split source task, tell the performer to
read its retained artifacts and complete source diff, and state which current
implementation it adopts. Later replacements depend on the carrier whenever
they consume that code. Create the carrier initially as pristine unclaimed
`todo`; the publication helper transfers its reservation and
`owned-paths.txt`. Do not copy phase conclusions as approvals: the carrier runs
its own focused planning, complete review, and evidence loop over the retained
implementation.
Do not discard existing implementation merely because a cleaner decomposition
would have started differently. Do not reset it, hide it in an ignored patch,
or split it into unreviewed commits during routing. If no coherent carrier can
own it, stop with the split-required task and source state untouched and report
the exact conflict for a human decision.
When `implementation: none`, create no carrier and leave every replacement
unclaimed.
## Write replacement state
Create at least two dated replacement task directories using the ordinary
`process-inbox` task and state schemas. Every replacement uses `type: implement`
and names the split receipt in `inbox_receipt`. Before publication every state
is pristine unclaimed `todo` and omits or sets `carried_from: null`; the helper
sets the carrier's durable ownership transfer.
Write one receipt under `receipts/YYYY/MM/DD/` containing:
- the split source id and why one campaign was incoherent;
- every replacement id, shipped boundary, acceptance oracle, and dependency;
- the implementation carrier or `none`;
- how every original acceptance criterion and retained source path is owned;
- every changed dependent and project link.
Replace the source task's link in its project index with the replacement links
in dependency/routing order. Rewrite every live task dependency on the source
to the exact successor or successors that provide the needed behavior. A
retired split id must remain in neither a live dependency nor a project index.
Do not edit the source task directory; the helper replaces only its
`state.yaml` with a sealed multi-target `split.yaml`.
## Validate and publish
Check the complete dependency graph, task links, projects, receipt, native text
format, and absence of persisted commit hashes. Then publish through:
```bash
python3 .agents/skills/process-inbox/scripts/workspace.py split-publish \
--source-task <source-id> \
--receipt <receipts/YYYY/MM/DD/split-receipt.md> \
--replacement <first-id> \
--replacement <next-id> \
[--implementation-carrier <first-id>] \
--path <tasks/source-id> \
--path <tasks/first-id> \
--path <tasks/next-id> \
--path <each-changed-project-dependent-or-receipt-path>
```
Use the host's available Python 3 command. The helper rechecks the sealed source
worktree, replacement state, receipt, project indexes, full dependency graph,
and retained source-task digest. It publishes `Split <source-id>`. With a
carrier, it leaves the source refs and working implementation intact and makes
the carrier a checkout-owned `todo`; the scheduler must select it first and use
the normal `start` command, which rechecks the worktree seal, transfers the refs,
and publishes the carrier's canonical `Start`. Without a carrier the helper
removes the obsolete source refs after publication.
On an ordinary publication race, retry through the helper. Preserve an
unpublished split commit on semantic conflict or remote outage. Report the
source id, ordered replacements, carrier, receipt, publication state, and any
hard stop; never report commit hashes.

View File

@@ -1,6 +1,6 @@
---
name: perform-task
description: Resolve, start or resume, implement, review, test, and publish exactly one existing ai-tdesktop task by short slug or full dated id, including rare blocked unfinished work. Use when the user invokes $perform-task or /perform-task with a known task name, or when the continue scheduler delegates one selected task. Runs standard review lenses with fast applicability bailouts and selects task-specific domain and evidence instruments without selecting additional work.
description: Resolve, start or resume, implement, review, test, and publish exactly one existing ai-tdesktop task by short slug or full dated id, including rare blocked retries and split-required results. Use when the user invokes $perform-task or /perform-task with a known task name, or when the continue scheduler delegates one selected task. Runs standard review lenses with fast applicability bailouts and selects task-specific domain and evidence instruments without selecting additional work.
---
# Perform One AI Task
@@ -10,7 +10,8 @@ before any other host-specific delegation rule and apply its substitutions.
Own exactly one task through its retained change, or a proved
`already-satisfied` outcome, and a canonical AI `Approve` or exceptional
`Block`. Do not process the inbox, split the task, drain the queue,
`Block`, or a canonical `Split-required` result. Do not process the inbox,
create replacement tasks, drain the queue,
select a follow-up, or consolidate pending tasks afterward. The `continue`
scheduler isolates discovery routing and queue consolidation in fresh workers
after this performer returns.
@@ -62,6 +63,9 @@ Inspect the resolved task, readiness, `other_active_task`, status, and owner.
- If another task is already `in-progress` for this checkout, stop.
- If this task is `approved`, report its completed result and stop.
- If it is `split-required`, report its published split proposal and stop. A
direct invocation leaves routing to the human; a scheduler invocation returns
control so `continue` can launch the dedicated split worker.
- If it is owned by another checkout, stop. Cross-checkout restart is a rare
explicit human reassignment, never an implicit steal.
- If its dependencies are unfinished, report them and stop without starting.
@@ -109,7 +113,7 @@ the scheduler may continue work that does not depend on it.
## Run and publish
Execute `references/pipeline.md` exactly. A task that changes the repository
produces:
and is approved produces:
1. one or more tested source implementation-attempt commits, each with an
exact one-line subject using the pipeline's conditional `[ai] ` prefix,
@@ -123,12 +127,16 @@ New and unfinished tasks use the single adaptive `implement` path. Assessment
must first confirm that the request is one cohesive implementation/review/test
unit. If it contains independently useful and independently testable product
boundaries, record `Scope: split-required` and a concrete split proposal before
source edits, then stop for queue rescoping; do not force the broad request
through smaller implementation phases and call it one task. The independent
assessment has veto authority over implementation, not authority to create,
retire, or rewrite tasks. The performer validates and preserves the proposal;
the checkout scheduler owns any later queue mutation. A direct invocation
returns the proposal to the human.
source edits. The same result may arise later from the bounded convergence
assessment when the retained implementation proves that one review/evidence
campaign is not coherent. Do not force the broad request through smaller
implementation phases and call it one task. The independent assessment has
veto authority over further implementation, not authority to create, retire,
or rewrite tasks. The performer writes the split result, preserves any owned
implementation and source refs, and publishes it with
`finish --status split-required`. The checkout scheduler owns the later queue
mutation and implementation transfer. A direct invocation returns the
published proposal to the human.
For a cohesive task, use one mandatory general review, all five standard review
lenses, and a falsifiable evidence plan. On the initial implementation the

View File

@@ -350,6 +350,9 @@ independent assessor has veto authority because it is the first phase with the
exact implementation and evidence plan. It does not create, retire, supersede,
or rewrite queue tasks; the performer preserves the proposal and returns it to
the scheduler, which owns any rescope transaction.
The performer does not leave this as an unpublished `in-progress` marker: after
validating the proposal it writes the split result and calls
`finish --status split-required`, preserving any owned source state.
```text
You are a plan assessment agent. Review and refine an implementation plan.
@@ -908,6 +911,11 @@ blocking findings, stop with its unapproved artifacts; do not begin another
campaign automatically.
~~~
On `RESCOPE_REQUIRED`, the performer validates `split-proposal.md`, inventories
all retained owned source paths, writes the canonical split result, and calls
`finish --status split-required`. It does not clean or checkpoint source work;
the scheduler's later split worker assigns the sealed implementation carrier.
### Review fix
~~~text

View File

@@ -277,9 +277,12 @@ Run sequentially:
approach rejection with the assessor's simpler direction as added input —
or the terminal pre-edit `Scope: split-required` planning boundary.
This assessor has authority to veto source implementation because it has the
first exact implementation/evidence plan. It cannot mutate task state beyond
its local artifacts or create replacement tasks; the scheduler independently
validates and owns rescoping.
first exact implementation/evidence plan. It cannot create replacement tasks;
the performer publishes the proposal as the task's `split-required` result,
and the scheduler independently owns the deep split transaction. The same
result may be produced after source edits by the bounded convergence
assessment when the retained implementation exposes several independently
shippable/testable boundaries.
3. **Implement.** Run one leaf per assessed plan phase. Before each edit,
update `work/owned-paths.txt`. A leaf edits only its owned paths and its
phase status; it does not commit.
@@ -718,14 +721,22 @@ behavior. `Blocker-Type: test` additionally requires `work/test.md` to contain
Computer Use infrastructure-unavailable case. A `TEST_FLAW`, a run cap, or a
missing capture can never be the blocked verdict.
For `split-required`, do not clean, reset, stash, or checkpoint retained source
implementation. Require current task base/run refs, keep the owned diff and any
green commit in place, and inventory every owned path in `work/owned-paths.txt`.
The publication helper seals that work in `work/carried-work.json`; the later
split transaction rechecks the seal before assigning an implementation carrier.
Write `work/result.md` with exactly one value for every field:
```text
# Task result: <TASK_ID>
STATUS: DONE | BLOCKED
Outcome: changed | already-satisfied | blocked
Verdict: APPROVED | <specific blocker>
STATUS: DONE | BLOCKED | SPLIT_REQUIRED
Outcome: changed | already-satisfied | blocked | split-required
Verdict: APPROVED | SPLIT_REQUIRED | <specific blocker>
Blocker-Type: none | test | impl | unrecoverable
Implementation: retained | none
Split-Proposal: work/split-proposal.md | none
Attempts: <n>
Test-Runs: <n>
UI-Driver: overlay | hybrid | mixed | hybrid-unavailable | not-applicable
@@ -733,7 +744,7 @@ Touched: <source paths or none>
Test-Report: work/test.md
Evidence: <tracked evidence paths and what they prove>
Unverified: none | <exact behavior and manual follow-up>
Checkout: clean-buildable | unsafe
Checkout: clean-buildable | source-state-retained | unsafe
Discovered: none | present
## Discovered tasks
@@ -746,6 +757,14 @@ source commit, and direct evidence that the requested proposition held before
the task. A blocked result uses `Outcome: blocked` and retains a latest safe
implementation attempt when one exists.
A split result uses `STATUS: SPLIT_REQUIRED`, `Outcome: split-required`,
`Verdict: SPLIT_REQUIRED`, `Blocker-Type: none`, and
`Split-Proposal: work/split-proposal.md`. Set `Implementation: retained` and
name its paths under `Touched:` when owned source work exists; otherwise use
`Implementation: none` and `Touched: none`. It uses
`Checkout: source-state-retained` and does not require a test report, because
each replacement receives its own complete review and evidence campaign.
For approved project work, promote `work/project.proposed.md` to the project's
`project.md` immediately before final AI publication. For blocked work, retain
the proposal only as a task artifact.
@@ -755,7 +774,7 @@ Publish final AI state only after the Telegram commit and result are final:
```bash
python3 SOURCE_ROOT/.agents/skills/process-inbox/scripts/workspace.py \
finish --source-root SOURCE_ROOT --task TASK_ID \
--status approved|blocked --model MODEL_SHORT_NAME
--status approved|blocked|split-required --model MODEL_SHORT_NAME
```
`--model` is required and records which model finished the task, into the
@@ -768,14 +787,17 @@ resumed by a different model after an interruption records the model that
actually completed it. Never guess or copy the value from another task; if you
cannot tell what you are, say so and stop rather than recording a wrong name.
The helper verifies a clean source checkout, local task refs, current `HEAD`,
and either the retained implementation's exact three-line commit message or
the strict no-change state required by `already-satisfied`. It commits
all task-scoped local artifacts and final state as `Approve <TASK_ID>` or the
exceptional `Block <TASK_ID>`, fetches newer canonical state when configured,
For approval or Block, the helper verifies a clean source checkout, local task
refs, current `HEAD`, and either the retained implementation's exact three-line
commit message or the strict no-change state required by `already-satisfied`.
For split-required it verifies current refs, seals the owned working state, and
does not require a clean source checkout. It commits all task-scoped local
artifacts and final state as `Approve <TASK_ID>`, exceptional
`Block <TASK_ID>`, or `Split-required <TASK_ID>`, fetches newer canonical state when configured,
rebases the slot, publishes without force, and fast-forwards local AI master.
It deletes all local task refs after approval; after a block it deletes only
`RUN_REF` and retains implementation recovery refs for the next invocation.
`RUN_REF` and retains implementation recovery refs for the next invocation;
after split-required it retains all refs for the scheduler's carrier transfer.
Do not report final state until that AI commit reaches canonical master.
Preserve an unpublished final slot commit on a semantic conflict or remote
outage and hard-stop instead of pretending completion.
@@ -825,13 +847,13 @@ delays finishing the work actually in hand.
- `Scope: split-required` before source edits and `RESCOPE_REQUIRED` from a
convergence assessment are planning boundaries, not task `Block` verdicts
and not permission to keep retrying. Preserve `split-proposal.md`, all
validated source recovery, and the task's `in-progress` state; return the
proposal to the scheduler (or human for a direct invocation) and stop
automatic performance until the queue is deliberately replaced. Assessment
owns the stop; only the scheduler owns task creation, dependency rewrites,
and superseding the original. A resumed performer that sees the same
unresolved boundary reports it immediately without rerunning context,
and not permission to keep retrying. Preserve `split-proposal.md`, all owned
implementation and source recovery, write the split result, and publish
`finish --status split-required`; then return control to the scheduler (or
human for a direct invocation). Assessment owns the stop; only the scheduler
owns task creation, dependency rewrites, implementation-carrier transfer,
and retiring the original. A performer resolving an already published
split-required task reports it immediately without rerunning context,
implementation, review, or builds.
- Source lineage has a strict timing boundary. Before Phase 1, a missing
approved prerequisite is a clean pre-phase routing stop: do not publish

View File

@@ -63,6 +63,10 @@ paths. Follow `superseded_by` chains to their live task when deduplicating,
resolving prior receipt references, or checking whether a same-digest result
still exists. New dependencies and project links must name the final live task,
never an alias. A dated slug occupied by an alias still counts as a collision.
Some retired oversized tasks instead have `split.yaml`, whose `split_into`
list resolves to several live successors. Treat the retained directory as
history, deduplicate against all live successors, and never put the split id in
a new dependency or project link. A split path is also permanently occupied.
Use one disposable leaf planner when the harness supports delegation; instruct
it not to delegate. Otherwise perform the same work locally. The planner may
@@ -136,7 +140,10 @@ files, APIs, phases, ownership boundaries, and evidence design; it may veto the
single-task shape when that richer proof exposes independently shippable and
testable boundaries. That veto does not mean task sizing is based on elapsed
time or diff length, and it does not authorize the performer to mutate the
queue itself.
queue itself. The performer publishes `split-required`; the checkout scheduler
then launches a dedicated deep split transaction that creates replacements,
rewrites dependencies and project links, and retires the source with a durable
multi-target split record.
Project slugs are unique across `projects/` and `projects/archive/`. When a
request belongs to an archived project, restore it before routing to it:
@@ -224,11 +231,13 @@ claimed_at: null
claim_order: null
lease_until: null
phase: null
carried_from: null
inbox_receipt: receipts/YYYY/MM/DD/<receipt>.md
```
Do not write a `model` field. It records which model finished the task, so only
`finish` writes it, at the canonical `Approve` or `Block` boundary; a task
`finish` writes it, at the canonical `Approve`, `Block`, or `Split-required`
boundary; a task
carrying one before it is claimed is malformed.
Use a project slug instead of `null` when routed to a project. Use a YAML list
@@ -263,9 +272,9 @@ Create one tracked Markdown receipt under `receipts/YYYY/MM/DD/`. Include:
- deduplication decisions.
Before writing, search receipts for the same digest. If it was already fully
processed and every referenced task either has live state or has a durable alias
chain reaching live state, create nothing and reuse that receipt for
finalization.
processed and every referenced task either has live state, has a durable alias
chain reaching live state, or has a durable split record whose successors all
resolve to live state, create nothing and reuse that receipt for finalization.
## Validate and publish

File diff suppressed because it is too large Load Diff

View File

@@ -2898,6 +2898,226 @@ class MechanicsTest(unittest.TestCase):
self.assertTrue(result["golden_account_present"])
self.assertFalse(result["live_marker_present"])
def test_finish_publishes_split_required_with_carried_work(self):
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
source, slot, work, config = source_repo_with_task(root)
config["slot_worktree"] = str(slot)
for name in ("base", "run"):
git(
source,
"update-ref",
workspace.source_task_ref(TASK_ID, name),
"HEAD",
)
(work / "owned-paths.txt").write_text(
"tracked.txt\n", encoding="utf-8",
)
(work / "split-proposal.md").write_text(
"# Split proposal\n\nTwo independent boundaries.\n",
encoding="utf-8",
)
(work / "result.md").write_text(
"""STATUS: SPLIT_REQUIRED
Outcome: split-required
Verdict: SPLIT_REQUIRED
Implementation: retained
Touched: tracked.txt
Split-Proposal: work/split-proposal.md
Checkout: source-state-retained
""",
encoding="utf-8",
)
(source / "tracked.txt").write_text("carried\n", encoding="utf-8")
with (
mock.patch.object(
workspace,
"task_action_config",
return_value=(config, slot),
),
mock.patch.object(workspace, "commit_paths", return_value=True),
):
result = run_command(
workspace.command_finish,
task=TASK_ID,
status="split-required",
model="gpt-5.6-sol",
)
state = workspace.load_state(slot, work.parent / "state.yaml")
self.assertEqual(result["status"], "split-required")
self.assertEqual(state["status"], "split-required")
self.assertEqual(state["phase"], "split-required")
carried = json.loads((work / "carried-work.json").read_text(
encoding="utf-8-sig",
))
self.assertEqual(carried["implementation"], "retained")
self.assertEqual(carried["owned_dirty_paths"], ["tracked.txt"])
self.assertEqual((source / "tracked.txt").read_text(), "carried\n")
def test_carried_work_snapshot_seals_owned_submodule_changes(self):
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
source, slot, work, config = source_repo_with_task(root)
nested = source / "nested"
git_repo(nested)
(nested / "owned.txt").write_text("base\n", encoding="utf-8")
git(nested, "add", "owned.txt")
git(nested, "commit", "-m", "Create nested baseline")
nested_head = git(nested, "rev-parse", "HEAD")
git(
source,
"update-index",
"--add",
"--cacheinfo",
f"160000,{nested_head},nested",
)
git(source, "commit", "-m", "Track nested repository")
(work / "owned-paths.txt").write_text(
"nested/owned.txt\n", encoding="utf-8",
)
(nested / "owned.txt").write_text("carried\n", encoding="utf-8")
snapshot = workspace.source_worktree_snapshot(
config, slot, TASK_ID,
)
self.assertEqual(snapshot["owned_dirty_paths"], ["nested"])
self.assertEqual(snapshot["outside_owned_paths"], [])
self.assertNotEqual(snapshot["worktree_digest"], "0" * 64)
def test_split_publish_routes_and_starts_implementation_carrier(self):
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
config = inbox_worktrees(root)
main = Path(config["ai_main"])
slot = Path(config["slot_worktree"])
source = root / "source"
git_repo(source)
(source / "Telegram" / "build").mkdir(parents=True)
(source / "tracked.txt").write_text("base\n", encoding="utf-8")
git(source, "add", "-A")
git(source, "commit", "-m", "Create baseline")
config["source_root"] = str(source)
source_task = "2026/07/18/active-task"
source_dir = main / "tasks" / source_task
state_path = source_dir / "state.yaml"
state_path.write_text(
state_path.read_text(encoding="utf-8")
.replace("status: todo", "status: split-required")
.replace("claimed_by: null", "claimed_by: macbook-twork")
.replace("claimed_at: null", "claimed_at: 2026-07-18T10:00:00+04:00")
.replace("claim_order: null", "claim_order: 1")
.replace("phase: null", "phase: split-required")
.replace(
"inbox_receipt:",
"model: gpt-5.6-sol\ninbox_receipt:",
),
encoding="utf-8",
)
work = source_dir / "work"
work.mkdir()
(work / "owned-paths.txt").write_text(
"tracked.txt\n", encoding="utf-8",
)
for name in ("base", "run"):
git(
source,
"update-ref",
workspace.source_task_ref(source_task, name),
"HEAD",
)
(source / "tracked.txt").write_text("carried\n", encoding="utf-8")
snapshot = workspace.source_worktree_snapshot(
config, main, source_task,
)
(work / "carried-work.json").write_text(
json.dumps({"implementation": "retained", **snapshot}) + "\n",
encoding="utf-8",
)
git(main, "add", f"tasks/{source_task}")
git(main, "commit", "-m", f"Split-required {source_task}")
git(slot, "merge", "--ff-only", "master")
replacements = [
"2026/07/20/adopt-active-task-implementation",
"2026/07/20/finish-active-task-integration",
]
receipt = "receipts/2026/07/20/split-active-task.md"
for task_id in replacements:
directory = slot / "tasks" / task_id
directory.mkdir(parents=True)
(directory / "task.md").write_text(
f"# {task_id.rsplit('/', 1)[-1]}\n",
encoding="utf-8",
)
(directory / "state.yaml").write_text(
f"""status: todo
type: implement
created: 2026-07-20
project: null
depends_on: []
claimed_by: null
claimed_at: null
claim_order: null
lease_until: null
phase: null
inbox_receipt: {receipt}
""",
encoding="utf-8",
)
receipt_path = slot / receipt
receipt_path.parent.mkdir(parents=True)
receipt_path.write_text(
"\n".join([source_task, *replacements]) + "\n",
encoding="utf-8",
)
with mock.patch.object(
workspace, "worktree_config", return_value=config,
):
result = run_command(
workspace.command_split_publish,
source_task=source_task,
replacements=replacements,
receipt=receipt,
implementation_carrier=replacements[0],
paths=[
f"tasks/{source_task}",
*(f"tasks/{task_id}" for task_id in replacements),
receipt,
],
)
self.assertEqual(result["status"], "split")
self.assertTrue((main / "tasks" / source_task / "split.yaml").is_file())
self.assertFalse((main / "tasks" / source_task / "state.yaml").exists())
resolved = workspace.resolve_task(
main, workspace.load_states(main), source_task,
)
self.assertEqual(resolved["status"], "split")
self.assertEqual(resolved["split_into"], replacements)
carrier = workspace.load_states(main)[replacements[0]]
self.assertEqual(carrier["claimed_by"], "macbook-twork")
self.assertEqual(carrier["carried_from"], source_task)
self.assertEqual((source / "tracked.txt").read_text(), "carried\n")
with mock.patch.object(
workspace, "worktree_config", return_value=config,
):
started = run_command(
workspace.command_start,
task=replacements[0],
require=[],
)
self.assertEqual(started["status"], "in-progress")
self.assertIsNotNone(workspace.resolved_ref(
source,
workspace.source_task_ref(replacements[0], "base"),
))
self.assertIsNone(workspace.resolved_ref(
source,
workspace.source_task_ref(source_task, "base"),
))
if __name__ == "__main__":
unittest.main()

View File

@@ -13,10 +13,10 @@ This file adapts harness mechanics and removes unnecessary text normalization.
prompt containing exact repository, task, artifact, and input paths. Do not
rely on the parent conversation being inherited.
- Tell every disposable phase leaf Agent not to delegate and never to commit.
Publication-owning orchestrators — `process-inbox`, `perform-task`, discovered
routing, and pending-task consolidation — follow the shared workflow's exact
helper, commit, and publication contract instead. Preserve its single-writer
and one-stateful-performer constraints.
Publication-owning orchestrators — `process-inbox`, `perform-task`, split
routing, discovered routing, and pending-task consolidation — follow the
shared workflow's exact helper, commit, and publication contract instead.
Preserve its single-writer and one-stateful-performer constraints.
- Every phase leaf and the performer inherit the parent model, as the shared
workflow says. Do not pass a model override on the Agent call: its family
aliases already resolve to the newest permitted model of that family, so an
@@ -49,9 +49,9 @@ This file adapts harness mechanics and removes unnecessary text normalization.
- If the first real leaf Agent is rejected before work begins because nested
delegation is unavailable, use the shared same-session fallback. Do not
treat mere presence of the Agent tool as a successful delegation probe.
- Whenever an Agent is asked to run `process-inbox`, `perform-task`,
discovered-task routing, or pending-task consolidation — the orchestrating
roles — explicitly tell it to read this adapter completely before the
- Whenever an Agent is asked to run `process-inbox`, `perform-task`, split-task
routing, discovered-task routing, or pending-task consolidation — the
orchestrating roles — explicitly tell it to read this adapter completely before the
applicable shared skill or reference. Do NOT tell leaf phase agents to read
this adapter: their phase prompts are self-contained and already carry the
leaf rules (no delegation, no commits, progress and reply contracts); an

View File

@@ -19,6 +19,14 @@ follow-up requires its affirmative independence test. Continue until that
frozen-and-derived batch reaches the scheduler's normal stop condition or a
global hard stop.
Apply the shared `split-required` result and routing phase. A performer publishes
that state; this parent launches one fresh foreground split Agent that reads the
complete shared split reference, creates and publishes the replacements, and
retires the source task. Replace the source id in the frozen batch with those
replacements. When retained implementation exists, start the checkout-owned
carrier first so the helper transfers its sealed source state; do not reset or
checkpoint that work.
Apply the shared source-lineage gate in this parent scheduler. Before batch
freeze, a missing approved prerequisite pauses through `AskUserQuestion` after
the exact branch and compatible-branch report. After freeze but before Phase 1,
@@ -35,10 +43,10 @@ scheduler context, and require it to read the complete shared consolidation
reference. Never run consolidation inside the performer or discovery-routing
Agent, and defer it while the active task owns dirty local phase state.
Every inbox worker, performer, discovered-task routing Agent, and pending-task
consolidation Agent must be told in its initial prompt to read
`.claude/ai-workflow-adapter.md` completely before the applicable shared skill
or reference. Use the Agent tool for those workers; do not start Claude
Every inbox worker, performer, split-routing Agent, discovered-task routing
Agent, and pending-task consolidation Agent must be told in its initial prompt
to read `.claude/ai-workflow-adapter.md` completely before the applicable shared
skill or reference. Use the Agent tool for those workers; do not start Claude
subprocesses through Bash.
Arguments, when present, are natural-language hints for new shared work; their

View File

@@ -11,6 +11,9 @@ the Claude adapter's delegation and text-handling substitutions. Resolve, start
or resume, implement, verify, and publish only the named task. Do not
continue with other queue work, route discoveries, or consolidate pending tasks
afterward; those post-result phases belong to the `continue` scheduler.
If assessment or review convergence proves the task intrinsically broad,
publish the shared `split-required` result with retained source state and stop;
do not create its replacement tasks in this command.
Apply the shared pipeline's conditional `[ai] ` commit-subject rule exactly;
decide it per commit, require it only when all changes and the purpose are

2
.gitignore vendored
View File

@@ -45,6 +45,8 @@ stage
/snap/gui/*.png
/snap/gui/*.desktop
/snap/plugins/__pycache__
__pycache__/
*.pyc
/Telegram/*.user.*
*.txt.user

View File

@@ -32,9 +32,9 @@ resumability, and AI publication. This file adapts harness mechanics.
consolidation, and phase leaves. Do not restrict `capability_mode`;
those workers need shell plus writes.
- Tell every disposable phase leaf not to delegate and never to commit.
Publication-owning orchestrators — `process-inbox`, `perform-task`,
discovered routing, and pending-task consolidation — follow the shared
workflow's exact helper, commit, and publication contract. Preserve its
Publication-owning orchestrators — `process-inbox`, `perform-task`, split
routing, discovered routing, and pending-task consolidation — follow the
shared workflow's exact helper, commit, and publication contract. Preserve its
single-writer and one-stateful-performer constraints.
- When a blocking call returns without its required artifact, retry that
disposable worker once in a fresh `spawn_subagent` with more specific
@@ -57,8 +57,8 @@ nested-delegation probe.
This session is the scheduler only. Do not plan or implement Telegram
changes here.
Spawn each inbox worker, performer, discovery-routing worker, and
pending-task consolidation worker as a blocking `spawn_subagent`. Tell
Spawn each inbox worker, performer, split-routing worker, discovery-routing
worker, and pending-task consolidation worker as a blocking `spawn_subagent`. Tell
every one of those workers, in its initial prompt, to read this adapter
completely before the applicable shared skill or reference.
@@ -133,8 +133,9 @@ invocation resume it.
The only scheduler state that is not already on disk is the frozen batch
(`invocation_mode`, `initial_batch_task_ids`, `batch_task_ids`,
`discovered_task_ids`, `attempted_blocked`, consolidation records). After
a compact, recover those lists from the compact summary and the last
`discovered_task_ids`, `attempted_blocked`, consolidation records, and ordered
split records with carrier ids). After a compact, recover those lists from the
compact summary and the last
scheduler notes. Do not take a fresh queue snapshot and freeze a new
batch. A later queue id that was never in this invocation's batch stays
out, exactly as the shared skill says.

View File

@@ -21,6 +21,14 @@ until that frozen-and-derived batch reaches the scheduler's normal stop
condition or a global hard stop. Do not stop on your own because a compact
is approaching.
Apply the shared `split-required` result and routing phase. A performer publishes
that state; this parent launches one fresh blocking split worker that reads the
complete shared split reference, creates and publishes the replacements, and
retires the source task. Replace the source id in the frozen batch with those
replacements. When retained implementation exists, start the checkout-owned
carrier first so the helper transfers its sealed source state; do not reset or
checkpoint that work.
Apply the shared skill's pending-task consolidation phase too. At each
eligible clean AI-slot boundary, recover an older `pending_consolidations`
marker or consolidate newly routed tasks in one fresh blocking
@@ -30,9 +38,9 @@ the complete shared consolidation reference. Never run consolidation inside
the performer or discovery-routing worker, and defer it while the active
task owns dirty local phase state.
Every inbox worker, performer, discovered-task routing worker, and
pending-task consolidation worker must be told in its initial prompt to
read `.grok/ai-workflow-adapter.md` completely before the applicable shared
Every inbox worker, performer, split-routing worker, discovered-task routing
worker, and pending-task consolidation worker must be told in its initial prompt
to read `.grok/ai-workflow-adapter.md` completely before the applicable shared
skill or reference. Use `spawn_subagent` with `background: false` for those
workers. A continue-spawned performer is at depth 1 and must run every
phase leaf as a same-session checklist. Do not start Grok subprocesses

View File

@@ -12,6 +12,9 @@ substitutions. Resolve, start or resume, implement, verify, and publish only
the named task. Do not continue with other queue work, route discoveries, or
consolidate pending tasks afterward; those post-result phases belong to the
`continue` scheduler.
If assessment or review convergence proves the task intrinsically broad,
publish the shared `split-required` result with retained source state and stop;
do not create its replacement tasks in this command.
Apply the shared pipeline's conditional `[ai] ` commit-subject rule exactly;
decide it per commit, require it only when all changes and the purpose are