Skip to content

Commit 12ff380

Browse files
MaxLinCodeclaude
andcommitted
Update planning-request-flow doc to reflect PendingWriteOperation model
Replace WriteContract/committedSlots/resolved_slots/intentKind references with operationKind, resolvedFields, resolvedTargetRef, workflowChanged, and pending_write_operation throughout steps B, C, D, Phase 5, seams, state machine, and code map. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent b7a3bf3 commit 12ff380

1 file changed

Lines changed: 48 additions & 39 deletions

File tree

docs/architecture/planning-request-flow.md

Lines changed: 48 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -151,29 +151,30 @@ a user says something like "yes but make it 3pm".
151151
Slot extraction runs **only** for `SLOT_COMMITTING_TURN_TYPES`:
152152
`planning_request`, `edit_request`, `clarification_answer`.
153153

154-
Before calling the LLM, `derivePendingSlots()` computes which required slots
155-
are still missing from the current `WriteContract`:
154+
Before calling the LLM, `requiredSlotsForOperation()` computes which schedule
155+
fields are still missing given the current `operationKind`:
156156

157-
- **`DEFAULT_WRITE_CONTRACT`** (plan intent): requires `["day", "time"]`
158-
- **`EDIT_CONTRACT`** (edit intent): requires `["time"]`
157+
- **`plan`**: requires `["day", "time"]`
158+
- **`edit` / `reschedule`**: requires `["time"]`
159+
- **`complete` / `archive`**: requires `[]`
159160

160-
`resolveWriteContract()` picks the active contract:
161-
- `planning_request`always `DEFAULT_WRITE_CONTRACT`
162-
- `edit_request`always `EDIT_CONTRACT`
163-
- everything else → inherits `priorContract` from discourse state
161+
`resolveOperationKind()` (`packages/core`) derives the active operation:
162+
- `planning_request``plan`
163+
- `edit_request``edit`
164+
- everything else → inherits `priorOperationKind` from `pending_write_operation`
164165

165-
**Slot types** (`ResolvedSlots`):
166+
**Schedule slot types** (grouped under `scheduleFields` in `ResolvedFields`):
166167
```ts
167168
{
168-
day?: string; // e.g. "monday", "tomorrow"
169-
time?: TimeSpec; // absolute {hour,minute}, relative {minutes}, or window {morning|afternoon|evening}
170-
duration?: number; // minutes
171-
target?: string; // entity ID of the task/block being referenced
169+
day?: string; // e.g. "monday", "tomorrow"
170+
time?: TimeSpec; // absolute {hour,minute}, relative {minutes}, or window {morning|afternoon|evening}
171+
duration?: number; // minutes
172172
}
173173
```
174174

175-
The slot normalizer (`packages/core/src/slot-normalizer.ts`) validates ranges
176-
and converts raw LLM output into typed `ResolvedSlots`.
175+
Target entity resolution happens via `classification.resolvedEntityIds`, not
176+
slot extraction. The slot normalizer (`packages/core/src/slot-normalizer.ts`)
177+
validates ranges and converts raw LLM output into typed schedule values.
177178

178179
**Output:** `{ extractedValues, confidence, unresolvable }`
179180
- `confidence` is a per-slot score (0–1)
@@ -184,29 +185,36 @@ and converts raw LLM output into typed `ResolvedSlots`.
184185
**File:** `packages/core/src/commit-policy.ts``applyCommitPolicy()`
185186

186187
The commit policy is the gate between "LLM extracted something" and "the
187-
system will act on it". Each extracted slot is individually evaluated:
188+
system will act on it". Each extracted schedule field is individually evaluated:
188189

189190
| Condition | Result |
190191
|-----------|--------|
191-
| Slot is in `unresolvable` |`needsClarification` |
192-
| `confidence[slot] < 0.75` |`needsClarification` |
193-
| Slot corrects a prior value AND `confidence < 0.90` |`needsClarification` |
194-
| Otherwise |`committedSlots` |
192+
| Field is in `unresolvable` |`needsClarification` |
193+
| `confidence[field] < 0.75` |`needsClarification` |
194+
| Field corrects a prior value AND `confidence < 0.90` |`needsClarification` |
195+
| Otherwise |`resolvedFields.scheduleFields` |
195196

196-
The correction threshold (0.90 vs 0.75) is higher because overwriting a slot
197+
The correction threshold (0.90 vs 0.75) is higher because overwriting a field
197198
the user previously confirmed carries more risk.
198199

199-
**Contract change detection:** if the new `activeContract.intentKind` differs
200-
from `priorContract.intentKind` (e.g., the user switches from scheduling to
201-
editing), all prior resolved slots are discarded so stale plan-slots don't
202-
bleed into an edit.
200+
**Workflow change detection:** if `operationKind` changed from the prior
201+
operation, or if `currentTargetEntityId` differs from the prior
202+
`targetRef.entityId`, all prior resolved schedule fields are discarded so stale
203+
fields don't bleed into a new workflow. `workflowChanged: true` is surfaced in
204+
the output so `buildResolvedOperation` can reset `originatingText`/`startedAt`.
205+
206+
**Target resolution:** `resolvedTargetRef` is the canonical next target —
207+
`currentTargetEntityId` when the classifier resolved one, otherwise carried
208+
forward from `priorPendingWriteOperation.targetRef`.
203209

204210
**Output:** `CommitPolicyOutput`
205211
```ts
206212
{
207-
committedSlots: ResolvedSlots; // slots safe to act on
208-
needsClarification: SlotKey[]; // slots extracted but not confident enough
209-
missingSlots: SlotKey[]; // required by contract, not yet resolved
213+
resolvedFields: ResolvedFields; // schedule fields safe to act on
214+
resolvedTargetRef: TargetRef; // canonical target for this workflow step
215+
needsClarification: string[]; // dot-path fields not confident enough (e.g. "scheduleFields.time")
216+
missingFields: string[]; // required by operationKind, not yet resolved
217+
workflowChanged: boolean; // true when operation or target switched
210218
}
211219
```
212220

@@ -251,9 +259,8 @@ relative to a prior proposal.
251259
targetEntityId?: string;
252260
targetProposalId?: string;
253261
mutationInputSource?: "direct_user_turn" | "recovered_proposal";
254-
clarificationSlots?: string[];
255-
committedSlots: ResolvedSlots;
256-
resolvedContract: WriteContract;
262+
clarificationSlots?: string[]; // dot-path fields (e.g. "scheduleFields.time")
263+
resolvedOperation?: PendingWriteOperation; // set for non-reply_only turns
257264
}
258265
```
259266

@@ -431,7 +438,7 @@ After any branch completes, the conversation state is updated:
431438
- New pending clarification (for `ask_clarification`)
432439
- New proposal entity (for `present_proposal`)
433440
- Conversation mode update (planning → clarifying / confirming)
434-
- Committed slots in `pending_write_contract` and `resolved_slots`
441+
- `pending_write_operation` updated from `policy.resolvedOperation` (non-`reply_only` turns only)
435442

436443
**Mutation branch:**
437444
`deriveMutationState()` (`apps/web/src/lib/server/conversation-state.ts`)
@@ -480,8 +487,8 @@ computed, not stored directly — it is derived from whether there are pending
480487
clarifications or presented proposals in the entity registry.
481488

482489
**`editing` mode** is entered when an `edit_request` turn arrives and an
483-
editable entity is present. It is structurally similar to `planning` but uses
484-
`EDIT_CONTRACT` which requires only `time`.
490+
editable entity is present. It is structurally similar to `planning` but the
491+
`edit` operationKind requires only `time`.
485492

486493
---
487494

@@ -502,7 +509,7 @@ slots.
502509
**From:** `applyCommitPolicy()``CommitPolicyOutput`
503510
**To:** `decideTurnPolicy()``TurnPolicyDecision`
504511

505-
The policy decision reads `missingSlots` and `needsClarification` from the
512+
The policy decision reads `missingFields` and `needsClarification` from the
506513
commit output to determine whether the system can proceed or must ask. It also
507514
reads the entity registry to check for consent requirements.
508515

@@ -590,12 +597,14 @@ User-facing reply renderer for mutation outcomes. Converts
590597
`ProcessedInboxResult` to a human-readable message in the user's timezone.
591598

592599
### `packages/core/src/commit-policy.ts`
593-
Slot gating logic. Thresholds: 0.75 (normal), 0.90 (correction). Contract
594-
change detection resets prior slots. Pure function, no LLM calls.
600+
Schedule field gating logic. Thresholds: 0.75 (normal), 0.90 (correction).
601+
Operation/target change detection clears prior schedule fields and sets
602+
`workflowChanged`. Outputs `resolvedTargetRef` as the canonical next target.
603+
Pure function, no LLM calls.
595604

596605
### `packages/core/src/write-contract.ts`
597-
Contract definitions and resolver. `DEFAULT_WRITE_CONTRACT` requires
598-
`[day, time]`. `EDIT_CONTRACT` requires `[time]`.
606+
`resolveOperationKind()` — derives the active `OperationKind` from turn type
607+
and prior operation. Required fields per operation are owned by commit-policy.
599608

600609
### `packages/core/src/discourse-state.ts`
601610
All discourse state schema types, helpers, and mode derivation. Contains

0 commit comments

Comments
 (0)