feat(agent): support custom metadata in jsonl session headers (#6417)

Allow callers to attach an opaque JSON object to the session header so
application context needed to rebuild a harness (for example an agent
profile reference) is readable from the first line alone, without
scanning session entries. The field is optional and ignored by readers
that do not use it; fork inherits the source metadata unless overridden.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
ArcadiaLin
2026-07-08 17:18:43 +08:00
committed by GitHub
parent cc2db98002
commit 7198e78f99
5 changed files with 81 additions and 0 deletions
+24
View File
@@ -65,4 +65,28 @@ describe("JsonlSessionRepo", () => {
expect(existsSync(sourceMetadata.path)).toBe(false);
await expect(repo.open(sourceMetadata)).rejects.toThrow("Session not found");
});
it("persists header metadata through create, list, and fork", async () => {
const root = createTempDir();
const env = new NodeExecutionEnv({ cwd: root });
const repo = new JsonlSessionRepo({ fs: env, sessionsRoot: root });
const source = await repo.create({
cwd: "/tmp/source",
id: "source-session",
metadata: { profile: "reviewer" },
});
const sourceMetadata = await source.getMetadata();
expect(sourceMetadata.metadata).toEqual({ profile: "reviewer" });
expect((await repo.list({ cwd: "/tmp/source" })).map((listed) => listed.metadata)).toEqual([
{ profile: "reviewer" },
]);
const fork = await repo.fork(sourceMetadata, { cwd: "/tmp/target", id: "fork-session" });
expect((await fork.getMetadata()).metadata).toEqual({ profile: "reviewer" });
const overridden = await repo.fork(sourceMetadata, {
cwd: "/tmp/target",
id: "overridden-session",
metadata: { profile: "writer" },
});
expect((await overridden.getMetadata()).metadata).toEqual({ profile: "writer" });
});
});
@@ -186,6 +186,48 @@ describe("JsonlSessionStorage", () => {
expect(await loadJsonlSessionMetadata(env, filePath)).toEqual(metadata);
});
it("round-trips custom header metadata", async () => {
const dir = createTempDir();
const env = new NodeExecutionEnv({ cwd: dir });
const filePath = join(dir, "session.jsonl");
const storage = await JsonlSessionStorage.create(env, filePath, {
cwd: dir,
sessionId: "session-1",
metadata: { profile: "reviewer" },
});
expect((await storage.getMetadata()).metadata).toEqual({ profile: "reviewer" });
const loaded = await JsonlSessionStorage.open(env, filePath);
expect((await loaded.getMetadata()).metadata).toEqual({ profile: "reviewer" });
expect((await loadJsonlSessionMetadata(env, filePath)).metadata).toEqual({ profile: "reviewer" });
});
it("omits header metadata when not provided", async () => {
const dir = createTempDir();
const env = new NodeExecutionEnv({ cwd: dir });
const filePath = join(dir, "session.jsonl");
await JsonlSessionStorage.create(env, filePath, { cwd: dir, sessionId: "session-1" });
expect(JSON.parse(readFileSync(filePath, "utf8").trim())).not.toHaveProperty("metadata");
expect((await loadJsonlSessionMetadata(env, filePath)).metadata).toBeUndefined();
});
it("throws for non-object header metadata", async () => {
const dir = createTempDir();
const env = new NodeExecutionEnv({ cwd: dir });
const filePath = join(dir, "session.jsonl");
const header = {
type: "session",
version: 3,
id: "session-1",
timestamp: "2026-01-01T00:00:00.000Z",
cwd: dir,
metadata: "profile",
};
writeFileSync(filePath, `${JSON.stringify(header)}\n`);
await expect(JsonlSessionStorage.open(env, filePath)).rejects.toThrow(
"session header metadata must be an object",
);
});
it("loads existing entries and reconstructs leaf", async () => {
const dir = createTempDir();
const env = new NodeExecutionEnv({ cwd: dir });