From c6ad566cf2e2aa2c5f36e2d20f6285169c3d63d5 Mon Sep 17 00:00:00 2001 From: YuZhangLarry Date: Fri, 14 Aug 2026 11:20:45 +0800 Subject: [PATCH 1/2] docs(rfc): add development context and scope model RFC Introduce an RFC resolving the three-way overload of scope_id (project / memory-isolation partition / Workstream) by keeping scope_id as the Workstream identity and adding an application-layer Project that carries shared context. Includes English and Chinese versions. Refs oceanbase/powercontext#1219 --- ...000_development_context_and_scope_model.md | 467 ++++++++++++++++++ ...000_development_context_and_scope_model.md | 398 +++++++++++++++ 2 files changed, 865 insertions(+) create mode 100644 docs/en/rfcs/0000_development_context_and_scope_model.md create mode 100644 docs/zh/rfcs/0000_development_context_and_scope_model.md diff --git a/docs/en/rfcs/0000_development_context_and_scope_model.md b/docs/en/rfcs/0000_development_context_and_scope_model.md new file mode 100644 index 000000000..158f1743c --- /dev/null +++ b/docs/en/rfcs/0000_development_context_and_scope_model.md @@ -0,0 +1,467 @@ +- Proposal Name: `development_context_and_scope_model` +- Start Date: 2026-08-12 +- Status: Draft +- Tracking Issue: [oceanbase/powercontext#1219](https://github.com/oceanbase/powercontext/issues/1219) +- Related RFCs: [RFC 0002](0002_core_sdk_product_model.md), [RFC 0019](0019_local_source_memory_runtime.md), + [RFC 0028](0028_context_pack.md), [RFC 0048](0048_handoff_artifact.md), + [RFC 0072](0072_scoped_statistics_and_usage.md), and [RFC 0082](0082_handoff_report.md) + +# Summary + +PowerContext derives a `scope_id` from a project and uses it to share durable context across Sessions. Handoff later +reused that same scope as the identity of one linear Workstream. As a result `scope_id` now carries three conflated +meanings at once: the **project** (the Codex integration derives it from the Git remote or path), the **memory +isolation unit** (the Runtime keys one Source journal, one Memory head, and one Trigger cursor on it), and the +**Workstream** (Handoff keeps one linear history per scope). This conflation cannot express parallel work inside one +project or development that spans several projects. + +Three approaches were weighed: **A** — a three-layer model with a new `workstream_id` (cleanest, but reverses two +RFC 0082 decisions and forces a backfill migration); **B** — keep `scope_id` as the Workstream identity and add a +separate, opt-in Project layer to carry the *shared* meaning; **C** — documentation-only clarification (no new +capability, does not actually solve the problem). **This RFC adopts B.** The full side-by-side comparison is in +*Rationale and alternatives*; the rest of this document specifies B. + +Concretely, this RFC fixes the identity boundaries relating **Project**, **Workstream**, **Session**, and **scope**. It +decides: + +- **`scope_id` stays the Workstream identity and the Runtime partition key** — this version introduces no `workstream_id`. +- **Project** becomes an explicit, application-layer grouping that carries *shared* project context while each Workstream keeps an *isolated* history. +- **Session** is a transient participant boundary, never an identity. +- **`project_id`** stays a separate, server-owned identity that lives only in the Builtin Runtime application layer and never enters the Core Protocol. +- Existing `scope_id`-keyed API, CLI, integrations, and data keep their contracts; the new sharing is additive and opt-in. + +This RFC decides the model and invariants. It does not fix detailed user-flow orchestration, migration tooling, or the +concrete API/CLI/Dashboard shapes; those are follow-up work created after acceptance. + +# Motivation + +The current model works for a single linear project but breaks in two common situations: + +- **Parallel work in one project.** Two features, or a long refactor alongside ongoing bug fixes, have different + objectives and next actions and are continued by different Agents. Today the only way to isolate their histories is + to derive different `scope_id` values — which also fragments the shared project context they should see. +- **Work spanning multiple projects.** A cross-repository change has no first-class relationship, only a weak + `external_refs` tag. + +Both stem from one cause: `scope_id` is overloaded with three meanings, and the *sharing* meaning wants scopes to be the +**same** while the *isolation* meaning wants them **different** — one key cannot be both. + +| Meaning | Who relies on it | What it keys | Wants scopes to be… | +| --- | --- | --- | --- | +| **Project** (sharing unit) | Codex integration, README | context derived from the repo's Git remote (or path) | the **same** (to share) | +| **Memory isolation unit** (Runtime partition key) | Core Runtime | one Source journal + one Memory head + one Trigger cursor | — (mechanical) | +| **Workstream** (isolation unit) | Handoff, RFC 0048/0082 | one linear Handoff history | **different** (to isolate) | + +This RFC resolves rows 1 and 3 by moving the *sharing* meaning up to a Project layer and leaving *isolation* on +`scope_id`. (The system already runs these as two unreconciled layers — a flat `scope_id` in Core/Runtime/Memory and a +`Project → Workstream(≡scope_id)` catalog in Handoff Report; see Prior art.) The tracking issue's load-bearing +invariants — preserve shared context across Sessions, keep Workstream histories isolated, never equate Session with +Workstream, don't assume the current `project_id` is final, treat migration as follow-up — are each preserved below. + +# Guide-level explanation + +Four concepts; the binding definitions are the invariants I1–I8 in the Reference-level section, so this is only the +mental model. + +- **scope** — an opaque, integration-owned partition string (≤256 chars), unchanged from RFC 0019. One scope keys one + Source journal + one Memory head + one Trigger cursor. +- **Workstream** — one continuable line of work (single objective, single linear Handoff history). **Its identity *is* + its `scope_id`**; no separate `workstream_id`. Two pieces of work are different Workstreams — and use different + scopes — exactly when they can be continued independently (RFC 0082's rule); branch switches/renames/rebases reuse + one scope. +- **Project** — an explicit, application-layer grouping of Workstreams (a repo, service, or long effort) with an + immutable `project_id`. It is the boundary for *aggregation* (RFC 0082, existing) and *shared context* (new), and is + **never** a Handoff scope. +- **Session** — the transient participant boundary that reads/prepares/receives context in one interval. Never an + entity, identity, or Workstream; appears only as optional `session_id` attribution. + +Default behavior is **share, don't isolate**: `derive_scope_id` keys only on the Git remote/path and does not read the +branch, so switching branches resolves the *same* scope. To run work as an independent Workstream you **declare** a +distinct scope (e.g. a git worktree with its own `POWERCONTEXT_CODEX_SCOPE_ID`); branch is never a partition key. The +declaration surface is follow-up design. + +**What does *not* start a new Workstream.** Opening another CLI, terminal, or Agent process is a new *Session*, not a +new Workstream (I6). Two CLIs pointed at the *same* scope are two Sessions continuing the *one* Workstream — they share +its single Memory head and single linear Handoff history, and concurrent commits are resolved by RFC 0048's CAS +conflict, not by forking a second history. A new Workstream appears only when a *distinct scope is declared*; the test +is "can these be continued independently" (separate objective, separate linear history), never "is this a different +process, branch, or CLI". So: two CLIs on one declared scope = one Workstream; two CLIs on two declared scopes = two +Workstreams — the CLI is never the deciding factor. + +``` +Project (project_id — server-owned grouping; shared context + aggregation; never a Handoff scope) + ├── Workstream A (scope_id = git:host/repo#featureX) ── isolated Sources / Memory / Handoff / Stats + ├── Workstream B (scope_id = git:host/repo#refactorY) ── isolated ... + └── Workstream C (scope_id = git:host/repo) ── isolated ... + ▲ + └─ shared Project context is readable from any Workstream's Session, + but each Workstream's committed Handoff history stays private. + +Session (transient; attributes activity to a Workstream; never an identity) +``` + +`#featureX` / `#refactorY` are *explicitly declared* scopes, not branch-derived — default `derive_scope_id` yields only +`git:host/repo` (Workstream C). Isolation lives at the scope level (unchanged); sharing lives at the Project level (new, +additive, opt-in). An existing single-Workstream user is unaffected: an ungrouped scope behaves exactly as today. + +# Reference-level explanation + +Everything from here to *Drawbacks* specifies **Alternative B** — the chosen model. It is not a neutral survey: the +invariants, data model, and resolution below are B's. Where **Alternative A** would take a materially different path, +the difference is flagged inline as *Under A: …*; the full A-vs-B comparison lives in *Rationale and alternatives*. + +## Model and invariants + +These eight invariants (I1–I8) are the binding decisions of this RFC; everything else in the document follows from them. + +- **I1 — scope is opaque and Runtime-owned.** Unchanged from RFC 0019: non-empty string ≤256 chars; the Runtime does + not parse structure from it. (No change to `validate_scope_id`, `MAX_SCOPE_ID_LENGTH`, or the OpenAPI pattern.) +- **I2 — Workstream identity is `scope_id`.** No `workstream_id` is introduced in this version. A Workstream's Sources, + Memory, Handoff, and Statistics are partitioned by its scope and isolated from other Workstreams. The one-linear- + history-per-scope and CAS-conflict guarantees of RFC 0048/0082 are preserved verbatim. *Under A:* this is the one + invariant that inverts — `scope_id` becomes a routing key and a new `workstream_id` carries identity; every invariant + downstream of I2 is B's because B keeps identity on the scope. +- **I3 — Project is an application-layer grouping, not a Core concept.** `project_id` is server-owned and immutable; it + lives only in the Builtin Runtime application layer. Core Protocol (RFC 0002; `core-protocol.md`) still knows nothing + about scope, Project, Workstream, or Session. Project membership creates **no foreign key** into Core tables + (Sources, Artifacts, Memory, Handoff), consistent with the existing `catalog_store` decision. +- **I4 — one scope belongs to at most one Project.** Registering a scope under a second Project is a conflict + (`scope_already_grouped`), unchanged from RFC 0082. A scope may also belong to no Project (ungrouped), in which case + it behaves exactly as today. +- **I5 — Project is not a Handoff scope.** A Project never owns Handoff history, never receives a committed Handoff, + and never writes back into a Workstream. Handoff and Continue remain scope-bound (RFC 0048). +- **I6 — Session is transient and non-identity.** Session is never persisted as a domain identity and never determines + a Workstream. It equals neither Workstream nor scope. +- **I7 — Branch is not an identity axis.** Unchanged from RFC 0082: branch metadata is a weak signal and untrusted + activity attribution, never a Handoff boundary. +- **I8 — The Workstream boundary is declared and semantic, not derived from branch or task.** The determinant is + independent continuability (I2 / RFC 0082), not the git branch or the number of tasks. The default is one scope per + repository (its Git remote); an explicit declaration starts a distinct Workstream. Neither the branch nor a git + worktree changes the derived scope — only an explicit `POWERCONTEXT_CODEX_SCOPE_ID` (or a different remote) does. + +## Shared Project context (the new capability) + +Today RFC 0019 persists a strict one-to-one `scope_id → Memory Artifact` binding via `MemoryBindingStore`, and RFC 0019 +itself names the extension path: "supporting multiple instances requires an explicit extension of the application +mapping." This RFC uses exactly that seam. The mechanism below fixes the *shape* of the extension; the ranking/merge +policy and the write surface stay Unresolved (see below). + +### Data model + +- **Per-Workstream binding (unchanged).** `MemoryBindingStore` keeps its one-to-one `scope_id → Memory Artifact` + mapping. Each Workstream continues to own exactly one Memory head, isolated from every other scope. +- **Project context binding (new).** A Project **may** own one additional, Project-level binding + `project_id → Memory Artifact`, resolving to a Memory Artifact distinct from any Workstream's. This is the **Project + context**. It is a *second row in the application-layer mapping, not a second head on a scope*: no `scope_id` gains a + second binding, and the per-scope one-to-one invariant is untouched. +- Both bindings live only in the Builtin Runtime application layer. Neither introduces a foreign key into Core tables + (I3); Core still resolves a Memory Artifact from an opaque key and knows nothing about Project. + +The full model is three application-layer catalog records plus the unchanged per-scope head — only the last row is new: + +| Record | Shape | Cardinality | Origin | +| --- | --- | --- | --- | +| Per-scope Memory head | `scope_id → Memory Artifact` | one per scope | RFC 0019 `MemoryBindingStore` (unchanged) | +| Project | `{ project_id, project_key, title }` | one per Project | RFC 0082 catalog | +| Membership | `scope_id → project_id` | ≤ 1 per scope (I4) | RFC 0082 Workstream catalog (`WorkstreamDescriptor`) | +| Project context | `project_id → Memory Artifact` | ≤ 1 per Project | **new (this RFC)** | + +A scope's own head is resolved directly; its Project context is resolved indirectly, by following Membership to a +`project_id` and then the Project-context binding. The two Memory Artifacts are always distinct handles. + +### Storage seam + +The Project context binding is persisted alongside the existing Project catalog (RFC 0082's `catalog_store`), +keyed by `project_id`. Resolving it reuses the same Memory Artifact machinery the per-scope head already uses — the only +new thing is *which key selects which Artifact*, not how Memory Artifacts are stored, revised, or searched. No Core +Memory table, revision format, or search path changes. + +### Resolution + +Every read and write resolves deterministically from the request's `scope_id`, with no new required parameter on the +call surface: + +1. **Locate the scope.** The application layer looks `scope_id` up in the catalog. +2. **Ungrouped → one binding.** If the scope belongs to no Project, resolution stops at its own `scope_id → Memory + Artifact` head — byte-for-byte today's path. No Project context is consulted or created. +3. **Grouped → two bindings.** If the scope carries a Membership to a `project_id`, resolution additionally resolves + that Project's context binding. If the Project has no context binding yet, resolution degrades to the ungrouped case + (own head only). +4. **Reads vs writes.** A *read* hands both resolved heads to the composition step below. A *write* targets **only** the + scope's own head (I2, I5); the Project context is never on a write path in this version. + +Which keys select which Artifacts, and in what order they are resolved, is fully decided here. Only how two resolved +read-heads are *ranked and merged* is deferred — the next subsection. + +### Read composition (shape fixed, policy deferred) + +When a Session prepares context for a Workstream whose scope is grouped under a Project, the application layer resolves +**two** Memory heads — the Workstream's own scope-level head and the Project context — and composes them into the +prepared context. Two things are **decided** here: + +- **Both are readable.** The Workstream's own head is always read; the Project context is *readable* from any + constituent Workstream's Session. +- **Composition is a read-only overlay.** Composing never writes to either binding, never copies one Workstream's + entries into another, and never mutates the Project context. Isolation is preserved *structurally* — the two heads + remain two distinct Artifacts, so no Workstream's private history can end up in another's store. + +What stays **Unresolved** (deferred to the Memory/Context follow-up): the precedence between the two heads, the +merge/dedup/ranking rule when both surface an entry, and how cross-Workstream leakage is bounded at read time. This RFC +fixes that composition *happens over two isolated Artifacts*; it does not fix *how* they rank. + +### Write isolation + +A Workstream's committed Handoff history is never written to the Project, and the Project never writes back into a +Workstream (I5). Whether a Session can *write* into Project context — and through which operation and trust markers — is +deferred; the default in this version is that **Project context is read-shared only**. + +### Context Pack (RFC 0028) + +Context Pack keeps its "one scope per request, no mixed scopes" contract unchanged. Surfacing Project-level reads +through `prepare_context` would require either a new contract version or an explicit Project parameter; this RFC does +not modify the existing single-scope contract and leaves the surface choice to the follow-up. + +## Project entity and registration + +A Project is a catalog record in the Builtin Runtime application layer (RFC 0082's `ProjectDescriptor` in the +`catalog_store`), not a Core entity (I3). It carries three fields: + +- `project_id` — server-generated, immutable (`prj_`), the durable identity. Never client-supplied, never + reused after retirement. +- `project_key` — a catalog-unique human key (e.g. `acme/api`) used to look a Project up without knowing its + `project_id`. +- `title` — a mutable display label; changing it never changes identity. + +**Registration.** A scope becomes a Project member by binding its `scope_id` to a `project_id` in the Workstream +catalog (the existing `WorkstreamDescriptor` / `create_workstream` seam, keyed on `scope_id`). Binding a scope that is +already grouped under a *different* Project is the existing `scope_already_grouped` conflict (I4) — one scope, at most +one Project. An **ungrouped** scope is the default and needs +no registration: it owns its Sources/Memory/Handoff/Statistics exactly as today and reads no Project context. + +**Grouping states.** A scope is in exactly one of two states, and this version defines exactly one transition between +them: + +- **Ungrouped** — no Membership record; behaves exactly as today. +- **Grouped under P** — Membership `scope_id → P`; additionally reads P's Project context. + +`ungrouped → grouped(P)` is the registration above. Re-homing (`grouped(P) → grouped(Q)`) and un-grouping +(`grouped → ungrouped`) are **not** defined here — a second registration under a different Project is *refused* with +`scope_already_grouped`, never silently re-homed (see Unresolved: moving scopes between Projects). Grouping a scope +never rewrites, moves, or merges its existing per-scope Memory or Handoff history; it only adds the ability to read P's +Project context. + +**Lifecycle.** `title` is mutable; `project_id` is immutable for the life of the Project. A Project exists once at +least one scope is bound to it (or once it is explicitly created, if the follow-up adds an explicit create surface). +Moving a scope between Projects, and merging or splitting Projects, are **out of scope** for this RFC (listed under +Unresolved / future work) — this version fixes only that a scope belongs to at most one Project and that membership is +an application-layer binding. + +## Worked example: two parallel Workstreams in one Project + +A team develops the `acme/api` service. They register a Project: + +- `project_id = prj_ac…`, `project_key = acme/api`, `title = "ACME API"`. + +Two lines of work run in parallel and must not share a Handoff history: + +- **W-main** — ongoing bug fixes on the default checkout. `derive_scope_id` reads only the Git remote, so its scope is + `git:github.com/acme/api`. No branch is encoded. +- **W-refactor** — a long storage rewrite that must be independently continuable. A worktree of the same repo shares + the *same* Git remote, so it would otherwise derive the *same* scope as W-main; the team therefore **explicitly + declares** a distinct scope via `POWERCONTEXT_CODEX_SCOPE_ID = git:github.com/acme/api#storage-rewrite`, which + `derive_scope_id` uses verbatim. The worktree only lets two checkouts coexist on disk — the *declared scope alone* + creates the isolation, and the `#storage-rewrite` suffix is *declared*, not derived from the branch (I8). + +Both scopes are bound to `prj_ac…`. Now: + +- **Reads.** A Session on W-refactor prepares context and resolves **two** heads — W-refactor's own scope-level Memory + head ⊕ the Project context bound to `prj_ac…` — composed as a read-only overlay. It can therefore see shared + project-wide knowledge (build quirks, service conventions) **without** seeing W-main's in-flight bug-fix history. A + Session on W-main symmetrically sees its own head ⊕ the same Project context. +- **Writes.** Each Session's committed Handoff appends only to its own scope's single linear history: W-refactor's + Handoffs never enter `git:github.com/acme/api`, and neither Workstream writes back into the Project (I5). The Project + context is read-shared only in this version. + +Both invariants hold **simultaneously**: sharing (both Sessions read one Project context) and isolation (two separate +linear Handoff histories, two separate Memory heads) — which is exactly what a single overloaded `scope_id` could not +express. An ungrouped `acme/api` checkout (no Project bound) would simply read its own head and no Project context, +behaving exactly as today. + +## Acceptance scenarios + +These pin the model to observable behavior. Each asserts *structure*, not the deferred read-composition policy. + +| # | Given | When | Then | Invariant | +| --- | --- | --- | --- | --- | +| 1 | scope bound to no Project | a Session prepares context | only the scope's own `scope_id → Artifact` head resolves; no Project context is read or created — byte-for-byte today | I4, Resolution ② | +| 2 | scope grouped under `P`, and `P` has a context binding | a Session prepares context | two **distinct** Artifact handles resolve (own head ⊕ `P`'s context), composed as a read-only overlay | Resolution ③ | +| 3 | scope grouped under `P`, but `P` has no context binding yet | a Session prepares context | resolution degrades to the ungrouped case (own head only) | Resolution ③ | +| 4 | two Workstreams under one `P` | one commits a Handoff | it appends only to its own scope's linear history; the sibling's history and `P`'s context are never written | I2, I5 | +| 5 | two Workstreams under one `P` | one's Session reads | it sees `P`'s shared context but **not** the sibling's in-flight head (two Artifacts stay distinct) | Read composition (structure) | +| 6 | scope already grouped under `P` | it is registered under `Q` | `scope_already_grouped` conflict; never silently re-homed | I4 | +| 7 | one repository checkout | the git branch is switched | the **same** scope resolves; no new Workstream is created | I7, I8 | +| 8 | any Project membership | Core tables are inspected | no foreign key into Core points at `project_id`; Core still resolves an Artifact from an opaque key | I3 | + +Scenarios 2 and 5 deliberately assert only that two isolated Artifacts are resolved — the precedence/merge order +between them is Unresolved (see below) and must not be pinned by a test yet. + +## Interaction with existing subsystems + +- **Memory (RFC 0019).** Per-scope binding unchanged. Optional Project-level binding added via the documented + application-mapping extension. No change to Core Memory, Revisions, or search. +- **Handoff (RFC 0048).** Fully unchanged. One linear history per scope; CAS conflict; Prepared vs committed; + evidence/Continue `untrusted_history` semantics all preserved. Workstream stays ≡ scope. +- **Handoff Report (RFC 0082).** Reconciled, mostly reaffirmed. The Project → Workstream catalog, `WorkstreamDescriptor`, + `WorkspaceBinding`, activity store, and `handoff-reports` API keep their current shape. The one clarification is that + Project now also carries *shared context* in addition to *aggregation*; RFC 0082's "aggregation-only, never writes + back" rule is preserved (Project context is a separate read binding, not a write-back of Handoffs). +- **Statistics (RFC 0072).** Per-scope statistics unchanged. Optional Project/Workstream roll-up is **deferred** to a + follow-up (this RFC does not require cross-scope aggregation now, but records it as the natural place for it). +- **Core Protocol (RFC 0002).** Unchanged and explicitly out of bounds for Project/Workstream (I3). + +## Compatibility + +- **Existing data.** No rewrite required. Existing scopes keep their Sources, Artifacts, Memory, Handoff, and + Statistics unchanged. Grouping and Project context are additive; an ungrouped scope reads no Project context and + behaves exactly as today. +- **API.** Every existing `scope_id`-keyed endpoint (Memory, Handoff, stats, sources, context) keeps its contract. New + Project-context reads are additive and opt-in. No existing request shape changes meaning. +- **CLI.** Existing `--scope-id` commands are unchanged. +- **Codex integration.** `derive_scope_id` (Git remote → path fallback) is unchanged. `POWERCONTEXT_CODEX_SCOPE_ID` + keeps its meaning. Registering a derived scope under a Project is an additional, optional step. +- **Deprecations.** None in this RFC. + +## Identity encoding (unchanged surfaces, for reference) + +- `scope_id`: opaque string ≤256, client-supplied; derived from the Git remote, else a `local:` path hash; + `POWERCONTEXT_CODEX_SCOPE_ID` overrides it verbatim. +- `project_id`: server-generated immutable (`prj_`), catalog-owned. +- `project_key`: catalog-unique human key; `title`: mutable display. +- No `workstream_id` in this version. + +# Drawbacks + +- **Parallel work still requires deriving multiple scopes.** Because Workstream stays ≡ scope, expressing parallel work + is a naming/derivation discipline (distinct scopes under one Project), not a first-class "one Workstream, many + branches" identity. Teams that want a single Workstream to span branches or repositories are not served by this + version. +- **Cross-Project work remains weakly modeled.** Development spanning multiple Projects still relies on `external_refs` + rather than a first-class cross-Project relationship. This RFC does not close that gap. +- **Two Memory bindings introduce a composition question.** Adding Project context means a Session may read two Memory + sources; the precedence/merge policy is real design work deferred to the follow-up, and getting it wrong risks + leaking one Workstream's private context into another via the shared layer if the boundary is drawn incorrectly. +- **A second identity may still be wanted later.** By deliberately not introducing `workstream_id`, this RFC bets that + the deferred extension is acceptable; if parallel-branch identity becomes a hard requirement, a follow-up RFC must + reopen RFC 0082's central identity decision. + +# Rationale and alternatives + +Three directions were considered. **A and B are the two real contenders** (C is a no-new-capability baseline). Both +start from the same problem — `scope_id` carrying three meanings — but split it differently. Read the table +top-to-bottom: **A moves each meaning onto its own key** (three keys), while **B keeps isolation on `scope_id` and +lifts only the *sharing* meaning onto a new, additive Project layer** (one key, one opt-in layer). + +| Design dimension | Today (`scope_id`) | **Alt A** — three-layer (deferred) | **Alt B** — Project layer (this RFC) | +| --- | --- | --- | --- | +| Sharing (project context) | `scope_id` | `project_id` (first-class) | new **Project layer** (additive) | +| Runtime isolation / partition key | `scope_id` | `scope_id` (routing only) | `scope_id` (unchanged) | +| Workstream identity | `scope_id` | new `workstream_id` | `scope_id` (unchanged) | +| `project_id` role | thin, app-layer | first-class identity | app-layer grouping (unchanged) | +| One Workstream across branches/repos | no | **yes** (first-class) | no (derive distinct scopes) | +| Migration of existing scopes | — | **required** (backfill every scope) | **none** | +| RFC 0082 identity decisions | — | **reversed** (two of them) | preserved | +| Core Protocol change | — | likely | none | + +### Alternative A — Three-layer model with a distinct `workstream_id` (deferred) +**What it does.** Give each conflated meaning its own key. `project_id` becomes a first-class identity (sharing and +aggregation); `scope_id` is demoted to a pure Runtime partition/routing key that no longer *means* "Workstream" — one +`project_id` may map to many scopes, and a scope is just a storage partition; a new `workstream_id` becomes the +Handoff/history identity, so a single Workstream can span several scopes/branches/repositories because its identity no +longer rides on the partition key. + +**What it buys.** Parallel and cross-repository work become first-class: one Workstream moves across branches or repos +without fragmenting its history; Branch and Session get natural non-identity positions; sharing and isolation never +contend because they live on different keys by construction. This is the cleanest expression of the three meanings. + +**What it costs.** It reverses two central RFC 0082 decisions ("`scope_id` is the only Workstream identity"; "no +separate `workstream_id`"); it adds a resolution/indirection layer that every operation across Handoff, Handoff Report, +and Runtime must traverse (`workstream_id` → scope(s) → Artifacts); it forces a **backfill migration of every existing +scope** into the new three-key model; and it likely touches the Core Protocol boundary. That is a large blast radius +for a first version. + +**Verdict — deferred, not discarded.** Preserved as a Future possibility, and reachable *on top of* B without +contradicting B's invariants — B is the smaller first step on the same path. + +### Alternative B — Keep `scope_id` as Workstream identity; add a Project shared-context layer (this RFC, chosen) +**What it does.** Leave isolation exactly where it is — scope ≡ Workstream, `scope_id` unchanged as both identity and +partition key — and add *sharing* as a new, additive, opt-in Project-context binding (`project_id` → Memory Artifact). +No key is demoted or reassigned; `workstream_id` is not introduced. + +**What it buys.** It satisfies both hard constraints from the issue (shared context *and* isolated histories) with +**zero migration and no Core change**; it lands inside seams the existing RFCs already left open (RFC 0048's "parallel +workstreams, derived scopes"; RFC 0019's "multiple instances via explicit application-mapping extension"; RFC 0082's +deferred cross-Project reporting) rather than overturning RFC 0048's linear-history model; and it keeps Alternative A +reachable as a later extension. + +**What it costs (accepted).** Parallel work stays a scope-derivation discipline, not a first-class "one Workstream, +many branches" identity; cross-Project work remains weakly modeled; and the two-binding read-composition policy is real +work deferred to the follow-up. These are the Drawbacks above, accepted as the price of a small, reversible first step. + +**Verdict — chosen.** + +### Alternative C — Clarify semantics only, no new grouping capability +**What it does.** Only document the three conflated meanings of `scope_id`, standardize the derivation and parallel-work +naming conventions, and optionally add Project roll-up to Statistics. No new modeled capability. + +**Verdict — rejected.** It does not actually solve the issue's core need: parallel work and shared-but-isolated context +would remain a convention rather than a modeled guarantee. + +**Impact of not doing this.** `scope_id` keeps carrying three meanings; teams either fragment shared context to gain +isolation or pollute one Workstream's history to keep context shared. RFC 0019/0048/0082 continue to encode an +unstated model that new features must each rediscover. + +# Prior art + +- **RFC 0048** first asserts "one current workstream per scope" and explicitly parks *parallel workstreams* and + *derived scopes* under Future possibilities — the seam this RFC builds on. +- **RFC 0082** specifies the current `Project → Workstream(≡scope_id)` catalog, the "Project aggregates, never writes + back" rule, and the branch-is-not-identity rule; this RFC reaffirms those and adds shared context. +- **RFC 0019** documents the `MemoryBindingStore` one-to-one mapping and names the multiple-instance extension path + used here. +- The RFC process itself (`docs/en/rfcs/README.md`): validate the problem with maintainers first, keep the initial + scope narrow enough to review and implement. + +# Unresolved questions + +To resolve before merge: + +- **Context composition and precedence.** When a Workstream's scope is grouped under a Project, exactly how are the + Workstream Memory head and the Project context composed on read? What precedence, and how is cross-Workstream leakage + prevented? (Blocks the Memory/Context follow-up.) +- **Writing Project context.** Can a Session write into Project context, through which operation, and with what trust + markers? Default in this version is read-shared only. +- **Context Pack surface.** Should Project context be surfaced through `prepare_context` (new contract version) or a + separate read path? (RFC 0028 stays single-scope for now.) +- **Workstream boundary declaration (I8).** Through what surface, and when, is the per-repository default overridden? + This must reconcile RFC 0082's "parallel branches use distinct scopes" rule with the current `derive_scope_id`, which + keys only on the repository. + +Intentionally out of scope (follow-up decisions, may need their own RFC): + +- Introducing a distinct `workstream_id` (Alternative A). +- First-class cross-Project relationships beyond `external_refs`; Portfolio/Program entities above Project. +- Statistics roll-up across a Project or Workstream group. +- Moving, merging, or splitting Workstreams across Projects. +- Migration tooling and detailed user-flow orchestration. + +# Future possibilities + +- **Alternative A as an extension.** If parallel-branch or cross-repository Workstream identity becomes a hard + requirement, a follow-up RFC can introduce `workstream_id` on top of this model, with `scope_id` demoted to a routing + key — reachable without contradicting the invariants set here. +- **Cross-Project aggregation.** Portfolio/Program-level reads and cross-Project Handoff Reports (already deferred by + RFC 0082). +- **Project-scoped Statistics roll-up** (natural extension of RFC 0072). +- **Richer Session semantics** (concurrency signals, live-state hints) as long as Session remains non-identity — + including how *concurrent Sessions on one scope* coordinate beyond RFC 0048's CAS conflict (the open residue of "does + a different CLI start a new Workstream": no, but simultaneous CLIs on one scope may still want live-state signals). diff --git a/docs/zh/rfcs/0000_development_context_and_scope_model.md b/docs/zh/rfcs/0000_development_context_and_scope_model.md new file mode 100644 index 000000000..60485f41b --- /dev/null +++ b/docs/zh/rfcs/0000_development_context_and_scope_model.md @@ -0,0 +1,398 @@ +- 提案名称:`development_context_and_scope_model` +- 起始日期:2026-08-12 +- 状态:草稿(Draft) +- 跟踪 Issue:[oceanbase/powercontext#1219](https://github.com/oceanbase/powercontext/issues/1219) +- 相关 RFC:[RFC 0002](0002_core_sdk_product_model.md)、[RFC 0019](0019_local_source_memory_runtime.md)、 + [RFC 0028](0028_context_pack.md)、[RFC 0048](0048_handoff_artifact.md)、 + [RFC 0072](0072_scoped_statistics_and_usage.md)、[RFC 0082](0082_handoff_report.md) + +# 摘要(Summary) + +PowerContext 从项目派生出一个 `scope_id`,用它在多个 Session 之间共享持久化上下文。此后 Handoff 又复用同一个 scope +作为「一条线性 Workstream」的身份。于是 `scope_id` 同时承载了三种被混为一谈的语义:**项目**(Codex 集成按 Git remote 或 +路径派生它)、**记忆隔离单元**(Runtime 以它为键选择一条 Source journal、一个 Memory head、一组 Trigger cursor)、以及 +**Workstream**(Handoff 为每个 scope 维护一条线性历史)。这种混淆无法表达同一项目内的并行工作,也无法表达横跨多个项目的 +开发。 + +权衡过三个方案:**A** —— 引入新 `workstream_id` 的三层模型(最干净,但推翻 RFC 0082 的两条决策、且迫使全量回填迁移); +**B** —— 保持 `scope_id` 为 Workstream 身份,另加一个可选启用的 Project 层来承载*共享*语义;**C** —— 仅文档层澄清(不加 +能力,并未真正解决问题)。**本 RFC 采用 B。** 三者的逐项对照见*理由与备选方案*一节;本文档其余部分即是对 B 的规约。 + +具体而言,本 RFC 固定 **Project**、**Workstream**、**Session**、**scope** 之间的身份边界。它决定: + +- **`scope_id` 仍是 Workstream 身份与 Runtime 分区键** —— 本版不引入 `workstream_id`。 +- **Project** 成为显式的应用层分组,承载*共享的*项目上下文,而每个 Workstream 保持*隔离的*历史。 +- **Session** 是短暂的参与者边界,永不作为身份。 +- **`project_id`** 仍是独立的、服务端所有的身份,仅存在于 Builtin Runtime 应用层,永不进入 Core Protocol。 +- 既有以 `scope_id` 为键的 API、CLI、集成与存量数据契约不变;新增的共享是加法式、可选启用。 + +本 RFC 只决定模型与不变量,不固化详细的用户流程编排、迁移工具或具体的 API/CLI/Dashboard 形态;这些在接受后作为后续工作 +创建。 + +# 动机(Motivation) + +当前模型对单条线性项目可行,但在两种常见场景下失效: + +- **同一项目内的并行工作。** 两个 feature,或一次长期重构与并行进行的缺陷修复,拥有不同的目标与下一步动作,且由不同 + Agent 各自继续。今天要隔离它们的历史,唯一办法是派生出不同的 `scope_id`——但这同时也把它们本应共享的项目上下文切碎了。 +- **横跨多个项目的开发。** 一次跨仓库的改动没有一等的关系表达,唯一可用的关联是弱引用 `external_refs`。 + +两者同出一因:`scope_id` 被三种语义重载,而*共享*语义希望 scope **相同**、*隔离*语义希望 scope **不同**——一个键无法同时 +承担两者。 + +| 语义 | 谁依赖它 | 它作为什么的键 | 希望 scope 是… | +| --- | --- | --- | --- | +| **项目**(共享单元) | Codex 集成、README | 由仓库 Git remote(或路径)派生的项目级上下文 | **相同**(以共享) | +| **记忆隔离单元**(Runtime 分区键) | Core Runtime | 一条 Source journal + 一个 Memory head + 一组 Trigger cursor | ——(机械性) | +| **Workstream**(隔离单元) | Handoff、RFC 0048/0082 | 一条线性 Handoff 历史 | **不同**(以隔离) | + +本 RFC 通过把第一行的*共享*语义上移到 Project 层、把*隔离*语义留在 `scope_id` 上来解决一、三行的冲突。(系统今天其实已把 +二者当作两层未打通地运行——Core/Runtime/Memory 里是扁平的 `scope_id`,Handoff Report 里是 `Project → Workstream(≡scope_id)` +目录;见「现有工作」。)跟踪 Issue 的承重不变量——保留跨 Session 的共享上下文、保持 Workstream 历史隔离、绝不把 Session 等同于 +Workstream、不假定当前 `project_id` 是最终形态、把迁移视为后续——在下文逐条保留。 + +# 指南级说明(Guide-level explanation) + +四个概念;其绑定定义即参考级说明中的不变量 I1–I8,这里只给心智模型。 + +- **scope** —— 不透明、集成方所有的分区字符串(≤256 字符),与 RFC 0019 一致。一个 scope 作为键选一条 Source journal + + 一个 Memory head + 一组 Trigger cursor。 +- **Workstream** —— 一条可继续的工作线(单一目标、单条线性 Handoff 历史)。**它的身份*就是*其 `scope_id`**;无独立 + `workstream_id`。当且仅当两段工作可各自独立继续时,它们才是不同的 Workstream、使用不同的 scope(RFC 0082 的判据);分支 + 切换/改名/rebase 复用同一个 scope。 +- **Project** —— 对 Workstream 的显式、应用层分组(一个仓库、服务或长期项目),带不可变的 `project_id`。它是*聚合*(RFC + 0082,已有)与*共享上下文*(新增)的边界,且**永不是** Handoff scope。 +- **Session** —— 在一个工作区间内读取/准备/接收上下文的短暂参与者边界。永不是实体、身份或 Workstream;仅以可选的 + `session_id` 归属出现。 + +默认行为是**共享、不隔离**:`derive_scope_id` 只依据 Git remote/路径、不读取分支,因此换分支解析到*同一个* scope。若要把某段 +工作作为独立 Workstream 推进,就**显式声明**一个新 scope(例如用 git worktree 配置专属的 `POWERCONTEXT_CODEX_SCOPE_ID`);分支 +永不作为分区键。声明入口留待后续设计。 + +**什么*不会*开启新 Workstream。** 另开一个 CLI、终端或 Agent 进程,是一个新 *Session*,而非新 Workstream(I6)。指向*同一个* +scope 的两个 CLI,是同一条 Workstream 上的两个 Session——它们共享那唯一的 Memory head 与唯一的线性 Handoff 历史,并发提交由 +RFC 0048 的 CAS 冲突兜底,而不是分叉出第二条历史。新 Workstream 只在「*声明了不同的 scope*」时出现;判据永远是「能否各自独立 +继续」(不同目标、不同线性历史),而不是「是不是换了个进程、分支或 CLI」。所以:同一个声明 scope 上的两个 CLI = 一条 +Workstream;两个不同声明 scope 上的两个 CLI = 两条 Workstream——CLI 从来不是决定因素。 + +``` +Project (project_id —— 服务端所有的分组;共享上下文 + 聚合;永不是 Handoff scope) + ├── Workstream A (scope_id = git:host/repo#featureX) ── 隔离的 Sources / Memory / Handoff / Stats + ├── Workstream B (scope_id = git:host/repo#refactorY) ── 隔离 ... + └── Workstream C (scope_id = git:host/repo) ── 隔离 ... + ▲ + └─ 共享的 Project 上下文可从任意 Workstream 的 Session 读取, + 但每个 Workstream 已提交的 Handoff 历史保持私有。 + +Session (短暂;把活动归属到某个 Workstream;永不作为身份) +``` + +`#featureX` / `#refactorY` 仅示意*显式声明*的 scope,并非由分支派生——默认 `derive_scope_id` 只会得到 `git:host/repo`(即 +Workstream C)。隔离位于 scope 层(不变),共享位于 Project 层(新增、加法式、可选启用)。现有单 Workstream 用户不受影响:一个 +未分组的 scope 行为与今天完全一致。 + +# 参考级说明(Reference-level explanation) + +自此处起直到「缺点」一节,规约的都是**备选 B**——即被采用的模型。这并非中立综述:下文的不变量、数据模型与解析都属于 B。 +凡**备选 A** 会走上实质不同路径之处,都以*换作 A:……*就地标出;A 与 B 的完整对照见「理由与备选方案」。 + +## 模型与不变量 + +以下八条不变量(I1–I8)是本 RFC 的有约束力决策;文档其余内容皆由它们推导而来。 + +- **I1 —— scope 不透明且 Runtime 所有。** 与 RFC 0019 一致:非空字符串 ≤256 字符;Runtime 不从中解析结构。(不改动 + `validate_scope_id`、`MAX_SCOPE_ID_LENGTH` 或 OpenAPI 的 pattern。) +- **I2 —— Workstream 身份即 `scope_id`。** 本版不引入 `workstream_id`。Workstream 的 Sources、Memory、Handoff、 + Statistics 按其 scope 分区并与其他 Workstream 隔离。RFC 0048/0082 的「每 scope 一条线性历史」与 CAS 冲突保证原样保留。 + *换作 A:* 唯一会翻转的正是这条——`scope_id` 变为路由键,改由新增的 `workstream_id` 承载身份;I2 之后的每条不变量都属于 + B,因为 B 把身份留在 scope 上。 +- **I3 —— Project 是应用层分组,而非 Core 概念。** `project_id` 服务端所有且不可变;仅存在于 Builtin Runtime 应用层。 + Core Protocol(RFC 0002;`core-protocol.md`)仍完全不知道 scope、Project、Workstream、Session。Project 成员关系**不建立 + 到 Core 表(Sources、Artifacts、Memory、Handoff)的外键**,与既有 `catalog_store` 的决策一致。 +- **I4 —— 一个 scope 至多属于一个 Project。** 把一个 scope 注册到第二个 Project 是冲突(`scope_already_grouped`),与 + RFC 0082 一致。一个 scope 也可以不属于任何 Project(未分组),此时行为与今天完全一致。 +- **I5 —— Project 不是 Handoff scope。** Project 永不拥有 Handoff 历史、永不接收已提交的 Handoff、永不写回某个 + Workstream。Handoff 与 Continue 仍是 scope 绑定的(RFC 0048)。 +- **I6 —— Session 短暂且非身份。** Session 永不作为域身份持久化,永不决定某个 Workstream。它既不等于 Workstream,也不等于 + scope。 +- **I7 —— Branch 不是身份轴。** 与 RFC 0082 一致:分支元数据是弱信号与不可信的活动归属,永不作为 Handoff 边界。 +- **I8 —— Workstream 边界是被声明的、语义的,而非从分支或任务派生。** 决定因素是「能否独立继续」(I2 / RFC 0082), + 不是 git 分支或任务数量。默认是每个仓库(其 Git remote)一个 scope;显式声明开启一条独立的 Workstream。分支与 git + worktree 都不改变派生出的 scope——只有显式的 `POWERCONTEXT_CODEX_SCOPE_ID`(或不同的 remote)才会。 + +## 共享 Project 上下文(新增能力) + +今天 RFC 0019 通过 `MemoryBindingStore` 持久化一个严格的 `scope_id → Memory Artifact` 一对一绑定,而 RFC 0019 本身已点明 +扩展路径:「支持多个实例需要对 application mapping 做显式扩展」。本 RFC 正是使用这一接缝。下面的机制固定扩展的*形状*;排序/ +合并策略与写入表层仍为待定(见下)。 + +### 数据模型 + +- **per-Workstream 绑定(不变)。** `MemoryBindingStore` 保持其 `scope_id → Memory Artifact` 一对一映射。每个 Workstream + 仍恰好拥有一个 Memory head,与其他任何 scope 隔离。 +- **Project 上下文绑定(新增)。** 一个 Project **可以**额外拥有一个 Project 级绑定 `project_id → Memory Artifact`,解析到 + 一个独立于任何 Workstream 的 Memory Artifact。这就是 **Project 上下文**。它是*应用层映射里的第二行,而非某个 scope 上的 + 第二个 head*:没有任何 `scope_id` 获得第二个绑定,per-scope 的一对一不变量不受触动。 +- 两个绑定都只存在于 Builtin Runtime 应用层。两者都不向 Core 表引入外键(I3);Core 仍只从一个不透明键解析 Memory + Artifact,完全不知道 Project。 + +完整模型是三条应用层目录记录 + 那条不变的 per-scope head——只有最后一行是新增的: + +| 记录 | 形状 | 基数 | 来源 | +| --- | --- | --- | --- | +| per-scope Memory head | `scope_id → Memory Artifact` | 每个 scope 一条 | RFC 0019 `MemoryBindingStore`(不变) | +| Project | `{ project_id, project_key, title }` | 每个 Project 一条 | RFC 0082 目录 | +| 成员关系 | `scope_id → project_id` | 每个 scope ≤ 1(I4) | RFC 0082 Workstream 目录(`WorkstreamDescriptor`) | +| Project 上下文 | `project_id → Memory Artifact` | 每个 Project ≤ 1 | **新增(本 RFC)** | + +scope 自身的 head 直接解析;它的 Project 上下文间接解析——先沿成员关系找到 `project_id`,再解析 Project 上下文绑定。两个 +Memory Artifact 始终是不同的句柄。 + +### 存储接缝 + +Project 上下文绑定与既有的 Project 目录(RFC 0082 的 `catalog_store`)一起持久化,以 `project_id` 为键。解析它复用 +per-scope head 已在使用的同一套 Memory Artifact 机制——唯一新增的是*哪个键选中哪个 Artifact*,而非 Memory Artifact 如何存储、 +修订或检索。不改动任何 Core Memory 表、修订格式或检索路径。 + +### 解析(Resolution) + +每一次读与写都从请求的 `scope_id` 确定性地解析出来,调用表层不新增任何必填参数: + +1. **定位 scope。** 应用层在目录里查 `scope_id`。 +2. **未分组 → 一个绑定。** 若该 scope 不属于任何 Project,解析止于其自身的 `scope_id → Memory Artifact` head——逐字节即 + 今天的路径。不查询、也不创建任何 Project 上下文。 +3. **已分组 → 两个绑定。** 若该 scope 带有指向某 `project_id` 的成员关系,则额外解析该 Project 的上下文绑定。若该 Project + 尚无上下文绑定,则退化为未分组情形(只有自身 head)。 +4. **读与写。** *读*把两个解析出的 head 交给下面的组合步骤;*写*只落到该 scope 自身的 head(I2、I5)——本版中 Project + 上下文永不出现在写路径上。 + +「哪个键选中哪个 Artifact、按什么顺序解析」在此完全已定。只有「两个已解析的读 head 如何排序与合并」留待后续——即下一子节。 + +### 读取组合(形状已定,策略留待定) + +当某 Session 为一个「其 scope 已归组到某 Project」的 Workstream 准备上下文时,应用层解析**两个** Memory head——该 Workstream +自身 scope 级的 head 与 Project 上下文——并把它们组合进准备好的上下文。这里有两点是**已决**的: + +- **两者皆可读。** Workstream 自身的 head 总会被读取;Project 上下文*可从*任一成员 Workstream 的 Session *读取*。 +- **组合是只读叠加。** 组合永不写入任一绑定,永不把一个 Workstream 的条目复制到另一个,也永不改动 Project 上下文。隔离在 + *结构上*被保留——两个 head 仍是两个独立的 Artifact,因此任何 Workstream 的私有历史都不可能落入另一个的存储。 + +仍为**待定**(推迟到 Memory/Context 后续工作):两个 head 之间的优先级、当两者都浮现某条目时的合并/去重/排序规则、以及读取 +时如何界定跨 Workstream 泄漏。本 RFC 固定「组合发生在两个隔离的 Artifact 之上」;它不固定它们*如何*排序。 + +### 写入隔离 + +Workstream 已提交的 Handoff 历史永不写入 Project,Project 也永不写回 Workstream(I5)。Session 是否能*写入* Project 上下文、 +通过哪个操作、带何种信任标记,推迟到后续;本版默认 **Project 上下文只做读共享**。 + +### Context Pack(RFC 0028) + +Context Pack 保持其「一次请求一个 scope、不混合 scope」的契约不变。若要通过 `prepare_context` 暴露 Project 级读取,需要一个 +新的契约版本或一个显式的 Project 参数;本 RFC 不修改既有单 scope 契约,把该表层选择留给后续。 + +## Project 实体与注册 + +Project 是 Builtin Runtime 应用层里的一条目录记录(RFC 0082 的 `ProjectDescriptor`,位于 `catalog_store`),而非 Core +实体(I3)。它承载三个字段: + +- `project_id` —— 服务端生成、不可变(`prj_`),持久身份。永不由客户端提供,退休后永不复用。 +- `project_key` —— 目录内唯一的人类可读键(如 `acme/api`),用于在不知道 `project_id` 时查找 Project。 +- `title` —— 可变的展示标签;改动它永不改变身份。 + +**注册。** 一个 scope 通过在 Workstream 目录里把其 `scope_id` 绑定到某个 `project_id` 而成为 Project 成员(既有的 +`WorkstreamDescriptor` / `create_workstream` 接缝,以 `scope_id` 为键)。把一个已归组到*不同* Project 的 scope 再次绑定, +就是既有的 `scope_already_grouped` 冲突(I4)——一个 scope 至多一个 Project。**未分组**的 scope 是默认且无需注册:它照旧完全按今天的方式拥有自己的 Sources/Memory/Handoff/Statistics,且不读取任何 +Project 上下文。 + +**归组状态。** 一个 scope 恰处于两态之一,本版只定义两态之间的一条转移: + +- **未分组** —— 无成员关系记录;行为与今天完全一致。 +- **归组于 P** —— 成员关系 `scope_id → P`;额外读取 P 的 Project 上下文。 + +`未分组 → 归组(P)` 即上述注册。改挂(`归组(P) → 归组(Q)`)与解组(`归组 → 未分组`)在此**均不定义**——对已归组的 scope 再向 +不同 Project 注册会被 `scope_already_grouped` *拒绝*,绝不静默改挂(见待定:在 Project 间移动 scope)。归组一个 scope 永不重写、 +移动或合并它既有的 per-scope Memory 或 Handoff 历史;它只是新增「可读取 P 的 Project 上下文」这一能力。 + +**生命周期。** `title` 可变;`project_id` 在 Project 的整个生命周期内不可变。一旦至少有一个 scope 绑定到它(或后续若新增显式 +创建入口,一旦被显式创建),Project 即存在。在 Project 间移动 scope、以及合并或拆分 Project,均**不在本 RFC 范围内**(列入待定 / +后续工作)——本版只固定「一个 scope 至多属于一个 Project」以及「成员关系是一条应用层绑定」。 + +## 端到端示例:同一 Project 内的两条并行 Workstream + +某团队开发 `acme/api` 服务。他们注册了一个 Project: + +- `project_id = prj_ac…`、`project_key = acme/api`、`title = "ACME API"`。 + +两条工作线并行推进,且不得共享同一条 Handoff 历史: + +- **W-main** —— 在默认 checkout 上进行的持续缺陷修复。`derive_scope_id` 只读取 Git remote,因此其 scope 是 + `git:github.com/acme/api`。不编码任何分支。 +- **W-refactor** —— 一次必须可独立继续的长期存储重写。同一仓库的 worktree 共享*同一个* Git remote,否则会派生出与 + W-main *相同*的 scope;因此团队通过 `POWERCONTEXT_CODEX_SCOPE_ID = git:github.com/acme/api#storage-rewrite` **显式声明** + 一个独立 scope,`derive_scope_id` 会逐字采用。worktree 只是让两个 checkout 在磁盘上共存——*仅凭那个被声明的 scope* 才 + 产生隔离,而 `#storage-rewrite` 后缀是*声明*出来的,并非从分支派生(I8)。 + +两个 scope 都绑定到 `prj_ac…`。于是: + +- **读取。** W-refactor 上的一个 Session 准备上下文,解析**两个** head——W-refactor 自身 scope 级的 Memory head ⊕ 绑定到 + `prj_ac…` 的 Project 上下文——以只读叠加组合。它因此能看到项目级的共享知识(构建怪癖、服务约定),却**看不到** W-main + 在途的缺陷修复历史。W-main 上的 Session 对称地看到自身 head ⊕ 同一个 Project 上下文。 +- **写入。** 每个 Session 已提交的 Handoff 只追加到自身 scope 的那条线性历史:W-refactor 的 Handoff 永不进入 + `git:github.com/acme/api`,两个 Workstream 也都不写回 Project(I5)。本版中 Project 上下文只做读共享。 + +两个不变量**同时**成立:共享(两个 Session 读取同一个 Project 上下文)与隔离(两条独立的线性 Handoff 历史、两个独立的 Memory +head)——这正是单个被重载的 `scope_id` 无法表达的。一个未分组的 `acme/api` checkout(未绑定 Project)则只读取自身 head、不读取 +任何 Project 上下文,行为与今天完全一致。 + +## 验收场景(Acceptance scenarios) + +把模型钉到可观察行为上。每条只断言*结构*,不断言留待定的读取组合策略。 + +| # | Given | When | Then | 不变量 | +| --- | --- | --- | --- | --- | +| 1 | scope 未绑定任何 Project | 某 Session 准备上下文 | 只解析出自身 `scope_id → Artifact` head;不读取也不创建 Project 上下文——逐字节即今天 | I4、解析② | +| 2 | scope 归组于 `P`,且 `P` 有上下文绑定 | 某 Session 准备上下文 | 解析出两个**不同**句柄(自身 head ⊕ `P` 的上下文),以只读叠加组合 | 解析③ | +| 3 | scope 归组于 `P`,但 `P` 尚无上下文绑定 | 某 Session 准备上下文 | 退化为未分组情形(只有自身 head) | 解析③ | +| 4 | 同一 `P` 下的两条 Workstream | 其一提交 Handoff | 只追加到自身 scope 的线性历史;兄弟的历史与 `P` 的上下文永不被写入 | I2、I5 | +| 5 | 同一 `P` 下的两条 Workstream | 其一的 Session 读取 | 能看到 `P` 的共享上下文,但**看不到**兄弟的 in-flight head(两个 Artifact 始终独立) | 读取组合(结构) | +| 6 | scope 已归组于 `P` | 再注册到 `Q` | `scope_already_grouped` 冲突;绝不静默改挂 | I4 | +| 7 | 一个仓库 checkout | 切换 git 分支 | 解析到**同一个** scope;不产生新 Workstream | I7、I8 | +| 8 | 任意 Project 成员关系 | 检查 Core 表 | 没有指向 `project_id` 的 Core 外键;Core 仍只从不透明键解析 Artifact | I3 | + +场景 2 与 5 刻意只断言「解析出两个隔离的 Artifact」——两者之间的优先级/合并顺序为待定(见下),现在不得由测试钉死。 + +## 与既有子系统的交互 + +- **Memory(RFC 0019)。** per-scope 绑定不变。通过文档化的 application-mapping 扩展新增可选的 Project 级绑定。不改动 + Core Memory、Revisions 或检索。 +- **Handoff(RFC 0048)。** 完全不变。每 scope 一条线性历史;CAS 冲突;Prepared 与 committed;evidence/Continue 的 + `untrusted_history` 语义全部保留。Workstream 仍 ≡ scope。 +- **Handoff Report(RFC 0082)。** 予以协调,且大体重申。Project → Workstream 目录、`WorkstreamDescriptor`、 + `WorkspaceBinding`、活动存储与 `handoff-reports` API 保持现有形态。唯一的澄清是:Project 现在除了*聚合*之外,还承载 + *共享上下文*;RFC 0082 的「只聚合、绝不写回」规则得以保留(Project 上下文是独立的读绑定,而非 Handoff 的写回)。 +- **Statistics(RFC 0072)。** per-scope 统计不变。可选的 Project/Workstream 汇总**推迟**到后续(本 RFC 现在不要求跨 + scope 聚合,但记录它是聚合的自然落点)。 +- **Core Protocol(RFC 0002)。** 不变,且对 Project/Workstream 明确越界(I3)。 + +## 兼容性 + +- **存量数据。** 无需重写。现有 scope 的 Sources、Artifacts、Memory、Handoff、Statistics 保持不变。归组与 Project 上下文 + 都是加法式;未分组的 scope 不读取任何 Project 上下文,行为与今天完全一致。 +- **API。** 每个既有的以 `scope_id` 为键的接口(Memory、Handoff、stats、sources、context)契约不变。新的 Project 上下文 + 读取是加法式、可选的。没有任何既有请求形态改变含义。 +- **CLI。** 既有的 `--scope-id` 命令不变。 +- **Codex 集成。** `derive_scope_id`(Git remote → 路径回退)不变。`POWERCONTEXT_CODEX_SCOPE_ID` 保持其含义。把派生出的 + scope 注册到某个 Project 之下是一个额外的、可选的步骤。 +- **弃用。** 本 RFC 无。 + +## 身份编码(不变的表层,供参考) + +- `scope_id`:不透明字符串 ≤256,客户端提供;由 Git remote 派生,否则回退为 `local:` 路径哈希; + `POWERCONTEXT_CODEX_SCOPE_ID` 逐字覆盖。 +- `project_id`:服务端生成且不可变(`prj_`),目录所有。 +- `project_key`:目录内唯一的人类可读键;`title`:可变展示名。 +- 本版无 `workstream_id`。 + +# 缺点(Drawbacks) + +- **并行工作仍需派生多个 scope。** 由于 Workstream 仍 ≡ scope,表达并行工作是一种命名/派生纪律(同一 Project 下的不同 + scope),而不是一等的「一个 Workstream、多条分支」身份。想让单个 Workstream 横跨多分支或多仓库的团队,本版不予支持。 +- **跨 Project 工作仍建模薄弱。** 横跨多个 Project 的开发仍依赖 `external_refs`,而非一等的跨 Project 关系。本 RFC 不弥合 + 这一缺口。 +- **两个 Memory 绑定引入组合问题。** 新增 Project 上下文意味着一个 Session 可能读取两个 Memory 来源;其优先级/合并策略 + 是推迟到后续的实际设计工作,若边界画错,可能通过共享层把一个 Workstream 的私有上下文泄漏到另一个。 +- **将来可能仍想要第二身份。** 通过刻意不引入 `workstream_id`,本 RFC 赌这一延后的扩展是可接受的;若并行分支身份成为硬 + 需求,后续 RFC 必须重开 RFC 0082 的中心身份决策。 + +# 理由与备选方案(Rationale and alternatives) + +考虑了三个方向。**A 与 B 才是两个真正的候选**(C 是不增能力的基线)。两者都从同一个问题出发——`scope_id` 背负三种语义 +——但拆分方式不同。自上而下读这张表:**A 把每种语义各自搬到独立的键上**(三个键),而 **B 把隔离留在 `scope_id` 上,只把 +*共享*语义抬到一个新的、加法式的 Project 层**(一个键 + 一个可选启用的层)。 + +| 设计维度 | 今天(`scope_id`) | **备选 A** —— 三层(延后) | **备选 B** —— Project 层(本 RFC) | +| --- | --- | --- | --- | +| 共享(项目上下文) | `scope_id` | `project_id`(一等) | 新增 **Project 层**(加法式) | +| Runtime 隔离 / 分区键 | `scope_id` | `scope_id`(仅路由) | `scope_id`(不变) | +| Workstream 身份 | `scope_id` | 新增 `workstream_id` | `scope_id`(不变) | +| `project_id` 角色 | 薄,应用层 | 一等身份 | 应用层分组(不变) | +| 一个 Workstream 跨分支/仓库 | 否 | **是**(一等) | 否(派生不同 scope) | +| 存量 scope 迁移 | —— | **需要**(回填每个 scope) | **无** | +| RFC 0082 身份决策 | —— | **推翻**(两条) | 保留 | +| Core Protocol 改动 | —— | 很可能 | 无 | + +### 备选 A —— 引入独立 `workstream_id` 的三层模型(延后) +**做法。** 让每种被混淆的语义各占一个键。`project_id` 成为一等身份(承载共享与聚合);`scope_id` 降级为纯 Runtime 分区/ +路由键,不再*表示*「Workstream」——一个 `project_id` 可映射到多个 scope,scope 只是一个存储分区;新增 `workstream_id` 作为 +Handoff/历史身份,于是单个 Workstream 可横跨多个 scope/分支/仓库,因为它的身份不再骑在分区键上。 + +**换来什么。** 并行与跨仓库工作成为一等:一个 Workstream 可在分支或仓库间移动而不切碎其历史;Branch 与 Session 获得自然的 +非身份位置;共享与隔离因落在不同键上而在构造上永不相争。这是对三种语义最干净的表达。 + +**代价。** 它反转了 RFC 0082 的两处中心决策(「`scope_id` 是唯一的 Workstream 身份」;「不存在独立的 `workstream_id`」); +在 Handoff、Handoff Report、Runtime 全链路加一层每次操作都要穿过的解析/间接(`workstream_id` → scope(s) → Artifacts); +迫使**每个存量 scope 回填迁移**进新的三键模型;并很可能触及 Core Protocol 边界。对第一版而言影响面过大。 + +**结论 —— 延后,而非弃用。** 作为 Future possibility 保留,且可*在 B 之上*触达而不违背 B 的不变量——B 是同一条路上更小的 +第一步。 + +### 备选 B —— 保持 `scope_id` 为 Workstream 身份,新增 Project 共享上下文层(本 RFC,采用) +**做法。** 让隔离原地不动——scope ≡ Workstream,`scope_id` 作为身份与分区键均不变——并把*共享*作为一个新的、加法式、可选 +启用的 Project 上下文绑定(`project_id` → Memory Artifact)加上去。不降级、不重新指派任何键;不引入 `workstream_id`。 + +**换来什么。** 它以**零迁移、无 Core 改动**同时满足 Issue 的两条硬约束(共享上下文*与*隔离历史);它落在既有 RFC 已预留的 +接缝里(RFC 0048 的「parallel workstreams, derived scopes」;RFC 0019 的「通过显式 application-mapping 扩展支持多实例」; +RFC 0082 延后的跨 Project 报表),而非推翻 RFC 0048 的线性历史模型;并让备选 A 作为日后扩展仍可触达。 + +**代价(已接受)。** 并行工作仍是一种 scope 派生纪律,而非一等的「一个 Workstream、多条分支」身份;跨 Project 工作仍建模 +薄弱;两绑定的读取组合策略是推迟到后续的实际工作。这些即上文的「缺点」,作为「小而可回退的第一步」的代价被接受。 + +**结论 —— 采用。** + +### 备选 C —— 仅澄清语义,不新增分组能力 +**做法。** 只在文档层澄清 `scope_id` 的三种混淆含义,规范派生与并行工作的命名约定,并可选地给 Statistics 加 Project 汇总。 +不新增任何被建模的能力。 + +**结论 —— 不采用。** 它并未真正解决 Issue 的核心诉求:并行工作与「共享但隔离」的上下文仍停留在约定,而非被建模保证。 + +**不做的影响。** `scope_id` 继续背负三种含义;团队要么为了隔离而切碎共享上下文,要么为了共享而污染某个 Workstream 的 +历史。RFC 0019/0048/0082 继续编码一个未言明的模型,而每个新特性都必须各自重新发现它。 + +# 现有工作(Prior art) + +- **RFC 0048** 首次断言「每 scope 一个当前 workstream」,并把*并行 workstream*与*派生 scope*明确放进 Future + possibilities —— 本 RFC 所依托的接缝。 +- **RFC 0082** 规定了当前的 `Project → Workstream(≡scope_id)` 目录、「Project 只聚合、绝不写回」规则、以及「branch 非 + 身份」规则;本 RFC 重申这些并新增共享上下文。 +- **RFC 0019** 记录了 `MemoryBindingStore` 的一对一映射,并点明了这里所用的多实例扩展路径。 +- RFC 流程本身(`docs/en/rfcs/README.md`):先与 maintainer 验证问题,把初始范围收窄到可评审、可实现。 + +# 待定问题(Unresolved questions) + +合并前需解决: + +- **上下文组合与优先级。** 当某 Workstream 的 scope 归组到某 Project 时,读取时 Workstream 的 Memory head 与 Project + 上下文究竟如何组合?优先级如何?如何防止跨 Workstream 泄漏?(阻塞 Memory/Context 后续工作。) +- **写入 Project 上下文。** Session 能否写入 Project 上下文、通过哪个操作、带何种信任标记?本版默认只读共享。 +- **Context Pack 表层。** Project 上下文应通过 `prepare_context` 暴露(新契约版本)还是走独立的读路径?(RFC 0028 目前 + 仍保持单 scope。) +- **Workstream 边界的声明(I8)。** 通过什么入口、在何时显式覆盖「每仓库一个」的默认?这需与 RFC 0082 的「并行分支使用不同 + scope」规则、以及当前只按仓库取键的 `derive_scope_id` 对齐。 + +有意排除在外(后续决策,可能需要各自的 RFC): + +- 引入独立的 `workstream_id`(备选 A)。 +- 超越 `external_refs` 的一等跨 Project 关系;Project 之上的 Portfolio/Program 实体。 +- 跨 Project 或 Workstream 组的 Statistics 汇总。 +- 在 Project 间移动、合并、拆分 Workstream。 +- 迁移工具与详细的用户流程编排。 + +# 未来可能性(Future possibilities) + +- **备选 A 作为扩展。** 若并行分支或跨仓库的 Workstream 身份成为硬需求,后续 RFC 可在本模型之上引入 `workstream_id`,把 + `scope_id` 降级为路由键——在不违背此处所设不变量的前提下即可触达。 +- **跨 Project 聚合。** Portfolio/Program 级读取与跨 Project 的 Handoff Report(RFC 0082 已延后)。 +- **Project 级 Statistics 汇总**(RFC 0072 的自然扩展)。 +- **更丰富的 Session 语义**(并发信号、实时状态提示),只要 Session 仍保持非身份即可——包括*同一 scope 上的并发 Session* + 如何在 RFC 0048 的 CAS 冲突之外协调(即「换个 CLI 是否开启新 Workstream」的开放残留:答案是否,但同一 scope 上并发的多个 + CLI 仍可能想要实时状态信号)。 From b5751541cac89cd1f46117e3c30afbd7a3c3036b Mon Sep 17 00:00:00 2001 From: YuZhangLarry Date: Fri, 14 Aug 2026 14:41:55 +0800 Subject: [PATCH 2/2] docs(rfc): rename to PR number 1238 --- ...scope_model.md => 1238_development_context_and_scope_model.md} | 0 ...scope_model.md => 1238_development_context_and_scope_model.md} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename docs/en/rfcs/{0000_development_context_and_scope_model.md => 1238_development_context_and_scope_model.md} (100%) rename docs/zh/rfcs/{0000_development_context_and_scope_model.md => 1238_development_context_and_scope_model.md} (100%) diff --git a/docs/en/rfcs/0000_development_context_and_scope_model.md b/docs/en/rfcs/1238_development_context_and_scope_model.md similarity index 100% rename from docs/en/rfcs/0000_development_context_and_scope_model.md rename to docs/en/rfcs/1238_development_context_and_scope_model.md diff --git a/docs/zh/rfcs/0000_development_context_and_scope_model.md b/docs/zh/rfcs/1238_development_context_and_scope_model.md similarity index 100% rename from docs/zh/rfcs/0000_development_context_and_scope_model.md rename to docs/zh/rfcs/1238_development_context_and_scope_model.md