Initial commit: Agent Governance System Phase 8
Phase 8 Production Hardening with complete governance infrastructure: - Vault integration with tiered policies (T0-T4) - DragonflyDB state management - SQLite audit ledger - Pipeline DSL and templates - Promotion/revocation engine - Checkpoint system for session persistence - Health manager and circuit breaker for fault tolerance - GitHub/Slack integrations - Architectural test pipeline with bug watcher, suggestion engine, council review - Multi-agent chaos testing framework Test Results: - Governance tests: 68/68 passing - E2E workflow: 16/16 passing - Phase 2 Vault: 14/14 passing - Integration tests: 27/27 passing Coverage: 57.6% average across 12 phases Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,320 @@
|
||||
/**
|
||||
* LLM Planner Agent - TypeScript/Bun Version
|
||||
* Tier 0 Observer with OpenRouter LLM capabilities
|
||||
*/
|
||||
|
||||
import OpenAI from "openai";
|
||||
import { z } from "zod";
|
||||
import { $ } from "bun";
|
||||
import { Database } from "bun:sqlite";
|
||||
|
||||
// =============================================================================
|
||||
// Agent Metadata
|
||||
// =============================================================================
|
||||
|
||||
const AGENT_METADATA = {
|
||||
agent_id: "llm-planner-ts-001",
|
||||
agent_role: "observer",
|
||||
owner: "system",
|
||||
version: "0.1.0",
|
||||
tier: 0,
|
||||
allowed_side_effects: ["read_docs", "read_inventory", "read_logs", "generate_plan", "llm_inference"],
|
||||
forbidden_actions: ["ssh", "create_vm", "modify_vm", "delete_vm", "run_ansible", "run_terraform"],
|
||||
confidence_threshold: 0.7,
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// Types
|
||||
// =============================================================================
|
||||
|
||||
interface TaskRequest {
|
||||
task_type: "plan" | "analyze";
|
||||
description: string;
|
||||
context?: Record<string, any>;
|
||||
constraints?: string[];
|
||||
}
|
||||
|
||||
interface AgentOutput {
|
||||
agent_id: string;
|
||||
version: string;
|
||||
timestamp: string;
|
||||
action: string;
|
||||
decision: "EXECUTE" | "SKIP" | "ESCALATE" | "ERROR";
|
||||
confidence: number;
|
||||
assumptions: string[];
|
||||
side_effects: { type: string; target: string; reversible: boolean }[];
|
||||
notes_for_humans: string;
|
||||
llm_model?: string;
|
||||
llm_response?: string;
|
||||
plan?: Record<string, any>;
|
||||
error?: {
|
||||
type: string;
|
||||
message: string;
|
||||
triggering_input: string;
|
||||
recommended_action: string;
|
||||
};
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Vault Client
|
||||
// =============================================================================
|
||||
|
||||
async function getVaultSecret(path: string): Promise<Record<string, any>> {
|
||||
const initKeys = await Bun.file("/opt/vault/init-keys.json").json();
|
||||
const token = initKeys.root_token;
|
||||
const result = await $`curl -sk -H "X-Vault-Token: ${token}" https://127.0.0.1:8200/v1/secret/data/${path}`.json();
|
||||
return result.data.data;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Ledger
|
||||
// =============================================================================
|
||||
|
||||
function logToLedger(output: AgentOutput, success: boolean) {
|
||||
const db = new Database("/opt/agent-governance/ledger/governance.db");
|
||||
|
||||
db.run(`
|
||||
INSERT INTO agent_actions (timestamp, agent_id, agent_version, tier, action, decision, confidence, success, error_type, error_message)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`, [
|
||||
output.timestamp,
|
||||
output.agent_id,
|
||||
output.version,
|
||||
AGENT_METADATA.tier,
|
||||
output.action,
|
||||
output.decision,
|
||||
output.confidence,
|
||||
success ? 1 : 0,
|
||||
output.error?.type ?? null,
|
||||
output.error?.message ?? null,
|
||||
]);
|
||||
|
||||
db.run(`
|
||||
INSERT INTO agent_metrics (agent_id, current_tier, total_runs, last_active_at, compliant_runs, consecutive_compliant)
|
||||
VALUES (?, ?, 1, ?, ?, ?)
|
||||
ON CONFLICT(agent_id) DO UPDATE SET
|
||||
total_runs = total_runs + 1,
|
||||
compliant_runs = CASE WHEN ? = 1 THEN compliant_runs + 1 ELSE compliant_runs END,
|
||||
consecutive_compliant = CASE WHEN ? = 1 THEN consecutive_compliant + 1 ELSE 0 END,
|
||||
last_active_at = ?,
|
||||
updated_at = ?
|
||||
`, [
|
||||
output.agent_id, AGENT_METADATA.tier, output.timestamp,
|
||||
success ? 1 : 0, success ? 1 : 0,
|
||||
success ? 1 : 0, success ? 1 : 0,
|
||||
output.timestamp, output.timestamp,
|
||||
]);
|
||||
|
||||
db.close();
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Agent
|
||||
// =============================================================================
|
||||
|
||||
class LLMPlannerAgent {
|
||||
private llm!: OpenAI;
|
||||
private model: string;
|
||||
|
||||
constructor(model: string = "anthropic/claude-sonnet-4") {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
async init() {
|
||||
const secrets = await getVaultSecret("api-keys/openrouter");
|
||||
this.llm = new OpenAI({
|
||||
baseURL: "https://openrouter.ai/api/v1",
|
||||
apiKey: secrets.api_key,
|
||||
});
|
||||
console.log("[INIT] Agent " + AGENT_METADATA.agent_id + " v" + AGENT_METADATA.version);
|
||||
console.log("[INIT] Model: " + this.model);
|
||||
}
|
||||
|
||||
private now(): string {
|
||||
return new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
||||
}
|
||||
|
||||
private validateAction(action: string): boolean {
|
||||
if (AGENT_METADATA.forbidden_actions.includes(action)) return false;
|
||||
if (AGENT_METADATA.allowed_side_effects.includes(action)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
async generatePlan(request: TaskRequest): Promise<AgentOutput> {
|
||||
const action = "generate_plan";
|
||||
|
||||
if (!this.validateAction(action)) {
|
||||
const output: AgentOutput = {
|
||||
agent_id: AGENT_METADATA.agent_id,
|
||||
version: AGENT_METADATA.version,
|
||||
timestamp: this.now(),
|
||||
action,
|
||||
decision: "ERROR",
|
||||
confidence: 0,
|
||||
assumptions: [],
|
||||
side_effects: [],
|
||||
notes_for_humans: "",
|
||||
error: {
|
||||
type: "FORBIDDEN_ACTION",
|
||||
message: "Action '" + action + "' not permitted",
|
||||
triggering_input: request.description,
|
||||
recommended_action: "Escalate to higher tier",
|
||||
},
|
||||
};
|
||||
logToLedger(output, false);
|
||||
return output;
|
||||
}
|
||||
|
||||
// Get context
|
||||
let contextInfo = "Context unavailable";
|
||||
try {
|
||||
const inventory = await getVaultSecret("inventory/proxmox");
|
||||
contextInfo = "Cluster: " + inventory.cluster + ", Pools: " + inventory.pools;
|
||||
} catch {}
|
||||
|
||||
const systemPrompt = `You are a Tier 0 Observer agent. Generate implementation plans only - you CANNOT execute.
|
||||
Output JSON: {"title":"","summary":"","confidence":0.0-1.0,"assumptions":[],"steps":[{"step":1,"action":"","reversible":true,"rollback":""}],"estimated_tier_required":0-4,"risks":[]}
|
||||
Context: ${contextInfo}`;
|
||||
|
||||
try {
|
||||
const response = await this.llm.chat.completions.create({
|
||||
model: this.model,
|
||||
messages: [
|
||||
{ role: "system", content: systemPrompt },
|
||||
{ role: "user", content: "Task: " + request.description + "\nGenerate a plan." },
|
||||
],
|
||||
max_tokens: 2000,
|
||||
temperature: 0.3,
|
||||
});
|
||||
|
||||
const llmResponse = response.choices[0].message.content || "";
|
||||
|
||||
let plan: Record<string, any> = {};
|
||||
let confidence = 0.5;
|
||||
|
||||
try {
|
||||
const jsonMatch = llmResponse.match(/\{[\s\S]*\}/);
|
||||
if (jsonMatch) {
|
||||
plan = JSON.parse(jsonMatch[0]);
|
||||
confidence = plan.confidence || 0.5;
|
||||
}
|
||||
} catch {
|
||||
plan = { raw_response: llmResponse };
|
||||
}
|
||||
|
||||
const tierRequired = plan.estimated_tier_required || "unknown";
|
||||
|
||||
const output: AgentOutput = {
|
||||
agent_id: AGENT_METADATA.agent_id,
|
||||
version: AGENT_METADATA.version,
|
||||
timestamp: this.now(),
|
||||
action,
|
||||
decision: confidence >= AGENT_METADATA.confidence_threshold ? "EXECUTE" : "ESCALATE",
|
||||
confidence,
|
||||
assumptions: plan.assumptions || [],
|
||||
side_effects: [{ type: "llm_inference", target: this.model, reversible: true }],
|
||||
notes_for_humans: "Tier required: " + tierRequired,
|
||||
llm_model: this.model,
|
||||
plan,
|
||||
};
|
||||
|
||||
logToLedger(output, true);
|
||||
return output;
|
||||
|
||||
} catch (e: any) {
|
||||
const output: AgentOutput = {
|
||||
agent_id: AGENT_METADATA.agent_id,
|
||||
version: AGENT_METADATA.version,
|
||||
timestamp: this.now(),
|
||||
action,
|
||||
decision: "ERROR",
|
||||
confidence: 0,
|
||||
assumptions: [],
|
||||
side_effects: [],
|
||||
notes_for_humans: "",
|
||||
error: {
|
||||
type: "LLM_ERROR",
|
||||
message: e.message,
|
||||
triggering_input: request.description,
|
||||
recommended_action: "Check API connectivity",
|
||||
},
|
||||
};
|
||||
logToLedger(output, false);
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
async run(request: TaskRequest): Promise<AgentOutput> {
|
||||
console.log("[TASK] " + request.task_type + ": " + request.description.slice(0, 80) + "...");
|
||||
|
||||
if (request.task_type === "plan") {
|
||||
return this.generatePlan(request);
|
||||
}
|
||||
|
||||
const output: AgentOutput = {
|
||||
agent_id: AGENT_METADATA.agent_id,
|
||||
version: AGENT_METADATA.version,
|
||||
timestamp: this.now(),
|
||||
action: request.task_type,
|
||||
decision: "ERROR",
|
||||
confidence: 0,
|
||||
assumptions: [],
|
||||
side_effects: [],
|
||||
notes_for_humans: "",
|
||||
error: {
|
||||
type: "UNSUPPORTED",
|
||||
message: "Task type '" + request.task_type + "' not implemented in TS version",
|
||||
triggering_input: request.description,
|
||||
recommended_action: "Use 'plan' task type or Python agent",
|
||||
},
|
||||
};
|
||||
logToLedger(output, false);
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// CLI
|
||||
// =============================================================================
|
||||
|
||||
async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
if (args.length < 2) {
|
||||
console.log("Usage: bun run index.ts <plan|analyze> \"description\"");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const taskType = args[0] as "plan" | "analyze";
|
||||
const description = args[1];
|
||||
|
||||
const agent = new LLMPlannerAgent();
|
||||
await agent.init();
|
||||
|
||||
const output = await agent.run({ task_type: taskType, description });
|
||||
|
||||
console.log("\n" + "=".repeat(60));
|
||||
console.log("Decision: " + output.decision);
|
||||
console.log("Confidence: " + output.confidence);
|
||||
console.log("=".repeat(60));
|
||||
|
||||
if (output.plan) {
|
||||
console.log("\nPLAN:");
|
||||
console.log(JSON.stringify(output.plan, null, 2));
|
||||
}
|
||||
|
||||
if (output.error) {
|
||||
console.log("\nERROR:");
|
||||
console.log(" Type: " + output.error.type);
|
||||
console.log(" Message: " + output.error.message);
|
||||
}
|
||||
|
||||
if (output.assumptions && output.assumptions.length > 0) {
|
||||
console.log("\nASSUMPTIONS:");
|
||||
output.assumptions.forEach(a => console.log(" - " + a));
|
||||
}
|
||||
|
||||
console.log("\n" + "=".repeat(60));
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
Reference in New Issue
Block a user