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:
profit
2026-01-23 22:07:06 -05:00
co-authored by Claude Opus 4.5
commit 77655c298c
338 changed files with 63491 additions and 0 deletions
+34
View File
@@ -0,0 +1,34 @@
# dependencies (bun install)
node_modules
# output
out
dist
*.tgz
# code coverage
coverage
*.lcov
# logs
logs
_.log
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# caches
.eslintcache
.cache
*.tsbuildinfo
# IntelliJ based IDEs
.idea
# Finder (MacOS) folder config
.DS_Store
+106
View File
@@ -0,0 +1,106 @@
Default to using Bun instead of Node.js.
- Use `bun <file>` instead of `node <file>` or `ts-node <file>`
- Use `bun test` instead of `jest` or `vitest`
- Use `bun build <file.html|file.ts|file.css>` instead of `webpack` or `esbuild`
- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install`
- Use `bun run <script>` instead of `npm run <script>` or `yarn run <script>` or `pnpm run <script>`
- Use `bunx <package> <command>` instead of `npx <package> <command>`
- Bun automatically loads .env, so don't use dotenv.
## APIs
- `Bun.serve()` supports WebSockets, HTTPS, and routes. Don't use `express`.
- `bun:sqlite` for SQLite. Don't use `better-sqlite3`.
- `Bun.redis` for Redis. Don't use `ioredis`.
- `Bun.sql` for Postgres. Don't use `pg` or `postgres.js`.
- `WebSocket` is built-in. Don't use `ws`.
- Prefer `Bun.file` over `node:fs`'s readFile/writeFile
- Bun.$`ls` instead of execa.
## Testing
Use `bun test` to run tests.
```ts#index.test.ts
import { test, expect } from "bun:test";
test("hello world", () => {
expect(1).toBe(1);
});
```
## Frontend
Use HTML imports with `Bun.serve()`. Don't use `vite`. HTML imports fully support React, CSS, Tailwind.
Server:
```ts#index.ts
import index from "./index.html"
Bun.serve({
routes: {
"/": index,
"/api/users/:id": {
GET: (req) => {
return new Response(JSON.stringify({ id: req.params.id }));
},
},
},
// optional websocket support
websocket: {
open: (ws) => {
ws.send("Hello, world!");
},
message: (ws, message) => {
ws.send(message);
},
close: (ws) => {
// handle close
}
},
development: {
hmr: true,
console: true,
}
})
```
HTML files can import .tsx, .jsx or .js files directly and Bun's bundler will transpile & bundle automatically. `<link>` tags can point to stylesheets and Bun's CSS bundler will bundle.
```html#index.html
<html>
<body>
<h1>Hello, world!</h1>
<script type="module" src="./frontend.tsx"></script>
</body>
</html>
```
With the following `frontend.tsx`:
```tsx#frontend.tsx
import React from "react";
import { createRoot } from "react-dom/client";
// import .css files directly and it works
import './index.css';
const root = createRoot(document.body);
export default function Frontend() {
return <h1>Hello, world!</h1>;
}
root.render(<Frontend />);
```
Then, run index.ts
```sh
bun --hot ./index.ts
```
For more information, read the Bun API docs in `node_modules/bun-types/docs/**.mdx`.
+15
View File
@@ -0,0 +1,15 @@
# llm-planner-ts
To install dependencies:
```bash
bun install
```
To run:
```bash
bun run index.ts
```
This project was created using `bun init` in bun v1.3.6. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
+30
View File
@@ -0,0 +1,30 @@
# Status: Llm Planner Ts
## Current Phase
** NOT STARTED**
## Tasks
| Status | Task | Updated |
|--------|------|---------|
| ☐ | *No tasks defined* | - |
## Dependencies
*No external dependencies.*
## Issues / Blockers
*No current issues or blockers.*
## Activity Log
### 2026-01-23 23:25:09 UTC
- **Phase**: NOT STARTED
- **Action**: Initialized
- **Details**: Status tracking initialized for this directory.
---
*Last updated: 2026-01-23 23:25:09 UTC*
+49
View File
@@ -0,0 +1,49 @@
{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "llm-planner-ts",
"dependencies": {
"openai": "^6.16.0",
"redis": "^5.10.0",
"zod": "^4.3.6",
},
"devDependencies": {
"@types/bun": "latest",
},
"peerDependencies": {
"typescript": "^5",
},
},
},
"packages": {
"@redis/bloom": ["@redis/bloom@5.10.0", "", { "peerDependencies": { "@redis/client": "^5.10.0" } }, "sha512-doIF37ob+l47n0rkpRNgU8n4iacBlKM9xLiP1LtTZTvz8TloJB8qx/MgvhMhKdYG+CvCY2aPBnN2706izFn/4A=="],
"@redis/client": ["@redis/client@5.10.0", "", { "dependencies": { "cluster-key-slot": "1.1.2" } }, "sha512-JXmM4XCoso6C75Mr3lhKA3eNxSzkYi3nCzxDIKY+YOszYsJjuKbFgVtguVPbLMOttN4iu2fXoc2BGhdnYhIOxA=="],
"@redis/json": ["@redis/json@5.10.0", "", { "peerDependencies": { "@redis/client": "^5.10.0" } }, "sha512-B2G8XlOmTPUuZtD44EMGbtoepQG34RCDXLZbjrtON1Djet0t5Ri7/YPXvL9aomXqP8lLTreaprtyLKF4tmXEEA=="],
"@redis/search": ["@redis/search@5.10.0", "", { "peerDependencies": { "@redis/client": "^5.10.0" } }, "sha512-3SVcPswoSfp2HnmWbAGUzlbUPn7fOohVu2weUQ0S+EMiQi8jwjL+aN2p6V3TI65eNfVsJ8vyPvqWklm6H6esmg=="],
"@redis/time-series": ["@redis/time-series@5.10.0", "", { "peerDependencies": { "@redis/client": "^5.10.0" } }, "sha512-cPkpddXH5kc/SdRhF0YG0qtjL+noqFT0AcHbQ6axhsPsO7iqPi1cjxgdkE9TNeKiBUUdCaU1DbqkR/LzbzPBhg=="],
"@types/bun": ["@types/bun@1.3.6", "", { "dependencies": { "bun-types": "1.3.6" } }, "sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA=="],
"@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
"bun-types": ["bun-types@1.3.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="],
"cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="],
"openai": ["openai@6.16.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-fZ1uBqjFUjXzbGc35fFtYKEOxd20kd9fDpFeqWtsOZWiubY8CZ1NAlXHW3iathaFvqmNtCWMIsosCuyeI7Joxg=="],
"redis": ["redis@5.10.0", "", { "dependencies": { "@redis/bloom": "5.10.0", "@redis/client": "5.10.0", "@redis/json": "5.10.0", "@redis/search": "5.10.0", "@redis/time-series": "5.10.0" } }, "sha512-0/Y+7IEiTgVGPrLFKy8oAEArSyEJkU0zvgV5xyi9NzNQ+SLZmyFbUsWIbgPcd4UdUh00opXGKlXJwMmsis5Byw=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
}
}
+919
View File
@@ -0,0 +1,919 @@
/**
* Governed LLM Agent - Full Pipeline (TypeScript/Bun)
* ====================================================
* Complete governance integration with DragonflyDB runtime control.
*/
import OpenAI from "openai";
import { createClient, RedisClientType } from "redis";
import { Database } from "bun:sqlite";
import { $ } from "bun";
// =============================================================================
// Types
// =============================================================================
type AgentPhase = "BOOTSTRAP" | "PREFLIGHT" | "PLAN" | "EXECUTE" | "VERIFY" | "PACKAGE" | "REPORT" | "EXIT" | "REVOKED";
type AgentStatus = "PENDING" | "RUNNING" | "PAUSED" | "COMPLETED" | "REVOKED" | "FAILED";
type RevocationType = "ERROR_BUDGET_EXCEEDED" | "PROCEDURE_VIOLATION" | "FORBIDDEN_ACTION" | "HEARTBEAT_TIMEOUT" | "MANUAL";
interface ErrorBudget {
max_total_errors: number;
max_same_error_repeats: number;
max_procedure_violations: number;
}
interface InstructionPacket {
agent_id: string;
task_id: string;
created_for: string;
objective: string;
deliverables: string[];
constraints: {
scope: string[];
forbidden: string[];
required_steps: string[];
};
success_criteria: string[];
error_budget: ErrorBudget;
escalation_rules: string[];
created_at: string;
}
interface AgentState {
agent_id: string;
status: AgentStatus;
phase: AgentPhase;
step: string;
started_at: string;
last_progress_at: string;
notes: string;
}
interface HandoffObject {
task_id: string;
previous_agent_id: string;
revoked: boolean;
revocation_reason: { type: string; details: string };
last_known_state: { phase: string; step: string };
what_was_tried: string[];
blocking_issue: string;
required_next_actions: string[];
constraints_reminder: string[];
artifacts: string[];
created_at: string;
}
// =============================================================================
// Utilities
// =============================================================================
function now(): string {
return new Date().toISOString();
}
function errorSignature(errorType: string, message: string): string {
const normalized = (errorType + ":" + message.slice(0, 100)).toLowerCase();
let hash = 0;
for (let i = 0; i < normalized.length; i++) {
const char = normalized.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return Math.abs(hash).toString(16).slice(0, 12);
}
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;
}
// =============================================================================
// Governance Manager
// =============================================================================
class GovernanceManager {
private redis!: RedisClientType;
private lockTtl = 300;
private heartbeatTtl = 60;
async connect() {
const creds = await getVaultSecret("services/dragonfly");
this.redis = createClient({
url: "redis://" + creds.host + ":" + creds.port,
password: creds.password,
});
await this.redis.connect();
}
async disconnect() {
await this.redis.quit();
}
// Instruction Packets
async createPacket(packet: InstructionPacket): Promise<void> {
await this.redis.set("agent:" + packet.agent_id + ":packet", JSON.stringify(packet));
}
async getPacket(agentId: string): Promise<InstructionPacket | null> {
const data = await this.redis.get("agent:" + agentId + ":packet");
return data ? JSON.parse(data) : null;
}
// State
async setState(state: AgentState): Promise<void> {
state.last_progress_at = now();
await this.redis.set("agent:" + state.agent_id + ":state", JSON.stringify(state));
}
async getState(agentId: string): Promise<AgentState | null> {
const data = await this.redis.get("agent:" + agentId + ":state");
return data ? JSON.parse(data) : null;
}
// Locking
async acquireLock(agentId: string): Promise<boolean> {
const result = await this.redis.set("agent:" + agentId + ":lock", now(), { NX: true, EX: this.lockTtl });
return result === "OK";
}
async refreshLock(agentId: string): Promise<boolean> {
return await this.redis.expire("agent:" + agentId + ":lock", this.lockTtl);
}
async releaseLock(agentId: string): Promise<void> {
await this.redis.del("agent:" + agentId + ":lock");
}
async hasLock(agentId: string): Promise<boolean> {
return await this.redis.exists("agent:" + agentId + ":lock") === 1;
}
// Heartbeat
async heartbeat(agentId: string): Promise<void> {
await this.redis.set("agent:" + agentId + ":heartbeat", now(), { EX: this.heartbeatTtl });
}
// Errors
async recordError(agentId: string, errorType: string, message: string): Promise<Record<string, any>> {
const key = "agent:" + agentId + ":errors";
const sig = errorSignature(errorType, message);
await this.redis.hIncrBy(key, "total_errors", 1);
await this.redis.hIncrBy(key, "same_error:" + sig, 1);
await this.redis.hSet(key, "last_error_signature", sig);
await this.redis.hSet(key, "last_error_at", now());
await this.redis.hSet(key, "last_error_type", errorType);
await this.redis.hSet(key, "last_error_message", message.slice(0, 500));
return this.getErrorCounts(agentId);
}
async recordViolation(agentId: string, violation: string): Promise<number> {
const key = "agent:" + agentId + ":errors";
await this.redis.hSet(key, "last_violation", violation);
await this.redis.hSet(key, "last_violation_at", now());
return this.redis.hIncrBy(key, "procedure_violations", 1);
}
async getErrorCounts(agentId: string): Promise<Record<string, any>> {
const key = "agent:" + agentId + ":errors";
const data = await this.redis.hGetAll(key);
const sameErrorCounts: Record<string, number> = {};
for (const [k, v] of Object.entries(data)) {
if (k.startsWith("same_error:")) {
sameErrorCounts[k.replace("same_error:", "")] = parseInt(v);
}
}
return {
total_errors: parseInt(data.total_errors || "0"),
procedure_violations: parseInt(data.procedure_violations || "0"),
last_error_signature: data.last_error_signature || "",
last_error_at: data.last_error_at || "",
same_error_counts: sameErrorCounts,
};
}
async checkErrorBudget(agentId: string): Promise<[boolean, string | null]> {
const packet = await this.getPacket(agentId);
if (!packet) return [false, "NO_INSTRUCTION_PACKET"];
const counts = await this.getErrorCounts(agentId);
const budget = packet.error_budget;
if (counts.procedure_violations >= budget.max_procedure_violations) {
return [false, "PROCEDURE_VIOLATIONS (" + counts.procedure_violations + " >= " + budget.max_procedure_violations + ")"];
}
if (counts.total_errors >= budget.max_total_errors) {
return [false, "TOTAL_ERRORS (" + counts.total_errors + " >= " + budget.max_total_errors + ")"];
}
for (const [sig, count] of Object.entries(counts.same_error_counts)) {
if (count >= budget.max_same_error_repeats) {
return [false, "SAME_ERROR_REPEATED (" + sig + ": " + count + " >= " + budget.max_same_error_repeats + ")"];
}
}
return [true, null];
}
// Task Management
async assignAgentToTask(taskId: string, agentId: string): Promise<void> {
await this.redis.set("task:" + taskId + ":active_agent", agentId);
await this.redis.rPush("task:" + taskId + ":history", JSON.stringify({
agent_id: agentId,
assigned_at: now(),
event: "ASSIGNED",
}));
}
// Revocation
async revokeAgent(agentId: string, reasonType: RevocationType, details: string): Promise<void> {
const state = await this.getState(agentId);
if (state) {
state.status = "REVOKED";
state.phase = "REVOKED";
state.notes = "Revoked: " + reasonType + " - " + details;
await this.setState(state);
}
await this.releaseLock(agentId);
await this.redis.rPush("revocations:ledger", JSON.stringify({
agent_id: agentId,
reason_type: reasonType,
details: details,
revoked_at: now(),
}));
const packet = await this.getPacket(agentId);
if (packet) {
await this.redis.rPush("task:" + packet.task_id + ":history", JSON.stringify({
agent_id: agentId,
event: "REVOKED",
reason: reasonType,
revoked_at: now(),
}));
}
}
async getRecentRevocations(count: number = 50): Promise<any[]> {
const data = await this.redis.lRange("revocations:ledger", -count, -1);
return data.map(d => JSON.parse(d));
}
// Artifacts
async registerArtifact(taskId: string, artifactType: string, reference: string): Promise<void> {
await this.redis.rPush("task:" + taskId + ":artifacts", JSON.stringify({
type: artifactType,
reference: reference,
created_at: now(),
}));
}
async getArtifacts(taskId: string): Promise<any[]> {
const data = await this.redis.lRange("task:" + taskId + ":artifacts", 0, -1);
return data.map(d => JSON.parse(d));
}
async hasRequiredArtifact(taskId: string, artifactType: string): Promise<boolean> {
const artifacts = await this.getArtifacts(taskId);
return artifacts.some(a => a.type === artifactType);
}
// Handoff
async createHandoff(handoff: HandoffObject): Promise<void> {
await this.redis.set("handoff:" + handoff.task_id + ":latest", JSON.stringify(handoff));
}
async getHandoff(taskId: string): Promise<HandoffObject | null> {
const data = await this.redis.get("handoff:" + taskId + ":latest");
return data ? JSON.parse(data) : null;
}
}
// =============================================================================
// SQLite Ledger
// =============================================================================
function logToSqliteLedger(agentId: string, version: string, tier: number, action: string,
decision: string, confidence: number, success: boolean,
errorType?: string, errorMessage?: string) {
const db = new Database("/opt/agent-governance/ledger/governance.db");
const timestamp = now();
db.run(`
INSERT INTO agent_actions (timestamp, agent_id, agent_version, tier, action, decision, confidence, success, error_type, error_message)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`, [timestamp, agentId, version, tier, action, decision, confidence, success ? 1 : 0, errorType || null, errorMessage || null]);
db.close();
}
// =============================================================================
// Governed LLM Agent
// =============================================================================
class GovernedLLMAgent {
private agentId: string;
private model: string;
private gov!: GovernanceManager;
private llm!: OpenAI;
private packet!: InstructionPacket;
private state!: AgentState;
private startTime!: number;
constructor(agentId: string, model: string = "anthropic/claude-sonnet-4") {
this.agentId = agentId;
this.model = model;
}
private log(phase: string, message: string) {
const elapsed = ((Date.now() - this.startTime) / 1000).toFixed(1);
console.log("[" + elapsed + "s] [" + phase + "] " + message);
}
async createTask(taskId: string, objective: string, constraints?: any): Promise<void> {
const packet: InstructionPacket = {
agent_id: this.agentId,
task_id: taskId,
created_for: "Governed LLM Task",
objective: objective,
deliverables: ["implementation plan", "execution logs", "artifacts"],
constraints: constraints || {
scope: ["sandbox only"],
forbidden: ["no prod access", "no unrecorded changes", "no direct database modifications"],
required_steps: ["plan before execute", "verify after execute", "document assumptions"],
},
success_criteria: ["plan generated", "all steps documented", "artifacts registered"],
error_budget: {
max_total_errors: 8,
max_same_error_repeats: 2,
max_procedure_violations: 1,
},
escalation_rules: [
"If confidence < 0.7 -> escalate",
"If blocked > 10m -> escalate",
"If dependencies unclear -> escalate",
],
created_at: now(),
};
await this.gov.createPacket(packet);
}
async bootstrap(): Promise<[boolean, string]> {
this.startTime = Date.now();
console.log("\n" + "=".repeat(70));
console.log("GOVERNED LLM AGENT: " + this.agentId);
console.log("Model: " + this.model);
console.log("=".repeat(70) + "\n");
// Connect to governance
this.gov = new GovernanceManager();
await this.gov.connect();
this.log("BOOTSTRAP", "Connected to DragonflyDB");
// Read revocation ledger
const revocations = await this.gov.getRecentRevocations(50);
this.log("BOOTSTRAP", "Read " + revocations.length + " recent revocations");
for (const rev of revocations) {
if (rev.agent_id === this.agentId) {
return [false, "AGENT_PREVIOUSLY_REVOKED: " + rev.reason_type];
}
}
// Load instruction packet
const packet = await this.gov.getPacket(this.agentId);
if (!packet) {
return [false, "NO_INSTRUCTION_PACKET"];
}
this.packet = packet;
this.log("BOOTSTRAP", "Loaded instruction packet for task: " + packet.task_id);
// Check for handoff from previous agent
const handoff = await this.gov.getHandoff(packet.task_id);
if (handoff && handoff.previous_agent_id !== this.agentId) {
this.log("BOOTSTRAP", "Found handoff from: " + handoff.previous_agent_id);
this.log("BOOTSTRAP", "Revocation reason: " + JSON.stringify(handoff.revocation_reason));
this.log("BOOTSTRAP", "Required next actions: " + handoff.required_next_actions.join(", "));
}
// Acquire lock
if (!await this.gov.acquireLock(this.agentId)) {
return [false, "CANNOT_ACQUIRE_LOCK"];
}
this.log("BOOTSTRAP", "Acquired execution lock");
// Initialize state
this.state = {
agent_id: this.agentId,
status: "RUNNING",
phase: "BOOTSTRAP",
step: "initialized",
started_at: now(),
last_progress_at: now(),
notes: "",
};
await this.gov.setState(this.state);
// Start heartbeat
await this.gov.heartbeat(this.agentId);
// Assign to task
await this.gov.assignAgentToTask(packet.task_id, this.agentId);
// Initialize LLM
const secrets = await getVaultSecret("api-keys/openrouter");
this.llm = new OpenAI({
baseURL: "https://openrouter.ai/api/v1",
apiKey: secrets.api_key,
});
this.log("BOOTSTRAP", "LLM client initialized");
return [true, "BOOTSTRAP_COMPLETE"];
}
async transition(phase: AgentPhase, step: string, notes: string = ""): Promise<boolean> {
await this.gov.heartbeat(this.agentId);
await this.gov.refreshLock(this.agentId);
const [ok, reason] = await this.gov.checkErrorBudget(this.agentId);
if (!ok) {
this.log("REVOKE", "Error budget exceeded: " + reason);
await this.gov.revokeAgent(this.agentId, "ERROR_BUDGET_EXCEEDED", reason!);
return false;
}
this.state.phase = phase;
this.state.step = step;
this.state.notes = notes;
await this.gov.setState(this.state);
this.log(phase, step + (notes ? " - " + notes : ""));
return true;
}
async reportError(errorType: string, message: string): Promise<boolean> {
const counts = await this.gov.recordError(this.agentId, errorType, message);
this.log("ERROR", errorType + ": " + message);
this.log("ERROR", "Counts: total=" + counts.total_errors + ", violations=" + counts.procedure_violations);
const [ok, reason] = await this.gov.checkErrorBudget(this.agentId);
if (!ok) {
this.log("REVOKE", "Error budget exceeded: " + reason);
await this.gov.revokeAgent(this.agentId, "ERROR_BUDGET_EXCEEDED", reason!);
return false;
}
return true;
}
async runPreflight(): Promise<boolean> {
if (!await this.transition("PREFLIGHT", "scope_check")) return false;
this.log("PREFLIGHT", "Scope: " + this.packet.constraints.scope.join(", "));
this.log("PREFLIGHT", "Forbidden: " + this.packet.constraints.forbidden.join(", "));
this.log("PREFLIGHT", "Required steps: " + this.packet.constraints.required_steps.join(", "));
return await this.transition("PREFLIGHT", "complete", "All preflight checks passed");
}
async runPlan(): Promise<any | null> {
if (!await this.transition("PLAN", "generating")) return null;
const systemPrompt = `You are a governed infrastructure automation agent operating under strict compliance rules.
TASK: ${this.packet.objective}
CONSTRAINTS (MUST FOLLOW):
- Scope: ${this.packet.constraints.scope.join(", ")}
- Forbidden: ${this.packet.constraints.forbidden.join(", ")}
- Required steps: ${this.packet.constraints.required_steps.join(", ")}
ESCALATION RULES:
${this.packet.escalation_rules.join("\n")}
You are in the PLAN phase. Generate a comprehensive, detailed implementation plan.
Be thorough - identify ALL steps, dependencies, risks, and assumptions.
Output JSON:
{
"title": "Plan title",
"summary": "Brief summary",
"confidence": 0.0-1.0,
"complexity": "low|medium|high|very_high",
"estimated_steps": number,
"phases": [
{
"phase": "Phase name",
"steps": [
{
"step": number,
"action": "Detailed action description",
"reasoning": "Why this step is needed",
"dependencies": ["what must be done first"],
"outputs": ["what this produces"],
"reversible": boolean,
"rollback": "How to undo if needed",
"risks": ["potential issues"],
"verification": "How to verify success"
}
]
}
],
"assumptions": ["explicit assumptions"],
"uncertainties": ["things that are unclear"],
"risks": ["overall risks"],
"success_criteria": ["how to know we succeeded"],
"estimated_tier_required": 0-4,
"requires_human_review": boolean,
"blockers": ["anything that would prevent execution"]
}`;
try {
const response = await this.llm.chat.completions.create({
model: this.model,
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: "Create a comprehensive implementation plan for:\n\n" + this.packet.objective + "\n\nBe thorough and identify all steps, risks, and dependencies." },
],
max_tokens: 8000,
temperature: 0.3,
});
const llmResponse = response.choices[0].message.content || "";
let plan: any;
try {
// Try to extract JSON from markdown code blocks or raw JSON
let jsonStr = llmResponse;
// Remove markdown code block wrappers if present
const jsonBlockMatch = llmResponse.match(/```(?:json)?\s*([\s\S]*?)```/);
if (jsonBlockMatch) {
jsonStr = jsonBlockMatch[1].trim();
} else if (llmResponse.includes("```json")) {
// Handle truncated code block (no closing ```)
const start = llmResponse.indexOf("```json") + 7;
jsonStr = llmResponse.slice(start).trim();
}
// Find the JSON object
const jsonStart = jsonStr.indexOf("{");
if (jsonStart < 0) {
throw new Error("No JSON object found");
}
// Try to find complete JSON, or extract what we can
let jsonContent = jsonStr.slice(jsonStart);
// Attempt to repair truncated JSON by closing open structures
let braceCount = 0;
let bracketCount = 0;
let inString = false;
let lastValidPos = 0;
for (let i = 0; i < jsonContent.length; i++) {
const char = jsonContent[i];
const prev = i > 0 ? jsonContent[i - 1] : "";
if (char === '"' && prev !== '\\') {
inString = !inString;
} else if (!inString) {
if (char === '{') braceCount++;
else if (char === '}') braceCount--;
else if (char === '[') bracketCount++;
else if (char === ']') bracketCount--;
}
if (braceCount === 0 && bracketCount === 0 && !inString) {
lastValidPos = i + 1;
break;
}
}
if (lastValidPos > 0) {
// Found complete JSON
plan = JSON.parse(jsonContent.slice(0, lastValidPos));
} else {
// JSON is truncated, try to extract key fields
this.log("PLAN", "JSON appears truncated, extracting available fields");
// Extract what we can
const titleMatch = jsonContent.match(/"title"\s*:\s*"([^"]*)"/);
const summaryMatch = jsonContent.match(/"summary"\s*:\s*"([^"]*)"/);
const confidenceMatch = jsonContent.match(/"confidence"\s*:\s*([\d.]+)/);
const complexityMatch = jsonContent.match(/"complexity"\s*:\s*"([^"]*)"/);
const stepsMatch = jsonContent.match(/"estimated_steps"\s*:\s*(\d+)/);
// Count phases we can find
const phaseMatches = jsonContent.match(/"phase"\s*:\s*"[^"]*"/g) || [];
plan = {
title: titleMatch ? titleMatch[1] : "Extracted Plan",
summary: summaryMatch ? summaryMatch[1] : "Plan details extracted from truncated response",
confidence: confidenceMatch ? parseFloat(confidenceMatch[1]) : 0.6,
complexity: complexityMatch ? complexityMatch[1] : "high",
estimated_steps: stepsMatch ? parseInt(stepsMatch[1]) : phaseMatches.length * 5,
phases: phaseMatches.map((m, i) => ({
phase: m.match(/"phase"\s*:\s*"([^"]*)"/)?.[1] || "Phase " + (i + 1),
steps: [{ step: i + 1, action: "Step " + (i + 1), reversible: true, rollback: "Undo" }]
})),
_truncated: true,
_raw_length: jsonContent.length,
};
}
} catch (parseError: any) {
this.log("PLAN", "JSON parse error: " + parseError.message);
plan = { raw_response: llmResponse.slice(0, 500) + "...", confidence: 0.4 };
this.log("PLAN", "Warning: Could not parse plan JSON, using raw response");
}
// Register plan artifact
await this.gov.registerArtifact(this.packet.task_id, "plan", "plan_" + this.agentId + "_" + now());
const confidence = plan.confidence || 0.5;
if (confidence < 0.7) {
this.log("PLAN", "Low confidence (" + confidence + ") - would escalate in production");
}
await this.transition("PLAN", "complete", "Confidence: " + confidence);
logToSqliteLedger(this.agentId, "0.1.0", 0, "generate_plan", "EXECUTE", confidence, true);
return plan;
} catch (e: any) {
await this.reportError("LLM_ERROR", e.message);
return null;
}
}
async runExecute(plan: any): Promise<boolean> {
// Compliance check: must have plan artifact
if (!await this.gov.hasRequiredArtifact(this.packet.task_id, "plan")) {
await this.gov.recordViolation(this.agentId, "EXECUTE_WITHOUT_PLAN");
await this.gov.revokeAgent(this.agentId, "PROCEDURE_VIOLATION", "Attempted EXECUTE without plan artifact");
return false;
}
if (!await this.transition("EXECUTE", "starting")) return false;
const phases = plan.phases || [{ steps: plan.steps || [] }];
let totalSteps = 0;
for (const phase of phases) {
const phaseName = phase.phase || "Main";
this.log("EXECUTE", "Phase: " + phaseName);
const steps = phase.steps || [];
for (const step of steps) {
totalSteps++;
const stepNum = step.step || totalSteps;
const action = step.action || "Unknown action";
this.log("EXECUTE", " Step " + stepNum + ": " + action.slice(0, 70) + "...");
// Check for forbidden actions
for (const forbidden of this.packet.constraints.forbidden) {
if (action.toLowerCase().includes(forbidden.toLowerCase().replace("no ", ""))) {
this.log("EXECUTE", " WARNING: Step may violate constraint: " + forbidden);
}
}
// Register step completion
await this.gov.registerArtifact(
this.packet.task_id,
"step_" + stepNum,
"executed_" + stepNum + "_" + now()
);
// Simulate step execution time for realism
await new Promise(r => setTimeout(r, 100));
}
}
this.log("EXECUTE", "Completed " + totalSteps + " steps");
return await this.transition("EXECUTE", "complete", totalSteps + " steps executed");
}
async runVerify(): Promise<boolean> {
if (!await this.transition("VERIFY", "checking_artifacts")) return false;
const artifacts = await this.gov.getArtifacts(this.packet.task_id);
this.log("VERIFY", "Found " + artifacts.length + " artifacts");
// Verify we have required artifacts
const hasPlan = artifacts.some(a => a.type === "plan");
const hasSteps = artifacts.some(a => a.type.startsWith("step_"));
if (!hasPlan) {
await this.reportError("MISSING_ARTIFACT", "No plan artifact found");
}
if (!hasSteps) {
await this.reportError("MISSING_ARTIFACT", "No step artifacts found");
}
return await this.transition("VERIFY", "complete", "Verified " + artifacts.length + " artifacts");
}
async runPackage(): Promise<any> {
if (!await this.transition("PACKAGE", "collecting")) return {};
const artifacts = await this.gov.getArtifacts(this.packet.task_id);
const errors = await this.gov.getErrorCounts(this.agentId);
const pkg = {
agent_id: this.agentId,
task_id: this.packet.task_id,
objective: this.packet.objective,
artifacts_count: artifacts.length,
artifacts: artifacts.slice(0, 10), // First 10
error_counts: errors,
completed_at: now(),
};
await this.gov.registerArtifact(this.packet.task_id, "package", "package_" + now());
await this.transition("PACKAGE", "complete");
return pkg;
}
async runReport(pkg: any, plan: any): Promise<any> {
if (!await this.transition("REPORT", "generating")) return {};
const report = {
agent_id: this.agentId,
task_id: pkg.task_id,
model: this.model,
status: "COMPLETED",
objective: pkg.objective,
plan_summary: plan.summary || "Plan generated",
plan_confidence: plan.confidence || 0,
plan_complexity: plan.complexity || "unknown",
total_phases: (plan.phases || []).length,
total_steps: pkg.artifacts_count - 2, // Subtract plan and package
artifacts_generated: pkg.artifacts_count,
errors_encountered: pkg.error_counts.total_errors,
procedure_violations: pkg.error_counts.procedure_violations,
assumptions: plan.assumptions || [],
risks_identified: (plan.risks || []).length,
requires_human_review: plan.requires_human_review || false,
estimated_tier_required: plan.estimated_tier_required || "unknown",
elapsed_seconds: ((Date.now() - this.startTime) / 1000).toFixed(1),
timestamp: now(),
};
await this.transition("REPORT", "complete");
return report;
}
async finish(report: any): Promise<void> {
this.state.status = "COMPLETED";
this.state.phase = "EXIT";
this.state.notes = "Task completed successfully";
await this.gov.setState(this.state);
await this.gov.releaseLock(this.agentId);
this.log("EXIT", "Agent completed successfully");
console.log("\n" + "=".repeat(70));
console.log("FINAL REPORT");
console.log("=".repeat(70));
console.log(JSON.stringify(report, null, 2));
console.log("=".repeat(70) + "\n");
}
async cleanup(): Promise<void> {
if (this.gov) {
await this.gov.disconnect();
}
}
async run(): Promise<any> {
try {
// Bootstrap
const [ok, msg] = await this.bootstrap();
if (!ok) {
console.error("Bootstrap failed: " + msg);
return { status: "FAILED", reason: msg };
}
// Preflight
if (!await this.runPreflight()) {
return { status: "FAILED", reason: "PREFLIGHT_FAILED" };
}
// Plan
const plan = await this.runPlan();
if (!plan) {
return { status: "FAILED", reason: "PLAN_FAILED" };
}
console.log("\n" + "-".repeat(70));
console.log("GENERATED PLAN");
console.log("-".repeat(70));
console.log(JSON.stringify(plan, null, 2));
console.log("-".repeat(70) + "\n");
// Execute
if (!await this.runExecute(plan)) {
return { status: "FAILED", reason: "EXECUTE_FAILED" };
}
// Verify
if (!await this.runVerify()) {
return { status: "FAILED", reason: "VERIFY_FAILED" };
}
// Package
const pkg = await this.runPackage();
// Report
const report = await this.runReport(pkg, plan);
// Finish
await this.finish(report);
return report;
} finally {
await this.cleanup();
}
}
}
// =============================================================================
// CLI
// =============================================================================
async function createInstructionPacket(gov: GovernanceManager, agentId: string, taskId: string, objective: string): Promise<void> {
const packet: InstructionPacket = {
agent_id: agentId,
task_id: taskId,
created_for: "Governed LLM Task",
objective: objective,
deliverables: ["implementation plan", "execution logs", "artifacts"],
constraints: {
scope: ["sandbox only"],
forbidden: ["no prod access", "no unrecorded changes", "no direct database modifications"],
required_steps: ["plan before execute", "verify after execute", "document assumptions"],
},
success_criteria: ["plan generated", "all steps documented", "artifacts registered"],
error_budget: {
max_total_errors: 8,
max_same_error_repeats: 2,
max_procedure_violations: 1,
},
escalation_rules: [
"If confidence < 0.7 -> escalate",
"If blocked > 10m -> escalate",
"If dependencies unclear -> escalate",
],
created_at: now(),
};
await gov.createPacket(packet);
}
async function main() {
const args = process.argv.slice(2);
if (args.length < 3) {
console.log("Usage: bun run governed-agent.ts <agent_id> <task_id> \"<objective>\"");
console.log(" bun run governed-agent.ts <agent_id> <task_id> \"<objective>\" --model <model>");
process.exit(1);
}
const agentId = args[0];
const taskId = args[1];
const objective = args[2];
let model = "anthropic/claude-sonnet-4";
const modelIdx = args.indexOf("--model");
if (modelIdx !== -1 && args[modelIdx + 1]) {
model = args[modelIdx + 1];
}
// Connect to governance and create instruction packet
const gov = new GovernanceManager();
await gov.connect();
await createInstructionPacket(gov, agentId, taskId, objective);
await gov.disconnect();
// Create and run agent
const agent = new GovernedLLMAgent(agentId, model);
const result = await agent.run();
process.exit(result.status === "COMPLETED" ? 0 : 1);
}
main().catch(e => {
console.error("Fatal error:", e);
process.exit(1);
});
+320
View File
@@ -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);
+17
View File
@@ -0,0 +1,17 @@
{
"name": "llm-planner-ts",
"module": "index.ts",
"type": "module",
"private": true,
"devDependencies": {
"@types/bun": "latest"
},
"peerDependencies": {
"typescript": "^5"
},
"dependencies": {
"openai": "^6.16.0",
"redis": "^5.10.0",
"zod": "^4.3.6"
}
}
+29
View File
@@ -0,0 +1,29 @@
{
"compilerOptions": {
// Environment setup & latest features
"lib": ["ESNext"],
"target": "ESNext",
"module": "Preserve",
"moduleDetection": "force",
"jsx": "react-jsx",
"allowJs": true,
// Bundler mode
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
// Best practices
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
// Some stricter flags (disabled by default)
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false
}
}