engineering-behavior-observatory 0.2.5 → 0.2.7

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.
@@ -116,8 +116,8 @@ export async function readQualifiedRunCapture(bundleRoot) {
116
116
  const manifest = readManifest(root);
117
117
  await assertPersistedStructuralQualification(root, manifest);
118
118
  const qualification = await qualifyRunBundle(root);
119
- if (!qualification.semanticAnalysisUsable
120
- || !["qualified", "qualified-with-gaps"].includes(qualification.status)) {
119
+ if (!qualification.semanticAnalysisUsable || Object.entries(qualification.dimensions)
120
+ .some(([dimension, result]) => dimension !== "workspace" && result.status === "unqualified")) {
121
121
  const reasons = qualification.reasons.map(({ code }) => code).join(", ") || "unknown qualification failure";
122
122
  throw new Error(`Agent SDK normalization requires capture-qualified evidence: ${reasons}.`);
123
123
  }
@@ -677,11 +677,14 @@ function assertQualifiedInput(input) {
677
677
  throw new Error("Agent SDK normalization input identity is invalid.");
678
678
  }
679
679
  const kinds = new Set(input.records.map(({ record }) => record.kind));
680
- for (const required of ["session", "hook", "workspace", "manifest"]) {
680
+ for (const required of ["session", "hook", "manifest"]) {
681
681
  if (!kinds.has(required))
682
682
  throw new Error(`Capture-qualified Agent SDK input is missing required ${required} evidence.`);
683
683
  }
684
684
  const assessmentMode = input.records.map(assessmentModeOf).find((value) => value !== undefined);
685
+ if (assessmentMode !== "observational" && !kinds.has("workspace")) {
686
+ throw new Error("Capture-qualified verified input is missing required workspace evidence.");
687
+ }
685
688
  if (assessmentMode !== "observational" && !input.records.some(isVerifierRecord)) {
686
689
  throw new Error("Capture-qualified verified input is missing required verifier evidence.");
687
690
  }
@@ -720,7 +723,8 @@ async function assertPersistedStructuralQualification(root, manifest) {
720
723
  return;
721
724
  const report = asRecord(parseJson(await readVerifiedArtifact(root, descriptor.relativePath, digestFrom(descriptor.digest), MAX_EVIDENCE_BYTES)));
722
725
  const structural = asRecord(report?.structuralQualification);
723
- if (structural?.status === "unqualified" || structural?.semanticAnalysisUsable === false) {
726
+ if (structural?.semanticAnalysisUsable === false
727
+ || structural?.status === "unqualified" && structural?.semanticAnalysisUsable !== true) {
724
728
  throw new Error("Agent SDK normalization rejects the persisted unqualified structural capture report.");
725
729
  }
726
730
  }
@@ -102,8 +102,15 @@ export async function captureClaudeAgentSdkRun(options) {
102
102
  return workspace;
103
103
  },
104
104
  cleanup: async (context) => {
105
- if (workspace?.status === "ready")
106
- await captureWorkspace();
105
+ // Retain the source on packaging failure; the assembler records the outcome gap.
106
+ if (workspace?.status === "ready") {
107
+ try {
108
+ await captureWorkspace();
109
+ }
110
+ catch {
111
+ return;
112
+ }
113
+ }
107
114
  await options.workspace.cleanup?.(context);
108
115
  },
109
116
  };
@@ -89,8 +89,15 @@ export async function captureCodexAppServerRun(options) {
89
89
  return workspace;
90
90
  },
91
91
  cleanup: async (context) => {
92
- if (workspace?.status === "ready")
93
- await captureWorkspace();
92
+ // Packaging gaps must not rewrite native completion or delete recoverable work.
93
+ if (workspace?.status === "ready") {
94
+ try {
95
+ await captureWorkspace();
96
+ }
97
+ catch {
98
+ return;
99
+ }
100
+ }
94
101
  await options.workspace.cleanup?.(context);
95
102
  },
96
103
  };
@@ -70,7 +70,7 @@ export async function runCursorSdkQueueEntry(options) {
70
70
  sandbox: { enabled: toolPolicy.sandbox.enabled },
71
71
  settingSources: [],
72
72
  autoReview: false,
73
- enableAgentRetries: false,
73
+ enableAgentRetries: toolPolicy.enableAgentRetries,
74
74
  },
75
75
  maxNativeRecordBytes: limits.maxNativeRecordBytes,
76
76
  };
@@ -215,8 +215,8 @@ function validateConfigurationRecord(record, kind, reference) {
215
215
  if (!Array.isArray(record.settingSources) || record.settingSources.length !== 0) {
216
216
  throw configurationError(reference, "settingSources must be [] so unrelated user/candidate settings are not loaded");
217
217
  }
218
- if (record.autoReview !== false || record.enableAgentRetries !== false) {
219
- throw configurationError(reference, "autoReview and enableAgentRetries must both be false for an observational attempt");
218
+ if (record.autoReview !== false || record.enableAgentRetries !== true) {
219
+ throw configurationError(reference, "autoReview must be false and enableAgentRetries must be true for native recovery");
220
220
  }
221
221
  return;
222
222
  case "capture-profile":
@@ -18,7 +18,7 @@ export type CursorSdkToolPolicy = {
18
18
  };
19
19
  settingSources: readonly [];
20
20
  autoReview: false;
21
- enableAgentRetries: false;
21
+ enableAgentRetries: true;
22
22
  };
23
23
  export type CursorSdkCaptureConfiguration = {
24
24
  model: ModelSelection;
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
2
2
  import { existsSync } from "node:fs";
3
3
  import { lstat, mkdir, open, stat } from "node:fs/promises";
4
4
  import { dirname, join, parse, resolve } from "node:path";
5
- import { Agent, JSONL_LOCAL_AGENT_STORE_FILES, JsonlLocalAgentStore, } from "@cursor/sdk";
5
+ import { Agent, Cursor, JSONL_LOCAL_AGENT_STORE_FILES, JsonlLocalAgentStore, } from "@cursor/sdk";
6
6
  import { assertNoDuplicateJsonKeys, digestMetadata } from "./artifacts.js";
7
7
  import { createAttemptIdentity, createRunIdentity, executeRunAttempt, } from "./lifecycle.js";
8
8
  import { createCapturedNativeEvidenceResolver, describeNormalizedDataset, validateNormalizedDataset, } from "./normalization-integrity.js";
@@ -56,6 +56,9 @@ export const CURSOR_SDK_CAPABILITIES = {
56
56
  export async function captureCursorSdkRun(options) {
57
57
  requireText(options.apiKey, "Cursor API key");
58
58
  requireText(options.prompt, "Cursor prompt");
59
+ if (options.configuration.toolPolicy.enableAgentRetries !== true) {
60
+ throw new Error("Cursor capture requires enableAgentRetries: true for native recovery.");
61
+ }
59
62
  if (options.definition.run.model.id !== options.configuration.model.id) {
60
63
  throw new Error("The declared model must match the Cursor SDK model configuration.");
61
64
  }
@@ -119,8 +122,15 @@ export async function captureCursorSdkRun(options) {
119
122
  return workspace;
120
123
  },
121
124
  cleanup: async (context) => {
122
- if (workspace?.status === "ready")
123
- await captureWorkspace();
125
+ // Retain the source on packaging failure; the assembler records the outcome gap.
126
+ if (workspace?.status === "ready") {
127
+ try {
128
+ await captureWorkspace();
129
+ }
130
+ catch {
131
+ return;
132
+ }
133
+ }
124
134
  await options.workspace.cleanup?.(context);
125
135
  },
126
136
  };
@@ -276,11 +286,13 @@ async function executeCursorSdk(options) {
276
286
  });
277
287
  try {
278
288
  await assertCursorWorkspaceIsolation(options.workspacePath);
289
+ Cursor.configure({ local: { useHttp1ForAgent: true } });
279
290
  await options.writer.record("configuration", {
280
291
  sdkVersion: CURSOR_SDK_VERSION,
281
292
  runtimeIdentity: { status: "not-exposed", detail: "The public SDK does not expose a separately versioned local agent runtime." },
282
293
  model: options.configuration.model,
283
294
  toolPolicy: options.configuration.toolPolicy,
295
+ transport: { useHttp1ForAgent: true },
284
296
  workingDirectory: options.workspacePath,
285
297
  workingDirectoryPolicy: "isolated-local.cwd",
286
298
  environmentKeys: environment.keys,
@@ -299,7 +311,7 @@ async function executeCursorSdk(options) {
299
311
  settingSources: [],
300
312
  sandboxOptions: { enabled: options.configuration.toolPolicy.sandbox.enabled },
301
313
  autoReview: false,
302
- enableAgentRetries: false,
314
+ enableAgentRetries: options.configuration.toolPolicy.enableAgentRetries,
303
315
  },
304
316
  });
305
317
  agent = await abortable(agentPromise, options.signal, "Cursor agent creation", async (lateAgent) => {
@@ -130,7 +130,7 @@ export async function captureOpenHandsAgentServerRun(options) {
130
130
  catch (error) {
131
131
  if (workspaceOutcome === undefined) {
132
132
  workspaceCaptureError = error instanceof Error ? error.message : String(error);
133
- throw error;
133
+ return; // Preserve native completion and the source for later capture recovery.
134
134
  }
135
135
  }
136
136
  await options.workspace.cleanup?.(context);
@@ -318,7 +318,8 @@ export async function captureOpenHandsAgentServerRun(options) {
318
318
  const retainedWorkspacePath = workspace?.status === "ready" && workspaceOutcome === undefined
319
319
  ? workspace.path
320
320
  : undefined;
321
- if (!["qualified", "qualified-with-gaps"].includes(qualification.status)) {
321
+ if (!qualification.semanticAnalysisUsable || Object.entries(qualification.dimensions)
322
+ .some(([dimension, result]) => dimension !== "workspace" && result.status === "unqualified")) {
322
323
  return {
323
324
  attempt,
324
325
  manifest,
@@ -332,7 +333,8 @@ export async function captureOpenHandsAgentServerRun(options) {
332
333
  await validateUniformEvents(normalized.events, {
333
334
  resolve: (reference) => resolvesCaptureReference(capture, reference),
334
335
  });
335
- return { attempt, manifest, qualification, capture, normalized };
336
+ return { attempt, manifest, qualification, capture, normalized,
337
+ ...(retainedWorkspacePath === undefined ? {} : { retainedWorkspacePath }) };
336
338
  }
337
339
  catch (error) {
338
340
  return {
package/dist/src/pi.js CHANGED
@@ -150,8 +150,15 @@ export async function capturePiSdkRun(options) {
150
150
  return workspace;
151
151
  },
152
152
  cleanup: async (context) => {
153
- if (workspace?.status === "ready")
154
- await captureWorkspace();
153
+ // Retain the source on packaging failure; the assembler records the outcome gap.
154
+ if (workspace?.status === "ready") {
155
+ try {
156
+ await captureWorkspace();
157
+ }
158
+ catch {
159
+ return;
160
+ }
161
+ }
155
162
  await options.workspace.cleanup?.(context);
156
163
  },
157
164
  };
@@ -1,9 +1,9 @@
1
1
  import { execFile, spawn } from "node:child_process";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { createReadStream, createWriteStream } from "node:fs";
4
- import { cp, lstat, mkdir, mkdtemp, readdir, realpath, rm, rmdir, utimes, writeFile } from "node:fs/promises";
4
+ import { cp, lstat, mkdir, mkdtemp, readdir, readlink, realpath, rm, rmdir, symlink, utimes, writeFile } from "node:fs/promises";
5
5
  import { tmpdir } from "node:os";
6
- import { join, resolve, sep } from "node:path";
6
+ import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
7
7
  import { promisify } from "node:util";
8
8
  import { Transform } from "node:stream";
9
9
  import { pipeline } from "node:stream/promises";
@@ -259,7 +259,33 @@ async function withWorkspaceOutcomeProjection(options, use) {
259
259
  throw new Error(`Workspace outcome exclusion "${name}" is invalid.`);
260
260
  }
261
261
  }
262
- if (exclusions.length === 0 && options.respectGitignore !== true && options.omitEmptyDirectories !== true) {
262
+ const absoluteLinks = [];
263
+ const sourceRoot = await realpath(finalPath);
264
+ const contained = (path) => {
265
+ const rel = relative(sourceRoot, path);
266
+ return !isAbsolute(rel) && rel !== ".." && !rel.startsWith(`..${sep}`);
267
+ };
268
+ const collectLinks = async (directory) => {
269
+ for (const entry of await readdir(directory, { withFileTypes: true })) {
270
+ const path = join(directory, entry.name);
271
+ if (entry.isDirectory()) {
272
+ if (!exclusions.includes(entry.name))
273
+ await collectLinks(path);
274
+ }
275
+ else if (entry.isSymbolicLink()) {
276
+ const target = await readlink(path);
277
+ if (!isAbsolute(target))
278
+ continue;
279
+ const resolvedTarget = await realpath(path);
280
+ if (!contained(resolvedTarget)) {
281
+ throw new Error(`Workspace symbolic link escapes its root at "${relative(sourceRoot, path)}".`);
282
+ }
283
+ absoluteLinks.push({ path: relative(sourceRoot, path), target: relative(dirname(path), resolvedTarget) || "." });
284
+ }
285
+ }
286
+ };
287
+ await collectLinks(sourceRoot);
288
+ if (absoluteLinks.length === 0 && exclusions.length === 0 && options.respectGitignore !== true && options.omitEmptyDirectories !== true) {
263
289
  return use(startPath, finalPath);
264
290
  }
265
291
  await assertNoWorkspaceHardLinks(finalPath, new Set(exclusions));
@@ -274,6 +300,12 @@ async function withWorkspaceOutcomeProjection(options, use) {
274
300
  filter: async (source) => source === finalPath
275
301
  || !await isExcludedWorkspaceDirectory(finalPath, source, exclusions),
276
302
  });
303
+ // Only the derived capture is relocated; native workspace link text is untouched.
304
+ for (const link of absoluteLinks) {
305
+ const path = join(projectedPath, link.path);
306
+ await rm(path);
307
+ await symlink(link.target, path);
308
+ }
277
309
  if (options.respectGitignore === true)
278
310
  await removeIgnoredWorkspaceEntries(startPath, projectedPath);
279
311
  if (options.omitEmptyDirectories === true)
@@ -91,7 +91,7 @@ async function hashWorkspaceDirectory(directory, relativeDirectory, hash, includ
91
91
  const rel = relative(root, candidate);
92
92
  return !isAbsolute(rel) && rel !== ".." && !rel.startsWith(`..${sep}`);
93
93
  };
94
- if (isAbsolute(target) || !contained(resolve(dirname(path), target))) {
94
+ if (!isAbsolute(target) && !contained(resolve(dirname(path), target))) {
95
95
  throw new Error(`Workspace symbolic link escapes its root at "${relativePath}".`);
96
96
  }
97
97
  let resolved;
@@ -41,6 +41,20 @@ events can still explain a stop. Keep terminal state, infrastructure failure,
41
41
  and capture quality separate. Preserve `retainedWorkspacePath` when outcome
42
42
  packaging failed; that path is a recovery location, not qualified evidence.
43
43
 
44
+ A workspace packaging error does not change a completed observational execution
45
+ into a model or infrastructure failure. EBO omits the unpublishable workspace
46
+ artifact, retains the source for recovery, and records the outcome gap. Native
47
+ events, hooks, and telemetry remain available. Normalization and behavior analysis
48
+ can use qualified semantic evidence even when the workspace dimension is
49
+ unqualified; claims about missing work-product evidence must abstain. Verified
50
+ tasks still require their verifier and workspace binding.
51
+
52
+ Overall bundle qualification can remain `unqualified` because an outcome is
53
+ missing. Consumers should inspect `semanticAnalysisUsable` and the individual
54
+ qualification dimensions instead of treating that aggregate as a pipeline-wide
55
+ fatal error. Export independently enforces its sanitization and sharing policy;
56
+ successful capture is not permission to share.
57
+
44
58
  Capture reports retain packaging exceptions as `workspace-capture-error`
45
59
  missing-evidence entries. Inspect the detail before retrying capture against a
46
60
  retained workspace. A later successful capture is recovery evidence; it does
@@ -57,10 +71,13 @@ unless a fingerprint was recorded then. Do not count the recovery as another
57
71
  model trial or as an independent sample.
58
72
 
59
73
  Workspace outcomes preserve relative symbolic links whose lexical targets and
60
- resolved referents stay inside the workspace. Link text is hashed and copied
61
- verbatim, not replaced by the referent's bytes. Empty-directory omission keeps
62
- directories referenced by links. Absolute, escaping, dangling and
63
- cyclic links fail capture. Patch application or snapshot extraction must still
74
+ resolved referents stay inside the workspace. Contained absolute links are valid:
75
+ the derived capture rewrites them as relative links to their resolved in-workspace
76
+ targets, so the result survives relocation. The source workspace is not changed.
77
+ Relative link text is preserved, not replaced by the referent's bytes.
78
+ Empty-directory omission keeps directories referenced by links. External,
79
+ dangling and cyclic links prevent workspace artifact packaging, not native event
80
+ capture. Patch application or snapshot extraction must still
64
81
  reproduce the resulting tree digest. This does not relax task-archive admission
65
82
  or verifier sandbox rules.
66
83
 
@@ -38,7 +38,21 @@ the following digest-pinned settings for every Cursor queue entry:
38
38
  MDM, or plugin settings are not silently loaded;
39
39
  - `autoReview: false`, so instrumentation does not add a behavior-changing
40
40
  classifier; and
41
- - `enableAgentRetries: false`, so a retained attempt is not silently replaced.
41
+ - `enableAgentRetries: true`, enabling Cursor's native transport/stall recovery
42
+ within the owned run. EBO does not add a retry loop or replacement attempt.
43
+
44
+ Before agent creation, EBO calls
45
+ `Cursor.configure({ local: { useHttp1ForAgent: true } })` to select HTTP/1.1
46
+ for local backend streams. This mitigates the observed `NGHTTP2_INTERNAL_ERROR`;
47
+ it is not a proven root-cause fix. Both settings use the
48
+ [public SDK API](https://cursor.com/docs/sdk/typescript).
49
+ The native `configuration` record retains `transport.useHttp1ForAgent` and
50
+ `toolPolicy.enableAgentRetries`. Terminal errors and partial evidence remain
51
+ retained if native recovery fails; the attempt deadline still applies.
52
+
53
+ Existing frozen policies with retries disabled are rejected at launch, not
54
+ silently overridden. Prepare new configuration records and a new queue for
55
+ future runs; preserve earlier frozen experiments and captures unchanged.
42
56
 
43
57
  SDK `1.0.31` treats an ancestor Git checkout as the local project root. EBO
44
58
  therefore rejects a materialized workspace nested under another `.git` path
@@ -79,7 +93,7 @@ provider ID containing dots.
79
93
  ```
80
94
 
81
95
  ```json
82
- { "schemaVersion": "ebo.cursor-sdk-config/v1", "kind": "native-tool-policy", "tools": ["read", "edit", "grep", "glob", "ls"], "disallowedTools": ["shell", "task", "mcp", "webSearch", "webFetch"], "sandbox": { "enabled": false }, "settingSources": [], "autoReview": false, "enableAgentRetries": false }
96
+ { "schemaVersion": "ebo.cursor-sdk-config/v1", "kind": "native-tool-policy", "tools": ["read", "edit", "grep", "glob", "ls"], "disallowedTools": ["shell", "task", "mcp", "webSearch", "webFetch"], "sandbox": { "enabled": false }, "settingSources": [], "autoReview": false, "enableAgentRetries": true }
83
97
  ```
84
98
 
85
99
  ```json
@@ -1 +1 @@
1
- {"schemaVersion":"ebo.cursor-sdk-config/v1","kind":"native-tool-policy","tools":["read","edit","grep","glob","ls"],"disallowedTools":["shell","task","mcp","webSearch","webFetch"],"sandbox":{"enabled":false},"settingSources":[],"autoReview":false,"enableAgentRetries":false}
1
+ {"schemaVersion":"ebo.cursor-sdk-config/v1","kind":"native-tool-policy","tools":["read","edit","grep","glob","ls"],"disallowedTools":["shell","task","mcp","webSearch","webFetch"],"sandbox":{"enabled":false},"settingSources":[],"autoReview":false,"enableAgentRetries":true}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "engineering-behavior-observatory",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "description": "Capture engineering-agent trajectories, evaluate behavior, and inspect cited evidence across harnesses.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -49,6 +49,8 @@
49
49
  "release/0.2.3/",
50
50
  "release/0.2.4/",
51
51
  "release/0.2.5/",
52
+ "release/0.2.6/",
53
+ "release/0.2.7/",
52
54
  "schemas/",
53
55
  "scripts/atlas-grafana.sh"
54
56
  ],
@@ -0,0 +1,15 @@
1
+ # v0.2.6 known limitations
2
+
3
+ The [v0.2.5 limitations](../0.2.5/KNOWN_LIMITATIONS.md) continue to apply, except
4
+ that contained absolute workspace links are now supported.
5
+
6
+ - Link relocation describes a derived workspace artifact, not byte-identical
7
+ original link text. Source directories are not modified.
8
+ - Missing workspace evidence remains unqualified for work-product claims.
9
+ Semantic analysis may proceed only when its own evidence qualifies.
10
+ - A verified task whose verifier or workspace binding fails is not reported as
11
+ successful. Genuine harness, setup, persistence, and cleanup errors remain
12
+ distinct from workspace packaging gaps.
13
+ - Native telemetry receipt limits and provider evidence gaps are unchanged.
14
+ - No models are rerun to recover capture. A later recovered workspace cannot
15
+ prove its exact earlier state without a fingerprint recorded at that time.
@@ -0,0 +1,26 @@
1
+ # v0.2.6: Contain workspace capture failures
2
+
3
+ Workspace outcome capture accepts absolute symbolic links that resolve inside
4
+ the source workspace. The derived patch or snapshot uses portable relative
5
+ links; the source workspace is unchanged. External, dangling, and cyclic links
6
+ remain rejected for workspace packaging.
7
+
8
+ A workspace packaging failure no longer rewrites native completion as an
9
+ infrastructure failure. Capture records the missing outcome and retains the
10
+ source for recovery. Native semantic evidence can be normalized and analyzed
11
+ independently of workspace qualification. Verified task outcomes still require
12
+ a verifier bound to the captured workspace. Export safety checks are unchanged.
13
+
14
+ The run-manifest v1 schema now permits completed observational runs without a
15
+ workspace artifact. Consumers must use qualification dimensions when deciding
16
+ which evidence supports their analysis, rather than infer completeness from
17
+ terminal state.
18
+
19
+ Regression coverage includes absolute-link patch/snapshot relocation, preserved
20
+ source links, invalid-target rejection, cleanup containment, and normalization
21
+ with missing workspace evidence. A real retained xhigh trajectory generated 21
22
+ structural observations without its workspace artifact and without model calls.
23
+
24
+ See [known limitations](KNOWN_LIMITATIONS.md), the
25
+ [reproducibility manifest](reproducibility.json), and the
26
+ [evidence guide](../../docs/guides/evidence-and-sharing.md).
@@ -0,0 +1,74 @@
1
+ {
2
+ "schemaVersion": "ebo.release-reproducibility/v1",
3
+ "release": {
4
+ "name": "engineering-behavior-observatory",
5
+ "version": "0.2.6"
6
+ },
7
+ "runtime": {
8
+ "node": "24.19.0",
9
+ "claudeAgentSdk": "0.3.258",
10
+ "openhandsAgentServer": "1.46.0",
11
+ "deepseekClient": "0.1.1-rc.2",
12
+ "deepseekProtocol": "0.1.1-rc.2",
13
+ "deepseekRuntime": "0.1.1-rc.2",
14
+ "codexAppServer": "0.153.4",
15
+ "grafana": "13.2.0",
16
+ "grafanaInfinity": "4.0.0",
17
+ "piSdk": "0.85.1",
18
+ "cursorSdk": "1.0.31"
19
+ },
20
+ "commands": [
21
+ "npm ci",
22
+ "npm run acceptance"
23
+ ],
24
+ "fixtureCoverage": {
25
+ "agent-sdk": "frozen queue entry through qualified capture, approved export, normalization, configurable judging, review, aggregation, and Atlas",
26
+ "openhands-agent-server": "pinned REST/WebSocket stream-final reconciliation, workspace/outcome evidence, partial capture, and retained evaluation",
27
+ "deepseek-harness": "official-client JSON-RPC composition, receipt-to-idle completion, stderr, interruption, shutdown, swaps, and retained evaluation",
28
+ "codex-app-server": "owned stdio lifecycle, full history, OTLP, interruption/failure, export, and retained evaluation; configurable workspace-write networking with explicit offline queue propagation",
29
+ "acceptance-cases": "seeded ordering, secret scanning, partial attempts, native references, abstention, disputes, denominators, and unsupported comparisons",
30
+ "pi-sdk": "frozen queue, passive hooks, durable session, partial cleanup, export, retained normalization and evaluation",
31
+ "cursor-sdk": "frozen queue, official store, callbacks/history, bounded cancellation, export, retained normalization and evaluation",
32
+ "workspace-capture": "Contained absolute links relocated in derived captures; workspace packaging gaps preserve native completion and allow semantic analysis independently"
33
+ },
34
+ "determinism": {
35
+ "stable": [
36
+ "task, run, attempt, event, assertion, aggregate, and Atlas case identities",
37
+ "fixture, native-reference, normalized-dataset, source/cohort, archive, and package digests",
38
+ "seeded queue and review-sample ordering"
39
+ ],
40
+ "excluded": [
41
+ "native and EBO observation timestamps",
42
+ "lifecycle start/finish timestamps",
43
+ "provider timing and usage",
44
+ "temporary workspace and output paths"
45
+ ]
46
+ },
47
+ "fixtures": {
48
+ "tests/fixtures/task-packet.valid.v1.json": "ac2ef1043c0cc16bf4c21d05c1dc880ca1b4b8cc2f9ea7653328e91f0b0e2681",
49
+ "test/fixtures/agent-sdk-normalizer/complete.input.json": "7285dee088517e5de38ceda84fc7c58d9303949c7448ab3b5e10a5a2d2ebdfe3",
50
+ "test/fixtures/agent-sdk-normalizer/complete.expected.jsonl": "d32fa3a013bc179f1673f7e1a89b8a721bb8ddaf63eba5bc8a3f14f1cc148ed8",
51
+ "contracts/openhands-agent-server-v1.44.1.json": "e7a07977688a0703b15751a8a1ab17f29be45f9fdf438017e0dbcbc49cca37b0",
52
+ "contracts/openhands-agent-server-v1.46.0.json": "455cdddd2c206cb2f20245c2189b92e773657ec14d651236baa532b2dbadeb28",
53
+ "test/fixtures/openhands/v1.44.1/streamed-events.json": "d751083ba2ef94c9865117db481cc2f4933772bbbbb6b9fcf60571563715c1ea",
54
+ "test/fixtures/openhands/v1.44.1/final-events.json": "551db9904e860004d604c923692b5c5d3aaee966220492214c60003d2df415b6",
55
+ "test/fixtures/deepseek/golden-success.json": "f3867d38169bf0e28f64aba1dd60553f21cf07f41f81b4bad48261e49fa3ca88",
56
+ "test/fixtures/deepseek/golden-interrupted.json": "79e1f8404df1ee3b562d0bdbc0b2ee712ffc6d5774992b92970814ea815677b7",
57
+ "test/fixtures/deepseek/compositions/minimal/composition.json": "72fe4368e105f1bad82b7fdf8380e92c9fa689d8985f6ab91f534f0baf3eda02",
58
+ "contracts/codex-app-server-0.153.4/manifest.json": "e62281ff5e5d1cc71d07763b85e997ccc9ddbad9aa82a469da4181ee4b1890a8",
59
+ "test/fixtures/codex/legacy-0.150.1.dataset.json": "f71906b009e546afbf3b4e79396f223181382f3b3d8795e9cfcd032398a9d6bf",
60
+ "test/fixtures/behavior-assertions/abstained.json": "7f1962864f664c794925a67d93074e7b150af0f57c58041a81e062990d641197",
61
+ "test/fixtures/behavior-assertions/disputed.review.json": "450d3d9efe7feefaf7500b3ad20ab2803b412c7e53b91763125e15a83be87981",
62
+ "test/fixtures/comparison/exact.json": "cafa68e94b8d50dc308ddcfc2e73aa139c828b4ecd1e1c762bbc51bf1b44501e",
63
+ "test/fixtures/structural-observations/golden.json": "bad71a3a93524b76c0a2fdbb58e98e1e65be46cae14acf18ac89d9ba76e03e8a",
64
+ "test/atlas-fixture.ts": "9f03ce9cbcd0e5d3e3a5916866554ceb1c21f1c8712e58529499c1ea499f048d",
65
+ "test/pi.test.ts": "a00997c5617ca178e19b25b0d483cbdce8ce7c306d4c871557945b31a2e657cf",
66
+ "test/cursor-sdk.test.ts": "753094f127b11dc392ec2244c9edddabec27aa94347bb6159a292129ed2a2822",
67
+ "examples/cursor-sdk/README.md": "a2cf481123e7f3deb1c5a682ba208a775c3f91c35e1a6c1a428c517c79692618",
68
+ "examples/cursor-sdk/capture-profile.json": "d1a5787875daf5523b3783f259b891c256a904cbb10279d5be214af846305a2d",
69
+ "examples/cursor-sdk/harness.json": "2f1da6f2b17258d0ee59431f687696901ce3692589175d182f3a4bbe8dd4d689",
70
+ "examples/cursor-sdk/model.json": "ad8f31ff5d2387c02d79226679022358afafc61ba5dda73040e3f754530a230a",
71
+ "examples/cursor-sdk/native-limits.json": "40fe451f56b0863e79b42f5c85defbce678e1dc90dd248d15c430db176d71511",
72
+ "examples/cursor-sdk/native-tool-policy.json": "0fef98b36cd39de82914c5b068022c1335dadda24b7d6e7df398b2430e6a16e0"
73
+ }
74
+ }
@@ -0,0 +1,8 @@
1
+ # v0.2.7 known limitations
2
+
3
+ The [v0.2.6 limitations](../0.2.6/KNOWN_LIMITATIONS.md) continue to apply.
4
+
5
+ - HTTP/1.1 mitigates the observed HTTP/2 failure; it is not a proven root-cause fix.
6
+ - Native recovery can still fail or reach the attempt deadline.
7
+ - Deterministic tests verify configuration and evidence handling, not live
8
+ provider recovery. No new full trial was run for this patch.
@@ -0,0 +1,17 @@
1
+ # v0.2.7: Cursor native recovery
2
+
3
+ Cursor capture configures HTTP/1.1 before agent creation and enables the SDK's
4
+ native transport/stall retries. The configuration evidence records both settings.
5
+ EBO does not launch replacement attempts or add its own retry loop. Terminal
6
+ errors and partial evidence remain available when native recovery fails.
7
+
8
+ New queue policies must explicitly set `enableAgentRetries: true`. Earlier
9
+ frozen policies are rejected at launch rather than silently overridden; prior
10
+ experiments and retained captures remain unchanged.
11
+
12
+ Regression tests cover configuration ordering, effective evidence, policy
13
+ validation, and terminal-error retention with exactly one agent creation.
14
+
15
+ See the [Cursor guide](../../docs/harnesses/cursor-sdk.md),
16
+ [known limitations](KNOWN_LIMITATIONS.md), and
17
+ [reproducibility manifest](reproducibility.json).
@@ -0,0 +1,74 @@
1
+ {
2
+ "schemaVersion": "ebo.release-reproducibility/v1",
3
+ "release": {
4
+ "name": "engineering-behavior-observatory",
5
+ "version": "0.2.7"
6
+ },
7
+ "runtime": {
8
+ "node": "24.19.0",
9
+ "claudeAgentSdk": "0.3.258",
10
+ "openhandsAgentServer": "1.46.0",
11
+ "deepseekClient": "0.1.1-rc.2",
12
+ "deepseekProtocol": "0.1.1-rc.2",
13
+ "deepseekRuntime": "0.1.1-rc.2",
14
+ "codexAppServer": "0.153.4",
15
+ "grafana": "13.2.0",
16
+ "grafanaInfinity": "4.0.0",
17
+ "piSdk": "0.85.1",
18
+ "cursorSdk": "1.0.31"
19
+ },
20
+ "commands": [
21
+ "npm ci",
22
+ "npm run acceptance"
23
+ ],
24
+ "fixtureCoverage": {
25
+ "agent-sdk": "frozen queue entry through qualified capture, approved export, normalization, configurable judging, review, aggregation, and Atlas",
26
+ "openhands-agent-server": "pinned REST/WebSocket stream-final reconciliation, workspace/outcome evidence, partial capture, and retained evaluation",
27
+ "deepseek-harness": "official-client JSON-RPC composition, receipt-to-idle completion, stderr, interruption, shutdown, swaps, and retained evaluation",
28
+ "codex-app-server": "owned stdio lifecycle, full history, OTLP, interruption/failure, export, and retained evaluation; configurable workspace-write networking with explicit offline queue propagation",
29
+ "acceptance-cases": "seeded ordering, secret scanning, partial attempts, native references, abstention, disputes, denominators, and unsupported comparisons",
30
+ "pi-sdk": "frozen queue, passive hooks, durable session, partial cleanup, export, retained normalization and evaluation",
31
+ "cursor-sdk": "frozen queue, official store, callbacks/history, bounded cancellation, export, retained normalization and evaluation; HTTP/1.1 configuration ordering, native retry validation, terminal transport error retention without replacement",
32
+ "workspace-capture": "Contained absolute links relocated in derived captures; workspace packaging gaps preserve native completion and allow semantic analysis independently"
33
+ },
34
+ "determinism": {
35
+ "stable": [
36
+ "task, run, attempt, event, assertion, aggregate, and Atlas case identities",
37
+ "fixture, native-reference, normalized-dataset, source/cohort, archive, and package digests",
38
+ "seeded queue and review-sample ordering"
39
+ ],
40
+ "excluded": [
41
+ "native and EBO observation timestamps",
42
+ "lifecycle start/finish timestamps",
43
+ "provider timing and usage",
44
+ "temporary workspace and output paths"
45
+ ]
46
+ },
47
+ "fixtures": {
48
+ "tests/fixtures/task-packet.valid.v1.json": "ac2ef1043c0cc16bf4c21d05c1dc880ca1b4b8cc2f9ea7653328e91f0b0e2681",
49
+ "test/fixtures/agent-sdk-normalizer/complete.input.json": "7285dee088517e5de38ceda84fc7c58d9303949c7448ab3b5e10a5a2d2ebdfe3",
50
+ "test/fixtures/agent-sdk-normalizer/complete.expected.jsonl": "d32fa3a013bc179f1673f7e1a89b8a721bb8ddaf63eba5bc8a3f14f1cc148ed8",
51
+ "contracts/openhands-agent-server-v1.44.1.json": "e7a07977688a0703b15751a8a1ab17f29be45f9fdf438017e0dbcbc49cca37b0",
52
+ "contracts/openhands-agent-server-v1.46.0.json": "455cdddd2c206cb2f20245c2189b92e773657ec14d651236baa532b2dbadeb28",
53
+ "test/fixtures/openhands/v1.44.1/streamed-events.json": "d751083ba2ef94c9865117db481cc2f4933772bbbbb6b9fcf60571563715c1ea",
54
+ "test/fixtures/openhands/v1.44.1/final-events.json": "551db9904e860004d604c923692b5c5d3aaee966220492214c60003d2df415b6",
55
+ "test/fixtures/deepseek/golden-success.json": "f3867d38169bf0e28f64aba1dd60553f21cf07f41f81b4bad48261e49fa3ca88",
56
+ "test/fixtures/deepseek/golden-interrupted.json": "79e1f8404df1ee3b562d0bdbc0b2ee712ffc6d5774992b92970814ea815677b7",
57
+ "test/fixtures/deepseek/compositions/minimal/composition.json": "72fe4368e105f1bad82b7fdf8380e92c9fa689d8985f6ab91f534f0baf3eda02",
58
+ "contracts/codex-app-server-0.153.4/manifest.json": "e62281ff5e5d1cc71d07763b85e997ccc9ddbad9aa82a469da4181ee4b1890a8",
59
+ "test/fixtures/codex/legacy-0.150.1.dataset.json": "f71906b009e546afbf3b4e79396f223181382f3b3d8795e9cfcd032398a9d6bf",
60
+ "test/fixtures/behavior-assertions/abstained.json": "7f1962864f664c794925a67d93074e7b150af0f57c58041a81e062990d641197",
61
+ "test/fixtures/behavior-assertions/disputed.review.json": "450d3d9efe7feefaf7500b3ad20ab2803b412c7e53b91763125e15a83be87981",
62
+ "test/fixtures/comparison/exact.json": "cafa68e94b8d50dc308ddcfc2e73aa139c828b4ecd1e1c762bbc51bf1b44501e",
63
+ "test/fixtures/structural-observations/golden.json": "bad71a3a93524b76c0a2fdbb58e98e1e65be46cae14acf18ac89d9ba76e03e8a",
64
+ "test/atlas-fixture.ts": "9f03ce9cbcd0e5d3e3a5916866554ceb1c21f1c8712e58529499c1ea499f048d",
65
+ "test/pi.test.ts": "a00997c5617ca178e19b25b0d483cbdce8ce7c306d4c871557945b31a2e657cf",
66
+ "test/cursor-sdk.test.ts": "ceb8aeb719b9554d51bb3cfe78fd0bc796acdce8ddc5ef205a327fac4226f57e",
67
+ "examples/cursor-sdk/README.md": "a2cf481123e7f3deb1c5a682ba208a775c3f91c35e1a6c1a428c517c79692618",
68
+ "examples/cursor-sdk/capture-profile.json": "d1a5787875daf5523b3783f259b891c256a904cbb10279d5be214af846305a2d",
69
+ "examples/cursor-sdk/harness.json": "2f1da6f2b17258d0ee59431f687696901ce3692589175d182f3a4bbe8dd4d689",
70
+ "examples/cursor-sdk/model.json": "ad8f31ff5d2387c02d79226679022358afafc61ba5dda73040e3f754530a230a",
71
+ "examples/cursor-sdk/native-limits.json": "40fe451f56b0863e79b42f5c85defbce678e1dc90dd248d15c430db176d71511",
72
+ "examples/cursor-sdk/native-tool-policy.json": "8148b9ad57134d217a03e47cc11bcc85758e1115b64bb06b85564139aecd1cd9"
73
+ }
74
+ }
package/release/README.md CHANGED
@@ -5,6 +5,8 @@ For downloads and published artifacts, see
5
5
 
6
6
  | Version | Changes and verification | Support boundary |
7
7
  | :--- | :--- | :--- |
8
+ | [0.2.7](0.2.7/README.md) | Cursor native transport/stall recovery and recorded HTTP/1.1 settings | [Known limitations](0.2.7/KNOWN_LIMITATIONS.md) |
9
+ | [0.2.6](0.2.6/README.md) | Portable contained absolute links; isolate workspace packaging failures from native completion and semantic analysis | [Known limitations](0.2.6/KNOWN_LIMITATIONS.md) |
8
10
  | [0.2.5](0.2.5/README.md) | Preserve contained workspace symlinks and macOS AppleDouble files | [Known limitations](0.2.5/KNOWN_LIMITATIONS.md) |
9
11
  | [0.2.4](0.2.4/README.md) | Stream workspace snapshots and integrity checks; support archives up to 1 GiB | [Known limitations](0.2.4/KNOWN_LIMITATIONS.md) |
10
12
  | [0.2.3](0.2.3/README.md) | Retain workspace capture errors in partial-bundle reports | [Known limitations](0.2.3/KNOWN_LIMITATIONS.md) |
@@ -179,7 +179,6 @@
179
179
  {
180
180
  "if": { "properties": { "state": { "const": "completed" } }, "required": ["state"] },
181
181
  "then": {
182
- "required": ["workspaceArtifactId"],
183
182
  "properties": {
184
183
  "failureClass": { "const": "none" },
185
184
  "stopReason": { "const": "none" }