177b5596b5
Co-authored-by: nova28 <17953305+nova28@users.noreply.github.com> Co-authored-by: Carlos Florencio <carlosmflorencio@gmail.com>
336 lines
20 KiB
Markdown
336 lines
20 KiB
Markdown
---
|
|
status: draft
|
|
system: tasks
|
|
created: 2026-09-01
|
|
owners:
|
|
- kandev
|
|
---
|
|
|
|
# Task plan append-mode write Requirements
|
|
|
|
## Overview
|
|
|
|
`update_task_plan_kandev` replaces the whole plan document. There is no partial write at
|
|
any layer, so a caller adding a section must read the whole plan and send it back with
|
|
the addition attached. One write therefore costs the size of the entire plan, and a run
|
|
costs the square of the number of writes in it.
|
|
|
|
The plan is also a task's only durable memory across a context reset, so it grows
|
|
monotonically for as long as the task runs. On this instance the largest stored revision
|
|
is 158,393 characters, so the two effects compound in production, not in theory.
|
|
|
|
This capability adds a second write mode in which the caller submits only the fragment
|
|
it wants to add and the system composes the stored document. It changes what a caller
|
|
must send and what the system must guarantee about composing it, not what a plan is, how
|
|
revisions are numbered, or how the browser edits one.
|
|
|
|
The `tasks` system owns this capability because the durable artifact is the plan and its
|
|
revision history, not the MCP surface exposing the write. The missing-task boundary
|
|
stays owned by `REQ-TASKS-DOCUMENTS-001`.
|
|
|
|
Write serialization and truncation reporting belong to `plan-write-consistency`; the
|
|
stored-content size limit to `plan-content-size-limit`, both in this canon. This
|
|
capability composes inside the per-task write lock the first established, and
|
|
`REQ-TASKS-PLAN-APPEND-007` measures an append against the second.
|
|
`REQ-TASKS-PLAN-APPEND-003` still states in full what append requires of serialization,
|
|
so this contract stays readable and testable on its own.
|
|
|
|
## Prior art
|
|
|
|
Recorded in full, with both search receipts, in the system design under [Prior
|
|
art](../system-design/plan-write-append-mode.md#prior-art).
|
|
|
|
## Terminology
|
|
|
|
- **Plan write:** an operation that upserts the task plan HEAD row and writes or merges
|
|
a revision.
|
|
- **HEAD:** the single `task_plans` row for a task, holding the current content.
|
|
- **Mode:** the requested composition behavior of an agent plan update, either `replace`
|
|
or `append`.
|
|
- **Fragment:** the `content` a caller submits in `append` mode; an addition to the
|
|
stored document, not the whole of it.
|
|
- **Composed content:** the content a plan write commits in `append` mode, formed from
|
|
the stored content and the fragment.
|
|
- **Stored content:** the plan content in effect for that task at the instant the
|
|
composing write reads it inside the serialized write path.
|
|
- **Agent write path:** the MCP tools `create_task_plan_kandev` and
|
|
`update_task_plan_kandev`.
|
|
- **Browser write path:** the WebSocket actions handled by `wsCreateTaskPlan` and
|
|
`wsUpdateTaskPlan`.
|
|
- **Truncation guard:** the existing check reporting a write that retains less than half
|
|
the characters of the content it replaces.
|
|
- **Whitespace:** a character whose Unicode `White_Space` property is true. This set is
|
|
normative wherever this document says "whitespace"; it includes U+0085 NEL and U+00A0
|
|
no-break space, so a hand-rolled `" \t\r\n"` test does not satisfy this contract.
|
|
- **Whitespace-only line:** a line whose characters are all whitespace, excluding the
|
|
line terminator.
|
|
- **Size limit:** the maximum stored plan content a single write may commit, owned by
|
|
`plan-content-size-limit` and unchanged here.
|
|
|
|
## Requirements
|
|
|
|
### REQ-TASKS-PLAN-APPEND-001: A caller selects the write mode explicitly and an
|
|
unrecognized mode changes nothing
|
|
|
|
**Intent:** The default must stay what every existing caller relies on, and an
|
|
unrecognized value must not be interpreted: treating it as `replace` would let one typo
|
|
overwrite a plan with a fragment, the destructive outcome this capability exists to
|
|
remove.
|
|
|
|
#### Acceptance criteria
|
|
|
|
- **AC-TASKS-PLAN-APPEND-001.1:** When an agent plan update omits the mode or supplies
|
|
an empty mode, the system shall treat the write as `replace`.
|
|
- **AC-TASKS-PLAN-APPEND-001.2:** When an agent plan update supplies the mode `replace`
|
|
or `append`, the system shall perform that mode.
|
|
- **AC-TASKS-PLAN-APPEND-001.3:** When an agent plan update supplies any other mode
|
|
value, including a value differing only in letter case such as `Append` or `APPEND`,
|
|
the system shall reject the request as a validation error, name the two accepted
|
|
values in the error, leave the stored plan content, title and revision history
|
|
unchanged, and create no revision.
|
|
- **AC-TASKS-PLAN-APPEND-001.4:** When an agent plan update supplies no content, or
|
|
supplies content that is empty, the system shall reject the request as a validation
|
|
error in either mode and create no revision.
|
|
- **AC-TASKS-PLAN-APPEND-001.5:** When an agent plan update in `append` mode supplies a
|
|
fragment consisting only of whitespace as this document defines it, the system shall
|
|
reject the request as a validation error and create no revision, because such a
|
|
fragment can only add separator noise to a document from which it cannot be removed.
|
|
- **AC-TASKS-PLAN-APPEND-001.6:** When an agent plan update in `append` mode targets a
|
|
task that has no plan, the system shall reject the request with the same
|
|
plan-not-found outcome that `replace` produces for that task, and shall not create a
|
|
plan.
|
|
- **AC-TASKS-PLAN-APPEND-001.7:** When one request violates more than one criterion, the
|
|
system shall report exactly one failure, selected in this order: mode validity
|
|
(AC-TASKS-PLAN-APPEND-001.3); task-reach authorization (AC-TASKS-PLAN-APPEND-005.6);
|
|
content validity (AC-TASKS-PLAN-APPEND-001.4, AC-TASKS-PLAN-APPEND-001.5); plan
|
|
existence (AC-TASKS-PLAN-APPEND-001.6); failure to read the stored content
|
|
(AC-TASKS-PLAN-APPEND-003.5); the size limit (AC-TASKS-PLAN-APPEND-007.2). In every
|
|
case the stored plan content, title and revision history are unchanged and no revision
|
|
is created. Missing-task is not in this order; it stays owned by
|
|
`REQ-TASKS-DOCUMENTS-001`.
|
|
|
|
### REQ-TASKS-PLAN-APPEND-002: An append composes the stored document with a separator
|
|
that cannot join two lines
|
|
|
|
**Intent:** Downstream consumers count headings. A fragment beginning with a heading
|
|
appended onto the last line of the stored content yields a line that is no longer a
|
|
heading, so any consumer counting `##` headings silently reads one fewer. Stated over
|
|
headings generally, not one consumer's heading text, so the rule outlives any counter
|
|
built on it. The separator is contract, not implementation.
|
|
|
|
#### Acceptance criteria
|
|
|
|
- **AC-TASKS-PLAN-APPEND-002.1:** When a plan write in `append` mode commits, the
|
|
composed content shall consist of the stored content, then a separator, then the
|
|
fragment, in that order. The only characters the system may add are the separator
|
|
itself; the only ones it may remove are the edge whitespace that
|
|
AC-TASKS-PLAN-APPEND-002.2, AC-TASKS-PLAN-APPEND-002.3 and AC-TASKS-PLAN-APPEND-002.5
|
|
each require it to remove. Beyond those it shall perform no insertion, reordering or
|
|
removal, and shall not examine or alter any interior character of either body. This
|
|
criterion frames its siblings rather than contradicting them.
|
|
- **AC-TASKS-PLAN-APPEND-002.2:** When both the stored content and the fragment contain
|
|
a non-whitespace character, the composed content shall contain exactly one empty line
|
|
between the last non-empty line of the stored content and the first non-empty line of
|
|
the fragment, regardless of how many line endings the stored content ends with or the
|
|
fragment begins with. Leading spaces and tabs on the fragment's first non-empty line
|
|
shall be preserved.
|
|
- **AC-TASKS-PLAN-APPEND-002.3:** When the stored content is empty or contains only
|
|
whitespace, the composed content shall be the fragment with its leading empty and
|
|
whitespace-only lines removed, shall preserve leading spaces and tabs on the
|
|
fragment's first non-empty line, and shall not begin with a blank line.
|
|
- **AC-TASKS-PLAN-APPEND-002.4:** When a plan write in `append` mode commits, the
|
|
composed content shall preserve the fragment's trailing characters as submitted, and
|
|
the system shall neither add nor remove a trailing line ending.
|
|
- **AC-TASKS-PLAN-APPEND-002.5:** When a plan write in `append` mode commits, the
|
|
composed content shall preserve every character of the stored content that precedes
|
|
its trailing whitespace. Trailing whitespace on the stored content's final line may be
|
|
dropped.
|
|
- **AC-TASKS-PLAN-APPEND-002.6:** When a plan write in `append` mode supplies a title,
|
|
the system shall apply it exactly as `replace` does; when it supplies none, the system
|
|
shall leave the stored title unchanged.
|
|
|
|
### REQ-TASKS-PLAN-APPEND-003: An append reads and commits against the same state, and
|
|
never commits a fragment alone
|
|
|
|
**Intent:** An append is a read-modify-write. If the read runs outside the serialization
|
|
that orders plan writes, two concurrent appends read the same stored content and the
|
|
second commit overwrites the first, losing a section while both callers are told the
|
|
write succeeded. The replace-only API prevented that by construction; it must not return
|
|
one layer down, invisible.
|
|
|
|
#### Acceptance criteria
|
|
|
|
- **AC-TASKS-PLAN-APPEND-003.1:** When a plan write in `append` mode commits, the stored
|
|
content it composed from shall be the plan content in effect for that task at the
|
|
instant that same write commits, not content read before another write to the same
|
|
task committed.
|
|
- **AC-TASKS-PLAN-APPEND-003.2:** When two plan writes in `append` mode for the same
|
|
task run concurrently and both report success, the resulting plan content shall
|
|
contain both fragments in full.
|
|
- **AC-TASKS-PLAN-APPEND-003.3:** When plan writes for the same task run concurrently,
|
|
the resulting plan content shall equal the result of performing those writes one after
|
|
another in some order. The system shall not guarantee which order, and shall not
|
|
guarantee that the order matches the order in which the callers submitted them.
|
|
- **AC-TASKS-PLAN-APPEND-003.4:** When a plan write in `append` mode runs concurrently
|
|
with one in `replace` mode for the same task, the system shall apply
|
|
AC-TASKS-PLAN-APPEND-003.3 to that pair. A replace committing between an append's read
|
|
and that append's commit would otherwise be discarded entirely, leaving content
|
|
matching no serial order. Serializing appends against each other alone does not
|
|
satisfy this criterion.
|
|
- **AC-TASKS-PLAN-APPEND-003.5:** When the stored content for an `append` cannot be
|
|
read, the system shall fail the write, create no revision, leave the stored plan
|
|
unchanged rather than committing the fragment as the whole document, and report the
|
|
failure distinguishably from AC-TASKS-PLAN-APPEND-001.6's plan-not-found outcome,
|
|
exposing no storage detail.
|
|
- **AC-TASKS-PLAN-APPEND-003.6:** When the same `append` request is submitted more than
|
|
once, the system shall append the fragment once per accepted request. An append is not
|
|
idempotent and repeated fragments shall not be deduplicated.
|
|
|
|
### REQ-TASKS-PLAN-APPEND-004: The truncation guard does not judge an append
|
|
|
|
**Intent:** The guard catches a replace that dropped content. An append cannot drop
|
|
content, since the composition contains the stored content in full, so running the guard
|
|
there yields only a false warning and a needless revision split, teaching an agent to
|
|
distrust an otherwise reliable warning.
|
|
|
|
#### Acceptance criteria
|
|
|
|
- **AC-TASKS-PLAN-APPEND-004.1:** When a plan write in `append` mode commits, the system
|
|
shall emit no truncation warning, whatever the ratio between the fragment's length and
|
|
the stored content's length.
|
|
- **AC-TASKS-PLAN-APPEND-004.2:** When a plan write in `append` mode commits, the system
|
|
shall not force a new revision on the truncation guard's account, and shall leave the
|
|
write eligible for the existing coalescing rules.
|
|
- **AC-TASKS-PLAN-APPEND-004.3:** When a plan write in `append` mode commits, the
|
|
response shall carry no truncation warning field and no prior revision number field,
|
|
matching a non-truncating write's shape today.
|
|
|
|
### REQ-TASKS-PLAN-APPEND-005: Replace-mode behavior and every other plan surface are
|
|
unchanged
|
|
|
|
**Intent:** This capability adds a mode. A regression in the default would cost more
|
|
than the change saves, and surfaces never asked to change must not drift.
|
|
|
|
#### Acceptance criteria
|
|
|
|
- **AC-TASKS-PLAN-APPEND-005.1:** When a plan write runs in `replace` mode, the stored
|
|
content, stored title, revision numbering, revision coalescing, truncation detection,
|
|
forced-revision behavior, emitted events and response shape shall be identical to
|
|
those produced before this capability existed. The **wording** of the truncation
|
|
warning is excluded from this freeze and owned by REQ-TASKS-PLAN-APPEND-006; its
|
|
trigger condition, the counts it reports and the response field carrying it are frozen
|
|
here.
|
|
- **AC-TASKS-PLAN-APPEND-005.2:** When a caller supplies a non-empty mode to
|
|
`create_task_plan_kandev`, the system shall **reject** the request as a validation
|
|
error naming `update_task_plan_kandev` as the tool that offers `append`, store
|
|
nothing, and leave stored content, title and revision history unchanged. It shall not
|
|
ignore the value, which would commit the fragment as the entire plan. An absent or
|
|
empty mode is treated as absent, leaving that tool's behavior unchanged, consistent
|
|
with AC-TASKS-PLAN-APPEND-001.1.
|
|
- **AC-TASKS-PLAN-APPEND-005.3:** The browser write path shall have no mode field in its
|
|
payload contract and shall **ignore** an unrecognized field rather than reject it —
|
|
that path's existing behavior for any unknown field, and therefore what "unchanged"
|
|
means for it. It never sends a mode, so it takes the `replace` default. The asymmetry
|
|
with AC-TASKS-PLAN-APPEND-005.2 is deliberate.
|
|
- **AC-TASKS-PLAN-APPEND-005.4:** When a plan write in `append` mode commits, the system
|
|
shall emit the same events, in the same order, that a `replace` write committing the
|
|
same resulting content emits.
|
|
- **AC-TASKS-PLAN-APPEND-005.5:** When a plan write in `append` mode commits, it shall
|
|
create or coalesce exactly one revision as a `replace` write does, and the stored
|
|
revision content shall be the composed content, not the fragment.
|
|
- **AC-TASKS-PLAN-APPEND-005.6:** When a plan write in `append` mode runs, the system
|
|
shall apply the same task-reach authorization `replace` applies, and shall not read
|
|
the stored content of a task the caller cannot reach.
|
|
|
|
### REQ-TASKS-PLAN-APPEND-006: Every agent-facing text describing plan writes admits
|
|
that append exists
|
|
|
|
**Intent:** A caller decides between the two modes from the text the system shows it.
|
|
Several texts today assert there is no append mode, spread across both plan tools'
|
|
descriptions and `content` parameters and the truncation warning shown after a write
|
|
destroyed content. Left alone they argue against the mode; the warning does so at the
|
|
worst moment, steering a caller who just lost content away from the remedy, and the
|
|
create tool while prescribing the read-then-resend pattern this capability removes.
|
|
Softened too far, they drop the warning that keeps the replace path safe. Enumerating
|
|
these texts has twice missed one, so AC-TASKS-PLAN-APPEND-006.9 binds the class instead
|
|
of a list.
|
|
|
|
#### Acceptance criteria
|
|
|
|
- **AC-TASKS-PLAN-APPEND-006.1:** The `update_task_plan_kandev` description shall name
|
|
both modes, state which is the default, and state that `replace` submits the whole
|
|
document while `append` submits only an addition.
|
|
- **AC-TASKS-PLAN-APPEND-006.2:** The `update_task_plan_kandev` description shall
|
|
retain, scoped to `replace`, the warning that the write overwrites the entire document
|
|
and that sending only a section deletes the rest.
|
|
- **AC-TASKS-PLAN-APPEND-006.3:** The `update_task_plan_kandev` description shall state
|
|
that `append` places a blank line between the stored content and the fragment.
|
|
- **AC-TASKS-PLAN-APPEND-006.4:** The `update_task_plan_kandev` description shall state
|
|
that an append is not idempotent and that resubmitting it adds the fragment again.
|
|
- **AC-TASKS-PLAN-APPEND-006.5:** The `update_task_plan_kandev` description shall state
|
|
that `append` does not require the caller to read the plan first.
|
|
- **AC-TASKS-PLAN-APPEND-006.6:** When a plan write in `append` mode succeeds, the
|
|
acknowledgement returned to the caller shall report the size of the stored document
|
|
after the write rather than the size of the submitted fragment.
|
|
- **AC-TASKS-PLAN-APPEND-006.7:** The truncation warning shall not state or imply that
|
|
no partial update or append mode exists.
|
|
- **AC-TASKS-PLAN-APPEND-006.8:** The truncation warning shall name `append` as the way
|
|
to add a section without resubmitting the whole document, while continuing to state
|
|
where the pre-write content lives, that the MCP plan tools cannot fetch it, and that
|
|
the caller must not rewrite the plan from memory.
|
|
- **AC-TASKS-PLAN-APPEND-006.9:** No agent-facing text describing a plan write shall
|
|
state or imply that no partial update or append mode exists, or direct a caller who
|
|
wants to add a section to read the plan and resubmit it. This binds every tool and
|
|
parameter description on the agent write path, not only those above. On
|
|
`create_task_plan_kandev` each shall still state that **this** tool replaces the whole
|
|
plan and name `update_task_plan_kandev` in `append` mode as the way to add a section,
|
|
and its schema MAY declare a `mode` parameter that only rejects, keeping
|
|
AC-TASKS-PLAN-APPEND-005.2's message reaching the caller.
|
|
|
|
### REQ-TASKS-PLAN-APPEND-007: An append is measured against the document it will store
|
|
|
|
**Intent:** The size limit measures what the caller submitted. In `append` the caller
|
|
submits a fragment but the system stores the composition, so measuring the submission
|
|
would let a small fragment commit an oversized document, defeating an existing limit.
|
|
|
|
#### Acceptance criteria
|
|
|
|
- **AC-TASKS-PLAN-APPEND-007.1:** When a plan write in `append` mode is admitted, the
|
|
system shall evaluate the size limit against the composed content, not against the
|
|
submitted fragment.
|
|
- **AC-TASKS-PLAN-APPEND-007.2:** When the composed content would exceed the size limit,
|
|
the system shall reject the write, store nothing, create no revision, and leave the
|
|
stored plan content and title unchanged. Composed content whose size is exactly the
|
|
limit shall be accepted, matching the existing check's boundary.
|
|
- **AC-TASKS-PLAN-APPEND-007.3:** When the system rejects an append for exceeding the
|
|
size limit, the size it reports shall be that of the composed content, so a caller is
|
|
not told a fragment far below the limit exceeded it.
|
|
- **AC-TASKS-PLAN-APPEND-007.4:** When the system evaluates the size limit for an
|
|
`append`, it shall do so against the same stored content that write composed from,
|
|
inside the region that serializes plan writes for the task, so the value admitted is
|
|
the value committed.
|
|
- **AC-TASKS-PLAN-APPEND-007.5:** When a plan write runs in `replace` mode, the size
|
|
limit's value, the point at which it is evaluated and the error it produces shall be
|
|
unchanged.
|
|
|
|
## Out of scope
|
|
|
|
- **The plan size limit itself.** Its value and error are owned by
|
|
`plan-content-size-limit`. REQ-TASKS-PLAN-APPEND-007 fixes only *which* content an
|
|
append is measured against; nothing here raises, lowers or removes the ceiling, nor
|
|
bounds growth below it.
|
|
- **Revision compaction and server-side collapse.** An append creates or coalesces a
|
|
revision as any other write does; none is merged, pruned or rewritten.
|
|
- **Automatic collapse of spent detail.** Collapsing stays a deliberate
|
|
read-then-replace by the caller: per `concepts/agents-md-pattern.md`, automating the
|
|
full rewrite is what erodes detail, so only accumulation becomes cheap.
|
|
- **A section-patch or replace-a-named-heading mode.** Only whole-document replace and
|
|
end-of-document append are defined.
|
|
- **An idempotency key for appends.** AC-TASKS-PLAN-APPEND-003.6 states the
|
|
non-idempotence rather than removing it; deduplication needs a caller-supplied key and
|
|
a store for it — a separate contract.
|
|
- **A mode on `create_task_plan_kandev` or on the browser write path.** Fixed by
|
|
AC-TASKS-PLAN-APPEND-005.2 and AC-TASKS-PLAN-APPEND-005.3.
|
|
- **Reading a past revision through MCP.** Unchanged: no plan tool returns one.
|
|
- **Prepend, or insertion at any position other than the end.**
|