From dc2990e4dc3dfd44b40a0e165cb7610e6fae6c2b Mon Sep 17 00:00:00 2001 From: Drew Stone Date: Thu, 23 Jul 2026 01:39:43 -0600 Subject: [PATCH] feat(interface): bind exact improvement sources --- .changeset/breezy-sources-sing.md | 5 ++ .../src/agent-improvement-source.test.ts | 46 ++++++++++++++++ .../src/agent-improvement-source.ts | 52 +++++++++++++++++++ packages/agent-interface/src/index.ts | 1 + 4 files changed, 104 insertions(+) create mode 100644 .changeset/breezy-sources-sing.md create mode 100644 packages/agent-interface/src/agent-improvement-source.test.ts create mode 100644 packages/agent-interface/src/agent-improvement-source.ts diff --git a/.changeset/breezy-sources-sing.md b/.changeset/breezy-sources-sing.md new file mode 100644 index 0000000..e1d9543 --- /dev/null +++ b/.changeset/breezy-sources-sing.md @@ -0,0 +1,5 @@ +--- +"@tangle-network/agent-interface": minor +--- + +Add a portable, validated source reference for signed agent improvement proposals. diff --git a/packages/agent-interface/src/agent-improvement-source.test.ts b/packages/agent-interface/src/agent-improvement-source.test.ts new file mode 100644 index 0000000..d4f9655 --- /dev/null +++ b/packages/agent-interface/src/agent-improvement-source.test.ts @@ -0,0 +1,46 @@ +import { describe, expect, it } from "vitest"; +import { + AGENT_IMPROVEMENT_SOURCE_METADATA_KEY, + agentImprovementSourceMetadata, + agentImprovementSourceSchema, + readAgentImprovementSource, +} from "./agent-improvement-source.js"; + +const source = { + kind: "platform-agent-profile", + sourceIdentity: "ap_reviewer", + sourceDigest: `sha256:${"a".repeat(64)}` as const, + sourceRevision: 17, +}; + +describe("agent improvement source", () => { + it("round-trips one exact source through signed proposal metadata", () => { + const metadata = agentImprovementSourceMetadata(source); + + expect(metadata).toEqual({ + [AGENT_IMPROVEMENT_SOURCE_METADATA_KEY]: source, + }); + expect(readAgentImprovementSource(metadata)).toEqual(source); + }); + + it("rejects missing, lossy, and malformed source references", () => { + expect(() => readAgentImprovementSource({})).toThrow( + /missing its source reference/, + ); + expect(() => + agentImprovementSourceSchema.parse({ + ...source, + sourceDigest: "sha256:not-a-digest", + }), + ).toThrow(); + expect( + agentImprovementSourceSchema.parse({ + ...source, + sourceRevision: "git:abc123", + }), + ).toMatchObject({ sourceRevision: "git:abc123" }); + expect(() => + agentImprovementSourceSchema.parse({ ...source, unexpected: true }), + ).toThrow(); + }); +}); diff --git a/packages/agent-interface/src/agent-improvement-source.ts b/packages/agent-interface/src/agent-improvement-source.ts new file mode 100644 index 0000000..9800fef --- /dev/null +++ b/packages/agent-interface/src/agent-improvement-source.ts @@ -0,0 +1,52 @@ +import { z } from "zod"; +import type { AgentCandidateJsonValue } from "./agent-candidate.js"; +import { sha256DigestSchema } from "./agent-candidate-schema-common.js"; + +/** Metadata key that binds a measured improvement to its exact external source. */ +export const AGENT_IMPROVEMENT_SOURCE_METADATA_KEY = "agentImprovementSource"; + +/** + * Stable reference for the state from which an improvement candidate was made. + * `sourceIdentity` identifies the provider object; `sourceDigest` is the exact + * measured source state; `sourceRevision` is an + * opaque provider version retained to make stale-source errors intelligible to + * callers. A revision may be numeric or textual because providers use both + * counters and immutable revision identifiers. + */ +export const agentImprovementSourceSchema = z + .object({ + kind: z.string().trim().min(1).max(100).regex(/^[a-z][a-z0-9-]*$/), + sourceIdentity: z.string().trim().min(1).max(256), + sourceDigest: sha256DigestSchema, + sourceRevision: z.union([ + z.string().trim().min(1).max(256), + z.number().int().nonnegative(), + ]), + }) + .strict(); + +export type AgentImprovementSource = z.infer< + typeof agentImprovementSourceSchema +>; + +/** Attach one validated source reference to signed improvement metadata. */ +export function agentImprovementSourceMetadata( + source: AgentImprovementSource, +): Record { + return { + [AGENT_IMPROVEMENT_SOURCE_METADATA_KEY]: + agentImprovementSourceSchema.parse(source), + }; +} + +/** Read the exact source reference from a signed improvement proposal. */ +export function readAgentImprovementSource( + metadata: unknown, +): AgentImprovementSource { + const record = z.record(z.string(), z.unknown()).parse(metadata); + const source = record[AGENT_IMPROVEMENT_SOURCE_METADATA_KEY]; + if (source === undefined) { + throw new Error("signed improvement proposal is missing its source reference"); + } + return agentImprovementSourceSchema.parse(source); +} diff --git a/packages/agent-interface/src/index.ts b/packages/agent-interface/src/index.ts index b585d55..330566a 100644 --- a/packages/agent-interface/src/index.ts +++ b/packages/agent-interface/src/index.ts @@ -824,6 +824,7 @@ export * from "./interaction.js"; export * from "./agent-candidate.js"; export * from "./agent-candidate-schema.js"; export * from "./agent-candidate-promotion-schema.js"; +export * from "./agent-improvement-source.js"; export * from "./agent-profile.js"; export * from "./profile-diff.js"; export * from "./harness.js";