Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/breezy-sources-sing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tangle-network/agent-interface": minor
---

Add a portable, validated source reference for signed agent improvement proposals.
46 changes: 46 additions & 0 deletions packages/agent-interface/src/agent-improvement-source.test.ts
Original file line number Diff line number Diff line change
@@ -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();
});
});
52 changes: 52 additions & 0 deletions packages/agent-interface/src/agent-improvement-source.ts
Original file line number Diff line number Diff line change
@@ -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<string, AgentCandidateJsonValue> {
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);
}
1 change: 1 addition & 0 deletions packages/agent-interface/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Loading