Skip to content

Commit f78ab85

Browse files
Merge branch 'main' into feat/rust-go-publishing
2 parents ab9572b + c63fb93 commit f78ab85

File tree

3 files changed

+98
-1
lines changed

3 files changed

+98
-1
lines changed

packages/agent-mesh/sdks/typescript/src/trust.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
3+
import * as fs from 'fs';
34
import { AgentIdentity } from './identity';
45
import {
56
TrustConfig,
@@ -27,15 +28,20 @@ interface AgentTrustState {
2728
* with configurable decay.
2829
*/
2930
export class TrustManager {
30-
private readonly config: Required<TrustConfig>;
31+
private readonly config: Required<Omit<TrustConfig, 'persistPath'>>;
3132
private readonly agents: Map<string, AgentTrustState> = new Map();
33+
private readonly persistPath?: string;
3234

3335
constructor(config?: TrustConfig) {
3436
this.config = {
3537
initialScore: config?.initialScore ?? 0.5,
3638
decayFactor: config?.decayFactor ?? 0.95,
3739
thresholds: { ...DEFAULT_THRESHOLDS, ...config?.thresholds },
3840
};
41+
this.persistPath = config?.persistPath;
42+
if (this.persistPath) {
43+
this.loadFromDisk();
44+
}
3945
}
4046

4147
/** Verify a peer agent's identity and return a trust result. */
@@ -83,6 +89,7 @@ export class TrustManager {
8389
state.successes += 1;
8490
state.score = Math.min(1, state.score + reward);
8591
state.lastUpdate = Date.now();
92+
this.saveToDisk();
8693
}
8794

8895
/** Record a failed interaction with an agent. */
@@ -92,6 +99,7 @@ export class TrustManager {
9299
state.failures += 1;
93100
state.score = Math.max(0, state.score - penalty);
94101
state.lastUpdate = Date.now();
102+
this.saveToDisk();
95103
}
96104

97105
// ── Private helpers ──
@@ -133,4 +141,30 @@ export class TrustManager {
133141
if (total === 0) return this.config.initialScore;
134142
return Math.round((state.successes / total) * 1000) / 1000;
135143
}
144+
145+
private saveToDisk(): void {
146+
if (!this.persistPath) return;
147+
try {
148+
const data: Record<string, AgentTrustState> = {};
149+
for (const [key, value] of this.agents) {
150+
data[key] = value;
151+
}
152+
fs.writeFileSync(this.persistPath, JSON.stringify(data), 'utf-8');
153+
} catch {
154+
// best-effort: ignore write errors
155+
}
156+
}
157+
158+
private loadFromDisk(): void {
159+
if (!this.persistPath) return;
160+
try {
161+
const raw = fs.readFileSync(this.persistPath, 'utf-8');
162+
const data = JSON.parse(raw) as Record<string, AgentTrustState>;
163+
for (const [key, value] of Object.entries(data)) {
164+
this.agents.set(key, value);
165+
}
166+
} catch {
167+
// best-effort: ignore missing or corrupt files
168+
}
169+
}
136170
}

packages/agent-mesh/sdks/typescript/src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ export interface TrustConfig {
3636
trusted: number;
3737
verified: number;
3838
};
39+
/** Optional file path for persisting trust scores across restarts */
40+
persistPath?: string;
3941
}
4042

4143
export type TrustTier = 'Untrusted' | 'Provisional' | 'Trusted' | 'Verified';
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
import * as fs from 'fs';
4+
import * as os from 'os';
5+
import * as path from 'path';
6+
import * as crypto from 'crypto';
7+
import { TrustManager } from '../src/trust';
8+
9+
function tempFilePath(): string {
10+
return path.join(os.tmpdir(), `trust-${crypto.randomUUID()}.json`);
11+
}
12+
13+
afterEach(() => {
14+
// cleanup is best-effort; individual tests also clean up
15+
});
16+
17+
describe('TrustManager persistence', () => {
18+
it('persists scores across instances', () => {
19+
const file = tempFilePath();
20+
try {
21+
const tm1 = new TrustManager({ initialScore: 0.5, persistPath: file });
22+
tm1.recordSuccess('agent-a', 0.1);
23+
tm1.recordFailure('agent-b', 0.2);
24+
25+
// Second instance should load persisted state
26+
const tm2 = new TrustManager({ initialScore: 0.5, persistPath: file });
27+
expect(tm2.getTrustScore('agent-a').overall).toBeGreaterThan(0.5);
28+
expect(tm2.getTrustScore('agent-b').overall).toBeLessThan(0.5);
29+
} finally {
30+
try { fs.unlinkSync(file); } catch { /* ignore */ }
31+
}
32+
});
33+
34+
it('handles missing file gracefully', () => {
35+
const file = tempFilePath(); // does not exist
36+
expect(() => {
37+
const tm = new TrustManager({ initialScore: 0.5, persistPath: file });
38+
expect(tm.getTrustScore('agent-x').overall).toBe(0.5);
39+
}).not.toThrow();
40+
});
41+
42+
it('handles corrupt file gracefully', () => {
43+
const file = tempFilePath();
44+
try {
45+
fs.writeFileSync(file, '<<<not json>>>', 'utf-8');
46+
expect(() => {
47+
const tm = new TrustManager({ initialScore: 0.5, persistPath: file });
48+
expect(tm.getTrustScore('agent-x').overall).toBe(0.5);
49+
}).not.toThrow();
50+
} finally {
51+
try { fs.unlinkSync(file); } catch { /* ignore */ }
52+
}
53+
});
54+
55+
it('does not persist when persistPath is not set', () => {
56+
const tm = new TrustManager({ initialScore: 0.5 });
57+
tm.recordSuccess('agent-a', 0.1);
58+
// No error, no file created — just verifying the code path works
59+
expect(tm.getTrustScore('agent-a').overall).toBeGreaterThan(0.5);
60+
});
61+
});

0 commit comments

Comments
 (0)