@sema-agent/core 5.35.0 → 5.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/dist/agents/subagent.d.ts +10 -0
  3. package/dist/agents/subagent.js +29 -2
  4. package/dist/core/auto-compaction.d.ts +23 -0
  5. package/dist/core/auto-compaction.js +8 -0
  6. package/dist/core/checkpoint-store.d.ts +16 -0
  7. package/dist/core/context-guard.d.ts +41 -0
  8. package/dist/core/context-guard.js +76 -0
  9. package/dist/core/governance-codes.js +4 -0
  10. package/dist/core/memory-engine/engine.d.ts +142 -0
  11. package/dist/core/memory-engine/engine.js +265 -3
  12. package/dist/core/memory-engine/file-backend.d.ts +490 -16
  13. package/dist/core/memory-engine/file-backend.js +1099 -36
  14. package/dist/core/memory-engine/index.d.ts +2 -2
  15. package/dist/core/memory-engine/index.js +1 -1
  16. package/dist/core/memory-engine/layout.d.ts +42 -2
  17. package/dist/core/memory-engine/layout.js +76 -12
  18. package/dist/core/memory-engine/memory-backend-contract.d.ts +13 -0
  19. package/dist/core/memory-engine/memory-backend-contract.js +89 -0
  20. package/dist/core/park-selfcheck.d.ts +5 -0
  21. package/dist/core/protocol-table.d.ts +4 -4
  22. package/dist/core/runner/assemble-result.d.ts +8 -0
  23. package/dist/core/runner/assemble-result.js +4 -1
  24. package/dist/core/runner/git-status-frame.d.ts +219 -0
  25. package/dist/core/runner/git-status-frame.js +212 -0
  26. package/dist/core/runner/prepare-memory.d.ts +11 -1
  27. package/dist/core/runner/prepare-memory.js +48 -2
  28. package/dist/core/runner/prepare-task.d.ts +21 -0
  29. package/dist/core/runner/prepare-task.js +28 -35
  30. package/dist/core/runner/runtask.js +270 -5
  31. package/dist/core/task-registry-agent.d.ts +15 -0
  32. package/dist/core/task-registry-agent.js +9 -0
  33. package/dist/core/task-registry.d.ts +3 -0
  34. package/dist/core/task-registry.js +4 -1
  35. package/dist/core/types.d.ts +122 -7
  36. package/dist/engine/harness/types.d.ts +65 -1
  37. package/dist/engine/harness/types.js +20 -0
  38. package/dist/engine/session/import-validate.js +10 -1
  39. package/dist/engine/session/session.d.ts +37 -1
  40. package/dist/engine/session/session.js +56 -1
  41. package/dist/index.d.ts +2 -2
  42. package/dist/index.js +1 -1
  43. package/dist/internal/harness-types.d.ts +1 -0
  44. package/dist/internal/harness.d.ts +2 -0
  45. package/dist/internal/harness.js +2 -0
  46. package/dist/prompt-assembly/epoch.js +1 -1
  47. package/dist/prompt-assembly/event-registry.js +1 -0
  48. package/dist/prompts/default.d.ts +20 -7
  49. package/dist/prompts/default.js +2 -7
  50. package/package.json +1 -1
  51. package/test/export-surface.snapshot.json +13 -1
@@ -2713,6 +2713,79 @@ export interface RemoteEnvFailureNote {
2713
2713
  message: string;
2714
2714
  }
2715
2715
  /** Result returned when a task finishes or gets stuck. Designed to be machine-readable for an external AI. */
2716
+ /**
2717
+ * design/178 v2 §2.3 (件①) — the value of {@link TaskResult.effectiveMemoryScopes}: the memory
2718
+ * visibility face a leg ACTUALLY ran under, as a DISCRIMINATED three-state union (deliberately no
2719
+ * `"partial"` state — the engine session is built only after every plane materialized, so a
2720
+ * half-mounted scene is a fail-open `memoryless` with residue, never a served half-face):
2721
+ *
2722
+ * - `"mounted"` — the memory engine session mounted. `scopes` is the EFFECTIVE VISIBILITY set in
2723
+ * the effective serving order, both exactly as the engine mounted them: per plane, the
2724
+ * admission-projected read layering PLUS a distinct write-only scope where one exists (the
2725
+ * engine registers the write scope's dir and its entries ride the injected index — a write-only
2726
+ * scope IS visible; the common writeScope∈scopes form dedups to the plain layering), project
2727
+ * plane first on a dual root. Each row carries its admission ORIGIN (`"deployment"` = the
2728
+ * operator's own declared set; `"request"` = the caller's spec — the two trust planes of the
2729
+ * org admission door). `writeScope` is the effective write face as granted (an org writeScope
2730
+ * not explicitly granted reads `null` here, exactly as it ran). `contract` names the
2731
+ * scope-identity contract in force (`"v2"` typed keys / `"legacy"` opaque strings).
2732
+ * SCOPE OF THE CLAIM (stated precisely): the rows are the MEMORY-ENGINE scope set this leg
2733
+ * mounted — the dirs the engine registered and indexed. They deliberately do NOT cover the
2734
+ * separate memory-adjacent faces (the shared-memory store pair, the project-context memory
2735
+ * layer — each its own surface with its own disclosure), and two REGISTERED engine-plane
2736
+ * channels can carry another scope's bytes without a row here: a root-owning scope's non-empty
2737
+ * on-disk index is served verbatim to a later mount of that root ("live file wins"),
2738
+ * and control-plane announcements carry no producing scope and drain plane-wide. Both are
2739
+ * engine mount semantics under their own tickets, disclosed here so this face is never read as
2740
+ * a complete cross-face visibility proof.
2741
+ * - `"memoryless"` — memory was configured but did not mount. `reason: "mount-failed"` = the
2742
+ * fail-open mount arm caught a fault anywhere in the mount phase (directory resolution through
2743
+ * materialize — named for the whole captured span, not one step); `reason: "no-backend"` = the
2744
+ * spec enables memory but no `RunnerDeps.memoryBackend` is configured. `materializedResidue`
2745
+ * (mount-failed only) lists plane scopes whose PHYSICAL materialize had already completed when
2746
+ * the fault hit — a LOWER bound (a plane that threw mid-materialize is not listed; on-disk
2747
+ * residue is ≥ this list), and explicitly NOT a visibility face: an auditor must never read
2748
+ * residue as mounted scopes, which is why it is a separate seat from the always-empty `scopes`.
2749
+ * - `"none"` — memory was not in play at all: `"no-spec"` = the task carries no usable memory
2750
+ * spec; `"disabled"` = a spec is present with `enabled: false`.
2751
+ *
2752
+ * Deliberate-refusal configurations (`config.memory_*` codes) fail the whole prepare and produce
2753
+ * NO observation — this union never dresses a refusal as a state. Honesty note (design r7): the
2754
+ * `?: never` members forbid non-`undefined` values at the type level; the repo does not compile
2755
+ * with `exactOptionalPropertyTypes`, so explicit-`undefined` presence is a wire-validator concern,
2756
+ * not a type-level one.
2757
+ */
2758
+ export type EffectiveMemoryScopes = {
2759
+ state: "mounted";
2760
+ reason?: never;
2761
+ contract: "v2" | "legacy";
2762
+ scopes: Array<{
2763
+ scope: string;
2764
+ origin: "deployment" | "request";
2765
+ }>;
2766
+ writeScope: string | null;
2767
+ materializedResidue?: never;
2768
+ } | {
2769
+ state: "memoryless";
2770
+ reason: "mount-failed";
2771
+ contract?: "v2" | "legacy";
2772
+ scopes: [];
2773
+ writeScope: null;
2774
+ materializedResidue?: string[];
2775
+ } | {
2776
+ state: "memoryless";
2777
+ reason: "no-backend";
2778
+ contract?: "v2" | "legacy";
2779
+ scopes: [];
2780
+ writeScope: null;
2781
+ } | {
2782
+ state: "none";
2783
+ reason: "no-spec" | "disabled";
2784
+ contract?: never;
2785
+ scopes: [];
2786
+ writeScope: null;
2787
+ materializedResidue?: never;
2788
+ };
2716
2789
  export interface TaskResult {
2717
2790
  taskId: string;
2718
2791
  /** Use this to continue the same conversation on the next call. */
@@ -2972,6 +3045,28 @@ export interface TaskResult {
2972
3045
  * governing the WORK, never the contents of any checkpoint row.
2973
3046
  */
2974
3047
  effectiveReadDenyPatterns?: readonly import("../tools/fs/read-deny.js").NormalizedReadDenyEntry[];
3048
+ /**
3049
+ * design/178 v2 §2.3 (件①) — the memory VISIBILITY face this leg actually ran under, as an
3050
+ * engine-filled OBSERVATION (never a knob: writing it on a spec does nothing). This is the
3051
+ * EFFECTIVE face, not the requested one: a refused request never reaches a terminal at all (the
3052
+ * whole prepare fails with its governance code), so what this seat answers is "what was actually
3053
+ * given" — the delta against the request is directly readable (an org writeScope not explicitly
3054
+ * granted collapses to `null` here as it did in the run; a fail-open mount failure reads
3055
+ * `memoryless`, never a dressed-up mount).
3056
+ *
3057
+ * **In-presence condition** (same law as {@link effectiveReadFace}): present on every terminal of
3058
+ * a leg that COMPLETED prepare — the memory-less states are answered as their own values
3059
+ * (`none` / `memoryless`), so consumers must never read ABSENCE as "no memory"; absence means
3060
+ * only "prepare never completed". Delegated children mint their own on their own legs (their
3061
+ * request plane can only narrow the parent's frozen org verdict); a resume leg's value is the
3062
+ * re-adjudication at resume time (admission runs in every prepare — the current-policy reading,
3063
+ * same axis as the read-face seats).
3064
+ *
3065
+ * Minted AFTER the materialize outcome, not after the admission verdict — the fail-open mount
3066
+ * arm sits between the two, and stamping earlier would report a mount that never happened.
3067
+ * See {@link EffectiveMemoryScopes} for the per-state field law.
3068
+ */
3069
+ effectiveMemoryScopes?: EffectiveMemoryScopes;
2975
3070
  /**
2976
3071
  * `turns`/`tokens`/`costMicroUsd` are this task's OWN model usage. `nested` is the summed usage of any
2977
3072
  * delegated sub-runs (sub-agents) it spawned — present only when it delegated. The true total
@@ -3647,9 +3742,15 @@ export type TaskEvent = ({
3647
3742
  * (one event per attachment; a multi-attachment boundary still coalesces into ONE steer message
3648
3743
  * on the model lane). [c209-C]: the listing family's FIRST-FRAME deliveries (initial roster /
3649
3744
  * `<skills>` block riding the first user message, not a steer) emit the same echo frames.
3745
+ *
3746
+ * `git_status` (env-tail migration, additive member): the git-status frame — the turn-dynamic
3747
+ * git facts' carrier since they left the system prompt. Emitted at the frame's append RECEIPT
3748
+ * (first-frame / resume-continuation / compaction re-assert deliveries alike). Its `preview`
3749
+ * is a CONSTANT wording on purpose (never frame bytes): branch names and status text are
3750
+ * repo-controlled and must not enter the event telemetry plane through this echo.
3650
3751
  */
3651
3752
  type: "steering_injected";
3652
- source: "limit_approach" | "todo_reminder" | "task_reminder" | "tool_search_usage_reminder" | "changed_files" | "plan_mode" | "date_change" | "instructions_change" | "workflow_size_guideline_change" | "budget_usd" | "background_tasks" | "tools_delta" | "agent_listing" | "skills_listing" | "mcp_instructions" | "mcp_dropped_tools" | "final_verification";
3753
+ source: "limit_approach" | "todo_reminder" | "task_reminder" | "tool_search_usage_reminder" | "changed_files" | "plan_mode" | "date_change" | "instructions_change" | "workflow_size_guideline_change" | "budget_usd" | "background_tasks" | "tools_delta" | "agent_listing" | "skills_listing" | "mcp_instructions" | "mcp_dropped_tools" | "final_verification" | "git_status";
3653
3754
  preview: string;
3654
3755
  } & TaskEventIdentity) | ({
3655
3756
  /**
@@ -3790,6 +3891,18 @@ export type TaskEvent = ({
3790
3891
  * read absence as "not an agent".
3791
3892
  */
3792
3893
  taskType?: DelegationTaskType;
3894
+ /**
3895
+ * #258 — the registry row's STOP-CYCLE generation this tick reports from (fresh
3896
+ * spawn = 1, every launched revival bumps it), the same counter `TaskNotificationPayload.seq`
3897
+ * and `BackgroundChildEvent.seq` already speak — one axis, not a third spelling. It answers
3898
+ * the one question a fleet consumer cannot otherwise decide when a frame arrives late: "late
3899
+ * first frame of the cycle I know (same value), or a revived run I have not folded yet
3900
+ * (higher value)?" Advisory observation, stamped at spawn/revive from the registry's own
3901
+ * counter; the settle-time ledger stays the authority. ABSENT is a fact, not a gap: a run
3902
+ * with no `a*` registry row (a SYNCHRONOUS delegated child, a workflow `wa*` agent, a
3903
+ * top-level run) has no generation concept, and absence must never be read as "cycle 1".
3904
+ */
3905
+ seq?: number;
3793
3906
  /** [1611] workflow-lane self-identification (server field-proof: the SSE-forwarded tick of a
3794
3907
  * WORKFLOW child previously carried a bare uuid with no workflow identity — indistinguishable
3795
3908
  * from an unknown nested subagent; the fleet lane had `wa*`+workflowRunId but this lane had
@@ -4294,12 +4407,14 @@ export interface BackgroundChildEvent {
4294
4407
  resumable?: boolean;
4295
4408
  /** terminal: the settled status. */
4296
4409
  status?: "completed" | "killed" | "failed";
4297
- /** terminal (PAIR-REVIEW-01 C-3 half): the stop-cycle number — MIRROR of the sibling
4298
- * task_notification's `TaskNotificationPayload.seq` (same settle, same X3 snapshot), so a
4299
- * fleet-lane consumer can key multi-cycle events (`taskId:status[:seq]`) without the notify lane.
4300
- * Same honest downgrade: present whenever a cycle number is knowable (the retain ledger's `cycleSeq`,
4301
- * or the durable row's `seq` on a tier-3 revival / parked resume), absent when neither carrier exists
4302
- * (single-cycle child; forging a period would lie). */
4410
+ /** terminal + spawn/tick (#258 widened the carriers): the stop-cycle number — on a TERMINAL frame
4411
+ * a MIRROR of the sibling task_notification's `TaskNotificationPayload.seq` (same settle, same X3
4412
+ * snapshot); on SPAWN and TICK frames the registry row's generation at emit (fresh spawn = 1, a
4413
+ * revived cycle's bumped counter), so a fleet consumer can tell a LATE first frame from a revived
4414
+ * cycle's frame without waiting for the terminal. One axis with `task_progress`'s `seq` the tick
4415
+ * mirrors the frame's own stamp. Same honest downgrade as ever: present whenever a cycle number is
4416
+ * knowable (the registry handle / retain ledger's `cycleSeq`, the durable row's `seq` on a tier-3
4417
+ * revival / parked resume), absent when no carrier exists (forging a period would lie). */
4303
4418
  seq?: number;
4304
4419
  /** terminal, P1-3(黑板 [1920]/[1921]/[1924]/[1925]): the cross-channel completion correlation id —
4305
4420
  * MIRROR of the sibling task_notification's `TaskNotificationPayload.completionId` (same settle,
@@ -498,6 +498,53 @@ export interface WorkspaceState {
498
498
  export interface WorkspaceStateEntry extends SessionTreeEntryBase, WorkspaceState {
499
499
  type: "workspace_state";
500
500
  }
501
+ /** The closed kind set of a git-status announcement (the availability half of the `(kind, hash)`
502
+ * comparison tuple): `full` = five-segment snapshot frame; `degraded` = branch+dirty two-line frame
503
+ * (the snapshot round-trip failed while the basic probe succeeded); `unavailable` / `non-repo` =
504
+ * tombstone frames announcing that earlier git frames no longer describe the tree. */
505
+ export type GitAnnouncementKind = "full" | "degraded" | "unavailable" | "non-repo";
506
+ /**
507
+ * The git-status frame ANNOUNCED STATE — which rendered git view the model has last been shown on
508
+ * this branch, as a `(kind, hash)` tuple (hash = content hash of the rendered frame body with the
509
+ * frame format version and the canonical repo root bound into the digest domain). Two-phase receipt
510
+ * protocol: `pending: true` = a re-announcement is OWED but its frame append has not produced a
511
+ * receipt (a compaction restates the tuple as pending in the same CAS that lands the summary; the
512
+ * announced form is written only once the frame's own append returned an entry id) — any reader
513
+ * that finds pending nearest MUST conservatively re-announce. `entryId` = the session entry of the
514
+ * message CARRYING the announced frame; an announced state whose entryId is not on the active
515
+ * branch (rewind cut it) is treated as pending by the read walk (branch-authority read ladder).
516
+ */
517
+ export interface GitAnnouncementState {
518
+ kind: GitAnnouncementKind;
519
+ hash: string;
520
+ /** Session entry id of the message carrying the announced frame (absent on a pending restatement). */
521
+ entryId?: string;
522
+ /** Two-phase receipt: the tuple is owed but its frame append has no receipt yet. */
523
+ pending?: true;
524
+ }
525
+ /**
526
+ * The git-status announced state as a FIRST-CLASS typed entry (same snapshot doctrine as
527
+ * {@link AnnouncedListingEntry}): the nearest carrier on the branch IS the announced state.
528
+ * Written at every frame COMMIT point (first-frame receipt / resume-continuation receipt /
529
+ * compaction re-assertion receipt); a compaction additionally restates the tuple as `pending`
530
+ * inside its own `details.gitAnnouncement` (same-CAS with the new baseline) until the re-asserted
531
+ * frame's append receipt lands. The checkpoint mirror (`CheckpointState.gitAnnouncement`) is the
532
+ * lower seed rung, consulted only when the branch carries no mirror at all.
533
+ */
534
+ export interface GitAnnouncementEntry extends SessionTreeEntryBase, GitAnnouncementState {
535
+ type: "git_announcement";
536
+ }
537
+ /** Bounded cap for {@link normalizeGitAnnouncement} entry ids (self-generated ids are short; an
538
+ * oversize value is a forgery signal, same posture as the sibling caps). */
539
+ export declare const GIT_ANNOUNCEMENT_MAX_ENTRY_ID_CHARS = 256;
540
+ /**
541
+ * Strict shape gate for the git announced state — the SINGLE normalization the read walk, the
542
+ * public append, and the import-validate door all use (the {@link normalizeAnnouncedListing}
543
+ * posture). Returns a shaped copy carrying ONLY the known keys, or undefined when the value is not
544
+ * structurally valid: closed kind set, full `sha256:<hex64>` hash grammar (a prefix-only check
545
+ * would accept junk), bounded entryId, `pending` only as literal `true`.
546
+ */
547
+ export declare function normalizeGitAnnouncement(v: unknown): GitAnnouncementState | undefined;
501
548
  /** Bounded cap for {@link normalizeWorkspaceState} paths (a sha is capped by its hex shape check). */
502
549
  export declare const WORKSPACE_STATE_MAX_PATH_CHARS = 4096;
503
550
  /**
@@ -544,7 +591,7 @@ export declare function normalizeAnnouncedListing(v: unknown): {
544
591
  models?: string[];
545
592
  } | undefined;
546
593
  /** All persisted session tree entry variants. */
547
- export type SessionTreeEntry = MessageEntry | ThinkingLevelChangeEntry | ModelChangeEntry | CompactionEntry | CustomEntry | CustomMessageEntry | LabelEntry | SessionInfoEntry | LeafEntry | PromptEpochEntry | AnnouncedListingEntry | WorkspaceStateEntry;
594
+ export type SessionTreeEntry = MessageEntry | ThinkingLevelChangeEntry | ModelChangeEntry | CompactionEntry | CustomEntry | CustomMessageEntry | LabelEntry | SessionInfoEntry | LeafEntry | PromptEpochEntry | AnnouncedListingEntry | GitAnnouncementEntry | WorkspaceStateEntry;
548
595
  export interface SessionContext {
549
596
  messages: AgentMessage[];
550
597
  thinkingLevel: string;
@@ -684,6 +731,23 @@ export interface Session<TMetadata extends SessionMetadata = SessionMetadata> {
684
731
  * epoch restatement) calls it with `?.()` — an implementer without it degrades to the pre-epoch
685
732
  * path (no restatement), never a crash. */
686
733
  getPromptEpoch?(): Promise<import("../../prompt-assembly/epoch.js").PromptEpochDescriptor | undefined>;
734
+ /** Persist the git-status announced state as a first-class {@link GitAnnouncementEntry}
735
+ * (snapshot semantics; see `Session.appendGitAnnouncement`). OPTIONAL on the interface — same
736
+ * external-implementer posture as `getPromptEpoch`; consumers call it with `?.()` and an
737
+ * implementer without it degrades to conservative re-announcement every leg (duplicate-tolerant
738
+ * by design), never a crash. */
739
+ appendGitAnnouncement?(state: GitAnnouncementState): Promise<string>;
740
+ /** Branch-authority read of the git announced state (see `Session.getGitAnnouncement`): nearest
741
+ * carrier on the active branch, with a pending restatement — or an announced state whose frame
742
+ * entryId is NOT on the active branch — surfaced as `status:"pending"` (the reader must
743
+ * conservatively re-announce). Undefined ⇒ no carrier visible on this branch. OPTIONAL, same
744
+ * posture as `appendGitAnnouncement`. */
745
+ getGitAnnouncement?(): Promise<{
746
+ kind: GitAnnouncementKind;
747
+ hash: string;
748
+ status: "announced" | "pending";
749
+ entryId?: string;
750
+ } | undefined>;
687
751
  }
688
752
  export interface SessionCreateOptions {
689
753
  id?: string;
@@ -66,6 +66,26 @@ export class AgentHarnessError extends Error {
66
66
  this.code = code;
67
67
  }
68
68
  }
69
+ export const GIT_ANNOUNCEMENT_MAX_ENTRY_ID_CHARS = 256;
70
+ export function normalizeGitAnnouncement(v) {
71
+ if (typeof v !== "object" || v === null)
72
+ return undefined;
73
+ const src = v;
74
+ if (src.kind !== "full" && src.kind !== "degraded" && src.kind !== "unavailable" && src.kind !== "non-repo")
75
+ return undefined;
76
+ if (typeof src.hash !== "string" || !/^sha256:[0-9a-f]{64}$/.test(src.hash))
77
+ return undefined;
78
+ if (src.entryId !== undefined && (typeof src.entryId !== "string" || src.entryId.length === 0 || src.entryId.length > GIT_ANNOUNCEMENT_MAX_ENTRY_ID_CHARS))
79
+ return undefined;
80
+ if (src.pending !== undefined && src.pending !== true)
81
+ return undefined;
82
+ return {
83
+ kind: src.kind,
84
+ hash: src.hash,
85
+ ...(src.entryId !== undefined ? { entryId: src.entryId } : {}),
86
+ ...(src.pending === true ? { pending: true } : {}),
87
+ };
88
+ }
69
89
  export const WORKSPACE_STATE_MAX_PATH_CHARS = 4096;
70
90
  export function normalizeWorkspaceState(value) {
71
91
  if (value === null || typeof value !== "object")
@@ -1,4 +1,4 @@
1
- import { SessionError, normalizeAnnouncedListing, normalizeWorkspaceState, normalizeCompactionStateCarrier, isValidThinkingLevelChange, isValidModelChange, isOptionalDisplayString, } from "../harness/types.js";
1
+ import { SessionError, normalizeAnnouncedListing, normalizeGitAnnouncement, normalizeWorkspaceState, normalizeCompactionStateCarrier, isValidThinkingLevelChange, isValidModelChange, isOptionalDisplayString, } from "../harness/types.js";
2
2
  import { leafIdAfterEntry } from "./storage-base.js";
3
3
  import { parseSessionTimestampMs } from "./timestamps.js";
4
4
  import { flattenableUserText, normalizeEngineSegments } from "../../core/untrusted-text.js";
@@ -87,6 +87,11 @@ export class StreamingImportValidator {
87
87
  throw new SessionError("invalid_session", `workspace_state entry "${e.id}" is structurally invalid`);
88
88
  }
89
89
  }
90
+ else if (e.type === "git_announcement") {
91
+ if (!normalizeGitAnnouncement(e)) {
92
+ throw new SessionError("invalid_session", `git_announcement entry "${e.id}" is structurally invalid`);
93
+ }
94
+ }
90
95
  if (e.type === "thinking_level_change" && !isValidThinkingLevelChange(e)) {
91
96
  throw new SessionError("invalid_session", `thinking_level_change entry "${e.id}" carries an invalid thinkingLevel`);
92
97
  }
@@ -119,6 +124,10 @@ export class StreamingImportValidator {
119
124
  if (restatedListings !== undefined && !normalizeAnnouncedListing(restatedListings)) {
120
125
  throw new SessionError("invalid_session", `compaction "${e.id}" carries a structurally invalid announcedListings restatement`);
121
126
  }
127
+ const restatedGit = e.details?.gitAnnouncement;
128
+ if (restatedGit !== undefined && !normalizeGitAnnouncement(restatedGit)) {
129
+ throw new SessionError("invalid_session", `compaction "${e.id}" carries a structurally invalid gitAnnouncement restatement`);
130
+ }
122
131
  const carrier = e.details;
123
132
  const shaped = normalizeCompactionStateCarrier(e.details);
124
133
  if (carrier?.thinkingLevel !== undefined && shaped.thinkingLevel === undefined) {
@@ -1,6 +1,6 @@
1
1
  import type { ImageContent, TextContent } from "../llm/index.js";
2
2
  import type { AgentMessage } from "../loop/types.js";
3
- import type { Session, SessionContext, SessionMetadata, SessionStorage, SessionTreeEntry, WorkspaceState } from "../harness/types.js";
3
+ import type { GitAnnouncementState, Session, SessionContext, SessionMetadata, SessionStorage, SessionTreeEntry, WorkspaceState } from "../harness/types.js";
4
4
  import type { BuildContextOptions } from "../harness/types.js";
5
5
  import type { PromptEpochDescriptor } from "../../prompt-assembly/epoch.js";
6
6
  /** Build model context from the active session branch and its latest state markers.
@@ -45,6 +45,31 @@ export declare class StoredSession<TMetadata extends SessionMetadata = SessionMe
45
45
  skills?: readonly string[];
46
46
  models?: readonly string[];
47
47
  }): Promise<string>;
48
+ /**
49
+ * Git-status frame announced state (branch-authority mirror): persist the `(kind, hash)` tuple —
50
+ * announced form carries the frame's own entry id; a compaction restates the tuple as `pending`
51
+ * inside its `details.gitAnnouncement` instead (same-CAS with the new baseline). Called
52
+ * best-effort at every frame COMMIT point (append receipt in hand) — an append failure degrades
53
+ * to a conservative re-announcement on the next leg (duplicate-tolerant), never fails the run.
54
+ */
55
+ appendGitAnnouncement(state: GitAnnouncementState): Promise<string>;
56
+ /**
57
+ * Branch-authority read of the git announced state: the nearest carrier walking back from the
58
+ * leaf (a `git_announcement` entry or a compaction's `details.gitAnnouncement` restatement;
59
+ * SNAPSHOT semantics — first hit wins). The pending gate is a MANDATORY one: a nearest carrier
60
+ * that is pending, or an announced carrier whose frame entryId is NOT on the active branch (a
61
+ * rewind cut the frame while a descendant mirror survived), reads as `status:"pending"` — the
62
+ * caller must conservatively re-announce rather than trust a mirror whose frame the model cannot
63
+ * see. Malformed carriers are skipped (defense in depth behind the import door). Undefined ⇒ no
64
+ * carrier visible on this branch (pre-migration session / bounded-tail floor cut every carrier) —
65
+ * callers fall back to the checkpoint mirror rung, then to conservative re-announcement.
66
+ */
67
+ getGitAnnouncement(): Promise<{
68
+ kind: GitAnnouncementState["kind"];
69
+ hash: string;
70
+ status: "announced" | "pending";
71
+ entryId?: string;
72
+ } | undefined>;
48
73
  /**
49
74
  * design/155 (cli [1580]): persist the settle-time workspace state (tracked cwd + active
50
75
  * EnterWorktree session) as a first-class typed entry. Called best-effort at task settle when the
@@ -115,3 +140,14 @@ export declare class StoredSession<TMetadata extends SessionMetadata = SessionMe
115
140
  appendLabel(targetId: string, label: string | undefined): Promise<string>;
116
141
  appendSessionName(name: string): Promise<string>;
117
142
  }
143
+ /**
144
+ * Falsification round 1 (F3) — is `entryId` not merely ON the active branch but inside the region
145
+ * a context rebuild can actually contain? A branch walk sees the whole tree; `buildSessionContext`
146
+ * replays only [latest compaction's firstKeptEntryId .. leaf] (plus the summary). A git frame that
147
+ * sits BEFORE the latest compaction's kept tail is therefore gone from every future request even
148
+ * though its id is a legitimate branch member — an announced mirror pointing at it must read as
149
+ * PENDING (re-announce), or the announced hash would suppress re-sends of bytes the model can no
150
+ * longer see. Shared by the session mirror walk and the run loop's checkpoint seeding rung (one
151
+ * rule, both readers). Conservative on damage: an unresolvable firstKeptEntryId reads as invisible.
152
+ */
153
+ export declare function gitFrameContextVisible(branch: SessionTreeEntry[], entryId: string): boolean;
@@ -1,5 +1,5 @@
1
1
  import { asAgentMessage, createCompactionSummaryMessage, createCustomMessage, } from "../harness/messages.js";
2
- import { SessionError, isValidModelChange, normalizeAnnouncedListing, normalizeCompactionStateCarrier, normalizeWorkspaceState } from "../harness/types.js";
2
+ import { SessionError, isValidModelChange, normalizeAnnouncedListing, normalizeCompactionStateCarrier, normalizeGitAnnouncement, normalizeWorkspaceState } from "../harness/types.js";
3
3
  import { normalizePromptEpoch } from "../../prompt-assembly/epoch.js";
4
4
  import { budgetInvokedSkillsRetention, readElidedMessages, readRetainedInvokedSkills, renderInvokedSkillsRetention, } from "../compaction/utils.js";
5
5
  const RETENTION_CLAMP_DEFAULT_CHARS_PER_TOKEN = 4;
@@ -181,6 +181,39 @@ export class StoredSession {
181
181
  ...shaped,
182
182
  });
183
183
  }
184
+ async appendGitAnnouncement(state) {
185
+ const shaped = normalizeGitAnnouncement(state);
186
+ if (!shaped) {
187
+ throw new SessionError("invalid_entry", "appendGitAnnouncement: state is not a structurally valid git announcement (closed kind, sha256 hash, bounded entryId)");
188
+ }
189
+ return this.appendTypedEntry({
190
+ type: "git_announcement",
191
+ id: await this.storage.createEntryId(),
192
+ parentId: await this.storage.getLeafId(),
193
+ timestamp: new Date().toISOString(),
194
+ ...shaped,
195
+ });
196
+ }
197
+ async getGitAnnouncement() {
198
+ const branch = await this.getBranch();
199
+ for (let i = branch.length - 1; i >= 0; i--) {
200
+ const entry = branch[i];
201
+ let carried;
202
+ if (entry.type === "git_announcement") {
203
+ carried = normalizeGitAnnouncement(entry);
204
+ }
205
+ else if (entry.type === "compaction") {
206
+ carried = normalizeGitAnnouncement(entry.details?.gitAnnouncement);
207
+ }
208
+ if (carried === undefined)
209
+ continue;
210
+ const announced = carried.pending !== true && carried.entryId !== undefined && gitFrameContextVisible(branch, carried.entryId);
211
+ return announced
212
+ ? { kind: carried.kind, hash: carried.hash, status: "announced", entryId: carried.entryId }
213
+ : { kind: carried.kind, hash: carried.hash, status: "pending" };
214
+ }
215
+ return undefined;
216
+ }
184
217
  async appendWorkspaceState(state) {
185
218
  const shaped = normalizeWorkspaceState(state);
186
219
  if (!shaped) {
@@ -314,3 +347,25 @@ export class StoredSession {
314
347
  });
315
348
  }
316
349
  }
350
+ export function gitFrameContextVisible(branch, entryId) {
351
+ let frameIdx = -1;
352
+ let compactionIdx = -1;
353
+ let firstKeptId;
354
+ for (let i = 0; i < branch.length; i++) {
355
+ const e = branch[i];
356
+ if (e.id === entryId)
357
+ frameIdx = i;
358
+ if (e.type === "compaction") {
359
+ compactionIdx = i;
360
+ firstKeptId = e.firstKeptEntryId;
361
+ }
362
+ }
363
+ if (frameIdx === -1)
364
+ return false;
365
+ if (compactionIdx === -1 || frameIdx > compactionIdx)
366
+ return true;
367
+ if (firstKeptId === undefined)
368
+ return false;
369
+ const keptIdx = branch.findIndex((e) => e.id === firstKeptId);
370
+ return keptIdx !== -1 && frameIdx >= keptIdx;
371
+ }
package/dist/index.d.ts CHANGED
@@ -164,7 +164,7 @@ export { AdoptionError, assertAdoptionBootGate, readRootAdoptionFile, writeRootA
164
164
  export { adoptLocalDataRoot, ackAdoptionConfig, witnessAdoptionConfig, listAdoptionQuarantine, readAdoptionStatus, type AdoptionStatus, type AdoptLocalDataRootOptions, type AdoptLocalDataRootResult, type AdoptionCarriageLeg, type AdoptionCarriageLegContext, type AdoptionConfigWitnessReceipt, } from "./stores/file/adoption/adopt.js";
165
165
  export { formatHookFeedback, runToolGate, createHookEnvCapabilities, createPreToolUseConstraintPolicy, type PersistedRuleHit, type PersistedRuleUnreadable, type PersistedRuleAnswer, normalizePersistedRuleHit, type Hooks, type HookToolContext, type HookEnvCapabilities, type HookToolOutput, type PreToolUseResult, type PostToolUseResult, type UserPromptSubmitResult, type HookToolFailure, type PostToolUseFailureResult, type PostToolBatchCall, type PostToolBatchResult, type PreCompactContext, type PreCompactResult, type PostCompactContext, type StopFailureContext, type PermissionDeniedPayload, type PermissionDeniedSource, } from "./core/hooks.js";
166
166
  export { InMemoryMemoryStore, composeMemoryBlock, composeLayeredMemoryBlock, normalizeMemorySpec, type NormalizedMemorySpec, type MemorySpecInput, supportsConsolidation, supportsPeriodicConsolidation, firstSentence, expandLexicalTerms, lexicalSearchMatch, type Embedder, classifyPromotable, enforcePromotableWriteGate, guardedMemoryStore, MemoryGateError, detectSecret, enforceSecretWriteGate, enforceStructuredNoteSecretGate, type UtilityGate, type MemoryStore, type MemoryVectorMode, type ScoredMemory, type MemoryNoteHeader, type MemoryNoteRecord, type MemoryNoteType, type StructuredNoteInput, } from "./core/memory.js";
167
- export { MemoryEngine, FileMemoryEngineBackend, memoryBackendContract, assertMemoryBackendSearchEquivalence, type MemoryBackendContractHooks, buildMemoryInstruction, truncateIndex, scanEntryFiles, deriveRepoKey, deriveRepoMemoryDir, deriveControlPlaneDir, deriveRepoControlPlaneDir, resolveMemoryEngineRoot, scopeDirName, ControlPlaneCorruptError, parseEntryFile, serializeEntryFile, computeEntryRev, entryFromFile, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_RECALL_DISCIPLINE, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, readV2HeaderHints, type V2HeaderHints, LINEAGE_FILE, CHALLENGES_FILE, CHALLENGED_HISTORY_FILE, CHALLENGE_LEDGER_MAX_EVENTS, rebuildStrictControlPlaneLedger, type ControlPlaneRebuildReceipt, type StrictControlPlaneLedger, type ChallengeAssignment, type ChallengeEvent, type ChallengedHistoryRow, type LineagePendingTxn, type LineagePromotion, MEMORY_INDEX_FILENAME, MEMORY_INDEX_MAX_LINES, MEMORY_INDEX_MAX_BYTES, MEMORY_SEARCH_TOOL_NAME, MEMORY_GET_TOOL_NAME, MEMORY_ENGINE_TOOL_NAMES, type MemorySearchDetails, type MemorySearchHit, type MemoryGetDetails, STUB_ARCHIVED_LINE, DEFAULT_MAX_MEMORY_FILES, DEFAULT_MAX_ENTRY_DEPTH, DEFAULT_HARVEST_DEADLINE_MS, DEFAULT_HARVEST_FILE_BUDGET, MASS_DELETION_FUSE_RATIO, scanMemoryWrite, scanMemoryFileName, scanRemediation, MEMORY_FILENAME_SEGMENT_RE, renderAnnouncements, enqueueMemoryAnnouncement, drainMemoryAnnouncements, peekMemoryAnnouncements, bumpScanFuse, scanFuseCount, clearScanFuse, ANNOUNCEMENTS_FILE, MEMORY_ANNOUNCEMENTS_MAX, SCAN_FUSE_FILE, SCAN_FUSE_THRESHOLD, type MemoryAnnouncement, type ScanFinding, type MemoryEngineOptions, type MemoryInjection, type MemoryBackend, type MemoryEntry, type MemoryEntryFrontmatter, type MemoryEntryHeader, type ScoredMemoryEntry, type NotePatch, type PatchReport, type MaterializedFile, type MemorySessionHandle, type HarvestReport, type HarvestRejection, type HarvestRejectionCode, type ParsedEntryFile, type ScannedEntryFile, SCOPE_SEGMENT_MAX_ENCODED, PROJECT_MARKER_PATH, encodeScopeSegment, decodeScopeSegment, parseScopeKey, formatUserScope, formatOrgScope, formatProjScope, formatUserProjScope, isPersonalScope, assertScopeContractPlacement, formatProjectMarker, parseProjectMarker, resolveProjectId, PROJECT_ID_REGEX, type ParsedScopeKey, migrateScope, type MigrateScopeReport, materializeEntriesToFiles, harvestFilesToPatches, projectionsToWriteBack, screenInboundEntries, REMOTE_HARVEST_PER_FILE_BYTES, REMOTE_MASS_DELETION_FUSE_RATIO, type RemoteMemoryFile, type RemoteMemoryBaseline, type RemoteMaterialization, type RemoteHarvestResult, type InboundEntryFinding, deriveProjectMemoryDir, deriveProjectControlDir, recordProjectIdHint, lookupProjectIdHint, PROJECT_ID_HINTS_FILE, reconcileMemoryEntries, nextSyncBaseline, mergeRecallHits, type MemorySyncCursor, type MemorySyncConflict, type MemorySyncPlan, type RecallSource, syncMemoryScope, type MemorySyncTransport, type MemorySyncRequestBody, type MemorySyncResponseBody, type MemorySyncClientConflict, type SyncMemoryScopeOptions, type MemorySyncClientResult, } from "./core/memory-engine/index.js";
167
+ export { MemoryEngine, FileMemoryEngineBackend, memoryBackendContract, assertMemoryBackendSearchEquivalence, type MemoryBackendContractHooks, buildMemoryInstruction, truncateIndex, scanEntryFiles, deriveRepoKey, deriveRepoMemoryDir, deriveControlPlaneDir, deriveRepoControlPlaneDir, resolveMemoryEngineRoot, scopeDirName, ControlPlaneCorruptError, parseEntryFile, serializeEntryFile, computeEntryRev, entryFromFile, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_RECALL_DISCIPLINE, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, readV2HeaderHints, type V2HeaderHints, LINEAGE_FILE, CHALLENGES_FILE, CHALLENGED_HISTORY_FILE, CHALLENGE_LEDGER_MAX_EVENTS, rebuildStrictControlPlaneLedger, type ControlPlaneRebuildReceipt, type StrictControlPlaneLedger, type ChallengeAssignment, type ChallengeEvent, type ChallengedHistoryRow, type LineagePendingTxn, type LineagePromotion, MEMORY_INDEX_FILENAME, MEMORY_INDEX_MAX_LINES, MEMORY_INDEX_MAX_BYTES, MEMORY_SEARCH_TOOL_NAME, MEMORY_GET_TOOL_NAME, MEMORY_ENGINE_TOOL_NAMES, type MemorySearchDetails, type MemorySearchHit, type MemoryGetDetails, STUB_ARCHIVED_LINE, DEFAULT_MAX_MEMORY_FILES, DEFAULT_MAX_ENTRY_DEPTH, DEFAULT_HARVEST_DEADLINE_MS, DEFAULT_HARVEST_FILE_BUDGET, MASS_DELETION_FUSE_RATIO, scanMemoryWrite, scanMemoryFileName, scanRemediation, MEMORY_FILENAME_SEGMENT_RE, renderAnnouncements, enqueueMemoryAnnouncement, drainMemoryAnnouncements, peekMemoryAnnouncements, bumpScanFuse, scanFuseCount, clearScanFuse, ANNOUNCEMENTS_FILE, MEMORY_ANNOUNCEMENTS_MAX, SCAN_FUSE_FILE, SCAN_FUSE_THRESHOLD, type MemoryAnnouncement, type ScanFinding, type MemoryEngineOptions, type MemoryInjection, type EntryProvenanceAccount, type TransferEvidence, type CommittedBinding, type CommittedEntrySnapshot, type CommittedScopeSnapshots, type EntryCustodyReport, erasureSelectHash, type EraseMemoryEntriesInput, type ErasureSelect, type ErasedBinding, type MemoryErasureAttestation, type MemoryBackend, type MemoryEntry, type MemoryEntryFrontmatter, type MemoryEntryHeader, type ScoredMemoryEntry, type NotePatch, type PatchReport, type MaterializedFile, type MemorySessionHandle, type HarvestReport, type HarvestRejection, type HarvestRejectionCode, type ParsedEntryFile, type ScannedEntryFile, SCOPE_SEGMENT_MAX_ENCODED, PROJECT_MARKER_PATH, encodeScopeSegment, decodeScopeSegment, parseScopeKey, formatUserScope, formatOrgScope, formatProjScope, formatUserProjScope, isPersonalScope, assertScopeContractPlacement, formatProjectMarker, parseProjectMarker, resolveProjectId, PROJECT_ID_REGEX, type ParsedScopeKey, migrateScope, type MigrateScopeReport, materializeEntriesToFiles, harvestFilesToPatches, projectionsToWriteBack, screenInboundEntries, REMOTE_HARVEST_PER_FILE_BYTES, REMOTE_MASS_DELETION_FUSE_RATIO, type RemoteMemoryFile, type RemoteMemoryBaseline, type RemoteMaterialization, type RemoteHarvestResult, type InboundEntryFinding, deriveProjectMemoryDir, deriveProjectControlDir, recordProjectIdHint, lookupProjectIdHint, PROJECT_ID_HINTS_FILE, reconcileMemoryEntries, nextSyncBaseline, mergeRecallHits, type MemorySyncCursor, type MemorySyncConflict, type MemorySyncPlan, type RecallSource, syncMemoryScope, type MemorySyncTransport, type MemorySyncRequestBody, type MemorySyncResponseBody, type MemorySyncClientConflict, type SyncMemoryScopeOptions, type MemorySyncClientResult, } from "./core/memory-engine/index.js";
168
168
  export { SHARED_MEMORY_READ_CAP_BYTES, SHARED_MEMORY_LIST_PAGE_SIZE, SharedMemoryStoreError, type SharedMemoryStoreProvider, type SharedMemoryStoreReader, type SharedMemoryPagedList, type SharedMemoryStoreInfo, type SharedMemoryDocumentEntry, type SharedMemorySnapshot, type SharedMemoryRequestContext, type MemoryListDetails, type MemoryReadDetails, } from "./core/shared-memory/types.js";
169
169
  export { sharedMemoryStoreContract, type SharedMemoryFixture, type SharedMemoryStoreContractHooks, } from "./core/shared-memory/contract.js";
170
170
  export { termSet, jaccardDistance, cosineDistance } from "./core/memory-vector.js";
@@ -254,7 +254,7 @@ export { retryBackoffMs, parseRetryAfter } from "./brain/retry.js";
254
254
  export { type BrainTimeoutConfig } from "./brain/timeout.js";
255
255
  export { createAssistantMessageEventStream } from "./internal/llm.js";
256
256
  export type { AssistantMessage, AssistantMessageEvent, CompleteSimpleFn, Context, DocumentContent, ImageContent, Message, StopReason, StreamFn, TextContent, ThinkingContent, ToolCall, ToolResultMessage, Usage, UserMessage, } from "./internal/llm.js";
257
- export type { AgentDefinition, BeforeWriteHook, BeforeWriteRequest, BeforeWriteResult, HandsBandOptions, Brain, BrainStatus, BrainStatusPhase, BrainRetryErrClass, ImageInput, McpElicitRequest, McpElicitResponse, McpServerSpec, A2aServerSpec, OnElicit, Model, ModelRef, ProjectMemoryLoad, RunnerDeps, EngineNotice, RuntimeCaps, BackgroundChildEvent, SkillManifest, SkillSpec, TaskEvent, TaskEventIdentity, ToolActivity, TaskLimits, StaleToolResultOffloadOptions, TaskResult, RemoteEnvFailureNote, TaskSpec, TaskStatus, TaskStream, CompactOutcome, ThinkingLevel, ToolExecuteContext, ToolReturn, ToolSpec, ReversibilityVerdict, ToolEffect, ToolContentOrigin, WorkflowGovernanceBaseline, DelegationTaskType, } from "./core/types.js";
257
+ export type { AgentDefinition, BeforeWriteHook, BeforeWriteRequest, BeforeWriteResult, HandsBandOptions, Brain, BrainStatus, BrainStatusPhase, BrainRetryErrClass, ImageInput, McpElicitRequest, McpElicitResponse, McpServerSpec, A2aServerSpec, OnElicit, Model, ModelRef, ProjectMemoryLoad, RunnerDeps, EngineNotice, RuntimeCaps, BackgroundChildEvent, SkillManifest, SkillSpec, TaskEvent, TaskEventIdentity, ToolActivity, TaskLimits, StaleToolResultOffloadOptions, TaskResult, EffectiveMemoryScopes, RemoteEnvFailureNote, TaskSpec, TaskStatus, TaskStream, CompactOutcome, ThinkingLevel, ToolExecuteContext, ToolReturn, ToolSpec, ReversibilityVerdict, ToolEffect, ToolContentOrigin, WorkflowGovernanceBaseline, DelegationTaskType, } from "./core/types.js";
258
258
  export { Type } from "typebox";
259
259
  export type { TSchema, Static } from "typebox";
260
260
  export { explainPromptAssembly, describeDefaultPack, type DefaultPackDescription, type ExplainInput } from "./prompt-assembly/explain.js";
package/dist/index.js CHANGED
@@ -126,7 +126,7 @@ export { AdoptionError, assertAdoptionBootGate, readRootAdoptionFile, writeRootA
126
126
  export { adoptLocalDataRoot, ackAdoptionConfig, witnessAdoptionConfig, listAdoptionQuarantine, readAdoptionStatus, } from "./stores/file/adoption/adopt.js";
127
127
  export { formatHookFeedback, runToolGate, createHookEnvCapabilities, createPreToolUseConstraintPolicy, normalizePersistedRuleHit, } from "./core/hooks.js";
128
128
  export { InMemoryMemoryStore, composeMemoryBlock, composeLayeredMemoryBlock, normalizeMemorySpec, supportsConsolidation, supportsPeriodicConsolidation, firstSentence, expandLexicalTerms, lexicalSearchMatch, classifyPromotable, enforcePromotableWriteGate, guardedMemoryStore, MemoryGateError, detectSecret, enforceSecretWriteGate, enforceStructuredNoteSecretGate, } from "./core/memory.js";
129
- export { MemoryEngine, FileMemoryEngineBackend, memoryBackendContract, assertMemoryBackendSearchEquivalence, buildMemoryInstruction, truncateIndex, scanEntryFiles, deriveRepoKey, deriveRepoMemoryDir, deriveControlPlaneDir, deriveRepoControlPlaneDir, resolveMemoryEngineRoot, scopeDirName, ControlPlaneCorruptError, parseEntryFile, serializeEntryFile, computeEntryRev, entryFromFile, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_RECALL_DISCIPLINE, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, readV2HeaderHints, LINEAGE_FILE, CHALLENGES_FILE, CHALLENGED_HISTORY_FILE, CHALLENGE_LEDGER_MAX_EVENTS, rebuildStrictControlPlaneLedger, MEMORY_INDEX_FILENAME, MEMORY_INDEX_MAX_LINES, MEMORY_INDEX_MAX_BYTES, MEMORY_SEARCH_TOOL_NAME, MEMORY_GET_TOOL_NAME, MEMORY_ENGINE_TOOL_NAMES, STUB_ARCHIVED_LINE, DEFAULT_MAX_MEMORY_FILES, DEFAULT_MAX_ENTRY_DEPTH, DEFAULT_HARVEST_DEADLINE_MS, DEFAULT_HARVEST_FILE_BUDGET, MASS_DELETION_FUSE_RATIO, scanMemoryWrite, scanMemoryFileName, scanRemediation, MEMORY_FILENAME_SEGMENT_RE, renderAnnouncements, enqueueMemoryAnnouncement, drainMemoryAnnouncements, peekMemoryAnnouncements, bumpScanFuse, scanFuseCount, clearScanFuse, ANNOUNCEMENTS_FILE, MEMORY_ANNOUNCEMENTS_MAX, SCAN_FUSE_FILE, SCAN_FUSE_THRESHOLD, SCOPE_SEGMENT_MAX_ENCODED, PROJECT_MARKER_PATH, encodeScopeSegment, decodeScopeSegment, parseScopeKey, formatUserScope, formatOrgScope, formatProjScope, formatUserProjScope, isPersonalScope, assertScopeContractPlacement, formatProjectMarker, parseProjectMarker, resolveProjectId, PROJECT_ID_REGEX, migrateScope, materializeEntriesToFiles, harvestFilesToPatches, projectionsToWriteBack, screenInboundEntries, REMOTE_HARVEST_PER_FILE_BYTES, REMOTE_MASS_DELETION_FUSE_RATIO, deriveProjectMemoryDir, deriveProjectControlDir, recordProjectIdHint, lookupProjectIdHint, PROJECT_ID_HINTS_FILE, reconcileMemoryEntries, nextSyncBaseline, mergeRecallHits, syncMemoryScope, } from "./core/memory-engine/index.js";
129
+ export { MemoryEngine, FileMemoryEngineBackend, memoryBackendContract, assertMemoryBackendSearchEquivalence, buildMemoryInstruction, truncateIndex, scanEntryFiles, deriveRepoKey, deriveRepoMemoryDir, deriveControlPlaneDir, deriveRepoControlPlaneDir, resolveMemoryEngineRoot, scopeDirName, ControlPlaneCorruptError, parseEntryFile, serializeEntryFile, computeEntryRev, entryFromFile, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_RECALL_DISCIPLINE, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, readV2HeaderHints, LINEAGE_FILE, CHALLENGES_FILE, CHALLENGED_HISTORY_FILE, CHALLENGE_LEDGER_MAX_EVENTS, rebuildStrictControlPlaneLedger, MEMORY_INDEX_FILENAME, MEMORY_INDEX_MAX_LINES, MEMORY_INDEX_MAX_BYTES, MEMORY_SEARCH_TOOL_NAME, MEMORY_GET_TOOL_NAME, MEMORY_ENGINE_TOOL_NAMES, STUB_ARCHIVED_LINE, DEFAULT_MAX_MEMORY_FILES, DEFAULT_MAX_ENTRY_DEPTH, DEFAULT_HARVEST_DEADLINE_MS, DEFAULT_HARVEST_FILE_BUDGET, MASS_DELETION_FUSE_RATIO, scanMemoryWrite, scanMemoryFileName, scanRemediation, MEMORY_FILENAME_SEGMENT_RE, renderAnnouncements, enqueueMemoryAnnouncement, drainMemoryAnnouncements, peekMemoryAnnouncements, bumpScanFuse, scanFuseCount, clearScanFuse, ANNOUNCEMENTS_FILE, MEMORY_ANNOUNCEMENTS_MAX, SCAN_FUSE_FILE, SCAN_FUSE_THRESHOLD, erasureSelectHash, SCOPE_SEGMENT_MAX_ENCODED, PROJECT_MARKER_PATH, encodeScopeSegment, decodeScopeSegment, parseScopeKey, formatUserScope, formatOrgScope, formatProjScope, formatUserProjScope, isPersonalScope, assertScopeContractPlacement, formatProjectMarker, parseProjectMarker, resolveProjectId, PROJECT_ID_REGEX, migrateScope, materializeEntriesToFiles, harvestFilesToPatches, projectionsToWriteBack, screenInboundEntries, REMOTE_HARVEST_PER_FILE_BYTES, REMOTE_MASS_DELETION_FUSE_RATIO, deriveProjectMemoryDir, deriveProjectControlDir, recordProjectIdHint, lookupProjectIdHint, PROJECT_ID_HINTS_FILE, reconcileMemoryEntries, nextSyncBaseline, mergeRecallHits, syncMemoryScope, } from "./core/memory-engine/index.js";
130
130
  export { SHARED_MEMORY_READ_CAP_BYTES, SHARED_MEMORY_LIST_PAGE_SIZE, SharedMemoryStoreError, } from "./core/shared-memory/types.js";
131
131
  export { sharedMemoryStoreContract, } from "./core/shared-memory/contract.js";
132
132
  export { termSet, jaccardDistance, cosineDistance } from "./core/memory-vector.js";
@@ -12,4 +12,5 @@ export type { AgentHarnessEvent, CompactionSettings, ExecutionEnv, ExecutionErro
12
12
  export type { ExecutionEnvExecOptions, ExecResult } from "../engine/harness/types.js";
13
13
  export type { SessionWriteOptions, CompactionEntry } from "../engine/harness/types.js";
14
14
  export type { ActiveWorktreeSession, WorkspaceState } from "../engine/harness/types.js";
15
+ export type { GitAnnouncementKind, GitAnnouncementState } from "../engine/harness/types.js";
15
16
  export type { SessionForkOptions } from "../engine/harness/types.js";
@@ -23,3 +23,5 @@ export { InMemorySessionStorage } from "../engine/session/memory-storage.js";
23
23
  export { InMemorySessionRepo } from "../engine/session/memory-repo.js";
24
24
  export { uuidv7 } from "../engine/session/uuid.js";
25
25
  export { SessionError } from "../engine/harness/types.js";
26
+ export { normalizeGitAnnouncement } from "../engine/harness/types.js";
27
+ export { gitFrameContextVisible } from "../engine/session/session.js";
@@ -12,3 +12,5 @@ export { InMemorySessionStorage } from "../engine/session/memory-storage.js";
12
12
  export { InMemorySessionRepo } from "../engine/session/memory-repo.js";
13
13
  export { uuidv7 } from "../engine/session/uuid.js";
14
14
  export { SessionError } from "../engine/harness/types.js";
15
+ export { normalizeGitAnnouncement } from "../engine/harness/types.js";
16
+ export { gitFrameContextVisible } from "../engine/session/session.js";
@@ -50,7 +50,7 @@ const PROBE_INPUTS_BASE = {
50
50
  roleBase: "\u0000probe-role\u0000",
51
51
  roleAppend: "\u0000probe-append\u0000",
52
52
  mcpInstructionsBlock: "\u0000probe-mcp\u0000",
53
- environmentBlock: "\u0000probe-env\u0000",
53
+ environmentBlock: "\u0000probe-env@2\u0000",
54
54
  memoryBlock: "\u0000probe-memory\u0000",
55
55
  modelGuidance: "\u0000probe-guidance\u0000",
56
56
  };
@@ -13,6 +13,7 @@ export const EVENT_PROMPT_REGISTRY = new Map([
13
13
  { kind: "skills_listing", carrier: "message.user-prefix", trust: "operator", dedupe: "replace-by-key", defaultPolicy: "on", rendererRef: "turn-attachments.ts#renderSkillsListingDelta" },
14
14
  { kind: "mcp_instructions", carrier: "message.user-prefix", trust: "external", dedupe: "replace-by-key", defaultPolicy: "off", rendererRef: "turn-attachments.ts#renderMcpInstructionsDelta" },
15
15
  { kind: "mcp_dropped_tools", carrier: "message.user-prefix", trust: "external", dedupe: "replace-by-key", defaultPolicy: "off", rendererRef: "turn-attachments.ts#renderMcpDroppedTools" },
16
+ { kind: "git_status", carrier: "message.user-prefix", trust: "external", dedupe: "replace-by-key", defaultPolicy: "always", rendererRef: "git-status-frame.ts#renderGitStatusFrameBody" },
16
17
  { kind: "limit_approach_converge", carrier: "message.user-prefix", trust: "operator", dedupe: "once-per-run", defaultPolicy: "always", rendererRef: "runtask.ts#limitApproachFrames" },
17
18
  { kind: "limit_approach_deliver", carrier: "message.user-prefix", trust: "operator", dedupe: "once-per-run", defaultPolicy: "always", rendererRef: "runtask.ts#limitApproachFrames" },
18
19
  { kind: "final_verification", carrier: "message.user-prefix", trust: "operator", dedupe: "once-per-run", defaultPolicy: "always", rendererRef: "runtask.ts#final-verification" },
@@ -271,9 +271,13 @@ export interface EnvironmentFacts {
271
271
  cwd?: string;
272
272
  isGitRepo?: boolean;
273
273
  /** design/99 §E14 — the current git branch (remote = the container's repo). `"HEAD (detached)"` when on a
274
- * detached HEAD. Absent when not a git repo / no git. A prepare-time snapshot, like cwd (may go stale mid-run). */
274
+ * detached HEAD. Absent when not a git repo / no git. A prepare-time snapshot, like cwd (may go stale mid-run).
275
+ * env-tail migration (#254 shape): NO LONGER RENDERED by {@link buildEnvironmentContext} — the
276
+ * branch is a turn-dynamic fact and rides the `git_status` frame instead; the field remains as a
277
+ * structured data carrier for callers that assembled it. */
275
278
  gitBranch?: string;
276
- /** design/99 §E14 — whether the working tree has uncommitted changes (`git status --porcelain` non-empty). */
279
+ /** design/99 §E14 — whether the working tree has uncommitted changes (`git status --porcelain` non-empty).
280
+ * env-tail migration: NO LONGER RENDERED here (turn-dynamic; rides the `git_status` frame). */
277
281
  gitDirty?: boolean;
278
282
  /** design/99 §E14 — the working tree's toplevel (`git rev-parse --show-toplevel`). Lets the agent/shell know
279
283
  * it may be in a linked worktree rather than the main checkout. */
@@ -319,10 +323,10 @@ export interface EnvironmentFacts {
319
323
  /** A1 — outbound-network posture; `none` renders an explicit downloads-will-fail caveat (TB
320
324
  * autopsy 2026-07-05: agents burned budget retrying downloads in a no-egress sandbox). */
321
325
  sandboxEgress?: "none" | "allowlist" | "full";
322
- /** H4 (CC 2.1.198 git snapshot) — the pre-rendered {@link buildGitSnapshot} block, appended to the
323
- * `# Environment` block as its own paragraph. A PREPARE-TIME snapshot by contract (its first line
324
- * self-declares "will not update during the conversation"); the date_change re-render carries it
325
- * unchanged. Absent when not a git repo / the probe failed (whole section skipped, CC-same). */
326
+ /** H4 (CC 2.1.198 git snapshot) — the pre-rendered {@link buildGitSnapshot} block. env-tail
327
+ * migration (#254 shape): NO LONGER RENDERED by {@link buildEnvironmentContext} the snapshot is
328
+ * the turn-dynamic git view and rides the `git_status` frame (re-sent only when the rendered view
329
+ * changes); the field remains as a structured data carrier for callers that assembled it. */
326
330
  gitSnapshot?: string;
327
331
  /** [1451] B-half — per-lane resume-continuity facts (deployment-supplied via `TaskSpec.envFacts.resumeFacts`,
328
332
  * copied by prepare ONLY on a durable-resume leg). Each present field renders one honest sentence; absent
@@ -353,11 +357,20 @@ export interface EnvironmentFacts {
353
357
  export declare function buildScratchpadSection(scratchpadDir: string): string;
354
358
  /** CC 2.1.198 `Juo` — the git status truncation bound (chars) for {@link buildGitSnapshot}. */
355
359
  export declare const GIT_STATUS_MAX_CHARS = 2000;
360
+ /** The CC-verbatim first paragraph of the git snapshot (CC 2.1.198 `Quo` head sentence). Kept as a
361
+ * named constant so the git-status FRAME renderer (the snapshot's carrier since the env-tail
362
+ * migration) can swap exactly this paragraph for its own update-semantics preamble — under the
363
+ * frame protocol the snapshot IS re-sent when the visible view changes, so the "will not update"
364
+ * claim would be false there. {@link buildGitSnapshot} itself still renders it byte-exact. */
365
+ export declare const GIT_SNAPSHOT_CC_PREAMBLE = "This is the git status at the start of the conversation. Note that this status is a snapshot in time, and will not update during the conversation.";
356
366
  /**
357
367
  * H4 — the CC 2.1.198 ENV git snapshot block (B1/B2; template + truncation copy verified against
358
368
  * observed Claude Code v2.1.198 behavior). Pure renderer — the Runner gathers the raw values through the
359
369
  * ExecutionEnv seam (remote = the container's repo) and calls this once per prepareTask (a durable
360
- * resume re-prepares → re-snapshots, CC new-session 同型).
370
+ * resume re-prepares → re-snapshots, CC new-session 同型). env-tail migration (#254 shape): the
371
+ * rendered block now feeds the `git_status` TURN FRAME (src/core/runner/git-status-frame.ts, which
372
+ * swaps the {@link GIT_SNAPSHOT_CC_PREAMBLE} for an update-semantics preamble) instead of the
373
+ * `# Environment` system-prompt section — the template body itself stays CC-verbatim.
361
374
  *
362
375
  * Sanitization (deliberate CC deviation — CC injects raw): status/log/branch/user are REPO-controlled
363
376
  * text (filenames, commit subjects, a hostile clone's config) landing in the TRUSTED prompt region →