Skip to content

Commit e599df8

Browse files
scotty595claude
andcommitted
feat(register): caller-supplied agent id for identity binding
Adds an optional `id` field to AgentRegistration so callers can bind a new dashboard record to a framework's canonical agent id (e.g. Lua's `agent.agentId` from `lua.skill.yaml`) instead of letting register() generate a UUID. Solves the "first runtime enforce() call creates a duplicate agent" problem by making the dashboard record use the same id the runtime will send. Three coordinated pieces: - AgentRegistration.id (optional) — register() honors it when set, falls back to crypto.randomUUID() otherwise - GovernanceProcessorConfig.agentId (optional) — Mastra processor plugin passes it through to register() so framework integrations (lua-core etc.) can wire the canonical id from their config - ScannerPlugin.extractMetadata hook — scanner plugins surface framework-specific structured data (canonical ids, manifest fields) alongside the generic scan result via RepoScanResult.metadata. Convention key `externalId` is what consumers map to the register-time `id` field. Bumps governance-sdk to 0.8.2. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 04a0424 commit e599df8

9 files changed

Lines changed: 81 additions & 5 deletions

File tree

examples/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"hosted:all": "tsx hosted/vercel-ai.ts && tsx hosted/anthropic.ts && tsx hosted/mastra.ts && tsx hosted/langchain.ts && tsx hosted/openai-agents.ts && tsx hosted/mcp.ts"
2020
},
2121
"dependencies": {
22-
"governance-sdk": "^0.4.4",
22+
"governance-sdk": "^0.8.2",
2323
"tsx": "^4.19.0",
2424
"typescript": "^5.7.0"
2525
}

packages/governance-benchmark/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
},
6565
"homepage": "https://heygovernance.ai",
6666
"peerDependencies": {
67-
"governance-sdk": ">=0.4.0"
67+
"governance-sdk": "^0.8.2"
6868
},
6969
"peerDependenciesMeta": {
7070
"governance-sdk": {

packages/governance/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "governance-sdk",
3-
"version": "0.8.1",
3+
"version": "0.8.2",
44
"description": "AI Agent Governance for TypeScript — policy enforcement, scoring, compliance, and audit for AI agents",
55
"type": "module",
66
"main": "./dist/index.js",

packages/governance/src/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,10 @@ export function createGovernance(config: GovernanceConfig = {}): GovernanceInsta
177177
return remote.register(input);
178178
}
179179

180-
const id = crypto.randomUUID();
180+
// Honor a caller-supplied id when present (e.g. binding to a Lua
181+
// agent's canonical agentId from lua.skill.yaml so the dashboard
182+
// record uses the same id the runtime will send to enforce()).
183+
const id = input.id ?? crypto.randomUUID();
181184
const assessment = assessAgent(id, input);
182185

183186
// Persist capability booleans in metadata so re-scoring can reconstruct them

packages/governance/src/plugins/mastra-processor-types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,13 @@ export interface MastraProcessorInterface {
209209
// ─── Processor Configuration ──────────────────────────────────
210210

211211
export interface GovernanceProcessorConfig {
212+
/**
213+
* Optional caller-supplied agent id. When set, the processor will
214+
* register with this exact id so the runtime record matches a
215+
* pre-existing dashboard-scanned record (e.g. Lua's `agent.agentId`
216+
* from `lua.skill.yaml`). When omitted, the SDK generates a UUID.
217+
*/
218+
agentId?: string;
212219
agentName: string;
213220
owner: string;
214221
framework?: AgentFramework;

packages/governance/src/plugins/mastra-processor.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@ export class GovernanceProcessor implements MastraProcessorInterface {
8585

8686
private async doRegister(): Promise<void> {
8787
const registration: AgentRegistration = {
88+
// Pass through the caller-supplied id when present so the runtime
89+
// record binds to a pre-existing dashboard record (e.g. Lua's
90+
// canonical agentId). Without this, the SDK would generate a new
91+
// UUID on first register and create a duplicate row.
92+
id: this.config.agentId,
8893
name: this.config.agentName,
8994
framework: this.config.framework ?? "mastra",
9095
owner: this.config.owner,

packages/governance/src/repo-patterns.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,18 @@ export interface RepoScanResult {
2424
channels: string[];
2525
dependencies: string[];
2626
scannedFiles: number;
27+
/**
28+
* Framework-specific metadata extracted by a scanner plugin (e.g. the
29+
* Lua plugin pulling `agent.agentId` out of `lua.skill.yaml`). Always
30+
* undefined for plain `scanRepoContents` calls; populated by
31+
* `scanRepoContentsWithPlugins` when a plugin's `extractMetadata`
32+
* returns a value.
33+
*
34+
* The most important convention key is `externalId` — when present,
35+
* callers should use it as the agent's canonical id at registration
36+
* time so the dashboard record matches the runtime's enforce calls.
37+
*/
38+
metadata?: Record<string, unknown>;
2739
}
2840

2941
interface PatternDef {
@@ -389,7 +401,29 @@ export async function scanRepoContentsWithPlugins(
389401
}
390402
}
391403

392-
return { ...base, tools: [...tools] };
404+
// Collect framework-specific metadata from active plugins. Plugins are
405+
// merged in registration order — later plugins overwrite earlier keys
406+
// for the same field, but in practice only one plugin should claim a
407+
// given repo (gated by detectFramework) so collisions are rare.
408+
let metadata: Record<string, unknown> | undefined;
409+
for (const plugin of activePlugins) {
410+
if (!plugin.extractMetadata) continue;
411+
let extracted: Record<string, unknown> | null;
412+
try {
413+
extracted = await plugin.extractMetadata(fileContents);
414+
} catch {
415+
extracted = null;
416+
}
417+
if (extracted) {
418+
metadata = { ...(metadata ?? {}), ...extracted };
419+
}
420+
}
421+
422+
return {
423+
...base,
424+
tools: [...tools],
425+
...(metadata ? { metadata } : {}),
426+
};
393427
}
394428

395429
/** Files worth scanning — skip node_modules, dist, tests, assets. */

packages/governance/src/scanner-plugins/types.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,4 +131,23 @@ export interface ScannerPlugin {
131131
* that returns true wins. When omitted, the plugin is always applied.
132132
*/
133133
detectFramework?(fileContents: Map<string, string>): boolean;
134+
135+
/**
136+
* Optional: extract framework-specific metadata from the scanned
137+
* files. Used to surface canonical identifiers, config values, or
138+
* any other structured data the framework's manifest carries that
139+
* the generic scanner can't see.
140+
*
141+
* The most important field by convention is `externalId` — when set,
142+
* callers (e.g. governance-web's connect-repo route) use it as the
143+
* agent's canonical id at registration time so the dashboard record
144+
* matches whatever id the runtime will pass to `enforce()`. For Lua
145+
* this is `agent.agentId` from `lua.skill.yaml`.
146+
*
147+
* Plugins may also surface other framework-known fields here. The
148+
* scanner doesn't interpret them — it just merges into the result.
149+
*/
150+
extractMetadata?(
151+
fileContents: Map<string, string>,
152+
): Promise<Record<string, unknown> | null> | Record<string, unknown> | null;
134153
}

packages/governance/src/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,14 @@ export interface GovernanceAssessment {
7373

7474
/** Agent registration input */
7575
export interface AgentRegistration {
76+
/**
77+
* Optional caller-supplied agent id. When omitted, register() generates
78+
* a UUID. Provide this when binding to an existing canonical identity
79+
* — e.g. a Lua agent's `agent.agentId` from `lua.skill.yaml` — so the
80+
* dashboard-registered record matches the id the runtime will pass to
81+
* `enforce()`. The id must be unique within the org's storage.
82+
*/
83+
id?: string;
7684
name: string;
7785
framework: AgentFramework;
7886
description?: string;

0 commit comments

Comments
 (0)